5.26
从零搭建一个工业风地图面板,坐标转换、飞行动画、在线搜索一气呵成。
最近在做一个铁路站点管理系统,后端是 FastAPI,前端打算做成 SPA。核心需求很简单:能在地图上看到站点在哪、点进去能编辑、能在地图上直接新增。听起来是个"调个 Leaflet 就完事"的活,实际踩了一圈才知道,地图相关的坑远比想象的多。
单文件自包含:把所有东西塞进一个 HTML
这个地图页面最终是一个完整的单文件应用——map.html,CSS inline、JS inline、Leaflet 的样式表也直接嵌进去了,外部依赖只剩一个 jsdelivr 的 Leaflet CDN。
为什么这么干?因为这个项目部署环境不确定,越少外部依赖越稳。CSS 变量定义了一套暖色工业风主题(深色侧边栏 + 琥珀色高亮),视觉上比默认的 Leaflet 控件好看不少。
坐标转换:WGS84 ↔ GCJ-02 双向互转
在中国用地图,绕不开火星坐标系。后端存的都是 WGS84,但高德底图是 GCJ-02。之前只做了 WGS84→GCJ-02 单向转换,显示没问题,但地图上点击取点新增站点的坐标就偏了——因为 Leaflet 拿到的经纬度是 GCJ-02 的,直接存进数据库就会偏几百米。
解决办法是实现 GCJ-02→WGS84 的逆变换。用的是常见的迭代反推法——先正向算出偏移量,再从原坐标减掉偏移,两次迭代精度足够:
function gcjToWgs(lng, lat){
var g = wgsToGcj(lng, lat);
var dLng = g[0] - lng, dLat = g[1] - lat;
return [lng - dLng, lat - dLat];
}
这样无论是从数据库拿来显示(WGS→GCJ),还是从地图点击取点存回去(GCJ→WGS),都能对齐了。
顺便把三个示例站点的经纬度精度从小数点后三位提到四位,石家庄站从 114.487, 38.042 改到 114.4869, 38.0419,在地图上定位明显更准。
flyTo:不止是飞,还要"高亮+自动缩放+侧边栏联动"
之前点了侧边栏的站点卡片,地图只是简单地 flyTo 过去,体验很干。
现在做成了一个完整的交互链条:
- 飞行动画——飞到目标点,缩放级别拉到 16,时长 1 秒,节奏感刚好
- 高亮脉冲——目标标记点放大到 16px、变色为琥珀色、加粗白色边框,2 秒后恢复原样。一眼就能看到飞到了哪
- 自动弹窗——飞行结束后自动打开详情弹窗,加载站点完整信息
- 侧边栏滚动——右侧卡片列表同步滚动到该站点,两边联动
// 高亮脉冲
m.setRadius(16);
m.setStyle({fillColor:'#b8852e', color:'#fff', weight:4, fillOpacity:1});
setTimeout(function(){ /* 恢复 */ }, 2000);
在线搜索:用高德 API 找真实坐标
搜索框支持高德 POI 搜索,输入关键词返回真实坐标和名称。搜索结果直接在地图上标注,点击"在此创建车站"就能跳转到新增表单,经纬度自动填入。
这里也处理了坐标转换——高德返回的是 GCJ-02,标记在地图上直接用,但传给新建表单的是转换后的 WGS84。
两个修了一上午的 Bug
Bug 1:window.flyTo is not a function
HTML 的 onclick="flyTo(...)" 调用不到 IIFE 闭包里的函数。一开始把 window.flyTo = flyTo 写在函数定义前面,结果 flyTo 还没声明就是 undefined。最后把所有 window 暴露语句移到 IIFE 末尾——等所有函数定义完再挂到 window 上,这个顺序问题才算彻底解决。
Bug 2:页面加载时 Router 未定义
侧边栏的地图链接跳转会经过 SPA 路由,但如果路由模块还没加载完就点了链接,直接白屏。加了一层守卫检查,模块未就绪时降级为 window.location.href 直接跳转。
小结
一天下来,这个地图从一个"勉强能看"变成"用着顺手"的状态。核心收获三条:
- 在中国做地图,坐标转换不是"要不要做"的问题,是"不做会偏几百米"的问题。 WGS↔GCJ 双向转换必须从一开始就考虑好数据流向。
- 单文件自包含在部署端是不确定因素时是最稳的选择。 多一个 CDN 依赖就多一个故障点。
window.xxx赋值要写在定义之后。 看起来简单,但在 IIFE 里代码一长就容易放错位置。
接下来该做的是站点的网格化管理视图和批量导入功能,地图上的交互也应该还有不少优化空间——比如聚合标记、热力图、轨迹回放之类的,慢慢来。

浙公网安备 33010602011771号