机构经纬度坐标采集与核验
触发场景
这批学校的经纬度帮我查一下/空缺的坐标补全,要 WGS84核对下这些坐标对不对,有些可能是别的坐标系把坐标填进这张表,存疑的标红这批上报值到底是 GCJ-02 还是 WGS84
输出承诺:坐标统一为 WGS84(6 位小数)+ 每条的来源与可信度 + 存疑项标红 + 核对报告 + 结构保真证明。
〇、前置:必须自备天地图 key
本技能不内置任何 key,第一次使用必须让用户提供。
- 打开
https://console.tianditu.gov.cn注册,创建应用。 - 应用类型必须选「服务端」,并开通「地理编码」服务。
- 提供 key(任选其一):
-python scripts/pipeline.py set-key --tk <KEY>
- 环境变量TIANDITU_TK
- 写入~/.config/tianditu/tk
关键约束:服务端 key 必须用非浏览器 User-Agent 调用。带 Chrome UA 会返回 HTTP 403 / {"code":301013,"msg":"权限类型错误"};这不是 key 失效(无效/不存在的 key 是 301001),只是 UA 与 key 类型不匹配。技能已内置非浏览器 UA,用户不用管,但排查时要记得这条。
坐标系:天地图返回 CGCS2000,与 WGS84 差厘米级,直接当 WGS84 用,不要再做 GCJ-02 反解(做了反而偏几百米)。
一、五个阶段
mapx(导出机构名→行号)
↓
geocode(天地图采集,带缓存)
↓
classify(反判基准 → 统一转 WGS84) ← 只在有参照值时做
↓
fill(写入 + 标红)
↓
verify(规则校验 + 结构保真)→ report(报告)
1. mapx 导出机构名到行号的映射
python scripts/pipeline.py mapx --target 名册.xlsx --name-col H --start-row 2 --out mapx.json
先做这一步的原因:同一个机构名在名册里往往横跨多行(多台设备),后续写入要一次覆盖全部行;名称里的全角括号、多余空格会导致匹配失败,必须先看清实际写法。
2. geocode 采集坐标
python scripts/pipeline.py geocode --names names.txt --out geocode.json \
--cache .cache.json --min-score 80
names.txt 每行一个名称;同一机构有多个叫法时写 名册名,查询词 两列(名册写简称、名录写全称、地图上是旧名的情况很常见,三个名字都要试)。
--cache务必给:天地图有配额,重复查询直接命中缓存。- 结果带
score(0~100)与level。level含「区县/地市/省/国家行政区划」的是回退结果,不可用;score < 80要存疑。 - 查询词越接近地图 POI 名命中越好。机构名查不到时改用「行政区 + 机构类型」的地址式查询。
- 天地图对村级、园区级小 POI 覆盖一般,乡镇以下查不到属常态。
3. classify 反判基准
python scripts/pipeline.py classify --pairs pairs.csv --out classified.json
pairs.csv 表头固定为 name,reported_lng,reported_lat,ref_lng,ref_lat:同一机构,一列是待判定的上报值,一列是已知为 WGS84 的参照值。
判定逻辑:把上报值分别按「原样即 WGS84」和「GCJ-02 反解」两种假设去比参照值,谁近算谁。两支差距小于 --min-gap(默认 200 米)时返回 uncertain,不要硬判。
转换用完整椭球参数的迭代反解,不是「偏移量二次相减」的近似公式。华中一带 GCJ-02 相对 WGS84 偏移约 (+0.0056°, -0.0027°) ≈ 540 米(经)/300 米(纬),这个量级本身可以用来判断一批数据用的是哪套基准。
4. fill 写入
values.json 格式:
{
"某某学校": {"lon": 112.717678, "lat": 30.408432, "red": true, "h_red": false, "src": "天地图"}
}
python scripts/pipeline.py fill --target 名册.xlsx --name-col H --lon-col J --lat-col K \
--start-row 2 --values values.json --log log.json # 先加 --dry-run 看一遍
- 只改经度/纬度两列,其余列一律不动。
red控制坐标列标红;h_red控制机构名列标红(不传则不动该列颜色)。- 默认写前自动备份原文件(
--no-backup可关)。 - 数字格式统一
0.000000。
标红约定(要在交付时说清)
| 位置 | 含义 |
|---|---|
| 机构名列标红 | 该机构的坐标值不可信,或仍无坐标,需退回主管单位核实 |
| 坐标列标红 | 坐标值存疑、来源为自动地理编码、待人工确认 |
5. verify + report
python scripts/pipeline.py verify --target 名册.xlsx --baseline 原始副本.xlsx \
--name-col H --lon-col J --lat-col K --start-row 2 \
--bbox 112.0,30.0,113.5,31.0 --placeholder 114,31 --out verify.json
python scripts/pipeline.py report --out 核对报告.xlsx --log log.json --verify verify.json
校验规则(geo.check_pair):占位值 0,0、自定义占位值、经纬列颠倒、超出目标区域 bbox、小数位不足、空值;另外统计多个机构共用同一坐标(疑复制)。
结构保真必做:拿一份改之前的同形状副本当 --baseline,逐单元格比对(排除坐标列)。真实差异必须为 0,不为 0 说明改坏了别的内容。这是唯一能证明「只动了该动的列」的手段。
二、容易踩的坑
- 天地图 403 / 301013:key 类型与 UA 不匹配,见第〇节。
- 经纬列颠倒:上报表里
30.41/112.87这种「纬度在前」的写法很常见,looks_swapped()会识别,classify会自动交换并在结果里标注。不识别就会把整个市区的点搬到几百公里外。 - 把 CGCS2000 又做一次 GCJ-02 反解:直接引入约 500 米错误。天地图结果和 WGS84 是同尺度的。
- 批量查询把名册名直接当查询词:名册写简称、名录写全称、地图上可能是旧名。三者都试。
- 同名异地串味:搜「XX 幼儿园」很容易返回同省另一个县的同名机构。必须校验行政区域与门牌。
- 逐单元格
ws.cell()扫描大表:上万单元格会慢到不可用。用iter_rows(values_only=True)。 - 文件正在编辑器中打开:写入前确认已关闭预览,否则保存可能冲突或丢失。
- 小 POI 的误差量级:村级/园区级机构的 POI 定位误差可达数百米。正式上报数据应退回主管单位确认,自动采集值只做占位或初筛。
三、依赖与文件结构
依赖见 requirements.txt(只有 openpyxl,其余全走标准库)。安装后先自检:
python scripts/pipeline.py check --online # 依赖 + key + 一次在线查询
python scripts/selftest.py # 离线单测,不联网、不用 key
org-coord-collect/
├── SKILL.md 本文件,给 AI 读的执行说明
├── README.md 给人看的安装与使用步骤
├── requirements.txt
└── scripts/
├── geo.py 坐标转换、距离、基准反判、数值校验
├── tianditu.py 天地图地理编码客户端(key 读取、UA、缓存)
├── pipeline.py 全流程命令行入口
└── selftest.py 离线自测
四、汇报时必须说清的四件事
- 坐标系来源与转换过程:原始值是什么基准、怎么判定的、转成了什么。
- 可信度分级:哪些是 POI 精确命中、哪些是同级参考点、哪些是回退结果、哪些没查到。
- 结构保真结果:行数/列数是否未变、除坐标列外差异是否为 0。没做这一步就不要声称「只改了坐标列」。
- 遗留风险:自动采集的坐标未经机构确认;小 POI 误差可能有数百米;用非官方渠道查坐标有稳定性与合规风险,正式数据建议走官方 API 或退回主管单位补报。