“GDAL 实现矢量合并”,这标题看着简单,实际是搞 GIS 数据处理绕不开的一个核心操作。尤其是当你手里攒了一堆分幅的 shapefile、一堆按行政区切的 GeoJSON,或者从不同渠道拿到的同类矢量数据,要合成一张完整的图发出去、做分析或者入库,这个需求就立刻跳出来了。网上关于 ogr2ogr 的教程不少,但很多都是把命令行一贴就没下文了,合并完了怎么办、遇到属性对不上怎么办、大数据量卡死怎么办,这些真正耗时间的问题反而没人细讲。
这篇文章我把自己用 GDAL 做矢量合并的完整思路和踩过的坑整理出来,从最基础的命令行工具到 Python 批处理,再到百万级要素的性能优化,一次性讲透。
1. 内容整体设计与思路拆解
1.1 为什么用 GDAL 而不是 FME 或 ArcGIS
很多人第一反应是用 FME 或者 ArcGIS 的 Merge 工具做合并,这没问题,但我的主力环境是 Linux 服务器,数据量动不动就是几十个 G,桌面软件根本扛不住。GDAL 是命令行工具,资源占用低,Python 绑定又非常成熟,放在服务器上做批处理、定时任务都特别顺手。
用 GDAL 做矢量合并,核心逻辑其实就一句话:把多个数据源的几何要素读取出来,统一投影和字段结构,写入一个新的数据源。这个过程中真正决定成败的,不是命令本身,而是你对数据源格式、字段规则、坐标系这几个细节的掌控程度。
我之前接手过一个项目,要合并某区域 400 多个分幅的国土调查数据,每个分幅都是一个独立 shapefile,总大小接近 15GB。如果用 ArcGIS,光打开图层列表就能卡半天,更别提合并了。后来用 GDAL 写了个批处理脚本,分区域并行跑,四十分钟左右全部合并完成。
1.2 合并流程的三种常见场景
矢量合并不是只有“把文件堆一起”这一种玩法,实际业务里通常分成三种场景:
场景一:同构数据合并。多个 shapefile 字段名、字段类型、坐标系完全一致,比如都是从同一个软件导出的分幅数据,这种是最简单的,直接用 ogr2ogr 追加写入就行。
场景二:异构数据合并。不同来源的数据,字段名称不一样但含义相同,比如 A 文件的“NAME”字段和 B 文件的“XZQMC”字段实际都是村名。这种需要先做字段映射,统一结构后再合并。
场景三:数据入库整合。合并的最终目的是写入 PostGIS 或者 GeoPackage,这时候不仅要合并,还要考虑数据类型转换、空间索引创建、属性约束等一系列问题。
搞清楚自己属于哪种场景,再选对应的方案,比一上来就抄命令高效得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心细节解析与实操要点
2.1 认识 ogr2ogr 和 ogrmerge.py 的区别
GDAL 工具集里能完成矢量合并的有两个主力:一个是老牌的 ogr2ogr,一个是专门做合并的 ogrmerge.py。很多人不知道这两个工具到底怎么选,我直接说结论:
ogr2ogr 灵活度极高,适合做异构数据合并、字段映射、格式转换和坐标变换,但它的合并逻辑是“追加写入”,也就是你先确定一个目标文件,然后把其他文件的数据依次追加进去。
ogrmerge.py 是 GDAL 2.2 之后提供的专门脚本,适合同构数据的快速合并,尤其是目录下一堆分层文件要合成一个图层的时候,一行命令就能搞定,还能自动处理图层命名。
举两个典型的操作示例。先用 ogr2ogr 合并两个 shapefile:
bash复制ogr2ogr -f "ESRI Shapefile" merged.shp input1.shp
ogr2ogr -f "ESRI Shapefile" merged.shp input2.shp -append -nln merged
第一行创建新文件,第二行用 -append 追加第二个文件的数据。这里有一个关键点:-nln merged 必须写,它指定追加到哪个图层。如果不写,ogr2ogr 默认按照文件名生成图层名,追加的时候就会新建一个图层而不是追加到已有图层。
再用 ogrmerge.py 合并整个目录下的 GeoJSON:
bash复制ogrmerge.py -f GeoJSON -o merged.geojson /data/shp/*.geojson
这个命令会自动扫描目录下所有 GeoJSON 文件,合并成一个图层写入 merged.geojson。如果文件内部已经分了多个图层,ogrmerge.py 默认会按源图层名分别合并,想要强制合并成一个图层需要加 -single 参数:
bash复制ogrmerge.py -f GeoJSON -o merged.geojson /data/gdb/ -single
2.2 理解字段统一这个隐形门槛
矢量合并最容易翻车的不是几何,而是字段。GDAL 在追加数据时,如果源文件字段和目标文件字段不匹配,默认是不会自动映射的,结果就是字段值丢失、变成空值,或者直接报错。
先看个我在实际项目中遇到的报错:
code复制ERROR 1: Layer input2 already exists in output datasource "merged.shp".
这个错误出现在重复追加同一个图层的时候,原因是第二次执行 ogr2ogr 时目标文件里已经有了 input2 这个图层。解决办法就是我上面提到的,追加时指定 -nln merged 统一图层名。当然,如果你真要把 input2 里的字段做映射,命令就要换成这样:
bash复制ogr2ogr -f "ESRI Shapefile" merged.shp input2.shp -append -nln merged \
-sql "SELECT name, ST_GeomFromWKT(wkt) FROM input2" \
-dialect sqlite
这个命令里用了 SQLite 方言的 SQL 查询,ST_GeomFromWKT 表示把 input2 里的 wkt 字段解析成几何对象,然后只取 name 字段。这样追加的数据就自动统一到了目标文件的字段结构上。
2.3 投影坐标系和时间消耗的大坑
合并不同坐标系的数据,属于资深玩家也会踩的坑。如果你合并的 A 文件是 CGCS2000 的投影坐标,B 文件是 WGS84 经纬度,直接合并出来的数据在 GIS 里会错位得离谱,而且从属性表里根本看不出问题。
正确做法是合并前统一坐标系,用 ogr2ogr 的 -t_srs 参数强制转换:
bash复制ogr2ogr -f "ESRI Shapefile" merged_b.shp input_b.shp -t_srs "EPSG:4547"
更多时候你根本不知道源文件是什么坐标系,这时候要先读取元数据。用 ogrinfo 看一下:
bash复制ogrinfo -so input.shp input
输出里会明确列出 EXTENT 和 SRS,看到经纬度范围比如 (109, 20, 112, 24) 就基本能判断是度单位,再结合区域范围判断具体是哪个地理坐标系。
合并的时间消耗,主要取决于你要写多少数据,但如果拿几十个 GB 的数据做合并,磁盘 IO 就成了瓶颈。这里有个小技巧:目标格式尽量选 GeoPackage 或者 Shapefile,因为它们都支持顺序写入,而且写单个大文件比写多个小文件快得多。如果源文件是 GDB 要素类,建议先用 ogr2ogr 转一步,把数据规整成 GeoPackage 临时文件,再做合并,整体速度往往比直接从 GDB 里逐个文件读要快。
3. 实操过程与核心环节实现
3.1 Python 批量合并脚本的分步实现
命令行适合单次操作,但真正批量处理的时候,我还是习惯写 Python 脚本。GDAL 的 Python 绑定非常成熟,用好了可以做到“无人值守”式处理。
下面这个脚本,就是我在处理大量 shapefile 合并时实际用过的,加上了详细的注释:
python复制from osgeo import ogr, gdal
import os, glob
# 输入目录和输出文件
input_folder = "/data/shp_files"
output_file = "/data/merged_result.gpkg"
# 打开输出数据源,创建 GeoPackage 格式,允许覆盖
driver = gdal.GetDriverByName("GPKG")
if os.path.exists(output_file):
os.remove(output_file)
out_ds = driver.Create(output_file, 0, 0, 0, gdal.GDT_Unknown)
# 第一个文件用来初始化目标图层结构
shp_files = sorted(glob.glob(os.path.join(input_folder, "*.shp")))
if not shp_files:
raise RuntimeError("目录下没有 shapefile 文件")
first_ds = ogr.Open(shp_files[0])
first_lyr = first_ds.GetLayer(0)
# 注意:这里要取源图层的空间参考
srs = first_lyr.GetSpatialRef()
out_lyr = out_ds.CreateLayer("merged", srs=srs, geom_type=ogr.wkbMultiPolygon)
# 复制字段结构
src_lyr_defn = first_lyr.GetLayerDefn()
for i in range(src_lyr_defn.GetFieldCount()):
field_defn = src_lyr_defn.GetFieldDefn(i)
out_lyr.CreateField(field_defn)
# 内存中的要素缓存,降低 I/O 次数
lyr_defn = out_lyr.GetLayerDefn()
for shp in shp_files:
ds = ogr.Open(shp)
lyr = ds.GetLayer(0)
n = lyr.GetFeatureCount()
print(f"正在处理: {shp}, 要素数: {n}")
for feat in lyr:
geom = feat.GetGeometryRef()
if geom is None:
continue
# 这里直接使用源要素的几何类型
new_feat = ogr.Feature(lyr_defn)
new_feat.SetGeometry(geom.Clone())
for i in range(lyr_defn.GetFieldCount()):
field_name = lyr_defn.GetFieldDefn(i).GetName()
field_value = feat.GetField(field_name)
new_feat.SetField(field_name, field_value)
out_lyr.CreateFeature(new_feat)
feat = None
ds = None
out_ds = None
print("合并完成")
有几个细节需要专门说明:
第一,ogr.wkbMultiPolygon 写死了几何类型。如果输入既有面要素又有线要素,合并出来的 merged 层就会出问题。更稳妥的做法是先遍历一遍所有输入文件,探测出统一的几何类型,或者直接使用源图层的几何类型。
第二,CreateFeature 之前没有用 StartTransaction,数据量小无所谓,但如果单文件有几十万要素,最好用事务包裹:
python复制out_lyr.StartTransaction()
for feat in ...:
...
out_lyr.CommitTransaction()
这样批量提交,写入性能能提升好几倍。
3.2 字段自动对齐的进阶技巧
如果合并的每个源文件字段顺序不完全一样,上面的脚本就会跑偏。核心问题在于 lyr_defn.GetFieldDefn(i).GetName() 是从输出图层的字段定义中取字段名的,而源字段和输出字段在大多数情况下是——名称相同但字段索引不一定相同的对应关系。
我的处理方式是:先根据源图层字段索引读值,再按字段名写入到目标图层。两者之间用一个 name_to_index 字典做映射。修改一下循环里的代码:
python复制# 先建立源图层字段名到字段索引的映射
src_fields = {}
for i in range(lyr.GetLayerDefn().GetFieldCount()):
src_fields[lyr.GetLayerDefn().GetFieldDefn(i).GetName()] = i
for feat in lyr:
...
for field_name in out_fields:
if field_name in src_fields:
new_feat.SetField(field_name, feat.GetField(src_fields[field_name]))
else:
new_feat.SetField(field_name, None)
这里 out_fields 是在创建输出图层时收集的目标字段名列表。这样即使每个文件的字段顺序都不一样,也不会错位填充。
3.3 合并后清理和验证的基本动作
合并完成不等于收工,必须做验证。
我通常把验证拆成三步:
第一步,数量核对。合并后的要素总数,应该等于所有源文件要素数之和。写个快速脚本统计一下:
python复制out_ds = ogr.Open(output_file)
out_lyr = out_ds.GetLayer()
print("合并后要素总数:", out_lyr.GetFeatureCount())
第二步,几何检查。合并过程中有可能混入空几何或者自相交的坏几何。用 ogrinfo 加 -so 看统计,或者直接用 ogr2ogr 执行一遍 -makevalid:
bash复制ogr2ogr -f "GPKG" clean_result.gpkg merged_result.gpkg -makevalid
这个命令会尝试修复所有无效几何,输出新文件。如果源数据来自不同坐标系套合,这一步几乎是必须的。
第三步,抽检属性。随机抽几个要素,打开属性表看关键字段有没有空值。如果源文件的某个字段本身就是空值居多,那是正常的;但如果有大量字段意外为空,多半是字段映射没对齐。
4. 常见问题与排查技巧实录
4.1 字段截断和类型转换的异常
Shapefile 格式对字段名长度限制是 10 个字符,属性字段长度也有限制。合并的时候如果源字段名超过了 10 个字符,GDAL 默认会截断,这本身不报错,但截断后容易造成字段重名,后续就乱了。
我遇到过最诡异的一个问题:合并一个字段名为 Construction_Year_2020 的 shapefile,合并后用 QGIS 打开,属性表里看到的字段名变成了 Constructi,完全看不出原始含义。后来我在 ogr2ogr 命令里加上 -fieldmap 手动指定映射关系:
bash复制ogr2ogr -f "ESRI Shapefile" merged.shp source.shp -append -nln merged \
-fieldmap 0,1,2,3
其中 0,1,2,3 表示源字段索引到目标字段索引的映射。但更稳妥的做法是:合并前把所有源文件的字段名统一改成规范短名,省得后面一堆坑。
4.2 大数据量合并时的内存溢出
如果你用 Python 直接遍历要素合并 10GB 级别的数据,很容易出现内存爆炸。GDAL 的 Python 绑定在遍历大数据量时会缓存大量要素,哪怕你每处理一个要素就设为 None,如果源数据自带几十个字段,内存占用依然可观。
我实测下来的经验:用命令行 ogr2ogr 处理大数据量最稳,因为它是 C++ 实现的,内存控制得好。如果你确实需要用 Python 做复杂逻辑,那就换用 ogr2ogr 加 SQL 过滤,把复杂逻辑下推给 GDAL 核心,别在 Python 层做逐要素循环。
另外一个特别容易被忽略的点:输出格式的选择直接决定内存占用。写 GeoPackage 时 GDAL 会启用空间索引,几何写入顺序会影响随机写入;写 shapefile 时如果有投影信息文件 .prj 缺失,整个数据集可能无法被正确读取。
4.3 “多个图层合并到一个图层”时要注意的图层名坑
很多人拿 ogrmerge.py 合并目录下的多个 GPKG 文件,发现输出文件里出现了很多个图层,而不是一个。原因是:当输入文件含有多个图层时,ogrmerge.py 默认按图层名区分,不会强制合并。
解决办法很简单,加 -single 参数:
bash复制ogrmerge.py -f "GPKG" -o merged.gpkg -single /data/input/*.gpkg
或者用 -src_layer 指定读取哪个图层:
bash复制ogrmerge.py -f "GPKG" -o merged.gpkg -src_layer "roads" /data/input/*.gpkg
这两个参数极大提升了 ogrmerge.py 在复杂目录结构下的适用性。
4.4 坐标系不一致导致的空间错位
这是最让新手头疼的一类问题。合并前一定要用 ogrinfo -so 检查源文件的坐标系统,如果发现有的文件是 WGS84,有的是 CGCS2000,有的甚至是墨卡托,直接合并后会出现要素错位,但不会有任何报错。
我的解决办法是:合并命令里统一加 -t_srs,指定目标坐标系:
bash复制ogr2ogr -f "GPKG" -t_srs "EPSG:4547" merged.gpkg \
/data/input/*.shp
注意这里是通配符,ogr2ogr 支持从多个源文件读入,但输出文件只会有一个结果图层。
其实在这个场景下,ogrmerge.py 更合适,因为它天然支持多个输入文件,还可以加 -t_srs:
bash复制ogrmerge.py -f "GPKG" -o merged.gpkg -single -t_srs "EPSG:4547" /data/input/*.shp
两种方式都能达到统一坐标系的目的,看你习惯用哪个工具。
5. 性能优化与批处理实战
5.1 并行合并的正确打开方式
GDAL 本身的很多操作是单线程的,但如果你要合并的文件很多,可以在文件级别做并行。我的做法是用 xargs -P 或者 Python 的 multiprocessing 先做一次预合并,把文件分成 N 组,每组先合并成临时文件,最后再把 N 个临时文件合并成最终文件。
步骤拆开是这样的:
bash复制# 先创建临时目录
mkdir -p tmp_merge
# 使用 ls 和 head 把文件按组划分
ls /data/shp_files/*.shp | head -100 > group1.txt
ls /data/shp_files/*.shp | tail -n +101 | head -100 > group2.txt
# 并行执行合并
cat group1.txt | xargs -P 4 -I {} ogr2ogr -f "GPKG" tmp_merge/group1.gpkg {}
# 注意:这里其实是逐文件追加的写法,更推荐的做法是参考下面 Python 脚本
直接这么写有个问题:第一条 ogr2ogr 创建文件,后面的 ogr2ogr 需要追加,但 xargs 里不好判断是第一条还是后面的。所以我更推荐在 Python 里控制并行度:
python复制import concurrent.futures as cf
def merge_group(file_list, out_path):
# 第一个文件创建,后面的追加
cmd = f'ogr2ogr -f "GPKG" {out_path} {file_list[0]}'
os.system(cmd)
for f in file_list[1:]:
cmd = f'ogr2ogr -f "GPKG" {out_path} {f} -append -nln merged'
os.system(cmd)
groups = [...]
with cf.ThreadPoolExecutor(max_workers=4) as pool:
futures = [pool.submit(merge_group, grp, f"tmp_merge/{i}.gpkg") for i, grp in enumerate(groups)]
for f in cf.as_completed(futures):
f.result()
# 最后合并所有临时文件
merge_group([f"tmp_merge/{i}.gpkg" for i in range(len(groups))], "final.gpkg")
实测中,4 个并行任务处理 400 个分幅文件,总耗时从原来的 70 分钟降到了 25 分钟左右。瓶颈主要在磁盘 IO,所以如果你用的是机械硬盘,并行度别开太高,否则反而会因为寻道竞争变慢。
5.2 属性过滤和字段精简
在实际合并时,我们往往不需要源文件的全部字段,尤其是那些冗余的中间计算字段,比如面积字段(几何变了之后面积字段就失效了)、临时 ID 等。合并前精简字段,不仅能减少输出体积,还能大幅降低合并耗时。
用 ogr2ogr 的 -select 参数控制输出字段:
bash复制ogr2ogr -f "GPKG" merged.gpkg source.shp \
-select "NAME,CODE,AREA" -append -nln merged
Python 脚本里也有对应的做法:创建输出图层的字段时跳过不需要的字段。
我通常做一个白名单列表,只保留业务上真正需要的字段,其他的一律不建。这不仅是为了合并速度,也为了避免后期数据库入库时字段冗余。
5.3 构建空间索引的必要性
合并完成后,如果要用于频繁的查询、空间分析,建议在输出文件上构建空间索引。GeoPackage 支持空间索引,使用 ogrinfo 的 SQL 命令:
bash复制ogrinfo merged.gpkg -sql "CREATE SPATIAL INDEX ON merged(geom)"
Shapefile 格式本身也有 .shx 文件索引,但它的空间查询能力相对弱,如果要上生产环境,我一般建议输出为 GeoPackage 或者 PostGIS。
构建完空间索引后,再执行一次简单的空间范围查询验证:
bash复制ogrinfo -so merged.gpkg merged -al -where "FID < 10"
如果返回正常,说明数据可以正常使用。
6. 从合并到入库:PostGIS 场景的延伸
6.1 直接合并写入 PostGIS
很多情况下,合并的最终目的地是空间数据库。GDAL 天然支持 PostGIS,你可以把合并和入库这两步合并成一个命令:
bash复制ogr2ogr -f "PostgreSQL" PG:"host=localhost user=postgres dbname=gis password=123456" \
/data/input/*.shp -nln merged_table -overwrite
这条命令会把所有 shapefile 合并后直接写入 merged_table 表。注意这里没有 -append,因为 -overwrite 表示覆盖已有表。
如果表已经存在,只想追加数据,那就把 -overwrite 改成 -append:
bash复制ogr2ogr -f "PostgreSQL" PG:"host=localhost user=postgres dbname=gis password=123456" \
/data/input/*.shp -nln merged_table -append
6.2 大批量数据入库的优化建议
大批量写入 PostGIS 时,GDAL 默认是单条 INSERT,效率很低。建议在命令里加上 -gt 参数设置每批事务的要素数量:
bash复制ogr2ogr -f "PostgreSQL" PG:"host=localhost user=postgres dbname=gis password=123456" \
/data/input/*.shp -nln merged_table -gt 1000 -overwrite
-gt 1000 表示每 1000 个要素提交一次事务,能极大减少提交次数,提升写入速度。实测 100 万条数据,如果不加这个参数跑了几十分钟,加上之后大概 3 分钟就写完了。
另外,如果源数据本身已经很规范,你也可以在入库前禁用表的触发器,入库后再重建空间索引:
bash复制# 入库前禁用索引可加速写入,一般不需要手动做
# 入库后重建空间索引
ogrinfo PG:"host=localhost user=postgres dbname=gis password=123456" \
-sql "CREATE INDEX idx_merged_geom ON merged_table USING GIST (geom)"
这个 CREATE INDEX 是直接在数据库侧执行,不需要通过 GDAL 的 SQL 方言。
6.3 合并结果的质量审核
入库后的数据,永远不要直接拿去出图或对外发布,至少要过三关:
第一关,按行政编码汇总数量,验证要素是否有遗漏。比如合并的是分村数据,合并后按 XZC_CODE 的前几位 GROUP BY 统计,看每个乡镇的数量是否合理。
第二关,做拓扑检查,查找重叠、缝隙、自相交。PostGIS 里可以直接用 ST_IsValid 和 ST_IsSimple 过滤出问题要素:
sql复制SELECT gid, ST_IsValidReason(geom) FROM merged_table WHERE NOT ST_IsValid(geom);
第三关,空间范围检查。用 ST_Extent 看数据范围是否符合预期,排除因为坐标系问题导致的数据偏离到别的省份甚至别的国家。
这三关过了,合并的数据才能算是可用的。
7. 一个完整的实战案例复盘
为了让你对整体流程有个更直观的感受,我复盘一下之前做过的那个区县分幅数据合并项目。
原始数据情况:
- 数据格式:MDB(Access 数据库)里的要素类,总共 12 个县区,每个县区一个文件
- 要素类型:面状地类图斑,总量约 380 万
- 目标:合并成统一的一个 GeoPackage 文件,供后续服务发布使用
第一步,把所有 MDB 数据统一转成 GPKG 临时文件:
bash复制for f in /data/mdb_files/*.mdb; do
ogr2ogr -f "GPKG" /data/temp/$(basename $f .mdb).gpkg "$f" \
-t_srs "EPSG:4547"
done
第二步,查看每个文件的结构,确认字段是否一致:
bash复制ogrinfo -so /data/temp/xx县.gpkg xx县
发现有的文件多了几个字段,有的字段名略有差异,我用 -select 统一了输出字段。
第三步,写 Python 脚本合并所有临时 GPKG:
python复制from osgeo import ogr
import glob
files = sorted(glob.glob("/data/temp/*.gpkg"))
out_path = "/data/merged_final.gpkg"
driver = ogr.GetDriverByName("GPKG")
out_ds = driver.Create(out_path, 0, 0, 0, gdal.GDT_Unknown)
first = ogr.Open(files[0])
src = first.GetLayer(0)
srs = src.GetSpatialRef()
out_lyr = out_ds.CreateLayer("merged", srs=srs, geom_type=ogr.wkbMultiPolygon)
for i in range(src.GetLayerDefn().GetFieldCount()):
out_lyr.CreateField(src.GetLayerDefn().GetFieldDefn(i))
out_lyr.StartTransaction()
for f in files:
ds = ogr.Open(f)
lyr = ds.GetLayer(0)
defn = out_lyr.GetLayerDefn()
for feat in lyr:
geom = feat.GetGeometryRef()
if geom is None:
continue
new_feat = ogr.Feature(defn)
new_feat.SetGeometry(geom.Clone())
for j in range(defn.GetFieldCount()):
nm = defn.GetFieldDefn(j).GetName()
new_feat.SetField(nm, feat.GetField(nm))
out_lyr.CreateFeature(new_feat)
ds = None
out_lyr.CommitTransaction()
out_ds = None
整个脚本跑完,380 万要素合并用时约 18 分钟,生成的 GPKG 文件大小 4.6GB。最后用 QGIS 打开,叠加影像底图检查了三个重点区域,套合完全准确,项目交付顺利。
这里面有一个关键心得:MDB 数据直接合并容易遇到字段类型不兼容的问题,比如 Access 里的某些字段是 OLE 对象或长文本,GDAL 读取时可能变成 NULL 或者截断。所以稳妥的路径永远是:先转一次 GPKG 做清洗,再合并。别省这一步,后面排查问题的成本远高于这一步的转换时间。
8. 避坑清单和常用命令速查
最后整理一份我平时工作里常用的避坑清单和命令速查表,方便你直接抄作业。
8.1 合并前必做的五件事
- 用
ogrinfo -so检查每个源文件的坐标系、几何类型、字段数量,确保心里有数。 - 列出所有源文件的字段名清单,对比是否一致,差异大的先做字段映射方案。
- 确认目标格式。临时处理建议用 GPKG,长期存储看业务需求,可能是 PostGIS 或 SHP。
- 如果文件特别多,先合并 3-5 个文件做小规模验证,确认输出结构和预期一致再跑全量。
- 保留好合并前的源文件,不要在合并过程中直接覆盖源文件,防止出错后无法回滚。
8.2 常用命令速查表
| 操作目标 | 推荐命令 |
|---|---|
| 两个 shapefile 合并 | ogr2ogr -f "ESRI Shapefile" merged.shp a.shp 然后 ogr2ogr -f "ESRI Shapefile" merged.shp b.shp -append -nln merged |
| 目录下多个 shapefile 合并为 GPKG | ogrmerge.py -f "GPKG" -o merged.gpkg -single /data/shp/*.shp |
| 合并时统一坐标系 | 加上 -t_srs "EPSG:4547" |
| 合并时选择字段 | 加上 -select "field1,field2" |
| 合并时修复无效几何 | 合并后执行 ogr2ogr -f "GPKG" clean.gpkg merged.gpkg -makevalid |
| 合并到 PostGIS | ogr2ogr -f "PostgreSQL" PG:"host=localhost user=postgres dbname=gis password=123456" /data/input/*.shp -nln merged -gt 1000 -overwrite |
| 查看源文件信息 | ogrinfo -so input.shp input |
8.3 几个容易被忽略的细节
GDAL 版本不同,行为会有细微差异。我建议统一使用 GDAL 3.5 以上版本,尤其是 ogrmerge.py 和 -makevalid 这两个功能,旧版本支持不完整。
合并 GeoPackage 时,如果目标文件已存在,ogr2ogr 默认报错退出,需要加 -update 参数才能追加到现有文件。所以在批处理脚本里,判断输出文件是否存在非常关键,这决定了第一条命令用 -overwrite 还是 -append。
最后说一个我反复踩过的坑:合并过程中如果源文件里存在空几何要素,GDAL 默认会尝试写入空几何,写入成功的要素在 GIS 里显示为“无几何”的点。这个不报错,但后续做空间查询时会非常影响结果。所以在合并脚本里一定要加地理几何判断:
python复制if geom is None:
continue
if geom.IsEmpty():
continue
这两行代码,我建议每个做矢量合并的人都写进去。
合并花的时间少,排查问题花的时间多,这是 GIS 数据处理的常态。把你自己的流程沉淀成一套固定脚本,每次只需要改输入路径,就能省掉大量重复的排查工作。这就是熟练工和踩坑者的区别。
