// ========== config ==========
const GAODE_KEY = '21b3a11e940d284eecb6b66bdd090fcc'; // ←←← 换成自己的
const MAX_Z = 18; // 高德最大支持 18 级
const TILE_SIZE = 256; // 标准 256px 瓦片
// =================================
/* 降级算法:把高 z 映射到 maxZoom,并返回缩放/偏移量 */
function downgradeTile(x, y, z, maxZoom) {
if (z <= maxZoom) {
return { srcX: x, srcY: y, srcZ: z, scale: 1, dx: 0, dy: 0 };
}
const scale = 2 ** (z - maxZoom);
const srcX = Math.floor(x / scale);
const srcY = Math.floor(y / scale);
const offsetX = (x % scale) * TILE_SIZE / scale;
const offsetY = (y % scale) * TILE_SIZE / scale;
return {
srcX, srcY, srcZ: maxZoom,
scale,
dx: -offsetX * scale,
dy: -offsetY * scale
};
}
/* 高德使用 4 个域名做负载均衡 */
let subDomain = 0;
function nextSub() { subDomain = (subDomain + 1) % 4; return `wprd0${subDomain + 1}`; }
/* 生成高德瓦片地址
layer: vec 矢量底图 | cva 矢量注记 | img 影像底图 | cia 影像注记
*/
function buildGaodeUrl(x, y, z, layer) {
return `https://${nextSub()}.is.autonavi.com/appmaptile?style=7&tiletype=${layer}&x=${x}&y=${y}&z=${z}&lang=zh_cn&size=1&scale=1&key=${GAODE_KEY}`;
}
/* 记录已降级过的 key,防止重复绘制 */
const doneSet = new Set();
/* 创建注记层 <img> */
function createCvaImg(cvaSrc, templateImg) {
const img = new Image();
img.src = cvaSrc;
img.className = 'leaflet-tile';
img.style.cssText = templateImg.style.cssText; // 复制位置/变换
img.style.mixBlendMode = 'unset';
img.onload = () => img.classList.add('leaflet-tile-loaded');
return img;
}
/* 核心替换函数 */
function transformCartoImg(img, extraImgs) {
const src = img.src;
if (!src.includes('basemaps.cartocdn.com')) return;
const m = src.match(/\/(\d+)\/(\d+)\/(\d+)(?:@2x)?\.png/);
if (!m) return;
let [, z, x, y] = m.map(Number);
/* 1. 需要降级的情况 */
if (z > MAX_Z) {
const { srcX, srcY, srcZ, scale, dx, dy } = downgradeTile(x, y, z, MAX_Z);
const key = `${srcX},${srcY},${srcZ},${z}`;
if (doneSet.has(key)) { // 已处理过,直接隐藏
img.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
img.style.display = 'none';
return;
}
doneSet.add(key);
img.downgradeKey = key;
x = srcX; y = srcY; z = srcZ;
/* 叠加缩放+偏移到原有 transform 上 */
let t = img.style.transform || '';
if (t.includes('translate3d(')) {
const [, tx, ty] = t.match(/translate3d\(([^,]+),\s*([^,]+)/) || [];
if (tx !== undefined) {
const nx = parseFloat(tx) + dx;
const ny = parseFloat(ty) + dy;
t = t.replace(/translate3d\([^)]+\)/, `translate3d(${nx}px, ${ny}px, 0px)`);
}
}
if (!t.includes('scale(')) t += ` scale(${scale})`;
img.style.transform = t;
img.style.transformOrigin = 'top left';
img.style.width = TILE_SIZE + 'px';
img.style.height = TILE_SIZE + 'px';
}
/* 2. 组装高德地址 */
const vecSrc = buildGaodeUrl(x, y, z, 'vec');
const cvaSrc = buildGaodeUrl(x, y, z, 'cva');
/* 3. 注入 DOM */
if (extraImgs) { // 来自 appendChild 拦截
const cvaImg = createCvaImg(cvaSrc, img);
cvaImg.style.transformOrigin = 'top left';
extraImgs.push(cvaImg);
img.src = vecSrc;
} else { // 直接修改
img.style.backgroundImage = `url("${vecSrc}")`;
img.style.backgroundSize = `${TILE_SIZE}px ${TILE_SIZE}px`;
img.src = cvaSrc;
}
console.debug('[高德替换]', src, '→', vecSrc, '+注记');
}
/* ========== DOM 监听部分(与原脚本一致,仅函数名替换) ========== */
function initDomObserver() {
const _appendChild = Element.prototype.appendChild;
function onAdd(node) {
if (node.nodeType !== 1) return;
if (node.classList.contains('leaflet-layer')) {
node.appendChild = function (child) {
if (child.classList?.contains('leaflet-tile-container')) {
child.querySelectorAll('img').forEach(transformCartoImg);
child.appendChild = function (frag) {
const addArr = [];
[...frag.children].forEach(n => n.tagName === 'IMG' && transformCartoImg(n, addArr));
addArr.forEach(i => frag.appendChild(i));
[...frag.children].forEach(n => n.style.display === 'none' && n.remove());
return _appendChild.call(this, frag);
};
}
return _appendChild.call(this, child);
};
const tc = node.querySelector('.leaflet-tile-container');
if (tc) tc.appendChild = function (f) { [...f.children].forEach(n => n.tagName === 'IMG' && transformCartoImg(n)); return _appendChild.call(this, f); };
} else if (node.classList?.contains('leaflet-control-attribution')) {
node.remove(); // 隐藏原版权
}
if (node.shadowRoot) initShadow(node.shadowRoot);
}
function onRemove(node) {
if (node.nodeType !== 1) return;
if (node.tagName === 'IMG') {
if (node.downgradeKey) doneSet.delete(node.downgradeKey);
node.cvaImgRef?.remove();
}
}
const ob = new MutationObserver(list => list.forEach(m => {
m.addedNodes.forEach(onAdd);
m.removedNodes.forEach(onRemove);
}));
function initShadow(root) {
ob.observe(root, { childList: true, subtree: true });
root.querySelectorAll?.('.leaflet-layer').forEach(onAdd);
}
ob.observe(document, { childList: true, subtree: true });
const orig = Element.prototype.attachShadow;
Element.prototype.attachShadow = function (opt) {
const sh = orig.call(this, opt);
initShadow(sh);
return sh;
};
initShadow(document.body);
}
initDomObserver();