简介:这份资源提供四川省成都市乡镇街道级别的行政区划矢量数据,以shp格式组织,面向GIS从业者、城市规划研究人员、地理信息专业师生以及需要成都基层行政边界做空间分析的用户。数据覆盖成都市下辖各乡镇与街道,可用于专题制图、人口与经济数据空间关联、行政区划统计及地图可视化等场景,属于可直接导入ArcGIS、QGIS等平台使用的基础地理数据。压缩包共14个文件,约1.81MB,包含3个shp主文件及配套的shx索引、dbf属性表、prj投影定义、cpg编码说明、sbn与sbx空间索引,另有xml元数据文件,各类型协同保证数据在主流GIS软件中正常读取与显示。目前已有2506人学习下载,说明该数据在本地研究与教学中有一定使用基础。读者获取后可直接得到完整的成都乡镇街道边界图层,省去自行采集与数字化的工作,便于快速开展空间统计、制图出图与区域对比分析。
1. 成都市各乡镇街道行政区划shp文件:从一份边界数据到能跑的分析底图
做城市数据分析的人迟早会撞上一件事:手头有一堆 POI、人口、网格数据,却缺一份能直接用的成都市乡镇街道行政区划 shp 文件。街道级别是很多分析的最小行政单元,比区县细,比社区规整,做选址、做热力、做资源分配都绕不开它。这份数据的价值不在于“有”,而在于“对得上”——边界拓扑要闭合、投影要统一、属性表要有可关联的行政区代码。我见过太多人拿到一份 shp 就急着往 GIS 里拖,结果坐标系是地理坐标,面积算出来是度数平方,或者相邻街道之间留着肉眼看不见的缝隙,做空间连接时点全掉进缝里。这篇就按一线做法,把成都市乡镇街道行政区划 shp 文件从获取、检查、修复到落地分析的完整链路讲清楚,适合做城市研究、规划辅助、商业选址的从业者,也适合刚接触 GIS 但需要真实数据干活的人。
2. 拿到 shp 文件先别急着画图:四个字段和两个几何指标决定它能不能用
2.1 行政区划 shp 的属性表里到底该有什么
一份能用的成都市乡镇街道行政区划 shp 文件,属性表至少要有这几列:街道乡镇名称、所属区县名称、行政区划代码、以及可选的城乡分类代码。行政区划代码是关联外部数据的关键,街道级通常是 12 位,前 6 位是区县,中间 3 位是乡镇街道顺序码,最后 3 位是村级,但街道级 shp 一般只到前 9 位或 12 位全码。我一般会先打开属性表看三件事:名称列有没有“街道”“镇”“乡”后缀混用、代码列是不是文本类型、有没有空值。名称后缀不统一会导致按名称关联时匹配失败,代码列如果是数值类型,前导零会被吃掉,成都这边区县代码以 5101 开头,暂时没有前导零问题,但换成其他省份就会翻车。
提示:行政区划代码在 shp 里务必存成字符串,数值类型在导出 CSV 再导入时极易丢精度或变科学计数法。
2.2 几何检查:闭合、自相交、缝隙三件套
几何质量决定后续空间运算能不能跑通。用 QGIS 或 ArcGIS 打开后,先看拓扑:面是否闭合、有没有自相交、相邻面之间有没有缝隙或重叠。成都市乡镇街道 shp 文件常见的问题是边界沿河流或道路采集时产生细小自相交,以及不同批次数据拼接后相邻街道之间出现宽度不到 1 米的缝隙。这些在视觉上完全看不出来,但做空间连接时点会掉进去。我一般用 QGIS 的“拓扑检查器”跑一遍,或者用 Python 的 shapely 做批量验证。下面这段代码就是检查自相交和有效性的最小脚本:
import geopandas as gpd from shapely.validation import explain_validity # 读取成都市乡镇街道 shp 文件 gdf = gpd.read_file("chengdu_township.shp") # 检查几何有效性,invalid 会返回具体原因 gdf["validity"] = gdf.geometry.apply(lambda geom: explain_validity(geom)) invalid = gdf[gdf["validity"] != "Valid Geometry"] print(f"无效几何数量: {len(invalid)}") print(invalid[["name", "validity"]].head(10)) # 检查空几何 empty = gdf[gdf.geometry.is_empty] print(f"空几何数量: {len(empty)}")这段代码先读 shp,然后用explain_validity逐条判断几何是否有效,无效的会返回“Self-intersection”之类的具体原因。is_empty单独查空几何,空几何在后续裁剪时会直接报错。参数上没什么可调的,重点是看输出里无效几何的数量和类型,如果超过总面数的 1%,建议先修复再往下做。
2.3 坐标系与投影:算面积之前必须做的一步
成都市乡镇街道行政区划 shp 文件常见的坐标系有两种:WGS84 地理坐标(EPSG:4326)和 CGCS2000 投影坐标(EPSG:4544 或 4545)。地理坐标单位是度,直接算面积得到的是平方度,没有物理意义。做面积统计、缓冲区、密度分析之前,必须投影到以米为单位的坐标系。成都位于 CGCS2000 3 度带第 34 带,中央经线 102°E,对应 EPSG:4544。我一般会在分析前统一转成这个投影,代码很简单:
# 投影转换:从 WGS84 转到 CGCS2000 3 度带 gdf_proj = gdf.to_crs(epsg=4544) # 计算每个街道的面积(平方公里) gdf_proj["area_km2"] = gdf_proj.geometry.area / 1e6 print(gdf_proj[["name", "area_km2"]].sort_values("area_km2", ascending=False).head())to_crs是 geopandas 的投影转换方法,传入目标 EPSG 代码即可。面积除以 1e6 是把平方米转成平方公里。这里要注意,如果原始 shp 没有.prj文件,to_crs会报错,因为不知道源坐标系。遇到这种情况,先确认数据来源的坐标系,用gdf.set_crs(epsg=4326)手动指定后再转换。这一步不做,后面所有跟距离、面积相关的结论都是错的,属于典型的“地基没打牢”。
3. 从区县到乡镇街道:用 Python 做空间连接和属性关联的完整流程
3.1 把 POI 或人口数据挂到街道上的两种方式
拿到成都市乡镇街道行政区划 shp 文件之后,最常见的操作是把点数据或表格数据挂到街道上。点数据用空间连接,表格数据用属性关联。空间连接的核心是判断点落在哪个面里,geopandas 的sjoin一行搞定,但前提是两者的坐标系一致。我一般会先统一投影,再做连接:
import pandas as pd # 假设 poi_gdf 是已经读入的点数据,坐标系与街道一致 poi_gdf = gpd.read_file("poi.shp").to_crs(epsg=4544) township = gdf_proj.copy() # 空间连接:点落在哪个街道面内 joined = gpd.sjoin(poi_gdf, township, how="left", predicate="within") # 统计每个街道的点数量 count_by_town = joined.groupby("name").size().reset_index(name="poi_count") print(count_by_town.sort_values("poi_count", ascending=False).head(10))sjoin的predicate="within"表示点完全落在面内,how="left"保留所有点,没匹配上的点街道名为空。如果点刚好落在边界上,within可能判不进去,可以换成intersects,但那样边界上的点可能被两个街道同时匹配,需要去重。参数上,how控制保留哪边的记录,做统计一般用left保留全部点,做筛选可以用inner只留匹配上的。
3.2 属性关联:用行政区划代码做表连接
如果手头是 Excel 或 CSV 格式的人口、经济数据,用行政区划代码做表连接更稳。前提是 shp 里的代码列和表格里的代码列类型一致,都是字符串。我一般会先检查两边的代码长度和格式:
# 读取外部表格数据 df = pd.read_csv("township_population.csv", dtype={"code": str}) # 检查代码长度分布 print(df["code"].str.len().value_counts()) print(gdf["code"].str.len().value_counts()) # 做表连接 merged = gdf.merge(df, left_on="code", right_on="code", how="left") print(f"匹配失败数量: {merged['population'].isna().sum()}")dtype={"code": str}强制代码列为字符串,避免前导零丢失。两边长度分布要一致,比如都是 9 位或 12 位,不一致说明有一方截断了。merge之后看population列的空值数量,就是没匹配上的街道,通常是代码格式不一致或名称有出入。这一步的坑在于,有些表格里的代码是数值型,读进来变成整数,前导零没了,长度对不上,匹配全失败。
3.3 批量裁剪与分区统计:以成都市中心城区为例
做分析时经常只需要中心城区几个区的街道,或者按区县分组统计。用clip做裁剪,用dissolve做合并。比如只保留锦江、青羊、金牛、武侯、成华五个区的街道:
# 中心城区区县名称列表 central_districts = ["锦江区", "青羊区", "金牛区", "武侯区", "成华区"] # 筛选 central = gdf_proj[gdf_proj["district"].isin(central_districts)] # 按区县合并,得到区级边界 district_boundary = central.dissolve(by="district", aggfunc="sum") print(district_boundary[["name", "area_km2"]]) # 保存结果 central.to_file("chengdu_central_township.shp", encoding="utf-8")isin做列表筛选,dissolve按区县字段合并面,aggfunc="sum"会把面积字段加总。保存时encoding="utf-8"保证中文属性不乱码。这一步的实用价值在于,很多分析只需要特定区域,全量数据拖进去又慢又乱,先裁再算能省不少时间。
4. 避坑与排查:成都市乡镇街道 shp 文件最常见的五个翻车现场
4.1 中文乱码:属性表打开全是问号
现象:用 ArcGIS 打开 shp 后,属性表里的街道名称显示为乱码或问号。原因:shp 的.dbf文件默认编码是 GBK 或 Latin-1,而 QGIS 或 Python 按 UTF-8 读取。解决:在 QGIS 里右键图层设置编码为 GBK,或者用 Python 读取时指定encoding="gbk",导出时统一转成 UTF-8。我一般会在第一次读取时就试gbk,如果报错再换utf-8。
4.2 面积算出来是负数或零
现象:投影后计算面积,结果出现负数或零。原因:几何无效,比如自相交导致面积计算异常,或者面方向反了。解决:先用buffer(0)修复自相交,再重新计算面积。buffer(0)是 shapely 里修复几何的常用技巧,能把自相交的面变成有效面。
gdf_proj["geometry"] = gdf_proj.geometry.buffer(0) gdf_proj["area_km2"] = gdf_proj.geometry.area / 1e64.3 空间连接时点大量丢失
现象:sjoin之后很多点的街道名为空。原因:坐标系不一致,或者点落在街道之间的缝隙里。解决:先确认两者坐标系一致,再用intersects替代within,或者先对街道面做微小缓冲(比如 0.5 米)再连接。缓冲会引入微小误差,但能救回边界上的点。
4.4 行政区划代码匹配失败
现象:表连接后大量空值。原因:代码列类型不一致,一边是字符串一边是数值,或者长度不同。解决:统一转成字符串,检查长度分布,必要时补零。成都街道级代码一般是 9 位或 12 位,补零到统一长度再匹配。
4.5 导出后坐标系丢失
现象:保存为 shp 后,再打开发现坐标系变成未知。原因:geopandas 保存时如果没有.prj文件,或者源数据没有 CRS。解决:保存前确认gdf.crs不为空,保存时 geopandas 会自动写.prj。如果源数据没有 CRS,先用set_crs指定再保存。
5. 进阶用法:用街道边界做网格聚合与可视化验证
5.1 把街道边界转成网格统计单元
做热力图或格网分析时,街道边界可以直接作为聚合单元。但如果街道面积差异大,比如山区乡镇面积是城区街道的几十倍,直接按街道统计密度会失真。我一般会做一个 500 米 × 500 米的渔网,再用街道边界裁剪,得到每个网格所属的街道,这样既能保留街道属性,又能做精细化密度分析。
import numpy as np from shapely.geometry import box # 获取街道边界的范围 minx, miny, maxx, maxy = gdf_proj.total_bounds # 生成 500 米网格 cell_size = 500 cols = np.arange(minx, maxx, cell_size) rows = np.arange(miny, maxy, cell_size) cells = [box(x, y, x + cell_size, y + cell_size) for x in cols for y in rows] # 转成 GeoDataFrame grid = gpd.GeoDataFrame({"geometry": cells}, crs=gdf_proj.crs) # 用街道边界裁剪网格,保留街道属性 grid_with_town = gpd.overlay(grid, gdf_proj[["name", "geometry"]], how="intersection") print(f"网格数量: {len(grid_with_town)}")total_bounds返回边界范围,box生成矩形网格,overlay做相交裁剪。how="intersection"只保留网格和街道重叠的部分。这样每个网格都带街道名称,后续按网格统计 POI 密度再汇总到街道,比直接按街道统计更细。
5.2 可视化验证:用 QGIS 快速检查边界质量
代码跑完不代表数据没问题,我习惯在 QGIS 里做最后一道视觉检查。把街道边界叠加到天地图或高德底图上,看边界是否贴合实际道路和河流。重点看三个地方:河流沿岸的街道边界、区县交界处的街道归属、以及飞地(比如高新区在武侯区里的飞地)。飞地是成都行政区划的一个特点,高新区、天府新区直管区都有飞地,如果 shp 里飞地归属错了,做分区统计时数据会串。QGIS 里用“按属性选择”快速定位飞地,再和官方区划图对照。
5.3 一个我踩过的坑:别用旧版边界做新数据统计
最后说一个血泪教训。成都市乡镇街道行政区划调整比较频繁,比如乡镇改街道、区县托管调整,旧版 shp 的边界和代码可能和新版统计数据对不上。我有一次用 2019 年的街道边界去挂 2023 年的人口数据,结果几个新设街道没有边界,数据全挂到老街道上,统计结果偏差很大。后来养成习惯:拿到 shp 先看边界年份,和统计数据的年份对齐,对不上就找新版边界,或者用区县级别做过渡。这个习惯帮我省了很多后悔药。希望帮到你。
本文还有配套的精品资源,点击获取