普通视图

我的浪迹地图

原计划端午节带娃回趟老家,下午哄娃睡午觉的时候,手一伸摸到脖子,感觉温度不太对,应该是有轻微的低烧,拿体温计一验,37.1°,今天是学校的六一活动节,有很多好玩的节目,贝勒爷不想错过,但是出于对别人负责的态度,算了,请个假,候着,看体温变化,美团上顺便买了核酸检测剂,唉,两条杠。

所以回老家的计划取消了,也好在有三天假期,希望这三天尽快养好,不要耽误了上课。

前几日,在周天记看到博友弄的足迹地图,抄作业我还是会的,想想我也搞个页面吧,折腾了两天,代码类的都能照抄了,没啥难度,就是在同步云盘里找之前手机的备份照片,这些年换了N+1台手机,照片一大堆,而且当初备份的时候,没有选择按照手机备份,全部叠放在一起,找得脑袋瓜子嗡嗡的,到晚上哄娃吃完药睡下后,才有时间收尾。

反正站点是自己的,新建页面又不要钱,好吧,新开一个页面,我放到 浪迹地图 这里来了,用不同的年份照片做图标,整理也是件苦力活。

发现了一个问题,会不会是因为我导入的数组太多了,百度地图的API加载会有受限,有时候前端不能完全显示我的足迹标签,我的完整版是这样的,如下图:

(我图都截不到…)

算了,我还是分成两版试试吧!

国内版:大图链接…

世界版:大图链接…

果然,分成两版就正常了,全部显示了,哎呀,这搞得,没啥意思了啊!

生在泉州,活在厦门,这一生脚步迷乱,路过很多座城,也留下很多故事和事故,我是出走了半生,但是归来时已不是少年摸样。

这里就大概的记录了这些年的足迹,多的也许我故意遗落了,也许我自己也忘了,所以那是不重要的。标注的都是停留超过7天或有特殊意义的,时间更短的地方,就不提说去过了。

不过这个还是可以看出的,这些年我一直在往南走,嗯,此情此景,应该配乐一首。

一路往南走(种完麦子我就往南走) – 卢润泽

未获取商用授权?! — 这很百度

2025年11月13日 15:27

上午的时候,收到 梦不见的梦 的一条 qq 消息,说出现了授权问题。

看了下提示域名,大概率就是 tm 百度地图弹的,那个域名做了个足迹应用就这么放着。

就在上个月自己更换 ssl 证书的时候还一切正常,结果现在来了这么一出。本来就是个人开发者,纯自用的东西,还经常收到百度的电话让升级企业认证,之前就是不小心升级了,结果一年要五万的授权费用。

我 tm 就自己玩的,还需要花钱,真 tm 服了。看来这戏破玩意儿都完犊子之后,最后能玩的也就只剩下天地图了。

刚开始是以为嵌入的问题,看了下嵌入页面都会提示:

各种提示信息:

【d45a31】未获取商用授权,平台资源与服务稳定性受限;详情信息请前往:https://lbs.baidu.com/faq/search?id=314&title=908

并且后来发现,不单纯是弹窗在地图的贴图上也会出现授权提示,不得不说。这狗皮膏药贴的有水平。

对应的 js:

