首页 / 资讯中心 / 文章详情

全球省市区经纬度数据包:JSON结构、SQL导入与工程避坑

全球省市区经纬度数据包:JSON结构、SQL导入与工程避坑 ★ FEATURED ARTICLE
简介资源包提供全球各国与中国全量的省市区县层级结构及经纬度坐标数据适用于地图打点、地区联动选择、地理信息系统开发等场景。包内共4个文件含2个JSON数据文件和2个SQL脚本JSON便于前端直接解析SQL包含建表结构与数据插入语句可快速导入主流数据库兼顾不同开发习惯。压缩包整体仅385KB轻量易部署。其中中国数据覆盖全部省份、城市、区县及香港、澳门特别行政区并保留父子层级关系可按省→市→区县逐级下钻全球数据涵盖中国以外主要国家和地区的城市与地区信息每条记录均携带经纬度坐标。目前已有376人学习尤其适合需要快速接入行政区划数据或实现省市区联动下拉的开发者。拿到后可直接用于离线地图、区域搜索、数据可视化及基于位置的服务省去自行采集与清洗数据的繁琐工作。1. 全球省市区经纬度JSON与SQL数据包先看清层级结构再谈导入与使用全球各国省市区城市地区层级结构带经纬度的JSON与SQL数据包是地理业务系统里最容易被低估的一类基础数据。真正接进生产环境的人都知道行政区划远不是“一张表、一套JSON”那么简单有的国家两级就到底有的要四级才到区县同一个“市”在不同语境下可能是地级市、县级市或直辖市下属的区。这个数据包的价值在于把各不相同的行政层级统一成可查询的JSON嵌套结构和可外键关联的SQL表。适合谁用后端开发、数据工程师做地址库、电商收货地址解析、物流路径规划、地图可视化拿它当初始数据源能省下几周爬取时间。这篇内容不假设你已经拿到文件只把结构、导入、校验和坑位讲透。2. 数据包内部结构JSON的嵌套层级与SQL表关系怎么设计2.1 JSON结构从国家到区县的四级嵌套是怎么组织的先拆JSON。这类数据包常见的组织方式是一个根数组数组里每个元素代表一个国家国家下面挂省份省份下面挂城市城市下面挂区县有些还会挂到街道。示例结构如下数据已脱敏{ country_code: ZZ, country_name: 示例国, level: 1, lat: 35.0, lng: 103.0, children: [ { code: ZZ-01, name: 示例省, level: 2, lat: 35.5, lng: 103.5, children: [ { code: ZZ-0101, name: 示例市, level: 3, lat: 36.0, lng: 104.0, children: [ { code: ZZ-010101, name: 示例区, level: 4, lat: 36.1, lng: 104.1, children: [] } ] } ] } ] }这个嵌套设计的核心是用children数组表达父子关系每个节点自包含code、name、level、lat、lng。level字段特别重要因为不同国家的层级深度不一样。以某些国家为例首都直辖区下面的区是省级还是市级直接看level比看名字靠谱得多。我一般会先写一个Python脚本递归遍历数一下每个层级有多少节点确认数据规模和自己的预期一致再放进业务库。如果发现某个国家只有两级就到底了那不是数据缺了而是那个国家的行政体系本来就只有省和市两级。level字段在设计上就承担了这种“动态层级”的适配作用。import json with open(regions.json, encodingutf-8) as f: data json.load(f) def count_levels(nodes, level, stats): for node in nodes: stats[level] stats.get(level, 0) 1 count_levels(node.get(children, []), level 1, stats) stats {} count_levels(data, 1, stats) print(stats) # 预期输出类似 {1: 200, 2: 3500, 3: 40000, 4: 120000}这段代码不做什么复杂的计算就是把每个level的节点数统计出来。stats字典的key是层级value是该层级的节点总数。运行后如果发现level 4的数量特别大说明数据包包含区县级数据如果level最高只到3那它可能只到城市级。这个数字直接决定后续建表时需不需要为level字段建索引。JSON里的lat和lng是每个行政区域中心点的坐标注意不是边界。如果你要做区域圈选或地理围栏光靠这个数据是不够的它只够做定位展示和辅助判断归属。2.2 SQL表结构单表自关联为什么比多表设计更省事SQL版本一般做成一张表加parent_code自关联而不是为每个层级单独建表。原因很直接各国层级数量不一样多表结构遇到层级不够或层级过多的国家会很难受单表自关联只需要一个level字段就能兼容所有情况。建表语句大致如下CREATE TABLE regions ( code VARCHAR(32) PRIMARY KEY, name VARCHAR(128) NOT NULL, name_en VARCHAR(128), level TINYINT NOT NULL, parent_code VARCHAR(32), lat DECIMAL(10,6), lng DECIMAL(10,6), sort_order INT DEFAULT 0, INDEX idx_level (level), INDEX idx_parent (parent_code), CONSTRAINT fk_parent FOREIGN KEY (parent_code) REFERENCES regions(code) );把parent_code上的索引单独拎出来是因为业务上最频繁的查询是“按父级查子级”比如查某省份下的所有城市。没有这个索引数据量到几十万行时查询会明显变慢。level索引则用来支持按层级过滤比如“只要国家或省份级别”的列表。这里有个参数选择问题。code用VARCHAR(32)是为了兼容不同国家的编码规则有些国家会用字母加数字混合编码纯数字字段装不下。lat和lng用DECIMAL(10,6)6位小数对应的精度大约0.1米对行政区划中心点来说绰绰有余。如果你看到精度更高的数据比如8位小数那通常是边界点而不是中心点导入前要确认用途。自关联表查起来比多表JOIN简单。比如要查“某市的完整路径”可以用递归CTE也可以在业务代码里循环parent_code直到为NULL。常见做法是直接用代码循环因为层级最多四到五级循环开销可以忽略不计。2.3 经纬度字段的坐标系与精度选择数据包里存的经纬度95%以上是WGS84坐标系这是世界标准绝大多数GPS设备直接输出这个坐标。如果业务地图只覆盖单一国家且依赖该国地图服务商提供的数据那就涉及坐标转换WGS84转GCJ-02是必须的否则在地图上标记会出现几百米的偏移。这是个经典翻车点。我举一个实际案例某团队把数据包里的经纬度直接当GCJ-02用结果所有点位在地图上集体往东南方向偏了几百米。查了半天才定位到坐标系不一致。处理办法是先确认数据包的元信息没有元信息就抽查两三个经纬度把它们和在线地图上的已知地标比对偏差在几百米量级就基本可以判定是坐标系混用。精度方面统一保留到6位小数就够了。1度纬度约等于111公里6位小数对应0.1米级别十进制度数再长就是纯冗余。有些源文件会给到8位甚至9位小数导入前用ROUND函数规整一下顺便能过滤掉非法坐标——比如纬度超过90或经度超过180的脏数据。3. 把JSON与SQL真正跑起来导入、解析与业务对接的三个落地步骤3.1 SQL导入前的三个前置检查编码、约束顺序、重复主键拿到SQL文件不要直接往数据库里灌。我见过太多人第一步就翻车文件是UTF-8带BOM导入时数据库连接用了gbk结果所有中文名全部乱码或者表里带外键约束直接导入时子记录先于父记录被插入外键检查直接报错中止。前置检查我一般分三步都在命令行不超过两分钟完成。第一个检查是文件编码。用file命令看文件类型确认是UTF-8还是带BOM。file regions.sql # 输出里出现 UTF-8 Unicode (with BOM) 时要注意 sed -i 1s/^\xEF\xBB\xBF// regions.sql # 去掉BOM头第二和第三个检查可以合并做先看CREATE TABLE语句里的字段定义再统计INSERT语句里的记录数和JSON文件里的节点数对一下两个数字不一致就要警觉。grep -c INSERT INTO regions.sql python3 -c import json; print(len(json.load(open(regions.json, encodingutf-8))))这个对比的思路很简单SQL文件和JSON文件是同一份数据的不同形态数量对不上说明文件本身有问题或存在重复主键。一旦发现数量不一致先别急着改数据用主键冲突检测脚本过一遍。-- 导入后立刻执行查重复主键 SELECT code, COUNT(*) FROM regions GROUP BY code HAVING COUNT(*) 1;如果查出重复多半是源数据在生成时把某些地区的同一编码写了两遍。处理方式要看具体业务能容忍就用ROW_NUMBER()取最新一条不能容忍就要找数据提供方要修订版。这个坑在越大的数据集里越常见。导入命令本身没什么玄学就一行。用MySQL举例需要指定utf8mb4字符集、关闭外键检查先导入父表。mysql -u root -p --default-character-setutf8mb4 \ -e SET FOREIGN_KEY_CHECKS0; SOURCE /path/to/regions.sql; SET FOREIGN_KEY_CHECKS1; mydb用--default-character-setutf8mb4而不是utf8是因为utf8在MySQL里最多支持3字节有些生僻字的地名用4字节编码utf8mb4才能完整存下。FOREIGN_KEY_CHECKS先关再开是给那些没有按父子顺序排列的INSERT语句兜底先让数据全进去再用第5章的方法查悬空节点。3.2 Python解析JSON从嵌套字典到扁平列表的转换脚本SQL导入解决的是“存”的问题JSON解析解决的是“用”的问题。很多业务系统不直接查数据库而是希望把JSON加载到内存里做快速匹配比如电商收货地址的省市区三级联动。JSON是嵌套结构业务层拿到的往往是要扁平列表方便构建下拉框。写一个递归展平函数是标准解法import json def flatten(nodes, parent_codeNone, resultNone): if result is None: result [] for node in nodes: item { code: node[code], name: node[name], level: node[level], parent_code: parent_code, lat: node.get(lat), lng: node.get(lng) } result.append(item) children node.get(children, []) if children: flatten(children, node[code], result) return result with open(regions.json, encodingutf-8) as f: raw json.load(f) flat flatten(raw) print(len(flat))这个递归函数先把当前节点append进result再对有children的节点递归调用自身parent_code参数一路传递下去。这样得到的flat列表每一行都带完整的父子关系链可以直接批量写入SQL表也可以转成DataFrame做分析。有一个参数要注意node.get(lat)和node.get(lng)用了get而不是直接下标访问原因是某些节点可能缺坐标比如一些争议区域的中心点被数据提供方留空。用get能避免KeyError缺省值是None后面清洗时统一处理。展平之后我一般再做一步处理把level1的parent_code置为None表示顶层节点。另外还要保留children为空但level4的叶子节点它们代表区县级地区对电商收货地址来说是最常用的一级。这里容易踩的坑是递归深度Python默认递归限制是1000层而行政区划最多四五层不会触发RecursionError。但如果数据提供方把某些层级的children写成循环引用递归就会无限进行。可以在函数开头加一个visited集合已处理的国家代码放进去遇到重复就直接返回。3.3 与业务系统对接省市区三级联动的查询参数数据落库、JSON解析都完成之后下一步是写业务接口。最常见的场景是省市区三级联动前端先拉国家或省份列表选中后再拉下一级。这类接口的核心查询就是“按parent_code查children”。# 伪代码FastAPI风格假设db已完成SQLAlchemy会话配置 from fastapi import FastAPI from sqlalchemy import text app FastAPI() app.get(/regions) def get_regions(parent_code: str | None None, level: int | None None): sql SELECT code, name, level, lat, lng FROM regions WHERE 11 params {} if parent_code is None: sql AND level 1 else: sql AND parent_code :parent_code params[parent_code] parent_code if level is not None: sql AND level :level params[level] level sql ORDER BY sort_order, code return db.execute(text(sql), params).fetchall()这段代码用了一个常见写法parent_code为空时查顶层不为空时查指定父级下的子级。level参数是可选的如果业务需要“只显示市级”可以传入level3进行过滤。ORDER BY后面接sort_order再接code是为了让首都、直辖市排在普通城市前面sort_order字段在源数据里通常已经按区域重要性预设好了。这段接口代码假设Python 3.10以上str | None语法在更低版本会报错。有一点要提醒parent_code用字符串传参时要注意URL编码问题。某些行政区划代码里可能包含特殊字符不过大多数数据包的code只含字母数字和短横线问题不大。如果遇到带斜杠或空格的编码建议在接口层做一次转义或改用POST传参。这套对接逻辑对数据包本身没有任何改动属于纯消费端。写完之后建议抽查三五个地区的父子链比如选一个省份查它的城市列表再点进某个城市查区县列表确认每一级都没有断链。下一章专门讲断链这类坑。4. 行政区划数据落地避坑边界变动、重名、坐标偏移与父子断裂4.1 行政区划代码漂移去年还是市级今年变成了区级现象数据包里有某地区的code查它的level是3但业务方坚持说这个地区是地级市应该查它的children才能拿到区县。两边对不上。原因行政区划代码不是永恒不变的。很多国家都有辖区合并、拆分或级别调整的机制调整后原地区的行政级别和编码会变。数据包生成的时间点不同层级归属就不同。数据包本身没错只是政区调整发生在数据生成之后。解决不要试图在数据包里做实时更新那个维护成本太高。常见做法是业务系统里增加一张region_changes表记录code变更前后的映射旧数据查询时先映射到新code。具体的做法是在数据表里增加一个code_redirect字段存最新的替代编码查询时优先用新编码。这套方案翻车概率低因为行政区划调整频率不高。4.2 同名不同级查一个地名返回几十条记录现象某地名在SQL里查出来有一堆结果分布在不同的国家甚至同一个国家的不同层级。原因全球范围内地名重名太常见了。有些英文地名重名率尤其高比如某个常见的英文城市名在多个国家都有。如果业务只拿name做匹配不做父级约束结果必然混乱。解决所有查询必须带父级code约束。省市区三级联动天然规避了这个问题因为每一级都是通过parent_code从上一级带下来的。真正要小心的是地址解析场景用户输入“某市某区某街道”时解析算法必须先从国家或省份维度确定上下文。我会在解析结果里附带parent_code路径让前端展示“示例国 / 示例省 / 示例市 / 示例区”这样的完整面包屑重名冲突立刻暴露。4.3 经纬度坐标偏移点位集体偏到海里或邻国现象把数据包里的经纬度直接打到地图上点位整体偏移几百米到几公里个别边界地区直接画到了邻国。原因坐标系不匹配。前面第2章提过WGS84和GCJ-02之间有系统偏差。还有一种情况是数据包标注了WGS84但某些子区域的数据源实际用的是当地坐标系原始值直接存成了十进制度数。解决第一步是确认数据包元信息没有元信息就在地图上抽检。第二步是写一个坐标转换函数按需把WGS84转成GCJ-02。转换算法网上有公开的近似公式但我建议直接调用成熟工具库不要自己造轮子。第三步是加上坐标合理性阈值检查比如纬度绝对值不超过90、经度绝对值不超过180超出直接丢弃或标记。4.4 父子关系断裂子记录找不到父级外键约束一直报错现象SQL导入时外键约束失败或者导入后查询某省的城市列表返回空。原因源数据里存在悬空节点——子记录的parent_code在code集合里根本不存在。生成数据包时某些地区的数据被合并、拆分或删除但子节点的父级编码没有同步更新。解决导入时先不建外键约束等数据完整导入后再用一条SQL查出所有孤儿记录。这个在第5章会详细展开。查出孤儿记录后先看数量数量少就手工映射到正确的父级数量多就要考虑是不是整个层级的编码规则都变了需要重新导入。遇到这种情况我会把“孤儿率”作为数据质量的硬指标。4.5 UTF-8的隐性陷阱生僻字地名导入后变问号现象导入后name字段大部分正常个别生僻地名显示为问号或乱码。原因MySQL里utf8字符集只支持3字节UTF-8编码而某些生僻地名用字需要4字节编码。连接串没有指定utf8mb4时这些字就会被替换成问号。解决建表时直接指定DEFAULT CHARSETutf8mb4连接串里也加charsetutf8mb4。如果表已经建错了用ALTER TABLE改表字符集同时把受影响列逐列转成utf8mb4。从数据包落地第一天就用utf8mb4能为后面省掉很多痛苦。UTF-8这三个字在MySQL语境下有歧义按utf8mb4理解就不会翻车。5. 数据校验与质量评估怎么判断这套全球省市区数据靠不靠谱5.1 完整性校验统计每层节点数和JSON源文件对比数据导入到SQL后第一件事就是做完整性校验。别相信导入过程没报错就代表数据是完整的很多SQL文件在生成时就有缺漏。SELECT level, COUNT(*) AS cnt FROM regions GROUP BY level ORDER BY level;这个查询会输出每个层级的节点数量。把这些数字和第2章里Python统计的JSON节点数逐一对比。对不上就说明SQL文件在转换过程中丢失了记录或者JSON被截断过。还有一种更细致的查法是验证母子总数关系。理论上除了顶层节点外所有子节点都应该能在父节点列表里找到对应的parent_code。用一条NOT EXISTS查询就能扫出来SELECT COUNT(*) AS orphans FROM regions r WHERE r.parent_code IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM regions p WHERE p.code r.parent_code );这个COUNT结果如果是0说明数据包的父子关系是闭合的。如果是几百甚至上千那就要回到第4.4节处理孤儿节点。我个人的经验阈值是孤儿率orphans除以总节点数超过0.5%就必须换数据源或找数据提供方反馈。5.2 层级深度校验找出异常深或异常浅的节点行政区划数据的level字段虽然统一了但各国的实际层级并不一样。校验时重点不在于每一层的数量而在于有没有不符合预期的“极深”节点。import json with open(regions.json, encodingutf-8) as f: data json.load(f) def max_depth(nodes, depth1): if not nodes: return depth - 1 return max(max_depth(n.get(children, []), depth 1) for n in nodes) for country in data: d max_depth(country.get(children, [])) if d 5: print(country[country_code], depth, d)这段代码对每个国家计算最大层级深度超过5层的国家单独打印出来。正常情况下绝大多数国家的行政区划不会超过5层出现6层以上要么是数据里有“地区行政公署”这类中间层级要么是某一层被错误地多包了一层。发现深度异常的国家我会单独写一个脚本去看它的父链确认是设计如此还是数据错误。比如某国如果有“省—地区—市—县—镇”五级结构那它是合理的只是在做三级联动时要把它和那些只有“省—市—区”三级结构的数据区分对待。业务接口里level过滤参数在这里就是分水岭对深层级国家的数据前端要能动态显示每一层而不是写死三级。5.3 经纬度合法性校验边界框与合理性检查坐标校验是最容易被忽略但最容易出问题的环节。我见过坐标维度反了、经度写成190度、坐标落在海洋里等多种脏数据。写一个向量化的合法性检查和粗略的边界框检查import json with open(regions.json, encodingutf-8) as f: data json.load(f) def walk(nodes, issues): for n in nodes: lat, lng n.get(lat), n.get(lng) if lat is None or lng is None: issues.append((n[code], missing coords)) elif not (-90 lat 90 and -180 lng 180): issues.append((n[code], finvalid range lat{lat} lng{lng})) walk(n.get(children, []), issues) issues [] walk(data, issues) print(len(issues)) for code, reason in issues[:20]: print(code, reason)这段代码只做两件事坐标缺省检查和范围检查。range检查是硬指标lat超过90或lng超过180的节点直接判定非法。missing coords这类问题要看业务需求如果只是做行政区划名称展示缺坐标可以容忍如果要做地图打点缺坐标的地区建议用父级坐标继承。更进一步的合理性检查需要边界框数据把每个国家的经纬度边界范围min_lat、max_lat、min_lng、max_lng做成一张表用SQL JOIN查每个节点是否落在所属国家边界框内。这个检查不需要精确到国界所以误报率不高能抓出大部分坐标漂移问题。数据校验做完之后这套数据能不能用、哪里需要补你心里应该有数了。第6章聊两个细节怎么让查询更快以及数据更新时怎么避免把线上搞挂。6. 进阶技巧内存字典加速查询与数据更新版本化行政区划数据有一个特点总量不大全量数据通常几十万行完全放得进内存。所以进阶的第一招是把整张regions表加载到Redis或进程内字典接口查询不再走数据库而是直接查内存结构。常见做法是用country_code加parent_code做组合键构建一个“省→市→区”的层级映射查询时按路径逐级定位响应时间能压到1毫秒以内。这套方案在并发高的地址解析场景特别管用。第二招是数据更新版本化。行政区划数据不会频繁变化但每次变化都必须可追溯。我会在表里加一列version_id每次导入新数据时带上数据包版本号。更新时不是直接覆盖而是先写入staging表校验通过后把旧版本的数据标记为inactive新版本设置为active。查询时默认只查active版本历史版本保留用于审计。这个习惯帮我解决过不少“用户去年下的单地址今年查不到”的售后问题。最后一招是增量更新的判断标准。当数据提供方发布新版本时先全量比对新旧code集合用集合差集算出新增、失效和变更的节点数。新增和失效可以直接映射变更是最麻烦的——旧code指向的新code往往不是同一个level需要人工确认。我把这个比对逻辑做成定时任务每个月跑一次输出一份变更报告。这套数据包能不能用核心判断依据不是文件多大、格式多全而是导入后能不能回答三个问题每个节点能找到父级吗每个坐标落在合理范围吗每一条业务数据能追踪到版本吗三个问题都通过后续业务怎么折腾都不会有大问题。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站