简介:基于百度地图JavaScript API V3.0构建的离线地图示例,面向无外网、弱网环境下的Web前端或GIS开发人员。压缩包内共1174个文件,以1093张JPG地图切片为主体,按缩放层级切分,配合初始化脚本、功能扩展模块、HTML示例页面及图标光标资源,构成一套可在本地服务器部署运行的完整离线地图方案;整体约9.3MB,已有8238人浏览学习。开发人员可通过该示例理解地图切片的组织与加载机制,掌握初始化脚本中中心点、缩放级别、本地切片路径等参数配置,并调用modules目录中的接口扩展标注、轨迹、图层等交互能力。压缩包内包含演示页面,便于核对运行效果;配套的光标与UI图标资源也方便二次开发时沿用。该方案适用于车载导航、户外作业、内网系统及网络受限区域的地图展示,是掌握百度地图离线化开发与二次应用的实用参考。1. 离线地图不是简单下载个JS:它是一整套“断网也能跑”的地图服务有一次做某企业内部系统的可视化大屏,甲方明确说服务器隔离部署,浏览器连公网都不通。在线地图肯定是没戏了,就只能把百度地图的JS API和瓦片全部搬到本地。百度离线地图示例V3.0就是干这个的,它把离线场景需要的核心JS库、瓦片目录、示例页面都整理好了,解压后启动一个本地服务就能跑。适合两类人:一类是接政务、能源、安防这类内网可视化项目的开发,地图必须能离网渲染;另一类是在野外或无信号现场做移动演示的工程师。下文从部署到排坑,讲一轮我实际拆它的过程。2. 离线地图的架构脉络:从API到瓦片全链路要把“在线”改成“离线”,先得搞清楚在线时浏览器到底做了哪几件事。说白了就两件:第一,引用一个远程Script拿到BMap对象;第二,地图初始化之后,按当前视野范围请求一堆小瓷砖图片,也就是瓦片。离线V3.0把这两件事都变成了本地文件读取,但代价是:你需要自己保证JS路径、瓦片路径、坐标体系三处不打架。2.1 在线与离线的本质差异在线模式,页面里的script标签指向CDN域名,瓦片请求走的也是专用瓦片服务地址。浏览器不需要关心这些地址长什么样,域名定了,规则固定,地图组件自己拼URL。离线模式下,这些全部失效。JS文件要复制到本地静态目录,瓦片也要落到服务器某个目录下,然后由你手动配置瓦片的请求模板,告诉地图怎么在本地找图。对比项在线模式离线V3.0JS来源CDN远程地址本地静态目录瓦片来源在线瓦片服务器本地tile目录是否需要AK部分功能需要不需要能否断网不能能地图API能力完整基础功能可用AK这个点很有迷惑性。离线示例包把地图初始化逻辑都封装好了,不向在线服务申请密钥,所以部署时不需要申请AK,但同时也意味着绑定在线服务的高级能力不可用,比如POI检索、驾车路线规划,这部分能力离线包是不会有的。2.2 离线资源包的目录结构与运行前提解压示例V3.0之后,典型的目录长这样(不同版本文件名略有差异,以实际解压为准):map_offline/ ├── lib/ │ └── baidumap_offline_v3.0.js ├── tiles/ │ ├── 3/ │ ├── 4/ │ ├── ... │ └── 17/ ├── example/ │ ├── index.html │ └── demo.js └── readme.txttiles目录是瓦片,按级别分文件夹;example是入口页面。很多人第一反应是双击index.html就看效果,这恰恰是第一个坑。浏览器file协议下对JS的加载管制很严格,打开控制台大概率一片红,最常见的是CORS报错。正确做法是用任意HTTP静态服务把整个目录映射出去。我一般直接用nginx,配置一段:server { listen 8080; server_name localhost; root /opt/map_offline; index index.html; location / { try_files $uri $uri/ =404; } }然后把页面地址改成本机IP加端口访问,比如 http://本机IP:8080/example/index.html。注意root指向的目录要和工程解压路径一致,别放在带中文的路径下,Windows下尤其容易出问题。2.3 第一个可运行页面:最小离线地图工程里的演示页面通常包含完整配置,我把它压缩成最小可运行版本,方便对照排查:<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>离线地图最小示例</title> <style> #map_container { width: 100%; height: 600px; } </style> </head> <body> <div id="map_container"></div> <!-- 注意这里指向lib目录下的离线库 --> <script src="../lib/baidumap_offline_v3.0.js"></script> <script> // 指定容器ID,创建地图实例 var map = new BMap.Map("map_container"); // 中心点用经纬度,比城市名更可控 map.centerAndZoom(new BMap.Point(116.403, 39.914), 12); map.enableScrollWheelZoom(true); </script> </body> </html>这里有两个点要说明。地图对象挂载在容器上,容器必须有明确高度,写成百分比会间接导致白屏。centerAndZoom的第二个参数是初始级别,示例包瓦片一般做到17级,你定成18级,边缘区域会空白。另外,某些离线包对城市名解析支持不完整,我建议直接用经纬度坐标初始化,省去地名解析环节。这个最小页面跑通之后,再叠加后面的业务功能,排查起来就简单多了。3. 瓦片与坐标系:离线地图的“地基”工程如果只是跑通一个页面,上面内容就够了,但真正落地内网项目时,瓦片和坐标才是花时间最多的地方。瓦片决定你看到多少级、多清晰,坐标系决定你标的点位准不准。这两件事没理顺,后面所有业务叠加都是白搭。3.1 瓦片层级、编号与缩放级别的对应关系百度地图用的是墨卡托投影,全球被切成2的z次方行乘2的z次方列的小方块,z就是缩放级别,行业里叫level或z级。每个瓦片在一个级别下有唯一的坐标编号,常规的URL模板是 /tiles/{z}/{x}/{y}.png,x是横向编号,y是纵向编号。注意这里的y方向是从上往下数的,跟数学坐标里的y是反的,很多刚从谷歌地图切过来的同学会在这里翻车。示例包内置的瓦片级别不一定全,我一般先确认tiles目录下有哪几级文件夹,再设置地图minZoom和maxZoom,让用户最多只能在已有级别里缩放:var map = new BMap.Map("map_container", { minZoom: 3, // 和 tiles/3 对应 maxZoom: 17, // 和 tiles/17 对应 enableMapClick: false // 离线环境没有POI数据,关掉点击事件 });如果你硬把map拉到18级,离线包会尝试请求tiles/18/xxx.png,目录里没这层,瓦片请求全是404,控制台刷屏,画面就花了。所以拿到示例包第一件事,数一下级别,再决定地图的缩放区间。各级瓦片的使用场景有个大致划分,给个经验表:级别范围典型用途数据量3-8省域、大区域很小9-13市区、县区中等14-17街道、园区成倍膨胀3.2 离线瓦片落盘组织与加载规则瓦片切好之后的落盘方式,直接决定加载速度。示例包默认是文件目录逐级存放,与URL路径一一对应。也就是说,请求 /tiles/12/3386/1538.png 时,服务器上必须真实存在 tiles/12/3386/1538.png 这个文件,路径敏感,大小写不能错。Linux服务器上尤其严格,Windows跑得好好的,搬到Linux上目录名变成大写就404。另一个容易忽略的是瓦片请求的缓存策略。内网环境虽然不花钱,但流量和磁盘IO还是要管理的。我一般会在nginx上给tiles目录加一个强缓存:location /tiles/ { expires 30d; add_header Cache-Control "public, immutable"; }这样用户在同一个地图范围内来回拖拽时,瓦片直接走浏览器本地缓存,服务器压力小很多。发布新瓦片后记得清掉客户端缓存,否则总有人看到“老地图”——这个问题我在第5章会专门讲。3.3 坐标系换算:GPS转BD09百度地图坐标系是BD09,是在国测局GCJ02基础上又做了一层偏移。从GPS设备或常见数据库里拿到的原始坐标多数是WGS84,直接画上去,点位大约会偏移几百米,在楼宇级别的地图上简直没法看。所以离线项目里,数据入库前就要完成坐标转换。如果业务数据源本身是其它地图平台导出的GCJ02,还得先把GCJ02转BD09,这一步最容易被忽略。网上广泛流传的WGS84转BD09算法,核心思路是先转成GCJ02,再在此基础上二次偏移,下面给出一个我常用的JS实现:// WGS84 -> GCJ02 -> BD09 的简化处理 function wgs84ToBd09(lng, lat) { var gcj = wgs84ToGcj02(lng, lat); // 第一次偏移 var x = gcj.lng, y = gcj.lat; var z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * Math.PI * 3000 / 180); var t = Math.atan2(y, x) + 0.000003 * Math.cos(x * Math.PI * 3000 / 180); return { lng: z * Math.cos(t) + 0.0065, lat: z * Math.sin(t) + 0.006 }; }其中 wgs84ToGcj02 是公开的偏移算法,网上能查到完整实现,我这里不贴全。这套转换放在后端做更合适,数据量大的时候还能提前转好存数据库,前端就只负责把正确坐标画上去。注意一点:BD09和GCJ02之间的差异是非线性的,不能简单用一个固定偏移量修正,必须用算法转。4. 离线地图的业务接入:标注、图层与交互纯内网环境里,地图没数据就是一张底图,真正的价值在于业务标注和图层叠加。仓库、设备、管网、车辆位置,这些数据全在你们自己库里,跟在线地图服务无关,离线环境下照样可以做得和在线一样丰富。4.1 在地图上打点:Marker与信息窗口最常见的是设备点位展示。初始化地图之后,遍历业务数据数组,逐个创建Marker加进去,点Marker弹信息窗口。var points = [ { lng: 116.404, lat: 39.915, name: "一号设备", status: "正常" }, { lng: 116.408, lat: 39.918, name: "二号设备", status: "告警" } ]; points.forEach(function (item) { // 创建坐标对象 var pt = new BMap.Point(item.lng, item.lat); // 创建标注,指定坐标 var marker = new BMap.Marker(pt); marker.addEventListener("click", function () { var info = '<div style="padding:8px;">' + '<h5>' + item.name + '</h5>' + '<p>状态:' + item.status + '</p></div>'; var window = new BMap.InfoWindow(info, { width: 180, height: 60 }); map.openInfoWindow(window, pt); }); map.addOverlay(marker); });注意几点。addOverlay既接受Marker也接受Polyline、Polygon等覆盖物,统一出口。infoWindow每次点击都新建,量大了会有内存压力,更好的做法是先声明一个共用的窗口实例,点marker时只更新内容再打开。这个示例包没有提供批量打点优化API,所以数据过千条时,前端仍然需要自己做聚合,常见做法是网格聚合,或者实现一个简单的canvas覆盖层。4.2 加载本地GeoJSON数据业务数据如果已经转成GeoJSON文件,离线环境也可以直接用ajax读进来渲染。百度的原生API没有直接吃GeoJSON的方法,需要自己解析坐标串。// 以jQuery为例,fetch也行 $.getJSON("./data/pipe.json", function (geojson) { // 判断类型,只演示LineString var features = geojson.features || []; features.forEach(function (feature) { if (feature.geometry.type === "LineString") { var coords = feature.geometry.coordinates.map(function (c) { return new BMap.Point(c[0], c[1]); }); // 创建折线,线宽和颜色按业务来 var polyline = new BMap.Polyline(coords, { strokeColor: "#ff0000", strokeWeight: 4, opacity: 0.8 }); map.addOverlay(polyline); } }); });这里有个数据处理细节。GeoJSON里存的是经度、纬度,顺序是lng、lat,而BMap.Point构造器接受的参数顺序同样是lng、lat,顺序对了就不用额外调整。但如果你从数据库导出的数据是lat在前,就必须在脚本里交换,否则点位会画到海里去,我见过不止一版数据栽在这里。4.3 离线环境下的逆地理编码替代方案在线API里,地址转坐标、坐标转地址都现成的,离线包把这块能力砍掉了。这是必然的,因为它依赖在线服务。在线功能离线替代方案地址->坐标 Geocoder数据入库时提前解析,存好经纬度坐标->地址 ReverseGeocoder自建地址库,或按中心点返回自定义名称POI关键词搜索离线无解,只能预置数据我的习惯是:业务数据在后台入库时先做一次在线解析,解析结果连同地址一起存库,以后离线部署直接读库。坐标转地址的自建方案,常见做法是把行政区边界和重点建筑做一张小表,通过点在多边形内的判断反查名称。这套东西不属于地图API的能力,但对于在纯内网做可视化的人来说,这样已经够用了。5. 离线地图排查指南:五个高频坑离线地图大部分问题集中在“没加载起来”“加载起来没图”“有图点位不准”三个层面。下面五条是我在多个项目里反复撞见的高频问题,逐一按现象、原因、解决写清楚。5.1 页面白屏,控制台报 BMap is not defined现象:地图区域是空的,打开浏览器控制台,红色报错指向BMap未定义。原因:离线JS库没有正常加载。常见有两种,一是script标签的src路径写错,尤其用相对路径时,从/example/index.html访问和从/访问,相对路径的基准不同,结果差一层;二是服务器根本就没把这个JS文件映射出来,直接访问URL变成404。解决:先在控制台Network面板看script请求的状态码。404的话,改用绝对路径并对照目录结构调整src,例如 /lib/baidumap_offline_v3.0.js。这里提醒一句,绝对路径开头带不带/,行为完全不同,静态服务根目录下面有没有lib目录,也直接影响结果。5.2 地图有网格背景但瓦片全是404现象:地图能初始化,能拖拽缩放,但底图是一片灰白色网格,控制台一堆瓦片404。原因:瓦片路径和本地文件对不上。最典型的有三种:tiles目录名不一样;z/y/x拼接顺序错误,拼成了z/x/y,但文件实际按z/y/x存放;请求了超出示例范围的级别。示例工程瓦片在tiles/3到tiles/17,地图却拉到18级。解决:打开Network面板里任意一条瓦片请求,看URL模板,然后到tiles目录下按同样路径查一遍文件是否存在。目录错就改名,顺序错就调整配置文件里的URL模板,级别越界就把maxZoom限制住。5.3 点位与底图错位几百米现象:Marker能显示,但位置和底图上的道路、建筑明显对不上,偏移量看着像固定值,又好像不完全固定。原因:坐标系没转换。业务数据是WGS84坐标,地图底图是BD09,两者之间存在非线性偏移,不能通过加一个固定值解决。解决:数据入库前用3.3节里的算法转换,前端拿到坐标前确认已经是BD09。已经入库的错误数据,只能在库里清洗重算,不要在前端逐条救火,数据量上去之后前端计算会影响地图流畅度。5.4 拖拽地图卡顿,拖动时白块闪烁现象:地图可以正常显示,但拖动过程中明显卡顿,松手时大面积空白闪烁,过一会儿才补上。原因:本地瓦片服务没有做缓存,每个瓦片每次都要重新读磁盘。机械硬盘环境下尤其明显,浏览器缓存策略也不对,反复请求相同图片。解决:参考3.2节nginx缓存配置,给tiles目录设置expires和Cache-Control。如果已经设了缓存还卡,再看瓦片大小,示例包导出的瓦片如果超过200KB,说明切片参数有问题,应重切压缩。瓦片切得越碎,加载越快,单张普通瓦片尽量控制在几十KB以内。5.5 file协议直接打开工程文档,各种样式和功能失灵现象:在Windows上双击example/index.html,地图没渲染出来,控制台显示跨域报错,一些JS方法执行异常。原因:浏览器对file协议下本地脚本的加载限制很严格,不从HTTP服务访问就会触发CORS或安全问题;部分浏览器甚至直接拦截本地JS加载。解决:不要用file协议运行离线地图工程。在工程根目录启动任意静态服务,nginx或tomcat都行,然后通过 http://IP:端口/ 访问。内网部署也一样,服务器上跑服务,浏览器访问服务器地址,不要试图直接用文件路径打开。6. 进阶与发布习惯:给离线地图加缓存,顺手留后悔药离线地图上线运行之后,真正麻烦的不是首次部署,而是后续更新。瓦片一旦换过,浏览器和nginx缓存会让用户端保留旧画面,你的“新地图”根本显示不出来。我自己的经验是:每次发布新版瓦片,先停掉nginx,清掉tiles目录里的过期文件,再改一个版本号目录名,比如tiles_v2,最后同步修改前端URL模板。这个版本号就是后悔药,万一新瓦片切得有问题,改一行配置就能回滚,不用重新发布整个工程。除了瓦片更新,还有两个细节值得做:第一,地图初始化时,把视口默认级别定在实际需要的最小级别,别一上来就拉近景。比如展示全市设备,初始级别定12而不是17,既减少首屏瓦片请求数,也让用户一眼看到全貌。从列表点进去要定位到某个设备时,再用map.panTo加setZoom强行跳转,这种交互模式在大屏项目里明显更省资源。第二,离线地图通常被嵌在大屏或者管理后台里,地图初始化不是越快越好,而是要和其他图表错开。常见做法是先渲染其它图表,再用setTimeout把地图初始化延迟到空闲期,避免首屏同时发几十个请求卡住浏览器。从那以后,我每次部署离线地图都强制检查三件事:JS文件请求是否200、瓦片URL与本地目录是否一一对应、业务坐标是否已经转成BD09。这三条没跑完,我连测试都不看。这套习惯帮我避免了至少三次上线翻车,希望帮到你。本文还有配套的精品资源,点击获取
简介:基于百度地图JavaScript API V3.0构建的离线地图示例,面向无外网、弱网环境下的Web前端或GIS开发人员。压缩包内共1174个文件,以1093张JPG地图切片为主体,按缩放层级切分,配合初始化脚本、功能扩展模块、HTML示例页面及图标光标资源,构成一套可在本地服务器部署运行的完整离线地图方案;整体约9.3MB,已有8238人浏览学习。开发人员可通过该示例理解地图切片的组织与加载机制,掌握初始化脚本中中心点、缩放级别、本地切片路径等参数配置,并调用modules目录中的接口扩展标注、轨迹、图层等交互能力。压缩包内包含演示页面,便于核对运行效果;配套的光标与UI图标资源也方便二次开发时沿用。该方案适用于车载导航、户外作业、内网系统及网络受限区域的地图展示,是掌握百度地图离线化开发与二次应用的实用参考。1. 离线地图不是简单下载个JS:它是一整套“断网也能跑”的地图服务有一次做某企业内部系统的可视化大屏,甲方明确说服务器隔离部署,浏览器连公网都不通。在线地图肯定是没戏了,就只能把百度地图的JS API和瓦片全部搬到本地。百度离线地图示例V3.0就是干这个的,它把离线场景需要的核心JS库、瓦片目录、示例页面都整理好了,解压后启动一个本地服务就能跑。适合两类人:一类是接政务、能源、安防这类内网可视化项目的开发,地图必须能离网渲染;另一类是在野外或无信号现场做移动演示的工程师。下文从部署到排坑,讲一轮我实际拆它的过程。2. 离线地图的架构脉络:从API到瓦片全链路要把“在线”改成“离线”,先得搞清楚在线时浏览器到底做了哪几件事。说白了就两件:第一,引用一个远程Script拿到BMap对象;第二,地图初始化之后,按当前视野范围请求一堆小瓷砖图片,也就是瓦片。离线V3.0把这两件事都变成了本地文件读取,但代价是:你需要自己保证JS路径、瓦片路径、坐标体系三处不打架。2.1 在线与离线的本质差异在线模式,页面里的script标签指向CDN域名,瓦片请求走的也是专用瓦片服务地址。浏览器不需要关心这些地址长什么样,域名定了,规则固定,地图组件自己拼URL。离线模式下,这些全部失效。JS文件要复制到本地静态目录,瓦片也要落到服务器某个目录下,然后由你手动配置瓦片的请求模板,告诉地图怎么在本地找图。对比项在线模式离线V3.0JS来源CDN远程地址本地静态目录瓦片来源在线瓦片服务器本地tile目录是否需要AK部分功能需要不需要能否断网不能能地图API能力完整基础功能可用AK这个点很有迷惑性。离线示例包把地图初始化逻辑都封装好了,不向在线服务申请密钥,所以部署时不需要申请AK,但同时也意味着绑定在线服务的高级能力不可用,比如POI检索、驾车路线规划,这部分能力离线包是不会有的。2.2 离线资源包的目录结构与运行前提解压示例V3.0之后,典型的目录长这样(不同版本文件名略有差异,以实际解压为准):map_offline/ ├── lib/ │ └── baidumap_offline_v3.0.js ├── tiles/ │ ├── 3/ │ ├── 4/ │ ├── ... │ └── 17/ ├── example/ │ ├── index.html │ └── demo.js └── readme.txttiles目录是瓦片,按级别分文件夹;example是入口页面。很多人第一反应是双击index.html就看效果,这恰恰是第一个坑。浏览器file协议下对JS的加载管制很严格,打开控制台大概率一片红,最常见的是CORS报错。正确做法是用任意HTTP静态服务把整个目录映射出去。我一般直接用nginx,配置一段:server { listen 8080; server_name localhost; root /opt/map_offline; index index.html; location / { try_files $uri $uri/ =404; } }然后把页面地址改成本机IP加端口访问,比如 http://本机IP:8080/example/index.html。注意root指向的目录要和工程解压路径一致,别放在带中文的路径下,Windows下尤其容易出问题。2.3 第一个可运行页面:最小离线地图工程里的演示页面通常包含完整配置,我把它压缩成最小可运行版本,方便对照排查:<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>离线地图最小示例</title> <style> #map_container { width: 100%; height: 600px; } </style> </head> <body> <div id="map_container"></div> <!-- 注意这里指向lib目录下的离线库 --> <script src="../lib/baidumap_offline_v3.0.js"></script> <script> // 指定容器ID,创建地图实例 var map = new BMap.Map("map_container"); // 中心点用经纬度,比城市名更可控 map.centerAndZoom(new BMap.Point(116.403, 39.914), 12); map.enableScrollWheelZoom(true); </script> </body> </html>这里有两个点要说明。地图对象挂载在容器上,容器必须有明确高度,写成百分比会间接导致白屏。centerAndZoom的第二个参数是初始级别,示例包瓦片一般做到17级,你定成18级,边缘区域会空白。另外,某些离线包对城市名解析支持不完整,我建议直接用经纬度坐标初始化,省去地名解析环节。这个最小页面跑通之后,再叠加后面的业务功能,排查起来就简单多了。3. 瓦片与坐标系:离线地图的“地基”工程如果只是跑通一个页面,上面内容就够了,但真正落地内网项目时,瓦片和坐标才是花时间最多的地方。瓦片决定你看到多少级、多清晰,坐标系决定你标的点位准不准。这两件事没理顺,后面所有业务叠加都是白搭。3.1 瓦片层级、编号与缩放级别的对应关系百度地图用的是墨卡托投影,全球被切成2的z次方行乘2的z次方列的小方块,z就是缩放级别,行业里叫level或z级。每个瓦片在一个级别下有唯一的坐标编号,常规的URL模板是 /tiles/{z}/{x}/{y}.png,x是横向编号,y是纵向编号。注意这里的y方向是从上往下数的,跟数学坐标里的y是反的,很多刚从谷歌地图切过来的同学会在这里翻车。示例包内置的瓦片级别不一定全,我一般先确认tiles目录下有哪几级文件夹,再设置地图minZoom和maxZoom,让用户最多只能在已有级别里缩放:var map = new BMap.Map("map_container", { minZoom: 3, // 和 tiles/3 对应 maxZoom: 17, // 和 tiles/17 对应 enableMapClick: false // 离线环境没有POI数据,关掉点击事件 });如果你硬把map拉到18级,离线包会尝试请求tiles/18/xxx.png,目录里没这层,瓦片请求全是404,控制台刷屏,画面就花了。所以拿到示例包第一件事,数一下级别,再决定地图的缩放区间。各级瓦片的使用场景有个大致划分,给个经验表:级别范围典型用途数据量3-8省域、大区域很小9-13市区、县区中等14-17街道、园区成倍膨胀3.2 离线瓦片落盘组织与加载规则瓦片切好之后的落盘方式,直接决定加载速度。示例包默认是文件目录逐级存放,与URL路径一一对应。也就是说,请求 /tiles/12/3386/1538.png 时,服务器上必须真实存在 tiles/12/3386/1538.png 这个文件,路径敏感,大小写不能错。Linux服务器上尤其严格,Windows跑得好好的,搬到Linux上目录名变成大写就404。另一个容易忽略的是瓦片请求的缓存策略。内网环境虽然不花钱,但流量和磁盘IO还是要管理的。我一般会在nginx上给tiles目录加一个强缓存:location /tiles/ { expires 30d; add_header Cache-Control "public, immutable"; }这样用户在同一个地图范围内来回拖拽时,瓦片直接走浏览器本地缓存,服务器压力小很多。发布新瓦片后记得清掉客户端缓存,否则总有人看到“老地图”——这个问题我在第5章会专门讲。3.3 坐标系换算:GPS转BD09百度地图坐标系是BD09,是在国测局GCJ02基础上又做了一层偏移。从GPS设备或常见数据库里拿到的原始坐标多数是WGS84,直接画上去,点位大约会偏移几百米,在楼宇级别的地图上简直没法看。所以离线项目里,数据入库前就要完成坐标转换。如果业务数据源本身是其它地图平台导出的GCJ02,还得先把GCJ02转BD09,这一步最容易被忽略。网上广泛流传的WGS84转BD09算法,核心思路是先转成GCJ02,再在此基础上二次偏移,下面给出一个我常用的JS实现:// WGS84 -> GCJ02 -> BD09 的简化处理 function wgs84ToBd09(lng, lat) { var gcj = wgs84ToGcj02(lng, lat); // 第一次偏移 var x = gcj.lng, y = gcj.lat; var z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * Math.PI * 3000 / 180); var t = Math.atan2(y, x) + 0.000003 * Math.cos(x * Math.PI * 3000 / 180); return { lng: z * Math.cos(t) + 0.0065, lat: z * Math.sin(t) + 0.006 }; }其中 wgs84ToGcj02 是公开的偏移算法,网上能查到完整实现,我这里不贴全。这套转换放在后端做更合适,数据量大的时候还能提前转好存数据库,前端就只负责把正确坐标画上去。注意一点:BD09和GCJ02之间的差异是非线性的,不能简单用一个固定偏移量修正,必须用算法转。4. 离线地图的业务接入:标注、图层与交互纯内网环境里,地图没数据就是一张底图,真正的价值在于业务标注和图层叠加。仓库、设备、管网、车辆位置,这些数据全在你们自己库里,跟在线地图服务无关,离线环境下照样可以做得和在线一样丰富。4.1 在地图上打点:Marker与信息窗口最常见的是设备点位展示。初始化地图之后,遍历业务数据数组,逐个创建Marker加进去,点Marker弹信息窗口。var points = [ { lng: 116.404, lat: 39.915, name: "一号设备", status: "正常" }, { lng: 116.408, lat: 39.918, name: "二号设备", status: "告警" } ]; points.forEach(function (item) { // 创建坐标对象 var pt = new BMap.Point(item.lng, item.lat); // 创建标注,指定坐标 var marker = new BMap.Marker(pt); marker.addEventListener("click", function () { var info = '<div style="padding:8px;">' + '<h5>' + item.name + '</h5>' + '<p>状态:' + item.status + '</p></div>'; var window = new BMap.InfoWindow(info, { width: 180, height: 60 }); map.openInfoWindow(window, pt); }); map.addOverlay(marker); });注意几点。addOverlay既接受Marker也接受Polyline、Polygon等覆盖物,统一出口。infoWindow每次点击都新建,量大了会有内存压力,更好的做法是先声明一个共用的窗口实例,点marker时只更新内容再打开。这个示例包没有提供批量打点优化API,所以数据过千条时,前端仍然需要自己做聚合,常见做法是网格聚合,或者实现一个简单的canvas覆盖层。4.2 加载本地GeoJSON数据业务数据如果已经转成GeoJSON文件,离线环境也可以直接用ajax读进来渲染。百度的原生API没有直接吃GeoJSON的方法,需要自己解析坐标串。// 以jQuery为例,fetch也行 $.getJSON("./data/pipe.json", function (geojson) { // 判断类型,只演示LineString var features = geojson.features || []; features.forEach(function (feature) { if (feature.geometry.type === "LineString") { var coords = feature.geometry.coordinates.map(function (c) { return new BMap.Point(c[0], c[1]); }); // 创建折线,线宽和颜色按业务来 var polyline = new BMap.Polyline(coords, { strokeColor: "#ff0000", strokeWeight: 4, opacity: 0.8 }); map.addOverlay(polyline); } }); });这里有个数据处理细节。GeoJSON里存的是经度、纬度,顺序是lng、lat,而BMap.Point构造器接受的参数顺序同样是lng、lat,顺序对了就不用额外调整。但如果你从数据库导出的数据是lat在前,就必须在脚本里交换,否则点位会画到海里去,我见过不止一版数据栽在这里。4.3 离线环境下的逆地理编码替代方案在线API里,地址转坐标、坐标转地址都现成的,离线包把这块能力砍掉了。这是必然的,因为它依赖在线服务。在线功能离线替代方案地址->坐标 Geocoder数据入库时提前解析,存好经纬度坐标->地址 ReverseGeocoder自建地址库,或按中心点返回自定义名称POI关键词搜索离线无解,只能预置数据我的习惯是:业务数据在后台入库时先做一次在线解析,解析结果连同地址一起存库,以后离线部署直接读库。坐标转地址的自建方案,常见做法是把行政区边界和重点建筑做一张小表,通过点在多边形内的判断反查名称。这套东西不属于地图API的能力,但对于在纯内网做可视化的人来说,这样已经够用了。5. 离线地图排查指南:五个高频坑离线地图大部分问题集中在“没加载起来”“加载起来没图”“有图点位不准”三个层面。下面五条是我在多个项目里反复撞见的高频问题,逐一按现象、原因、解决写清楚。5.1 页面白屏,控制台报 BMap is not defined现象:地图区域是空的,打开浏览器控制台,红色报错指向BMap未定义。原因:离线JS库没有正常加载。常见有两种,一是script标签的src路径写错,尤其用相对路径时,从/example/index.html访问和从/访问,相对路径的基准不同,结果差一层;二是服务器根本就没把这个JS文件映射出来,直接访问URL变成404。解决:先在控制台Network面板看script请求的状态码。404的话,改用绝对路径并对照目录结构调整src,例如 /lib/baidumap_offline_v3.0.js。这里提醒一句,绝对路径开头带不带/,行为完全不同,静态服务根目录下面有没有lib目录,也直接影响结果。5.2 地图有网格背景但瓦片全是404现象:地图能初始化,能拖拽缩放,但底图是一片灰白色网格,控制台一堆瓦片404。原因:瓦片路径和本地文件对不上。最典型的有三种:tiles目录名不一样;z/y/x拼接顺序错误,拼成了z/x/y,但文件实际按z/y/x存放;请求了超出示例范围的级别。示例工程瓦片在tiles/3到tiles/17,地图却拉到18级。解决:打开Network面板里任意一条瓦片请求,看URL模板,然后到tiles目录下按同样路径查一遍文件是否存在。目录错就改名,顺序错就调整配置文件里的URL模板,级别越界就把maxZoom限制住。5.3 点位与底图错位几百米现象:Marker能显示,但位置和底图上的道路、建筑明显对不上,偏移量看着像固定值,又好像不完全固定。原因:坐标系没转换。业务数据是WGS84坐标,地图底图是BD09,两者之间存在非线性偏移,不能通过加一个固定值解决。解决:数据入库前用3.3节里的算法转换,前端拿到坐标前确认已经是BD09。已经入库的错误数据,只能在库里清洗重算,不要在前端逐条救火,数据量上去之后前端计算会影响地图流畅度。5.4 拖拽地图卡顿,拖动时白块闪烁现象:地图可以正常显示,但拖动过程中明显卡顿,松手时大面积空白闪烁,过一会儿才补上。原因:本地瓦片服务没有做缓存,每个瓦片每次都要重新读磁盘。机械硬盘环境下尤其明显,浏览器缓存策略也不对,反复请求相同图片。解决:参考3.2节nginx缓存配置,给tiles目录设置expires和Cache-Control。如果已经设了缓存还卡,再看瓦片大小,示例包导出的瓦片如果超过200KB,说明切片参数有问题,应重切压缩。瓦片切得越碎,加载越快,单张普通瓦片尽量控制在几十KB以内。5.5 file协议直接打开工程文档,各种样式和功能失灵现象:在Windows上双击example/index.html,地图没渲染出来,控制台显示跨域报错,一些JS方法执行异常。原因:浏览器对file协议下本地脚本的加载限制很严格,不从HTTP服务访问就会触发CORS或安全问题;部分浏览器甚至直接拦截本地JS加载。解决:不要用file协议运行离线地图工程。在工程根目录启动任意静态服务,nginx或tomcat都行,然后通过 http://IP:端口/ 访问。内网部署也一样,服务器上跑服务,浏览器访问服务器地址,不要试图直接用文件路径打开。6. 进阶与发布习惯:给离线地图加缓存,顺手留后悔药离线地图上线运行之后,真正麻烦的不是首次部署,而是后续更新。瓦片一旦换过,浏览器和nginx缓存会让用户端保留旧画面,你的“新地图”根本显示不出来。我自己的经验是:每次发布新版瓦片,先停掉nginx,清掉tiles目录里的过期文件,再改一个版本号目录名,比如tiles_v2,最后同步修改前端URL模板。这个版本号就是后悔药,万一新瓦片切得有问题,改一行配置就能回滚,不用重新发布整个工程。除了瓦片更新,还有两个细节值得做:第一,地图初始化时,把视口默认级别定在实际需要的最小级别,别一上来就拉近景。比如展示全市设备,初始级别定12而不是17,既减少首屏瓦片请求数,也让用户一眼看到全貌。从列表点进去要定位到某个设备时,再用map.panTo加setZoom强行跳转,这种交互模式在大屏项目里明显更省资源。第二,离线地图通常被嵌在大屏或者管理后台里,地图初始化不是越快越好,而是要和其他图表错开。常见做法是先渲染其它图表,再用setTimeout把地图初始化延迟到空闲期,避免首屏同时发几十个请求卡住浏览器。从那以后,我每次部署离线地图都强制检查三件事:JS文件请求是否200、瓦片URL与本地目录是否一一对应、业务坐标是否已经转成BD09。这三条没跑完,我连测试都不看。这套习惯帮我避免了至少三次上线翻车,希望帮到你。本文还有配套的精品资源,点击获取