地址:
https://api.map.baidu.com/?qt=cen&b=7597813.822562976%2C687420.7063757228%3B15519477.822562976%2C7879996.706375723&l=5&ie=utf-8&oue=1&fromproduct=jsapi&ak=BxlnBNX55clLsUHVFZlaukyJesN5F5VI&callback=BMapGL._rd._cbk28033&v=gl&seckey=hNJCxM58roJGqVuMKRuYPEZfDZ%2FdhlL4Pp7JxBsDoLNUSc3QN6CRIbBdAJ%2FOt7zPayXwFYMsbLGx0%2BZUValnOg%3D%3D%2ChNJCxM58roJGqVuMKRuYPEZfDZ_dhlL4Pp7JxBsDoLPlabo3s2HGFnIPFXI8e1esM9-LzywgHJdkZjwHcr89ZaWfdPB6XAgd7DE4lcFgxfu8J0x_GywX0u2he7lW2roGgTrZQMyK7kcSPMHFwMyFrx45ktBewEco-xsnR-zdIctg5lIvP6h-iihbsl6ehY9AbrnGwefDt0BtO6gKCedJ8PeXNIUPh2rilmKFv6PZRd0&timeStamp=1763012808396&sign=07d57e493b29
内容:
/**/BMapGL._rd._cbk28033 && BMapGL._rd._cbk28033({"result":{"b":"7597813.822562976,687420.7063757228;15519477.822562976,7879996.706375723","callback":"BMapGL._rd._cbk28033","catalogID":0,"count":0,"current_null":1,"db":0,"error":503,"error_msg":"未获取商用授权,平台资源与服务稳定性受限;详情信息请前往:https://lbs.baidu.com/faq/search?id=314\u0026title=908","fromproduct":"jsapi","ie":"utf-8","jump_back":0,"l":"5","op_gel":0,"oue":"1","popup":1,"qt":"cen","requery":"","res_l":-1,"res_x":"0.000000","res_y":"0.000000","return_query":"","seckey":"hNJCxM58roJGqVuMKRuYPEZfDZ/dhlL4Pp7JxBsDoLNUSc3QN6CRIbBdAJ/Ot7zPayXwFYMsbLGx0+ZUValnOg==,hNJCxM58roJGqVuMKRuYPEZfDZ_dhlL4Pp7JxBsDoLPlabo3s2HGFnIPFXI8e1esM9-LzywgHJdkZjwHcr89ZaWfdPB6XAgd7DE4lcFgxfu8J0x_GywX0u2he7lW2roGgTrZQMyK7kcSPMHFwMyFrx45ktBewEco-xsnR-zdIctg5lIvP6h-iihbsl6ehY9AbrnGwefDt0BtO6gKCedJ8PeXNIUPh2rilmKFv6PZRd0","sign":"07d57e493b29","spec_dispnum":0,"time":0,"timeStamp":"1763012808396","total":0,"tp":0,"type":11,"v":"gl","wd":"","wd2":"","what":"","where":""},"current_city":{"code":0,"geo":"","level":0,"name":"","sup":0,"sup_bus":0,"sup_business_area":0,"sup_lukuang":0,"sup_subway":0,"type":0,"up_province_name":""},"hot_city":["北京市|131","上海市|289","广州市|257","深圳市|340","成都市|75","天津市|332","南京市|315","杭州市|179","武汉市|218","重庆市|132"]})
地址:
https://api.map.baidu.com/?qt=verify&v=gl&type=webgl&ak=BxlnBNX55clLsUHVFZlaukyJesN5F5VI&time=1763012794639&callback=BMapGL.bmapVerifyCbk
内容:
/**/BMapGL.bmapVerifyCbk && BMapGL.bmapVerifyCbk({"error":503,"error_msg":"未获取商用授权,平台资源与服务稳定性受限;详情信息请前往:https://lbs.baidu.com/faq/search?id=314\u0026title=908","popup":1})

我这个破玩意儿就是纯粹个玩具啊,你何苦这么狠心呢?!

对于这个东西,其实我也没啥好办法,刚开始登录百度 lbs 地图后台提示账号要年审。结果进行账号年审之后依然提示这个错误:

这尼玛就离谱了啊,既然你年审不能解决,那就直接 hook 大法:

