ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

2017县级行政区shp数据处理全流程:从字段清洗到空间挂接

2017县级行政区shp数据处理全流程:从字段清洗到空间挂接 简介这份资源面向地理信息、城市规划与人口统计等方向的学习者和从业者提供2017年中国县级行政区划的矢量边界数据采用1:100万比例尺适合大范围空间分析与制图实践。压缩包共7个文件、约46.33MB以shp主文件承载几何图形dbf保存县名与代码等属性prj定义CGS_WGS_1984坐标系sbn、sbx、shx等索引文件加速检索xml记录元数据配套结构完整可直接在主流GIS软件中加载使用。已有615人学习下载说明其在教学与项目练习中具有一定参考价值。读者可借助该数据完成行政区划底图制作、人口密度与地形气候等专题叠加分析并理解Shapefile各组成文件的分工与坐标系统一要点为后续空间统计与规划决策打下数据基础。1. 拿到一份 2017 县级行政区 shp先别急着往地图里拖一份标注为 2017 年、覆盖县级行政区划的 shp 文件落到手里第一件事往往不是打开 QGIS 看效果而是判断它到底能不能用、该怎么用。基础地理数据库里的县级行政区图层本质是一份带属性表的矢量面数据承载的是行政区名称、代码、几何边界三样东西。它最常见的用途是给统计数据做空间挂接、给业务系统做底图、给分析任务做区域裁剪。但 2017 这个年份很关键区划调整年年有撤县设区、合并、更名都会让边界和代码对不上今天的口径。所以真正要问的是三件事——坐标系是什么、属性字段能不能对上你的业务主键、几何有没有拓扑问题。这篇笔记按我实际处理这类数据的顺序走一遍从字段结构、坐标转换、入库挂接到避坑和验证新手能照着复现熟手能直接跳到参数和边界那几节。2. 拆开 shp 看结构字段、编码与坐标系的三个确认点shp 从来不是单个文件而是一组同名文件的集合。拿到压缩包先解压用命令行把目录列清楚比在图形界面里点开更可靠因为你能立刻发现缺了 .prj 或 .dbf 这种致命问题。2.1 先确认文件组是否完整一个能正常读取的 shapefile 至少要有 .shp几何、.shx索引、.dbf属性三个文件.prj投影定义和 .cpg编码声明属于强烈建议存在的补充文件。缺 .prj 意味着坐标系未知缺 .cpg 意味着中文字段可能乱码。# 列出解压目录下所有同名文件组确认完整性 ls -lh ./county_2017/ | awk {print $9, $5} # 期望看到成对出现的 .shp .shx .dbf .prj .cpg这段命令只是把文件名和大小打出来重点看两件事同名文件是否成套、.dbf 大小是否合理。如果 .dbf 只有几 KB 而 .shp 有几十 MB说明属性表几乎是空的这种数据只能当底图用做不了属性挂接。参数上没什么可调的-lh只是让人眼可读awk取第 9 列文件名和第 5 列大小不同系统ls列序略有差异看不准就直接ls -l人工核对。2.2 用 Python 读出字段清单和坐标系图形界面能看但批量判断和留痕要靠代码。用 geopandas 读进来先打印 CRS 和字段类型这一步决定了后面所有转换动作。import geopandas as gpd # 读取时显式指定编码中文属性表常见 GBK/GB18030 gdf gpd.read_file(./county_2017/county_2017.shp, encodinggb18030) print(要素数量:, len(gdf)) print(坐标系:, gdf.crs) print(字段与类型:) print(gdf.dtypes) print(前两行属性:) print(gdf.drop(columnsgeometry).head(2))逻辑说明encoding参数是这类国内基础地理数据最容易翻车的地方默认 UTF-8 读 GBK 编码的 .dbf 会抛 UnicodeDecodeError 或读出乱码字段名。先试gb18030它兼容 GBK 且覆盖更全。gdf.crs如果打印出None说明 .prj 缺失或损坏必须手动补坐标系不能靠猜。字段类型里要特别关注行政区代码字段——如果它被读成 int64前导零会丢比如110101变成110101还好但010101这种就会变成10101挂接时全对不上。2.3 行政区代码字段的三种常见命名与清洗不同来源的县级图层代码字段可能叫ADCODE、CODE、XZQDM、AREA_CODE名称字段可能叫NAME、XZQMC、COUNTY。不要假设先看实际列名再写映射。清洗的核心是把代码统一成 6 位字符串并去掉可能存在的空格。# 按实际列名调整这里假设代码列叫 ADCODE名称列叫 NAME code_col ADCODE name_col NAME # 统一转 6 位字符串补前导零去空格 gdf[code_col] gdf[code_col].astype(str).str.strip().str.zfill(6) gdf[name_col] gdf[name_col].astype(str).str.strip() # 检查代码长度分布异常长度说明数据有问题 print(gdf[code_col].str.len().value_counts()) # 正常应几乎全是 6出现 5 或 7 要逐条排查参数说明zfill(6)是补零的关键astype(str)必须在它之前否则对数值型补零无效。str.strip()处理的是从 Excel 或数据库导出时混入的首尾空格这类空格肉眼看不见但会让merge静默失败——这是血泪经验里最常见的一类。长度分布打印出来如果出现非 6 位不要急着删先定位是哪些行往往是某个特殊区划或数据录入错误。3. 坐标系与投影从地理坐标到可计算面积的转换路径县级行政区数据最常见的坐标系是 CGCS2000 地理坐标单位度或某种高斯-克吕格投影单位米。前者适合存储和展示后者适合算面积、算距离。选错坐标系不会报错但会让面积统计差出几十倍这是最隐蔽的坑。3.1 判断当前是地理坐标还是投影坐标看gdf.crs的输出如果unit是degree就是地理坐标如果是metre就是投影坐标。也可以直接看几何坐标数值范围经度在 73 到 135 之间、纬度在 3 到 54 之间基本可判定为地理坐标。# 打印 CRS 详细信息和几何范围 print(gdf.crs.to_string() if gdf.crs else CRS 缺失) print(几何边界:, gdf.total_bounds) # 地理坐标下 total_bounds 大致是 [73, 3, 135, 54] 量级如果total_bounds出现几百万量级的数值说明已经是投影坐标单位大概率是米。这一步的判断直接决定下一步要不要做投影转换。注意total_bounds返回的是[minx, miny, maxx, maxy]别把经纬顺序搞反。3.2 面积计算必须切到等积投影在地理坐标下直接调.area得到的是平方度没有任何物理意义。正确做法是先投影到适合中国区域的等积坐标系再算面积。常见选择是 Albers 等积投影参数按中国范围设定。# 定义中国区域 Albers 等积投影 albers_crs projaea lat_125 lat_247 lat_00 lon_0105 x_00 y_00 datumWGS84 unitsm no_defs gdf_proj gdf.to_crs(albers_crs) gdf_proj[area_km2] gdf_proj.geometry.area / 1e6 print(gdf_proj[[NAME, area_km2]].sort_values(area_km2, ascendingFalse).head(5))参数说明lat_1和lat_2是标准纬线取 25 和 47 覆盖中国大部分纬度带能压低面积变形lon_0105是中央经线取中国中部经度。这套参数是业内常用配置不是唯一解但对全国尺度统计足够。/1e6是把平方米转平方公里。如果只做单省分析可以把标准纬线和中央经线收窄到该省范围面积精度会更好。注意to_crs会重算几何原始gdf不受影响别在转换后的对象上再调原始字段做混用。3.3 投影转换后的几何有效性检查投影转换本身可能放大原本就存在的几何问题比如自相交、重复点。转换后必须做一次有效性检查否则后续空间叠加会报拓扑错误。# 检查无效几何并尝试修复 invalid gdf_proj[~gdf_proj.geometry.is_valid] print(无效几何数量:, len(invalid)) if len(invalid) 0: gdf_proj[geometry] gdf_proj.geometry.buffer(0) print(修复后无效数量:, (~gdf_proj.geometry.is_valid).sum())buffer(0)是修复自相交的常用手段原理是把几何做一次零距离缓冲多数自相交会被规整。但它不是万能药对严重破损的几何可能产生空几何或碎片修复后要再查一次is_valid和is_empty。如果无效数量占比很高说明源数据质量差应该回到源头核对而不是硬修。4. 入库与挂接把县级行政区接到业务统计表上数据清洗完真正的落地动作是入库和挂接。这一步的目标是让县级行政区图层能和你的统计表按行政区代码一一对上对不上的要能定位原因而不是靠肉眼找。4.1 导出为 GeoPackage 或 PostGIS 的取舍shp 有字段名长度限制10 字符和编码问题长期使用建议转成 GeoPackage 或直接入 PostGIS。GeoPackage 单文件、无字段名限制、UTF-8 原生适合中小规模PostGIS 适合多用户并发和空间查询。# 导出 GeoPackage字段名不受 10 字符限制 gdf_proj.to_file(./county_2017.gpkg, layercounty, driverGPKG) # 如需入 PostGIS用 sqlalchemy 引擎 # from sqlalchemy import create_engine # engine create_engine(postgresql://user:passhost:5432/gisdb) # gdf_proj.to_postgis(county_2017, engine, if_existsreplace, indexFalse)参数说明driverGPKG必须显式指定否则 geopandas 可能按扩展名推断失败。if_existsreplace会覆盖同名表生产环境慎用建议先append再校验。入 PostGIS 前确认目标库已启用 PostGIS 扩展否则建表会失败。字段名在 GeoPackage 里可以保留中文但跨系统交换时仍建议用英文列名加中文别名减少编码依赖。4.2 按行政区代码做左连接并统计匹配率挂接的核心是左连接以业务统计表为主表县级图层为右表按代码连接统计匹配率。匹配率低于预期时问题一定出在代码格式或区划变更上。import pandas as pd # 模拟业务统计表代码为 6 位字符串 stats pd.DataFrame({ ADCODE: [110101, 130102, 310101, 999999], value: [120, 340, 210, 50] }) merged stats.merge( gdf_proj[[ADCODE, NAME, geometry]], onADCODE, howleft ) matched merged[NAME].notna().sum() print(f匹配率: {matched}/{len(merged)}) print(未匹配代码:, merged.loc[merged[NAME].isna(), ADCODE].tolist())逻辑说明howleft保证业务表所有行保留未匹配的NAME为 NaN便于定位。匹配率是判断数据可用性的硬指标低于 95% 就要停下来查。未匹配代码常见原因有三类业务表代码带前导零丢失、图层是 2017 口径而业务表用了新代码、代码字段混入了空格或全角字符。逐类排查比盲目重试有效。4.3 区划变更导致的代码对不上怎么处理2017 年的县级图层和今天的业务数据之间必然存在撤县设区、合并更名的情况。处理方式不是改图层而是建一张代码映射表把旧代码映射到新代码挂接前先过一遍映射。# 维护一张变更映射表旧代码 - 新代码 code_map pd.DataFrame({ old_code: [130102, 310101], new_code: [130102, 310101] # 示例占位实际按真实变更填写 }) stats stats.merge(code_map, left_onADCODE, right_onold_code, howleft) stats[ADCODE] stats[new_code].fillna(stats[ADCODE]) stats stats.drop(columns[old_code, new_code])这张映射表要作为独立资产维护每次区划调整后更新而不是散落在脚本里。参数上注意fillna的顺序先用新代码填充没有映射的保留原代码这样不会把未变更的记录误改。映射表本身要有来源记录否则半年后没人说得清某条映射的依据。5. 避坑与排查县级行政区 shp 处理中最容易翻车的五件事5.1 中文属性乱码字段名读出来是问号现象gdf.columns打印出一堆\uXXXX或问号NAME列的值也是乱码。原因.dbf 的编码声明缺失或与 .cpg 不一致geopandas 按默认 UTF-8 解码 GBK 内容。解决读取时显式传encodinggb18030如果仍乱码用dbfread单独读 .dbf 确认原始编码再决定是否用iconv转码后重写 .cpg。不要反复用不同编码试读后直接覆盖原文件先备份。5.2 面积算出来大得离谱或小得离谱现象某县面积算出几十万平方公里。原因在地理坐标下直接调.area得到的是平方度或者投影参数的标准纬线设错。解决先确认gdf.crs的unit地理坐标必须先to_crs到等积投影再算面积。投影参数按分析范围设定全国尺度用 Albers 标准纬线 25/47单省分析收窄参数。算完拿几个已知面积的县做交叉验证偏差超过 5% 就回头查参数。5.3 merge 之后匹配率莫名很低现象代码肉眼看着一样merge后大量 NaN。原因一侧是 int 一侧是 str或一侧带前导零一侧没有或混入了不可见空格。解决连接前两侧都做astype(str).str.strip().str.zfill(6)并在 merge 前各自打印str.len().value_counts()确认长度一致。这类问题不会报错只能靠匹配率统计发现所以每次挂接都要打印匹配率别省这一步。5.4 投影转换后几何报拓扑错误现象to_crs后做空间叠加抛TopologyException。原因源数据本身有自相交或重复点投影放大了问题。解决转换后立即做is_valid检查对无效几何用buffer(0)修复修复后再查is_empty。如果无效比例超过 1%不要硬修回到源数据核对硬修出来的边界可能已经偏离实际。5.5 用 2017 数据直接对今天的业务表现象挂接后某些区县永远匹配不上或匹配上了但边界明显不对。原因2017 至今的区划调整没有处理。解决建代码映射表挂接前先映射对边界敏感的分析明确标注数据年份不要默认它代表现状。年份不是元数据里的一个装饰字段它决定了这份数据能回答什么问题、不能回答什么问题。6. 验证与进阶用抽样核对和版本留痕把数据用稳数据能不能信不靠感觉靠可重复的验证。我一般会做两层核对一层是抽样人工核对挑几个熟悉的县把算出的面积和公开的行政区面积对比另一层是内部一致性核对检查代码唯一性、几何是否重叠、名称是否有重复。# 内部一致性三项检查 print(代码重复:, gdf_proj[ADCODE].duplicated().sum()) print(名称为空:, gdf_proj[NAME].isna().sum()) print(几何为空:, gdf_proj.geometry.is_empty.sum()) # 抽样核对取几个县打印面积人工比对 sample gdf_proj.sample(5, random_state42)[[NAME, area_km2]] print(sample)duplicated().sum()应为 0出现重复说明同一行政区有多条记录挂接时会产生笛卡尔积式的行数膨胀。is_empty检查能抓出投影修复后产生的空几何。抽样用固定random_state保证可复现方便下次对比。进阶用法上我习惯把整个处理流程固化成一条可重跑的脚本链读取 → 编码清洗 → 代码标准化 → 投影转换 → 有效性修复 → 导出 → 匹配率报告。每一步的输出都落盘中间产物带日期后缀这样任何一次结果异常都能回溯到具体环节。参数不写死在代码里而是集中到一个配置字典投影参数、编码、代码字段名都从配置读换一批数据只改配置不改逻辑。检查项合格标准不合格时的动作代码唯一性重复数为 0定位重复记录确认是否多版本混入代码长度全部为 6 位逐条排查前导零或录入错误几何有效性无效占比低于 1%低于阈值修复超过则回源核对挂接匹配率高于 95%查代码格式与区划变更映射面积抽样偏差与参考值偏差低于 5%复查投影参数与单位换算这张表我每次处理新批次数据都会过一遍五项全绿才敢往业务里推。最后说个习惯任何一份带年份的行政区数据我都会在文件名和图层名里保留年份并在属性表加一列data_year不是为了好看是为了半年后有人问“这个边界怎么和现在不一样”时能一句话说清是数据口径问题而不是处理错误。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表