简介:全国省、市、区、县、乡、镇、街道邮编及经纬度坐标数据集,收录46090条行政区划记录,覆盖从省级到街道的完整层级,面向GIS开发、物流配送、地图服务及数据分析等场景,可为地址解析、路径规划、区域统计提供基础数据支撑。压缩包为RAR格式,共1个文件,即一个可直接导入主流关系数据库的SQL脚本,大小942KB,脚本中每行对应一个行政区域,包含省市区县乡镇街道、邮政编码及经纬度等字段,便于按层级进行查询、关联与二次加工。已有795人浏览学习,对于需要快速搭建地理信息数据库的开发者而言,省去了自行采集与清洗数据的环节。配合SQL语句即可实现按地区筛选、坐标范围检索或邮编匹配,适用于智慧政务、电商物流、市场分析、灾害应急等需要精准位置信息的项目,是构建LBS应用和GIS服务的基础性参考资料。
1. 为什么需要一份全国省、市、区、县、乡、镇、街道邮编及经纬度坐标的基础表
做物流分拣、网点选址或地图可视化时,最缺的不是在线地图服务,而是一份能离线反复查的「全国省、市、区、县、乡、镇、街道邮编及经纬度坐标」基础表。把这个标题拆开看,其实是三个问题:行政区划怎么分层、邮编挂在哪一层、经纬度准不准。
三个问题凑到一起后,数据量比你想象的大,到乡镇街道一级通常接近五万行,而这五万行里最不可信的恰恰是乡镇街道的邮编和坐标。多数公开资料只给到区县,乡镇级坐标很多是爬虫顺手抓的,坐标系不清、偏移严重。
下面按一套能落地的流程来拆解:从层级建模、多源清洗、坐标系选择,到避坑和增量维护。目标不是做一份静态 Excel,而是把它变成可以长期查询和更新的本地库。适合 GIS 开发、后端工程师、数据同学,以及所有被地址数据折磨过的人。
2. 行政层级建模:用区划代码做主键,把省到街道拉成一张可查询的表
拿到数据先别急着 merge,先把「一张表能不能撑起五级行政层级」想明白。行政区划本质上是一棵树,从省到地市、从地市到区县、从区县再到乡镇街道,每一层都能往下钻。表的形状直接决定后续查询是顺手还是别扭。
2.1 行政区划代码为什么比名称更适合当主键
官方发布的行政区划代码是一串固定位数的编码,前两位代表省级,前四位代表地市级,前六位代表区县级,之后继续细分到乡镇街道。这套编码天然携带了父级信息,而且每年会随撤县设区、镇改街道调整。日常开发里大家更习惯用名称做关联,进入实战就会发现重名率极高,某市的「建设路街道」和另一个市的「建设路街道」,名字一样,邮编也可能相同。一旦拿名称去 join,后面排查数据错乱就是一场灾难。
所以主键必须选择 code 而不是 name。原因有三:一是名称可重复但 code 理论上唯一;二是 code 能直接推导出父级前缀,不用递归查树;三是变更时旧 code 可以标记失效而不是删除,不影响历史数据引用。
| 字段 | 类型 | 说明 |
|---|---|---|
| code | TEXT(12) | 行政区划代码,主键 |
| name | TEXT | 标准名称 |
| parent_code | TEXT(12) | 父级代码,省级可为空 |
| level | INTEGER | 1省 2市 3区县 4乡镇街道 |
| center_lng | REAL | 中心点经度 |
| center_lat | REAL | 中心点纬度 |
这里没有把邮编放进主表,原因在下一节说明。
2.2 字段粒度:邮编、坐标该挂在哪一层
一个街道通常对应一个或多个邮编,多个街道也可能共享一个邮编。邮编本质上是投递段的划分,并不严格对齐区划边界。如果直接在 divisions 表里放一列 postcode,遇到一对多只能存逗号分隔字符串,业务 join 时又要拆分,又慢又容易漏。
因此我一般会拆成两张表:divisions 只存区划与坐标,division_postcodes 存区划与邮编的关联关系,用 (code, postcode) 做联合主键。这样查询「某个邮编覆盖哪些街道」或「某个街道有哪些邮编」都是标准 SQL。坐标字段则直接冗余在 divisions 上,因为坐标是「点」属性,每个区划只需要一个中心点,不存在一对多。
另一个容易忽视的问题是层级不齐。有的区县下直接挂街道,中间没有乡镇;有的乡镇下面还挂着社区。设计时不要假设五级固定不变,level 字段必须保留,查询树时用 parent_code 递归比用固定的前缀截取更稳。
2.3 前导零与字符串类型:建表时最容易埋的坑
区划代码和邮编都是数字,但它们是「编号」不是「数值」。很多同学习惯把字段设成 INTEGER 或者 BIGINT,导入数据后发现前导零全部消失,比如某区划代码以 01 开头,入库后变成 1,父级前缀立刻对不上,这是百分之九十的踩坑现场。
CREATE TABLE divisions ( code TEXT PRIMARY KEY, name TEXT NOT NULL, parent_code TEXT, level INTEGER NOT NULL, center_lng REAL, center_lat REAL ); CREATE TABLE division_postcodes ( code TEXT NOT NULL, postcode TEXT NOT NULL, PRIMARY KEY (code, postcode) ); CREATE INDEX idx_divisions_parent ON divisions(parent_code); CREATE INDEX idx_postcodes_code ON division_postcodes(code);所有代码类字段全部用 TEXT,不要用 INTEGER;level 是语义上的层级,才用 INTEGER。加 parent_code 索引是因为后面大量按父级查询,几万行数据也许不明显,但 JOIN 时索引能避免全表扫描。postcode 字段同理,之后按邮编反查区划时用得上。
2.4 用前缀编码实现省、市、区县、乡镇街道的四级查询
区划代码的层级关系藏在编码前缀里。某个市级 code 的前两位是省级,前四位是该市级,前六位是其下区县。查询子级最直接的方式是对 code 做字符串截取。
-- 查询某省级下所有地市 SELECT * FROM divisions WHERE substr(code, 1, 2) = substr(?, 1, 2) AND level = 2; -- 查询某地市下所有区县 SELECT * FROM divisions WHERE substr(code, 1, 4) = substr(?, 1, 4) AND level = 3; -- 查询某区县下所有乡镇街道 SELECT * FROM divisions WHERE substr(code, 1, 6) = substr(?, 1, 6) AND level = 4;?是应用层传进来的父级 code,substr(code, 1, 2)的写法依赖字符串类型,所以建表时 TEXT 是硬性要求。这套 SQL 在五万行数据上执行很快,但有一个边界:遇到「区直管街道」或「县级市直接下挂社区」的区划,level 会跳级,按前缀截取可能把不该出现的数据带出来。所以更稳妥的查询其实是先过滤 parent_code 字段,前缀只用来做快速过滤。这也是建表时保留 parent_code 的原因,而不是完全靠派生。
3. 多源数据合并与清洗:把零散的省市表变成统一的 SQLite 库
网上能找到的资料大致分三类:区划代码表、邮编查询文件、地图坐标导出。它们字段各异、粒度不同、坐标系甚至都不是同一套。第一步不是写代码,而是先把字段映射关系画清楚。
3.1 字段映射:三个常见数据源如何对齐
区划代码表通常只给 code、name、level,优点是行政层级完整;邮编查询文件给的是一长段自然地址,例如「某省某市某区某路 100 号」,需要先拆到区划代码;地图坐标导出则是地标名加经纬度,地标名和标准名称之间往往差着「某新城」「某南路」这类描述。三者必须有一个共同键才能合,这个键只能是 code,没有 code 的来源就得靠名称匹配。
| 来源 | 可用字段 | 主要缺点 |
|---|---|---|
| 区划代码表 | code、name、level | 缺邮编和坐标 |
| 邮编查询文件 | postcode、自然地址 | 地址描述不规范,需解析区划 |
| 地图坐标导出 | 地标名、经纬度 | 名称与标准名不一致、坐标系混杂 |
我的处理顺序是:先以区划代码表为骨架,再左挂邮编和坐标。注意一定用 left join,如果某个乡镇没有坐标或邮编也保留空行,后面清洗时才能看出缺失集中在哪一层,而不是整行消失。
3.2 用 pandas 合并第一版全量数据
import pandas as pd df_code = pd.read_csv('divisions_code.csv', dtype={'code': str}) df_post = pd.read_csv('postcodes.csv', dtype={'code': str, 'postcode': str}) df_center = pd.read_csv('centers.csv', dtype={'code': str}) # 左连接,以区划代码表为骨架 df = df_code.merge( df_center[['code', 'center_lng', 'center_lat']], on='code', how='left' ).merge( df_post[['code', 'postcode']].drop_duplicates(), on='code', how='left' )dtype={'code': str}是必写项,省略的话 pandas 会把 code 读成 int64,前导零直接消失,后面所有前缀查询都会错。df_post[...].drop_duplicates()去掉同一个 code 下重复的邮编记录,因为后续要聚合。
# 邮编一对多,先聚合成逗号串便于预览,正式入库再拆分 df_agg = df.groupby( ['code', 'name', 'parent_code', 'level'], dropna=False ).agg( postcodes=('postcode', lambda s: ','.join( x for x in s.dropna().astype(str).unique() )), center_lng=('center_lng', 'first'), center_lat=('center_lat', 'first') ).reset_index()dropna=False是为了让完全没有邮编的区划也保持分组存在;.unique()去掉重复邮编;first用于坐标,因为同一个 code 理论上只有一个中心点,如果出现多条不同坐标,说明来源表有重复,建议先查重再合并。
3.3 邮编标准化:补位、去重与一对多拆分
邮编的坑在第一遍合并后集中爆发:有的写成整数 23000,有的带横杠 23-456,有的混进中文。统一做法是只保留六位数字,缺失返回空。
import re def normalize_postcode(raw): if pd.isna(raw): return None m = re.search(r'\d{6}', str(raw).replace('-', '')) return m.group(0) if m else None # 拆逗号字符串,逐个清洗 rows = [] for _, r in df_agg.iterrows(): if pd.isna(r['postcodes']): continue for pc in str(r['postcodes']).split(','): pc = normalize_postcode(pc) if pc: rows.append({'code': r['code'], 'postcode': pc}) df_post_map = pd.DataFrame(rows).drop_duplicates()normalize_postcode用正则\d{6}从任意文本里抽出六位数字,比 strip 更稳;replace('-', '')是为了处理带横杠邮编。drop_duplicates()同时去掉重复,最后得到的df_post_map就是干净的一对多关联表,可以写入division_postcodes。
如果你的源数据是 Excel,导出 CSV 时邮编很容易被自动转成科学计数法,读进来变成2.3E+05这类字符串。正则前先把类似2.3E+05转成正常数字再补零,或者干脆要求源头给文本格式,能省很多事。
3.4 写入 SQLite 并检查完整性
import sqlite3 conn = sqlite3.connect('divisions.db') df_agg[['code', 'name', 'parent_code', 'level', 'center_lng', 'center_lat']].to_sql( 'divisions', conn, if_exists='replace', index=False) df_post_map.to_sql('division_postcodes', conn, if_exists='replace', index=False) conn.commit()if_exists='replace'适合本地重建整表;如果线上继续用,建议改成先删除旧数据再插入,避免 drop 表带来的权限问题。写完后跑一轮校验。
-- 1. 父代码悬空 SELECT code, name, parent_code FROM divisions d WHERE parent_code IS NOT NULL AND parent_code NOT IN (SELECT code FROM divisions) LIMIT 10; -- 2. 缺少坐标的层级分布 SELECT level, COUNT(*) AS total, SUM(CASE WHEN center_lng IS NULL AND center_lat IS NULL THEN 1 ELSE 0 END) AS missing_xy FROM divisions GROUP BY level; -- 3. 每个层级覆盖邮编的比例 SELECT d.level, COUNT(DISTINCT d.code) AS total, COUNT(DISTINCT dp.code) AS has_postcode FROM divisions d LEFT JOIN division_postcodes dp ON d.code = dp.code GROUP BY d.level;第一条查出的「父代码悬空」基本可以断定来自旧版本区划代码,例如某区县撤并后其下辖乡镇仍指向旧 code。第二条让你看到缺失坐标集中在哪个层级。第三条如果乡镇街道层级的has_postcode明显低于区县级,说明邮编来源在该层覆盖不全,需要回到 3.3 补数据,而不是闷头建服务。
4. 坐标系统选型与查询服务:让经纬度真正用得上
数据合并好了,不代表坐标能用。很多一站式数据包默认给的是 GCJ-02,而你的 GPS 采集设备输出的是 WGS84,混着用直接偏出街区。先定坐标系,再谈查询。
4.1 WGS84、GCJ-02、BD09 选哪个:先想清楚终点数据要交给谁
WGS84 是卫星导航系统输出的原始坐标;GCJ-02 是官方坐标加密体系下的产物,国内主流地图产品都基于它;BD09 是某地图厂商在 GCJ-02 基础上二次加密得到的。三者之间没有简洁的线性转换关系,WGS84 到 GCJ-02 可以靠公开公式正向计算,反向则需要迭代逼近。
| 坐标系 | 来源 | 相对 WGS84 偏移 | 适用场景 |
|---|---|---|---|
| WGS84 | GPS 终端、国际数据 | 基准 | 物流轨迹、海外应用 |
| GCJ-02 | 国内地图产品 | 几十到几百米 | 地图展示、国内 POI |
| BD09 | 某地图厂商 | 再偏移一小段 | 仅在该厂商生态内使用 |
选型原则很简单:数据最终要投到国内地图上,就直接存 GCJ-02,避免每次请求都做在线转换;数据要给物流车机或海外分析用,存 WGS84。为了两边不打架,我会在表里同时存两套坐标,字段名写清楚坐标系,例如center_lng_wgs84和center_lng_gcj02。空间是小事,排查问题的时间才值钱。
4.2 坐标纠偏:从 GCJ-02 反算 WGS84 的迭代方法
如果你拿到的只有 GCJ-02,又必须输出 WGS84,常见做法是正向转换公式加迭代逼近。下面这组函数可以直接抄,核心思路是先假定 WGS84 坐标等于 GCJ-02 坐标,然后做正向转换,再用误差反向纠正,循环几次后收敛。
import math pi = 3.1415926535897932384626 a = 6378245.0 ee = 0.00669342162296594323 def out_of_range(lng, lat): return not (lng > 73 and lng < 135 and lat > 3 and lat < 54) def transform_lat(lng, lat): ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat ret += 0.1 * lng * lat + 0.2 * math.sqrt(abs(lng)) ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 * math.sin(2.0 * lng * pi)) * 2.0 / 3.0 ret += (20.0 * math.sin(lat * pi) + 40.0 * math.sin(lat / 3.0 * pi)) * 2.0 / 3.0 ret += (160.0 * math.sin(lat / 12.0 * pi) + 320.0 * math.sin(lat * pi / 30.0)) * 2.0 / 3.0 return ret def transform_lng(lng, lat): ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng ret += 0.1 * lng * lat + 0.1 * math.sqrt(abs(lng)) ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 * math.sin(2.0 * lng * pi)) * 2.0 / 3.0 ret += (20.0 * math.sin(lng * pi) + 40.0 * math.sin(lng / 3.0 * pi)) * 2.0 / 3.0 ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 * math.sin(lng / 30.0 * pi)) * 2.0 / 3.0 return ret def wgs84_to_gcj02(lng, lat): if out_of_range(lng, lat): return lng, lat dlat = transform_lat(lng - 105.0, lat - 35.0) dlng = transform_lng(lng - 105.0, lat - 35.0) radlat = lat / 180.0 * pi magic = math.sin(radlat) magic = 1 - ee * magic * magic sqrtmagic = math.sqrt(magic) dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi) dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi) return lng + dlng, lat + dlat def gcj02_to_wgs84(lng, lat, max_iter=8): if out_of_range(lng, lat): return lng, lat wgs_lng, wgs_lat = lng, lat for _ in range(max_iter): g_lng, g_lat = wgs84_to_gcj02(wgs_lng, wgs_lat) wgs_lng -= (g_lng - lng) wgs_lat -= (g_lat - lat) return wgs_lng, wgs_latout_of_range先做范围判断,避免对范围外坐标做无意义偏移。transform_lat和transform_lng是公开的近似公式,不同版本里的系数有细微出入,但结果差异在像素级。gcj02_to_wgs84的max_iter参数控制迭代次数,8 次通常把误差压到 1 米以内;如果结果仍然漂移几米,可以调到 12,但单点计算耗时也就几微秒,对离线处理无所谓。
注意:不要自己「推导」反算公式。GCJ-02 的偏移是非线性的,唯一靠谱的路径就是正向公式加迭代,或者直接调用成熟工具库。
4.3 封装一个按关键字查找区划的查询类
数据进了 SQLite,下一步是封装查询能力。最常见需求有三个:按标准名称精确查找区划;在指定父级下模糊搜索关键字;查询某个区划的所有下级。直接写在业务代码里会很散,我一般会用轻查询类把它收敛起来。
import sqlite3 class DivisionSearch: def __init__(self, db_path): self.conn = sqlite3.connect(db_path) self.rows = self._load() def _load(self): cur = self.conn.cursor() cur.execute(""" SELECT code, name, parent_code, level, center_lng, center_lat FROM divisions """) return cur.fetchall() def find(self, keyword, parent_code=None): exact = [] fuzzy = [] for code, name, parent, level, lng, lat in self.rows: if parent_code and parent != parent_code: continue if name == keyword: exact.append((code, name, parent, level, lng, lat)) elif keyword in name: fuzzy.append((code, name, parent, level, lng, lat)) return exact + fuzzy def children(self, code): return [r for r in self.rows if r[2] == code]find返回「精确命中 + 模糊命中」两个数组,顺序是精确优先,避免输入「某新区」时混淆掉「某新区街道」。children直接用 parent_code 过滤,不依赖前缀长度,这样在遇到「县下直挂街道」这种不规则层级时也不会漏数据。
参数上,find的parent_code必须传,否则「示例路」这种名字会在不同城市全部命中,业务上没法用。父级限定之后,同名街道才算真正被隔离。查询类的内存占用约等于五万行记录乘几个字节,常驻内存完全没问题,没有必要为每个请求开 SQLite 连接。如果数据量到了百万级,再考虑上分布式检索,当前这个阶段用 Python 列表足够。
5. 避坑记录:乡镇街道邮编与坐标的 5 个翻车点和排查方法
前面的流程能挡掉常规脏数据,但总有些坑藏在数据本身。这一章不打补丁,直接看现象、原因、解决,每一条都是真实翻车换来的。
5.1 五个高发异常:现象、原因、解决
异常一:乡镇级坐标全部落在市中心
现象:某省多个乡镇的经纬度完全一样,而且都指向地级市中心的坐标。原因:坐标来源在乡镇一级大量缺数,用市级中心点「填充」了这些空位。解决:删除完全相同的坐标对,或者引入乡镇政府驻地地标做交叉校验;如果只是为了展示热力,宁可让该点为空,也不要给用户一个假位置。
异常二:邮编前导零丢失
现象:邮编「023456」入库后变成「23456」。原因:CSV 或 Excel 导入时,邮编列被自动转成数值类型。解决:读文件时强制dtype={'postcode': str},清洗时统一用str.zfill(6)补位,再写一个正则校验六位数字。
异常三:一个街道多个邮编导致分拣出错
现象:业务系统按街道取邮编只取到第一个,快递分拣发错分拨中心。原因:把 postcode 设计成单值字段,忽略了一对多关系。解决:用division_postcodes关联表保存全部邮编,查询时返回集合,由业务规则决定优先级,而不是简单取第一条。
异常四:县下直接挂街道,中间少了一层
现象:用编码前缀查乡镇街道,某县下面直接出现一堆街道,中间没有乡镇。原因:当地行政区划近年做过「镇改街道」,街道和乡镇在编码上是平级。解决:查询树别依赖层级深度,直接用 parent_code 递归,业务展示时按实际 level 过滤。
异常五:坐标整体偏移几十米
现象:叠加到地图上看,所有点都偏出真实位置,方向一致。原因:GCJ-02 数据被当成 WGS84 使用,或者反过来。解决:入库前统一转成目标坐标系,字段名标注坐标系;不要相信来源文件标题里写的坐标系,抽样对地标验证后再全量处理。
5.2 一条无损校验脚本:把隐患挡在写入之前
上面五类异常里,前两类和第五类可以在写入前批量查出来。我习惯在每次合并后跑一轮校验脚本,不通过就不进库。假设你手里是合并后的宽表文件,包含 code、name、parent_code、level、center_lng、center_lat、postcode 列,其中 postcode 可能是多值逗号分隔。
import pandas as pd import re df = pd.read_csv('merged.csv', dtype={'code': str, 'parent_code': str, 'postcode': str}) # 1. 坐标越界 bad_range = df[(df.center_lng < 73) | (df.center_lng > 135) | (df.center_lat < 3) | (df.center_lat > 54)] print('越界坐标数量:', len(bad_range)) # 2. 父代码悬空 codes = set(df['code']) bad_parent = df[df['parent_code'].notna() & ~df['parent_code'].isin(codes)] print('父代码缺失数量:', len(bad_parent)) # 3. 同父级下同名街道 dup_name = df[df.duplicated(subset=['name', 'parent_code'], keep=False)] print('同名冲突数量:', len(dup_name)) # 4. 邮编非法(宽表按逗号拆分后逐段检查) bad_post = [] for raw in df['postcode'].dropna(): for pc in str(raw).split(','): if not re.fullmatch(r'\d{6}', pc.strip()): bad_post.append((raw, pc)) print('非法邮编数量:', len(bad_post))第一条的经纬度范围按全国跨度粗筛,能挡掉坐标是 0 或经纬度反了的记录;第二条查父代码悬空,对应异常四的编码链断裂;第三条查同父级下同名冲突,这种记录多半是历史撤并残留;第四条用正则硬校验邮编格式。四条全过的数据才允许进入下一阶段。
duplicated(subset=['name', 'parent_code'])用的是「同一父级下的同名」逻辑,不同父级下同名反而合法。keep=False会让重复行全部标出来,方便人工逐条确认。如果坐标列存在 NaN,前面的比较会返回 False,不会误报,但你也得知道 NaN 本身就是「缺失」,需要单独统计。
6. 验证与增量更新:把这份基础表当长期资产维护
数据入库后别急着上线。最靠谱的验证方式是抽几十个点做「真值对照」:选不同层级、不同方位的记录,人工在地图上确认其政府驻地或核心地标的实际位置,再和库里坐标做距离比较。
import math def haversine(lng1, lat1, lng2, lat2): R = 6371.0088 p1, p2 = math.radians(lat1), math.radians(lat2) dp = math.radians(lat2 - lat1) dl = math.radians(lng2 - lng1) a = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2 a = max(0, min(1, a)) return 2 * R * math.asin(math.sqrt(a)) # 真值文件:code, true_lng, true_lat truth = pd.read_csv('truth_points.csv', dtype={'code': str}) sample = truth.merge(df_agg[['code', 'center_lng', 'center_lat']], on='code') sample['distance_m'] = sample.apply( lambda r: haversine(r.center_lng, r.center_lat, r.true_lng, r.true_lat) * 1000, axis=1 ) print(sample[['code', 'distance_m']].describe())这里用球面距离公式,对几十到几百米的尺度完全够用。看distance_m的中位数和 p90:乡镇级中位数在 30 到 80 米是正常水平;上百上千则说明坐标来源是市级中心点填充,回到上一章异常一处理。
行政区划代码每年都在变。我的习惯是每年拿到新版本后跑一遍 diff,但旧代码不删除,只标记失效。
-- 新增区划 SELECT code, name FROM divisions_new WHERE code NOT IN (SELECT code FROM divisions_old); -- 失效区划 SELECT code, name FROM divisions_old WHERE code NOT IN (SELECT code FROM divisions_new);新增区划大多由「镇改街道」「撤并乡镇」产生,需要补邮编和坐标;失效区划不能物理删除,否则历史订单、历史轨迹无法关联到原有节点,正确做法是加valid_flag或valid_until字段。把变更清单同步给业务方,比闷头升级表更稳。
最后说一个我自己的教训:某次做网点地图,直接把下载好的坐标当成 WGS84 用,上线后几百个点集体偏出街区。排查了一个下午才发现是 GCJ-02。从那以后,我所有离线表里都同时存两套坐标,字段名写明坐标系,并且每年新版区划代码发布后立刻跑一次 diff。这套工序看起来多,但积累下来,这份「全国省、市、区、县、乡、镇、街道邮编及经纬度坐标」才真正变成可以长期依赖的基础资产。希望帮到你。
本文还有配套的精品资源,点击获取