<!-- 拦截百度地图弹窗相关请求和Hook方法 -->
    <script>
        (function() {
            // 1. 拦截 fetch 请求
            const originalFetch = window.fetch;
            window.fetch = function(...args) {
                const url = args[0];
                if (typeof url === 'string' && url.includes('api.map.baidu.com')) {
                    // 检查是否是弹窗相关的请求
                    if (url.includes('qt=verify') || url.includes('qt=cen')) {
                        console.log('拦截百度地图弹窗请求:', url);
                        // 返回一个模拟的成功响应,避免弹窗
                        return Promise.resolve(new Response(JSON.stringify({
                            error: 0,
                            error_msg: "",
                            popup: 0,
                            result: {
                                error: 0,
                                popup: 0
                            }
                        }), {
                            status: 200,
                            headers: { 'Content-Type': 'application/json' }
                        }));
                    }
                }
                return originalFetch.apply(this, args);
            };

            // 2. 拦截 XMLHttpRequest
            const originalXHROpen = XMLHttpRequest.prototype.open;
            const originalXHRSend = XMLHttpRequest.prototype.send;
            
            XMLHttpRequest.prototype.open = function(method, url, ...rest) {
                this._url = url;
                if (typeof url === 'string' && url.includes('api.map.baidu.com')) {
                    if (url.includes('qt=verify') || url.includes('qt=cen')) {
                        console.log('拦截百度地图弹窗XHR请求:', url);
                        // 标记为已拦截,在send时处理
                        this._intercepted = true;
                    }
                }
                return originalXHROpen.apply(this, [method, url, ...rest]);
            };

            XMLHttpRequest.prototype.send = function(...args) {
                if (this._intercepted) {
                    // 模拟成功响应
                    Object.defineProperty(this, 'status', { value: 200, writable: false });
                    Object.defineProperty(this, 'statusText', { value: 'OK', writable: false });
                    Object.defineProperty(this, 'responseText', { 
                        value: JSON.stringify({
                            error: 0,
                            error_msg: "",
                            popup: 0,
                            result: {
                                error: 0,
                                popup: 0
                            }
                        }), 
                        writable: false 
                    });
                    Object.defineProperty(this, 'readyState', { value: 4, writable: false });
                    
                    // 触发事件
                    if (this.onreadystatechange) {
                        this.onreadystatechange();
                    }
                    if (this.onload) {
                        this.onload();
                    }
                    return;
                }
                return originalXHRSend.apply(this, args);
            };

            // 3. 拦截 JSONP 回调(百度地图使用JSONP)
            const originalCreateElement = document.createElement;
            const originalAppendChild = Node.prototype.appendChild;
            const originalInsertBefore = Node.prototype.insertBefore;
            
            document.createElement = function(tagName, ...rest) {
                const element = originalCreateElement.apply(this, [tagName, ...rest]);
                
                // 处理div元素,防止创建弹窗
                if (tagName.toLowerCase() === 'div') {
                    const originalSetAttribute = element.setAttribute;
                    const originalAddClass = element.classList?.add;
                    
                    // Hook setAttribute,检查可能用于弹窗的属性
                    element.setAttribute = function(name, value) {
                        if (typeof value === 'string' && (
                            value.includes('商用授权') || 
                            value.includes('business_accredit') ||
                            value.includes('lbs.baidu.com') ||
                            (name === 'class' && (value.includes('dialog') || value.includes('modal') || value.includes('popup')))
                        )) {
                            console.log('拦截百度地图弹窗元素创建:', name, value);
                            // 不设置属性,或者设置为隐藏
                            if (name === 'style') {
                                return originalSetAttribute.call(this, name, 'display:none !important;');
                            }
                            return; // 不设置属性
                        }
                        return originalSetAttribute.apply(this, arguments);
                    };
                    
                    // Hook classList.add
                    if (element.classList && originalAddClass) {
                        const classListAdd = element.classList.add;
                        element.classList.add = function(...tokens) {
                            for (let token of tokens) {
                                if (typeof token === 'string' && (
                                    token.includes('dialog') || 
                                    token.includes('modal') || 
                                    token.includes('popup') ||
                                    token.includes('alert')
                                )) {
                                    console.log('拦截百度地图弹窗class添加:', token);
                                    continue; // 跳过这个class
                                }
                            }
                            return classListAdd.apply(this, tokens);
                        };
                    }
                    
                    // Hook appendChild,如果添加了包含授权相关文本的子元素,则隐藏
                    const originalDivAppendChild = element.appendChild;
                    element.appendChild = function(child) {
                        if (child && child.textContent) {
                            const text = child.textContent;
                            if (text.includes('商用授权') || 
                                text.includes('business_accredit') ||
                                text.includes('lbs.baidu.com') ||
                                text.includes('未完成商用授权')) {
                                console.log('拦截百度地图弹窗内容添加');
                                // 隐藏元素而不是添加
                                if (child.style) {
                                    child.style.display = 'none';
                                }
                            }
                        }
                        return originalDivAppendChild.apply(this, arguments);
                    };
                }
                
                if (tagName.toLowerCase() === 'script') {
                    const originalSetAttribute = element.setAttribute;
                    const originalSetProperty = Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype, 'src')?.set;
                    
                    // Hook setAttribute
                    element.setAttribute = function(name, value) {
                        if (name === 'src' && typeof value === 'string' && value.includes('api.map.baidu.com')) {
                            // 拦截所有包含callback参数的JSONP请求(包括缩放等操作触发的请求)
                            // 只要包含callback参数就拦截,确保所有百度地图API回调都被处理
                            if (value.includes('callback=')) {
                                console.log('拦截百度地图JSONP请求 (setAttribute):', value);
                                value = modifyJsonpCallback(value);
                            }
                        }
                        return originalSetAttribute.apply(this, arguments);
                    };
                    
                    // Hook src 属性设置
                    if (originalSetProperty) {
                        Object.defineProperty(element, 'src', {
                            set: function(value) {
                                if (typeof value === 'string' && value.includes('api.map.baidu.com')) {
                                    // 拦截所有包含callback参数的JSONP请求
                                    if (value.includes('callback=')) {
                                        console.log('拦截百度地图JSONP请求 (src property):', value);
                                        value = modifyJsonpCallback(value);
                                    }
                                }
                                originalSetProperty.call(this, value);
                            },
                            get: function() {
                                return this.getAttribute('src');
                            },
                            configurable: true
                        });
                    }
                    
                    // 标记这个script元素,以便在添加到DOM时检查
                    element._isBaiduMapScript = true;
                }
                
                return element;
            };
            
            // Hook appendChild 和 insertBefore,在添加到DOM前最后检查
            Node.prototype.appendChild = function(child) {
                if (child && child._isBaiduMapScript && child.src) {
                    // 拦截所有包含callback的百度地图请求
                    if (child.src.includes('api.map.baidu.com') && child.src.includes('callback=')) {
                        // 确保已经修改了回调
                        if (!child.src.includes('_baidu_map_popup_blocker_')) {
                            child.src = modifyJsonpCallback(child.src);
                        }
                    }
                }
                return originalAppendChild.apply(this, arguments);
            };
            
            Node.prototype.insertBefore = function(newNode, referenceNode) {
                if (newNode && newNode._isBaiduMapScript && newNode.src) {
                    // 拦截所有包含callback的百度地图请求
                    if (newNode.src.includes('api.map.baidu.com') && newNode.src.includes('callback=')) {
                        // 确保已经修改了回调
                        if (!newNode.src.includes('_baidu_map_popup_blocker_')) {
                            newNode.src = modifyJsonpCallback(newNode.src);
                        }
                    }
                }
                return originalInsertBefore.apply(this, arguments);
            };
            
            // 修改JSONP回调的辅助函数
            function modifyJsonpCallback(url) {
                // 匹配 callback=xxx 或 callback=xxx.xxx.xxx 格式
                return url.replace(
                    /callback=([^&]+)/,
                    (match, callbackName) => {
                        // 创建一个包装函数,修改返回数据
                        const wrapperName = '_baidu_map_popup_blocker_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
                        const callbackPath = callbackName.split('.');
                        
                        // 创建包装回调函数
                        window[wrapperName] = function(data) {
                            // 深度修改所有可能的弹窗相关字段
                            function removePopupFields(obj) {
                                if (!obj || typeof obj !== 'object') return;
                                
                                // 修改当前对象的字段
                                if (obj.popup !== undefined) {
                                    obj.popup = 0;
                                }
                                if (obj.error !== undefined && obj.error === 503) {
                                    obj.error = 0;
                                    obj.error_msg = "";
                                }
                                
                                // 递归处理嵌套对象
                                for (let key in obj) {
                                    if (obj.hasOwnProperty(key) && typeof obj[key] === 'object') {
                                        removePopupFields(obj[key]);
                                    }
                                }
                            }
                            
                            if (data && typeof data === 'object') {
                                removePopupFields(data);
                            }
                            
                            // 调用原始回调
                            let callback = window;
                            for (let i = 0; i < callbackPath.length; i++) {
                                if (callback && callback[callbackPath[i]]) {
                                    callback = callback[callbackPath[i]];
                                } else {
                                    callback = null;
                                    break;
                                }
                            }
                            if (callback && typeof callback === 'function') {
                                try {
                                    callback(data);
                                } catch(e) {
                                    console.error('Error calling original callback:', e);
                                }
                            }
                            
                            // 延迟清理包装函数,确保回调已执行
                            setTimeout(function() {
                                try {
                                    delete window[wrapperName];
                                } catch(e) {}
                            }, 1000);
                        };
                        
                        return 'callback=' + wrapperName;
                    }
                );
            }

            // 4. Hook BMapGL 相关方法(如果已经加载)
            function hookBMapGLMethods() {
                if (window.BMapGL) {
                    // Hook bmapVerifyCbk 回调
                    if (window.BMapGL.bmapVerifyCbk) {
                        const originalVerifyCbk = window.BMapGL.bmapVerifyCbk;
                        // 深度修改弹窗字段的辅助函数
                        function removePopupFieldsDeepVerify(obj) {
                            if (!obj || typeof obj !== 'object') return;
                            if (obj.popup !== undefined) obj.popup = 0;
                            if (obj.error !== undefined && obj.error === 503) {
                                obj.error = 0;
                                obj.error_msg = "";
                            }
                            for (let key in obj) {
                                if (obj.hasOwnProperty(key) && typeof obj[key] === 'object' && obj[key] !== null) {
                                    removePopupFieldsDeepVerify(obj[key]);
                                }
                            }
                        }
                        
                        window.BMapGL.bmapVerifyCbk = function(data) {
                            if (data && typeof data === 'object') {
                                removePopupFieldsDeepVerify(data);
                            }
                            return originalVerifyCbk ? originalVerifyCbk.call(this, data) : undefined;
                        };
                    }
                    
                    // Hook 动态回调(如 _rd._cbk28033)
                    if (window.BMapGL._rd) {
                        const originalRd = window.BMapGL._rd;
                        
                        // Hook _cbk 方法(用于创建动态回调)
                        if (originalRd._cbk) {
                            const originalCbk = originalRd._cbk;
                            // 深度修改弹窗字段的辅助函数(局部定义)
                            function removePopupFieldsDeepLocal(obj) {
                                if (!obj || typeof obj !== 'object') return;
                                if (obj.popup !== undefined) obj.popup = 0;
                                if (obj.error !== undefined && obj.error === 503) {
                                    obj.error = 0;
                                    obj.error_msg = "";
                                }
                                for (let key in obj) {
                                    if (obj.hasOwnProperty(key) && typeof obj[key] === 'object' && obj[key] !== null) {
                                        removePopupFieldsDeepLocal(obj[key]);
                                    }
                                }
                            }
                            
                            originalRd._cbk = function(callbackName) {
                                const originalCallback = originalCbk.call(this, callbackName);
                                if (typeof originalCallback === 'function') {
                                    return function(data) {
                                        if (data && typeof data === 'object') {
                                            removePopupFieldsDeepLocal(data);
                                        }
                                        return originalCallback.call(this, data);
                                    };
                                }
                                return originalCallback;
                            };
                        }
                        
                        // 使用 Proxy 拦截所有动态创建的回调属性
                        try {
                            // 深度修改弹窗字段的辅助函数(用于Proxy)
                            function removePopupFieldsDeepProxy(obj) {
                                if (!obj || typeof obj !== 'object') return;
                                if (obj.popup !== undefined) obj.popup = 0;
                                if (obj.error !== undefined && obj.error === 503) {
                                    obj.error = 0;
                                    obj.error_msg = "";
                                }
                                for (let key in obj) {
                                    if (obj.hasOwnProperty(key) && typeof obj[key] === 'object' && obj[key] !== null) {
                                        removePopupFieldsDeepProxy(obj[key]);
                                    }
                                }
                            }
                            
                            const rdProxy = new Proxy(originalRd, {
                                set: function(target, prop, value) {
                                    if (typeof prop === 'string' && prop.startsWith('_cbk') && typeof value === 'function') {
                                        // 包装回调函数
                                        target[prop] = function(data) {
                                            if (data && typeof data === 'object') {
                                                removePopupFieldsDeepProxy(data);
                                            }
                                            return value.call(this, data);
                                        };
                                        return true;
                                    }
                                    target[prop] = value;
                                    return true;
                                }
                            });
                            window.BMapGL._rd = rdProxy;
                        } catch(e) {
                            console.log('Proxy not supported, using fallback method');
                        }
                    }
                    
                    // 深度修改弹窗字段的辅助函数
                    function removePopupFieldsDeep(obj) {
                        if (!obj || typeof obj !== 'object') return;
                        
                        // 修改当前对象的字段
                        if (obj.popup !== undefined) {
                            obj.popup = 0;
                        }
                        if (obj.error !== undefined && obj.error === 503) {
                            obj.error = 0;
                            obj.error_msg = "";
                        }
                        
                        // 递归处理嵌套对象
                        for (let key in obj) {
                            if (obj.hasOwnProperty(key) && typeof obj[key] === 'object' && obj[key] !== null) {
                                removePopupFieldsDeep(obj[key]);
                            }
                        }
                    }
                    
                    // 定期检查并Hook新创建的回调函数
                    const callbackMonitor = setInterval(function() {
                        if (window.BMapGL && window.BMapGL._rd) {
                            // 检查所有以_cbk开头的属性
                            for (let key in window.BMapGL._rd) {
                                if (key.startsWith('_cbk') && typeof window.BMapGL._rd[key] === 'function') {
                                    const originalCbk = window.BMapGL._rd[key];
                                    // 如果还没有被Hook过
                                    if (!originalCbk._hooked) {
                                        window.BMapGL._rd[key] = function(data) {
                                            if (data && typeof data === 'object') {
                                                removePopupFieldsDeep(data);
                                            }
                                            return originalCbk.call(this, data);
                                        };
                                        window.BMapGL._rd[key]._hooked = true;
                                        console.log('Hook BMapGL callback:', key);
                                    }
                                }
                            }
                        }
                        
                        // 也检查BMapGL上的其他回调
                        if (window.BMapGL) {
                            for (let key in window.BMapGL) {
                                if ((key.includes('cbk') || key.includes('Cbk') || key.includes('callback') || key.includes('Callback')) 
                                    && typeof window.BMapGL[key] === 'function' && !window.BMapGL[key]._hooked) {
                                    const originalCbk = window.BMapGL[key];
                                    window.BMapGL[key] = function(data) {
                                        if (data && typeof data === 'object') {
                                            removePopupFieldsDeep(data);
                                        }
                                        return originalCbk.call(this, data);
                                    };
                                    window.BMapGL[key]._hooked = true;
                                    console.log('Hook BMapGL callback:', key);
                                }
                            }
                        }
                    }, 100); // 更频繁的检查(100ms)
                    
                    // 30秒后停止监控(给足够时间处理所有回调)
                    setTimeout(function() {
                        clearInterval(callbackMonitor);
                    }, 30000);
                }
            }

            // 5. 监听百度地图API加载完成
            const checkInterval = setInterval(function() {
                if (window.BMapGL) {
                    hookBMapGLMethods();
                    clearInterval(checkInterval);
                }
            }, 100);

            // 6. 全局拦截弹窗显示方法
            // 拦截 alert
            const originalAlert = window.alert;
            window.alert = function(message) {
                if (typeof message === 'string' && (
                    message.includes('商用授权') || 
                    message.includes('lbs.baidu.com') ||
                    message.includes('business_accredit') ||
                    message.includes('未完成商用授权')
                )) {
                    console.log('拦截百度地图授权弹窗 (alert):', message);
                    return;
                }
                return originalAlert.apply(this, arguments);
            };
            
            // 拦截 confirm
            const originalConfirm = window.confirm;
            window.confirm = function(message) {
                if (typeof message === 'string' && (
                    message.includes('商用授权') || 
                    message.includes('lbs.baidu.com') ||
                    message.includes('business_accredit') ||
                    message.includes('未完成商用授权')
                )) {
                    console.log('拦截百度地图授权弹窗 (confirm):', message);
                    return true; // 返回true避免阻塞
                }
                return originalConfirm.apply(this, arguments);
            };
            
            // 拦截 prompt
            const originalPrompt = window.prompt;
            window.prompt = function(message, defaultText) {
                if (typeof message === 'string' && (
                    message.includes('商用授权') || 
                    message.includes('lbs.baidu.com') ||
                    message.includes('business_accredit')
                )) {
                    console.log('拦截百度地图授权弹窗 (prompt):', message);
                    return null;
                }
                return originalPrompt.apply(this, arguments);
            };
            
            // 7. 拦截可能用于显示弹窗的方法
            window.addEventListener('load', function() {
                // 拦截可能用于显示弹窗的全局函数
                const popupKeywords = ['商用授权', 'business_accredit', 'lbs.baidu.com', '未完成商用授权'];
                
                // 监控DOM变化,移除包含授权相关文本的元素
                const observer = new MutationObserver(function(mutations) {
                    mutations.forEach(function(mutation) {
                        mutation.addedNodes.forEach(function(node) {
                            if (node.nodeType === 1) { // Element node
                                const text = node.textContent || '';
                                const className = node.className || '';
                                const id = node.id || '';
                                
                                // 检查是否包含授权相关文本
                                if (popupKeywords.some(keyword => 
                                    text.includes(keyword) || 
                                    className.includes(keyword) || 
                                    id.includes(keyword)
                                )) {
                                    console.log('检测到百度地图弹窗元素,正在移除:', node);
                                    // 立即移除或隐藏
                                    if (node.parentNode) {
                                        node.style.display = 'none';
                                        // 延迟移除,避免影响其他功能
                                        setTimeout(function() {
                                            if (node.parentNode) {
                                                node.parentNode.removeChild(node);
                                            }
                                        }, 100);
                                    }
                                }
                            }
                        });
                    });
                });
                
                // 开始观察DOM变化
                observer.observe(document.body, {
                    childList: true,
                    subtree: true
                });
                
                // 定期检查并移除弹窗元素
                const popupChecker = setInterval(function() {
                    const allElements = document.querySelectorAll('*');
                    allElements.forEach(function(el) {
                        const text = el.textContent || '';
                        const className = el.className || '';
                        const id = el.id || '';
                        const style = window.getComputedStyle(el);
                        
                        // 检查是否是可见的弹窗元素
                        if (popupKeywords.some(keyword => 
                            (text.includes(keyword) || className.includes(keyword) || id.includes(keyword)) &&
                            style.display !== 'none' &&
                            (style.position === 'fixed' || style.position === 'absolute') &&
                            (parseInt(style.zIndex) > 1000 || style.zIndex === 'auto')
                        )) {
                            console.log('检测到百度地图弹窗,正在移除:', el);
                            el.style.display = 'none';
                            setTimeout(function() {
                                if (el.parentNode) {
                                    el.parentNode.removeChild(el);
                                }
                            }, 100);
                        }
                    });
                }, 500);
                
                // 30秒后停止检查
                setTimeout(function() {
                    clearInterval(popupChecker);
                }, 30000);
            });
        })();
    </script>

