简介:面向地图制图、地理信息教学与空间数据分析场景,带审图号GS(2022)1873号的中国标准地图提供权威国境线与全国版图数据,适合需要规范底图的学生、教师及GIS从业者使用。压缩包共14个文件、约584KB,涵盖shp与shx空间几何及索引、dbf属性表、prj投影定义、cpg字符编码、json配置数据和qmd地图文档等类型,形成从图形、属性到显示配置的完整结构,便于在常见GIS软件中加载、查询与二次制图。已有475人学习或下载,在底图核对、专题图制作和教学演示中具有一定参考度。使用者可获得标准化版图边界、要素属性与投影说明,并完成地图配准、符号化展示和空间分析;对于需要校验行政边界或制作规划图的用户,也可减少来源甄别、编码与投影不匹配带来的返工。
1. 一张被退回的大屏底图,把审图号 GS(2022)1873 推到了台前
去年帮某公司做一个区域业务的数据大屏,地图那屏做完自认为挺像样:深色底、发光边界、鼠标悬浮高亮。上线前内部过会,被一句问住了——“这张中国地图的审图号在哪?”再往下查更尴尬:底图是从素材站下的 SVG,界线画法和标准地图对不上,还用 clip 把图幅下半截裁了。返工两轮,最后换成中国标准地图、把审图号 GS(2022)1873号 老老实实落到图面上才过。
这类问题跟审美无关,跟“可追溯”有关。审图号是一串能查的编号,它把一张具体的图(图名、比例尺、投影、图幅范围、版本日期)唯一地钉住;GS(2022)1873 就是其中一份公开可下载的标准地图。后面几章讲的是我自己走过一遍的路:怎么核这个号、怎么把这张图配准进 WebGIS、怎么在 ECharts 里用它做省级分布图、以及上线前用脚本卡一道。适合做数据大屏、报告插图、论文配图、内部 GIS 底图的人看,新手能照着做,做过的人也大概率会在第 5 章找到自己翻过的车。
2. 审图号 GS(2022)1873 锁定了什么:图名、比例尺与三份交付物
2.1 从审图号里能读出什么、读不出什么
先把字符串拆开看。GS(2022)1873号这种结构里,前缀表示这是标准地图类审图号,括号里的年份通常对应送审/批准的年份,后面的流水号只是当年的序号。也就是说,光看这串字符,你读不出它画的是全国还是某个区域、比例尺是 1:4000 万还是 1:6000 万、投影是什么、图幅里有没有附图、注记是几号字——这些信息全在核验页面和下载包里,不在编号本身。
这一步是很多人跳过的。我见过不止一个项目把“GS(2022)1873”当成万能通行证,底图换了、比例尺改了、甚至行政区划调整过一版了,编号还挂着旧的。核验的时候我会固定抄六项下来:图名、比例尺、投影、图幅范围、审图号、版本/更新日期。抄完存成一份元数据文件,别只记在脑子里,三个月后设计问你“这张图哪来的”,你得答得出来。
提示:审图号和版本日期要分开记。审图号回答“这张图有没有经过审核”,版本日期回答“这张图是不是现势的”,两者缺一不可。
还有一个容易踩的细节:同一份标准地图往往提供多个比例尺版本,比例尺不同,界线概括程度、注记密度、附图大小都不一样。你从 1:6000 万的图上抠下来的省界,放到 1:1000 万的大屏上放大三倍看,锯齿和简化痕迹一目了然。选比例尺的经验是:先算你最终出图的物理尺寸或像素宽度,再倒推需要的比例尺,别反过来。
2.2 三种交付形态:JPG、EPS、自己切片
同一张标准地图,落地到项目里通常有三种用法,代价完全不同:
| 形态 | 典型场景 | 好处 | 代价 |
|---|---|---|---|
| JPG/PNG 栅格 | 报告插图、静态大屏、PPT | 开箱即用,固定分辨率,审图号已经印在图面上 | 放大糊,不能改配色,改一个省的颜色得整张重做 |
| EPS/AI 矢量 | 印刷出版、需要改配色和注记字号 | 可编辑,任意缩放不失真 | 需要设计软件,文件大,误删界线风险高 |
| 自己配准切片 | 交互式 WebGIS、可缩放地图 | 支持缩放漫游,能和业务数据叠加 | 要做配准、切片、坐标系对齐,工作量最大 |
选型上我一般按“输出介质”定,而不是按“哪个看起来专业”定。一份要打印在 A4 报告里的插图,JPG 完全够,你上矢量纯属给自己找事;一个要支持鼠标悬浮、下钻、联动的地图屏,那必须切片。中间的模糊地带是“静态大屏但分辨率 4K”:这种情况我倾向用高分辨率栅格(导出时按 1.5 倍尺寸出),而不是矢量,因为大屏没人放大看细节,但审图号那一行必须清楚——这一点矢量反而容易翻车,见 2.3。
EPS 的坑集中在“可编辑”这三个字上。标准地图的 EPS 里,界线、面填充、注记、审图号文本经常在同一个图层组里,取消分组之后很容易把某段界线当装饰线删掉;更常见的是为了适配版面整体缩放 80%,审图号字号跟着缩,导出印刷时低于可读阈值。我的做法是:EPS 只用来改填充色和注记位置,界线图层锁死不动,审图号单独提出来做成一个顶层文本对象,跟图幅边框一起固定。
2.3 别拿搜索引擎里那张 png 当底图
这条几乎是我见过最贵的省事。搜“中国地图 png”,出来的图来源五花八门:有的是几年前的旧版、有的把图幅下方的附图裁掉了、有的审图号被水印盖住、还有的干脆就是别人二次描摹的界线。这些图在肉眼层面“看着都差不多”,但一旦进了交付物,问题就没法收敛——你既证明不了它对应哪个版本,也定位不了哪一段界线画错了。
更麻烦的是传播链。设计给的图,是从运营那里拿的;运营是从上一版 PPT 里抠的;PPT 是外包做的。链条一长,任何一次修改都变成“谁也不敢动”。所以只要有第二个人参与,我就会在项目里搭一个底图资产库,一个文件配一个 sidecar 元数据:
# assets/basemap/china-standard-2022-1873.yaml id: china-standard-2022-1873 name: 中国标准地图(按核验页面填写图名) shen_tu_hao: "GS(2022)1873号" source: "标准地图服务系统公开下载" fetched_at: "2024-03-11" scale: "按核验页面填写" # 不要凭印象,抄页面 projection: "按核验页面填写" files: - china-2022-1873.jpg # 栅格,报告插图用 - china-2022-1873.eps # 矢量,印刷改色用 sha256: "用校验命令算出来的值" # 冻结用,流水线里比对 notes: "图幅要素完整,未做任何裁剪"这份 sidecar 就是底图的“黑匣子”:出问题时能倒查来源和获取时间;sha256用来冻结版本,任何人替换了文件,流水线里的比对会直接报错。算哈希很简单:
# 记录底图指纹,后续任何替换都会被发现 sha256sum assets/basemap/china-2022-1873.jpg # 只留前 16 位做人工核对也够用,完整值写进 yaml参数说明就一句:对二进制文件做哈希,任何像素级改动都会变;所以一旦冻结,就不能“顺手美个图再存一遍”,要改就换新 id。
2.4 一张图选型的判断顺序
把顺序理清楚,比记工具名有用得多。我的顺序是:
- 定输出介质——屏幕、印刷、还是两者都要;
- 定是否需要交互——要缩放漫游就走切片,否则一律栅格;
- 定坐标系——业务数据是什么坐标系,底图就往上对齐,不要两头都改;
- 最后才定分辨率和瓦片层级。
多数翻车是把顺序做反了:先挑工具(比如“我要用矢量因为高级”),再回头适配介质,结果印刷丢字、大屏糊图。第 3 章开始就是第 2、3 步的具体操作。
3. 把标准地图接进 WebGIS:配准、偏移与瓦片的最小闭环
3.1 先认清三套坐标:WGS84、GCJ-02、BD-09
交互地图上所有“整体飘了”的问题,根子上都是这里。工程里常见三套坐标:
| 坐标 | 常见来源 | 与 WGS84 的关系 | 典型误用 |
|---|---|---|---|
| WGS84 | GPS 原始数据、多数国际开源数据 | 基准 | 直接和国内底图叠加,出现几百米偏移 |
| GCJ-02 | 国内多数在线地图服务的展示坐标 | 非线性偏移,量级几百米 | 当成 WGS84 用,点位整体往一个方向偏 |
| BD-09 | 部分地图服务在其基础上再偏移 | 在 GCJ-02 上二次偏移 | 与 GCJ-02 混用,偏移叠加 |
原则只有一条:展示层统一到一套坐标,转换只做一次,位置固定在数据入口。我一般统一到 WGS84 展示,把所有来源的经纬度在入库或出图前转成 WGS84,底图也按 WGS84 配准。这样多源数据叠加时只需要排查一次。
WGS84 转 GCJ-02 的实现到处都有公开版本,下面是 Python 版,保留它是为了让你看清结构:
import math A = 6378245.0 # 椭球长半轴 EE = 0.00669342162296594323 # 偏心率平方 def _dlat(x, y): r = -100.0 + 2.0*x + 3.0*y + 0.2*y*y + 0.1*x*y + 0.2*math.sqrt(abs(x)) r += (20.0*math.sin(6.0*x*math.pi) + 20.0*math.sin(2.0*x*math.pi)) * 2.0/3.0 r += (20.0*math.sin(y*math.pi) + 40.0*math.sin(y/3.0*math.pi)) * 2.0/3.0 r += (160.0*math.sin(y/12.0*math.pi) + 320*math.sin(y*math.pi/30.0)) * 2.0/3.0 return r def _dlng(x, y): r = 300.0 + x + 2.0*y + 0.1*x*x + 0.1*x*y + 0.1*math.sqrt(abs(x)) r += (20.0*math.sin(6.0*x*math.pi) + 20.0*math.sin(2.0*x*math.pi)) * 2.0/3.0 r += (20.0*math.sin(x*math.pi) + 40.0*math.sin(x/3.0*math.pi)) * 2.0/3.0 r += (150.0*math.sin(x/12.0*math.pi) + 300.0*math.sin(x/30.0*math.pi)) * 2.0/3.0 return r def wgs84_to_gcj02(lng, lat): dlat = _dlat(lng - 105.0, lat - 35.0) dlng = _dlng(lng - 105.0, lat - 35.0) rad = lat / 180.0 * math.pi magic = 1 - EE * math.sin(rad) ** 2 sqrt_magic = math.sqrt(magic) dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrt_magic) * math.pi) dlng = (dlng * 180.0) / (A / sqrt_magic * math.cos(rad) * math.pi) return lng + dlng, lat + dlat逻辑说明:两个辅助函数算出的是弧度意义上的偏移量,再用椭球参数换算成度。参数里105.0 / 35.0是公式的参考原点,A和EE决定换算尺度,改动它们等于换了一套椭球,不要动。
实际用法上有个纪律:只做正向偏移用于图面叠加。有人问我能不能“转回去对齐”,我的答复一律是别在这条路上找后悔药——工程上正确的做法是从数据源头统一坐标系,或者在服务端存一份带坐标系标记的原始数据,展示时再按目标坐标系出图。靠反复转换“凑对齐”,偏移会越凑越大,排查起来就是玄学。
3.2 栅格配准:给 JPG 补上地理参照
标准地图的 JPG 是“图”,不是“带坐标的数据”。要把它挂到地图上,第一步是补地理参照。GDAL 的gdal_translate就够:
# 给无坐标的栅格补上地理参照:左上角经度 纬度 右下角经度 纬度 gdal_translate \ -a_srs EPSG:4326 \ -a_ullr $XMIN $YMAX $XMAX $YMIN \ china-2022-1873.jpg china-2022-1873-geo.tif # 看一眼结果,确认尺寸和范围没有反 gdalinfo china-2022-1873-geo.tif | head -20参数说明,每个都值得停一下:
-a_srs EPSG:4326:声明这份栅格用经纬度。注意它只是“声明”,不改变像素,所以四角必须和声明一致,否则后面全错。-a_ullr的顺序是左上角经度、左上角纬度、右下角经度、右下角纬度,不是常见的 xmin/ymin/xmax/ymax。这是第一次做配准时最常见的翻车点,顺序错了图会上下或左右翻转。- 四角值从哪来:优先从矢量版(EPS)的地理参照或图幅角点注记里读;实在没有,就在图上找两三个特征明显的地物点(湖岸拐点、河口、图廓交点)反推,再用第三个点验证。
验证方法是关键一步。配准完别急着切片,先把这份 tif 拖进任意一个能显示底图的工具里,叠一层已知坐标的点(比如几个城市的经纬度),看残差。我的容忍线是:在目标最大缩放层级下,误差不超过 1 个像素。超过就回去查四角——八成是-a_ullr顺序错了,或者图幅带了白边没裁。
补一句白边的事:标准地图导出时常带一圈白边或图廓装饰线,这些不属于地理范围。用gdal_translate -srcwin先裁掉再配准,比配准完再去修边界省事得多。
3.3 用 gdal2tiles 出瓦片并用 Leaflet 挂上去
配准好的 tif 可以直接切瓦片。Web Mercator 是默认选择,因为绝大多数前端地图库都按它算:
# 切 XYZ 瓦片,层级按图幅宽度估算:最小层级 ≈ log2(图幅像素宽 / 256) gdal2tiles.py -p mercator -z 3-7 --xyz --tilesize=256 \ china-2022-1873-geo.tif ./tiles/china1873参数说明:
-p mercator:输出 Web Mercator,前端可以直接用。-z 3-7:层级范围。下限按“整幅图刚好铺满一屏”算,上限按“审图号文字还清楚”算。层级开太高只会让栅格糊成一片,还白占磁盘。--xyz:输出 XYZ 命名(y 轴从上往下)。不加这个参数出来的是 TMS(y 轴从下往上),前端直接引用会上下颠倒——这是第 5 章要展开的一个坑。--tilesize=256:和前端默认瓦片尺寸对齐,别乱改。
前端用 Leaflet 挂载,注意maxNativeZoom和 attribution 两处:
const map = L.map('map', { minZoom: 3, maxZoom: 10, zoomControl: true, attributionControl: true }); L.tileLayer('/tiles/china1873/{z}/{x}/{y}.png', { minZoom: 3, maxZoom: 10, maxNativeZoom: 7, // 真实瓦片只到 7 级,再放大是拉伸,不是新数据 noWrap: true, // 禁止横向环绕,避免出现重复图幅 attribution: '底图:中国标准地图 审图号 GS(2022)1873号' }).addTo(map);maxNativeZoom是最容易被忽略的一个参数。不设它,用户放大到 10 级时浏览器会去请求不存在的 8、9、10 级瓦片,一片空白;设了它,Leaflet 会拿 7 级的瓦片放大显示,虽然糊但连续。noWrap: true同理,关掉横向循环,世界地图在大屏上往左拖不会出现第二份图幅。
attribution这一行不是装饰。审图号在图面上必须有位置,交互地图里最省事、最不容易被后续改动弄丢的位置就是它——它跟着地图容器走,不会因为某个业务图层重绘而消失。
3.4 审图号在图面上的落位与最小可读尺寸
不同介质对“可读”的要求差很多。下面这张表是我自己项目里在用的经验值,不是条文,按实际版面微调:
| 介质 | 建议位置 | 最小字号/高度 | 补充 |
|---|---|---|---|
| 网页交互图 | 地图容器右下角 attribution | 12px | 跟随容器,不随图层重绘丢失 |
| 静态导出 PNG(1920 宽) | 图幅右下角内侧,留 8px 边距 | 14px | 导出后 100% 缩放要能辨认 |
| 印刷 A4 插图 | 图幅下方居中或右下角 | 不小于图面短边的 1/60 | 与图廓留白,不与图例挤在一起 |
| 4K 大屏 | 右下角,距边 24px | 20px 以上 | 深色底上要用浅灰,别用纯白晃眼 |
两个实践细节。第一,别把审图号放在图幅外面又指望它出现在导出图里——Canvas 导出只截画布区域,画布外的 HTML 元素不会被带上,这是第 4 章要专门处理的问题。第二,深色主题下审图号的对比度要单独验一遍,我吃过一次亏:在 4K 屏上肉眼可见,投到会议室的投影仪上就成了一条灰线。
4. ECharts 省级分布图:边界裁剪、registerMap 与截图里的审图号
4.1 边界数据的三条来路与取舍
ECharts 从 5 版起不再随包提供地图数据,GeoJSON 得自己准备。常见的三条来路:
| 来路 | 精度 | 工作量 | 风险 |
|---|---|---|---|
| 从标准地图 EPS 里提取界线 | 高,与底图同源 | 大,需要矢量编辑 | 提取时误删要素 |
| 权威公开的边界数据 | 中到高 | 中,需核对版本 | 版本与底图不一致 |
| 第三方简化/美化版 | 低 | 小 | 界线画法与标准地图有出入,附图常缺 |
我的选择是第二条为主、第一条做校验:用公开边界数据出图,再和标准地图做一次目视比对,重点看省界走向、飞地、附图要素是否完整。第一条的完整提取成本太高,只在印刷级交付时才做。第三条我基本不用,省下来的两小时,后面返工要还回去两天。
比对这件事别凭印象,做个笨办法:把标准地图底图铺在下面,GeoJSON 描边叠在上面,同一比例尺下截一张图,逐段扫一遍。有出入的地方记下来,别急着“修一下”——地图边界不能自己描,出入大就换数据源。
4.2 用 mapshaper 裁剪与瘦身
原始边界数据往往几十兆,直接扔给前端会拖慢首屏。mapshaper 是命令行里最好用的一个,先做裁剪和简化:
# 1) 只保留需要的区域,按属性字段过滤 mapshaper china-full.geojson \ -filter 'adcode >= 100000' \ -o precision=0.0001 format=geojson china-filtered.geojson # 2) 按矩形范围裁剪(xmin,ymin,xmax,ymax),做区域专题图时用 mapshaper china-filtered.geojson \ -clip bbox=$XMIN,$YMIN,$XMAX,$YMAX \ -o precision=0.0001 china-clip.geojson # 3) 拓扑保持的简化,keep-shapes 防止小要素被抹掉 mapshaper china-clip.geojson \ -simplify 12% keep-shapes \ -o precision=0.0001 format=geojson china-slim.geojson参数说明:
-filter里的表达式按属性字段做布尔判断,字段名要和你数据里的实际字段对齐,写错不会报错,只会静默输出空文件——所以每步之后都mapshaper xxx.geojson -info看一眼要素数量。-simplify 12%是保留顶点比例。12% 通常能把体积压到原来的十分之一,同时省界还看得过去;低于 5% 就开始出现折角。keep-shapes必须加,否则面积小的要素会被整块删掉,地图上出现空洞。-clip bbox=的四个值是 xmin,ymin,xmax,ymax。裁剪会切开边界多边形,如果要做面填充,记得后面用-dissolve按目标字段合并。-o precision=0.0001控制坐标保留位数,约 11 米精度,对省级分布图足够,能再省一截体积。
简化完必须做一次拓扑检查:相邻省之间不能出现缝隙。mapshaper 默认保持拓扑,但你一旦混用了多个来源的数据,缝隙就出来了。快速检查的办法是把填充色设成半透明,缝隙会以底色的细线形式暴露——这招比写脚本快。
4.3 registerMap 与 option 里三个必调项
数据准备好,注册和配置就几行:
// geoJson 由 fetch 或本地 import 得到 echarts.registerMap('china-std', geoJson); const option = { geo: { map: 'china-std', roam: true, // 允许缩放漫游 aspectScale: 0.85, // 见下方说明,别用默认值瞎猜 itemStyle: { areaColor: '#0e1a2b', borderColor: '#3a5b8c' }, emphasis: { itemStyle: { areaColor: '#1b3a63' } } }, series: [{ type: 'map', map: 'china-std', geoIndex: 0, // 与上面的 geo 共用一套投影和缩放 data: valueList // [{name:'某省', value:123}, ...] }], visualMap: { min: 0, max: 5000, calculable: true, inRange: { color: ['#123', '#2a6', '#8fd'] } } };三个必调项:
aspectScale:地图的横纵比缩放系数。默认 0.75,很多模板照抄不改,结果整张图被压扁,形状一眼不对。全国图我一般用 0.85 左右,具体值以“和标准地图目视一致”为准,别背数字。geoIndex:series.type='map'和geo组件同时存在时,靠这个把两者绑在一起;不绑会出现两份重叠的地图,交互事件只命中其中一份。roam:允许缩放漫游时要配合zoom上限,否则用户能缩到边界数据精度之外,省界变成一堆折线。
name字段要和 GeoJSON 里的属性名一一对应,对不上就是一片空白加控制台无提示——这是 ECharts 地图最典型的静默失败。
4.4 导出图片里审图号怎么保住
大屏截图、报告配图基本都走toolbox.saveAsImage或直接对 canvastoDataURL。这里有个硬约束:Canvas 导出只截画布内的内容。你把审图号写在地图容器外的一个div里,或者写在地图下方的 HTML 说明区,导出的 PNG 里就没有它。
解决办法是把审图号画进画布,用graphic组件定位在右下角:
graphic: [{ type: 'text', right: 14, bottom: 12, silent: true, // 不参与交互,避免遮挡地图事件 style: { text: '审图号:GS(2022)1873号', font: '14px sans-serif', fill: 'rgba(200,210,225,0.9)' // 深色底用浅灰,别用纯白 }, z: 100 // 压在图层之上 }]silent: true很关键,不加的话这块文字会吃掉鼠标事件,用户在地图右下角悬浮时高亮失效,排查起来很费时间。z要给足,否则在某些渲染顺序下会被 visualMap 的图例盖住。
导出之后还有一步:把 PNG 拉回 100% 缩放,看审图号那行字是不是真的能认。我见过太多“代码里写了、图上也有、但导出后糊成一团”的案例,尤其在 2 倍像素比的屏上做的图,导出到 1 倍时字号直接减半。
5. 避坑与排查:底图上线前最常翻车的 5 个点
5.1 点位整体往一个方向偏几百米
现象:业务点位和底图叠在一起,所有点统一往东南或西北偏一段距离,形状没变形,只是整体平移。
原因:坐标系混用。底图是按 WGS84 配准的,业务数据来自某个返回 GCJ-02 的接口,两者直接叠加就是这个结果。偏移量随经纬度变化,所以看起来“有的地方偏得多、有的地方偏得少”,很像玄学,其实完全可复现。
解决:在数据入口统一转换,别在渲染层修。做法是把所有外部来源的经纬度在入库时转成 WGS84,并给数据表加一列标记原始坐标系,转换只在入库那一步发生。已经混了的存量数据,按来源分批重跑一次转换,不要写“渲染时按图层判断要不要转”这种逻辑——那个分支迟早会被人改错。
5.2 瓦片上下颠倒,或者整体错开一屏
现象:底图加载出来了,但南北颠倒,或者和点位错开正好一个屏幕高度。
原因:TMS 和 XYZ 的 y 轴方向相反。gdal2tiles.py不加--xyz输出的是 TMS 命名,前端 Leaflet 默认按 XYZ 请求,第 0 级就一格,错不开;层级一高,错位量正好是一个屏幕。BD-09 那套东西如果混进来,还会有额外的缩放层级偏移,现象类似但成因不同。
解决:切瓦片时统一加--xyz;如果瓦片是别人给的、改不了,就在前端 URL 模板里用{-y}让 Leaflet 自己换算。判断方法很简单:打开tiles/0/0/0.png,看它是不是全球一张图;再打开tiles/1/0/0.png和tiles/1/0/1.png,谁在上面就是谁的方向。
5.3 导出的大屏截图里审图号消失了
现象:页面上明明有审图号,导出的 PNG 里没有。
原因:审图号写在地图 canvas 之外的 DOM 里,saveAsImage和toDataURL都只截 canvas 区域。另一种情况是用了html2canvas之类整页截图,但地图是 WebGL 渲染的,截出来一片空白。
解决:审图号用graphic画进 canvas(见 4.4),或者导出后做二次合成——拿到 canvas 的 dataURL,再用一个离屏 canvas 把审图号文字画到右下角,最后一起输出。第二种更稳,但要注意字体加载完成后再画,否则会 fallback 成默认字体,字号宽度全变。加一条自检:导出图宽度小于 1920 时直接告警,小图上的审图号基本不可读。
5.4 EPS 转曲之后,审图号既搜不到也改不了
现象:印刷交付前把文字转成曲线,之后想改审图号——文本已经变成路径,逐节点编辑;做自检脚本时也检索不到这段文字。
原因:转曲是不可逆操作。很多排版流程默认“交付前全部转曲”,但地图类图幅里的审图号属于标识信息,转曲之后既失去了语义,也失去了校验手段。
解决:流程上分两份——一份保留文本层的可编辑母版,一份是转曲后的印刷稿;母版永远不进交付物,只用来出下一版。同时在 sidecar 元数据里记死审图号字符串,自检脚本查元数据而不是查图面文字。印刷前再目视确认一次转曲稿里那行字的字重和字距没被压缩。
5.5 简化过头,小要素消失、省界出现缝
现象:地图上少了几块小区域,或者两个相邻省份之间出现一条细缝,放大看是底色透出来。
原因:-simplify比例给太低,又没有keep-shapes,面积小的多边形被整块删掉;多个来源的数据混合简化时拓扑被破坏,公共边界两侧的顶点不再重合,缝隙就是这么来的。
解决:简化比例从 20% 起试,逐步降到能接受的最小值,每降一档跑一次要素数量比对;永远带keep-shapes;混源数据先合并再简化,不要分别简化后拼接。验证用半透明填充看缝,比写脚本快。这一步没有捷径,只能一档一档试出来。
6. 把审图号变成流水线卡口:一份导出前的自检脚本
上面那些坑,靠人盯是盯不住的。我的做法是在出图流水线里加一步自检,跑不过就不出包。脚本不长,但把最容易丢的几件事都卡住了:
import hashlib, sys from PIL import Image def sha256(path): h = hashlib.sha256() with open(path, 'rb') as f: for chunk in iter(lambda: f.read(1 << 20), b''): h.update(chunk) return h.hexdigest() def check_export(png, sid, frozen_sha, min_w=1920): errs = [] img = Image.open(png).convert('RGB') w, h = img.size # 1) 尺寸太小,审图号必然糊 if w < min_w: errs.append(f'导出宽度 {w} < {min_w}') # 2) 审图号必须写进 PNG 元数据,而不是只存在前端 DOM 里 if img.info.get('shen_tu_hao') != sid: errs.append('PNG 元数据缺审图号') # 3) 右下角区域对比度粗检:全白/全黑说明文字没画上或被裁掉 crop = img.crop((int(w*0.72), int(h*0.88), w, h)).convert('L') lo, hi = crop.getextrema() if hi - lo < 40: errs.append('右下角对比度过低,审图号疑似未渲染') # 4) 与冻结版本比对,防止底图被悄悄换掉 if sha256(png) != frozen_sha: errs.append('导出图与冻结版本不一致') return errs if __name__ == '__main__': problems = check_export(sys.argv[1], 'GS(2022)1873号', sys.argv[2]) print('\n'.join(problems) or 'checks passed') sys.exit(1 if problems else 0)四项检查对应四类事故:尺寸检查拦“大屏糊图”,元数据检查拦“审图号只写在前端”,对比度粗检拦“graphic 没渲染上”,哈希比对拦“底图被替换”。第三项是粗检不是 OCR,它只回答“这块区域有没有东西”,不回答“这行字对不对”——要精确校验文字,还是得靠元数据那一条。
| 检查项 | 拦什么 | 误报率 | 出了误报怎么办 |
|---|---|---|---|
| 导出宽度 | 小图糊字 | 低 | 调阈值,别关检查 |
| 元数据审图号 | 漏写字样 | 极低 | 检查导出时的 pnginfo 写入 |
| 右下角对比度 | 文字未渲染 | 中 | 有图例挡右下角时改采样区域 |
| 哈希比对 | 底图被换 | 零 | 换底图就更新冻结值,走一次评审 |
最后说个习惯。我现在的项目里,底图目录是只读的,替换底图要走一次变更:更新 sidecar、重算哈希、重跑一次目视比对、把审图号写进发布说明。麻烦,但换来的是任何一张出去的地图都能在三分钟内答清楚“这张图哪来的、什么版本、审图号是多少”。地图这东西,画错一条线可能没人发现,但一旦被发现,返工的不是一张图,是这一版所有的交付物。希望帮到你。
本文还有配套的精品资源,点击获取