简介:这份2020年中国省、市、县三级行政区划矢量图,面向地理信息、城乡规划、数据统计及科研教学等领域的从业者与学习者,用于解决全国行政边界精确获取与空间分析的问题。压缩包共25个文件,约71.64MB,以shp、shx、dbf、prj、sbn、sbx、cpg、xml等Shapefile标准格式为主,分别承载几何边界、属性数据、空间索引、坐标参考与元数据信息,可直接被ArcGIS、QGIS等软件读取。目前已有3453人学习下载,说明其在同类数据中具备较高认可度。借助这套数据,读者可完成区域划分、缓冲区分析、人口密度计算等空间操作,也能为公共服务设施布局、地区发展差异研究、市场分析与选址决策提供底图支撑,是理解中国地理与社会结构的一份实用基础资料。
1. 2020中国省、市、县三级行政区划矢量图:从一份zip到一套能用的底图
做数据可视化或空间分析的人,迟早会撞上一个需求:把业务数据按行政区划落到地图上。销售按省汇总、门店按市分布、网点按县铺开,这时候你需要的不是一张好看的图片,而是一份能参与计算的矢量边界。2020中国省、市、县三级行政区划矢量图.zip 这类资源,解决的正是这个问题——它把全国三级行政单元的边界打包成矢量文件,让你能在 QGIS、ArcGIS、GeoPandas 里直接加载、裁剪、关联属性。适合谁?做区域报表的 BI 工程师、跑选址模型的数据分析师、写论文需要出图的研究生,以及任何被“给我一张能点选的地图”需求追着跑的人。但拿到 zip 只是开始,真正决定它能不能用的,是坐标系、字段结构、层级编码和拓扑质量这四件事。
2. 拆开这份矢量包:三级边界到底存了什么
2.1 省、市、县三级在矢量文件里怎么组织
一份标准的行政区划矢量包,通常有两种组织方式。第一种是三个独立图层:省界一个文件、市界一个文件、县界一个文件,各自带自己的属性表。第二种是一个图层里用字段区分层级,比如加一列 level,值为 1、2、3 分别代表省、市、县。两种方式没有绝对优劣,但用途不同。做分级设色时,独立图层更省事,直接按图层加载就行;做下钻联动时,单图层加层级字段更方便,前端一次加载、按字段过滤。
我一般会先看文件后缀。Shapefile 是最常见的,一套至少四个文件(.shp、.shx、.dbf、.prj),少一个都读不全。GeoJSON 是单文件,文本格式,方便版本管理和网页直接加载,但全国县级边界动辄几十兆,浏览器里要小心。GeoPackage 是单文件二进制,QGIS 和 GeoPandas 都支持,字段名长度不受限,比 Shapefile 舒服。拿到 zip 先解压,用文件管理器看一眼结构,比直接写代码猜要快。
属性表里最关键的字段是行政区划代码。省代码两位、市代码四位、县代码六位,这是国家标准编码规则。如果这份数据里只有名称没有代码,后面做数据关联会非常痛苦,因为“市辖区”“城区”这类重名在全国能找出几十个。所以第一步永远是确认代码字段在不在、是不是字符串类型。数值类型会把“010”变成“10”,前导零一丢,关联全错。
2.2 坐标系与投影:为什么你的图总是对不上
矢量边界能不能和你的业务数据叠上,九成问题出在坐标系。常见情况是:边界文件用的是地理坐标系(经纬度,单位是度),业务数据是投影坐标系(单位是米),两者直接叠,位置偏到姥姥家。国内常见的坐标系有 WGS84(EPSG:4326)、CGCS2000(EPSG:4490)以及各种地方坐标系。2020 年的数据,大概率是 CGCS2000 或 WGS84,两者在普通可视化精度下差异很小,但如果你要做面积计算,必须投影到等面积坐标系,比如 Albers(EPSG:9822 或自定义参数)。
判断当前坐标系,最直接的办法是看 .prj 文件内容,或者用 GeoPandas 读进来打印 crs 属性。如果 crs 是 None,说明文件没带投影信息,你得自己判断。一个土办法:看坐标数值范围。经度在 73 到 135 之间、纬度在 3 到 54 之间,那就是经纬度;如果数值是几十万、几百万,那就是投影坐标。判断错了,后面所有空间操作都是白费。
提示:不要假设 zip 里的 .prj 一定正确。我见过把 WGS84 的边界标成 CGCS2000 的包,差异虽小,但在做高精度叠加时会露馅。
2.3 用 GeoPandas 读进来先做三件事
拿到数据别急着画图,先做体检。下面这段代码是我每次拿到新矢量包都会跑的,检查坐标系、字段和几何有效性。
import geopandas as gpd # 读取县界图层,具体文件名按实际解压结果改 gdf = gpd.read_file("county_2020.shp", encoding="utf-8") # 第一件事:看坐标系 print("CRS:", gdf.crs) # 第二件事:看字段和类型 print(gdf.dtypes) print(gdf.head(3)) # 第三件事:检查几何有效性 invalid = gdf[~gdf.geometry.is_valid] print("无效几何数量:", len(invalid)) if len(invalid) > 0: print(invalid[["name", "code"]].head())这段代码的逻辑很直白:crs 告诉你坐标系,dtypes 告诉你代码字段是不是字符串,is_valid 告诉你有没有自相交、悬挂节点这类拓扑错误。无效几何不一定会让程序报错,但会在裁剪、求交时产生诡异结果,比如面积算出来是负的、叠加后出现碎片。发现无效几何,可以用 buffer(0) 做一次修复,但修复后要重新检查面积变化。
参数上唯一要注意的是 encoding。Shapefile 的 .dbf 文件编码可能是 GBK、UTF-8 或 Latin1,读出来乱码就换一个试。GeoPandas 默认用 UTF-8,遇到中文乱码时显式指定 encoding="gbk" 往往能解决。
3. 把边界用起来:关联业务数据的完整链路
3.1 行政区划代码的清洗与对齐
矢量边界只有地理信息,业务价值来自关联。关联的钥匙就是行政区划代码。但现实里,业务系统里的代码往往不干净:有的带省市区后缀,有的位数不对,有的用名称代替代码。清洗的目标是让两边代码字段类型一致、位数一致、格式一致。
常见做法是统一转成六位字符串,省级补零到六位(比如“110000”),市级补零到六位(“110100”),县级本身就是六位。如果业务数据只有名称,那就得先做名称到代码的映射,这一步没有捷径,只能靠一份标准的区划代码表做左连接。注意名称里的“市辖区”“省直辖县级行政区划”这类特殊值,直接匹配往往失败,需要单独处理。
import pandas as pd # 假设业务数据里有一列 region_code biz = pd.read_csv("sales.csv", dtype={"region_code": str}) # 统一补零到六位 biz["region_code"] = biz["region_code"].str.zfill(6) # 矢量数据里的代码也转字符串并补零 gdf["code"] = gdf["code"].astype(str).str.zfill(6) # 左连接,把业务数据挂到边界上 merged = gdf.merge(biz, left_on="code", right_on="region_code", how="left") # 检查匹配率 match_rate = merged["region_code"].notna().mean() print(f"匹配率: {match_rate:.2%}")逻辑说明:zfill(6) 保证两边位数一致,astype(str) 避免数值类型丢前导零。merge 用左连接,保证边界完整,业务数据缺失的单元显示为空。匹配率低于 90% 就说明代码体系有问题,得回去查是年份版本不一致还是编码规则不同。参数上,how="left" 是默认推荐,除非你只想保留有业务的区域。
3.2 按省、市、县聚合的三种写法
关联完成后,聚合方式取决于你的分析层级。按省聚合,用省代码前两位分组;按市聚合,用前四位;按县聚合,用六位。如果矢量包是单图层带层级字段,直接按 level 过滤再分组。如果是三个独立图层,就分别加载、分别聚合。
# 按省聚合:取代码前两位 gdf["prov_code"] = gdf["code"].str[:2] prov_summary = merged.groupby("prov_code").agg( total_sales=("sales", "sum"), store_count=("store_id", "nunique") ).reset_index() # 按市聚合:取代码前四位 gdf["city_code"] = gdf["code"].str[:4] city_summary = merged.groupby("city_code").agg( total_sales=("sales", "sum") ).reset_index()这里的关键参数是 str[:2] 和 str[:4],前提是代码已经补零到六位。如果代码是数值类型,切片会报错,所以前面转字符串这一步不能省。聚合后的结果要再关联回对应的边界图层才能出图,省界关联 prov_code,市界关联 city_code。
3.3 出图前的裁剪与简化
全国县级边界数据量大,直接出图又慢又卡。两个优化手段:裁剪和简化。裁剪是只保留你关心的区域,比如只做华东地区,就用一个矩形或省界做 clip。简化是减少节点数量,用 simplify 方法,容差根据出图比例尺定。容差太大边界会变形,太小起不到加速作用。
# 裁剪到华东区域(假设有一个华东的边界) east_china = gdf[gdf["prov_code"].isin(["31", "32", "33", "34", "35", "36", "37"])] # 简化几何,容差 0.01 度约等于 1 公里 east_china["geometry"] = east_china["geometry"].simplify(0.01, preserve_topology=True) # 出图 east_china.plot(column="total_sales", legend=True, figsize=(12, 8))simplify 的容差单位跟坐标系一致,经纬度下 0.01 度大约是 1 公里。preserve_topology=True 保证简化后不会出现自相交。如果只是网页展示,简化到 0.05 度也够用;如果要打印出版,0.001 度更稳妥。裁剪和简化的顺序建议先裁剪后简化,减少计算量。
4. 避坑与排查:矢量边界翻车的五个现场
4.1 现象:地图整体偏移几百米
原因:坐标系判断错误,或者 .prj 文件缺失导致软件用了默认坐标系。最常见的是把 CGCS2000 当成 WGS84,虽然差异小,但在高精度底图上会看出来。更严重的是把投影坐标当经纬度,偏移能到几十公里。
解决:先用 GeoPandas 打印 crs,确认 EPSG 代码。如果是 None,根据坐标数值范围判断。确认后统一用 to_crs 转换到目标坐标系。转换前先查清楚源坐标系到底是什么,猜错了转完更偏。
4.2 现象:关联后大量区域业务数据为空
原因:代码字段类型不一致,数值型丢了前导零;或者年份版本不一致,2020 年的边界配了 2019 年的业务代码,部分区划调整导致代码对不上。
解决:两边都转字符串并 zfill(6),打印未匹配的代码列表,人工核对。如果是版本问题,找一份对应年份的区划代码表做映射,或者用名称做辅助匹配。
4.3 现象:面积计算结果为负或异常大
原因:几何无效,存在自相交或环方向错误;或者用了地理坐标系直接算面积,单位是平方度,没有物理意义。
解决:先检查 is_valid,无效的用 buffer(0) 修复。算面积前必须投影到等面积坐标系,比如 Albers。投影参数根据区域定,全国用中央经线 105 度,分带用 3 度或 6 度。
4.4 现象:简化后边界出现尖刺或空洞
原因:simplify 容差过大,或者 preserve_topology 没开。拓扑保持关闭时,相邻多边形简化后可能出现缝隙或重叠。
解决:减小容差,开启 preserve_topology=True。如果还不行,先做一次 buffer(0) 清理,再简化。相邻边界的简化最好用拓扑感知的工具,比如 mapshaper,它能保证共享边界简化后仍然重合。
4.5 现象:Shapefile 字段名被截断
原因:Shapefile 的 .dbf 字段名限制 10 个字符,中文或长英文名会被截断,导致代码里引用的字段名找不到。
解决:读进来先打印 columns 看实际字段名。如果被截断,要么在读取时重命名,要么换 GeoPackage 格式。我一般会在读取后立刻 rename,把字段名统一成短英文,后面代码更稳。
5. 进阶技巧:让这份底图真正变成你的资产
5.1 用拓扑检查保证相邻边界严丝合缝
省、市、县三级边界理论上应该严格嵌套:县界拼成市界,市界拼成省界。但实际数据里,不同层级可能是分别数字化再合并的,边界对不齐。做下钻联动时,这种不对齐会导致视觉上的缝隙或重叠。检查方法是把县界 dissolve 到市级,再和原始市界做对称差,看差异面积占比。
# 县界融合到市级 county_dissolved = gdf.dissolve(by="city_code") # 加载原始市界 city_original = gpd.read_file("city_2020.shp") # 计算对称差面积 sym_diff = county_dissolved.geometry.symmetric_difference(city_original.geometry) print("对称差总面积:", sym_diff.area.sum())如果对称差面积接近零,说明嵌套良好;如果明显大于零,说明层级间边界不一致。修复办法是以县级为准,重新融合出市级,替换原始市界。这样保证下钻时边界完全重合。
5.2 把常用操作封装成一个函数
每次拿到新数据都重复体检、清洗、关联,不如封装成一个函数。下面这个函数覆盖了从读取到关联的核心步骤,参数化设计让不同项目都能复用。
def load_and_join(shp_path, biz_df, code_col="code", biz_code_col="region_code"): """ 读取矢量边界并关联业务数据 shp_path: 矢量文件路径 biz_df: 业务数据 DataFrame code_col: 矢量数据里的代码字段名 biz_code_col: 业务数据里的代码字段名 """ gdf = gpd.read_file(shp_path, encoding="utf-8") # 统一代码格式 gdf[code_col] = gdf[code_col].astype(str).str.zfill(6) biz_df[biz_code_col] = biz_df[biz_code_col].astype(str).str.zfill(6) # 检查几何有效性 if not gdf.geometry.is_valid.all(): gdf["geometry"] = gdf["geometry"].buffer(0) # 关联 merged = gdf.merge(biz_df, left_on=code_col, right_on=biz_code_col, how="left") # 打印匹配率 print(f"匹配率: {merged[biz_code_col].notna().mean():.2%}") return merged这个函数的参数设计考虑了几种常见变体:代码字段名可能不同,业务数据可能来自 CSV 或数据库。返回的 merged 可以直接用于出图或进一步聚合。我一般还会加一个参数控制是否简化,但简化容差因项目而异,就没放进去。
5.3 一个我踩过的坑:别在原始数据上直接改
早期我习惯读进来直接改 geometry 列,做简化、裁剪、修复,改完保存。后来发现原始数据被覆盖了,下次想换个容差重新简化,已经找不到原始边界。血泪经验是:读进来先 copy 一份,所有修改在副本上做,原始文件保持只读。GeoPandas 的 copy() 是深拷贝,几何对象也会复制,不会互相影响。
gdf_raw = gpd.read_file("county_2020.shp") gdf_work = gdf_raw.copy() # 所有操作在 gdf_work 上做这个习惯看起来简单,但能省下大量重新下载和解压的时间。尤其是这份 2020 中国省、市、县三级行政区划矢量图,解压后文件不小,重新获取的成本比 copy 一下高得多。
最后说一个验证方法:拿几个已知面积的县,比如你熟悉的家乡,用投影后的面积和官方公布面积对比,误差在 1% 以内就说明坐标系和几何都没问题。这个土办法比看任何文档都直接。希望帮到你。
本文还有配套的精品资源,点击获取