实际效果:

不过既然是 hook,存在一定的稳定性问题,有可能会失效。

等哪天彻底被恶心够了,就直接换地图 sdk 了!

【去过】页面上线

2025年9月12日 09:40

最终效果见这里

我这个人既不喜欢旅游,也不喜欢摄影,没出过国也没在国内溜达过几个地方,但这并不妨碍我也有一颗上线足迹地图的心。无奈谷歌地图连不上,百度地图不想连,OSM(OpenStreetMap)既连不上也连不明白,就搁置了。
直到上周,@Dayu上线了他的地图插件,试用之后发现表现形式与期待值有差距,想在帖子下面留言发建议的时候,发现了明明同学的另一份方案。虽然主要是js实现的,只需要知道leaflet这个开源库的名字,我就知道以Wordpress的海量扩展,这回成了!

找到了一个插件和插件的插件,配置之后,算是达到了心目中的效果。
插件使用比较复杂,这里记录一下,给自己个备忘,能帮到人更好。

插件Leaflet Map的配置

插件地址:Leaflet Map
逐一说明一下后台的配置项:
Default Latitude
地图默认纬度
Default Longitude
地图默认经度
Default Zoom
地图默认缩放比
Default Height
默认高度
Default Width
默认宽度
上面五个值可以以短代码参数的形式加到leaflet-map中,如果不加则使用后台的默认参。

