简介:百度离线地图示例V3.0是一套基于百度地图JavaScript API V3.0的离线开发工程,面向需要在无网络或弱网环境获得地图能力的开发者,可用于车载导航、户外作业与内网部署等场景。压缩包共1174个文件、约9.3MB,其中1093个jpg为按层级组织的256×256地图切片,48个js文件包含核心初始化脚本init.js与可扩展的modules模块,另外少量png、gif、cur等构成控件图标与交互样式,并附带html与txt说明文件。这套示例搭建了完整的离线地图技术骨架,读者可借此学习切片本地化组织方式、init.js对地图中心点与缩放级别的配置逻辑,以及通过modules扩展标记、路径等功能的调用方法,从而快速移植出适配自有业务的离线地图应用。目前已有8227人学习下载,对希望深入理解百度地图离线方案的中高级前端或GIS开发者具有实用参考价值。 做地图开发这几年,我总结出一条铁律:能在上线前暴露的问题都不叫问题,真正让人头皮发麻的是客户现场告诉你“我们这台机器只能内网访问”。尤其园区、电网、野外作业这类场景,数据不能出内网,地图却必须能用。百度离线地图示例V3.0 说白了就是一套把百度地图 JavaScript API 第三版跑在完全断网环境下的落地参考,核心解决三件事:API 脚本本地化、瓦片数据本地化、常用交互能力本地化。项目一旦搭好,地图初始化、缩放、打点、画范围、只显示某个市这些功能在纯内网里都能正常跑。这篇文章适合正在做内网地图项目、离线大屏展示,或者想彻底搞懂瓦片地图机制的开发同学参考。
1. 离线地图 V3.0 的整体设计思路
1.1 为什么离线不砍瓦片,而是把瓦片搬到本地
很多非 GIS 背景的同事第一反应是:没网了就别加载地图底图了呗,用个静态图片打底不就行了。静态图片在定点展示场景勉强能用,但一旦需要缩放、平移,图片就露馅了。地图的本质是瓦片金字塔,每个缩放级别下用规则网格把世界切成一堆小方块,浏览器只需要加载当前视野内的几十张图,视觉上就是一张完整地图。所以离线版不是不做瓦片,而是把在线瓦片服务搬到本地,让 getTilesUrl 从线上 URL 改为相对路径。这样缩放逻辑、缓存机制、overlay 叠加全部保留,只是数据源的请求从外网换成了本地。
这么做还有一个隐藏的好处:加载速度快。内网瓦片延迟通常是毫秒级,比公网 CDN 稳得多。尤其做园区导览、巡检可视化这类对交互流畅度有要求的页面,离线瓦片反而体验更好。
1.2 前端离线架构:静态资源 + 本地瓦片目录
整个离线示例的目录结构其实非常朴素:一个 HTML 页面、一份本地化的 API 核心 JS、一个 tiles 瓦片目录、再加上若干示例用的 JS 文件。有条件的团队会再用 Nginx 起一个静态文件服务,把瓦片目录暴露为http://localhost:8080/tiles/xxxx.png,前端代码里相对路径和绝对路径都可以。没有条件的直接在file://协议下打开也能跑——V3.0 离线版对本地文件协议的支持比较友好,但要注意 Chrome 对本地文件的某些跨域限制,建议用 Nginx 或者直接上 Electron,省得被 CORS 坑。
架构上的关键决定是不要让前端代码直接连数据库或文件系统,而是维持一个静态文件服务层。因为后续要加搜索、定位、轨迹回放,都依赖一个能访问本地数据的后端接口,静态文件服务是这一切的最小公共底座。
1.3 与天地图离线、自建瓦片服务的横向对比
这里多说一句选型。离线地图不止百度一家能做,很多项目问我为什么不用天地图或 Leaflet 加自建瓦片。我把实际对比的结果放在下面:
| 方案 | 坐标系 | 离线落地难度 | 适合场景 |
|---|---|---|---|
| 百度离线 V3.0 | BD-09 / BD09MC | 低,社区方案成熟 | 已有百度系代码、POI/行政区划数据绑定的项目 |
| 天地图离线 | CGCS2000 / WGS84 | 中,数据要自己切 | 测绘资质、政务公开类项目 |
| Leaflet + 自建瓦片 | WGS84 / GCJ02 均可 | 中高,自由度大 | 新项目、定制渲染需求强 |
如果项目里已经用了百度地图开放平台的 JS API,历史代码里全是 BMap 相关对象,那切到 Leaflet 等于把标注、聚合、轨迹全部重写,成本远大于离线化的代价。反过来,如果项目还没写代码、精度要求高、数据涉及国家标准坐标系,那可以考虑天地图离线。标题里这个示例 V3.0 最大的意义,是给存量百度项目提供一条低改造成本的内网化路径。
2. 核心原理:瓦片、投影与 API 本地化
2.1 瓦片金字塔与 URL 规则的秘密
要理解离线地图,先理解瓦片编号。地图从第 3 级到第 19 级,每一级把整个世界切分成2^zoom级别的网格,瓦片坐标通常用 z/x/y 表示:z 是缩放级别,x 是列号,y 是行号。在线版百度地图请求瓦片时,URL 长这样:
// 在线百度瓦片 URL(示意) https://online0.map.bdimg.com/onlinelabel/?qt=tile&x={x}&y={y}&z={z}&styles=pl但你不需要自己去拼这个 URL——JS API 内部会算好当前视野需要的瓦片坐标,然后调用我们配置的 getTilesUrl 去取图。离线版要做的,就是提前把指定范围内的瓦片按同样的目录规则下载好,然后改一个规则:不拿 x/y/z 去请求在线服务,而是拼出本地路径。
2.2 百度坐标系与 BD09MC 投影
不少人在离线项目里遇到“地图正常,设备 GPS 点总是飘”的问题,根因是坐标系。百度地图用的 BD-09 是对国测局 GCJ-02 坐标再做一次二次加密漂移的结果,和手机 GPS 拿到的 WGS-84 原始坐标直接画上去,偏差通常在几百米量级。离线版依然是 BD-09 坐标系,不存在离线就把坐标改成标准坐标的说法。正确做法是在后端把 GPS 坐标转成百度坐标再下发,或者用 map 的 projection 对象做一次坐标转换。
BD09MC 则是百度地图使用的墨卡托变体投影,是所有瓦片切割和坐标计算的底层。大多数前端开发不需要手写投影公式,但做瓦片批量下载工具时要特别注意:网上很多工具默认输出 XYZ/谷歌编号,直接拿来当百度瓦片用会产生偏移甚至显示错乱,切片时一定选“百度/BD09MC”格式。
2.3 JS API 本地加载的三步走
V3.0 离线版不是把整个开放平台 API 原封不动拿下来就可以,需要处理三个环节:
- 本地化脚本:把 mapapi.js 及其依赖的 core、module 等文件放到本地,确保页面加载不到外网资源时也能拿到 BMap 全局对象。
- 拦截远程请求:部分功能模块在初始化时还是会尝试访问外网,需要把初始化参数里的资源地址指向本地。
- 替换瓦片源:初始化后通过 addTileLayer 把默认在线图层替换成本地瓦片层。
这个过程不同版本细节差异较大,建议以你下载到的离线包内说明为准。判断离线是否成功有个很简单的指标:打开浏览器开发者工具,切到 Network 面板,把所有外网域名请求清零,地图依然能拖动、缩放,就是内网部署可行的标志。
3. 从零落地:完整实操过程
3.1 准备离线瓦片数据
先说最费工夫的一步——瓦片数据。有正规数据源的团队,可以用切片工具把标准格式的栅格地图切成百度编号的瓦片;没有自建数据的,则需要借助公网在线地图在授权范围内下载某个行政区的瓦片,或者直接采购第三方离线数据包。我的建议是:先把范围圈小,不要一上来就下全国。比如只做某个市,最大级别到 17 级,下载完大概几 GB;如果还要局部 19 级,再单独把热点区域高精度补一层。
下载工具的常见输出结构是:
tiles ├── 3 │ ├── 0 │ │ └── 0.png │ └── 1 │ └── 0.png ├── 4 └── ...路径上的第一层是 zoom,第二层是 x,第三层是 y.png。这个结构可以直接映射到前端 getTilesUrl 的返回路径。注意文件夹名和文件名的拼写保持全小写,Linux 部署时大小写敏感,Windows 上没问题,换到 Linux 服务器就 404 的坑我踩过不止一次。
3.2 引入本地 API 并初始化地图
示例页面里加载 API 的推荐方式是这样:
<script src="./local/mapapi.js?v=3.0"></script> <script> // 等 BMap 全局对象出现后再初始化 if (typeof BMap !== 'undefined') { const map = new BMap.Map('map-container', { minZoom: 3, maxZoom: 19, enableMapClick: false }); map.centerAndZoom(new BMap.Point(120.15, 30.28), 12); map.enableScrollWheelZoom(true); } </script>注意enableMapClick: false是我推荐默认关掉的。离线环境里点击底图弹出的 POI 信息窗多半是死链,而且这个功能还会引入额外的外网请求,关掉最省心。如果项目需要点击交互,建议用自定义 marker 和自定义信息窗口代替。
3.3 自定义瓦片层接入本地目录
核心的一段代码是自定义 TileLayer:
const offlineLayer = new BMap.TileLayer({ isTransparentPng: true }); offlineLayer.getTilesUrl = function (tileCoord, zoom) { // 根据缩放级别和瓦片坐标拼接本地相对路径 return './tiles/' + zoom + '/' + tileCoord.x + '/' + tileCoord.y + '.png'; }; map.addTileLayer(offlineLayer);如果本地瓦片和默认底图的坐标编号存在偏差,可以在 return 之前对 x、y 做修正。这个修正关系取决于瓦片来源:来自百度在线切片工具的,通常不需要改;来自谷歌/OSM 标准工具的,需要整体偏移。建议拿到瓦片后先用几个不同级别的坐标对比定位,确认 x/y 公式正确后再涉及业务代码。
3.4 只显示某个市和行政边界裁剪
项目里有个高频需求是“只显示某个市”。实现分两层:第一层在瓦片数据层,只下载目标城市的瓦片,视野外的地图自然空白;第二层在交互层,用行政区边界数据生成 Polygon 覆盖物,把边界以外盖上半透明蒙层,同时监听地图移动结束事件,把视野拉回界内,避免用户拖到一片空白区域不知道发生了什么。
用 V3.0 的 Polygon 来画边界很直接:
const boundary = new BMap.Polygon(cityBoundaryPoints, { strokeColor: '#1E90FF', strokeWeight: 2, fillColor: '#1E90FF', fillOpacity: 0.1 }); map.addOverlay(boundary);cityBoundaryPoints 可以从离线化的行政区划数据里取。这里有个经验:边界点数据不要太多,单城市几千个点足够,太多会拖慢 Canvas 渲染。需要中心点就执行map.setCenter(cityCenter),限制视野用map.setBounds(boundary.getBounds())配合拖拽事件里的判断。
3.5 覆盖物与交互功能补齐
底图能显示了,剩下的标注、气泡、聚合这些都是常规操作。V3.0 里 BMap.Marker、BMap.InfoWindow、BMapLib.MarkerClusterer 都支持离线使用,只要引入对应 JS 文件即可。要注意的是,有些扩展库内部会请求地图 API 的在线资源,必须在引入前先替换或直接引入本地版本。
做过一个园区大屏项目,当时把几百个设备点全部用 MarkerClusterer 聚合,离线环境下的聚合性能比在线还顺滑,因为网络延迟没了,瓶颈只剩浏览器渲染。
4. 常见问题与调试实录
4.1 白屏、瓦片不出来的排查顺序
这类问题接手过很多次,总结一套排查顺序:
| 现象 | 排查点 | 解决办法 |
|---|---|---|
| 页面完全白屏 | BMap 对象是否已经加载 | 看 Console 是否有BMap is not defined,检查本地 API 路径 |
| 有地图但只有蓝色底 | 瓦片 404 | Network 面板看瓦片请求路径,和目录结构逐级对比 |
| 部分级别瓦片加载失败 | 下载级别不全 | 检查 tiles 目录下 zoom 文件夹是否存在 |
| 拖拽后出现空白块 | 视野超出已下载范围 | 加地图边界限制,或者补充下载范围 |
绝大多数瓦片不显示的问题,根因都是 getTilesUrl 返回的本地路径和实际文件路径对不上。不要猜,直接在浏览器里打开一个瓦片 URL,比如/tiles/12/338/515.png,看能不能出图。出不来就说明目录结构或下载工具输出格式不对。
4.2 GPS 定位偏移和不同手机的定位差异
离线环境下的定位是个容易忽略的坑。百度 JS API 的 geolocation 功能在离线时往往不可用,因为它依赖在线服务返回逆地理编码。我自己做过一轮不同手机的定位测试,结论挺典型:iPhone 在纯 GPS 模式下的 WGS-84 坐标直接标到百度地图上会偏 300 到 500 米,Android 部分机型如果开启了 GPS 加网络辅助,返回的坐标反而接近 GCJ-02,偏移方向还不一致。所以不要在展示层做坐标猜测,统一在后端做 WGS-84 到 BD-09 的转换,前端只展示转换后的坐标。
4.3 离线环境下的搜索、定位替代方案
离线没有在线 POI 搜索是很多业务接受不了的。我的替代方案是:从原始数据源导出一份本地 POI 表(名称、经纬度、分类、地址),用后端接口提供模糊查询,前端拿到结果后直接map.addOverlay打点,不需要任何在线服务。只要数据质量到位,体验不比在线差,还能顺带实现“只搜本园区、本市”的限定搜索。
4.4 往 Qt、移动端扩展的注意点
这套瓦片目录不仅给 Web 用。有团队问过 Qt 加载离线瓦片地图怎么做,思路其实一样:QWebEngine 里加载本地 HTML,把 getTilesUrl 指向本地资源路径即可;纯原生客户端则直接按 z/x/y 读瓦片文件,用 QGraphicsView 或 OpenGL 纹理绘制。瓦片数据是跨端复用的,切好一套数据,Web、Qt、iOS/Android 都能吃。关键是把瓦片命名规范和坐标转换算法封装成公共模块,别每个端各写一套。
有些终端厂商提的 CE 版、定制版,本质也是把同一套离线瓦片能力封装进特定设备的壳子里,原理没变。手机上那种悬浮小窗、只显示本区的快捷模式,同样可以做,前端组件自己实现,数据源切成当地瓦片和本地 POI 就行。
最后分享一个我自己的习惯:离线地图项目验收前,一定把服务器网线拔了完整跑一遍核心流程——初始化、缩放、打点、搜索、定位。在线 Demo 能跑的功能,断网后往往只剩七八十个。百度离线地图示例V3.0 的价值不在于技术多高深,而是它把所有“在线依赖”一个个找出来并替换掉,让地图真正变成内网里一个安安静静的静态资源目录。多踩几次坑之后你会发现,离线化最难的从来不是写代码,而是把所有隐形的在线请求都揪出来。
本文还有配套的精品资源,点击获取