简介:面向地理信息、城乡规划与基层治理从业者,这份压缩包提供苏州市下辖全部乡镇、街道级行政区划边界矢量数据。数据采用标准ESRI Shapefile格式,包含shp几何主文件、shx索引、dbf属性表、prj投影定义、cpg字符集及xml元数据等16个完整组件,压缩包仅552KB,便于快速传输与直接加载。坐标系以WGS84或CGCS2000为准,属性表含乡镇街道名称与代码字段,可用于GIS出图、空间查询、人口统计和应急调度等精细场景。压缩包内附一张预览图与简易处理脚本,方便核对边界效果;需注意混有南京市同级数据副本,主用文件以“苏州市”开头、无“- 副本”标记,筛选后即可安全使用。目前已有120人学习,适合需要精细行政单元底图的GIS使用者快速取用。
1. 一套苏州乡镇街道SHP边界数据,为什么建议先体检再使用
拿到一套“苏州全市乡镇街道边界矢量数据(含SHP完整文件组)”,很多人第一反应是直接拖进 GIS 软件里看个大概。我的习惯相反:先在文件层面做两分钟体检,确认坐标系、属性字段、字符编码、几何质量四个方向都正常,再谈后续分析。否则等数据进了可视化大屏或统计报表才发现偏差,返工成本远比想象中高。这套 SHP 数据适合三类人:做苏州区划维度聚合、统计、选址分析的从业者;做地图可视化大屏的前端开发;以及做配送分区、网格化管理等业务的后端工程师。数据本身不产生价值,和业务数据对齐才产生价值,而对齐的前提就是这套边界数据本身是干净的。
2. SHP完整文件组:文件职责、快速体检与拓扑检查
SHP 从来不是单个文件,而是一组文件。标题里“完整文件组”这几个字,恰恰是数据能不能用的关键。很多人只拿到一个 .shp,软件里打开要么报错,要么坐标完全对不上,问题往往出在配套文件丢失。这一章先从文件职责讲起,再用命令做一次快速体检,最后补一句几何拓扑检查,把基础打牢。
2.1 三件套与附属文件:谁丢了会出什么事
一个规范的 SHP 数据组,核心是三个文件:.shp 存几何坐标,.dbf 存属性表,.shx 是几何和属性之间的索引。三个文件缺一不可:缺 .shx,软件还能扫描重建,只是打开变慢;缺 .dbf,属性表全部丢失,名称和区划代码就没了;缺 .shp,整个图层都无法显示。除了三件套,文件组里经常还有几个容易被忽略的附属文件:
- .prj:用 WKT 文本描述坐标系,是判断投影和地理坐标的关键。这个文件在传输时最容易漏,漏了之后软件默认按无坐标系处理,叠加到其他数据上就会出现偏移错位。
- .cpg:纯文本文件,记录 .dbf 属性表的字符集,内容通常写着 UTF-8 或 GBK。没有它,中文属性字段在大部分软件里打开就是乱码。
- .sbn/.sbx:空间索引文件,由部分桌面 GIS 软件生成,不是必需;没有它软件会自己重建,只是首次打开稍慢。
我收到压缩包的第一件事,就是检查扩展名清单,确认 .prj 和 .cpg 是否齐全。判断逻辑很简单:连 .prj 都不带的数据包,不是来源不正规,就是被压缩工具吞了文件;后者还可以补救,前者基本可以直接放弃。
2.2 用 ogrinfo 做一次快速元信息体检
检查完文件组成,下一步是用命令行工具读一下元信息,比拖进软件里看更直观、更省事。命令如下:
# -so 只输出摘要信息,-al 表示读取全部图层 ogrinfo -so -al /path/to/suzhou.shp如果文件组织和字段定义正常,输出大致是这样(示例输出,实际以你的文件为准):
INFO: Open of 'suzhou.shp' using driver 'ESRI Shapefile' successful. Layer name: suzhou Geometry: Polygon Feature Count: 108 Extent: (...) - (...)输出里重点看四件事:图层名、几何类型、要素数量和坐标系统。几何类型应该是 Polygon 或 MultiPolygon,如果出现 MultiPoint 或 LineString 混入,说明原始数据不纯,后续做空间关联会出怪结果。要素数量也能说明问题:苏州全域的乡镇街道按正常行政区划口径统计,数量应该在百的量级。如果只有几十个,大概率只覆盖了主城区;如果上千个,就要怀疑混入了村级边界或地块数据。坐标系统那一行,标准数据会写出类似 CGCS2000 的完整 WKT;如果显示未命名或直接没有坐标系,基本可以判断这套数据是第三方手工绘制的,只能在图上看看,不能作为统计底图。
去掉 -so 再看一遍字段列表,目的是确认属性表结构。我一般只看两列:名称字段和 12 位区划代码字段。只有名称没有代码的版本,通常是从地图上人工勾的边,做不了按区县、按街道的统计关联,价值有限。
2.3 几何拓扑检查:在源头把脏数据拦下来
元信息没问题,不代表几何本身没问题。乡镇街道边界这类面数据,最常见的几何问题是自相交、重复顶点和微小缝隙。自相交多边形在面积计算时会算出负值或明显异常的结果,这在后续统计阶段极难察觉。
-- 统计无效几何要素的数量,返回 0 表示全部通过 SELECT count(*) AS invalid_cnt FROM suzhou_township WHERE ST_IsValid(geom) = false;上面这条 SQL 运行在空间数据库里,ST_IsValid 能识别自相交、环退化等常见几何错误。如果返回值不是 0,先别急着做任何转换,先用空间数据库的 ST_MakeValid 或桌面 GIS 软件的修复几何工具批量处理一遍,再把修复后的版本重新导出为一份新的 SHP,作为后续所有工作的起点。我处理类似边界数据时,一直坚持“源数据阶段就把拓扑清零”这个习惯,后面做边界匹配、空间连接、缓冲区分析时能省掉大量排错时间。
3. 从SHP到GeoJSON:重投影、编码与简化的三条可复现路径
这是问得最多的一环:数据到手是 SHP,前端要的是 GeoJSON,而且必须是 WGS84 经纬度坐标。直接改后缀名是行不通的——SHP 里的坐标是投影坐标,单位是米,直接改后缀只会得到一套“百万级数字”的假 GeoJSON。正确路径是:确认源坐标系,重投影,再导出。
3.1 确认源坐标系:是投影坐标还是地理坐标
苏州位于东经 120 度附近,正式来源的 SHP 数据通常采用 CGCS2000 基准下的 3 度带高斯-克吕格投影,中央经线 120 度,对应投影坐标,单位是米。打开 .prj 文件看一眼就能判断:里面出现 PROJCS 关键字就是投影坐标,出现 GEOGCS 就是地理坐标。如果 .prj 里写的是北京 1954 或西安 1980 这类老坐标系,就不能直接重投影了,得先做转换参数,否则误差会以米为单位累积。判断方法很土,但很管用:把 .prj 内容复制到文本编辑器里,逐行读一遍,比在软件里点来点去要直观得多。
拿到投影坐标 SHP 之后,要明确两个目标输出:一份米制投影数据,留在桌面 GIS 和数据库里做面积计算、空间分析;一份经纬度数据,输出成 GeoJSON 供 Web 端展示。两套数据不要混用,这是基本纪律。
3.2 ogr2ogr 转换:一条命令和三个关键参数
命令行转换用 ogr2ogr,一步到位完成重投影、编码处理、格式转换:
# 读取GBK编码的dbf属性表,把投影坐标转成WGS84经纬度,输出GeoJSON ogr2ogr -f GeoJSON suzhou_wgs84.geojson suzhou.shp \ --config SHAPE_ENCODING GBK \ -t_srs EPSG:4326 \ -lco COORDINATE_PRECISION=6参数逐个说:--config SHAPE_ENCODING GBK表示按 GBK 编码读取 .dbf 属性表;如果 .cpg 文件内容本身就是 GBK,这行可以省略。我的习惯是导出后发现中文乱码,再回来加这个参数重跑。-t_srs EPSG:4326把输出坐标转成 WGS84 经纬度,这是 Web 端和绝大多数开源可视化库的通用格式。刚才在 3.1 里说的那套米制版本,把目标坐标系换成 EPSG:4547 再跑一次即可,两个版本各存各的。-lco COORDINATE_PRECISION=6把经纬度保留 6 位小数,大约对应 0.1 米级别的坐标精度,对乡镇街道边界完全够用,还能把 GeoJSON 文件体积压低不少,页面加载更快。
命令跑完,用上一章的 ogrinfo 命令检查输出文件的坐标范围。苏州的经纬度应该在东经 120 度、北纬 31 度附近;如果看到几十万开头的数字,说明重投影没生效,问题多半出在源数据坐标系缺失。
3.3 简化边界:性能和拓扑之间的取舍
未经简化的全要素 SHP 直接丢给前端,大屏首屏能卡好几秒。常见做法是输出一份简化版本给预览和首屏加载用。ogr2ogr 自带简化参数:
# 对已转成经纬度的GeoJSON做简化,容差按当前坐标系单位计算 ogr2ogr -f GeoJSON suzhou_simplify.geojson suzhou_wgs84.geojson \ -simplify 0.0005注意此时数据坐标是经纬度,0.0005 度对应大约 50 米。这个参数用的是经典简化算法,它对每个多边形独立处理,简化后相邻乡镇街道的公共边可能产生细小缝隙,放大看就是一条条白线。血的教训是:简化要尽早做,而且要在米制投影坐标系里做。我更推荐在空间数据库里用保拓扑的简化函数,按 5 米、10 米、20 米分档处理,再转成经纬度导出:
-- 在米制投影坐标下按10米容差做保拓扑简化,避免相邻边界产生缝隙 UPDATE suzhou_township SET geom = ST_SimplifyPreserveTopology(geom, 10);这样导出的数据无论在哪一层缩放,都不会出现交叉和裂隙。简化的另一个价值是文件体积:容差 10 米的版本比原始数据往往能小一半以上,对 Web 传输非常友好。
3.4 Python 处理路径:用 geopandas 兜底
命令行不是唯一选择,Python 的 geopandas 库更灵活,适合需要顺带做字段处理和数据筛选的场景:
import geopandas as gpd # 读取SHP,encoding参数对应.dbf编码,出现乱码时改成"gbk" gdf = gpd.read_file("suzhou.shp", encoding="utf-8") # 源数据没有坐标系信息时手动补上,防止后续重投影出错 if gdf.crs is None: gdf.crs = "EPSG:4547" gdf_wgs = gdf.to_crs("EPSG:4326") gdf_wgs.to_file("suzhou_wgs84.geojson", driver="GeoJSON")read_file 的 encoding 参数就是给 .dbf 用的,刚才命令行里的乱码问题在这里改成 gb 编码重读一次即可。关键是 crs 为空的情况:很多第三方加工的 SHP 没有元数据,不手动指定 EPSG:4547,to_crs 不会报错,但输出的坐标全部是错的。如果属性字段名带空格或中文,导出前用 rename 处理一遍,前端联调阶段会省很多沟通成本。
4. 坐标系、ADCODE与面积口径:边界数据对齐业务的三个关键
边界数据要跟业务数据对齐,绕不开三个关键点:坐标系和底图的关系、区划代码的语义、面积统计的口径。这三个点任何一个没对齐,后面全盘皆输。
4.1 坐标系:明文坐标和商业底图之间差着一层加密
苏州这套乡镇街道边界,正式来源的坐标系一般标注为 CGCS2000。这里有个细节,很多刚开始接触边界数据的同学不清楚:CGCS2000 和 WGS84 在地心框架上的差异只有厘米到亚米级,对乡镇尺度边界的视觉展示几乎无差别。真正影响叠加体验的是国内商业地图平台普遍采用的 GCJ-02 加密坐标系。把未加密的 SHP 直接叠加到商业平台底图上,整体偏移数百米是常态,这不是数据错了,而是两套坐标框架没有对齐。
| 数据坐标系 | 底图坐标系 | 能否直接叠加 | | CGCS2000 / WGS84 | 官方地理信息平台或开放地图服务 | 可直接叠加 | | CGCS2000 / WGS84 | 商业地图平台(加密坐标) | 需先做加密坐标偏移 | | GCJ-02 | CGCS2000 / WGS84 底图 | 需先做逆偏移 |
判断坐标系是否加密的最简单办法,是取几个街道边界的明显拐点,和底图上的同位置地物对比,看偏移方向是否一致、偏移量是否接近常数。如果偏移量在不同区域明显不同,就不是简单平移能解决的。遇到加密坐标系底图,用现成的坐标转换库做整体转换,不要手动平移数据——手动平移在局部看起来对了,换一个区就歪掉。
4.2 ADCODE 字段:用代码做关联,别拿名称硬碰
乡镇街道 SHP 属性表里一般会有一个标准 12 位区划代码字段,语义是:前 2 位省级,前 4 位地级市,前 6 位区县,后 6 位乡镇街道。因为苏州全域包含下辖县级市,这套数据里前 4 位并不完全相同,聚合时用字符串截取,就能自然拼出一棵从市级到街道的三级目录。
有一个真实的坑:功能区代管。苏州有几类功能区实际管辖多个街道,但统计用区划代码可能挂在另一个行政单元名下,或者代码和实际管理口径不一致。按名称关联业务表一定会出问题——同一个名称可能挂在两个不同代码下,或者代码与业务系统里的行政区划表对不上。稳妥做法是维护一张“代码到业务口径”的映射表,政府统计口径以标准代码为准,业务系统口径建别名关联。
-- 按标准代码前6位做区县维度聚合,业务表只需包含完整12位代码 SELECT substr(adcode, 1, 6) AS district_code, sum(population) AS pop_sum FROM township_data GROUP BY substr(adcode, 1, 6);这段 SQL 想表达的核心是,只要业务表里有 12 位代码,任何层级的统计都不用改表结构,截位即可。这也是我始终坚持把 ADCODE 当成关联主键的原因。
4.3 面积口径:椭球面积和几何面积不是一回事
在桌面 GIS 里计算乡镇面积,字段计算器里常见的两个函数会给出不同答案。其中一个函数直接对几何坐标做平面计算:如果图层是米制投影坐标,算出来就是平方米;如果图层是经纬度,得到一串没有任何物理意义的“平方度”。另一个函数按当前项目的椭球设置做测地线面积计算,单位取决于项目配置,和图层坐标系无关。
两种算法的误差在乡镇尺度上通常有百分之几的差异。我的建议是:凡是进入统计报表的面积,统一用椭球面积,并把项目坐标系固定为 CGCS2000;凡是只用于图上尺寸比较的场景,用米制投影坐标下的平面面积即可。最忌讳的是两种算法在一个项目里混用,最后汇总对不上账,这种问题在验收阶段极难排查,因为只差几个百分点,不一定有人较真。
4.4 区划代码版本:数据年份要跟着数据一起走
行政区划代码不是永久不变的,撤并乡镇、街道改设、功能区调整,都会导致代码变化。一套边界数据在手,除了看坐标系和字段,还要确认它的发布年份和区划代码版本。跨年份对比分析时,如果不注意版本,会出现“去年还能统计,今年找不到这个街道”的怪现象。我处理这类数据时,习惯在文件名里带上年份和坐标系,比如统一命名为“乡镇街道边界_年份_坐标系”的格式。半年后再翻出来用,只看文件名就知道这套数据的适用口径,不用重新回忆和排查。
5. 避坑清单:SHP边界数据最常见的五个翻车现场
这一章直接讲问题。每一条都是我在处理类似边界数据时真实踩过的或者帮别人排过的坑,按“现象、原因、解决”三部分写,方便对照。
5.1 属性表中文全部变成乱码
现象:把 SHP 拖进软件,名称字段显示成“锟斤拷”、问号或拼音,完全没法读。原因:.dbf 属性表实际编码是 GBK,但文件组里没有 .cpg,软件按 UTF-8 去读,解码就错了。解决:在数据源设置里把字符集手动改成 GBK 重新加载;批量处理时用命令行加编码参数重读一遍,输出成 UTF-8 的新文件,并把 .cpg 写入新文件组,避免二次传播继续乱码。判断编码还有一个土办法:用文本编辑器直接打开 .dbf 的字段值,看到中文正常就是 GBK,看到一堆乱码再试 UTF-8。
5.2 叠加商业底图时整体偏移数百米
现象:SHP 里的街道轮廓和商业地图平台底图上的地标错开几百米,边界内部形状看起来完全正常,缩放也不改善。原因:源数据是明文坐标,商业底图是加密坐标,两套坐标框架不同。解决:先确认底图来源。如果是商业平台底图,对源数据统一做加密坐标偏移后再叠加;如果是官方地理信息平台这类非加密服务,则不需要处理。注意不同平台的加密算法并非完全一致,换一个底图就要重新验证一次叠加效果。
5.3 按街道统计面积,结果和官方口径对不上
现象:同一街道,自己从 SHP 算出的面积比公开资料大或小百分之几到十几。原因:投影坐标系不同导致面积变形,混用了不同面积函数,或者忽略了水域和飞地。解决:统一在 CGCS2000 米制投影下用椭球面积计算,并且固定软件项目的椭球设置;跨部门对比时,先确认对方公开面积的口径是否剔除了水域,是否包含飞地。飞地是这个坑里最麻烦的部分——如果一套边界数据里某个街道包含几十块不相连的飞地图斑,统计前一定要说清楚算不算飞地面积。
5.4 简化导出后,相邻街道出现缝隙或重叠
现象:预览版 GeoJSON 在 Web 端放大到街道级别,相邻边界之间出现白线缝隙,或者局部互相覆盖,视觉效果一塌糊涂。原因:简化算法对每个多边形独立处理,公共边被两边各自简化,拓扑被破坏。解决:简化务必使用保拓扑的简化函数,在米制投影坐标下完成,再导出经纬度版本;已经出问题的数据,用桌面 GIS 的修复工具做一次吸附处理。记住一条原则:简化永远在原始米制投影阶段做,不要等转成经纬度后再简化。
5.5 按名称合并街道时,一个街道拆成了两个要素
现象:按名称字段做聚合,某个街道出现两条重复要素,或者业务表里的街道名称在边界属性表里找不到。原因:功能区代管背景下,同名行政单元可能挂在两个不同代码下;也可能该街道已经撤并,而 SHP 用的是旧版区划。解决:一律用标准代码做合并键,不要拿名称做合并键。业务系统里如果只有名称,先建立“名称到代码”的映射表再关联。还有一种常见来源是两张图幅拼接,属性一致但几何没有合并,用代码做一次融合即可。
6. 进阶用法:基于ADCODE把边界数据做成可下钻的三级区划树
最后这一步,把理顺的边界数据变成能直接交付前端的结构。思路不复杂:ADCODE 天然就是分级码,前 4 位对应地市级,前 6 位对应区县级,后 6 位对应乡镇街道。我一般按代码前缀拆出两级图层,各导出一份 GeoJSON,前端按点击层级逐层请求,首屏只加载街道轮廓,下钻到区县时再加载区县边界。
import geopandas as gpd gdf = gpd.read_file("suzhou.shp", encoding="utf-8") # 按标准代码前6位溶解,得到区县级边界 district_gdf = gdf.dissolve(by=gdf["adcode"].str[:6]).reset_index() # 按完整12位代码溶解,合并同代码的多要素,得到乡镇街道级边界 township_gdf = gdf.dissolve(by=gdf["adcode"]).reset_index() district_gdf.to_file("suzhou_district.geojson", driver="GeoJSON") township_gdf.to_file("suzhou_township.geojson", driver="GeoJSON")dissolve 按代码聚合几何,能把同代码下的散碎片段自动合并成一张完整面,比前端做渲染合并可靠得多。导出前记得对区县层做一次保拓扑简化,乡镇层保留更高精度,同时保留一份原始全精度数据,作为后端空间查询的权威版本。前端拿到这两份 GeoJSON 后,按代码前缀匹配父级和子级,三级联动就完成了。
我最早做类似大屏项目时,把全要素未简化数据一股脑丢给前端,首屏等了快十秒,后来养成了一个习惯:原始库和交付库分开维护。原始库永远保留完整精度和全部属性,交付库按场景逐级简化、按字段裁剪。这个习惯帮我省掉了很多上线前的排查时间,也让我再没遇到过“页面加载慢不知道是数据问题还是代码问题”的尴尬。边界数据这类基础图层,坐标系、编码、代码字段这三件事处理干净了,后面的空间聚合和下钻联动基本就是按部就班的事。希望帮到你。
本文还有配套的精品资源,点击获取