Fit Bounds
是否令地图随标记点的范围进行缩放。如果为true,那么地图会自动计算当前所有Marker的中心位置,以及包括所有Marker点的合适的缩放比。可以用默认值,也可以在map中单独设置。

Show Zoom Controls
是否显示缩放按钮

Scroll Wheel Zoom
是否允许滚轮缩放
这项设置之后,在地图上并不是直接用滚轮缩放,而是需要用Ctrl,感觉不便所以我自己没加。

Double Click Zoom
是否允许双击放大
注意如果不显示缩放按钮的话,放大之后就回不来了。

Default Min/Max Zoom
最小和最大放大倍数。最终的放大倍数不仅跟这个后台设定项有关,也跟使用的地图瓦片API有关。

Default Tiling Service
有两个选项,“I will provide my own map tile URL”和MapQuest。MapQuest我考察了一番,感觉太难看,没继续尝试。所以重点说第一个选项,自定义tile URL。
先解释一下什么是tile URL。本插件所依赖的地图服务,叫做Map Tile Server(地图瓦片服务)。它分成Raster、Vector和Static三种形式。本插件使用的是Raster形式,而Dayu和明明同学用的都是Vector形式。Vector的参数更加丰富,Raster配置起来更简便。

※※※Map Tile URL※※※
重中之重!!因为默认的OSM服务访问不了,所以这里必须填一个自定义的。
明明同学找的cartocdn就挺好用的。他使用的light_nolabels是不带标签的版本,换成light_all就是带标签的版本。至于他为什么用不带标签的,自己悟。

