☰
市级行政区域shp数据全流程处理:从体检清洗到入库应用
2026/11/6 9:52:05 网站建设 项目流程

简介:这份资源提供中国全部地级市行政区域的矢量边界数据,采用Shapefile格式,面向GIS学习者、城市规划人员及地理相关研究者,可用于行政区划制图、城市分布分析与空间统计等场景。压缩包共11个文件,约1.91MB,包含shp空间几何数据、dbf属性表、prj投影信息,以及shx、sbn、sbx索引文件与xml元数据,各类型分工明确,便于在ArcGIS等平台直接加载、裁剪与合并。数据年代为2004年,属性表可查城市名称、代码、人口、面积等信息,适合作为行政边界底图或历史区划对比的基础素材。目前已有2079人学习下载,读者可据此快速搭建市级行政区划图层,完成专题制图与空间分析练习,也可按需更新至最新年份以提升准确性。

1. 市级行政区域 shp 数据:从“拿到一份能用的边界”开始

做专题图、做空间统计、做区域看板,绕不开的第一件事就是拿到一份能用的市级行政区域 shp。很多人以为这是最没技术含量的环节,结果真正动手才发现:坐标系对不上、市辖区和地级市混在一起、飞地和跨省界的地方画歪了、属性表里连个像样的编码都没有。我见过不止一个项目,业务逻辑写了三千行,最后卡在“两个市的边界重叠了 0.3 平方公里”这种问题上。

这篇讲的就是市级行政区域 shp 这件事本身:一份市级边界数据到底该长什么样,怎么判断手里的数据能不能用,怎么把它清洗成能进生产环境的版本,以及入库、投影、拓扑检查这些环节里最容易翻车的地方。适合正在做区域分析、GIS 可视化、行政区划匹配的从业者,也适合刚接手一份“别人给的 shp”不知道从哪下手的新手。下面按“数据是什么 → 怎么验 → 怎么洗 → 怎么用 → 坑在哪”的顺序推。

2. 市级行政区域 shp 的数据结构与选型判断

2.1 shp 不是单个文件,属性表和几何要分开看

先把这个最基础也最容易被忽略的点说清楚。Shapefile 是一组同名文件的集合,缺一个就打不开或者丢信息。常见构成如下:

文件后缀作用缺失后果
.shp存储几何(点/线/面)直接无法读取
.shx几何索引部分库报错或读取极慢
.dbf属性表只剩图形,没有名称和编码
.prj坐标系定义坐标数值在,但不知道是什么投影
.cpg属性表字符编码中文属性乱码

血泪经验是:别人发你一个压缩包,解压出来只有 .shp 和 .dbf,看着能打开,但 .prj 一缺,你根本不知道这份市级边界是 WGS84 经纬度还是某种投影坐标。数值上差几十万倍,直接叠到自己的底图上就是“图形飞到天外”。所以拿到数据第一件事不是打开看,是先确认这五个文件齐不齐。

属性表这边,市级行政区域 shp 至少要能回答三个问题:这个面是哪个市(名称)、它的行政编码是多少(唯一标识)、它属于哪个省(上级归属)。没有编码的数据不是不能用,但一旦要做跨数据源匹配,名称的写法差异会让你痛不欲生——“某某市”和“某市”、“XX地区”和“XX市”,靠字符串模糊匹配迟早出事。

2.2 市级边界到底指哪一级:地级市、市辖区还是县级市

这是选型阶段最容易埋雷的地方。中文语境里“市级”至少对应三种口径:

  • 地级行政区:包括地级市、地区、自治州、盟,是省级下面的完整一层,面与面之间无缝拼接。
  • 市辖区:地级市内部的区,只覆盖城区,不覆盖下辖县市,拼起来有大量空洞。
  • 县级市:由地级市代管或省直管,行政级别是县级,但名字里带“市”。

如果你的需求是“按地级市做区域汇总”,却拿到了一份市辖区数据,那每个市的面积都会严重偏小,因为下辖县根本没算进去。反过来,做城市建成区分析却用了地级市全域边界,城区会被周边大片农村稀释。判断方法很直接:看属性表里有没有“级别”或“类型”字段,或者数一下总要素数——全国地级行政区大约三百多个,市辖区数量是它的数倍。数量对不上,口径大概率错了。

提示:不要用“名字里带不带市”来判断层级,县级市同样带“市”。以行政编码的层级位或数据说明为准。

