简介:这是一份面向Web GIS开发者的LeafLet离线地图示例合集,帮助开发者快速掌握离线地图从搭建到交互的完整流程。压缩包共723个文件,大小14.06MB,以319个js脚本、175个html页面和29个css样式文件为主体,配合png/svg图标与md说明文档,形成了代码、页面、样式、文档四位一体的学习结构。内容覆盖地图初始化与图层管理、标记与弹出窗、离线瓦片加载与缓存、GeoJSON几何绘制、事件监听、地理编码等关键场景,并包含markercluster聚合、LeafLet.draw绘制等插件用法演示。每个demo都对应独立功能模块,可直接运行或改造成项目基础代码。目前已有1731人学习下载,适合初学LeafLet的Web前端人员以及需要离线地图方案的技术开发者参考。 年初有个电力巡检的项目,整个方舱里不能碰外网,但业务又必须依赖地图看杆塔位置、规划巡检路径。当时我第一个反应是上在线地图SDK,甲方一句话给堵回来了:所有数据必须内网闭环,地图瓦片也要全部本地化。那个项目让我开始认真整理Leaflet离线地图的整套思路,于是就有了这个demo合集。它不涉及任何在线服务,地图瓦片、交互脚本、坐标系处理、内网托管方案全部打包,下载解压就能跑,适合正在做内网地图应用、想用Leaflet又不想依赖在线服务的开发者。
这套demo合集的价值在于,它把离线地图从"能显示瓦片"到"能处理业务坐标"之间那段空白补齐了。很多人卡住的不是Leaflet本身,而是瓦片数据怎么准备、坐标系偏移怎么处理、内网部署怎么配。这篇文章就把合集里每个demo背后的设计思路和踩坑记录展开说清楚,你可以直接对照自己的场景复用。
1. 先弄清楚一件事:离线地图到底在解决什么问题
1.1 断网可用不是矫情,是刚需
很多人在公网环境下开发习惯了,觉得离线地图是给自己找麻烦。但实际业务里,断网可用的需求比你想象的要多得多——政务内网、电力、石化、军工、海事这些行业,数据不能出内网是硬规定;还有一些野外作业场景,比如地质勘探、应急抢险、边境巡逻,网络信号本身就是奢侈品。
我见过最典型的翻车案例是:一个团队在公网开发调试一切正常,部署到客户内网后地图白屏,排查了半天,发现是SDK初始化时内置的鉴权请求连不上外网,直接拒绝渲染。这个坑在高德、百度、腾讯这些在线SDK的内网部署里特别常见,它们的设计前提就是"设备必须能访问互联网"。而Leaflet完全不同,它本身只是一个轻量级开源JS库,没有内置瓦片数据,你给它喂本地的瓦片图片,它就在本地渲染,天然适配内网环境。
1.2 为什么是Leaflet,而不是别的框架
离线地图的技术选型,市面上无外乎Leaflet、OpenLayers、MapLibre GL这三条路。我把它们的对比放到一个表里,方便你根据自己项目的情况判断:
| 对比维度 | Leaflet | OpenLayers | MapLibre GL |
|---|---|---|---|
| 体积 | 约42KB(gzip后) | 约700KB+ | 约200KB+(含依赖) |
| 上手成本 | 极低,几行代码就能出图 | 中等,概念较多 | 中等,需要理解样式规范 |
| 瓦片支持 | 栅格瓦片最顺手 | 栅格、矢量都支持 | 主打矢量瓦片,栅格也能用 |
| 坐标系处理 | 依赖插件,灵活 | 内置强 | 依赖配置 |
| 适合场景 | 栅格瓦片、快速交付 | 复杂GIS分析 | 矢量渲染、样式定制 |
Leaflet的优势不在功能堆砌,而在"够用且轻"。离线地图的核心需求就是加载本地瓦片、叠加业务标记、做基本交互,这些Leaflet开箱即用。而且它的插件生态非常成熟,坐标系转换、地图旋转、测量工具、聚合标记都有对应的成熟方案。
1.3 demo合集里装了哪些东西
打开LeafLet离线地图详细demo合集.rar,里面不是单个工程,而是按功能拆成了多个独立演示目录。每个demo都是HTML+CSS+JavaScript的结构,不需要构建工具,双击就能在浏览器里跑。我把主要文件的功能整理一下:
01-basic-map:最基础的离线瓦片加载,展示整个地图初始化的最小代码02-markers-popups:标记点、自定义图标、弹窗交互03-geolocation:浏览器定位与地图联动04-draw-measure:绘制、测距、测面积05-coordinate-transform:WGS-84与GCJ-02坐标纠偏演示06-rotate-map:Leaflet地图旋转的两种实现方式07-multi-layers:多个瓦片源切换和图层叠加08-nginx-deploy:内网部署的Nginx配置示例
每个demo里的代码注释都写得比较细,关键函数的上方会标注"为什么这么做",而不只写"做了什么"。这样你拿过去改的时候,能知道自己改的是哪一部分逻辑。
2. 瓦片数据:离线地图的全部家底
2.1 瓦片从哪里来:下载工具和注意事项
很多人一旦决定做离线地图,第一个问题都是:瓦片怎么搞?客观地说,直接从在线地图批量下载瓦片用于商业项目,这里面有版权和使用条款的约束,你使用前需要自行确认是否合规。这里分享的是技术层面的实现思路,具体数据来源的合规性请务必先确认清楚。
技术手段上,常用的有几种。第一种是专用的瓦片下载工具,比如Mobile Atlas Creator(MOBAC)、全能地图下载器等,指定范围、缩放级别、地图源,就能批量下载瓦片图片。第二种是写爬虫脚本直接请求瓦片服务,但这种方法容易触发对方限流,不推荐大规模使用。第三种是如果项目本身有GIS数据源(比如自己切图),用QGIS或ArcGIS导出瓦片目录,这是最干净的路子——不存在版权问题,但需要你有原始数据。
缩放级别是下载瓦片前必须想清楚的决策。第1到3级是全世界轮廓,占不了多少空间;真正的体积大头在15级以后的城市级瓦片。比如一个中等城市的主城区,拉到17级大致覆盖几十平方公里,瓦片数量可能在几万张,体积一口气冲到几个G。项目开始前,先确认业务上需要看到多细的道路、多少个POI点,再定缩放级别,省得白下几万张用不上的瓦片。
2.2 瓦片目录结构:某个Z/X/Y的约定
瓦片文件在磁盘上的组织方式直接决定了Leaflet怎么读取。无论你从什么渠道获得瓦片,最终要整理成这样的目录结构:
tiles/ ├── {z}/ │ ├── {x}/ │ │ ├── {y}.png │ │ ├── {y+1}.png │ │ └── ... │ ├── {x+1}/ │ │ └── ... │ └── ... ├── 1/ │ └── 0/ │ └── 0.png └── ...其中{z}是缩放级别,{x}和{y}是列号和行号,这是瓦片行业通用的XYZ约定。Leaflet在请求瓦片时会自动拼接这套路径,你只需要在L.tileLayer里指定URL模板:
var tileLayer = L.tileLayer('./tiles/{z}/{x}/{y}.png', { maxZoom: 18, minZoom: 3, attribution: '离线地图' });这里有个默认约定需要注意:Leaflet默认的瓦片编号原点在左上角(Web Mercator约定),第0级是一张256x256的整图,然后四级象限分割。如果你的瓦片数据是TMS格式(原点在左下角),就要额外加上tms: true选项,不然瓦片位置会上下颠倒。
2.3 坐标系和比例尺:为什么瓦片总是不在你的位置
瓦片对不上位置,十有八九是坐标系的问题。国内在线地图的瓦片分两种主流坐标系:WGS-84(GPS原生坐标系,Google Earth卫星图用这个)和GCJ-02(国测局加密坐标,高德、腾讯这些国内地图厂商对外发布的坐标)。国外地图用的OpenStreetMap、Esri等,坐标是WGS-84的。
Leaflet本身默认按WGS-84的Web Mercator来处理经纬度。如果你加载的瓦片是国内GCJ-02坐标系(比如从高德上扒下来的瓦片),而业务坐标又是GPS采集的WGS-84坐标,那么不纠偏的话,标记点会整体偏移几百米——具体偏移量与经纬度相关,没有固定值,不是简单加个常数就行的。
所以离线地图项目启动前,第一件事是确认你的瓦片源坐标系和业务数据坐标系是否一致。不一致就过一遍坐标纠偏(下面第4章会专门写方案),一致就直接用。
3. demo合集逐个拆解:从瓦片栅格到业务轨迹
3.1 基础瓦片加载:最少的代码跑起来
第一个demo演示的就是最短路径跑通。一个HTML文件,引入本地leaflet.js和leaflet.css,几百行代码不超过20行,就能让地图显示出来。这个demo的价值在于给你一个干净的基线环境,调瓦片路径、调缩放级别、调初始中心点,改完立即能看到效果。
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="leaflet/leaflet.css" /> <style>html, body, #map { height: 100%; margin: 0; }</style> </head> <body> <div id="map"></div> <script src="leaflet/leaflet.js"></script> <script> var map = L.map('map', { center: [39.9042, 116.4074], zoom: 12 }); var tileLayer = L.tileLayer('tiles/{z}/{x}/{y}.png', { minZoom: 3, maxZoom: 18 }); tileLayer.addTo(map); </script> </body> </html>初次运行时你可能会遇到一个常见现象:地图显示一个灰底,背景有网格但没瓦片,控制台报404。这几乎都是瓦片路径不对或文件名不匹配。先把单个瓦片URL在浏览器地址栏里手动访问,确认能打开,再回来找JS的问题,排查效率会高很多。
3.2 标记、弹窗和自定义图标:业务叠加的核心交互
地图只是底图,真正干活的是叠加层。第二个demo实现了标记点、弹窗和自定义图标三件套。Leaflet的L.marker接受经纬度坐标,通过addTo(map)挂到地图上;bindPopup绑定弹窗内容,内容支持HTML字符串,所以你可以塞表格、图片、链接,非常灵活。
var marker = L.marker([39.9042, 116.4074], { icon: L.icon({ iconUrl: 'icons/tower.png', iconSize: [32, 32], iconAnchor: [16, 32], popupAnchor: [0, -32] }) }); marker.addTo(map); marker.bindPopup('<b>杆塔001</b><br>电压等级:10kV<br>最近巡检:2024-03-12');这里有个新手容易踩的坑:iconAnchor必须设置成图片底边的中心点位置,否则图标会以左上角对齐经纬度位置,视觉上整体偏移半张图。iconSize是图片显示尺寸,如果原图是64x64但你想显示32x32,直接改iconSize就行,不一定要切图。
3.3 浏览器定位与地图联动:定位不准并不都是前端的问题
第三个demo是浏览器定位。navigator.geolocation.getCurrentPosition能拿到设备的经纬度,然后map.setView把地图中心移过去,再放一个跳动的小蓝点。这段逻辑本身不难,但有两个问题必须在demo里处理:
一是浏览器安全策略。geolocation API在非HTTPS环境下只有localhost格外放宽,用内网IP访问时会被浏览器当不安全环境拒绝。解决方案是给内网服务器配一张自签证书启用HTTPS,或者部署在localhost。二是定位精度问题。浏览器定位在城市里经常几十米误差,原因是它依赖Wi-Fi和基站辅助定位,这个精度对精度有要求的业务不够。我在demo里做了辅助展示——定位成功后画一个误差范围的圆,让使用方直观看到当前位置可信度。
3.4 地图旋转:Leaflet本身没这功能
地图旋转是个特殊需求,比如巡检轨迹回放时希望地图沿着路线方向转动。Leaflet原生不支持旋转,这也是很多人觉得它"太简单"的原因之一。但插件生态里有好几个方案,我挑了两种在demo里展示对比:
第一个方案是leaflet-rotate插件。它会整体旋转整个地图Div,包括标记和控制件,效果是所有元素一起转,给人一种"世界转了"的感觉。这种方案实现上最简单,适合只需要转动视角的展示场景。
第二个方案是leaflet-rotated-marker,它只旋转单个Marker图标,地图本身不动。你给Marker设置rotationAngle和rotationOrigin属性即可:
var plane = L.marker([39.9042, 116.4074], { rotationAngle: 45, rotationOrigin: 'center' }); plane.addTo(map);第二个方案更常用,适用于车辆、飞机、箭头这种需要朝向指定方向的业务对象。合集里把两个方案的写法都放进demo了,你根据自己的业务形态选就行。
4. 高德离线瓦片的坐标纠偏:GCJ-02和WGS-84绕不过去
4.1 偏移问题的本质:火星坐标
如果你在离线项目中用了高德的瓦片(不管是下载的还是客户提供的),很快就会发现:GPS采集的坐标放到地图上后,标记点偏离真实位置少则几十米、多则几百米。这不是Leaflet的bug,而是国内地图坐标系和GPS坐标系的底层差异。
WGS-84是GPS设备输出的坐标基准;GCJ-02是国家测绘部门在WGS-84基础上做了一次偏移加密的结果。高德、腾讯对外发的所有坐标都是GCJ-02的,所以GPS坐标直接往高德瓦片上叠,必然偏。这个算法本身并没有公开的精确方程,但经过数学反向推导,业界已经总结出了误差在可接受范围内的近似转换公式。
4.2 纠偏代码的实际应用
demo里的坐标转换模块直接引入了coordtransform这个库,它提供了WGS-84和GCJ-02之间的互转函数。我在demo里写了一个可交互的验证工具:左边框手动输入GPS坐标,点按钮转换后,地图打点显示转换前和转换后的两个位置,你可以直观看到偏移量。
// 使用coordtransform库进行坐标纠偏 var wgs84Coord = [39.9042, 116.4074]; var gcj02Coord = coordtransform.wgs84togcj02( wgs84Coord[1], wgs84Coord[0] ); // gcj02Coord形式为经度、纬度 var marker = L.marker([gcj02Coord[1], gcj02Coord[0]]); marker.addTo(map);刚接触这块时容易犯一个错误:只在地图加载前转一次,或者干脆在瓦片URL上加偏移参数。实际上更合理的架构是在数据入口统一做转换——瓦片是GCJ-02的,就保持不动,业务数据源进来的WGS-84坐标统一转成GCJ-02后再传给地图的marker层。这样底图和标注在一个坐标系内工作,不会产生叠加误差。
4.3 天地图瓦片:另一种离线选择
合集里还留了一个天地图瓦片的延伸演示。天地图是国家地理信息公共服务平台的成果,数据是CGCS2000坐标系,和WGS-84之间的偏差极小,几乎可以直接使用。如果项目的瓦片数据来源是天地图或者自有GIS数据,坐标转换的压力会小很多。这点在做技术选型时可以重点考虑。
5. 内网部署实操:Nginx托管、缓存策略和常见坑
5.1 Nginx托管瓦片目录:一个配置断掉90%的404问题
离线地图做完本地调试后,要部署到内网服务器上给其他人访问,最省心的就是用Nginx托管静态文件。demo合集里附带了一个可直接改的nginx.conf片断,核心逻辑是两条location规则:
server { listen 80; server_name localhost; root /data/offline-map; index index.html; location /tiles/ { add_header Cache-Control "public, max-age=86400"; try_files $uri =404; } location / { try_files $uri $uri/ /index.html; } }瓦片文件有个特点:文件数量巨大、单个文件小、基本不变动。所以给/tiles/加一个Cache-Control头特别合适,客户端只要访问过一次瓦片,一天之内不会再向服务器发请求,内网流量压力会小很多。
5.2 跨域问题:你一定会遇到
假设地图页面部署在http://192.168.1.10:8080,而瓦片放在http://192.168.1.11/tiles/,浏览器会直接拦截跨域请求。典型的报错是Failed to load resource: No 'Access-Control-Allow-Origin' header is present。
解决办法在Nginx端加一段跨域头配置:
location /tiles/ { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, OPTIONS'; }更省事的做法是前端和瓦片目录放在同一个站点下面,这样不存在跨域问题,我推荐优先这么做。如果要求前后端分离,再考虑跨域配置。
5.3 大瓦片加载慢、卡顿的处理思路
瓦片数量一大,本地加载也会卡。不是网速问题,而是浏览器对同域名的并发请求数有限制。一个实用方案是给Leaflet的瓦片请求设置域名切片——把瓦片目录拆到多个子域名,或者子路径下,让请求分散:
var tileLayer = L.tileLayer('tiles/{z}/{x}/{y}.png', { subdomains: ['t1', 't2', 't3', 't4'], // 配合DNS解析到同一服务器的不同子域名 });另一个见效比较快的思路是提前裁剪瓦片尺寸。把256x256的瓦片重新拼接成512x512的大瓦片文件,图片数量直接缩到四分之一,请求量大幅下降,加载速度有质的变化。代价是瓦片文件单个体积略大,但总体积不变。合集里的tools目录附了一个基于Python Pillow的瓦片合并脚本,直接改输入输出路径就能跑。
6. 试运行demo时常见问题的完整排查链路
6.1 瓦片灰屏的排查顺序
我整理demo合集的时候,特意把最常见的故障场景做成了一个排查清单。如果你部署后发现地图灰屏,别急着在代码上加日志,按这个链路逐层查:
- 手动打开一个瓦片URL,比如
http://your-server/tiles/12/3456/789.png,直接在浏览器访问。能打开说明文件在,打不开说明目录路径或文件名有问题。 - 看浏览器的Network面板,找到瓦片请求,看状态码是200还是404。404是路径问题;403是权限问题;canceled是浏览器并发限制或者页面已经刷新。
- 看瓦片请求返回的文件类型,有时服务器把.png当成
application/octet-stream返回,浏览器直接拒渲染。需要检查Nginx的mime.types配置是否包含了png。 - 检查地图中心点的缩放级别是否在你瓦片数据的覆盖范围内。数据只下载到15级,但初始化zoom设成18,自然连瓦片都不存在。
- 最后再看JS控制台有没有
L is not defined这类基础引用错误。
6.2 标记偏移的排查链路
如果你发现瓦片显示正常,但标记偏移,按下面几步定位:
- 确认瓦片源的坐标系类型。高德、腾讯瓦片是GCJ-02,天地图、OSM是WGS-84/CGCS2000。
- 确认业务坐标数据的采集方式。GPS设备输出的原始坐标是WGS-84;反过来,数字地图上点的坐标可能已经在GCJ-02坐标系里。
- 如果两者不一致,在数据入口增加坐标转换,而不是渲染时临时转换。统一在数据层解决,避免前端各个组件各转各的。
- 用demo里的坐标验证工具,输入一组已知坐标,对比转换前后的偏差,确认转换逻辑本身没写错。
6.3 其他容易被忽略的细节
内存占用是另一个坑。低端电脑打开一个包含几千个标记点的地图,浏览器直接崩掉是常事。我在demo里给大数据量场景准备了一个Canvas标记方案:用L.canvasIconLayer把所有标记画到Canvas上,替代传统的DOM Marker。实测在5000个标记点场景下,DOM方案会卡到3帧,Canvas方案还能跑在40帧以上。
还有一个中文路径的坑:瓦片目录不要带中文名,部分Windows环境下的Nginx对中文路径的URL编码处理不一致,会出现随机404。文件夹命名统一用纯英文和数字,别省这点事。
7. 这套demo合集可以怎么扩展成自己的项目
7.1 从demo到实际业务的改造路径
合集的8个demo并不复杂,各自解决一块独立问题。实际项目里你可能不会只用一个demo的能力,而是把几块组合起来——比如:用基础瓦片加载+demo坐标纠偏+标记覆盖层,就能搭出一个内网资产管理系统地图页;在此基础上叠加绘制和测距,就能改造一个野外作业的路径规划工具页面。
我建议你拿到合集后按这个节奏走:先跑通01,确认瓦片路径没问题;再打开05,确认坐标系匹配情况;然后是02、03、04逐项按业务需要调整;最后做部署的时候参考08的Nginx配置。不要想着一次把全部demo都改完,那样反而容易查不出问题。
7.2 可选的进阶方向
如果项目还继续往下走,有几个方向可以考虑:一是换用MapLibre GL加载矢量瓦片,渲染更细腻也支持样式动态改,但复杂度会上去一个量级;二是接入leaflet.markercluster做海量POI聚合,对标记多到互相覆盖的场景非常有用;三是写一个基于Node.js或Python的小服务,把离线瓦片生成、坐标转换、标记配置做成界面化工具,团队其他人就不需要直接碰代码了。
另外,如果在纯内网环境里要同时支持PC端和手机端,Leaflet也完全能胜任,demo合集里的页面本身是响应式的,手机浏览器访问会自动适配手势缩放。
最后说一个我自己的使用习惯:我会在项目启动时把瓦片范围、坐标系类型、缩放级别范围、业务坐标来源这四项信息写在一个固定的README文件里。离线地图项目最容易出问题的就是这些前置信息在团队中口口相传传递失真。记录下来之后,后期维护的人从第一分钟就知道地图环境是什么状态,不用靠猜。这套demo合集里的目录结构也是按这个思路组织的,你解压后看到的每个子目录里都有一个简短说明文件,交代这个demo的运行前提和典型使用场景,照着走会省掉不少摸索时间。
本文还有配套的精品资源,点击获取