简介:这份无锡市乡镇街道级矢量地图资源,面向GIS开发、城市规划与空间分析人员,提供无锡各区县与乡镇街道的行政区划边界数据,可直接用于地图绘制、区划统计、可视化展示或空间分析。压缩包共含21个文件,涵盖shp几何数据、dbf属性表、shx空间索引、prj投影坐标及xml元数据等完整配套格式,并额外整理区县与乡镇两级shp文件,方便按层级调用;包体仅275KB,轻量高效,适配ArcGIS、QGIS等常见平台。数据包含县区和乡镇两类图层,属性字段附有地名信息,便于关联业务数据。目前已有526人学习使用,数据经过整理校验,若使用中遇到问题可协助处理。资源适合需要快速获取无锡基层行政边界、开展地理数据可视化或作为研究底图的读者,能够节省自行搜集与配准的时间,提升开发与制图效率。
1. 无锡乡镇街道 shp 数据包,解决的不止是“找文件”
做城市尺度的项目时,区县级边界通常不够用。社区网格、15 分钟生活圈、土地利用现状分析,往往要求到乡镇街道一级。我接过好几个需求,对方拿到的无锡边界要么是市级轮廓,要么是网上拼来的区县边界,精度参差不齐。这个压缩包里同时包含无锡各乡镇街道和区县两个层级的 shapefile,还有配套的 prj、dbf、shx 等文件,意味着不需要再从天地图或统计年鉴里逐条描边界。适合正在写规划论文、做 GIS 课程设计,或者要在 BI 工具里做行政区划图的人。前提是得弄清坐标系和属性字段,否则后续叠路网、叠人口栅格都会错位。
2. shapefile 伴生文件与坐标系判读:拿到手先别急着拖进地图
2.1 从 .prj 到 .shp.xml:每个伴生文件都是线索
shapefile 不是一个单文件后缀,而是一个由 .shp、.shx、.dbf、.prj 等组成的文件族。这个压缩包里的文件对应得很整齐:无锡各乡镇.shp配的是无锡各乡镇.dbf、无锡各乡镇.shx、无锡各乡镇.prj、无锡各乡镇.sbn、无锡各乡镇.sbx、无锡各乡镇.shp.xml。无锡各区县.shp也有一整套,同时还有一份无锡.shp作为全市范围轮廓。下面对照文件后缀说明各自的作用。
| 后缀 | 作用 | 这个包里对应的文件 |
|---|---|---|
| .shp | 保存几何坐标和要素类型 | 无锡.shp、无锡各乡镇.shp、无锡各区县.shp |
| .dbf | 保存属性字段,每行对应一个要素 | 无锡.dbf、无锡各乡镇.dbf、无锡各区县.dbf |
| .prj | 保存坐标参考系统的 WKT/ESRI 字符串 | 无锡.prj、无锡各乡镇.prj、无锡各区县.prj |
| .shx | 几何索引,用于快速定位 | 无锡.shx、无锡各乡镇.shx、无锡各区县.shx |
| .sbn / .sbx | ArcGIS 生成的空间索引 | 无锡.sbn、无锡.sbx 等 |
| .shp.xml | 元数据,记录来源、坐标系和时间 | 无锡.shp.xml、无锡各乡镇.shp.xml 等 |
.sbn和.sbx是 ArcGIS 在做空间索引时生成的,QGIS 和大多数开源库都不依赖它们,缺了也能读;如果有,说明这份数据曾经在 ArcMap 里被打开并校验过,可信度会高一些。.shp.xml里一般用 ESRI 元数据格式记录字段含义和生成时间,文本打开可以看到<Esri><CreaDate>这样的节点。我拿到压缩包的第一件事,是先把 .prj 和 .shp.xml 都读一遍,而不是直接拖图层。
2.2 坐标系判读与常见误用
用文本编辑器打开 .prj,内容大致是两种形态:要么是 ESRI 的PROJCS字符串,要么是 OGC WKT 的GEOGCS块。无锡在 WGS84 下的经度范围约东经 119.5° 到 120.5°,纬度北纬 31.4° 到 31.7°。如果 .prj 里的坐标系是GCS_WGS_1984,那数据就是经纬度;如果出现PROJECTION["Transverse_Mercator"]或CGCS2000_3_Degree_GK_CM_120E,那就说明是投影坐标,单位是米。
用命令行快速查看:
# 打印 prj 文件内容,确认坐标系 WKT 字符串 cat 无锡各乡镇.prj这里cat直接输出坐标系字符串,重点看三点:GEOGCS里的基准面名称、DATUM是D_WGS_1984还是D_China_2000,以及是否存在PROJECTION和UNIT["metre"]。很多从网上下载的“镇街 shp”,.prj 是空的,或写的是GCS_Unknown,这种情况软件会把坐标当作经纬度显示,但实际可能是地方坐标系,叠到遥感影像上会偏移几十米到几百米。最简单的验证办法是加载一份已知正确的底图,比如天地图影像,看边界是否吻合。
投影坐标和地理坐标的混用是行政区划数据最常见的坑。投影坐标直接算面积、生成渔网、做缓冲区都可以用米,经纬度则需要用estimate_utm_crs或类似方法转投影,否则计算出来的面积单位是平方度,完全不能看。在 QGIS 的图层属性里看到 CRS 是 EPSG:4490(CGCS2000 地理坐标系)或 EPSG:4326,并不代表数据错了,只代表你是用经纬度展示;真正做分析前要再投影到适合无锡的 Gauss-Kruger 带。
2.3 用 pyogrio 快速读取属性与几何信息
直接拖进 QGIS 看属性当然可以,但写脚本能得到更确定的结果。我常用pyogrio读取,它的读写速度比传统geopandas默认引擎快,而且能直接拿到 CRS 和几何类型。以下代码可以在 Jupyter 里逐行跑:
import pyogrio path = "无锡各乡镇.shp" # 读取元信息,不加载几何,速度很快 info = pyogrio.read_info(path) print(info["crs"]) # 输出坐标系对象,None 就要警惕 print(info["geometry_type"]) # 应该输出 Polygon 或 MultiPolygon print(info["fields"]) # 列出所有属性字段名和类型 # 读取完整 GeoDataFrame,用于后续筛选 gdf = pyogrio.read_dataframe(path) print(gdf.columns.tolist()) print(gdf.head(3))这段代码先通过read_info拿到 CRS、几何类型和字段列表,不加载几何数据,文件大也能秒回;然后read_dataframe把要素读成 GeoDataFrame,方便看前几行和字段结构。尤其要看镇街名称字段到底叫XZQMC、town还是NAME,同一个来源的数据字段命名习惯不同,后面写筛选表达式时用错字段名会直接报 KeyError。
再看字段类型:
print(gdf.dtypes) if "XZQDM" in gdf.columns: # 行政区划代码转成字符串,截取前6位作为区县代码 code = gdf["XZQDM"].astype(str).str[:6] print(code.unique())这里.astype(str)是关键。很多 shp 的行政区划代码在 dbf 里被存成整数或浮点数,比如320205会变成320205.0,如果直接做字符串匹配就会漏掉所有乡镇。截取前6位可以识别区县,滨湖区是 320211,新吴区是 320214,这样后面用区县代码关联各乡镇数据时就不会出错。
3. 属性筛选、层级匹配与 shp 导出实战
3.1 用字段筛选出指定镇街
拿到数据后最常见需求是“只要某几个街道”。在 QGIS 中可以直接打开属性表,用表达式筛选。如果字段名是XZQMC,表达式就是:
"XZQMC" LIKE '%华庄%'这条 SQL 表达式会在字段XZQMC中模糊匹配包含“华庄”的记录,选中后右键图层可以导出“保存所选要素”。但从重复性任务角度看,用 Python 脚本更稳定,特别是需要按一批名称筛选时。例如筛选华庄、雪浪两个街道:
import geopandas as gpd gdf = gpd.read_file("无锡各乡镇.shp") # 用正则匹配街道名称,两个名称用 | 分隔 selected = gdf[gdf["XZQMC"].str.contains("华庄|雪浪")].copy() selected.to_file("华庄雪浪.shp", driver="ESRI Shapefile", encoding="utf-8")代码中str.contains默认支持正则,华庄|雪浪表示包含任意一个即通过;.copy()避免后续修改影响原数据。to_file里的encoding="utf-8"是为了让 dbf 里的中文在 Excel 里不乱码,如果编码有误,下次打开属性表会看到一堆问号。这里对应很多人在“shp 文件下载”之后的第一类需求,本质是属性筛选,不涉及空间裁剪;如果要按范围裁剪,应该用clip操作。
3.2 区县与乡镇的层级匹配
包里既有无锡各区县.shp也有无锡各乡镇.shp,做统计时经常需要把乡镇挂到所属区县上。最直接的是属性表里如果有区县代码字段,直接用left join。如果没有,就需要用空间连接。常见做法是用sjoin先做相交,再处理边界归属:
towns = gpd.read_file("无锡各乡镇.shp") counties = gpd.read_file("无锡各区县.shp")[ ["XZQMC", "geometry"] ].rename(columns={"XZQMC": "区县名"}) # 用空间连接给乡镇加区县属性 joined = gpd.sjoin(towns, counties, how="left", predicate="intersects") print(joined[["XZQMC", "区县名"]].head())sjoin的predicate="intersects"会判断乡镇面与区县面是否相交,index_right是右侧表命中的索引,可以用来关联区县名称。边界上的乡镇可能出现同时命中两个区县的重复行,这时最好用质心归属法来保证每个乡镇只属于一个区县:
# 用乡镇质心落在哪个区县内来确定归属 towns_copy = towns.copy() towns_copy["geometry"] = towns.geometry.centroid joined_point = gpd.sjoin( towns_copy, counties[["区县名", "geometry"]], how="left", predicate="within", )这里先复制一份数据,再把 geometry 替换为质心,随后用within找包含该质心的区县。sjoin的how保持"left",这样不会有乡镇因为质心稍微越过边界而丢失。需要注意的是,质心法可能把狭长乡镇归错,如果乡镇形状特别不规则,建议结合centroid和representative_point两个结果对照。实际上,对于同一套底图切割出来的区县和乡镇,边界完全吻合,直接用属性代码关联更稳,空间连接只是兜底方案。
3.3 导出为 KML、GeoJSON、TXT 的常用参数
很多场景并不直接用 shp:Web 前端要 GeoJSON,导航软件要 KML,批量导入数据库要 TXT/CSV。用 GDAL 自带的ogr2ogr可以一次完成:
# 输出为 GeoJSON,并强制转成 WGS84 经纬度 ogr2ogr -f GeoJSON 无锡乡镇.geojson 无锡各乡镇.shp -t_srs EPSG:4326 # 输出为 KML,Google Earth 可直接打开 ogr2ogr -f KML 无锡乡镇.kml 无锡各乡镇.shp # 属性表导出为带坐标的 TXT(逗号分隔) ogr2ogr -f CSV 无锡乡镇.txt 无锡各乡镇.shp -lco GEOMETRY=AS_XY这三行命令分别处理不同输出格式。-t_srs EPSG:4326表示输出坐标参考系统,避免 GeoJSON 里残留投影坐标;-f KML不需要指定-t_srs,KML 标准强制经纬度;-lco GEOMETRY=AS_XY是 CSV 的图层创建选项,它会在每行末尾追加 X 和 Y 坐标。网上常见的“shp转txt”多数情况用的就是这个命令,但请注意这样导出的 TXT 只保留属性加坐标点,并不包含面边界;如果后续要画完整的多边形,应该用 GeoJSON 或 GeoPackage。
顺便说一句,如果需要 shp 批量转 KML,Shell 循环就能搞定:
for f in *.shp; do # 去扩展名后生成同名 KML 文件 name=$(basename "$f" .shp) ogr2ogr -f KML "${name}.kml" "$f" done循环里用basename去掉后缀,再拼接.kml,避免文件名重复;ogr2ogr每次都独立读入一个 shp,输出对应的 KML。如果文件很多,Windows 下建议在 Git Bash 或 WSL 里运行,避免批处理转义问题。
| 输出格式 | driver 名称 | 关键参数 | 典型用途 |
|---|---|---|---|
| GeoJSON | GeoJSON | -t_srs EPSG:4326 | Leaflet / ECharts 渲染 |
| KML | KML | 无,输出经纬度 | Google Earth、导航设备 |
| CSV/TXT | CSV | -lco GEOMETRY=AS_XY | 报表、数据分析 |
| GeoPackage | GPKG | -nlt PROMOTE_TO_MULTI | 本地地图包、PostGIS |
-nlt PROMOTE_TO_MULTI在转 GPKG 时推荐加上,因为 shapefile 允许 Polygon 与 MultiPolygon 混存,而部分数据库或后端不接受混合几何;强制提升为 MultiPolygon 后能避免导入 PostGIS 时报 “Geometry type mismatch”。
4. 无效几何排查与批量压缩、转 3dtiles
4.1 先查拓扑,再谈转换
许多行政区划 shp 在人工编辑边界时会产生自相交、重复顶点、无效环等问题。直接拿去转 GeoJSON 还能用,但转 3dtiles 或做空间计算时会出现空洞、面积异常。先用ogrinfo快速确认几何类型:
# -so 只输出图层摘要,过滤出几何类型行 ogrinfo -so 无锡各乡镇.shp 无锡各乡镇 | grep "Geometry"这条命令输出几何类型,如果显示Polygon是正常的,如果出现Geometry Collection或空值,就要小心了。更严格的自相交检测要用 GEOS 的is_valid:
import geopandas as gpd gdf = gpd.read_file("无锡各乡镇.shp") # 找出无效几何并统计数量 invalid = gdf[~gdf.geometry.is_valid] print(invalid.shape[0], "个无效几何") # 用 buffer(0) 重建有效几何,修复自相交 gdf["geometry"] = gdf.geometry.buffer(0) print(gdf.geometry.is_valid.all())buffer(0)是修复 shp 无效几何的经典做法:它会把自相交环重新构建为有效多边形,代价是可能抹掉极小缝隙。执行后再用is_valid.all()确认是否全部恢复。这一步对后续 shp 转 3dtiles 很关键,因为 3dtiles 要求闭合且无重叠,自相交面会在三角化过程中报错。如果无效要素数量很多,建议先gdf = gdf[gdf.geometry.is_valid]剔除坏数据,不要盲目修复。
常见几何错误与处理方式如下表:
| 错误类型 | 现象 | 处理办法 |
|---|---|---|
| 自相交(Self-intersection) | is_valid=False,转 KML 出现破损 | buffer(0) |
| 重复顶点 | 面积统计无异常但渲染错乱 | QGIS 的 Delete Duplicate Geometries |
| 缝隙 | 相邻面之间有空白 | Union/ fill gaps |
| 混合几何 | Polygon 和 MultiPolygon 混用 | -nlt PROMOTE_TO_MULTI或dissolve |
表格中列出的四类问题里,混合几何最容易在导出时爆错,因为它不是拓扑错误而是类型问题。处理方式可以统一转 Multi,也可以在导出时由 GDAL 自动提升。
4.2 单个 shp 如何批量压缩
行政区划 shp 即使一个乡镇只有几百个顶点,整个无锡的文件也可能到几十 MB。发布为地图服务前,通常要做节点简化。用mapshaper能快速简化边界:
# 使用 Douglas-Peucker 算法,保留约20%的节点 mapshaper 无锡各乡镇.shp -simplify dp 20% -o 无锡乡镇_simplified.shpdp指定算法,20%意思是保留 20% 的节点数。简化后视觉上几乎不变,但文件体积大幅下降。需要注意简化算法不感知拓扑,多个相邻镇街各自简化后可能出现微小缝隙,矢量切片服务会容忍,但做面积统计时不要用简化后的数据。
如果要一次处理多个 shp,用 Shell 循环:
for f in *.shp; do # 提取不带扩展名的文件名 name=$(basename "$f" .shp) mapshaper "$f" -simplify dp 30% -o "out/${name}_simplified.shp" done这个循环会把当前目录所有 shp 文件逐个简化,输出到out目录;basename用于去掉扩展名,避免拼接文件名时重复.shp.shp。批量压缩后的文件可以直接发布为本地图层包,也可以再转成 GeoPackage 这种单文件格式来管理。
4.3 从 shp 到 3dtiles 的典型链路
网上经常看到“shp转3dtiles”的需求。直接转 3dtiles 的工具其实都走了同一条中间路径:先把 shp 转成 GeoJSON,再用 Cesium 相关工具或pg2b3dm切瓦片。步骤通常是:
# 第一步:shp 转 GeoJSON,统一用 WGS84 经纬度 ogr2ogr -f GeoJSON 无锡乡镇.geojson 无锡各乡镇.shp -t_srs EPSG:4326 # 第二步:用 3d-tiles-tools 生成 3dtiles 瓦片目录 npx 3d-tiles-tools --input 无锡乡镇.geojson --output 无锡乡镇_3dtiles这里的第二步依赖 Node.js 环境,npx会自动下载工具。参数上要注意输入 GeoJSON 必须是经纬度,且要素类型必须统一为 Polygon,不能把 MultiPolygon 混在一起,否则工具会报 “unsupported geometry type”。所以转换前先做dissolve或统一提升为 MultiPolygon。另外,如果只是做可视化,不需要真的转 3dtiles,可以先转成 GeoJSON 用 Mapbox 加载,性能差别不大;3dtiles 更多是给 Cesium 场景或大体量数据用。
4.4 与其他空间数据叠加时的坐标系对齐
做道路分析时,网上搜来的“2020全国道路shp下载”“遥感道路shp文件”大多是 WGS84 或 Web Mercator 投影,而行政区划可能是 CGCS2000 或西安80。不能直接叠加,必须先统一 CRS。我的做法是先把道路数据转到乡镇数据的坐标系:
# 将道路数据转换为 CGCS2000 地理坐标系 ogr2ogr -t_srs EPSG:4490 道路_cgcs2000.shp 道路.shpEPSG:4490 是 CGCS2000 地理坐标系,和 WGS84 在无锡区域差异只有几十厘米,如果只是叠加展示,可以忽略;但和栅格做像元统计时,最好转到当地投影坐标,比如 EPSG:4538(CGCS2000 / 3-degree Gauss-Kruger CM 120E),这样面积计算才准确。生成渔网分割 shp 时也一样,先统一 CRS 再切,否则网格和边界会互相偏移。地下水位栅格这类数据自带的 .prj 往往也是经纬度,用read_csv读出来再转 GeoDataFrame 时要显式设置 CRS,否则默认为 None,后续空间操作直接报错。
5. 验证一份镇街边界数据是否可信的三个土办法
5.1 叠合影像:看边界是否“贴”着建成区
将无锡各乡镇.shp加载进 QGIS,底图选择 XYZ Tiles 中的天地图影像。正常情况下,乡镇界应该沿着河道、公路中心线或山脊延伸,和影像上的明显地物重合。如果边界与影像中村庄边缘交叉错乱,说明数据经历了不正确的坐标变换,或者数字化精度不足。验证时把图层透明度调到 50%,对比 1:50000 视图下的吻合程度即可。
5.2 拓扑自检:检查重复和缝隙
使用 QGIS 的 Topology Checker 插件,添加must not have gaps和must not have duplicates规则到乡镇层。无锡乡镇边界应是无缝铺满全区,任意两个乡镇之间不应有空隙;有缝隙说明数据在裁剪时丢失了窄带区域。处理办法是用fill gaps工具把缝隙分配到相邻要素,或者回到buffer(0)修复。对“无锡各区县.shp”也要用同一套规则检查,因为区县边界应完全贴合乡镇外边界。
5.3 面积对齐:和统计年鉴比大小
一个简单有效的验证方法,是把乡镇面积和统计公报中的面积对比。用 SQL 查询到每个乡镇的面积即可:
-- 将几何转成 geography 计算平方千米面积 SELECT name, ST_Area(geom::geography) / 1000000.0 AS area_km2 FROM wuxi_towns ORDER BY area_km2 DESC;这里把几何转成geography是为了在数据库中直接得到以米为单位的面积结果,除以 1000000 得到平方千米。如果直接在平面坐标系下用ST_Area(geom),返回结果取决于坐标系单位,可能是度,不是米。对比时允许 1%~2% 的误差,超过 5% 需要考虑是否有区域归属争议或数据扫描变形。另外,用统计年鉴的户籍人口做离线检查时,可以用乡镇面积乘以平均人口密度大概判断,但不要反过来用面积去反推人口,那会掩盖内部差异。
本文还有配套的精品资源,点击获取