https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png
http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png

这里的{z}{x}{y}都不要动,第一个{s}是指地址里的subdomain,也就是接下来的配置项【Map Tile URL Subdomains】。
有的API是需要API Key的。这样的API都有说明文档,把Key按照人家给的例子粘贴进来即可。
cartocdn对我来说有点太素了,于是周末花了一天时间寻找能用的瓦片服务,结果惨不忍睹,汇报一下成果:
maptiler.com:要注册、被墙。
opentopomap.org:免注册、丑。
stadiamaps.com:要注册、比较流畅、免费用户限流严重
tomtom.com:要注册、慢、地图不详细
here.com:要注册、中文地名乱七八糟
geoapify.com:要注册、速度尚可、账号登录验证需要梯子,服务本身不需要。
至于为啥不找国内的地图服务,是我不想吗?本插件所支持的是Raster服务,也叫zxy服务,客户端GET的结果必须是一张PNG/JPG/WEBP图片。国产的高德和天地图我也试了,根本不是一样的格式。甚至有种说法,说国内不允许提供这种服务。
然后呢,这服务也可以自己搭建,数据和教程也都有,但总觉得这种行为像为了吃顿酱肘子先花一年时间晒酱油,没必要。
兜兜转转一圈,还是要回到cartocdn。什么功不唐捐,屁咧!

