2026/10/9 22:10:10

七大地理区划Shape文件制作全流程:从数据来源到自动化校验

七大地理区划Shape文件制作全流程:从数据来源到自动化校验 简介这份资源面向地理信息、区域经济与城乡规划方向的研究者和学生提供中国七大地理区划华北、东北、华中、华东、西北、西南、华南的标准Shape文件解决区域分析中边界数据缺失、坐标系不统一的问题。压缩包共65个文件以shp、shx、dbf、prj、cpg等Shapefile核心组件为主另含xml元数据与sbx、sbn空间索引文件整体约1.26MB既包含七大分区合并后的汇总图层也提供各单个区划的独立文件统一采用WGS84或CGCS2000坐标系并规范属性字段。目前已有962人学习下载。读者可直接将其用于气候与生态研究、资源与土地利用统计、区域差异比较及专题地图制作属性表中的区划名称、编号、面积与边界坐标便于快速筛选与二次加工适合在GIS软件或编程环境中直接调用省去自行数字化边界的繁琐流程。1. 七大地理区划 Shape 文件从“找不到现成数据”到“自己拼出一套能用的”做区域统计分析的人多半遇到过这个场景手头有一份全国 POI 或订单数据领导要按“华北、东北、华中、华东、西北、西南、华南”七个地理区划出一张汇总表你打开 GIS 软件想加载一份现成的区划边界结果发现要么只有省级行政边界要么只有零散的大区划分七大区划的 Shape 文件根本找不到一个能直接用的版本。这不是数据不存在而是它散落在各种口径里——有按行政区划拼的有按自然地理分的有按经济区划划的边界还不完全一致。这篇笔记要解决的就是这件事怎么把七大地理区划的边界数据整理成一套标准 Shape 文件既能做全国汇总也能单独拆出某一个区划单独用。适合两类人一类是刚接手区域分析任务、需要快速拿到可用边界数据的分析师另一类是已经有一份区划数据但发现属性表混乱、坐标系不统一、边界有缝隙想重新整理成规范文件的 GIS 从业者。下面从数据来源、字段设计、合并拆分、坐标处理一路讲到踩坑排查每一步都给出可复现的操作。2. 七大区划边界数据从哪来三种常见来源的取舍2.1 省级行政边界拼接法最常见的做法是拿一份省级行政区划 Shape 文件按七大区划的省份归属做 dissolve融合。这种方法的优点是数据容易获取省级边界精度高、拓扑干净缺点是七大区划的省份归属本身有不同口径。比如内蒙古有的口径把整个内蒙古划入华北有的把东部盟市划入东北山东在多数口径里属于华东但也有划入华北的。所以用这种方法之前必须先确定一套省份归属规则并且把规则写进属性表否则后面做汇总时口径对不上。我一般会先建一张省份归属对照表用 CSV 维护方便随时调整。表结构大概是三列省级名称、所属大区、备注。备注列用来记录这个省份是否存在跨区争议比如“内蒙古东部三盟市可划入东北”。这张表是整个流程的锚点后面所有操作都依赖它。import pandas as pd # 省份归属对照表实际使用时从 CSV 读取 region_map { 北京市: 华北, 天津市: 华北, 河北省: 华北, 山西省: 华北, 内蒙古自治区: 华北, 辽宁省: 东北, 吉林省: 东北, 黑龙江省: 东北, 上海市: 华东, 江苏省: 华东, 浙江省: 华东, 安徽省: 华东, 福建省: 华东, 江西省: 华东, 山东省: 华东, 河南省: 华中, 湖北省: 华中, 湖南省: 华中, 广东省: 华南, 广西壮族自治区: 华南, 海南省: 华南, 重庆市: 西南, 四川省: 西南, 贵州省: 西南, 云南省: 西南, 西藏自治区: 西南, 陕西省: 西北, 甘肃省: 西北, 青海省: 西北, 宁夏回族自治区: 西北, 新疆维吾尔自治区: 西北, } df pd.DataFrame(list(region_map.items()), columns[province, region]) df.to_csv(province_region_map.csv, indexFalse, encodingutf-8-sig) print(f共 {len(df)} 个省级单元归入 {df[region].nunique()} 个大区)这段代码做了一件事把省份到大区的映射固化成 CSV。逻辑说明上用字典维护映射关系比硬编码在 GIS 操作里更灵活因为七大区划的省份归属在不同项目里可能微调改 CSV 比改脚本快。参数说明上encodingutf-8-sig是为了 Excel 打开不乱码如果后续用 QGIS 或 ArcGIS 读取UTF-8 也兼容。注意内蒙古、山东这两个省份如果你的项目口径和我这里不同直接改字典值即可不影响后续流程。2.2 自然地理区划数据裁剪法另一种来源是自然地理区划数据比如按地形、气候、流域划分的区划边界。这类数据的优点是地理意义更明确缺点是边界往往和省级行政边界不重合做统计汇总时会出现“一个省被切成两半”的情况。如果你的分析对象是自然地理过程比如流域水文用这类数据更合适如果分析对象是社会经济数据比如 GDP、人口还是用省级拼接法更稳妥。实际操作中我见过有人把两种数据混用用自然地理区划做可视化底图用省级拼接数据做统计汇总结果两张图叠在一起边界对不上汇报时被追问。所以选型阶段就要明确这套 Shape 文件是给谁用的、用来做什么。如果是给统计口径用的省级拼接法优先如果是给地理分析用的自然地理区划优先。两者不要混在一套文件里。2.3 公开区划数据集的字段清洗不管用哪种来源拿到的原始数据通常字段名混乱、编码不统一。常见问题包括省级名称有“省”“自治区”“市”后缀不统一大区名称有“华北地区”“华北”“北部”等多种写法还有的用拼音或英文缩写。清洗的目标是让属性表里只有两个关键字段province省级名称和region七大区划名称其他字段一律去掉或归档。import geopandas as gpd # 读取原始省级边界 gdf gpd.read_file(raw_province.shp) # 统一省级名称去掉后缀差异便于和对照表匹配 def normalize_province(name): for suffix in [省, 市, 自治区, 壮族, 回族, 维吾尔]: name name.replace(suffix, ) return name.strip() gdf[province_clean] gdf[NAME].apply(normalize_province) # 合并对照表 region_df pd.read_csv(province_region_map.csv) region_df[province_clean] region_df[province].apply(normalize_province) gdf gdf.merge(region_df[[province_clean, region]], onprovince_clean, howleft) # 检查是否有未匹配的省级单元 unmatched gdf[gdf[region].isna()] if len(unmatched) 0: print(未匹配的省级单元, unmatched[NAME].tolist()) else: print(全部匹配成功)逻辑说明先对原始数据的省级名称做归一化去掉“省”“市”“自治区”等后缀以及民族名称再和对照表做 merge。参数说明上howleft保证原始几何不被丢弃未匹配的单元会保留但region为空方便排查。这一步的关键是归一化规则要和对照表一致否则会出现“内蒙古”和“内蒙古自治区”匹配不上的情况。如果匹配失败先检查两边的名称写法再检查是否有隐藏空格。3. 汇总与拆分一套数据怎么同时满足全国和单区使用3.1 用 dissolve 生成七大区划汇总文件拿到带region字段的省级边界后按region做 dissolve 就能得到七大区划的汇总 Shape 文件。这一步在 GeoPandas 里一行代码但有几个参数直接影响结果质量。# 按大区融合生成汇总文件 region_gdf gdf.dissolve(byregion, aggfuncfirst).reset_index() # 检查几何有效性 invalid region_gdf[~region_gdf.geometry.is_valid] if len(invalid) 0: print(存在无效几何, invalid[region].tolist()) region_gdf[geometry] region_gdf[geometry].buffer(0) else: print(几何全部有效) region_gdf.to_file(seven_regions.shp, encodingutf-8) print(f汇总文件包含 {len(region_gdf)} 个要素)逻辑说明dissolve按region字段把同一大区的所有省级几何合并成一个多边形aggfuncfirst表示其他属性取第一条记录的值这里其实只保留region字段就够了。参数说明上buffer(0)是处理无效几何的常用手段能修复自相交、重复节点等问题但会轻微改变边界如果对精度要求极高应该先用make_valid或拓扑修复工具处理。输出时encodingutf-8保证中文属性不乱码Shape 文件本身对编码支持有限如果后续要在 ArcGIS 里用建议同时输出一份 GeoJSON 作为备份。3.2 按区划拆分单个 Shape 文件的批量脚本汇总文件有了但很多时候只需要某一个区划的数据比如只做华东区的分析。手动在 GIS 里筛选导出效率低写个批量脚本一次拆出七个文件。import os output_dir single_regions os.makedirs(output_dir, exist_okTrue) for region_name in region_gdf[region].unique(): subset region_gdf[region_gdf[region] region_name] # 文件名用拼音或英文避免中文路径在某些工具里出问题 filename f{output_dir}/region_{region_name}.shp subset.to_file(filename, encodingutf-8) print(f已输出 {region_name}{len(subset)} 个要素文件 {filename})逻辑说明遍历汇总文件里的每个大区名称筛选后单独输出。参数说明上文件名直接用中文在 Windows 下通常没问题但如果后续要在某些 Web 平台或命令行工具里用中文路径容易翻车所以这里保留中文名称但建议实际使用时替换为拼音或英文缩写。另外Shape 文件是一组文件.shp、.shx、.dbf、.prj输出时确保这四个文件都在同一目录缺一个都打不开。3.3 属性表设计让汇总和拆分都能追溯一套好的区划 Shape 文件属性表应该同时支持汇总和追溯。汇总文件里至少要有region字段拆分文件里除了region还应该有province列表或省份数量方便核对。我一般会在汇总文件里加两个字段province_count该区划包含的省级单元数量和province_list逗号分隔的省份名称这样拿到文件的人不用回去翻原始数据就能知道口径。# 为汇总文件补充省份数量和列表 province_stats gdf.groupby(region).agg( province_count(province_clean, count), province_list(province_clean, lambda x: ,.join(sorted(x))) ).reset_index() region_gdf region_gdf.merge(province_stats, onregion, howleft) region_gdf.to_file(seven_regions_with_stats.shp, encodingutf-8) # 打印核对 for _, row in region_gdf.iterrows(): print(f{row[region]}{row[province_count]} 个省级单元 - {row[province_list]})逻辑说明用groupby统计每个大区的省份数量和名称列表再合并回汇总文件。参数说明上lambda x: ,.join(sorted(x))把省份名称排序后用逗号连接方便阅读。这一步看起来多余但在实际项目里能省掉大量核对时间——当有人质疑“为什么华东有 7 个省”时直接看属性表就能解释。4. 坐标系与边界处理别让投影问题毁掉整套数据4.1 地理坐标系和投影坐标系的选择Shape 文件最常见的翻车点之一是坐标系不统一。省级原始数据可能是 WGS84 地理坐标系EPSG:4326做面积统计时需要投影坐标系比如 Albers 等面积投影做 Web 可视化时又需要 Web MercatorEPSG:3857。如果一套文件里混了多个坐标系叠加分析时边界会偏移面积计算会出错。我的做法是汇总文件和拆分文件统一用 WGS84 地理坐标系存储因为这是最通用的交换格式如果要做面积统计在分析脚本里临时投影不改变原始文件。这样既保证了数据通用性又避免了反复投影带来的精度损失。# 统一坐标系为 WGS84 if region_gdf.crs is None: region_gdf region_gdf.set_crs(EPSG:4326) else: region_gdf region_gdf.to_crs(EPSG:4326) # 面积统计时临时投影到 Albers以中国为例的常用参数 albers_crs projaea lat_125 lat_247 lat_00 lon_0105 x_00 y_00 datumWGS84 unitsm no_defs region_albers region_gdf.to_crs(albers_crs) region_albers[area_km2] region_albers.geometry.area / 1e6 for _, row in region_albers.iterrows(): print(f{row[region]}{row[area_km2]:.0f} 平方公里)逻辑说明先确保数据有坐标系定义没有就设为 WGS84有就转换到 WGS84。面积统计时用 Albers 等面积投影参数里lat_1和lat_2是标准纬线lon_0是中央经线这套参数在国内区域统计里比较常用。参数说明上unitsm保证面积单位是平方米除以1e6得到平方公里。注意不同投影参数算出的面积会有差异如果项目有指定投影以项目要求为准。4.2 边界缝隙与重叠的排查省级边界拼接成大区边界时常见问题是相邻省份之间有缝隙或重叠dissolve 之后这些瑕疵会被放大。缝隙通常来自原始数据精度不一致重叠则可能是数据源本身有拓扑错误。排查方法是先做拓扑检查再决定是否修复。# 检查大区内部的几何问题 for _, row in region_gdf.iterrows(): geom row[geometry] if not geom.is_valid: print(f{row[region]} 几何无效尝试修复) fixed geom.buffer(0) if fixed.is_valid: print(f{row[region]} 修复成功) else: print(f{row[region]} 修复失败需要人工检查) else: print(f{row[region]} 几何有效)逻辑说明逐个检查大区几何的有效性无效的用buffer(0)尝试修复。参数说明上buffer(0)对大多数自相交和重复节点有效但对复杂的拓扑错误可能无效这时需要回到省级数据逐个省检查。实际项目中我遇到过某省边界有自相交导致 dissolve 后整个大区几何无效的情况最后是单独修复那个省再重新融合。4.3 简化边界以减小文件体积Shape 文件对文件体积比较敏感尤其是高精度边界数据七个大区加起来可能几十兆。如果只是做可视化或粗略统计可以适当简化边界减小文件体积。但简化会改变边界形状做精确面积统计时不能用简化后的数据。# 简化边界容差根据精度需求调整 region_simplified region_gdf.copy() region_simplified[geometry] region_simplified[geometry].simplify(tolerance0.01, preserve_topologyTrue) region_simplified.to_file(seven_regions_simplified.shp, encodingutf-8) # 对比简化前后节点数量 for idx, row in region_gdf.iterrows(): original_coords len(row[geometry].exterior.coords) simplified_coords len(region_simplified.loc[idx, geometry].exterior.coords) print(f{row[region]}简化前 {original_coords} 个节点简化后 {simplified_coords} 个节点)逻辑说明simplify用 Douglas-Peucker 算法减少节点数量tolerance0.01是容差单位是度因为数据是 WGS84大约相当于 1 公里。参数说明上preserve_topologyTrue保证简化后几何仍然有效不会出现自相交。容差越大文件越小但边界越粗糙一般可视化用 0.01 到 0.05 之间精确分析用 0.001 以下或干脆不简化。5. 避坑与排查七个最容易翻车的点5.1 省份归属口径不一致导致汇总对不上现象汇总文件里华东区的省份数量和统计年鉴对不上或者某个省在汇总文件里消失了。原因省份归属对照表和原始数据的省级名称没有完全匹配merge 时howleft导致未匹配的省region为空dissolve 时被丢弃。解决merge 后必须检查region为空的行打印出来逐个核对名称写法。我一般会在脚本里加一个断言未匹配数量大于 0 就直接报错不往下走。5.2 dissolve 后属性丢失或错乱现象汇总文件里region字段是对的但其他属性变成了某个省的值或者直接为空。原因dissolve的aggfunc默认只对数值字段做聚合字符串字段取第一条如果原始数据字段多且杂结果不可控。解决dissolve 之前只保留region和geometry两列其他字段先归档到单独的 CSV需要时再通过region关联回来。5.3 坐标系缺失导致叠加偏移现象把区划 Shape 文件加载到 GIS 软件里和底图叠加时边界偏移几百米到几公里。原因Shape 文件没有.prj文件或者.prj里的坐标系定义和实际数据不符。解决拿到数据第一件事就是检查crs属性没有定义就手动设置定义不对就转换。如果.prj文件丢失可以根据数据来源判断坐标系国内数据常见的是 WGS84 或 CGCS2000两者在多数场景下差异可忽略。5.4 中文属性乱码现象在 QGIS 里打开 Shape 文件属性表里的中文显示为乱码。原因Shape 文件的.dbf文件对编码支持有限默认可能是 Latin-1 或系统编码。解决输出时指定encodingutf-8如果目标软件不支持 UTF-8可以输出为 GBK 或同时输出一份 GeoJSON。GeoJSON 对 UTF-8 支持更好适合跨平台交换。5.5 单个区划文件缺少省份信息现象拆分出来的华东区 Shape 文件只有几何和region字段想知道包含哪些省还得回去翻汇总文件。原因拆分时只筛选了几何没有保留省份属性。解决拆分前先把省份列表和数量写进汇总文件的属性表拆分时这些字段会一起带过去。如果已经拆完了可以用sjoin把省级数据重新关联回来。6. 进阶用法用一套数据支撑多口径切换与自动化校验6.1 多口径切换把省份归属做成可配置项七大区划的省份归属在不同项目里可能不同与其每次改脚本不如把归属规则做成配置文件。我一般用 YAML 或 JSON 维护多套口径每套口径一个文件脚本读取时指定口径名称即可。import json # 多套口径配置 schemes { standard: { 华北: [北京市, 天津市, 河北省, 山西省, 内蒙古自治区], 东北: [辽宁省, 吉林省, 黑龙江省], 华东: [上海市, 江苏省, 浙江省, 安徽省, 福建省, 江西省, 山东省], 华中: [河南省, 湖北省, 湖南省], 华南: [广东省, 广西壮族自治区, 海南省], 西南: [重庆市, 四川省, 贵州省, 云南省, 西藏自治区], 西北: [陕西省, 甘肃省, 青海省, 宁夏回族自治区, 新疆维吾尔自治区], }, inner_mongolia_east: { 华北: [北京市, 天津市, 河北省, 山西省], 东北: [辽宁省, 吉林省, 黑龙江省, 内蒙古自治区], 华东: [上海市, 江苏省, 浙江省, 安徽省, 福建省, 江西省, 山东省], 华中: [河南省, 湖北省, 湖南省], 华南: [广东省, 广西壮族自治区, 海南省], 西南: [重庆市, 四川省, 贵州省, 云南省, 西藏自治区], 西北: [陕西省, 甘肃省, 青海省, 宁夏回族自治区, 新疆维吾尔自治区], }, } with open(region_schemes.json, w, encodingutf-8) as f: json.dump(schemes, f, ensure_asciiFalse, indent2) print(已保存两套口径standard 和 inner_mongolia_east)逻辑说明把不同口径的省份归属写成 JSON脚本根据项目需求选择对应口径。参数说明上ensure_asciiFalse保证中文正常写入indent2方便人工阅读和修改。这样做的价值在于当项目要求从“内蒙古整体归华北”切换到“内蒙古归东北”时只需要改配置文件名不用动脚本逻辑。6.2 自动化校验每次生成后自动核对省份数量和面积数据生成后人工核对容易漏我习惯加一段自动校验检查每个大区的省份数量是否和配置一致、面积是否在合理范围内。如果校验不通过脚本直接报错退出避免把错误数据交给下游。def validate_regions(gdf, scheme): errors [] for region_name, provinces in scheme.items(): subset gdf[gdf[region] region_name] if len(subset) 0: errors.append(f{region_name} 没有匹配到任何省级单元) continue actual_count subset[province_clean].nunique() expected_count len(provinces) if actual_count ! expected_count: errors.append(f{region_name} 省份数量不符期望 {expected_count}实际 {actual_count}) # 面积粗查每个大区面积应在 10 万到 200 万平方公里之间 area subset.geometry.area.sum() if area 0.01 or area 20: errors.append(f{region_name} 面积异常{area:.4f}度平方) return errors errors validate_regions(gdf, schemes[standard]) if errors: for e in errors: print(校验失败, e) else: print(校验通过可以输出文件)逻辑说明遍历配置里的每个大区检查实际匹配到的省份数量和配置是否一致同时粗查面积是否在合理范围。参数说明上面积阈值用的是度平方因为数据是 WGS840.01 到 20 度平方大约对应 10 万到 200 万平方公里具体阈值可以根据实际数据调整。这段校验代码看起来简单但能拦住大部分因为名称不匹配或数据缺失导致的问题。6.3 输出格式的取舍Shape 文件不是唯一选择虽然标题里说的是 Shape 文件但实际项目中我通常会同时输出 GeoJSON 和 GeoPackage。Shape 文件的优势是兼容性好几乎所有 GIS 软件都能打开缺点是字段名长度限制、编码问题、文件体积大。GeoJSON 适合 Web 端和跨平台交换GeoPackage 适合本地数据库管理。如果项目允许建议至少输出 Shape 和 GeoJSON 两份Shape 给传统 GIS 用户GeoJSON 给开发人员。# 同时输出三种格式 region_gdf.to_file(seven_regions.shp, encodingutf-8) region_gdf.to_file(seven_regions.geojson, driverGeoJSON) region_gdf.to_file(seven_regions.gpkg, driverGPKG, layerregions) print(已输出 Shape、GeoJSON、GeoPackage 三种格式)逻辑说明同一份数据用不同 driver 输出满足不同使用场景。参数说明上GeoJSON 默认 UTF-8不需要额外指定编码GeoPackage 支持多图层layer参数指定图层名。注意 Shape 文件的字段名不能超过 10 个字符如果属性表字段名较长输出前要重命名否则会被截断。这套流程我用了几年最大的体会是区划数据本身不复杂复杂的是口径和坐标系。每次接手新项目先花十分钟确认省份归属和坐标系比后面花两小时排查边界偏移划算得多。希望帮到你。本文还有配套的精品资源点击获取