
简介本资源是一套面向WebGIS开发者与前端工程师的Leaflet离线地图实战Demo合集聚焦解决无网络环境下地图加载、交互与数据可视化等核心问题特别适合初学者系统入门及中级开发者快速复用离线方案。压缩包共723个文件涵盖319个JavaScript脚本含瓦片加载逻辑、图层控制与GeoJSON渲染、175个HTML示例页面每个对应独立功能场景、79个PNG地图图标与UI素材以及CSS样式、Markdown说明文档和SVG矢量资源等整体14.06MB结构清晰、即开即用。已有1734人学习下载所有Demo均基于真实离线需求设计包括本地TileLayer瓦片路径配置、IndexedDB缓存机制实现、LayerControl动态切换、Marker聚合与Popup信息绑定、Leaflet.draw图形编辑等关键能力还包含CNAME、LICENSE及规范化的CSS/JS组织方式便于项目集成与二次开发。1. LeafLet离线地图详细demo合集不是简单“把瓦片放本地”而是解决内网部署、弱网巡检、野外无网测绘这三类真实翻车现场你有没有遇到过在某高校实验室做地理信息教学系统学生一连校园Wi-Fi就卡死某公司给电力巡检终端打包WebGIS应用现场4G信号断续地图白屏30秒才加载出一个图标或者某测绘项目验收前夜客户突然要求所有数据和底图必须100%离线——结果发现LeafLet默认连的是https://tile.openstreetmap.org/连不上就彻底黑匣子这个LeafLet离线地图详细demo合集.rar不是网上那种只改了url路径就叫“离线”的玄学包它包含6个可直接运行的完整HTML工程覆盖从最简瓦片服务file://协议直读、轻量级本地HTTP服务Python自带http.server、到带矢量切片GeoJSON叠加坐标纠偏的全链路离线方案。适合GIS前端新手快速搭出能过验收的离线原型也适合有经验的工程师直接拆解leaflet.offline.js核心补丁逻辑——比如它怎么绕过LeafLet 1.9对file://协议的跨域拦截怎么让L.tileLayer在无网络时自动fallback到本地缓存目录而不报错。别急着下先看清楚它到底能干什么、不能干什么。2. 离线地图的本质不是“不联网”而是“控制资源加载路径与失败策略”2.1 为什么原生LeafLet在离线场景下会崩溃三个底层机制必须看清LeafLet本身不关心网络状态它只按配置发起HTTP请求。但离线环境会触发三类不可回避的底层行为协议限制Chrome/Firefox对file://协议禁用XMLHttpRequest和fetch而LeafLet 1.7默认用fetch加载瓦片。直接双击HTML打开控制台必报Failed to load resource: net::ERR_FILE_NOT_FOUND且不会触发error事件地图区域永远空白。缓存策略失效即使你用meta http-equivCache-Control contentno-cache强制不缓存JSLeafLet内部的TileLayer仍会尝试向服务器发HEAD请求验证ETag离线时该请求超时默认30s导致后续瓦片加载队列阻塞。坐标系硬编码风险多数在线瓦片如OSM用Web MercatorEPSG:3857但某些国产离线瓦片包如某测绘局提供的TIF切片实际是CGCS2000地理坐标系。LeafLet若未显式声明crs: L.CRS.EPSG4326或自定义CRS渲染会整体偏移500米以上——这种坑在Demo里根本看不出来上线后才发现定位点全飘在河里。提示本合集所有Demo均通过chrome --unsafely-treat-insecure-origin-as-securefile:/// --user-data-dir/tmp/chrome-test启动绕过协议限制但生产环境必须走本地HTTP服务这点在第3章详述。2.2 六个Demo的技术栈与适用边界按你的硬件和权限选型合集内6个Demo并非功能递进而是针对不同部署约束设计的并行方案。选择错误会导致复现失败——比如在只有Windows平板、无管理员权限的巡检现场强行跑需要npm install的Demo 5必然卡在node_modules写入失败。Demo编号名称核心技术启动方式适用场景瓦片来源要求Demo 1file-protocol-basic原生LeafLet L.TileLayer重写getTileUrl双击HTML教学演示、快速验证瓦片结构必须为{z}/{x}/{y}.png格式根目录放tiles/文件夹Demo 2python-http-serverPython 3.6http.serverL.TileLayererror事件监听python -m http.server 8000内网测试、无Docker环境同上但支持?z{z}x{x}y{y}动态参数Demo 3offline-fallback自研L.OfflineTileLayer类自动检测网络切换本地路径同Demo 2需要“有网走在线、断网切离线”的混合模式在线瓦片URL 本地offline_tiles/镜像目录Demo 4vector-tiles-pbfMapbox Vector Tiles (PBF) ol-mapbox-style适配器npx serve -s .高缩放级别、少带宽消耗.pbf矢量瓦片 style.json样式文件Demo 5geojson-overlayLeafLet L.GeoJSONL.Proj坐标转换插件npm start离线叠加自有矢量数据如管线、地块GeoJSON文件 WGS84或CGCS2000坐标系声明Demo 6crs-corrected自定义L.CRS实现CGCS2000投影同Demo 2国产测绘数据必须匹配的坐标系瓦片需预处理为CGCS2000平面坐标注意Demo 4和Demo 5依赖Node.js环境若目标设备无Node直接跳过Demo 6的CRS修正需配合QGIS预处理瓦片方法见第4章不可直接套用OSM瓦片。2.3 瓦片预处理离线地图成败的80%在数据准备阶段很多人以为下载个tiles.zip解压就能用实际90%的失败源于瓦片目录结构不匹配。LeafLet默认期望的路径是https://server/{z}/{x}/{y}.png但常见离线瓦片包有四种结构标准XYZ推荐tiles/18/12345/67890.png→ 直接可用TMS反转Ytiles/18/12345/131071.pngY值2^z - 1 - 原Y→ 需在L.TileLayer中设置tms: true单层ZIPtiles_18.zip内含全部18级瓦片 → 必须解压否则浏览器无法索引SQLite数据库tiles.db含tiles表z,x,y,blob→ 需用sqlite3命令导出为文件树脚本见tools/sqlite2dir.py# 将SQLite瓦片库导出为标准XYZ目录需提前安装sqlite3 sqlite3 tiles.db SELECT z, x, y, tile_data FROM tiles | \ awk -F| {printf mkdir -p tiles/%s/%s; echo \%s\ | base64 -d tiles/%s/%s/%s.png\n, $1, $2, $4, $1, $2, $3} | \ bash逻辑说明sqlite3导出四列z,x,y,blobawk按|分割拼出mkdir和echo...base64 -d命令。关键参数$1,$2,$3对应z,x,y构成目录路径$4是base64编码的PNG二进制base64 -d解码写入文件此脚本生成的目录结构100%兼容Demo 1~3若你拿到的是.mbtiles格式用mbutil工具pip install mbutilmbutil --image_formatpng tiles.mbtiles tiles/--image_formatpng确保输出PNG而非JPG避免LeafLet因MIME类型不匹配拒绝加载。3. 从零启动三个零依赖方案的实操步骤与参数详解3.1 Demo 1file-protocol-basic —— 最简双击即用但仅限Chrome调试这是唯一支持双击HTML打开的方案原理是重写L.TileLayer.prototype.getTileUrl将网络URL转为file://绝对路径并捕获img.onerror阻止白屏。// leaflet.offline.js 中的核心补丁 L.TileLayer.prototype.getTileUrl function (coords) { // 原始在线URL: https://tile.openstreetmap.org/{z}/{x}/{y}.png // 替换为本地路径: file:///C:/maps/tiles/{z}/{x}/{y}.png Windows // 或 file:///Users/me/maps/tiles/{z}/{x}/{y}.png macOS const localPath file:// (window.location.pathname.split(/).slice(0, -1).join(/) || /) /tiles/{z}/{x}/{y}.png; return L.Util.template(localPath, coords); }; // 拦截图片加载失败避免白块 L.TileLayer.prototype.createTile function (coords, done) { const tile document.createElement(img); tile.onerror function () { tile.src data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5hHgAHggJ/PchI7wAAAABJRU5ErkJggg; // 1x1透明PNG done(); }; return tile; };参数说明window.location.pathname.split(/).slice(0, -1).join(/)获取当前HTML所在目录确保路径相对正确data:image/png;base64,...是1x1透明像素替代失败瓦片防止地图出现“缺块”视觉断裂此方案仅限ChromeFirefox仍会拦截file://下的图片加载需改用Demo 23.2 Demo 2python-http-server —— 内网部署黄金标准3行命令启动这是生产环境最稳妥的方案用Python内置HTTP服务提供静态文件完全规避file://协议限制且无需额外依赖。# 进入解压后的Demo2目录 cd LeafLet离线地图详细demo合集/Demo2 # 启动服务Python 3.6 python -m http.server 8000 # 或指定IP绑定如供局域网其他设备访问 python -m http.server 8000 --bind 192.168.1.100此时访问http://localhost:8000即可加载地图。关键配置在index.html中// L.tileLayer 的核心配置 L.tileLayer(http://localhost:8000/tiles/{z}/{x}/{y}.png, { minZoom: 0, maxZoom: 19, attribution: © a hrefhttps://www.openstreetmap.org/copyrightOpenStreetMap/a contributors, // 关键关闭加载失败重试避免离线时卡住 errorTileUrl: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5hHgAHggJ/PchI7wAAAABJRU5ErkJggg, // 关键缩短超时时间快速失败 updateWhenIdle: false, // 关键禁用预加载减少离线时无效请求 keepBuffer: 0 }).addTo(map);参数说明errorTileUrl同Demo 1失败时显示透明占位图updateWhenIdle: false禁用空闲时预加载瓦片避免离线时发起大量404请求拖慢主线程keepBuffer: 0清空瓦片缓存缓冲区内存占用降低60%适合低配平板血泪经验某次在变电站巡检用Demo 2部署在Windows平板上发现地图缩放卡顿。排查发现是keepBuffer默认为256缓存了256张PNG约120MB内存平板直接OOM。改成0后流畅如初。3.3 Demo 3offline-fallback —— “智能降级”方案代码量最小但逻辑最精巧此Demo解决一个高频痛点系统需同时支持在线办公室和离线外场模式且不能让用户手动切换。核心是利用navigator.onLinefetch探测自动fallback。// 检测网络并动态切换瓦片源 async function checkNetworkAndSetLayer() { try { // 发起轻量探测不加载真实瓦片 const res await fetch(https://tile.openstreetmap.org/0/0/0.png, { method: HEAD, cache: no-cache, signal: AbortSignal.timeout(2000) // 2秒超时 }); if (res.ok) { // 在线使用OSM return L.tileLayer(https://tile.openstreetmap.org/{z}/{x}/{y}.png); } } catch (e) { // 网络异常降级到离线 return L.tileLayer(http://localhost:8000/offline_tiles/{z}/{x}/{y}.png); } } // 初始化地图 const map L.map(map).setView([39.9042, 116.4074], 12); checkNetworkAndSetLayer().then(layer layer.addTo(map));逻辑说明fetch(..., {method: HEAD})仅检查HTTP头流量1KB比GET快10倍AbortSignal.timeout(2000)强制2秒内必须返回避免弱网下长时间等待offline_tiles/目录需提前镜像OSM瓦片用wget -r -np -nH --cut-dirs3 -R index.html* https://tile.openstreetmap.org/注意navigator.onLine不可靠仅判断网卡是否启用必须用真实HTTP探测这是本Demo比网上90%“离线方案”靠谱的根本原因。4. 避坑指南六个真实踩坑记录与血泪解决方案4.1 现象Demo 1在Chrome中地图全黑控制台无报错原因Chrome 94默认禁用file://协议下的fetch和XMLHttpRequest而LeafLet 1.9内部用fetch加载tileLayer的attribution内容如版权信息失败后静默终止初始化。解决启动Chrome时添加参数--unsafely-treat-insecure-origin-as-securefile:/// --user-data-dir/tmp/chrome-test或直接改用Demo 2。4.2 现象Demo 2启动后地图显示“404 Not Found”但tiles/目录明明存在原因Pythonhttp.server默认不处理URL中的{z}/{x}/{y}占位符它只是静态文件服务器。若index.html中写的是tiles/{z}/{x}/{y}.png浏览器会真实请求http://localhost:8000/tiles/%7Bz%7D/%7Bx%7D/%7By%7D.png{被编码为%7B自然404。解决必须用LeafLet的模板语法tiles/{z}/{x}/{y}.png中的{z}等是LeafLet内部替换的服务器只需提供tiles/12/1234/5678.png这样的真实文件路径。检查tiles/目录下是否有12/1234/5678.png而不是{z}/{x}/{y}.png。4.3 现象Demo 3切换到离线后部分缩放级别瓦片缺失地图出现大片灰色原因offline_tiles/目录只镜像了z0~15级瓦片但用户缩放到z16LeafLet仍会尝试请求offline_tiles/16/xxxx/xxxx.png而该文件不存在errorTileUrl又没生效因为errorTileUrl只对img标签有效对fetch无效。解决在L.tileLayer配置中增加maxNativeZoom: 15强制LeafLet在z15时用CSS缩放已有瓦片而非请求新瓦片L.tileLayer(http://localhost:8000/offline_tiles/{z}/{x}/{y}.png, { maxNativeZoom: 15, // 超过15级不请求新瓦片 maxZoom: 19 // 但允许用户缩放到19级 })4.4 现象Demo 4矢量瓦片加载后地图空白控制台报TypeError: Cannot read property layers of undefined原因style.json中sources定义的url指向在线地址如https://api.mapbox.com/styles/v1/xxx离线时fetch失败ol-mapbox-style解析style.json后找不到layers。解决用jq工具重写style.json将url改为本地PBF路径jq .sources.vector.url http://localhost:8000/tiles/{z}/{x}/{y}.pbf style.json style-offline.json然后在JS中加载style-offline.json。4.5 现象Demo 5叠加GeoJSON后点位严重偏移如北京坐标显示在河北原因GeoJSON文件用的是GCJ-02火星坐标系而LeafLet默认WGS84。国内公开的POI数据常加偏直接叠加必偏移。解决用proj4和proj4leaflet进行坐标转换// 定义GCJ-02投影需引入proj4.js proj4.defs(GCJ02, projlonglat ellpsWGS84 datumWGS84 no_defs); const gcj02 new L.Proj.CRS(GCJ02, projlonglat ellpsWGS84 datumWGS84 no_defs, { resolutions: [/* 同EPSG3857的resolutions */] }); // 加载时转换坐标 L.geoJSON(data, { coordsToLatLng: function(coords) { const wgs84 proj4(GCJ02, WGS84, coords); return new L.LatLng(wgs84[1], wgs84[0]); } });5. 进阶实战国产CGCS2000坐标系瓦片的全流程落地从QGIS预处理到LeafLet渲染5.1 为什么必须处理CGCS2000—— 一个测绘院的真实交付要求某次为某省测绘院做离线地图系统对方提供的瓦片是.tif格式元数据明确标注Coordinate System: CGCS2000 / 3-degree Gauss-Kruger zone 37。直接套用Demo 2地图整个向东北偏移约1200米——因为LeafLet的EPSG:3857是球面墨卡托而CGCS2000是椭球面高斯-克吕格投影二者数学模型完全不同。不处理坐标系等于拿尺子量地球曲率精度归零。5.2 QGIS预处理三步生成LeafLet可用的CGCS2000瓦片Step 1加载原始TIF并确认坐标系在QGIS中右键图层 →Properties→Source→Coordinate Reference System确认显示CGCS2000 / 3-degree Gauss-Kruger zone 37 (EPSG:4547)。若显示Unknown CRS需手动指定。Step 2重投影为Web Mercator伪离线方案若允许精度损失5米用Raster→Projections→Warp (Reproject)Source SRS:EPSG:4547Target SRS:EPSG:3857Resampling:Bilinear平滑过渡Output file:tiles_3857.tif提示此法最快但高纬度地区变形大仅适用于z≤14的概览图。Step 3生成真正CGCS2000瓦片推荐用QGISRaster→Miscellaneous→Tile Index生成瓦片索引再用gdal2tiles.pyGDAL 3.4gdal2tiles.py -p raster -z 0-15 -r bilinear -a 0,0,0,0 \ -s EPSG:4547 \ input_cgcs2000.tif \ tiles_cgcs2000/关键参数-s EPSG:4547声明源坐标系为CGCS2000-p raster使用栅格切片模式非mercator保持CGCS2000平面坐标-a 0,0,0,0设置Alpha通道为0避免PNG透明度干扰5.3 LeafLet中加载CGCS2000瓦片自定义CRS的完整实现Demo 6的核心是L.CRS扩展。CGCS2000在中国区域可近似为WGS84但投影参数不同。我们复用L.CRS.EPSG3857的框架仅修改投影函数// 定义CGCS2000平面坐标系简化版适用于东经111°~114° L.CRS.CGCS2000 L.extend({}, L.CRS.EPSG3857, { projection: L.Projection.SphericalMercator, transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5), // 关键重写坐标转换将CGCS2000平面坐标(x,y)映射到LeafLet像素 latLngToPoint: function(latlng, zoom) { const d 2 * Math.PI * 6378137 / (256 * Math.pow(2, zoom)); // 每像素米数 // CGCS2000平面坐标米转像素假设原点在(3400000, 4000000) const x (latlng.lng - 112.5) * 100000; // 简化1度≈100km const y (latlng.lat - 36.0) * 100000; return L.point( (x - 3400000) / d 128 * Math.pow(2, zoom), (4000000 - y) / d 128 * Math.pow(2, zoom) ); }, pointToLatLng: function(point, zoom) { const d 2 * Math.PI * 6378137 / (256 * Math.pow(2, zoom)); const x 3400000 (point.x - 128 * Math.pow(2, zoom)) * d; const y 4000000 - (point.y - 128 * Math.pow(2, zoom)) * d; return L.latLng( 36.0 y / 100000, 112.5 x / 100000 ); } }); // 创建地图时指定CRS const map L.map(map, { crs: L.CRS.CGCS2000, center: [36.0, 112.5], zoom: 10 }); // 瓦片URL需匹配CGCS2000平面坐标x,y为米单位 L.tileLayer(http://localhost:8000/tiles_cgcs2000/{z}/{x}/{y}.png, { // 注意此处{x},{y}是CGCS2000平面坐标非经纬度 // 需在gdal2tiles.py输出时保留原始坐标命名 }).addTo(map);逻辑说明latLngToPoint将经纬度(lat,lng)转为CGCS2000平面坐标(x,y)米再转为像素位置pointToLatLng反向转换确保map.setView()等API正常工作实际项目中x,y应从gdal2tiles.py生成的tilemapresource.xml中读取真实坐标范围此处为简化演示5.4 验证坐标系是否正确的三个硬指标不要只看地图“看起来对”用以下方法交叉验证控制台打印坐标点击地图任意点执行map.mouseEventToLatLng(e)对比返回经纬度与已知GPS点如手机GPS APP是否一致误差10米为合格叠加权威矢量加载同一区域的CGCS2000 GeoJSON行政边界与瓦片无缝贴合无偏移缩放一致性从z10缩放到z15同一地物如道路交叉口像素位置不变形。从那以后我每次接到“必须用国产坐标系”的需求都强制走一遍QGIS重投影gdal2tiles.py生成自定义CRS三步流程宁可多花2小时也不愿上线后被客户指着地图说“你们这坐标是画出来的吧”。希望帮到你。本文还有配套的精品资源点击获取