Map Tile URL Subdomains
具体填什么要看API的文档说明,像openstreetmap和cartocdn都是abcde或a,b,c,d,e。有的API前面没有前缀那就不填。

Detect Retina
是否检测高清设备。如果上面的瓦片服务中支持@2x,那么选中这项以后,如果leaflet发现使用者用的是高清屏幕,就会自动使用高清地图。窃以为意义不大。

Tile Size
一般情况下服务本身已经设好了,留空即可。

Tile Id
Access Token
用Mapbox的服务才有用。但是我的Mapbox没配明白,只有一个默认的卫星地图能用,这两项也没用上。

Zoom Offset
好像是在zoom的时候自动加减一下这个值,没理解意义。

No Wrap (tiles)
地图是否与经线对齐。(这有啥用?)

JavaScript URLCSS URL
css和js的位置,默认即可。

Default Attribution
地图做成后,右下角的一串版权信息。尊重服务商要求即可。

再往后没用上。
插件装好后,其实已经可以编辑地图了。但因为还有个插件的插件,所以怎么用放后面再说。

插件Extensions for Leaflet Map的配置

插件地址:Extensions for Leaflet Map
跟主插件正相反,这个插件的后台,大多数时间在说怎么用。

Marker Clustering–When you mouse over a cluster it shows the bounds of its markers.
聚合显示时,默认会将聚合的标记点连接起来形成一个多边形。我嫌不好看,勾掉了。

When you click a cluster we zoom to its bounds.
点击聚合点时,直接缩放到所有聚合点的范围。

增加一个leaflet map

语法和关键字很多,但能用上的不多。通常只需要地图、标记点、地形json文件。

leaflet-map
主插件的主函数,用于插入一张leaflet-map。参数就是配置页面的那些,没什么特别的。

leaflet-marker
标记一个地点。参数比较多,挑有用的说。
– lat,lng / address:用于定位标记的位置。lat是纬度,lng是经度。经纬度可以从OpenStreetMap.org上查。国产地图查到的经纬度与OSM查到的有一些偏差,注意要保持一致。而address的数据库偏差非常大,不建议。
– iconUrl:标记用图标。可以是网址,也可以是相对路径,甚至base64代码。如果不设,使用默认的蓝色pin图标。
– title:鼠标移动到标记点上时显示的提示信息。这个与点击标记后显示的内容是两码事。
– iconanchor:图标的锚点偏移。这个参数有点意思,它表示的是你图标的“尖”所在的位置。以默认图标为例,规格是19*25,“尖”在最下方,所以iconanchor就应该是宽的一半和全部的高,也就是10,25。这个参默认项是0,0。每个标记都要自己手动加,相当烦。不过也能够理解,一旦有人用的是左箭头呢?
– opacity:图标的透明度。
– 【leaflet-marker】【/leaflet-marker】之间,放置弹出的html内容。放啥都行,也可以定义css。
另外还有一些参数,我都没用上官网说明文档里都有。

