甘肃矢量shp数据处理全指南:投影统一、字段清洗与避坑实战

发布时间:2026/10/12 13:41:08
甘肃矢量shp数据处理全指南:投影统一、字段清洗与避坑实战
简介这份数据包是一套面向 GIS 学习者、城乡规划与地理信息从业者的甘肃省基础地理数据可直接用于地图制图、空间分析和专题研究。压缩包共包含 225 个文件体积约 17.3MB核心为 39 组 Shapefile 矢量图层每组由 .shp 几何信息、.dbf 属性表、.shx 索引及 .prj 坐标系统文件构成另附 .sbn/.sbx 空间索引与 4 个 ArcGIS 工程文档。数据内容覆盖省界、地级市、县界、乡镇名等面状行政边界公路、国道、省道、县乡道、单线水系等线状要素以及火车站、加油站、旅游景点、建筑物等点状设施要素分层清晰、命名规范可灵活叠加检索。借助这些矢量数据用户可在 ArcGIS、QGIS 等软件中分析交通网络连通性、水资源分布与旅游资源潜力也可转换输出为 GeoJSON、KML 等通用格式以便共享。目前已有 3028 人学习下载适合作为甘肃省地理教学、城市研究和规划决策的实用数据基础。1. 甘肃基础数据完整版拿到手之后先别急着“全选加载”拿到一份命名为“甘肃基础数据完整版矢量 shp 格式”的压缩包多数人第一步是解压、拖进 QGIS、全选加载。结果通常是省界、县界、乡镇界、水系、道路一起铺满画布颜色乱、位置漂、属性表里一堆乱码。这类 shp 数据的价值不在文件多全而在坐标系、属性结构、编码方式能不能撑住后续的拼接、裁剪和统计分析。甘肃东西跨度大县域行政区划边界 shp 常按 3 度带分带存储相邻两县的中央经线可能不同不做投影统一就直接叠加边界就会在肉眼可见的尺度上错开。这篇按一线处理顺序来解压盘点、投影与裁剪、字段清洗、导出与二次开发、避坑清单最后用渔网分割 shp 做一次可复验的数据体检。适合刚开始接触矢量 shp 数据或者正在做甘肃相关制图、数据分析和 GIS 二次开发的人照着跑一遍。2. 解压与盘点从 .rar 到能打开的第一份 .shp 要处理的 5 件事2.1 中文文件名解压乱码用 Python 接管而不是双击解压Windows 上双击解压 .rar常见结果是解出一堆像“鏉傚悍”这样的文件名。这不是压缩包坏了而是打包时文件名用了 GBK 编码解压工具却按 UTF-8 解码。对 shp 数据这种以文件名为线索的资料包名字丢了等于索引丢了。我一般用 Python 接管解压import rarfile # rarfile 依赖系统里的 unrar/rar 可执行文件没有的话会直接抛异常 rf rarfile.RarFile(甘肃基础数据完整版矢量shp格式.rar) for item in rf.infolist(): rf.extract(item, path./data/) print(解压完成)这段代码逐个读取压缩包内的文件条目并按原名解压避免默认工具把中文路径二次编码。rf.infolist()返回的是文件元数据列表不占内存如果你只是想看一眼里面有哪些图层可以先rf.namelist()打印文件名确认之后再动手解压。Windows 上如果嫌装 unrar 麻烦也可以用 7-Zip 或 Bandizip 手动解压但要注意把“文件名编码”选项切到 ANSI/GBK而不是默认的 UTF-8。2.2 一个图层不止一个文件shp/shx/dbf/prj 缺一不可很多人以为 .shp 是地图文件拷给同事的时候只发了一个 .shp结果对方打开只有图形没有属性表或者干脆打不开。一个完整的 shapefile 图层至少由四个伴生文件组成.shp 存几何、.shx 存几何索引、.dbf 存属性表、.prj 存坐标系定义有些包还会带 .cpg编码声明和 .xml元数据。解压后第一件事是检查每个图层是否都带齐这四个文件。for f in ./data/*.shp; do echo $f ogrinfo -so $f | head -n 6 doneogrinfo来自 GDAL装 QGIS 时会一并装上。-so是 summary only只输出图层类型、要素数量、字段列表不扫描全部几何速度很快。如果你没装命令行工具也可以在 QGIS 的图层属性——元数据面板里看到同样的信息。这一步能快速确认每个 shp 是不是一个正常可读的图层也能发现哪些目录下混进了空文件。2.3 坐标范围一眼判断经纬度还是投影坐标拿到 shp 先看范围和坐标系这决定了你后面所有操作的路径。用 Python 读入一个文件打印出 total_bounds 和 crsimport geopandas as gpd gdf gpd.read_file(./data/某县.shp, encodingutf-8) print(gdf.total_bounds) # [minx, miny, maxx, maxy] print(gdf.crs)甘肃的经度大致在 92°E 到 109°E纬度在 32°N 到 43°N。如果 total_bounds 里的 x 是 92~109 这样的数字说明数据是经纬度的地理坐标如果 x 是 5 位数如 500000 左右或者 8 位数如 35500000那就是投影坐标。投影坐标里x 是 35500000 这种带带号的大数往往是 CGCS2000 3 度带高斯投影x 是 500000 左右的则是去掉了带号的写法。这个判断不需要懂投影公式但能帮你避开后面 90% 的“图层跑到海里”问题。2.4 属性字段和空值决定数据能不能直接用于统计坐标系只是第一步属性表的结构同样关键。很多“完整版”数据包的属性字段名是拼音缩写或代码空值率也参差不齐。用一段小代码把所有字段的类型和空值数量列出来for col in gdf.columns: print(col, gdf[col].dtype, gdf[col].isna().sum())重点看两类字段。一类是行政区划代码甘肃的省代码是 62市级代码是 6201、6202、6203 这样的 4 位或 6 位数字这种字段建议用dtypestr读取避免被当成整数导致后续拼接时对不上。另一类是名称字段常见问题包括全角空格、省市区后缀混用比如同一个字段里既有“甘肃省”又有“甘肃”这在后面做关联时要先清洗。2.5 缺 shx 或 dbf 时的补救ogr2ogr 重建伴生文件解压过程中偶尔会出现文件缺失最常见的是缺 .shx 或 .dbf。缺 .shx 时很多软件只能扫描式读取打不开属性缺 .dbf 则直接没有属性表。遇到这种情况常见做法是用 ogr2ogr 把原始 shp 重写一遍ogr2ogr repaired.shp raw.shp这个命令以 raw.shp 为源新建一个完整的 repaired.shp同时补齐 shx/dbf/prj。参数顺序是“目标在前源在后”这一点和很多 Linux 命令相反容易记错。如果 .dbf 本身已经损坏重建出来的属性表可能是空的所以解压后先复制一份原始包再动手是我做所有数据预处理前的固定动作。GIS 二次开发里传参时只传一个 .shp 文件也是常见翻车现场要把整个伴生文件组放进同一个目录。3. 把甘肃底图变成能用的分析底图投影、裁剪与字段清洗3.1 甘肃数据到底该用哪个投影先看用途再选参数甘肃横跨约 17 个经度县级的 shp 数据最常见的是 CGCS2000 3 度带高斯投影中央经线按县所在地单独设定相邻县的中央经线不同直接叠加就会出现边界错位。做全省一张图时我习惯把数据统一到一个中央经线常用的是 105°E因为甘肃中部大致在 104°E~106°E 之间105 作为全图中轴误差相对均衡。做高精度的面积统计或工程分析则建议保留原始分带不强行统一。另一个常见选择是 Albers 等积投影或兰勃特投影适合全省范围的面积统计。但要注意基础数据包里的原始投影多半是高斯投影你转成 Albers 是分析需要不能把两者混着叠加。判断一个文件是不是高斯投影看 prj 里有 tmerc 或者 Gauss-Kruger 字样如果 crs 是 EPSG:4490那就是 CGCS2000 经纬度不能直接算面积需要先投影。3.2 统一投影ogr2ogr 指定 tmerc 参数带号写在 x_0 里给甘肃数据统一投影一条常用命令是这样ogr2ogr gansu_105.shp gansu_raw.shp \ -t_srs projtmerc lat_00 lon_0105 k1 x_035500000 y_00 ellpsGRS80 unitsm这里目标坐标系用的是一套完整的 proj4 参数不需要猜 EPSG 编号。projtmerc是横轴墨卡托投影也就是高斯-克吕格投影的等价描述lon_0105是中央经线k1是比例因子3 度带通常取 16 度带通常取 0.9996最关键的是x_035500000这个值由“500000 带号 × 1000000”组成35 表示 3 度带的第 35 带中央经线正好是 35 × 3 105。如果你把中央经线换成其他值x_0 要跟着改否则坐标会出现约几十公里的伪偏移。如果原始文件没有 .prj直接-t_srs会报错因为 ogr2ogr 不知道源坐标系。这种情况先要确定原始投影带号再用-a_srs或-s_srs强制指定。判断带号的办法也可以反推看坐标里 x 的前缀比如 x_0 是 35500000 而不是 35000000这多出的 500000 就是假东偏移量。3.3 用县域行政区划边界 shp 裁剪ogr2ogr 和 Python 两种做法做分析时经常需要从全省数据里提取某一个县。ogr2ogr 直接支持用面图层做裁剪范围命令是ogr2ogr output_tianshui.shp landuse.shp \ -clipsrc county.shp \ -clipsrcwhere NAME天水市-clipsrc county.shp指定以 county.shp 的几何作为裁剪边界这比-spat按矩形范围裁剪要精确得多不会把边界外的碎块带进来。-clipsrcwhere则是先用 SQL 条件挑出天水市这一条记录再裁。这里的前提是 landuse.shp 和 county.shp 已经在同一个坐标系下如果源文件是分带的先做 3.2 的统一投影再裁剪顺序不能反。Python 侧更灵活适合批量处理import geopandas as gpd county gpd.read_file(county.shp, encodingutf-8) land gpd.read_file(landuse.shp, encodingutf-8) # 先只保留目标区县 target county[county[NAME] 天水市] clipped gpd.clip(land, target) clipped.to_file(tianshui_land.shp, encodingutf-8)gpd.clip会保留与目标几何相交的所有要素并按目标边界切割。注意to_file里指定encodingutf-8这一步常被省略结果生成的文件在部分 GIS 软件里再次乱码。裁剪结果如果出现空白优先检查两个文件的 crs 是否一致而不是怀疑数据是空的。3.4 字段清洗行政区划代码要用文本读名称别乱替换属性表清洗的四个高频动作是去空格、去全角字符、统一行政区代码位数、保留原始字段副本。代码写法如下# 字段名里的空格和全角空格 gdf.columns [c.strip().replace( , ) for c in gdf.columns] # 名称字段去掉残留的空行 gdf[区县名] gdf[区县名].astype(str).str.strip() # 行政区划代码统一补零到 12 位 gdf[区划代码] gdf[区划代码].astype(str).str.zfill(12)行政区划代码是数据关联的主键比名称字段可靠得多。甘肃的代码有一个规律省是 62市是 6201/6202/6203 这样的四位区县是六位乡镇是九位村是十二位。用astype(str)转文本后再用zfill(12)补足前导零可以避免 0 开头的代码在 Excel 或 GIS 属性表里被吞掉。名称字段只适合给人看不适合做匹配两个图层里一个写“天水市”另一个写“天水”名称匹配一定翻车。3.5 面积计算的地雷地理坐标算出的面积是“度²”这是甘肃 shp 数据最常踩的坑之一。直接用经纬度坐标算面积geometry.area返回的是度²不是平方米数值完全不能用于统计。正确的做法是先把数据投影到米制坐标系再算面积proj projtmerc lat_00 lon_0105 k1 x_035500000 y_00 ellpsGRS80 unitsm gdf gdf.to_crs(proj) gdf[面积_公顷] gdf.geometry.area / 10000area / 10000把平方米换算成公顷这是县域统计里常用的单位。对于甘肃这样一个跨带省份如果数据分带存储且没统一投影面积误差可能达到几个百分点。要求更高时改用 Albers 等积投影单纯看比例关系则无所谓但千万别把度²直接写进报告里。4. 导出与二次开发shp 转 txt、GeoJSON、3dtiles 与 dwg 转 shp4.1 shp 转 txt先把“坐标”和“属性”分清楚“GIS 矢量如何转 txt”是高频需求但很多人没想清楚自己到底要什么。如果只是把属性表导出来ogr2ogr 用 CSV 驱动最省事ogr2ogr -f CSV attrs.csv in.shp -lco GEOMETRYAS_XYZGEOMETRYAS_XYZ会把几何写成 x、y、z 三列每条要素对应一行面和线的所有折点会展开成多行并且每行都带全量属性所以导出的 CSV 可能比你想象的更臃肿。如果只想导出不带几何的属性表可以去掉这个参数或者用 python 只取字段列。如果想把每个面要素的质心坐标导成 txt我习惯用 geopandasimport geopandas as gpd gdf gpd.read_file(county.shp, encodingutf-8) gdf gdf.to_crs(4326) # 转成经纬度便于直接阅读 centroid gdf.geometry.centroid out gpd.GeoDataFrame({name: gdf[区县名], lng: centroid.x, lat: centroid.y}) out.to_csv(county_centroid.txt, sep,, indexFalse)这里to_crs(4326)是把坐标转成常见经纬度方便下游程序直接使用。centroid.x和centroid.y是 GeoSeries 的属性不需要再遍历坐标。要注意的是质心不等于几何内部点如果一个县是 U 形或带岛屿质心可能落在境外或湖面上做这种导出时最好用representative_point()替代centroid。4.2 shp 转 GeoJSON给 Web 端二次开发用Web 端 GIS 二次开发添加 shp 数据时浏览器不能直接读取 shapefile 伴生文件组常见做法是后端把 shp 转成 GeoJSON 再返回。转换命令很简单ogr2ogr -f GeoJSON out.geojson in.shp -t_srs EPSG:4326强制转成 EPSG:4326 是因为大多数 Web 地图引擎只接受经纬度坐标。GeoJSON 文件会把属性和几何写在一起单文件分发比 shapefile 四件套方便得多。如果你的前端框架支持矢量瓦片还可以用 ogr2ogr 转 MVT 格式但 MVT 对字段类型和坐标范围有额外限制数据量大的时候再考虑。4.3 shp 转 3dtiles先查几何有没有 Z 值shp 转 3dtiles 的常规路径是shp 先转 GeoJSON再由开源切片工具或三维平台生成 3dtiles。这里有一个前置检查你的 shp 几何有没有高程 Z 值。没有 Z 值生成的 3dtiles 是平的建筑或地形全贴在地面上。import geopandas as gpd gdf gpd.read_file(buildings.shp, encodingutf-8) print(gdf.geometry.has_z) # True 表示带 ZFalse 表示没有has_z是 GeoSeries 的快速检查方法返回一个布尔序列全 False 说明这个 shapefile 只有二维坐标。3dtiles 切片如果工具要求 Z 值需要先在 GIS 软件里给要素赋值高度如果只是把甘肃的行政区边界放到 Cesium 里看轮廓二维坐标也能用工具会在渲染时做默认贴地处理。4.4 dwg 转 shp别让 ogr2ogr 直接啃 DWG数据包里如果混着 CAD 格式的底图dwg 转 shp 的落地姿势要稳一点。ogr2ogr 直接读 DWG 需要额外装 DWG 读取插件很多人没装就报错。我一般让 CAD 那边先另存为 DXF 格式再用 ogr2ogr 转ogr2ogr -f ESRI Shapefile out.shp in.dxf -t_srs EPSG:4490DXF 是公开文档格式GDAL 原生支持转换稳定性比硬啃 DWG 好得多。CAD 数据转过来以后常见问题包括线没有闭合、面边界有重复折点、图层的属性只有图层名没有业务字段。这些都要回到第 5 章的拓扑检查去处理不能转完就直接用。4.5 导出格式选型速查目标格式常用命令 / 做法适用场景txt / CSVogr2ogr -f CSV 或 pandas 导出程序入参、给非 GIS 同事看数据GeoJSONogr2ogr -f GeoJSONWeb 二次开发、前后端交换MVT 矢量瓦片ogr2ogr -f MVT大范围地图渲染3dtilesshp→GeoJSON→切片工具Cesium 等三维场景shapefileogr2ogr 重建伴生文件交付给 ArcGIS/QGIS 用户这个表基本覆盖主流需求。选格式的核心标准是下游系统能直接消费什么而不是哪个格式“更高级”shp 转 3dtiles 之前先想清楚你需要的可能只是一个能加载到 Cesium 的 GeoJSON。5. 甘肃 shp 数据避坑清单编码、跨带、拓扑、面积与字段匹配5.1 属性表中文乱码默认 UTF-8 读 GBK 文件现象QGIS 里打开属性表中文显示成“锟斤拷”“鎵掕”这类乱码英文和数字正常。原因shapefile 的 dbf 属性表在国产或老数据包里普遍用 GBK/GB18030 编码而新版 QGIS 和 geopandas 默认按 UTF-8 解码两边对不上。解决geopandas 读取时指定编码import geopandas as gpd for enc in [utf-8, gbk, gb18030]: try: gdf gpd.read_file(data.shp, encodingenc) print(读取成功, enc) break except UnicodeDecodeError: continue这段代码按顺序尝试三种常见编码直到成功为止。如果三种都失败说明 dbf 文件本身已经损坏考虑用 2.5 的重建方法处理。修复乱码后建议另存为encodingutf-8的新 shp 再分发给下游避免别人重复踩同一个坑。5.2 相邻县边界错位同一个包里有多个投影带现象把两个相邻县的边界 shp 叠进同一张地图边界在对角方向错开缝隙或重叠肉眼可见。原因甘肃东西跨度大不同县的 shp 可能各自采用不同的 3 度带投影一个县用 105°E 中央经线隔壁县用 99°E直接在未经投影转换的图层里叠加位置就会漂。解决先筛查每个图层的带号再统一投影。带号可以直接从 prj 里看for f in ./data/*.shp; do echo $f [ -f $f.prj ] grep -i lon_0 $f.prj done看到 lon_0 各不相同就用第 3 章的 ogr2ogr 命令统一转到一个中央经线。注意转换要从原始坐标系转不能在已经错位的图层上反复转否则误差会叠加。这个问题在第 2 章盘点时就该发现而不是等地图拼完才查。5.3 要素跑到海里或境外prj 缺失或坐标系被误判现象甘肃的县界出现在非洲或太平洋或者明明打开的是甘肃数据画布默认定位到经纬度 0,0 附近。原因shp 文件缺少 .prj 定义软件把它默认当 WGS84 经纬度显示而实际坐标是投影坐标比如 35500000, 4000000 这样的大数两者混用必然飞。解决先看坐标范围再强制指定源坐标系ogr2ogr -s_srs projtmerc lat_00 lon_0105 k1 x_035500000 y_00 ellpsGRS80 \ -t_srs EPSG:4326 out_wgs.shp in.shp-s_srs是强制声明源坐标系适合没有 prj 的老文件。前提是你通过坐标范围反推出正确的带号反推办法就是看 x 坐标前缀35500000 是 3 度带 35 带20000000 再加上 500000 就对应其他带。乱猜带号会让数据错得更隐蔽所以这个参数要慎用。5.4 自相交多边形拓扑检查失败面积算不准现象用 GEOS 的拓扑检查报 “Ring Self-intersection”或者一个面要素在面积统计里出现负值、异常大值。原因矢量化时节点顺序错乱或折点重复多边形环自相交基础数据包越靠人工数字化这个问题越常见。解决用 shapely 的make_valid修复from shapely.validation import make_valid gdf[geometry] gdf.geometry.apply(make_valid) gdf.to_file(fixed.shp, encodingutf-8)make_valid会把自相交环拆分成合法几何但拆分出来的结果可能是 MultiPolygon还可能产生极小的碎面。修复后重新检查要素数量和面积分布如果碎面数量多说明原始数据质量较差单靠修复不够要回到源头换数据或重新矢量化。5.5 按区县名匹配不上简称全称混用代码才是主键现象用“天水市”去匹配“天水市麦积区”总是失败或者一张表里写“甘肃”另一张写“甘肃省”。原因名称字段没有统一规范有的带省后缀有的是简称有的还混进了乡镇和街道名。解决不要用名称做关联改用行政区划代码。甘肃的区划代码规律是省 62、市 4 位、区县 6 位、乡镇 9 位、村 12 位代码不存在简称问题。处理时先看字段分布print(gdf[区县名].value_counts().head(20))这行代码能快速暴露名称字段里的脏值。之后再按代码字段关联名称列只保留给人看。如果两个数据源里一个代码是 6 位、一个是 12 位用astype(str).str.zfill(12)统一位数再匹配避免 620102 和 620102000000 对不上。6. 进阶验证用渔网分割 shp 做一次数据体检数据包解压完、投影统一完、字段也清干净了怎么确认它真的“完整”我的习惯是做一次渔网体检按固定步长生成网格叠加道路或水系数据再统计每个网格里有没有要素。大面积空网格往往意味着数据缺失或者坐标系还没统一。import geopandas as gpd from shapely.geometry import box # 读取县域边界并投影到米制单位 county gpd.read_file(county.shp, encodingutf-8) county county.to_crs(projtmerc lat_00 lon_0105 k1 x_035500000 y_00 ellpsGRS80) # 按 1km 步长生成覆盖全县的渔网 minx, miny, maxx, maxy county.total_bounds step 1000 grids [] for x in range(int(minx // step), int(maxx // step) 1): for y in range(int(miny // step), int(maxy // step) 1): grids.append(box(x * step, y * step, (x 1) * step, (y 1) * step)) grid_gdf gpd.GeoDataFrame({cell: range(len(grids))}, geometrygrids, crscounty.crs) # 用县界裁剪渔网避免把边界外的网格算进来 grid_intersect gpd.overlay(grid_gdf, county, howintersection) # 叠加道路数据找出没有道路经过的空网格 roads gpd.read_file(roads.shp, encodingutf-8).to_crs(county.crs) joined gpd.sjoin(roads, grid_intersect, howinner, predicateintersects) empty_cells set(grid_intersect.cell) - set(joined.cell) print(空网格数量, len(empty_cells), /, len(grid_intersect))step 1000表示网格边长 1 公里省域数据可以调到 5000 或 10000市县级数据 1000 比较直观。gpd.overlay(grid_gdf, county, howintersection)会把原始方格按县界切割县城弧形边界处的碎网格不会被误判成空值。gpd.sjoin做空间连接时会自动建索引速度比逐格遍历快得多predicateintersects表示只要道路碰过这个格子就算有数据。看到空网格后把empty_cells对应的网格另存成 shp在 QGIS 里叠加原始数据看分布。如果空格集中在高海拔山区那是正常情况如果空格呈条带或半边分布多半是坐标系错位回到第 3 章重新统一投影。我自己第一次做甘肃全省底图就是栽在跨带拼接上当时以为数据缺了一大片折腾半天才发现是两个县的 prj 带号不同。现在拿到任何 shp 包我都先建一个原始备份目录所有预处理只往新目录写原始包完全不碰。这个习惯救过我很多次希望帮到你。本文还有配套的精品资源点击获取