足迹地图更新:区域高亮显示

距离我第一次发布 基于高德地图实现 Hugo 足迹地图短代码 已经过去了一段时间,一直都没有进行更新。

直到昨天 S 大佬提出了一个高品质需求,更新刻不容缓:

当然最终还是大佬靠自己解决了😈……大家可围观大佬的足迹: 足迹 - 旅行漫记

但是觉得这个想法还是挺实用的,有一种逐渐点亮整个世界的感觉,成就满满!

整个过程也很简单,借助 AI,很快实现了鼠标悬停时的高亮效果,后来又实现了已标记地点的自动保持高亮的效果,最后因这两种效果都想保持且希望实现随时转换,因此直接解耦成单独的 js 文件,实现插件化调用。

演示效果可访问: 足迹 - 小十的个人博客

一、主要特性

1. 国家与国内省份高亮

不再局限于孤立的标记点,地图集成了边界解析引擎,国内精确到省级行政区,国外精确到国家级,并实现两者兼容展示。足迹落在哪里,哪里的版图就会被点亮。

2. 高亮功能插件化

不同的使用者可能对高亮的展示有不同的偏好,将高亮功能的实现拆分成了两个独立的插件:

  • 🌍 永久高亮模式 (plugin-visited.js):只要你的足迹数据里包含该区域的坐标,该省份/国家就会被永久高亮(默认呈现清新的薄荷青配色)。
  • 🖱️ 悬浮高亮模式 (plugin-hover.js):默认地图保持纯净,只有当鼠标滑过标记点或地图空白区域时,该区域才会高亮。

3. 智能标签排除(如“计划”行程)

有些地点我们可能只是打了个 ["计划"] 的标签,人还没去过。现在,插件内置了标签过滤,可以通过配置排除特定标签,未成行的足迹不会干扰到区域的点亮状态。

二、如何获取与处理边界数据

要实现区域高亮,最核心的基础就是 GeoJSON 边界数据

1. 获取国内省份数据

国内的省级边界数据相对容易获取,强烈推荐使用 阿里云 DataV 数据可视化平台

直接下载 GeoJSON 即可,并将文件命名为 provinces.geojson

2. 获取全球国家数据

全球数据我使用的是 Natural Earth 提供的开源标准数据集: Datahub - Geo Countries

在这个页面你可以下载到包含全球所有国家边界的 countries.geojson 文件。

但是这个毕竟是以西方为主导编写的数据,有一些错误观念在里面,但一时没找到合适的,大家如果有靠谱的数据,也欢迎评论推荐!

3. 使用 Mapshaper 压缩数据

标准的全球 GeoJSON 文件高达 14.6MB,特别是包含了海岸线细节。

如果直接交给前端解析,不仅会造成线程阻塞,还容易导致 3D 引擎在渲染几十万个顶点时直接崩溃。

因为只需要体现一个大概区域即可,所以需要给数据进行拓扑简化:

  1. 打开在线 GIS 拓扑处理工具: Mapshaper (mapshaper.org)
  2. 导入 14.6MB 的 countries.geojson
  3. 点击右上角的 Simplify (简化) 按钮。
  4. 勾选 prevent shape removal (防止小岛屿彻底消失),点击 Next。
  5. 将页面上方的滑块向左拉,拉到大概 2%4% 的位置。会发现地图的大致轮廓依然清晰,但顶点数量锐减了 95% 以上。
  6. 点击右上角 Export,选择 GeoJSON 格式导出。

经过这一步, 14MB 的文件会压缩至 500KB 左右,重命名为 world.geojson,将这两个文件放入项目的 static/data/ 目录下,基础数据准备就完成了。

三、WebGL 引擎的踩坑记录

这次更新表面看起来只是加载了两个 GeoJSON 图层,但过程还是比较曲折。

1. 透明度导致事件穿透(拾取失效)

悬浮高亮模式中,未激活的省份应该是完全透明的,因此将样式设为 fillOpacity: 0

现象:高德的 3D WebGL 引擎为了性能优化,直接在底层的射线碰撞检测(Hit Test)中剔除了完全透明的物体,导致鼠标滑过省份时根本触发不了 mouseover 事件。

措施:在插件内部运用代理模式封装了一个 getSafeStyle() 方法。外部配置依然写 0,但插件在传给高德底层时,会将其转换为 0.01。肉眼几乎看不出来,却能触发全地图的鼠标交互。

getSafeStyle(styleObj) {
    const safeStyle = { ...styleObj };
    // 如果是 0,强行转为 0.01 规避 WebGL 事件剔除 Bug
    if (safeStyle.fillOpacity === 0) safeStyle.fillOpacity = 0.01;
    if (safeStyle.strokeOpacity === 0) safeStyle.strokeOpacity = 0.01;
    return safeStyle;
}

