简介:1100万基础地理数据库(2017版)提供全国县级行政区划边界矢量数据,面向GIS开发、城乡规划、人口统计与环境分析等场景,适合需要标准县级面图层做叠加计算或制图的研究者与工程师。资源共7个文件,压缩包约46.33MB,包含核心shp几何文件、dbf属性表、prj坐标系定义(CGS_WGS_1984),以及sbn/sbx/shx等空间索引文件,配套结构完整,可直接加载进ArcGIS、QGIS等平台使用。已有615人学习下载。借助该数据集可快速获取2017年县级行政边界,免除手动矢量化与配准工作;同时属性表内嵌县名、代码等信息,便于与人口、经济等专题数据挂接,开展区域差异、可达性、生态区划等分析。对需要统一行政单元口径的地理数据项目而言,这是一套基础而实用的底图数据。
1. 1100万基础地理数据库里的2017县级行政区shp:先搞清这批数据的真实体量
收到一套“1100万基础地理数据库_2017县级行政区shp文件”,别急着把它当成1100万个要素的庞然大物。这个命名里的“1100万”,通常指基础地理数据库中全部图层要素或坐标记录的综合规模,真正承载“县级行政区”的 shp 面要素往往没有那么多。先确认数据构成与坐标系,再决定用哪种方式读取,后续处理才不会一开始就走偏。我会按一线做地理数据处理的顺序来讲:先拆解文件组成与坐标识别,再给两条可复现的读取与转换路线,接着处理 2017 年行政代码与现状不一致的问题,最后集中排一排乱码、缝隙、偏移这类高频坑,并给一套十分钟的入库前验证流程。新手可以照着步骤走,熟手则可以直接跳到第 5 章看边界情况。
2. 读 shp 前该认的三件事:文件三件套、坐标系与 1100 万的构成
2.1 shp 从来不是一个文件:缺了 shx 就开不了门的组件关系
很多人第一次拿到 shp 会觉得奇怪:明明叫“县级行政区.shp”,压缩包里却躺着一堆后缀各不相同的文件。这不是数据发错了,而是 shapefile 天生就是多文件构成的格式。一个完整可用的 shp 至少需要三个基础文件,配套文件缺一个,读取结果就会失灵或数据品质打折。
| 文件后缀 | 作用 | 缺失后果 |
|---|---|---|
| .shp | 存储几何坐标(面、线、点) | 数据无法打开 |
| .shx | 几何索引,用于快速定位记录 | GIS 软件打开时报“无法读取”或“索引损坏” |
| .dbf | dBASE 格式属性表,每条要素一行记录 | 图形还在但属性全无 |
| .prj | 坐标系描述(WKT 文本) | 软件按未知坐标系处理,位置可能偏差数百米 |
| .cpg | 声明属性表字符编码,常见为 UTF-8 或 GBK | 中文属性大概率乱码 |
| .sbn / .sbx | 空间索引(部分软件自动生成) | 缺失不影响打开,影响查询效率 |
我收到的这版 2017 县级数据,发行时一般会附带 .cpg 文件,但很多二次分发者只拷了 .shp、.shx、.dbf 三个文件就往外传,导致接收方无法确认坐标系与编码。实际工作中,我首先会检查.prj和.cpg是否存在,缺少的立即向数据来源方要说明,否则后续每一步都可能走歪。
2.2 坐标系识别:先看 prj 再定投影,别等图偏了才后悔
打开一个 shp 前先做一件事:看坐标系。2017 年发布的县级行政区数据,绝大多数已切换为国家 2000 大地坐标系(CGCS2000),但不同生产单位对数据作了不同的投影处理。同样是 CGCS2000,地理坐标系与投影坐标系在面积量算和空间分析上表现完全不一样。
常见做法是用文本编辑器直接打开 .prj 文件,里面是一段 WKT 描述。如果看到GCS_China_Geographic_2000,说明它是经度/纬度存储的地理坐标系;如果看到CGCS2000_3_Degree_GK_CM_120E或类似描述,则是高斯-克吕格投影坐标系。这两种情况下的处理方式完全不同。
| 坐标系简称 | EPSG 代码 | 存储单位 | 适用场景 |
|---|---|---|---|
| CGCS2000 地理坐标系 | 4490 | 度 | 全国范围分析、跨省拼接 |
| WGS84 地理坐标系 | 4326 | 度 | 与 GPS 原始数据、标准在线底图叠加 |
| Web 墨卡托 | 3857 / 常见投影代码 | 米 | Web 地图切片展示 |
| CGCS2000 高斯投影 | 4491-4505 等 | 米 | 省级及以下高精度量算 |
判断方法不止读 .prj 一种。我一般会用 GDAL 工具快速读取元数据,命令行下执行gdalsrsinfo county_2017.prj,输出里会直接给出 EPSG 编号和投影参数。没有 GDAL 环境时,直接看文件位置也行:数据落在经度 73 到 135、纬度 18 到 54 这个范围附近,大概率是地理坐标系;如果坐标动辄几十万、几百万,则是投影坐标系,需要反查中央经线再做后续投影转换。
2.3 1100 万这个数字该怎么读:要素记录、顶点数量与文件体积
“1100 万”最容易让人误判为县级行政区有 1100 万个。2017 年全国县级行政区划单位数量在数千个量级,远达不到百万。对做基础地理数据库的人来说,1100 万更可能是以下三者之一:
- 全库要素总数:指数据库里所有图层(境界、居民地、水系、交通、植被等)的要素记录合计,单看县级行政区图层,要素数只占很小一部分;
- 几何顶点总数:shp 里的每个面由成百上千个折点组成,复杂行政区边界全部顶点累加后可达百万甚至千万规模;
- 解压后文件总记录:有的生产单位把乡级以上的全部境界数据合并到一个图斑表里,行数会非常接近这个量级。
区分这三种含义,直接决定你用什么方式处理。如果只是县级边界,几千行的 GeoDataFrame 用个人电脑跑完全没问题;如果要对全部 1100 万图斑做拓扑检查,就必须考虑分块读取和空间索引。拿到数据后第一步就是打开属性表看记录数,而不是纠结文件名里的数字。
3. 用 OGR 与 GeoPandas 把 2017 县级 shp 读进来:两条实操路线
3.1 先用 GDAL/OGR 读元数据,半小时能看穿一个 shp 的底色
加载县级 shp 最容易犯的错是拿到文件直接拖进可视化界面,等它画出图来才发现坐标系不对、属性乱码。我习惯先用 GDAL/OGR 把元数据读出来,整个过程用不了一分钟,却能把文件底细摸清。
from osgeo import ogr # 打开 shp,第二个参数 0 表示只读,不修改源文件 shp_path = "county_2017.shp" ds = ogr.Open(shp_path, 0) if not ds: raise SystemExit("文件打不开,检查 .shp/.shx/.dbf 三件套是否齐全") layer = ds.GetLayer(0) # 图层名与要素数量 print("图层名:", layer.GetName()) print("要素数:", layer.GetFeatureCount()) # 空间参考信息:能导出的就导出,导不出说明 prj 缺失 spatial_ref = layer.GetSpatialRef() if spatial_ref: print(spatial_ref.ExportToPrettyWkt()) else: print("未找到坐标系定义,需要手动指定") # 读取第一个要素的几何类型,确认是面还是多面 feature = layer.GetNextFeature() geom = feature.GetGeometryRef() print("几何类型:", geom.GetGeometryName())这段代码输出四类关键信息:图层名、要素数量、坐标系定义、几何类型。要素数量让你确认数据实际规模;坐标系定义决定后续是否需要重投影;几何类型能帮你提前发现“县级行政区其实是线图层”这类发错文件的情况。参数方面,ogr.Open()的第一个参数是路径,第二个参数 0 表示只读,如果改成 1 就允许写入,但日常检查一律用只读,避免误改原始数据。
如果ExportToPrettyWkt()输出里包含GEOGCS["China Geodetic Coordinate System 2000",那坐标系明确是 CGCS2000 地理坐标系,对应 EPSG:4490。如果输出为空或报错,回到第 2 章补读 .prj,不要靠猜。
3.2 用 GeoPandas 读入、转坐标与快速绘图
确认元数据后,进入数据分析环境。GeoPandas 是 Python 生态里处理 shp 最顺手的工具,底层依赖 Shapely 与 Fiona,读取 shp 时需要注意编码参数。
import geopandas as gpd # 读取 shp # encoding 按 .cpg 声明选择,常见为 gbk,读出来乱码可逐个尝试 gdf = gpd.read_file("county_2017.shp", encoding="gbk") # 查看整体情况 print(gdf.shape) # 行数与列数 print(gdf.columns.tolist()) # 属性字段列表 print(gdf.crs) # 当前坐标系 # 统一转到 CGCS2000 地理坐标,便于后续跨省拼接和叠加 gdf = gdf.to_crs("EPSG:4490") # 快速计算每个县的面积(单位:平方公里) # 先投影到等积投影再算面积,结果更接近真实值 gdf["area_km2"] = ( gdf.geometry.to_crs("EPSG:3857").area / 1_000_000 ) print(gdf[["NAME", "area_km2"]].head())read_file()的encoding参数是这一节最容易翻车的地方。国内县级数据属性表大多用 GBK 或 GB2312 编码,如果默认按 UTF-8 读取,省、市、县名称会变成乱码;用utf-8读不出来的时候,依次尝试gbk、gb18030。to_crs("EPSG:4490")只做坐标转换,不改属性。面积计算这里我用了近似的等积投影方式,面积结果仍有轻微误差,但比直接在经纬度坐标系下计算稳妥得多,量级没有问题。
读取成功后先别急着分析,先绘图检查一眼,看面要素是否填满整个行政区域。用gdf.plot(figsize=(12, 8))即可,如果图形出现大面积空白或边界重叠,说明数据存在几何问题,下一章的避坑部分会专门讲。
4. 属性表处理:把 2017 年的行政区划代码对齐到当前
4.1 先读懂县级 shp 的属性字段再动手
2017 年版县级行政区的属性表字段,不同生产批次差异较大,但通常会包含以下几类核心信息:
| 字段名(常见写法) | 含义 | 需要注意的问题 |
|---|---|---|
| NAME | 县级行政区名称 | 可能带“县”“区”“市”后缀,也可能不带,需统一 |
| PAC / XZQHDM / CODE | 行政区划代码 | 2017 年代码与当前代码可能存在差异 |
| PROVINCE / PROV | 所属省级行政区名称 | 常有空格或简称差异 |
| CITY / CITY_NAME | 所属地级行政区名称 | 直辖市辖区可能为空 |
| AREA / SHAPE_AREA | 面积字段(平方度或平方米) | 单位混乱,尽量自己计算,别直接引用 |
处理 2017 年县级 shp,最核心的动作是识别“行政区划代码列”。2017 年版数据里代码列未必叫 PAC,也有可能叫 GB_CODE 或 XZDM,你需要打印出字段名逐一确认。我的经验是:先print(gdf.dtypes)看类型,再print(gdf.head(20))看样例值。如果代码列是数字类型,先转成字符串再处理,避免前导零丢失。
4.2 行政区划代码更新的三种情形与映射方法
2017 年距今已有多年,县级行政区划发生了不少调整。常见情形有三类:
- 撤县设区:某县改为某市某区,行政代码整体变化;
- 合并与拆分:多个县合并成一个,或一个县拆成多个;
- 名称变更但代码不变:仅地名发生变化,代码不动。
如果直接拿 2017 年版代码关联现状数据,会发现大量匹配不上。稳妥做法是保留原始代码列,另建一列映射到最新代码。映射关系必须人工核对,不要自动推断,因为个别调整存在代码跳号。
import pandas as pd # 假设 gdf 是已读取的 GeoDataFrame # 先保住原始代码列,再新增映射列 gdf["CODE_RAW"] = gdf["XZQHDM"].astype(str).str.strip() # 手工整理的代码映射表,格式为 {2017版代码: 最新代码} # 实际操作中每个调整都需要对照最新的行政区划代码公告确认 code_map = { "320281": "320281", # 假设这个县未调整,保持原代码 "321282": "321203", # 假设撤市设区,映射到新区的代码 # ... 继续补齐其他变更记录 } # 未在映射表中的记录,保留原代码并做标记 gdf["CODE_NEW"] = gdf["CODE_RAW"].map(lambda x: code_map.get(x, x)) # 找出需要人工复核的记录 mask = gdf["CODE_RAW"] != gdf["CODE_NEW"] print("行政代码发生变化的数量:", mask.sum()) print(gdf.loc[mask, ["NAME", "CODE_RAW", "CODE_NEW"]].head(10))映射表是这套处理的关键。我的习惯是不在代码里写死变更逻辑,而是维护一个独立的 CSV 映射文件,方便其他同事核对修改。map方法对没有匹配的记录默认返回None,所以我用code_map.get(x, x)让未匹配项保留原代码,再单独筛选出来人工处理。这一步做完,后续关联人口、经济等现状数据时,代码对齐率会大幅提升。
4.3 处理县界合并与拆分:保留原代码再开新字段
行政代码变更是属性层面的问题,但合并与拆分还会带来几何层面的问题。比如某两个县合并成一个新区,原始 shp 里是两个面要素,现状统计需要的是一个面要素。此时需要按新代码做聚合,几何上做 dissolve 合并。
# 按新代码聚合,合并几何和属性 merged = gdf.dissolve(by="CODE_NEW", aggfunc="first") # 重置索引,把 CODE_NEW 变回普通列 merged = merged.reset_index() print(merged.shape) print(merged.head(10))dissolve(by="CODE_NEW")会把相同新代码的相邻面要素合并成一个 MultiPolygon,aggfunc="first"表示属性字段取第一行记录,因为名称、代码在新代码级别应当一致。需要注意,dissolve 之后的要素有可能出现“一个县变成两个不相连的多边形”,这不一定是错误,也可能是飞地或岛屿,要结合具体地理位置判断,不能直接当作数据异常处理。
拆分情形则需要反过来:原始 shp 中一个面要素要拆分两种用途,通常用数据表关联的方式实现,几何上拆分需要借助另一份参考边界。2017 年县级 shp 本身没有拆分信息,我的做法是从最新的乡镇级边界数据中按属性截取,再合并回县级空间。这种操作较复杂,业务不涉及拆分时可以跳过。
5. 避坑排查:乱码、缝隙、坐标偏移与罕见几何异常的定位思路
5.1 现象:属性表打开全是乱码
现象:GeoPandas 读出来省份、县名全是“锟斤拷”“烫烫烫”一类字符,或在 QGIS 里打开属性表文字不可读。
原因:属性表 .dbf 文件使用 GBK 编码存储,读取时按 UTF-8 解码,中文字符被错误映射。多数情况是 .cpg 文件缺失,软件无法判断编码。
解决:首先看 .cpg 文件,里面若写GBK或GB2312,直接在read_file()里指定encoding="gbk";如果 .cpg 不存在,就用文本编辑器打开 .dbf 头几个字节判断,或者干脆依次尝试gbk、gb18030、utf-8。QGIS 里则在图层属性中修改“数据源编码”为 GBK。这里有个小规律:乱码字符若表现为常见中文问号加英文字母组合,多半是 GBK 被当 UTF-8 读;若表现为可打印符号,要考虑是不是编码声明与实际不一致。
5.2 现象:相邻县界之间出现缝隙或压盖
现象:放大查看时,两个相邻县边界中间有条白缝,或两块面相互重叠。
原因:shp 本质上是独立面要素的集合,并不保证拓扑一致。生产方不同批次数据拼接时,公共边界可能来自不同采集过程,差几十米都常见。缝隙和压盖不是数据损坏,而是数据生产精度差异。
解决:做县级汇总统计前,必须用缓冲区消除微小缝隙。常用做法是geometry = geometry.buffer(0),这一步能把自相交的碎屑清理掉,再对相邻小缝隙做修复。如果是更复杂的拓扑问题,比如需要生成无缝面层,就要用unary_union做整体融合再切分,但这样处理量大很多,建议只对业务范围内的子集做。叠加分析之前,务必先统一坐标系,否则缝隙和压盖会因投影转换而放大。
5.3 现象:数据整体与在线底图发生几十米偏移
现象:把县级 shp 与在线底图叠加显示,边界整体往一个方向偏移了几十米到一公里,行政界线与底图影像不贴合。
原因:最常见的是坐标系不匹配。shp 是 CGCS2000 地理坐标,在线底图是 WGS84 地理坐标,两套坐标系在部分地区存在数十米差距;还有的情况是 shp 本身是投影坐标但被当作地理坐标加载,偏移会达到数公里量级。
解决:先读取 .prj 确认原始坐标系,再统一到同一个 EPSG。CGCS2000 与 WGS84 之间的转换,多数场景直接to_crs("EPSG:4326")即可,误差在业务可接受范围;如果对精度有高要求,需要加七参数或网格改正。注意这里不要用平移方式硬凑,治标不治本,换一个底图又会偏。比较合适的做法是在读取后统一转成 EPSG:4490,并把在线底图也切到 CGCS2000 切片,很多图商底图支持 4490 版本。
5.4 现象:关键属性大量为空
现象:统计省份名称时发现几百行空值,或者行政区划代码缺失,导致关联数据失败。
原因:属性表在制作时漏填了字段,或因代码位数限制被截断。最常见的是“地级市”字段在直辖市场景下为空,以及代码字段数值类型前导零丢失。
解决:第一步先区分“不该为空但为空”和“本来就允许为空”。乡镇数据里地级市字段为空要警惕,直辖市辖区则属正常。第二步做填充策略:省级代码缺失的,从更粗的省级边界数据里空间匹配补齐;代码前导零丢失的,用zfill(6)补足到 6 位。我在实际项目中做过一次统计,空值分布往往集中在少数几个特殊区域,优先处理这些区域能明显减少数据错误带来的连锁问题。
5.5 现象:一个县变成了两个甚至多个多边形
现象:选中某县名称,属性表显示该要素几何类型是 MultiPolygon,由多个不相连的部分构成;更意外的是 dissolve 之后,一个名字对应两块分开的区域。
原因:这是正常现象,不代表数据错误。中国不少行政区存在飞地、岛屿、跨水域的属地,导致一个县级行政区在地理上天然分裂成多块。shp 里它被存储为一个 MultiPolygon 要素,展开后是多个 Polygon。
解决:分析时先确认是否要把飞地单独处理。县级尺度上做可视化不做拆分问题不大;但做面积统计、叠加分析时,MultiPolygon 的各个子块可能会与邻县边界产生交集。我通常会先用explore()方式把 MultiPolygon 展开成独立要素,逐一检查最大块面积占整体比重,确认没有“主块缺失”的异常。展开后若发现某县主体区域面积占比过小,就要怀疑原始数据是否有几何错误,再对照影像底图核实。另外,很多空间计算库对 MultiPolygon 自相交很敏感,先运行gdf.geometry.is_valid检查,若有 False 记录,执行gdf.geometry.buffer(0)修复。
6. 用十分钟快检流程,验证 2017 县级 shp 能不能用于业务
6.1 一套文件、两次投影、三个查询
拿到手先不急着写业务逻辑,按下面的快检流程走一遍,十分钟内能判断这套数据是否可用于生产。
一套文件:确认 .shp、.shx、.dbf、.prj、.cpg 全部存在。缺 .prj 就先补坐标系,缺 .cpg 就先验证属性编码,缺 .shx 找来源方重新导出。
两次投影:第一次打印gdf.crs,确认原始坐标系;第二次明确业务最终需要的坐标系,执行to_crs(),例如省级范围用 EPSG:4490,市级高精度计算用高斯投影。两次投影之间用面积和边长做检验,数值变化在合理范围内才算转换正确。
三个查询:
# 1. 属性空值检查 print("空值统计:\n", gdf.isna().sum()) # 2. 几何有效性检查 invalid = gdf[~gdf.geometry.is_valid] print("无效几何数量:", len(invalid)) # 3. 行政代码重复检查 dups = gdf.duplicated(subset="CODE_NEW", keep=False) print("重复代码记录数:", dups.sum())空值检查覆盖所有业务关键字段;无效几何用 Shapely 的is_valid判断,返回 False 的要素先buffer(0)修复再继续;重复代码检查用于判断是否存在同一行政区被重复采集的情况。三项查询都通过,数据具备入库条件;不通过就把异常记录筛选出来逐条处理。
6.2 抽样人工比对后,再决定要不要信任这批数据
自动化检查通过后,还差最后一步:抽样与可信底图人工比对。从不同省份区域里取三到五个县,缩放查看行政边界走向是否贴合常见的地理标志,比如河流走向、山脉山脊线。属性上随机抽几个县的名称和代码,与公开的行政区划信息核对,重点确认 2017 年与当前代码不一致的条目是否已经映射正确。我在某地市的业务项目里曾经跳过这一步直接出图,结果因一版代码映射错误导致整个街道汇总数据偏了一截,后来找原因才发现是省市两级代码混用。自此我养成了入库前只抽五个县做人工看一眼的习惯,花费时间很短,却能拦住大多数低级的源数据问题。
这套 2017 年县级行政区 shp,价值在于它反映了一个特定年份的行政区划快照,用来做历史复盘、对比分析是合适且稳妥的。确定它能用之后,建议把它作为基准数据在项目内统一共享,后续再做人口、GDP 空间化时,都基于同一套边界,避免各人手里版本不一致。希望帮到你。
本文还有配套的精品资源,点击获取