ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

2017县级行政区shp数据处理全流程:坐标系、编码与入库避坑指南

2017县级行政区shp数据处理全流程:坐标系、编码与入库避坑指南

简介:这份资源是面向GIS从业者、地理数据分析人员及规划相关专业学生的1:100万比例尺中国2017年县级行政区矢量边界数据集,可用于人口统计、城市规划、环境保护、灾害管理与交通规划等空间分析场景,帮助读者快速获得统一、规范的县级行政单元底图。压缩包共7个文件,约46.33MB,以shp主文件承载几何图形,dbf存储县名、代码等属性,prj定义CGS_WGS_1984坐标系,shx、sbn、sbx提供空间索引以加速检索,xml记录元数据,配套结构完整,便于直接加载与二次处理。目前已有615人学习下载。读者可据此完成行政区划叠加分析、专题制图与空间统计,省去边界数据搜集与格式转换的繁琐环节,为后续与人口、地形、气候等数据融合提供可靠基础。

1. 拿到一份 2017 县级行政区 shp,先别急着往 GIS 里拖

做地理信息这行,最怕的不是数据缺,而是数据“看着全、用起来错”。上周有个做县域商业选址的朋友找我,说他手上那份县级边界 shp 一叠加门店点位,边界全飘到隔壁县去了,查了两天才发现是坐标系没对上。这类问题在基础地理数据库里太常见了——1100 万比例尺的县级行政区划数据,覆盖全国两千多个县级单元,字段、编码、几何精度都直接影响后续的空间统计和制图。这份 2017 年的县级行政区 shp 文件,本质是一套带行政区代码和名称属性的面状矢量数据,适合做底图、做区域汇总、做点位归属判断。它不解决实时行政区划变更,也不适合做高精度地块分析,但对县域尺度的分析、专题制图、教学演示来说,是能直接上手的一手底料。下面按我实际拆包、入库、跑分析的顺序,把能抄的步骤和会翻车的地方都摆出来。

2. 拆包先看结构:shp 五件套与字段到底怎么读

2.1 一个 shp 不是单个文件,少一个都打不开

很多人拿到压缩包解压后只盯着.shp双击,结果 GIS 报“缺少投影文件”或者属性表一片空白。shapefile 是 ESRI 定义的多文件格式,一套完整的县级面数据至少包含这几个:

文件后缀作用缺失后果
.shp存储几何坐标无法打开
.shx几何索引部分软件报错或读取极慢
.dbf属性表(名称、代码等)属性全空,只剩图形
.prj坐标系定义(WKT)坐标未知,叠加必偏
.cpg字符编码声明中文名乱码

我一般解压后先跑一遍清单核对,用 bash 一行就能看清缺没缺:

# 列出目录下所有 shp 相关文件,按后缀排序 ls -1 *.shp *.shx *.dbf *.prj *.cpg 2>/dev/null | sort

这段命令的意义在于:2>/dev/null把不存在的后缀报错吞掉,只输出真实存在的文件。如果.prj没出现在结果里,别猜坐标系,直接找数据提供方要,或者用下一节的办法反推。.cpg缺失时中文属性大概率乱码,后面 2.3 会讲怎么救。

2.2 用 Python 把字段和坐标系一次读清楚

GIS 桌面端点属性表太慢,我习惯用geopandas先做一次体检,字段名、类型、坐标系、要素数一次拿全:

import geopandas as gpd # 读取县级行政区 shp,encoding 按 cpg 实际情况调整 gdf = gpd.read_file("county_2017.shp", encoding="utf-8") print("要素数量:", len(gdf)) print("坐标系:", gdf.crs) print("字段列表:") print(gdf.dtypes) print(gdf.head(3)[["NAME", "CODE"]]) # 字段名按实际调整

逻辑说明:read_file会自动关联同目录的.shx、.dbf、.prj,所以五个文件必须放在同一路径下。gdf.crs打印出来如果是EPSG:4326,说明是经纬度地理坐标;如果是EPSG:3857或带+proj=aea的字符串,说明是投影坐标。这一步决定了后面面积计算能不能直接用——经纬度坐标下直接算面积,单位是“平方度”,毫无意义,必须先投影。字段名各地不一,常见有NAME、NAME_2017、县、ADCODE等,先print(gdf.columns)看清再写后续代码,别硬套。

2.3 中文乱码与编码:cpg 缺失时的补救

.dbf的字符编码在 2017 年前后的数据里很杂,常见 GBK、GB18030、UTF-8 三种。如果打开发现县名是“鍖垮悕”这类乱码,先看.cpg内容:

cat county_2017.cpg

输出若是UTF-8就正常;若是936或GBK,而 geopandas 默认按 UTF-8 读就会乱。补救办法是显式指定编码重读:

# cpg 缺失或声明错误时,逐个编码试读,看哪个县名正常 for enc in ["gb18030", "gbk", "utf-8"]: try: g = gpd.read_file("county_2017.shp", encoding=enc) print(enc, "->", g.iloc[0]["NAME"]) except Exception as e: print(enc, "失败:", e)

参数说明:gb18030是 GBK 的超集,优先试它,能覆盖绝大多数老数据。读对之后建议立刻另存一份 UTF-8 版本,避免后续入库反复踩编码坑。这一步看着琐碎,但县级名称是后面做区域匹配的主键,乱码等于主键废了。

3. 坐标系与投影:叠加偏移的根子在这里

3.1 先判断是地理坐标还是投影坐标

县级行政区数据最常见的两种状态:一种是经纬度(WGS84 或 CGCS2000 地理坐标),单位是度;另一种是 Albers 等积投影或高斯投影,单位是米。判断方法很直接——看坐标数值量级。经度在 70~140、纬度在 3~55 之间,就是地理坐标;数值动辄几十万、几百万,就是投影坐标。用代码快速确认:

bounds = gdf.total_bounds # [minx, miny, maxx, maxy] print("范围:", bounds) if abs(bounds[0]) <= 180 and abs(bounds[1]) <= 90: print("判定为地理坐标(经纬度)") else: print("判定为投影坐标(米)")

这个判断不是学术洁癖。地理坐标下做缓冲区、算面积、算距离,结果全是错的,因为一度经度在不同纬度对应的实际距离差很多。我见过有人直接用经纬度数据算县域面积,得出某县“0.03 平方度”,这种数没法进报告。

3.2 统一到投影坐标系再算面积

县域分析里最常做的是面积汇总和点位归属。正确顺序是:先确认源坐标系,再投影到适合研究区的等积投影,最后算面积。全国尺度常用 Albers 等积投影,参数按中国区域标准设置:

# 若源为地理坐标,投影到中国 Albers 等积投影 gdf_proj = gdf.to_crs( "+proj=aea +lat_1=25 +lat_2=47 +lat_0=0 " "+lon_0=105 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs" ) # 新增面积字段,单位平方公里 gdf_proj["area_km2"] = gdf_proj.geometry.area / 1e6 print(gdf_proj[["NAME", "area_km2"]].sort_values("area_km2", ascending=False).head())

参数说明:lat_1、lat_2是双标准纬线,取 25 和 47 覆盖中国主要纬度带,能压低面积变形;lon_0=105是中央经线。如果你的研究区只在某个省,换成该省对应的中央经线和高斯投影,面积精度更高。/1e6是把平方米换成平方公里。算完拿几个已知面积的县对一下公开统计值,偏差在合理范围内才敢用。

3.3 叠加偏移的排查顺序

点位叠加上去偏了,按这个顺序查,基本能定位:

  1. 看两份数据的crs是否一致,不一致先to_crs统一;
  2. 看是否一份是地理坐标、一份是投影坐标却强行叠加;
  3. 看是否用了错误的基准面(如 WGS84 与 CGCS2000 混用,在县级尺度偏差通常很小,但跨带投影会放大);
  4. 看点位本身是不是 GCJ-02 等加密坐标,这类坐标与标准坐标存在系统性偏移,需要先纠偏再叠加。

前三条用代码能查,第四条得问数据来源。别一上来就怀疑 shp 本身错,九成偏移是坐标系没统一。

4. 入库与空间查询:把 shp 变成能查的底表

4.1 导入 PostGIS 的标准流程

做县域汇总、点位归属,最终多半要落到数据库里跑 SQL。shp 进 PostGIS 用ogr2ogr最稳,字段类型和编码都能控:

ogr2ogr -f "PostgreSQL" \ PG:"host=localhost dbname=gis user=postgres password=***" \ county_2017.shp \ -nln county_2017 \ -lco GEOMETRY_NAME=geom \ -lco FID=gid \ -lco ENCODING=UTF-8 \ -nlt MULTIPOLYGON \ -t_srs EPSG:4326

参数说明:-nln指定目标表名;-lco GEOMETRY_NAME和FID控制几何列与主键列名,方便后续写 SQL;-nlt MULTIPOLYGON强制统一几何类型,避免部分要素是单面、部分是多面导致入库失败;-t_srs EPSG:4326统一到经纬度,若后续要算面积,可在库内再建投影列或用ST_Transform。入库后先建空间索引,否则点位归属查询会慢到怀疑人生:

CREATE INDEX idx_county_geom ON county_2017 USING GIST (geom); ANALYZE county_2017;

4.2 点位归属:一个 SQL 搞定县名回填

门店、采样点这类点位表,最常见的需求是“每个点落在哪个县”。用ST_Contains或ST_Within做空间连接:

-- 给点位表回填县名与行政区代码 ALTER TABLE poi ADD COLUMN county_name text; ALTER TABLE poi ADD COLUMN county_code text; UPDATE poi p SET county_name = c.name, county_code = c.code FROM county_2017 c WHERE ST_Within(p.geom, c.geom);

逻辑说明:ST_Within(p.geom, c.geom)判断点是否落在县面内。边界上的点可能因浮点误差落不进任何县,跑完查一下county_name IS NULL的数量,少量可人工核,量大说明坐标系或几何有问题。若点位是投影坐标、县面是经纬度,先ST_Transform(p.geom, 4326)统一,否则ST_Within恒为假——这是新手最常翻的车之一。

4.3 县域汇总与制图前的字段整理

汇总前把字段理干净:行政区代码保留为文本型(前导零不能丢),县名去空格,面积字段单独存。制图时按代码排序能保证图例稳定。若要做分级设色,用 3.2 算出的area_km2或业务指标做自然断点,别用等距分级,县级面积差异大,等距会把大多数县压进同一档。

5. 避坑清单:这份 2017 数据最容易翻车的五个地方

5.1 现象:叠加后边界整体偏移几百米到几公里

原因:两份数据坐标系不一致,或点位是加密坐标未纠偏。解决:先to_crs统一到同一 CRS,再核对点位来源;加密坐标需先做纠偏转换,不能直接叠。

5.2 现象:属性表县名乱码,匹配全失败

原因:.dbf实际编码与.cpg声明不符,或 cpg 缺失被默认按 UTF-8 读。解决:按 2.3 逐个编码试读,确认后另存 UTF-8 版本,主键字段单独校验一遍。

5.3 现象:面积算出来小得离谱或大得离谱

原因:在地理坐标下直接算面积,单位是平方度;或投影参数与研究区不匹配。解决:先投影到等积投影再算,用已知县面积做校验,偏差超阈值就换投影参数。

5.4 现象:入库报几何类型不一致

原因:部分要素是POLYGON、部分是MULTIPOLYGON,目标表按单一类型建。解决:ogr2ogr加-nlt MULTIPOLYGON强制统一,或在 Python 里gdf.geometry = gdf.geometry.buffer(0)修复自相交后再入库。

5.5 现象:边界上的点归属为空

原因:浮点误差导致ST_Within判定失败,或点位恰好落在县界共享边上。解决:对空值点用ST_DWithin加小容差(如 1 米)二次匹配,仍为空的人工核;同时检查几何是否有自相交,ST_IsValid批量筛一遍。

6. 进阶用法:用这份底表做县域可达性与变更比对

数据用顺之后,我一般会多做两件事。一是做县域尺度的可达性粗算:以县政府驻地或几何中心为起点,用路网或直线距离算到相邻县的时间/距离矩阵,配合这份县级面做分级制图。直线距离版本用geopandas的centroid加distance就能跑,注意先投影到米制坐标:

# 县域中心点两两距离矩阵(投影坐标下,单位米) cent = gdf_proj.geometry.centroid coords = [(p.x, p.y) for p in cent] import numpy as np pts = np.array(coords) dist = np.sqrt(((pts[:, None, :] - pts[None, :, :]) ** 2).sum(axis=2)) print("距离矩阵形状:", dist.shape) # (县数, 县数)

这段只做量级参考,真实可达性要接路网,但用来快速筛“相邻县”足够。二是做行政区划变更比对:手上有更新年份的县级数据时,用行政区代码做外连接,code只在一侧出现的即为新增或撤并单元,再叠加几何差异看边界调整范围。比对时务必用代码而非名称做主键,县名重名和改名都很常见。

最后说个血泪教训:我早期做县域汇总,图省事直接拿经纬度数据算面积,报告交上去被一眼看穿,返工重算了两天。从那以后我每次拿到 shp,第一件事固定是print(gdf.crs)加total_bounds双确认,投影没定死之前不碰任何面积和距离计算。这份 2017 县级行政区数据本身结构清晰、字段够用,把坐标系、编码、几何类型这三关过了,后面入库、查询、制图都是顺水推舟。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进