44 KiB
Map Studio 项目文档
更新日期:2026-11-08
项目目录:D:\map\map
默认地址:http://127.0.0.1:8081/
1. 项目概览
Map Studio 是一个本地优先的地图工作台,包含底图、地形、铁路、水域、植被、行政边界、本地矢量瓦片、绘制分析、工程文件、制图打印和天地图离线瓦片下载。
系统由三部分组成:
- 浏览器前端:
index.html、app.js、coordinate-systems.js、styles.css。 - 本地 Python 服务:
server.py,负责静态资源、瓦片代理、缓存、数据查询和管理员接口。 - 可选离线数据构建工具:
tools/build_local_vector_tiles.py,把 GDB/GeoJSON 转成本地分块矢量数据。
运行时不依赖 Node.js,不要求前端构建。Python 服务启动时只使用标准库;高缩放瓦片首次生成时会从项目内的 _gisread 或项目同级的 ..\_gisread 延迟加载 shapely,也可用 MAP_STUDIO_GIS_DEPS 指定自定义目录。GDAL、pyogrio、pyarrow 只用于离线数据构建。
2. 当前功能
2.1 底图与地形
- 简洁底图、标准地图、卫星影像和 Esri 地形图。
- DEM 地形晕渲作为独立叠加开关,可叠加在上述任意底图以及天地图底图上。
- 天地图矢量、地形、影像底图,仅读取手动下载的本地瓦片。
- 天地图矢量注记、地形注记、影像注记互斥显示。
- 动态等高线和可叠加在任意底图上的地形晕渲,以及地形特征点。
- 可自定义的山顶、山谷阈值和颜色。
2.2 交通与环境
- OpenRailwayMap 铁路基础设施瓦片。
- OpenFreeMap 国铁、地铁、轻轨和单轨,按 Z3–Z16 生成本地铁路瓦片。
- 国铁线路和地铁线路使用两套互相独立的分类开关,均可分别控制主线、高速/客专、支线/专用线、站场、车辆段/停车场、侧线、渡线、联络线、试车线、环线、货运/工业、其他。分类综合
service=yard/siding/crossover/spur/loop、usage=main/branch/test/freight/industrial、highspeed、railway:traffic_mode及线路名中的车辆段、停车场、联络、试车、客专、环线等关键词。 - OpenFreeMap 的
transit/subway在低缩放通常不完整。Z9–Z13 首次请求只返回现有粗数据,同时由专用后台线程汇总 Z14 精细瓦片;浏览器每 4 秒自动刷新,完成后切换到完整结果。北京仍会额外合并内置快照,因此北京和外地城市的可用时间不同。 - Overpass 仅作为铁路数据的补充和兜底。
- OpenFreeMap 水域、河流、港口、植被,按 Z3–Z16 分区读取和缓存。
- MVT 缓冲区在服务端裁剪到瓦片范围,面状水域和植被不再发生接缝重叠。OpenFreeMap 原生最高为 Z14,Z15–Z16 从 Z14 精细瓦片继续分区,避免高缩放直接返回空瓦片。
- 本地 1:100 万道路、铁路、水系、居民地、植被、地形、设施和古迹。
2.3 行政边界
- 国界、省界、地级界、县级界。
- 省界、市界、区县界颜色可分别配置。
- GDB 图幅行政使用面填充和名称,不绘制 J49、J50、K49、K50 等图幅裁切边。
- GDB 面图层不显示无效面修复产生的残留线段或点。
2.4 绘制与分析
- 顶部产品标识右侧提供坐标系统下拉框:WGS84、GCJ02、Web 墨卡托、北京54、西安80、CGCS2000、BD09、NAD27 / NAD83、ETRS89 与 OSGB36。
- 坐标切换作用于底部实时坐标、路径节点、地形特征点、港口点、通视观察点、打印边框范围说明和坐标弹窗;地图底图与已绘制几何继续使用统一的内部 WGS84 坐标,避免切换时产生位移。
- 右下角实时坐标读数支持小数度与度分秒切换;该选择会保存在浏览器工作区和
.mpstd工程文件中。Web 墨卡托坐标固定使用 X/Y 米制显示。 - 经纬网格同步跟随当前坐标系统。WGS84、GCJ02、BD09、CGCS2000 及各国大地基准显示对应的经纬度值;Web 墨卡托显示 X/Y 米制值。经纬线按当前 Leaflet 投影逐点生成,不再使用纬度线性插值,因此网格标签与地图点选坐标一致。
- GCJ02、BD09 使用公开转换公式;北京54、西安80 和 OSGB36 使用公开近似基准转换参数,在界面提示中明确标记为近似值。
- 当前坐标系统保存在浏览器工作区和
.mpstd工程文件中,刷新页面或导入工程后自动恢复。 - 路径绘制、逐节点位置和高程剖面。
- 范围绘制与面积计算。
- 两点通视分析。
- 通视观察:支持内半径、外半径、观察高度和距离颜色。
- 山顶、山谷识别。
- 绘制对象可隐藏、改色、修改透明度。
- 浏览器自动保存视图、开关、设置和绘制内容。
.mpstd工程文件导入导出。
2.5 制图
- A4 横向制图模式。
- 可移动、缩放的四角图框。
- 屏幕图例与打印图例共用动态状态,只显示当前底图和实际开启的铁路、地形、边界、环境、通视等图例项。天地图图素图例按道路、铁路、水系、图幅行政、省界、市界、区县界、居民地、植被、地形、设施和古迹拆分。
- 国铁线路采用黑色底线叠加白色虚线段绘制,地铁继续使用橙色,避免两类线路混淆。
- 国铁、地铁和天地图铁路支持普通彩色线条、黑白相间铁路线两种方案。颜色设置覆盖国铁、地铁、水域、港口、植被、山顶、山谷及天地图图素各子项;颜色随工程文件保存,并同步到屏幕和打印图例。
- 可编辑图名、图例、比例尺和北箭头。
- 仅打印图框范围。
- 浏览器 PDF 输出。
2.6 下载后台
- 天地图图层手动选择。
- 地图矩形框选下载范围。
- 起始、结束缩放级别。
- 单次下载不设置固定瓦片张数上限。
- 已有瓦片默认跳过。
- 天地图与开源地图手动下载默认各使用 4 个并发瓦片线程;并发数分别由
server.py顶部的TIANDITU_DOWNLOAD_WORKERS和OPENMAP_DOWNLOAD_WORKERS配置。 - 管理员可在地图下载页中断正在运行的任务。中断后停止派发新瓦片,等待当前请求结束,已经完整写入的瓦片保留。
- 缓存先写入
cache/.download-tmp/,使用fsync同步后再通过os.replace()原子移入正式路径;服务器重启不会留下可见的半截瓦片。 - 启动时清理超过 24 小时的遗留
.part临时文件。 - 管理员缓存管理页显示各类缓存的文件数、占用空间和最近更新时间,可按分类更新或删除。
- 缓存管理页内嵌覆盖范围地图,可按全部类别或勾选类别显示;瓦片缓存按实际 Z/X/Y 块显示。铁路响应拆分为国铁线路和地铁/轻轨,环境响应拆分为水域、植被和港口,行政边界与待重试任务保持独立分类,各范围图层可单独开关。
- 瓦片缓存可以切换缓存级别查看实际覆盖空洞;范围总览适合检查整体分布。底图使用本地服务的简洁底图及注记。
- 覆盖地图支持拖拽矩形框选;框选后可只更新或只删除与范围相交的实际缓存文件。更新仅作用于勾选且允许在线更新的分类,删除还必须勾选范围删除确认。范围外文件不会被连带删除。
- 缓存更新只允许底图/注记/DEM、OpenFreeMap 原始矢量、铁路查询响应和环境查询响应;强制更新忽略半年有效期,后台按
--refresh-batch分批执行。 - 删除必须勾选“确认可删除”;运行中的天地图或开源地图任务存在时拒绝删除。
- 未登录时只显示登录框。
- 登录成功后才显示地图、图层、设置、进度和日志。
- 退出登录会清空服务器上的全部管理员会话。
3. 快速启动
cd D:\map\map
python server.py
或双击:
start-map-studio.bat
打开:
http://127.0.0.1:8081/
天地图离线下载后台:
http://127.0.0.1:8081/tianditu-admin.html
开放与预览地图离线下载后台:
http://127.0.0.1:8081/openmap-admin.html
管理员缓存管理页:
http://127.0.0.1:8081/cache-admin.html
缓存管理、天地图下载和开源地图下载三个后台共用管理员密码和服务器端会话。未登录时只显示密码框。主页面顶部固定显示内部使用提示,并链接到本地 regulations.html;该页面明确地图内容、数据许可、账号安全和地图公开审核要求。
地图离线后台除开放地图和 OpenFreeMap 外,也可预下载 Esri 简洁底图、简洁底图注记、地形图和卫星影像。简洁底图离线使用时应同时下载注记;Esri 地形图原生最高 Z13。框选预览背景还支持已下载的天地图矢量、影像和地形瓦片。
开源下载任务具有任务级停滞保护:后台批量下载对每个上游地址最多等待 8 秒;OpenFreeMap 或普通栅格单瓦片总超时约 24 秒,OpenTopoMap 的多个备用地址合计最多约 40 秒,瓦片锁等待上限为 110 秒;连续 150 秒没有新进度时,任务会收尾为 partial。任务状态包含活动请求数和当前瓦片,状态接口同时返回最近开始的活动任务,后台刷新或重新登录后可自动恢复进度轮询。
所有服务器外网请求可通过 HTTP/HTTPS 正向代理:
python server.py --upstream-proxy "http://127.0.0.1:7890"
python server.py --disable-upstream-proxy
也可使用 MAP_STUDIO_UPSTREAM_PROXY 或管理员下载页配置。管理员页面区分服务器默认代理和临时代理:默认模式不修改配置;临时设置及临时直连只在当前服务进程内生效;“保存为服务器默认”才写入 cache/upstream-proxy.setting;重启时恢复该默认代理。管理员状态接口只返回脱敏后的主机和端口,不返回代理账号密码;公开 /api/status 不返回任何代理信息。仅缓存模式的禁止外网规则优先于代理设置。
默认只监听本机回环地址。不要在未配置 HTTPS、访问控制和防火墙的情况下绑定到 0.0.0.0。
4. 目录结构
D:\map\map\
├─ index.html 主地图页面
├─ app.js 主应用逻辑
├─ styles.css 界面与打印样式
├─ coordinate-systems.js 坐标显示、投影与基准转换
├─ sw.js Service Worker 浏览器缓存
├─ server.py 多线程本地地图服务
├─ tianditu-admin.html 天地图下载后台
├─ openmap-admin.html 开源地图与开放地形预下载后台
├─ cache-admin.html 管理员缓存统计、更新与删除页面
├─ regulations.html 内部使用与地图合规规定
├─ compliance-gate.js 首次使用合规确认弹窗
├─ README.md 快速说明
├─ PROJECT_DOCUMENTATION.md 本文档
├─ api-comparison.html API 成本对比页面
├─ start-map-studio.bat Windows 启动脚本
├─ 导入地图压缩包.bat 可拖放 ZIP 的 Windows 导入入口
├─ 补充1比25万地图数据.bat 补充导入 1:25 万分区 ZIP
├─ tools\
│ ├─ build_local_vector_tiles.py GDB/Shapefile/GeoJSON 本地矢量瓦片构建
│ └─ import_map_archive.py ZIP 导入、来源登记与自动重建
├─ data\map-library\ 数据来源数据库和解压目录
├─ data\map-library-25\ 1:25 万独立来源数据库和解压目录
├─ vendor\
│ ├─ leaflet\ Leaflet 1.9.4
│ ├─ d3.min.js D3 7.9.0
│ ├─ d3-contour.min.js d3-contour 4.0.3
│ └─ rail-beijing*.js 北京铁路离线兜底数据
├─ cache\ 运行时缓存,不通过静态 URL 暴露
├─ admin-password.txt 管理员密码,不提交、不公开
└─ tianditu-key.txt 天地图 KEY,不提交、不公开
5. 配置
5.1 天地图 KEY
任选一种配置方式:
$env:TIANDITU_KEY="你的KEY"
python server.py
python server.py --tianditu-key "你的KEY"
也可写入 tianditu-key.txt,文件中只保存 KEY。
KEY 只由服务端读取。普通页面不会直接请求天地图,只有下载后台会在管理员认证后调用服务端下载任务。
5.2 管理员密码
$env:MAP_STUDIO_ADMIN_PASSWORD="长密码"
python server.py
python server.py --admin-password "长密码"
也可写入 admin-password.txt。天地图下载后台和地图离线下载后台共用该密码及同一个管理员会话。
用户名固定为 admin。建议密码至少 12 个字符,使用独立随机密码。密码不足 12 个字符时,启动日志会输出警告。
Windows 下两个秘密文件已设置为仅当前运行用户可访问。重新复制或重新创建文件后,应再次检查 ACL。
5.3 常用启动参数
python server.py --host 127.0.0.1 --port 8081
python server.py --ttl-hours 4320
python server.py --rail-ttl-hours 4320
python server.py --refresh-hours 4320
python server.py --refresh-batch 128
仅缓存模式:
python server.py --cache-only
python server.py --allow-online
也可设置 MAP_STUDIO_CACHE_ONLY=1。管理员可在 openmap-admin.html 动态切换,设置写入 cache/cache-only.setting。开启后禁止全部上游请求:缓存命中和过期缓存继续使用;缺失栅格返回本地占位 PNG;缺失矢量专题返回空结果;天地图、开源地图下载和缓存刷新接口返回拒绝状态。占位响应使用 X-Cache: PLACEHOLDER 且不进入 Service Worker 缓存。
6. 数据源
| 图层 | 数据源 | 说明 |
|---|---|---|
| 简洁底图 | Esri Light Gray | 服务端代理并缓存 |
| 标准地图 | OpenStreetMap | 服务端代理并缓存 |
| 卫星影像 | Esri ArcGIS | 服务端代理并缓存 |
| 地形图 | Esri World Topographic Map | 主地图和下载后台共用 cache/tiles/topo-preview 服务端缓存;原生最高 Z13,Z14–Z19 使用 Z13 放大显示 |
| 地形晕渲 | AWS Terrain Tiles | 浏览器根据 DEM 实时生成山体阴影 |
| DEM | AWS Terrain Tiles | 浏览器生成等高线和晕渲 |
| 铁路基础设施 | OpenRailwayMap | 服务端代理并缓存 |
| 国铁、地铁 | OpenFreeMap MVT | 转换为本地 Z3–Z16 铁路瓦片并缓存 |
| 水域、港口、植被 | OpenFreeMap MVT | 服务端裁剪 MVT 缓冲区 |
| 补充铁路 | Overpass | MVT 不可用或缺失时重试 |
| 天地图 | vec/cva/ter/cta/img/cia/ibo |
管理员手动下载,本地离线读取 |
| OpenStreetMap / OpenTopoMap / OpenRailwayMap | OSM 生态开放栅格 | 管理员可在开源地图后台按范围预下载 |
| OpenFreeMap 原始 MVT | OpenFreeMap / OpenMapTiles | 管理员预下载 Z3–Z14 原始矢量,前台服务继续按类别裁切 |
| AWS Terrain Tiles | AWS Open Data | 管理员预下载 Z3–Z15,用于等高线、晕渲和视线分析 |
| Esri 简洁底图与注记 | Esri Light Gray Canvas / Reference | 管理员预下载 Z1–Z19,写入 cache/tiles/clean* |
| Esri 地形图 | Esri World Topographic Map | 管理员预下载 Z1–Z13,写入 cache/tiles/topo-preview |
| Esri 卫星影像 | Esri World Imagery | 管理员预下载 Z1–Z19,写入 cache/tiles/satellite |
| 全国省界、市界、区县界 | 中国行政 GeoJSON | 本地 Z3–Z10 分块 |
| 1:100 万基础地理 | G/H/I/J/K/L/M/N 系列共 26 个 GDB 图幅 | 本地 Z3–Z10 分块 |
| 1:25 万基础地理 | D:\map\map_25 中导入的分区 Shapefile |
本地静态 Z8–Z12,按需生成并缓存 Z13–Z18 |
外部公开服务仅适合原型、内部和低流量用途。正式部署需要自行核对服务条款、署名、限额、数据许可和中国大陆合规要求。
7. 本地矢量瓦片
7.1 覆盖范围
- 1:100 万基础地理 GDB:全国多图幅数据,按实际图幅覆盖显示;全国行政 GeoJSON 约为东经 73.5°–135.1°、北纬 3.4°–53.6°。
- 1:100 万预览级别:Z3–Z16。Z3–Z10 预生成,Z11–Z16 在首次浏览时从对应的 Z10 母瓦片按需裁切并缓存。道路、水系、居民地、植被、设施、地形、古迹和行政类别均从 Z3 开始提供。
- 1:25 万预览级别:Z8–Z18。Z8–Z12 预生成,Z13–Z18 在首次浏览时从对应的 Z12 母瓦片按需裁切并缓存。
- 页面在 Z8 以上优先使用 1:25 万。1:25 万没有覆盖当前视窗时回退到 1:100 万;部分类别在 1:25 万源数据中不存在时,该类别由 1:100 万补充。
- 1:25 万当前数据主要含道路、铁路、水系和居民地,不包含植被、设施、地形及全国行政边界,这些分组因此使用 1:100 万补充。
当前本地数据是分幅数据,不是完整矩形填充。没有图幅的区域只显示已有类别,不会伪造道路、水系或居民地。
7.2 当前规模
| 级别 | 非空分块 | 裁剪后要素 |
|---|---|---|
| Z3 | 5 | 500,702 |
| Z4 | 10 | 521,951 |
| Z5 | 30 | 538,942 |
| Z6 | 84 | 550,505 |
| Z7 | 274 | 561,403 |
| Z8 | 954 | 585,194 |
| Z9 | 3,486 | 636,708 |
| Z10 | 13,255 | 762,338 |
当前分块格式是项目内部的 GeoJSON 矢量分块,不是 PNG 图片瓦片,也不是标准 Mapbox PBF。服务端只从本地磁盘读取,不访问互联网。
1:25 万当前来源库包含 D:\map\map_25 中 10 个已登记 ZIP、39 个有效分幅和 351 个有效图层来源,实际覆盖约东经 114°–123°、北纬 34°–43°。空 Shapefile 的 (0,0) 默认边界会被忽略,不会扩大虚假覆盖范围。
| 级别 | 非空分块 | 裁剪后要素 |
|---|---|---|
| Z8 | 52 | 795,223 |
| Z9 | 173 | 802,574 |
| Z10 | 676 | 818,222 |
| Z11 | 2,569 | 848,457 |
| Z12 | 9,888 | 909,256 |
当前 1:25 万瓦片约 2.15 GB(2,145,014,251 字节)。来源分组只有铁路、道路、水系和居民地,其他分组由 1:100 万补充。
7.3 构建依赖
构建环境需要:
pyogrio
pyarrow
shapely
pyogrio 和 pyarrow 只参与离线构建。shapely 在普通低缩放浏览时不会加载,只有首次请求 1:100 万 Z11–Z16 或 1:25 万 Z13–Z18 分块时才延迟加载。若项目内和项目同级的 _gisread 都缺失,低缩放仍可使用,按需高缩放会返回不可用。
7.4 构建命令
cd D:\map\map
$env:PYTHONPATH=(Resolve-Path '..\_gisread').Path
python tools\build_local_vector_tiles.py `
--source D:\map\_tdt_inspect `
--source D:\map\_tdt_inspect_extra `
--admin-source D:\map `
--output cache\local-vector `
--zooms 3,4,5,6,7,8,9,10
生成结果:
cache/local-vector/
├─ manifest.json
├─ 3\x\y.json
├─ 4\x\y.json
├─ 5\x\y.json
├─ 6\x\y.json
├─ 7\x\y.json
├─ 8\x\y.json
├─ 9\x\y.json
└─ 10\x\y.json
构建规则:
...A图层只输出面。...L图层只输出线。...P图层只输出点。- GDB 面图层不输出图幅外轮廓。
- GDB 面图层不输出无效面修复产生的点、线残留。
CPTL/CPTP图幅辅助线不输出。- 全国行政 GeoJSON 单独输出真实行政轮廓。
- 坐标默认保留 6 位小数。
- Z3–Z4 输出主要居民地面和植被面,Z5 起补充居民地、植被点,Z8 起恢复全部类别。
构建期间使用临时 SQLite 数据库按瓦片分桶,再逐个写出,避免所有要素同时驻留内存。数据源通过临时 JSON 清单传给构建器,不占 Windows 命令行长度上限,适合一次导入大量 Shapefile。
7.5 一键导入地图压缩包
把 ZIP 拖到:
D:\map\map\导入地图压缩包.bat
也可以双击该文件,通过文件选择框选择一个或多个 ZIP。
后续补充 1:25 万 ZIP 时使用:
D:\map\map\补充1比25万地图数据.bat
命令行:
python tools\import_map_archive.py D:\map\新的地图包.zip
python tools\import_map_archive.py --scale 25m D:\map\新的1比25万地图包.zip
导入器行为:
- 导入和瓦片重建只读取本地 ZIP/GDB/GeoJSON,不访问任何地图或 DEM 服务。
- 计算外层 ZIP 及内部区块 ZIP 的 SHA-256。外层文件改名或重新压缩不重要,内部区块逐个比对。
- 一个外层包最多包含多个区块。只有包含新内容区块时才导入;如果全部区块都已在资料库中,则立即跳过,不复制、不解压、不重建。
- 部分区块相同时,未变化区块不会被认定为新版本,变化区块按最新导入顺序替换。区块级索引保存内部 ZIP 名称、相对路径和 SHA-256。
- 限制归档解压总量为 20 GB、条目数 20 万,拒绝 ZIP 路径穿越和符号链接。
- 安全解压外层 ZIP,再递归处理其中的 GDB ZIP。
- 自动识别
I49.gdb、K51.gdb等图幅编号。 - 自动识别文件名含“省/市/县”或
province/city/county的行政 GeoJSON。 - 同名 GDB 图幅只使用最新导入版本,避免旧图幅与新图幅重复。
- 省、市、县行政数据同样按类别选择最新版本。
- 同一分幅/图层被新 ZIP 替换时,不需要保持 ZIP 文件名或哈希一致。导入器按来源键选择最新版本;旧归档全部失效时删除归档副本和解压目录,部分失效时清理被替代区块、图层文件及对应
_unpacked目录。 - 无资料库索引引用的
archives/<sha>.zip和sources/<sha>/会被自动识别为孤儿数据并清理。 - 来源记录保存在
data/map-library/index.json。 - 原始归档复制到
data/map-library/archives/。 - 解压内容保存在
data/map-library/sources/。 - 导入后自动重建
cache/local-vector的 Z3–Z10 瓦片。 --scale 25m使用data/map-library-25/index.json和cache/local-vector-25/,并自动重建静态 Z8–Z12;新增或更新后 Z13–Z18 会在浏览时自动生成。- 使用对应来源库目录下的
import.lock跨进程锁;1:100 万和 1:25 万各自独立,同一来源库的两个导入任务不会同时写入索引或瓦片。第二个任务默认等待,最多 6 小时。 --no-wait-lock可让第二个任务立即失败;异常退出的锁会通过进程 PID 检查自动清理。- 如果本次选择的 ZIP 全部已导入,程序会在显示
Skipped duplicate后直接退出。确需重新解压并重建时使用--force;确需只重建现有数据时使用--rebuild-only。 - 如果外层 ZIP 哈希不同但内部区块全部相同,程序会显示
all N internal blocks are already unchanged,并跳过瓦片重建。 --cleanup-only只整理资料库、清理被替代归档和孤儿目录,不导入 ZIP,也不重建瓦片。
当前 1:100 万来源库包含 29 个有效来源:
26 个 GDB:
G49/G50
H49/H50/H51
I49/I50/I51
J49/J50/J51
K49/K50/K51/K52
L49/L50/L51/L52/L53
M50/M51/M52/M53
N51/N52
3 个行政 GeoJSON:省/市/县
当前 1:100 万本地矢量瓦片数据约 3.05 GB(3,046,123,647 字节)。
7.6 水域和植被性能
- MVT 解码只处理本次需要的图层,不再解析同一瓦片中的全部注记、道路和无关图层。
- 水域和植被路径在瓦片坐标中裁剪后执行容差简化。
- 水域多边形和植被多边形不再绘制描边。
- 浏览器最多同时请求 5 个视窗区块,降低本地服务和上游请求拥塞。
- 服务端环境缓存版本为
environment-v6,浏览前端区块版本为openfreemap-environment-block-v6。 - 水域和植被支持 Z3–Z16。水体在 Z3–Z14 直接读取 OpenFreeMap 对应级别,Z15–Z16 使用 Z14 继续裁切;植被的
landcover在中国低级别为空,因此 Z3–Z6 使用 Z7 精细瓦片继续分区,Z15–Z16 使用 Z14。原始 PBF 与分区响应都会落盘缓存。 - 水域和植被缓存命中时,服务端响应通常约 0.1 秒;具体网络传输量取决于视窗和要素数量。
- 本地 1:100 万矢量瓦片从 Z3 起保留所有类别,不再按类别设置最低显示级别。Z3–Z7 只降低几何精度以减少低缩放负载,设施、地形、古迹、全部道路和水系仍然参与显示;省、市、县边界在 Z3–Z16 均可显示。
- Z8 以上优先使用 1:25 万的道路、铁路、水系和居民地;不存在于 1:25 万的分组继续使用 1:100 万。
- 高缩放瓦片使用
cache/local-vector-dynamic/和cache/local-vector-25-dynamic/保存;首次请求可能稍慢,之后由磁盘与浏览器缓存直接复用。 - 补充或重建来源数据后会清除对应的按需高缩放缓存,避免继续使用旧裁切结果。
- 服务只将清单中登记的预生成级别视为静态瓦片;未登记的旧级别目录会被忽略,并在下一次重建时清理。动态瓦片还会校验清单生成时间,旧裁切结果不会继续返回。
- 每个静态缩放级别先写入临时目录,完整构建成功后再整体替换旧级别目录,因此新数据中已消失的瓦片不会残留。
8. 缓存目录
cache/
├─ .download-tmp/ 下载暂存目录,完整写入后才原子移入正式缓存
├─ .cache-admin-coverage.json 缓存覆盖范围索引,不参与删除操作
├─ tiles/ 底图、标注、DEM、铁路和环境瓦片
├─ vector/ OpenFreeMap MVT 原始瓦片
├─ rail/ 铁路响应缓存
├─ rail-tiles/ Z3–Z16 本地铁路瓦片
├─ rail-metro-tiles/ Z9–Z13 从 Z14 汇总的国铁/地铁低缩放数据
├─ rail-pending/ 待重试铁路任务
├─ water/ 环境图层响应缓存
├─ water-pending/ 待重试环境任务
├─ boundaries/ 行政边界响应缓存
├─ local-vector/ 1:100 万 GDB/行政矢量分块
├─ local-vector-dynamic/ 1:100 万按需高缩放分块
├─ local-vector-25/ 1:25 万分区矢量分块
└─ local-vector-25-dynamic/ 1:25 万按需高缩放分块
来源归档和索引:
data/map-library/
├─ index.json
├─ archives/
└─ sources/
data/map-library-25/
├─ index.json
├─ archives/
└─ sources/
缓存默认保留半年。运行时维护线程定期更新过期数据;天地图手动瓦片不参与自动刷新。
缓存覆盖地图首次扫描会遍历瓦片路径,并解析铁路和环境响应中的实际专题几何,分别生成国铁、地铁/轻轨、水域、植被、港口等范围,结果写入 cache/.cache-admin-coverage.json。索引保留各个瓦片缩放的合并行段,避免向前端发送十万级单瓦片记录。后续打开页面直接读取索引;缓存变化后由管理员点击“重新扫描覆盖范围”更新。该索引不参与缓存删除,删除操作会主动清除并允许重新生成。
查询响应缓存按视窗生成,只覆盖用户实际访问或下载过的区域,不等同于全球数据;OpenFreeMap 全球原始矢量瓦片代表源数据层,实际可见范围仍取决于本地已缓存瓦片。页面会显示这一区别,并提供国铁、地铁/轻轨、水域、植被、港口和行政边界六个独立范围开关。每个响应图层同时保存视窗范围与从响应要素重建的来源瓦片坐标;选择具体 Z 级别后,页面会按瓦片绘制覆盖区域、悬停坐标和逐个瓦片边框。
缓存管理主列表直接展示这六个查询响应专题,并分别统计来源瓦片、响应索引和视窗范围。每项可单独选中,用于更新或删除;删除某个专题时会从共享响应文件中仅移除该专题要素,不会误删同一文件中保留的其他专题。每项的手动下载都按索引记录的真实 Z/X/Y 瓦片坐标执行,“下载这些瓦片”会直接创建 OpenFreeMap 瓦片任务,并在开放地图后台显示逐瓦片进度;框选范围更新同样先锁定相交瓦片,再刷新这些瓦片并使相关响应索引失效。行政边界由本服务从 OpenFreeMap 边界 MVT 生成,不依赖外部本地 API。
“显示级别”统计与当前类别选择同步,瓦片和视窗查询结果范围分别计数。服务端索引仍合并同一行连续瓦片以压缩响应,浏览器展开后使用独立 Canvas 像素矩形逐瓦片绘制 1 像素边框,避免 SVG 对亚像素路径的裁剪和尖角误差。瓦片小于 2 像素时只合并填充并在状态栏说明,放大后自动恢复逐个边框。具体级别默认聚焦高密度主缓存区,鼠标悬停可查看类别、Z/X/Y,原有范围框选、更新和删除仍可正常使用。
管理员缓存管理页 cache-admin.html 使用独立分类:
| 分类 | 内容 | 更新 | 删除后的行为 |
|---|---|---|---|
raster |
普通底图、注记、DEM 和预览栅格 | 支持 | 后续浏览重新下载 |
tianditu |
天地图手动下载瓦片 | 不支持 | 需从天地图下载后台重新获取 |
vector |
OpenFreeMap 原始 MVT | 支持 | 后续浏览重新下载 |
rail-national |
国铁线路来源瓦片 | 支持 | 按来源瓦片重建专题索引 |
rail-metro |
地铁/轻轨来源瓦片 | 支持 | 按来源瓦片重建专题索引 |
environment-water |
水域来源瓦片 | 支持 | 按来源瓦片重建专题索引 |
environment-vegetation |
植被来源瓦片 | 支持 | 按来源瓦片重建专题索引 |
environment-ports |
港口来源瓦片 | 支持 | 按来源瓦片重建专题索引 |
boundaries |
行政边界来源瓦片 | 支持 | 按来源瓦片重建专题索引 |
rail-derived |
国铁/地铁派生瓦片 | 不支持 | 后续请求重新派生 |
dynamic-vector |
1:100 万与 1:25 万按需裁切瓦片 | 不支持 | 后续高缩放请求重新裁切 |
pending |
铁路和环境待重试任务 | 不支持 | 未完成任务被清除 |
staging |
.download-tmp 下载暂存残留 |
不支持 | 没有活动下载任务时清理 |
缓存管理删除操作只接受 cache/ 内的上述分类路径,并会解析真实路径后检查父目录,防止路径穿越或符号链接越界。cache/local-vector/、cache/local-vector-25/、cache/cache-only.setting、data/map-library/ 和 data/map-library-25/ 不属于可删除分类。存在 queued 或 running 的天地图/开源地图手动下载任务时,删除接口返回 409 CACHE_BUSY。
地图框选请求在 refresh 和 delete 的请求体中增加 bounds: [south, west, north, east]。瓦片类别按 Z/X/Y 瓦片范围求交,铁路、环境、行政边界和待重试任务按 JSON 响应中的经纬度范围求交。下载暂存残留没有可靠空间位置,因此不会参与框选操作,仍需使用整分类删除。
cache/、源码、构建工具、日志和秘密文件均已从静态文件访问中排除。
9. 服务接口
9.1 公共只读接口
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/status |
服务、缓存和统计状态 |
| GET | /api/local-vector/status |
本地矢量清单 |
| GET | /api/local-vector/{z}/{x}/{y}.json |
本地矢量分块 |
| GET | /api/local-vector-25/status |
本地 1:25 万矢量清单 |
| GET | /api/local-vector-25/{z}/{x}/{y}.json |
本地 1:25 万矢量分块 |
| GET | /api/rail |
当前视窗铁路 |
| GET | /api/rail/status |
铁路后台任务状态 |
| GET | /api/rail-tiles/{z}/{x}/{y}.json |
本地铁路瓦片 |
| GET | /api/water |
当前视窗水域、港口、植被 |
| GET | /api/water/status |
环境后台任务状态 |
| GET | /api/boundaries |
OpenFreeMap 行政边界 |
| GET | /tiles/{provider}/{z}/{x}/{y}.png |
本地地图瓦片 |
9.2 管理员接口
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/admin/login |
管理员登录 |
| POST | /api/admin/logout |
注销全部管理员会话 |
| GET | /api/tianditu/status |
天地图下载状态 |
| GET | /api/tianditu/job?id=... |
下载任务进度 |
| POST | /api/tianditu/download |
创建手动下载任务 |
| GET | /api/openmap/status |
开源图层、缓存数量和活动任务 |
| GET | /api/openmap/job?id=... |
开源地图下载任务进度 |
| POST | /api/openmap/download |
创建开源地图批量缓存任务 |
| POST | /api/openmap/topic-download |
按缓存专题记录的 Z/X/Y 来源瓦片创建精确下载任务 |
| POST | /api/tianditu/job/cancel?id=... |
管理员中断天地图下载任务 |
| POST | /api/openmap/job/cancel?id=... |
管理员中断开源地图下载任务 |
| GET | /api/cache-policy |
查询服务器是否仅使用缓存 |
| POST | /api/cache-policy |
管理员切换仅缓存模式 |
| GET | /api/upstream-proxy |
查询脱敏后的外网代理状态 |
| POST | /api/upstream-proxy |
管理员设置或关闭外网代理 |
| POST | /api/cache/refresh-all |
分批刷新全部过期缓存 |
| GET | /api/cache-admin/status?refresh=1 |
管理员缓存分类统计;refresh=1 立即重算 |
| GET | /api/cache-admin/coverage?refresh=1 |
管理员缓存覆盖范围索引;refresh=1 重新扫描 |
| POST | /api/cache-admin/refresh |
更新所选可更新缓存分类 |
| POST | /api/cache-admin/delete |
删除所选缓存分类,需要删除确认字符串 |
缓存管理更新请求示例:
{"categories":["raster","vector"],"force":false,"bounds":[39.6,115.8,40.3,117.0]}
force 为 true 时忽略半年有效期;为 false 时只安排过期文件。整分类更新受启动参数 --refresh-batch 限制;明确框选范围时只安排范围内命中的文件。
缓存管理删除请求示例:
{"categories":["raster","rail"],"bounds":[39.6,115.8,40.3,117.0],"confirmation":"DELETE_CACHE"}
更新和删除接口都要求管理员会话,并分别限制为每分钟最多 3 次请求。仅缓存模式下更新接口返回 409 CACHE_ONLY_MODE。
9.3 当前视窗刷新
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/cache/refresh?south=...&west=...&north=...&east=...&zoom=... |
刷新当前视窗 |
当前视窗刷新必须在请求中提供有效包围盒和缩放级别,并受瓦片数量上限约束。该接口和 /api/cache/refresh-all 都要求管理员会话;主地图普通用户界面不提供缓存刷新按钮。
10. 线程与下载模型
- HTTP 服务:多请求线程。
- 缓存刷新线程池:8。
- 后台瓦片下载线程池:16。
- OpenFreeMap MVT 下载线程池:24。
- 低缩放铁路聚合线程池:1(独立于 HTTP 和前台矢量下载)。
- 低缩放聚合源瓦片读取线程池:4(独立于前台 OpenFreeMap 请求)。
- 天地图手动瓦片下载线程池:4。
- 开源地图批量下载线程池:4。
- 服务器响应线程与上游下载线程分离。
- 手动下载任务可在下载页中断;任务级取消事件在每次取瓦片前和工作线程派发时检查。
- 缓存写入使用统一临时目录和原子替换,避免服务器重启导致正式缓存出现损坏瓦片。
- OpenFreeMap 数据缺失时进入后台任务,页面轮询状态并自动重试。
10.1 资源上限
| 项目 | 上限 |
|---|---|
| 单次 MVT 查询瓦片数 | 1,200 |
| 单次当前视窗刷新坐标数 | 512 |
| 普通工程文件 | 100 MB |
| 登录请求体 | 16 KB |
| 天地图下载请求体 | 1 MB |
坐标列表在分配内存前先执行数量检查,避免伪造超大范围请求造成内存耗尽。
11. 安全设计
11.1 网络边界
- 默认绑定
127.0.0.1。 - 通过 WebVPN/反向代理访问时,代理本身必须启用认证、HTTPS 和访问控制。
- 随机 URL 前缀只能用于路由,不能替代身份认证。
- 不建议暴露到公网。
11.2 静态文件
- 使用明确白名单。
- 只公开主页面、样式、脚本、文档、API 对比页、下载页和
vendor/。 - 秘密文件、缓存、日志、源码、工具脚本、
data/map-library/不可通过 URL 访问。 - 编码的
..、%2f、%2e路径会先解析为真实路径,再做白名单检查。 - 禁止访问项目目录之外的文件。
11.3 管理员认证
- 用户名固定
admin。 - 管理员密码只从环境变量、启动参数或本地秘密文件读取。
- 会话令牌使用
secrets.token_urlsafe(32)。 - 会话有效期为 8 小时。
- Cookie:
HttpOnly、SameSite=Strict。 - 代理声明 HTTPS 时添加
Secure。 - Cookie 路径优先使用实际随机前缀路径。
- 退出登录清空服务器上的全部管理员会话,并删除根路径、API 路径和随机前缀路径下的 Cookie。
- 未登录时下载页只显示密码框。
11.4 CSRF 与跨域
- 管理员 Cookie 使用
SameSite=Strict。 - 管理员写接口需要有效会话或 HTTP Basic Auth。
- 全量缓存刷新需要管理员认证。
- 跨域 CORS 不授予凭证;管理员会话不能由任意第三方源跨域调用。
- 公共只读接口仍可能被跨域读取,这是为了支持
file://或独立前端调试;正式部署应通过代理限制来源。
11.5 频率限制
| 操作 | 限制 |
|---|---|
| 管理员登录 | 8 次 / 10 分钟 |
| Basic Auth 尝试 | 20 次 / 10 分钟 |
| 状态接口 | 60 次 / 分钟 |
| 本地矢量分块 | 300 次 / 分钟 |
| 铁路接口 | 40 次 / 分钟 |
| 水域接口 | 60 次 / 分钟 |
| 行政边界接口 | 60 次 / 分钟 |
| 天地图状态/任务轮询 | 180 次 / 分钟 |
| 天地图任务创建 | 12 次 / 分钟 |
| 当前视窗刷新 | 4 次 / 分钟 |
| 全量缓存刷新 | 2 次 / 分钟 |
服务通过实际连接的客户端地址识别来源。反向代理场景下,如果代理将所有请求都转为回环地址,多个外部用户会共享同一频率上限。
11.6 密钥保护
- 天地图 KEY 不进入浏览器代码。
- 上游错误和下载任务日志中的 KEY 会被替换为
***。 admin-password.txt、tianditu-key.txt不可通过 HTTP 获取。- Windows ACL 限制为当前运行用户。
.gitignore排除秘密文件、缓存和 Python 字节码。
11.7 XSS 与输入处理
- 外部铁路名称、港口名称、行政名称等动态文本进入 HTML 前使用转义或
textContent。 - 本地行政名称标签经过 HTML 转义。
- 资源加载错误使用 DOM
textContent,不再拼接 HTML。 .mpstd导入限制为 100 MB,并校验格式、版本和结构。- GeoJSON/MVT 几何在服务端裁剪到瓦片范围。
11.8 响应安全头
主要响应包含:
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer
X-Frame-Options: SAMEORIGIN
X-Robots-Tag: noindex, nofollow
Permissions-Policy: camera=(), microphone=(), geolocation=(self)
12. 安全审查结果
本轮审查修复了以下问题:
- 修复编码路径穿越可读取秘密文件的问题。
- 静态文件改为白名单,日志、缓存、源码和工具不再公开。
/api/cache/refresh-all改为必须管理员认证。- 当前视窗刷新增加坐标数量上限,避免
MemoryError和上游任务洪泛。 - MVT 查询在创建坐标列表前检查瓦片数量。
- 登录和 Basic Auth 增加频率限制。
- 退出登录要求有效管理员会话,并清空全部管理员会话。
- 修复天地图 KEY 可能进入错误日志的问题。
- 修复本地行政名称标签的 HTML 注入风险。
- 移除状态接口中的绝对缓存路径。
- 增加安全响应头。
- 限制 Windows 秘密文件 ACL。
- 缓存大小统计增加 15 秒缓存,降低状态接口扫描成本。
安全验证结果:
正常页面和静态资源 200
秘密文件直接访问 404
编码路径穿越访问秘密文件 404
编码路径穿越访问缓存 404
未认证全量缓存刷新 401
未认证退出登录 401
超大范围 Z18 刷新 400
管理员登录/访问/退出后访问 200/200/401
连续错误登录 第 8 次开始 429
13. 仍需运维注意
- 当前管理员密码长度不足推荐值,服务启动时会提示。请改为至少 12 个字符的独立随机密码。
- 本地 HTTP 没有 TLS。通过 WebVPN 或其他代理发布时,应终止 HTTPS,并传递可信的
X-Forwarded-Proto: https、X-Forwarded-Ssl: on。 - 随机前缀不是认证机制,不能作为唯一访问控制。
- CORS 公共接口使用通配来源,以兼容
file://调试;正式环境建议在代理层限制 Origin。 - 第三方地图、Overpass、OpenFreeMap、Esri、天地图数据需定期核对许可、署名声明的服务条款。
- Leaflet 1.9.4、D3 7.9.0、d3-contour 4.0.3 为当前打包版本。升级前应复核兼容性和安全公告。
- 本地矢量瓦片当前合计约 5.19 GB,应监控 D 盘空间。
- 1:100 万数据适合区域级展示;1:25 万仅在已导入的分幅范围内提供更细数据,范围外会回退 1:100 万。
14. WebVPN 与随机前缀
页面全部使用相对路径,支持:
http://127.0.0.1:8080/rUY4nlbUt3xbtGYo7sFkQ733oYgifvb6/
转发到:
http://127.0.0.1:8081/
服务可从 /tiles/ 和 /api/ 标记自动剥离未知前缀。
反向代理要求:
- 保留请求方法、查询参数和请求体。
- 保留 Cookie 和
Set-Cookie。 - 正确传递 HTTPS 协议头。
- 不要缓存管理接口响应。
- 不要公开转发
/cache/、秘密文件、源码或日志。 - 建议配置额外身份认证和 IP 白名单。
15. 故障排查
15.1 页面提示资源加载失败
- 确认服务运行在
8081。 - 检查随机前缀是否以
/结尾。 - 检查反向代理是否正确转发
vendor/、app.js、coordinate-systems.js和styles.css。 - 当前前端会对 D3、Leaflet、应用脚本执行最多 5 次带时间戳的重试,Service Worker 对静态资源额外重试 3 次。若页面仍然报错,再检查网络和代理。
- 浏览器缓存使用
map-studio-runtime-v5;激活新 Service Worker 时会删除旧版同名前缀缓存。仅在服务端仍异常或代理持续失败时才需要强制刷新。
15.2 天地图页面无法登录
- 检查
admin-password.txt或环境变量。 - 检查是否有足够 ACL 读取秘密文件。
- 连续错误登录会触发 429,等待 10 分钟后重试。
- 通过 HTTPS 代理访问时,检查
X-Forwarded-Proto。
15.3 退出后仍然登录
当前版本退出会清空全部管理员会话。若仍显示登录,确认浏览器加载的是新页面并检查代理是否缓存了管理接口。
15.4 缓存刷新失败
- 当前视窗过大时先放大。
- 全量刷新需要管理员会话。
- 检查上游网络、代理设置和
server.stderr.log。 - 检查 D 盘剩余空间。
15.5 水域或植被接缝异常
- 确认服务使用当前版本。
- 确认用户环境数据版本为
openfreemap-environment-block-v6。 - 清理旧 Service Worker 缓存或强制刷新页面。
- 检查服务端是否按 MVT extent 裁剪路径。
15.6 本地矢量加载失败
Invoke-WebRequest http://127.0.0.1:8081/api/local-vector/status
检查:
cache/local-vector/manifest.json是否存在。- 对应 Z/X/Y 文件是否存在。
- 数据是否在覆盖范围内。
- 重新运行构建工具。
16. 发布检查清单
- 修改默认管理员密码为至少 12 个字符。
- 确认服务只监听回环地址或受保护的内网地址。
- 确认反向代理启用 HTTPS。
- 确认随机前缀不是唯一认证。
- 确认秘密文件 ACL 正确。
- 确认
/admin-password.txt、/tianditu-key.txt、/cache/返回 404。 - 检查安全响应头。
- 检查超大缓存刷新请求返回 400。
- 检查未认证全量刷新和登出返回 401。
- 检查天地图下载和退出流程。
- 检查磁盘空间和缓存大小。
- 导入新压缩包后检查来源数据库和瓦片清单。
- 检查第三方数据许可与署名。
- 备份工程文件和自建瓦片清单。