leaflet-geojson
地理信息的json文件。中国的地理信息文件可以在阿里云下载,或者直接引用。建议下载后到mapshaper进行优化后上传,在本地使用。这里非常感谢明明桑,一个优化工具解决了困扰我许久的问题。
– src:地理信息源文件的路径,可以是相对路径或网址。
– color:leaflet提供的属性,边框颜色。
– weight;leaflet提供的属性,边框宽度。
– opacity:leaflet提供的属性,边框透明度。默认的3px蓝色实在是有点虎。缺点是每个marker都需要单独设置。
– fill:leaflet提供的属性,布尔型,是否填充。默认true
– fillColor:leaflet提供的属性,填充颜色,不设则默认使用color
– fillOpacity:leaflet提供的属性,填充颜色的透明度,默认0.2
想一个省一个省点亮的,就下载省级json,想像我这样一个市一个市点亮的,就下载市级json,一次勾一个边即可。话说,我一直认为用省划分习俗非常不科学,甚至用市来划分都不科学。毕竟有粤南粤北、胶东鲁西南,还有江苏十三太保这样的众所周知的文化差异。我甚至觉得应该精确到县。毕竟我们旁边就有一个散装的地级市,我认识一堆人,他们分别来自鲅鱼圈、熊岳、盖州、老边、大石桥,就没有一个称自己是营口人的。但是真正实装以后才发现,县级太困难了,我甚至都没在普兰店歇过脚,自己家都没搞定。

markerclustergroup:聚合分组方法,这是“扩展的扩展”的功能,也是安装它的主要目地。cluster是指将临近的点进行聚合,group是通过关键字对追加的点进行分组。只聚合可以用cluster,只分组可以用leaflet-optiongroup。
– feat:适配项目。可以是【leaflet-marker】中的title或者iconUrl中的一个。参数以部分匹配的方式,对title或者iconUrl形成过滤,然后在右上角形成组别。
– strings:分组过滤用的字符串,用半角逗号间隔。
– groups:分组后显示的字符串。
– position :分组显示位置
– collapsed :分组是否折叠

多说无益,直接举例

【leaflet-map fitbounds】//用fitbounds,就不用关心点的位置和缩放了
【fullscreen】//增加一个全屏按钮
【leaflet-marker lat=38.87430121 lng=121.55204380 iconUrl="/path_to/marker-blue.png" iconanchor="15,40" 】<b>辽师附中</b><i>2012 /04 /<a href="/2012/04/ancient-school-motto.html" target="_blank" rel="noopener">沙河口黑石礁尖山街</a> /街拍 </i><img src="/path_to/imag0114.jpg" alt="imag0114" />【/leaflet-marker】
【leaflet-marker lat=38.88611421 lng=121.63731716 iconUrl="/path_to/marker-blue.png" iconanchor="15,40"  opacity=0.5】<b>西岗中学</b><i>2003 /05 /<a href="/2023/06/post-truth-and-something-before-my-graduation.html" target="_blank" rel="noopener">西岗八一三环街</a> /其他 </i><img src="/path_to/vlcsnap-2023-05-21-17h15m27s675.jpg" alt="vlcsnap-2023-05-21-17h15m27s675" />【/leaflet-marker】
【leaflet-marker lat=38.88890 lng=121.70665 iconUrl="/path_to/marker-yellow.png" iconanchor="15,40"  】<b>棒棰岛</b><i>1993 /08 /中山老虎滩迎宾路 /海滩 </i>【/leaflet-marker】
【leaflet-marker lat=38.93471 lng=121.19765 iconUrl="/path_to/marker-yellow.png" iconanchor="15,40" 】<b>北海王家村</b><i>2018 /08 /旅顺北海王家村 /海滩 </i>【/leaflet-marker】
【leaflet-marker lat=38.98562089 lng=121.65895298 iconUrl="/path_to/marker-yellow.png" iconanchor="15,40"  】<b>蟹子湾</b><i>2015 /04 /<a href="/2015/04/chemical-factories-stories.html" target="_blank" rel="noopener">甘井子甘井子海茂路</a> /海滩 </i><img src="/path_to/BF59AEE251904BC4.jpg" alt="BF59AEE251904BC4" />【/leaflet-marker】
【markerclustergroup feat="iconUrl" strings="blue, yellow" groups="学校, 海滩" collapsed=true】
【leaflet-geojson src="/path_to/dalian.json" color="#FF8888" weight=2 fillOpacity=0.3】//加入大连地图
【zoomhomemap】//调整缩放比
[fullscreen ] [markerclustergroup feat=iconUrl strings=blue, yellow groups=学校, 浴场 collapsed=true ] [zoomhomemap ]

就到这。总之有现成儿的我才不会亲自动手呢。

❌