2.3 坐标系与精度:先搞清楚数据在哪个“尺子”上

市级行政区域 shp 常见的坐标参考有两类:地理坐标系(经纬度,单位是度)和投影坐标系(单位是米)。做面积统计、距离计算必须用投影坐标,否则在经纬度上直接算面积,高纬度地区误差能到百分之几十。做网页地图叠加,通常又需要经纬度。所以一份合格的数据要么自带正确的 .prj,要么你能明确知道它的原始坐标系。

精度方面,市级边界的合理精度大致在 1:100 万到 1:25 万之间。精度越高文件越大,但市级分析一般不需要到村级那种细度。判断精度是否够用,看边界在河流、山脊处是否出现明显锯齿——锯齿太粗说明是低精度简化过的,做小区域统计会失真;过于精细则可能带来几十万节点的面,渲染和空间运算都会变慢。选型时按用途定:看板展示用中等精度即可,空间叠加分析再上高精度。

3. 用 Python 把市级 shp 读进来并做第一轮体检

3.1 环境准备与最小读取脚本

我一般用 geopandas 做第一轮体检,因为它把读取、投影、属性、几何操作都收在一套 API 里,排查效率高。环境装好之后,先跑一个最小读取脚本,把基本信息打出来。

import geopandas as gpd # 读取市级行政区域 shp,注意路径指向 .shp 文件本身 gdf = gpd.read_file("./data/city_boundary.shp", encoding="utf-8") # 基本信息:要素数、字段、坐标系、几何类型 print("要素数:", len(gdf)) print("字段:", list(gdf.columns)) print("坐标系:", gdf.crs) print("几何类型:", gdf.geom_type.unique()) # 看前几行属性,确认名称和编码字段是否存在 print(gdf.head())

这段代码的逻辑是先确认“有没有”,再谈“对不对”。encoding="utf-8"是为了防止中文属性乱码,如果 .cpg 缺失且原始编码是 GBK,这里要改成gbk试一次。gdf.crs打印出来如果是None,说明 .prj 缺失或无效,后面所有投影相关操作都不能信。geom_type.unique()应该只返回Polygon或MultiPolygon,如果混进了LineString,说明数据里混了边界线要素,需要先过滤。

参数上,read_file对大文件可以加rows做抽样,先读前 100 行看结构,确认没问题再全量读,避免一个几百 MB 的文件卡住整个排查流程。

3.2 体检清单:空几何、重复、自相交、编码缺失

读完只是开始,真正决定这份数据能不能用的是下面这轮体检。我习惯把它写成一段可复用的检查函数,每次拿到新数据都跑一遍。

import geopandas as gpd def health_check(gdf, name_col="name", code_col="adcode"): report = {} # 1. 空几何与无效几何 report["空几何数"] = gdf.geometry.is_empty.sum() report["无效几何数"] = (~gdf.geometry.is_valid).sum() # 2. 名称与编码缺失 report["名称缺失"] = gdf[name_col].isna().sum() report["编码缺失"] = gdf[code_col].isna().sum() # 3. 编码重复(同一编码出现多次通常是数据拼接出错) report["编码重复"] = gdf[code_col].duplicated().sum() # 4. 面积异常:投影后统计,找出明显偏小或偏大的面 gdf_proj = gdf.to_crs(epsg=3857) # 仅用于相对比较,不用于正式面积 areas = gdf_proj.geometry.area report["面积最小值"] = round(areas.min(), 2) report["面积最大值"] = round(areas.max(), 2) return report print(health_check(gdf))

逻辑说明:空几何和无效几何是空间运算报错的头号来源,is_valid为 False 的面在做相交、裁剪时会直接抛异常。编码重复往往意味着同一区域被录入了两次,做汇总时会翻倍。面积检查用 EPSG:3857 只是为了快速找异常值,它本身在高纬度有变形,正式面积统计要换成对应分带的投影坐标系,比如按经度带选 UTM 或使用等面积投影。

参数上,name_col和code_col要按你数据的实际字段名改,很多数据里叫NAME、CODE或者拼音缩写,先list(gdf.columns)看清楚再传。这一步跑完,报告里任何一项不为零,都要在进入下一步之前处理掉。

3.3 投影转换与面积统计的正确姿势

市级数据做面积统计,投影选错是玄学级翻车点。经纬度直接算面积,单位是“平方度”,没有任何业务意义;用 Web 墨卡托算面积,越往北越虚高。正确做法是按研究区所在纬度带选等面积投影或 UTM 分带。

