
简介针对LeafLet离线地图开发场景这份demo合集整理了从基础到进阶的完整示例面向Web GIS开发者与需要快速上手的初学者。内容覆盖地图初始化、图层控制、离线瓦片加载、标记与弹出窗口、事件监听、地理编码与反编码以及markercluster聚合、draw绘图等常用扩展每个示例均配套可运行页面便于对照学习。资源包共723个文件以319个js脚本、175个html演示页、79个png图标与29个css样式为主体另有少许可视化配置与说明文档整体14.06MB逻辑清晰适合按需查阅。目前已吸引1735人学习下载。通过研读这些demo能够理解LeafLet的图层机制与离线数据组织方式掌握自定义图标、样式及性能优化技巧并能在无网络环境下独立搭建交互式地图应用是一份实战参考价值较高的离线地图开发资料。1. 离线地图这件事一份demo合集能给你什么Leaflet 离线地图这个关键词听起来像是“把官网示例下载到本地跑一遍”那么简单实际做起来完全不是。最常见的场景是公司内网完全不连外网地图展示、业务点标记全靠本地资源同事让你半天之内把页面搭起来这时候你就会发现地图库好说瓦片数据才是最麻烦的。这份demo合集把离线地图从零到可用的十几个示例打包在一起从最基础的地图初始化、离线瓦片加载到marker弹窗、图层控制、GeoJSON边界绘制和点聚合。适合两类人一类是内网部署项目救急的前端开发拿过来改坐标和瓦片路径就能跑另一类是刚接触Leaflet、想知道离线应用究竟怎么组织的入门者。反直觉的结论是Leaflet本身只是个轻量库离线的真正难点在于瓦片数据从哪来、目录按什么规则命名、坐标系和切图原点如何适配。demo合集解决的正是这几个最容易被卡住的地方。2. 目录结构解读一份demo合集是怎么组织的2.1 解压后先看什么目录树与角色分工拿到压缩包之后先别急着双击html文件而是把目录结构过一遍。这份合集不是随手堆了几十个页面它有清晰的分层页面demo、公共资源、静态依赖、瓦片数据四类东西各占一块这样你在二次开发的时候能快速判断“我要改的是页面还是公共配置”。leaflet-offline-demo/ ├── index.html # 合集入口页聚合所有示例跳转 ├── 01-basic-map/ # 基础底图最小可用离线地图 │ └── index.html ├── 02-marker-popup/ # 标记点与弹窗交互 │ └── index.html ├── 03-layer-control/ # 多图层叠加与显隐控制 │ └── index.html ├── 04-geojson-draw/ # GeoJSON边界数据绘制 │ └── index.html ├── 05-markers-cluster/ # 大规模marker聚合展示 │ └── index.html ├── common/ # 公共JS与CSS统一配置放这里 ├── libs/leaflet/ # leaflet.js和leaflet.css本地化文件 └── tiles/ # 离线瓦片根目录按 z/x/y.png 存放common目录是你改得最多的位置所有页面的公共初始化参数、坐标转换工具、通用样式都收敛在这里libs目录把Leaflet相关文件本地化了这是离线应用能跑起来的前提——内网环境里CDN是用不了的。tiles目录体积最大也是整个离线地图的“数据源”它内部的命名规则直接决定了瓦片请求URL怎么写。2.2 demo清单与渐进关系目录核心能力适合场景01-basic-map地图初始化 离线瓦片加载内网底图展示验证瓦片是否通02-marker-popup标记点添加与弹窗绑定仓库、门店、设备等业务点标注03-layer-control多图层叠加和显隐切换底图、卫星图、标注层同时存在04-geojson-drawGeoJSON面数据绘制与点击行政区划、地块边界、巡检区域05-markers-cluster大量marker聚合展示设备点位几千个以上的性能场景这份demo从01到05是一个典型的渐进关系先保证地图底图能显示出来这是离线地图的第一道门槛再往上挂业务点让地图有交互接着是多个图层怎么管理显隐然后解决面数据展示最后处理海量点位的性能问题。你在做实际项目的时候基本也是走这条路线所以每个示例都对应你开发周期里某个阶段的真实需求。2.3 公共资源的组织方式libs与config的边界libs目录里放的是Leaflet本体文件我一般不会去动它真正要关注的是common目录里是否有一个统一配置文件。常见做法是把中心点、缩放级别、瓦片路径、坐标系开关这些参数抽到一个config.js里每个页面通过script标签引入它这样你和同事接手的时候就不用翻遍每个html去找写死的坐标了。瓦片数据tiles目录本身不建议直接放demo页面里而是放在所有页面的同级上层。原因很简单多个demo都要引用同一份瓦片如果瓦片放进了某个具体demo文件夹其他页面引用起来就会变成../../tiles/{z}/{x}/{y}.png这种容易出错的相对路径一旦页面文件夹结构变了路径就崩掉。合集的这种放法算是比较标准的组织方式。3. 核心代码拆解瓦片加载和交互的完整链路3.1 地图初始化参数决定行为边界所有demo的起点都是L.map()这个函数做的事情是在指定容器里创建地图实例。第一个参数是容器的DOM id第二个参数是配置对象。很多人只写了center和zoom就以为完事了实际上离线场景里minZoom、maxZoom、maxBounds这几个参数同样关键它们决定了用户能看到什么、不能看到什么。// 01-basic-map/js/main.js var map L.map(map, { center: [35.0, 105.0], // 业务中心点换成你自己的坐标 zoom: 5, // 初始缩放级别对应瓦片层级 minZoom: 3, // 允许的最小缩放小于3级瓦片可能缺失 maxZoom: 15, // 允许的最大缩放超出后画面会灰白 maxBounds: [[18, 73], [53, 135]], // 限制拖动范围防止拖出无瓦片区域 zoomControl: true // 右下角显示缩放按钮 });center是视野打开时对准的位置实际项目中一般取你业务区域的正中心比如一个片区或者一座城市zoom是初始缩放级别它要和瓦片服务器上实际存在的级别对应设大了页面打开是灰的设小了业务细节看不到。minZoom和maxZoom是用户的缩放边界离线瓦片不可能覆盖全部级别通常下载工具只会抓某几个层级所以这两个值必须和tiles目录里实际存在的文件夹层级保持一致。maxBounds限制的是拖拽边界它用[[最小纬度, 最小经度], [最大纬度, 最大经度]]这种二维数组表达目的是防止用户把地图拖到没有瓦片的空白区域。地图初始化之后还可以监听事件比如map.on(zoomend, function () { ... })来判断用户缩放到了第几级或者map.invalidateSize()在容器尺寸变化后刷新地图布局。这些细节demo里有单独体现实际做后台管理系统时很常用。3.2 离线瓦片加载路径模板、TMS与缩放级别瓦片加载是离线地图里最容易翻车的环节核心代码只有几行但背后的切图规则决定了成败。L.tileLayer接收一个URL模板Leaflet会把模板里的{z}、{x}、{y}替换成当前视图对应的编号然后去tiles目录下找对应图片。// 01-basic-map/js/main.js var tileLayer L.tileLayer(tiles/{z}/{x}/{y}.png, { minZoom: 3, maxZoom: 15, tms: false, // false 表示 XYZ 切图规则true 表示 TMS 规则 attribution: 离线地图 }); tileLayer.addTo(map);URL模板里的tiles/{z}/{x}/{y}.png是相对路径相对的是页面文件所在位置。这里有一个重要概念tms: false代表的是XYZ规则切图原点在左上角tms: true则是TMS标准切图原点在左下角。市面上很多瓦片下载工具默认输出TMS格式如果你加载出来整个地图上下颠倒、或者水平位置偏了半屏第一反应就应该是检查tms开关。还有一个细节是zoom的对应。tiles目录下如果是0/0/0.png这种结构说明切图原点在最左上角有些工具会生成z/x/y但x和y顺序反了的目录这种就得在代码里做一次坐标映射。我一般会在初始化tileLayer之前先打开tiles目录看一眼层级文件夹长什么样再决定URL模板怎么写避免盲目套格式。3.3 业务覆盖物marker、popup与GeoJSON的离线用法底图跑通之后真正有价值的是业务数据怎么叠上去。marker是最基本的业务点它接收[纬度, 经度]数组作为位置参数bindPopup接收一段HTML字符串点击标记时弹出内容。GeoJSON则用来画边界和面要素style回调控制样式onEachFeature回调给每个面绑定交互。// 02-marker-popup/js/main.js var marker L.marker([35.05, 105.05]).addTo(map); marker.bindPopup(b某仓库/bbr库存123件br状态正常); // 04-geojson-draw/js/main.js L.geoJSON(boundaryData, { style: function (feature) { return { color: #ff7800, weight: 2, fillOpacity: 0.2 }; }, onEachFeature: function (feature, layer) { var name feature.properties feature.properties.name; layer.bindPopup(name || 未命名区域); } }).addTo(map);marker的popup内容支持完整HTML实际项目里常用它来展示业务卡片放在b标签里的标题、换行符br分隔的字段、甚至一个操作按钮都可以塞进去不一定要额外的弹窗组件。GeoJSON的style回调接收feature参数你可以根据properties里的某个字段返回不同颜色——比如用状态字段区分正常和异常的巡检区域红色Weight加粗表示重点区域。onEachFeature每个feature会调用一次这里的layer参数就是被创建出来的矢量图层对象绑定popup后用户点击就能看到属性信息。一个容易忽略的点离线环境下GeoJSON数据本身也必须放在本地。不要在图层的url里写一个http://开头的远程地址内网环境一加载就是一团黑。常见做法是把GeoJSON文件放在demo同级的data目录下用相对路径引入。4. 运行与排查离线地图的坑和验证方法4.1 最稳的启动方式本地HTTP服务很多第一次碰离线地图的人会直接双击index.html用浏览器打开文件结果地图区域一片空白控制台报一堆错。这个坑的根源在于file协议下浏览器的同源策略限制了JavaScript对本地文件的读取瓦片图片也可能因为相对路径解析问题加载失败。cd leaflet-offline-demo python3 -m http.server 8080执行完上面命令后浏览器访问http://localhost:8080从入口页进入各demo即可。python3 -m http.server是Python自带的静态文件服务不需要安装任何额外依赖就能把当前目录作为站点根目录提供HTTP访问。如果没有Python用npx serve也可以效果一样。部署到内网服务器时把整个leaflet-offline-demo目录原样放到Web服务器的静态站点根目录下访问方式一致。4.2 页面跑不起来时的排查链路地图页面黑屏或者瓦片不显示时不要先去改代码按顺序排查能省一半时间。打开浏览器F12开发者工具先看Network面板刷新页面后过滤png请求确认瓦片请求状态是200还是404。如果200但页面还是灰的右键查看瓦片图片内容确认图片本身是否损坏。如果404把请求的URL复制出来和tiles目录下的实际文件路径做对比看是z/x/y层级不匹配还是文件名大小写不一致。再看Console面板如果报Leaflet is not defined说明libs/leaflet/leaflet.js没有被正确加载。这时候检查script标签的src路径是不是对的路径是从当前html文件出发的相对路径页面层级深了就需要../往上跳。如果提示某个图层或插件不存在检查是否引入了额外的插件文件markercluster这类插件也需要下载js放到本地离线环境不会自动去CDN拉取。4.3 避坑记录四个让新手翻车的小事第一条地图整体错位道路和标注对不上。 现象底图显示正常但marker打在道路旁边弹窗位置看起来偏了几百米。 原因坐标系不一致。国内多数地图瓦片基于GCJ-02坐标而GPS设备输出的是WGS-84经纬度直接把WGS-84数据往GCJ-02底图上放整个画面就会偏移。 解决在业务数据进入地图前做一次坐标转换把WGS-84转成GCJ-02或者反过来把底图瓦片统一成业务数据的坐标系。常见做法是在common里放一个转换工具函数所有marker坐标先过一遍函数再addTo。第二条地图放大到某一级别后整屏变灰。 现象初始加载正常连续放大两次后画面全灰拖也拖不开。 原因本地瓦片没有下载当前级别对应的数据而map和tileLayer的maxZoom设置成了20甚至更高。 解决打开tiles目录确认最大层级目录编号比如只有15就把L.map和tileLayer的maxZoom都改成15。这里有个血泪教训我一开始只改了map的maxZoomtileLayer还是默认值地图照样灰屏两个地方必须保持一致。第三条双击html文件打开后白屏控制台全是SecurityError。 现象用file协议直接打开页面瓦片一张都不显示。 原因浏览器对file协议有很多限制图片请求和脚本读取都会受影响而且相对路径会从文件系统根目录开始解析。 解决放弃双击习惯用本地HTTP服务跑。这个坑几乎每个离线项目都会踩一次踩完之后就老实了。第四条本地跑得通部署到内网服务器后瓦片全404。 现象开发机上一切正常拷贝到内网服务器后页面框架在瓦片区域全空。 原因部署环境路径变了。页面被挂在某个子目录下相对路径tiles/指向了错误位置或者服务器没有把tiles目录映射到静态资源根目录。 解决上线前先curl一个瓦片地址验证比如curl http://服务器地址/tiles/10/200/300.png确认能取回图片再交给测试。如果用了前后端分离就让运维把tiles目录单独配置成静态资源映射。5. 二次开发把demo合集变成自己的离线地图基座5.1 配置化改造收拢散落的魔法数字第一步是把demo里写死的参数全部抽到config里。打开common/config.js把地图中心、缩放级别、瓦片路径、tms开关、最大层级统一管理起来这样后续同事接手不需要翻每个页面的源码找坐标。// common/config.js var appConfig { center: [35.0, 105.0], // 默认视野中心换成业务区域中心 initialZoom: 5, // 初始缩放级别 minZoom: 3, // 最小缩放对应本地瓦片最小编号 maxZoom: 15, // 最大缩放必须和tiles目录层级一致 tileUrl: tiles/{z}/{x}/{y}.png, tms: false, // 瓦片下载工具是TMS规则就改成true boundaryUrl: data/area.geojson };每个页面引入config.js后初始化地图时这样写L.map(map, { center: appConfig.center, zoom: appConfig.initialZoom, minZoom: appConfig.minZoom, maxZoom: appConfig.maxZoom })。瓦片图层同样换成appConfig.tileUrl替换瓦片数据时只改config一处全站生效不用一个个页面去搜代码。5.2 接入自己的瓦片和业务数据第二步是替换瓦片目录。把你自己下载好的瓦片文件夹整体拷贝进来覆盖tiles目录然后按config参数核对一遍目录命名是不是z/x/y结构最大层级文件夹编号是多少瓦片坐标系是不是和你的业务数据一致。不一致就改config的tms开关和maxZoom。第三步在02-marker-popup这个demo基础上叠加业务数据。把marker坐标换成自己的接口字段popup内容改成业务卡片。如果点位超过几百个就把05-markers-cluster的引入方式搬过来避免一次渲染太多DOM导致页面卡顿。5.3 改造后的验证方法每次改动后跑一遍相同的验证路径本地启动HTTP服务打开Network面板确认瓦片请求全绿随意拖动地图确认没有灰白区域点击几个业务点确认弹窗和图层事件正常。这三步过了基本可以放心提交。改造完成后上线还要再做一次静态路径验证确认服务器上的瓦片能通过URL直接访问。从那以后我每次拿到新的离线瓦片目录都会先确认三件事坐标系是什么、切图规则是XYZ还是TMS、最大层级到几。确认完这三个参数再动手改页面基本上就不会再遇到那种“地图加载不出来重启服务也无济于事”的玄学问题了。希望帮到你。本文还有配套的精品资源点击获取