ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Leaflet离线地图完整Demo合集:内网部署与坐标纠偏实战

Leaflet离线地图完整Demo合集:内网部署与坐标纠偏实战 简介这是一份面向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这三条路。我把它们的对比放到一个表里方便你根据自己项目的情况判断对比维度LeafletOpenLayersMapLibre GL体积约42KBgzip后约700KB约200KB含依赖上手成本极低几行代码就能出图中等概念较多中等需要理解样式规范瓦片支持栅格瓦片最顺手栅格、矢量都支持主打矢量瓦片栅格也能用坐标系处理依赖插件灵活内置强依赖配置适合场景栅格瓦片、快速交付复杂GIS分析矢量渲染、样式定制Leaflet的优势不在功能堆砌而在够用且轻。离线地图的核心需求就是加载本地瓦片、叠加业务标记、做基本交互这些Leaflet开箱即用。而且它的插件生态非常成熟坐标系转换、地图旋转、测量工具、聚合标记都有对应的成熟方案。1.3 demo合集里装了哪些东西打开LeafLet离线地图详细demo合集.rar里面不是单个工程而是按功能拆成了多个独立演示目录。每个demo都是HTMLCSSJavaScript的结构不需要构建工具双击就能在浏览器里跑。我把主要文件的功能整理一下01-basic-map最基础的离线瓦片加载展示整个地图初始化的最小代码02-markers-popups标记点、自定义图标、弹窗交互03-geolocation浏览器定位与地图联动04-draw-measure绘制、测距、测面积05-coordinate-transformWGS-84与GCJ-02坐标纠偏演示06-rotate-mapLeaflet地图旋转的两种实现方式07-multi-layers多个瓦片源切换和图层叠加08-nginx-deploy内网部署的Nginx配置示例每个demo里的代码注释都写得比较细关键函数的上方会标注为什么这么做而不只写做了什么。这样你拿过去改的时候能知道自己改的是哪一部分逻辑。2. 瓦片数据离线地图的全部家底2.1 瓦片从哪里来下载工具和注意事项很多人一旦决定做离线地图第一个问题都是瓦片怎么搞客观地说直接从在线地图批量下载瓦片用于商业项目这里面有版权和使用条款的约束你使用前需要自行确认是否合规。这里分享的是技术层面的实现思路具体数据来源的合规性请务必先确认清楚。技术手段上常用的有几种。第一种是专用的瓦片下载工具比如Mobile Atlas CreatorMOBAC、全能地图下载器等指定范围、缩放级别、地图源就能批量下载瓦片图片。第二种是写爬虫脚本直接请求瓦片服务但这种方法容易触发对方限流不推荐大规模使用。第三种是如果项目本身有GIS数据源比如自己切图用QGIS或ArcGIS导出瓦片目录这是最干净的路子——不存在版权问题但需要你有原始数据。缩放级别是下载瓦片前必须想清楚的决策。第1到3级是全世界轮廓占不了多少空间真正的体积大头在15级以后的城市级瓦片。比如一个中等城市的主城区拉到17级大致覆盖几十平方公里瓦片数量可能在几万张体积一口气冲到几个G。项目开始前先确认业务上需要看到多细的道路、多少个POI点再定缩放级别省得白下几万张用不上的瓦片。2.2 瓦片目录结构某个Z/X/Y的约定瓦片文件在磁盘上的组织方式直接决定了Leaflet怎么读取。无论你从什么渠道获得瓦片最终要整理成这样的目录结构tiles/ ├── {z}/ │ ├── {x}/ │ │ ├── {y}.png │ │ ├── {y1}.png │ │ └── ... │ ├── {x1}/ │ │ └── ... │ └── ... ├── 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-84GPS原生坐标系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 relstylesheet hrefleaflet/leaflet.css / stylehtml, body, #map { height: 100%; margin: 0; }/style /head body div idmap/div script srcleaflet/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/bbr电压等级10kVbr最近巡检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-age86400; 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的运行前提和典型使用场景照着走会省掉不少摸索时间。本文还有配套的精品资源点击获取
返回列表