# 按经度带选 UTM,中国东部大致在 48-51 带,需按实际经度调整 gdf_utm = gdf.to_crs(epsg=32650) # WGS84 / UTM zone 50N gdf_utm["area_km2"] = gdf_utm.geometry.area / 1e6 # 若做全国尺度,等面积投影更稳妥 gdf_eq = gdf.to_crs("+proj=aea +lat_1=25 +lat_2=47 +lat_0=0 +lon_0=105 +datum=WGS84") gdf_eq["area_km2"] = gdf_eq.geometry.area / 1e6

逻辑上,UTM 适合单个省或跨几个带以内的区域,变形小;全国尺度用 Albers 等面积投影,保证面积可比。参数里lat_1、lat_2是标准纬线,lon_0是中央经线,按研究区范围调整,不要照抄。面积除以 1e6 是把平方米换成平方公里,这一步别漏,否则报表数字会大六个数量级。

4. 清洗与入库:让市级边界能进生产环境

4.1 修复无效几何与消除重叠缝隙

体检出来的无效几何,常见原因是自相交(一个面自己绕回来交叉了)和环方向错误。修复用buffer(0)是老办法,但对复杂面可能改变形状,更稳的是make_valid。

from shapely.validation import make_valid # 只对无效几何做修复,避免全量重算 mask = ~gdf.geometry.is_valid gdf.loc[mask, "geometry"] = gdf.loc[mask, "geometry"].apply(make_valid) # 修复后复查 print("修复后无效几何数:", (~gdf.geometry.is_valid).sum())

逻辑说明:make_valid会把自相交的面拆成合法面,可能返回 MultiPolygon,这是正常的。修复后一定要复查,因为个别极端几何修复后仍不合法,需要人工看。参数上没有可调的,重点是只对无效行操作,全量 apply 在大数据上很慢。

重叠和缝隙是另一类问题。相邻市之间理论上应该无缝拼接,但不同来源的数据拼在一起,接缝处常有细长缝隙或重叠条带。处理思路是先做一次全量 union,再按属性重新切分,或者用拓扑规则约束。实操中更常见的是接受微小缝隙(比如小于 1 平方米的),只处理明显重叠。

4.2 属性表规范化:编码、名称、层级三件套

属性表不规范,后面所有匹配都是灾难。我一般把属性整理成固定几列:行政编码、名称、上级编码、层级。编码用国家行政区划代码的六位或十二位口径,保持字符串类型,千万别存成整数——前导零会丢,比如编码以 0 开头的会变成少一位。

# 统一成字符串并去空格,防止 "110000" 和 " 110000" 匹配不上 gdf["adcode"] = gdf["adcode"].astype(str).str.strip() gdf["name"] = gdf["name"].astype(str).str.strip() # 按编码前两位推上级省份编码(六位编码口径) gdf["province_code"] = gdf["adcode"].str[:2] + "0000" # 去重:同一编码只保留一条,保留面积最大的(通常是主面) gdf = gdf.sort_values("area_km2", ascending=False).drop_duplicates("adcode")

逻辑上,字符串化加去空格解决的是“看着一样实际不等”的隐形问题。推上级编码是为了后续按省汇总时不用再查表。去重保留最大面,是因为有些数据把飞地拆成了多条记录,主面才是业务上要的那个。参数上,编码位数要按你的数据口径改,六位和十二位的前缀规则不同,别混用。

4.3 导出 GeoPackage 与入库前的最后校验

shp 有字段名长度限制(10 字符)、编码问题、单文件 2GB 上限,生产环境我更倾向导出成 GeoPackage 或直接入库。导出前做最后一轮校验:要素数、编码唯一性、坐标系、几何有效性。

# 导出为 GeoPackage,字段名不受 10 字符限制,中文更稳 gdf.to_file("./out/city_boundary.gpkg", layer="city", driver="GPKG") # 入库前校验 assert gdf["adcode"].is_unique, "编码存在重复" assert gdf.crs is not None, "坐标系缺失" assert gdf.geometry.is_valid.all(), "仍有无效几何" print("校验通过,要素数:", len(gdf))