2. 两个 GeoJSON 文件兼容问题

同时加载 provinces.geojson (中国省份) 和 world.geojson (世界地图) 时,国内的省份高亮全部失效了。

现象:世界地图里本身就包含了一个完整的“中国”区块。虽然没让它亮,但它作为一个隐形的巨大多边形,层级盖在了各个省份之上,形成了一个隐形遮罩,把鼠标悬浮事件全挡住了。

措施:在解析 GeoJSON 时,通过 properties 进行拦截。一旦发现是“中国”,直接 return null,在物理层面彻底将高亮交互功能恢复到 34 个省级区块上。

3. 复杂群岛导致的破面拉丝与引擎崩溃

像美国、英国、印度,甚至国内的广东、浙江,在地理上拥有大量的海外飞地和附属小岛,这种数据结构在 GeoJSON 中被称为 MultiPolygon(多重多边形)。

现象:高德原生的 AMap.GeoJSON 插件在渲染这种嵌套极深的 3D 数组时存在底层 Bug。它会在主大陆和岛屿之间的海面上,错误地拉出网格连线,形成满屏的“重影和蜘蛛网”(GIS 领域俗称 破面拉丝 Polygon Spiking),最终导致地图彻底卡死。

措施:放弃高德内置的自动解析,重写了底层分发逻辑。只要遇到 MultiPolygon,就把它强行拆解,为独立的岛屿单独实例化一个 AMap.Polygon**。最后,通过判断 _pName(所属国家/省份名称),在逻辑层面将岛屿绑定在一起。

核心代码如下:

let polygonsCoords = [];
// 1. 拆解 MultiPolygon 为一维的普通 Polygon 坐标数组
if (geom.type === 'Polygon') {
    polygonsCoords = [geom.coordinates];
} else if (geom.type === 'MultiPolygon') {
    polygonsCoords = geom.coordinates;
}

// 2. 每一座岛屿独立渲染,彻底消灭破面拉丝
polygonsCoords.forEach(coords => {
    const polygon = new AMap.Polygon({
        path: coords, 
        // ... 注入安全样式
    });
    
    // 3. 逻辑绑定,记录它的名称(如 '英国')
    polygon._geoJsonGeometry = geom;
    polygon._pName = pName; 

    // 鼠标悬浮时,记录当前悬浮的名称
    polygon.on('mouseover', () => { this.hoveredName = pName; this.refreshStyles(); });
    this.regionPolygons.push(polygon);
});

// 4. 在 refreshStyles 中,只要名字对上,主大陆和所有群岛一起高亮
this.regionPolygons.forEach(polygon => {
    if (polygon._pName && polygon._pName === this.hoveredName) {
        polygon.setOptions(safeActive);
    } else {
        polygon.setOptions(safeDefault);
    }
});

四、两种高亮模式的插件化

为了确保主干代码的简洁,将高亮相关的功能独立成单独的文件,以插件方式进行接入:

1. 按顺序引入 JS 文件

主核心文件负责绘制基础地图、点位和弹窗。高亮插件会自动寻找主干并挂载。

<script src="/js/footprintmap.js"></script>

<script src="/js/plugin-visited.js"></script>
<script src="/js/plugin-hover.js"></script>

2. 在配置中一键切换模式

footprintmap.js 顶部的 CONFIG 中,你可以随意切换高亮模式:

HIGHLIGHT: {
    // 模式选择:'visited' (永久高亮), 'hover' (悬浮高亮), 'none' (关闭)
    mode: 'visited', 
    
    geojsonUrls: [
        './static/data/provinces.geojson',
        './static/data/world.geojson' // 全球与省份可同时加载
    ],
    // 带有此标签的足迹不会触发高亮
    excludeTags: ['计划'], 
    
    style: {
        default: {
            strokeColor: '#ffffff', strokeOpacity: 0, strokeWeight: 0,
            fillColor: '#ffffff', fillOpacity: 0, zIndex: 1
        },
        active: {
            strokeColor: '#5ee7df', strokeOpacity: 0.8, strokeWeight: 1,
            fillColor: '#06beb6', fillOpacity: 0.07, zIndex: 10
        }
    }
}

五、结语

所有的源码、数据处理示例均已更新至 GitHub 仓库: XiaoTen-FootprintMap GitHub

如果你觉得这个项目还不错,欢迎去给个 Star ⭐️ 支持一下!快去更新你的足迹地图,点亮你的世界版图吧!🌍

评论