
简介中国行政区划shp文件面向GIS从业者、规划研究人员、高校师生及地理数据开发者提供可直接用于地图制作、空间统计与区域分析的基础地理数据。压缩包共79个文件约33.05MB以26组shp、shx、dbf配套文件为主分别承载几何边界、空间索引以及行政区名称、代码等属性信息另含1个xml元数据便于在ArcGIS、QGIS及geopandas等环境中读取、编辑和二次开发。已有3802人学习下载适合用作教学演示、专题制图与业务原型的数据底座。数据涉及行政边界、水系、道路、居民地等要素类别可用于人口密度分析、经济区域划分、交通网络评估、城市规划与灾害影响范围判定也能与自有业务数据叠加完成空间查询和可视化表达。借助属性表与索引结构读者能较快完成图层组织、字段核对和基础空间分析减少前期找数据与格式转换的成本。1. 从一份行政区划 shp 文件说起它能解决哪些落地问题拿到一个压缩包解压后看到.shp、.dbf、.shx、.prj几个同名文件很多人第一反应是双击打开结果要么打不开要么只看到一张没有属性的空图。这其实是一份覆盖全国省、市、县三级的行政区划 shp 文件面要素表达行政边界属性表里带名称、代码、层级这类字段。它最适合三类人要做区域统计报表的、要把业务点落到行政区的、要出专题地图的。别把它当 Excel 用shp 文件的字段修改、坐标系转换、几何修复都有固定套路下面按真实落地流程拆开讲。2. 拆开 shp 文件伴生文件、字段口径与坐标系统2.1 一个 shp 文件不是单个文件伴生文件清单行政区划 shp 文件最容易被误解的地方是把它当成一个独立文件。实际上.shp只存几何属性、索引、坐标系、编码信息分散在同名伴生文件里。少一个.dbf属性表就是空的少一个.prj坐标系统就变成未知少一个.cpg中文字段可能直接乱码。我一般拿到压缩包后先不解压到中文路径而是先列一遍文件清单确认同名文件数量对得上再动手读。扩展名作用缺失后的典型表现.shp存储几何坐标文件无法打开.shx几何索引部分工具读取缓慢或报错.dbf存储属性表有图无属性字段全空.prj存储坐标系统坐标被当成未知面积和落区全错.cpg声明 DBF 编码中文名称乱码.qix空间索引大文件查询变慢不影响正确性.sbn/.sbx另一种空间索引同上属于性能文件先跑一条命令行体检比直接拖进软件更可靠。ogrinfo是 GDAL 自带的工具-so表示只输出摘要-al表示列出所有图层。下面这条命令会告诉你图层名、几何类型、要素数量、字段定义和坐标系统基本能判断这份 shp 文件是否完整。# 查看 shp 文件的摘要信息不输出每个要素 ogrinfo -so -al data/county.shp参数说明-so很关键行政区划数据动辄几千个面要素不加这个参数会刷屏-al会遍历文件内所有图层虽然 shp 文件通常只有一个图层但保留这个习惯能避免漏看。输出里重点看三处Geometry是不是Polygon或MultiPolygonFeature Count是否和预期层级数量接近Layer SRS WKT是否不是空值。如果SRS WKT为空先别做任何面积计算优先找回.prj或手动指定坐标系统。提示复制 shp 文件时必须整套复制尤其是.dbf和.prj。只拷.shp是血泪经验里出现频率最高的一类翻车。2.2 行政区划图层的三层粒度与字段设计行政区划 shp 文件通常按省、市、县三层组织有的资源包会拆成三个独立 shp 文件有的会合并成一个图层再用level字段区分。拆成三个文件的好处是字段口径干净做空间连接时不容易把市级面和县级面混在一起合并成一个图层的好处是图层数量少但每次筛选都要带上层级条件。我一般优先选拆开的版本因为后续做统计时县级面用于落点省级面用于出图两者用途不同混在一起反而容易误用。字段方面常见的设计是name、adcode、level、parent加上几何字段。adcode是行政区划代码层级不同长度可能不同不要默认它一定是六位level用来标记省级、市级、县级parent指向上一级代码用来做树形汇总。这里有个坑DBF 字段名最多只支持 10 个字符province_name这种字段名会被截断成province_n如果同时存在province_name和province_note截断后可能直接重名。读入后先检查字段名长度和重复情况比后面发现数据串列再回头修要省事。import geopandas as gpd gdf gpd.read_file(data/county.shp, encodingutf-8) # 检查字段名长度DBF 字段名超过 10 字符容易被截断 for col in gdf.columns: if len(col) 10: print(字段名偏长:, col, len(col)) # 检查字段名是否重复重复会导致后续按列名取值取错 print(字段名重复:, gdf.columns[gdf.columns.duplicated()].tolist()) # 查看字段类型和前几行确认 adcode 是否被读成数值 print(gdf.dtypes) print(gdf[[name, adcode, level]].head())这段代码的逻辑是先做字段层面的体检再碰几何。encodingutf-8只是常见起点如果中文名称读出来是乱码换成gbk再试一次不要在同一次读取里反复转码。gdf.dtypes能看出adcode是否被识别成整数如果代码里有前导零整数化会丢掉零后续按代码关联时会对不上所以代码字段我一般强制按字符串处理。2.3 坐标系统地理坐标与投影坐标先分清坐标系统是行政区划 shp 文件里最容易被忽略、后果又最直接的一项。地理坐标的单位是度投影坐标的单位是米。把地理坐标直接拿去除以面积得到的是“平方度”没有业务含义把投影坐标当成经纬度去和点位匹配点位会飞到地图外面。常见做法是展示和 Web 地图相关用EPSG:4326或EPSG:3857做面积统计和缓冲区分析用等积投影具体参数按数据范围选中央经线不要一套参数套全国。用途推荐坐标类型注意点经纬度落点、接口传参地理坐标EPSG:4326单位是度不能直接算面积Web 地图底图叠加Web MercatorEPSG:3857高纬度面积变形大只适合展示面积统计、缓冲区等积投影按范围自定义中央经线要覆盖数据范围制图简化投影坐标simplify的容差单位跟随 CRSimport geopandas as gpd gdf gpd.read_file(data/county.shp) # 先看当前 CRS如果是 None说明 .prj 缺失或未被识别 print(当前 CRS:, gdf.crs) # 判断是否为地理坐标地理坐标不能直接算面积 if gdf.crs and gdf.crs.is_geographic: print(当前是地理坐标面积计算前必须投影) # 自定义等积投影示例参数按数据范围调整 custom_crs projaea lat_125 lat_247 lat_00 lon_0105 x_00 y_00 datumWGS84 unitsm no_defs gdf_proj gdf.to_crs(custom_crs) # 投影后面积单位为平方米除以 1e6 得到平方公里 gdf_proj[area_km2] gdf_proj.geometry.area / 1e6 print(gdf_proj[[name, area_km2]].head())这里to_crs的参数可以是 EPSG 编号也可以是 PROJ 字符串。示例里的lat_1、lat_2、lon_0是双标准纬线和中央经线实际使用时按数据覆盖范围调整不要照抄后直接用于跨区域统计。geometry.area的单位跟随当前 CRS投影坐标下才是平方米。算完面积先看数量级县级面一般在几十到几千平方公里省级面在几万到几十万平方公里如果结果差几个数量级优先回头查 CRS而不是怀疑数据本身。3. 用 Python 读入 shp 文件字段清洗、坐标转换与导出3.1 环境与依赖GeoPandas、Shapely、PyProj、FionaPython 侧处理 shp 文件常见组合是 GeoPandas 做表级操作Shapely 做几何对象PyProj 管坐标转换Fiona 负责文件读写。底层还是 GDAL所以安装时最容易出问题的是 GDAL 相关依赖。Windows 上直接用 pip 有时会遇到 wheel 不匹配我一般优先用 conda 环境装或者先确认本机已有可用的 GDAL再装 Python 包。不要一上来就锁定某个版本号先让依赖解析器自己选兼容组合跑不通再回退。# 基础依赖建议在独立虚拟环境里执行 pip install geopandas shapely pyproj fiona # 如果读取 shp 文件时报 GDAL 相关错误优先检查底层 GDAL 是否可用 python -c from osgeo import gdal; print(gdal.__version__)这段命令不涉及业务逻辑目的是把读文件的能力准备好。geopandas负责read_file和to_fileshapely负责几何有效性和简化pyproj负责 CRS 转换fiona是常见读写引擎。最后一行不是必跑项如果read_file正常说明底层没问题如果报错先解决 GDAL再谈字段清洗。3.2 读取与体检行列数、CRS、几何类型、字段类型读入之后不要急着筛选和出图先做一轮体检。行政区划 shp 文件常见的异常包括几何类型混用有的面是Polygon有的是MultiPolygonadcode被读成整数name字段有空格或全角字符几何无效导致后续空间连接报错。体检代码不长但能提前暴露大部分问题。import geopandas as gpd gdf gpd.read_file(data/county.shp, encodingutf-8) print(行数、列数:, gdf.shape) print(坐标系统:, gdf.crs) print(几何类型分布:) print(gdf.geom_type.value_counts(dropnaFalse)) print(字段类型:) print(gdf.dtypes) print(几何是否有效:) print(gdf.geometry.is_valid.value_counts(dropnaFalse)) print(空几何数量:, gdf.geometry.is_empty.sum())gdf.shape用来确认要素数量是否和层级预期一致gdf.crs用来确认坐标系统有没有丢geom_type.value_counts能看出是否混入点、线或空几何is_valid是空间操作前的必查项。is_empty统计空几何空几何在裁剪和空间连接时容易产生意外结果。这里不要用gdf.head()代替体检前几行正常不代表整个文件正常尤其行政区划数据里偶尔会混入岛屿、飞地或重复面。3.3 字段清洗重命名、类型转换、去重、空值字段清洗的目标不是把表改得好看而是让后续关联和统计有稳定口径。第一步保留原始字段副本第二步把代码字段转成字符串第三步处理空值和重复。adcode如果被读成整数前导零会丢失所以要用astype(str)或zfill补位name字段要去掉首尾空格避免按名称关联时匹配不上。重复面在行政区划数据里不常见但合并多个来源时会出现按代码去重比按名称去重可靠。import geopandas as gpd import pandas as pd gdf gpd.read_file(data/county.shp, encodingutf-8) # 保留原始字段避免清洗后无法回溯 gdf[name_raw] gdf[name] gdf[adcode_raw] gdf[adcode] # 代码字段统一转字符串按实际长度补零 gdf[adcode] gdf[adcode].astype(str).str.strip().str.zfill(6) # 名称去空格空值标记出来而不是直接删 gdf[name] gdf[name].astype(str).str.strip() gdf.loc[gdf[name].isin([, None, nan]), name] pd.NA # 按行政区划代码去重保留第一条 gdf gdf.drop_duplicates(subset[adcode], keepfirst) # 删除几何为空的行空几何无法参与空间操作 gdf gdf[~gdf.geometry.is_empty] print(清洗后行数:, len(gdf)) print(名称空值:, gdf[name].isna().sum())参数上zfill(6)是常见做法但如果数据里存在更长或更短的代码先统计长度分布再决定补几位不要硬编码。drop_duplicates的subset用代码字段不用名称字段因为名称可能重名。删除空几何用布尔索引~表示取反。清洗完先看行数变化如果一下子少了很多回头查是代码重复还是空几何不要直接进入下一步。3.4 坐标转换与面积计算先投影再算面积坐标转换在 GeoPandas 里就是to_crs但选哪个目标 CRS 取决于你要干什么。出 Web 地图用EPSG:3857做面积统计用等积投影做缓冲区分析用投影坐标。EPSG:3857的优点是底图叠加方便缺点是高纬度面积变形明显所以我不拿它做行政区面积排名。更稳的做法是按数据范围自定义 Albers 等积投影或者用estimate_utm_crs先估一个 UTM 分区但全国范围跨多个 UTM 带不适合一套参数全用。import geopandas as gpd gdf gpd.read_file(data/county.shp, encodingutf-8) # 展示用 Web Mercator只用于底图叠加 gdf_web gdf.to_crs(epsg3857) # 统计用等积投影参数按数据范围调整 equal_area_crs projaea lat_125 lat_247 lat_00 lon_0105 x_00 y_00 datumWGS84 unitsm no_defs gdf_area gdf.to_crs(equal_area_crs) # 投影后算面积单位平方米转平方公里 gdf_area[area_km2] gdf_area.geometry.area / 1e6 # 按层级汇总面积检查数量级是否合理 print(gdf_area.groupby(level)[area_km2].agg([count, sum, min, max]))to_crs不会修改原对象而是返回新对象所以gdf_web和gdf_area可以并存。geometry.area必须在投影坐标下调用地理坐标下调用得到的是平方度。分组汇总时看min和max如果县级面出现个位数或几十万平方公里的极端值优先查几何无效、多部件面或坐标系统错位。面积字段建议保留在投影后的副本里不要写回原始地理坐标对象避免后续导出时单位混淆。3.5 导出GeoJSON、GPKG、CSV 的取舍导出格式决定后续协作成本。GeoJSON 字段名不截断适合前端和轻量交换但体积大、不支持多图层GPKG 是单文件空间数据库支持多图层、字段名完整、编码稳定适合作为中间成果CSV 只适合导属性表几何会丢。行政区划 shp 文件如果还要继续做空间操作我一般先导出 GPKG后续读 GPKG 比反复读 shp 更稳字段名也不会被截断。import geopandas as gpd gdf gpd.read_file(data/county.shp, encodingutf-8) # 导出 GPKG保留完整字段名和编码 gdf.to_file(out/administrative.gpkg, layercounty, driverGPKG) # 导出 GeoJSON适合前端或轻量交换 gdf.to_file(out/administrative.geojson, driverGeoJSON) # 只导属性表不带几何 gdf.drop(columnsgeometry).to_csv(out/administrative_attr.csv, indexFalse, encodingutf-8-sig)to_file的driver参数决定格式layer只在 GPKG 这类多图层格式里有意义。encodingutf-8-sig是给 CSV 加 BOM避免表格软件打开中文乱码GeoJSON 一般不需要额外指定编码。导出后不要只看文件是否存在重新读一遍 GPKG确认字段名、行数、CRS 和几何类型没有变化这一步能拦住大部分“导出成功但数据坏了”的问题。4. 空间操作落地筛选、裁剪、空间连接与制图简化4.1 属性筛选与几何筛选先筛属性再动几何行政区划 shp 文件动辄几千个面直接做空间操作会很慢。常见做法是先按属性筛到目标层级再用矩形范围或掩膜做几何筛选。属性筛选用布尔索引几何筛选可以用cx索引器按边界框取子集但cx的坐标必须和当前 CRS 一致。如果当前是地理坐标边界框写的是经纬度如果已经投影边界框写的是米。两者混用不会报错但会筛出空结果或错误范围。import geopandas as gpd gdf gpd.read_file(data/county.shp, encodingutf-8) # 先按层级筛减少后续几何计算量 county gdf[gdf[level] county].copy() # 按边界框筛坐标顺序是 minx:maxx, miny:maxy # 这里用变量表示范围实际使用时确认 CRS 是经纬度还是投影坐标 minx, maxx 100, 110 miny, maxy 20, 30 subset county.cx[minx:maxx, miny:maxy] print(属性筛选后:, len(county)) print(几何筛选后:, len(subset))这段代码的重点是顺序先属性后几何。copy()避免后续修改触发切片警告。cx的切片顺序是x范围在前、y范围在后写反了会筛不到数据。边界框是矩形不是行政区边界所以它只适合做粗筛精确裁剪还要用clip或overlay。如果筛完数量为 0先检查 CRS 和边界框单位不要急着怀疑数据缺失。4.2 掩膜裁剪clip 与 overlay 的差异裁剪行政区划数据有两种常见方式clip和overlay。clip用一个掩膜几何去裁目标图层保留左表结构适合“按某个区域裁出底图”overlay做两个图层的叠置分析会合并字段并处理相交关系适合“算两个分区体系的交集”。两者都要求 CRS 一致否则结果会错位。clip更快overlay更灵活但字段会变多后续要手动清理。import geopandas as gpd gdf gpd.read_file(data/county.shp, encodingutf-8) mask gpd.read_file(data/mask.gpkg, layerstudy_area) # 裁剪前统一 CRS避免错位 mask mask.to_crs(gdf.crs) # clip 保留左表字段结构适合按研究区裁行政区 clipped gpd.clip(gdf, mask) # overlay 做叠置字段会合并适合算交集面积 overlaid gpd.overlay(gdf, mask, howintersection) print(clip 行数:, len(clipped)) print(overlay 行数:, len(overlaid)) print(overlay 字段:, overlaid.columns.tolist())gpd.clip的第二个参数是掩膜可以是 GeoDataFrame 或几何对象。gpd.overlay的howintersection表示取交集还有union、difference等模式。裁剪后检查行数如果掩膜只覆盖局部clip行数应该少于原数据如果行数没变可能是掩膜范围过大或 CRS 没对齐。overlay字段变多后记得把不必要的前缀字段删掉避免后续导出时字段名冲突。4.3 点数据落区sjoin 的 predicate 与 CRS 一致性把业务点落到行政区是这份 shp 文件最高频的用法。sjoin的核心参数是predicate常见取值有within、intersects、contains。点落在面内部用within点在边界上是否算入取决于业务口径intersects会把边界点也算进去容易在相邻面之间产生重复。做统计时我一般先用within再对未匹配点单独检查避免边界点被重复计数。import geopandas as gpd polygons gpd.read_file(data/county.shp, encodingutf-8) points gpd.read_file(data/points.gpkg, layerpoi) # 空间连接前必须统一 CRS points points.to_crs(polygons.crs) # within 表示点落在面内部边界点不重复计入相邻面 joined gpd.sjoin(points, polygons[[name, adcode, geometry]], howleft, predicatewithin) # 未匹配的点单独拿出来排查 unmatched joined[joined[index_right].isna()] print(总点数:, len(points)) print(未匹配点数:, len(unmatched)) print(重复点检查:, joined[point_id].duplicated().sum())howleft保留所有点未匹配的index_right为空方便排查。predicatewithin不会把边界点同时分给两个面但边界点可能一个都匹配不上所以未匹配点要单独看。如果未匹配数量很大先检查 CRS 是否一致再检查点位是否落在行政区范围外。重复点检查用业务主键不要用index_right因为一个点理论上只应落入一个面重复说明predicate选得太宽或面本身有重叠。4.4 制图简化simplify 的 tolerance 怎么设行政区划边界在制图时不需要保留全部顶点尤其出小比例尺地图时几万个顶点会让渲染变慢。simplify的tolerance单位跟随当前 CRS投影坐标下是米地理坐标下是度。制图简化我一般先投影到 Web Mercator再按出图比例设容差比如几千像素宽的图用几百米容差具体值要看边界细节是否还保留。preserve_topologyTrue能减少相邻面之间出现缝隙但不是万能的简化后仍要检查共享边界。import geopandas as gpd gdf gpd.read_file(data/county.shp, encodingutf-8) # 制图用 Web Mercator容差单位为米 gdf_web gdf.to_crs(epsg3857) # 简化几何保留拓扑避免相邻面出现裂缝 gdf_web[geometry] gdf_web.geometry.simplify(500, preserve_topologyTrue) # 简化后检查几何有效性和面积变化 print(简化后有效几何:, gdf_web.geometry.is_valid.all()) print(简化前后面积差:, (gdf_web.geometry.area - gdf.to_crs(epsg3857).geometry.area).abs().max())simplify(500)里的 500 是容差单位米不是像素。preserve_topologyTrue会尽量保持面之间的相邻关系但代价是简化幅度受限。简化后的对象只用于出图不要覆盖原始数据也不要用简化后的面做面积统计。如果简化后面积变化明显说明容差设得太大回头调小再试。制图简化属于“看起来差不多就行”的环节但边界共享关系一旦破坏出图会出现白色裂缝这块最容易翻车。5. 避坑与排查行政区划 shp 文件最容易翻车的五处5.1 中文属性乱码cpg 缺失与 DBF 编码现象很直接图能打开但name字段显示成问号或乱码按名称筛选完全失效。原因通常有两个一是.cpg文件缺失读取工具不知道 DBF 用什么编码二是.cpg写的是UTF-8实际 DBF 是GBK或者反过来。解决时不要反复转码先备份原文件再用不同编码试读确认哪种能读出正常中文后导出成 GPKG 固定下来。如果.cpg缺失可以手动补一个纯文本文件内容写UTF-8或GBK但前提是确认实际编码不要盲猜。import geopandas as gpd # 先试 utf-8再试 gbk观察中文名称是否正常 for enc in [utf-8, gbk]: try: gdf gpd.read_file(data/county.shp, encodingenc) print(enc, gdf[name].head(3).tolist()) except Exception as e: print(enc, 读取失败:, e)这段代码只用于判断编码不要在同一次处理流程里反复读写 shp。确认编码后用to_file导出 GPKG后续都从 GPKG 读乱码问题基本不会再出现。5.2 面积算出来离谱CRS 用错现象是面积字段数量级不对县级面算出个位数或几十万平方公里或者同一批数据两次计算结果差很多。原因几乎都是在地理坐标下直接调用了geometry.area得到的是平方度另一种情况是投影参数选错中央经线偏离数据范围边缘区域变形被放大。解决方法是先看gdf.crs.is_geographic如果是地理坐标先to_crs到等积投影再算面积。算完先看分组统计的min和max数量级不对就回头查 CRS不要继续往下做汇总。import geopandas as gpd gdf gpd.read_file(data/county.shp, encodingutf-8) if gdf.crs and gdf.crs.is_geographic: equal_area_crs projaea lat_125 lat_247 lat_00 lon_0105 x_00 y_00 datumWGS84 unitsm no_defs gdf gdf.to_crs(equal_area_crs) gdf[area_km2] gdf.geometry.area / 1e6 print(gdf[area_km2].describe())describe()能快速看分布如果max远大于合理范围优先查多部件面和几何无效而不是继续调投影参数。5.3 字段名被截断DBF 10 字符限制现象是读入后字段名变短比如province_name变成province_n两个相似字段重名后按列名取值取到错误数据。原因是 DBF 格式对字段名长度有限制shp 文件本身不适合存长字段名。解决方法是在读入后立刻重命名字段建立一份字段映射表导出时用 GPKG 或 GeoJSON这两种格式不会截断字段名。如果必须交付 shp 文件就在导出前把字段名压到 10 个字符以内并确保不重复。import geopandas as gpd gdf gpd.read_file(data/county.shp, encodingutf-8) # 建立字段映射把截断字段改回可读名称 mapping { province_n: province_name, city_name: city_name, county_nam: county_name, } gdf gdf.rename(columns{k: v for k, v in mapping.items() if k in gdf.columns}) # 导出 GPKG避免再次截断 gdf.to_file(out/administrative.gpkg, layercounty, driverGPKG)映射前先打印gdf.columns确认实际截断后的名字不要凭猜测写映射。导出 GPKG 后再读一遍确认字段名完整。5.4 几何无效与自相交buffer(0) 不是万能药现象是is_valid出现False做sjoin、overlay或clip时报拓扑异常或者结果里出现空几何。原因包括边界自相交、重复点、环方向错误、多部件面内部重叠。常见做法是用buffer(0)修但它并不总是安全可能改变几何形状或把多部件面拆开。更稳的方式是先用explain_validity看原因再决定用make_valid还是手工处理。修复后要重新检查面积和要素数量确认没有把飞地或岛屿弄丢。import geopandas as gpd from shapely.validation import explain_validity gdf gpd.read_file(data/county.shp, encodingutf-8) invalid gdf[~gdf.geometry.is_valid] for idx, row in invalid.head(5).iterrows(): print(row[name], explain_validity(row.geometry)) # make_valid 比 buffer(0) 更可控修复后仍需复查 gdf[geometry] gdf.geometry.make_valid() print(修复后有效几何:, gdf.geometry.is_valid.all())make_valid可能把面拆成多部件修复后要检查geom_type和要素数量变化。不要在全量数据上直接buffer(0)后继续算面积先抽样验证。5.5 文件只拷了 .shp伴生文件缺失现象是换台机器打开后属性表空了、坐标不对或者工具直接报错。原因通常是压缩包里只解压了.shp或者复制时只选了主文件。.dbf缺失导致属性全空.prj缺失导致坐标系统未知.cpg缺失导致中文乱码。解决方法是整套复制同名文件解压后先列目录再用ogrinfo -so确认图层信息完整。交付给别人时我一般直接压成 zip而不是让对方自己挑文件。# 检查同名伴生文件是否齐全 ls -lh data/county.* # 用 ogrinfo 确认字段和 CRS 是否存在 ogrinfo -so -al data/county.shp如果ogrinfo输出里字段列表为空优先查.dbf如果Layer SRS WKT为空优先查.prj。这两个文件缺失最隐蔽因为图还能画出来但数据已经不对了。6. 进阶校验流水线把 shp 文件变成可复用的数据底座行政区划 shp 文件用久了会发现真正费时间的不是读文件而是每次拿到新版本都要重新确认字段、坐标系、几何有效性和代码唯一性。我后来固定成一个校验函数读入后先看 CRS 是否为空再统计几何类型和无效几何接着检查代码字段长度和重复最后输出一份摘要。这个函数不执行业务筛选只做体检跑完再决定要不要投影、要不要修几何、要不要导出 GPKG。import geopandas as gpd import pandas as pd def audit_shp(path, code_coladcode, name_colname): gdf gpd.read_file(path) summary { rows: len(gdf), crs: str(gdf.crs), geom_types: gdf.geom_type.value_counts().to_dict(), invalid_geom: int((~gdf.geometry.is_valid).sum()), empty_geom: int(gdf.geometry.is_empty.sum()), code_dup: int(gdf[code_col].astype(str).duplicated().sum()), code_len: gdf[code_col].astype(str).str.len().value_counts().to_dict(), name_null: int(gdf[name_col].isna().sum()), } return pd.Series(summary) print(audit_shp(data/county.shp))这段代码的关键参数是code_col和name_col不同资源的字段名可能不一样调用时显式传进去不要依赖默认值。code_len用来发现代码长度不统一code_dup用来发现重复面invalid_geom和empty_geom决定要不要进入修复分支。校验结果我一般存成一条记录跟数据版本一起归档下次换版本时直接对比比肉眼翻字段快得多。出图前的简化也一样不要直接在原始数据上改几何。先复制一份投影到展示坐标系再simplify出图只用简化副本统计和空间连接仍用原始几何。这样即使简化参数调大了也不会污染底层数据。从那以后我每次拿到新的 shp文件 都先跑一遍这个检查再决定要不要投影、要不要修几何。希望帮到你。本文还有配套的精品资源点击获取