逻辑说明:assert在这里是硬门槛,任何一条不满足就不该往下走。GeoPackage 用layer指定图层名,一个文件可以放多层,比 shp 好管理。参数上driver="GPKG"不能省,否则可能按默认格式写出。如果目标库是 PostGIS,可以用to_postgis,但要注意库里的 SRID 要和数据坐标系一致,否则查询时空间关系全错。

5. 市级 shp 使用中的避坑与排查

5.1 现象:两个市的边界重叠,汇总面积大于总面积

原因通常是数据来源拼接,相邻市各自带了一点对方的边,或者简化算法在接缝处产生了重叠。解决:先做全量 union 得到整体轮廓,再按属性重新切分;或者用拓扑检查工具找出重叠面积超过阈值的面对,人工修正。阈值我一般设 1 平方公里,小于这个的当噪声忽略。

5.2 现象:中文属性全是乱码,名称匹配全失败

原因是 .dbf 的字符编码和读取时指定的不一致,常见是数据本身 GBK 而按 UTF-8 读。解决:先试encoding="gbk",读出来正常就说明是 GBK;如果 .cpg 文件存在,里面会写明编码。根治办法是读进来后统一转 UTF-8 再导出,别让编码问题流到下游。

5.3 现象:空间相交查询返回空,但图形明明叠在一起

原因是两个图层的坐标系不一致,一个经纬度一个投影,数值上根本不在一套尺子里。解决:查询前统一to_crs到同一坐标系,且优先用投影坐标做相交,经纬度做相交在跨带时会有误差。排查时先打印两边的crs对比,这一步能省掉大量瞎猜。

5.4 现象:面积统计结果和官方公布数据差很多

原因可能是口径不对(地级市 vs 市辖区)、投影不对(用墨卡托算面积)、或者边界精度太低把海域、飞地算漏了。解决:先确认口径,再换等面积投影重算,最后和公开的行政区面积做量级对比,差一个数量级说明口径错了,差百分之几可能是精度和投影的正常误差。

5.5 现象:渲染时边界出现锯齿或断裂

原因是几何精度和渲染缩放级别不匹配,低精度数据放大后节点不够,高精度数据缩小后节点过密导致渲染卡顿。解决:按展示级别做几何简化,用simplify保留拓扑,简化容差按缩放级别定,别一刀切。简化后要复查相邻面是否还拼接,拓扑保留参数要开。

6. 进阶:用空间连接把业务数据挂到市级边界上

数据洗干净之后,真正的价值在于把业务数据挂上去。最常见的场景是:手里有一批带经纬度的点位,要统计每个市有多少个。做法是空间连接,但直接sjoin有两个前提——坐标系一致、点位落在面内。边界附近的点容易因为精度问题落在缝隙里,导致漏统计。

# 点位数据转成 GeoDataFrame,假设已有 lon/lat 字段 points = gpd.GeoDataFrame( df, geometry=gpd.points_from_xy(df["lon"], df["lat"]), crs="EPSG:4326" ) # 统一到市级边界的坐标系 points = points.to_crs(gdf.crs) # 先做一次微小缓冲,吸收边界缝隙造成的漏点 points["geometry"] = points.geometry.buffer(0.0001) joined = gpd.sjoin(points, gdf[["adcode", "name", "geometry"]], how="left", predicate="within") count_by_city = joined.groupby("adcode").size().reset_index(name="cnt")

逻辑说明:buffer(0.0001)是给点一点容差,让它能落进因缝隙而“漏掉”的面里,容差大小按数据精度定,经纬度下 0.0001 度大约十米。predicate="within"表示点完全落在面内,边界上的点可能被判为不在任何面内,所以缓冲很有必要。how="left"保证没匹配上的点也保留,方便排查是哪些点漏了。

参数上,buffer的容差不能太大,否则会把本该属于 A 市的点吸到 B 市;sjoin的predicate还可以用intersects,对边界点更宽容,但可能造成一个点被多个面匹配,需要去重。做完之后一定要核对总数:连接后的点数应该等于原始点数,少了就是有漏匹配,多了就是有重复匹配。

进阶一点的做法是把这套流程固化成脚本,每次新数据进来先跑体检、再跑清洗、最后跑连接,把中间报告落盘。我自己的习惯是每个项目建一个data_check目录,体检报告、修复日志、校验结果都放进去,出问题时能回溯是哪一步把数据改坏了。市级行政区域 shp 这件事,难点从来不在“拿到数据”,而在“拿到之后敢不敢用”。把体检和校验做成习惯,比事后救火省心得多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询