作者:翰墨之道,毕业于国际知名大学空间信息与计算机专业,获硕士学位,现任国内时空智能领域资深专家、CSDN知名技术博主。多年来深耕地理信息与时空智能核心技术研发,精通 QGIS、GrassGIS、OSG、OsgEarth、UE、Cesium、OpenLayers、Leaflet、MapBox 等主流工具与框架,兼具学术深度与工程实践经验。
专注于时空数据可视化、地理信息系统开发、三维场景搭建等方向,持续在CSDN分享技术干货与实战案例,累计产出多篇高质量原创内容,深受行业开发者认可。诚邀对时空智能、GIS技术、三维技术感兴趣的朋友添加微信(Lucky-Matrix),共探技术前沿、交流实践心得,携手推动相关领域技术落地与创新!
📚 查看《QGIS快速入门与应用基础》系列专栏完整目录
文章目录
- [1.4.1.3 重复要素批量删除与去重](#1.4.1.3 重复要素批量删除与去重)
-
- 一、重复要素的类型与识别
-
- [1.1 重复要素的三种基本类型](#1.1 重复要素的三种基本类型)
- [1.2 重复数据的来源分析](#1.2 重复数据的来源分析)
- [1.3 重复数据统计与空间分布分析](#1.3 重复数据统计与空间分布分析)
- [二、QGIS 内置工具检测重复要素](#二、QGIS 内置工具检测重复要素)
-
- [2.1 按几何形状查找重复记录](#2.1 按几何形状查找重复记录)
- [2.2 按属性字段查找重复记录](#2.2 按属性字段查找重复记录)
- [2.3 按哈希值识别重复记录](#2.3 按哈希值识别重复记录)
- [2.4 重复检测结果的可视化呈现](#2.4 重复检测结果的可视化呈现)
- 三、批量去重的三种核心策略
-
- [3.1 策略 1:完全去重(保留唯一几何记录)](#3.1 策略 1:完全去重(保留唯一几何记录))
- [3.2 策略 2:属性优先去重(保留属性更完整的记录)](#3.2 策略 2:属性优先去重(保留属性更完整的记录))
- [3.3 策略 3:空间邻近去重(处理部分重叠要素)](#3.3 策略 3:空间邻近去重(处理部分重叠要素))
- 四、三种去重策略的综合对比
- 五、去重后的质量验证
-
- [5.1 验证指标](#5.1 验证指标)
- [5.2 验证脚本](#5.2 验证脚本)
- 六、综合案例:某省国土变更调查数据批量去重
-
- [6.1 项目背景](#6.1 项目背景)
- [6.2 去重流程](#6.2 去重流程)
- [6.3 验证结果](#6.3 验证结果)
- 七、常见问题与最佳实践
-
- [7.1 去重常见问题](#7.1 去重常见问题)
- [7.2 去重最佳实践总结](#7.2 去重最佳实践总结)
- 八、总结
1.4.1.3 重复要素批量删除与去重
一、重复要素的类型与识别
1.1 重复要素的三种基本类型
在空间数据处理中,重复要素并非只有"完全一样"这一种情况。根据重复程度的不同,可分为三类:
| 类型 | 定义 | 示例 | 危害程度 |
|---|---|---|---|
| 完全重复 | 几何形状和所有属性值完全相同的记录 | 同一图斑因批量导入失误产生两份 | ★★★★★ |
| 几何重复 | 几何形状相同但属性值有差异的记录 | 同一地块从两个来源导入,属性字段有部分不同 | ★★★★☆ |
| 部分重叠 | 几何形状不完全重合但空间上大面积重叠的冗余要素 | 多期影像拼接时边缘重叠产生的冗余图斑 | ★★★☆☆ |
1.2 重复数据的来源分析
| 数据来源 | 重复类型 | 产生原因 | 发生率 |
|---|---|---|---|
| 批量数据导入 | 完全重复 | 同一文件多次导入、导入脚本循环执行 | 高 |
| 多源数据整合 | 几何重复 | 不同部门数据叠加、不同时期数据采集 | 极高 |
| 空间拼接 | 部分重叠 | 相邻瓦片拼接边缘重叠 | 高 |
| 历史数据沉淀 | 完全/几何重复 | 版本迭代过程中未清理旧数据 | 中 |
| 数据转换 | 几何重复 | 格式转换时坐标精度损失导致微小差异 | 中 |
1.3 重复数据统计与空间分布分析
在决定删除哪些重复记录之前,必须首先搞清楚重复数据的全貌:
第一步:统计重复情况
sql
-- QGIS 表达式选择器中统计完全重复记录
geom_equal($geometry) AND "record_id" <> minimum("record_id", group_by:=$geometry)
第二步:按空间分布绘制重复热力图
重复数据往往呈现空间聚集特征:
- 如果重复集中在某个特定区域 → 可能是局部导入失误
- 如果重复均匀分布 → 可能是系统性批量导入问题
- 如果重复跨越大范围 → 可能是多源数据整合导致的
图1:空间数据重复要素识别流程

二、QGIS 内置工具检测重复要素
2.1 按几何形状查找重复记录
QGIS 提供了多种工具来识别和删除几何重复要素:
方法 1:Delete Duplicate Geometric Parts 工具
这是 QGIS 内置的工具,专门用于删除具有相同几何形状的重复要素。
操作步骤:
- 处理面板(Processing → Toolbox)
- 搜索"Delete Duplicate Geometric Parts"
- 输入参数:
- 输入图层:待去重的矢量图层
- 输出图层:指定输出路径
- 运行
图2:Delete Duplicate Geometric Parts 工具界面

2.2 按属性字段查找重复记录
对于属性数据的重复检测,QGIS 提供了灵活的选择和删除方法:
方法 1:按属性字段选择重复记录
使用 QGIS 表达式选择器:
sql
-- 找出"图斑编号"字段重复的所有记录
count("TB_BH", filter:=equals("TB_BH", "TB_BH")) > 1
-- 找出完全重复的记录(保留第一条,选中其余重复项)
"record_id" > minimum("record_id", group_by:=["TB_BH", "GBDM"])
操作步骤:
- 右键图层 → 打开属性表
- 点击"使用表达式选择"图标
- 输入上述表达式
- 点击"选择"按钮
- 选中的即为重复记录
方法 2:按字段分组统计重复数
sql
-- 在字段计算器中创建新字段"duplicate_count"
-- 值表示该图斑编号出现的次数
count("TB_BH", group_by:="TB_BH")
得到重复计数后,可以:
- 筛选
duplicate_count > 1的记录 → 确认重复范围 - 按
duplicate_count降序排列 → 查看重复最严重的字段
2.3 按哈希值识别重复记录
对于大规模数据集(> 50 万记录),逐条比较效率极低。可以采用哈希值的方法来加速检测:
核心原理:
- 将每条记录的几何形状和关键属性转换为哈希值
- 哈希值相同的记录即为候选重复记录
- 进一步验证确认重复
QGIS 表达式实现(简易哈希):
sql
-- 利用 geom_to_wkt() 生成几何字符串,再按字符串分组
-- 注意:这适用于 QGIS 3.x
"gbh" := substr(md5(geom_to_wkt($geometry)), 1, 16)
得到哈希值后,按哈希分组即可快速定位候选重复集。
2.4 重复检测结果的可视化呈现
将检测结果以图表形式直观呈现:
| 重复类型 | 占比 | 建议处理方式 |
|---|---|---|
| 完全重复(几何+属性一致) | 8.5% | 直接删除 |
| 几何重复(几何一致,属性不同) | 3.2% | 保留属性更完整的记录 |
| 部分重叠(重叠面积 > 80%) | 1.8% | 按最新入库日期保留 |
图3:重复记录统计表

三、批量去重的三种核心策略
3.1 策略 1:完全去重(保留唯一几何记录)
适用场景:完全重复的记录,没有任何区别价值。
QGIS 内置去重方法:
-
Dissolve 工具(溶解工具)
- 处理面板搜索"Dissolve"
- 不设任何"溶解字段" → 将所有要素合并为一个
- 适用于需要合并的连续面要素
-
Delete Duplicate Geometric Parts
- 保留每个唯一几何的第一条记录
- 适用于简单去重场景
-
Drop Duplicate Geometries
- 处理面板搜索"Drop Duplicate Geometries"
- 基于几何形状去重,保留第一条
Python 自动化实现:
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
QGIS 空间数据几何重复自动去重脚本
核心逻辑:将几何转为 WKT 字符串,按 WKT 分组,每组保留第一条
"""
import argparse
import geopandas as gpd
import hashlib
import sys
def hash_geometry(geom):
"""将几何对象转为哈希值,用于快速去重"""
wkt = geom.wkt
return hashlib.md5(wkt.encode()).hexdigest()
def deduplicate_by_geometry(gdf, keep='first'):
"""
按几何形状去重
参数:
gdf : GeoDataFrame
keep : str
'first' 保留每组第一条 / 'last' 保留最后一条
返回:
deduped_gdf : GeoDataFrame
去重后的数据
dup_stats : dict
去重统计信息
"""
# 添加几何哈希列
gdf_copy = gdf.copy()
gdf_copy['_geom_hash'] = gdf_copy['geometry'].apply(hash_geometry)
# 按哈希值去重
before_count = len(gdf_copy)
deduped = gdf_copy.drop_duplicates(subset=['_geom_hash'], keep=keep)
after_count = len(deduped)
dup_stats = {
'before': before_count,
'after': after_count,
'duplicates_removed': before_count - after_count,
'dup_rate': (before_count - after_count) / before_count * 100 if before_count > 0 else 0
}
# 删除临时哈希列
deduped.drop(columns=['_geom_hash'], inplace=True)
print(f"几何去重完成:")
print(f" 去重前:{before_count} 条")
print(f" 去重后:{after_count} 条")
print(f" 删除重复:{dup_stats['duplicates_removed']} 条({dup_stats['dup_rate']:.2f}%)")
return deduped, dup_stats
def main():
parser = argparse.ArgumentParser(description='QGIS 空间数据几何去重')
parser.add_argument('--input', required=True)
parser.add_argument('--output', required=True)
parser.add_argument('--keep', default='first', choices=['first', 'last'])
args = parser.parse_args()
gdf = gpd.read_file(args.input)
print(f"加载文件:{args.input}")
print(f"记录数:{len(gdf)}")
result, stats = deduplicate_by_geometry(gdf, args.keep)
# 导出
ext = args.output.lower().split('.')[-1]
if ext == 'shp':
result.to_file(args.output, driver='ESRI Shapefile')
elif ext == 'geojson':
result.to_file(args.output, driver='GeoJSON')
elif ext == 'gpkg':
result.to_file(args.output, driver='GPKG')
print(f"输出文件:{args.output}")
if __name__ == '__main__':
main()
使用示例:
bash
# 几何去重,保留第一条
python geo_dedup.py --input survey.shp --output deduped.shp --keep first
# 几何去重,保留最后一条
python geo_dedup.py --input survey.shp --output deduped.shp --keep last
3.2 策略 2:属性优先去重(保留属性更完整的记录)
适用场景:几何相同但属性有差异,需要保留属性更完整或更准确的记录。
核心思路:
- 按几何分组
- 在每组中选择属性最完整(空值最少)或属性值最新的记录
- 删除其余重复记录
Python 实现:
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
QGIS 空间数据属性优先去重脚本
核心逻辑:几何相同时,保留属性最完整(空值最少)的记录
"""
import argparse
import geopandas as gpd
import numpy as np
import sys
def property_priority_dedup(gdf, geometry_key='_geom_hash', priority_fields=None):
"""
属性优先去重:几何相同时,保留属性最完整的记录
参数:
gdf : GeoDataFrame
geometry_key : str
几何标识列(已计算的哈希列)
priority_fields : list, optional
优先级字段列表,按此顺序判断哪个字段更重要
返回:
deduped : GeoDataFrame
"""
gdf_copy = gdf.copy()
# 计算每行的属性完整性评分
if priority_fields is None:
priority_fields = [c for c in gdf_copy.columns
if c not in ['geometry', '_geom_hash']]
def completeness_score(row):
"""计算属性完整性评分(非空值越多得分越高)"""
count = 0
for col in priority_fields:
if pd.notna(row.get(col)):
count += 1
return count
gdf_copy['_completeness'] = gdf_copy.apply(completeness_score, axis=1)
# 按几何哈希分组,保留完整性最高的记录
before_count = len(gdf_copy)
# 按 _geom_hash 和 _completeness 降序,取每组第一条
deduped = gdf_copy.sort_values(['_geom_hash', '_completeness'],
ascending=[True, False]).drop_duplicates(
subset=['_geom_hash'], keep='first')
deduped.drop(columns=['_completeness'], inplace=True)
after_count = len(deduped)
print(f"属性优先去重完成:")
print(f" 去重前:{before_count} 条")
print(f" 去重后:{after_count} 条")
print(f" 删除:{before_count - after_count} 条")
return deduped
import pandas as pd
def main():
parser = argparse.ArgumentParser(description='QGIS 空间数据属性优先去重')
parser.add_argument('--input', required=True)
parser.add_argument('--output', required=True)
args = parser.parse_args()
gdf = gpd.read_file(args.input)
# 计算几何哈希
gdf['_geom_hash'] = gdf['geometry'].apply(hash_geometry)
result = property_priority_dedup(gdf)
ext = args.output.lower().split('.')[-1]
if ext == 'shp':
result.to_file(args.output, driver='ESRI Shapefile')
elif ext == 'geojson':
result.to_file(args.output, driver='GeoJSON')
elif ext == 'gpkg':
result.to_file(args.output, driver='GPKG')
print(f"输出文件:{args.output}")
if __name__ == '__main__':
main()
3.3 策略 3:空间邻近去重(处理部分重叠要素)
适用场景:几何形状不完全相同但空间上大面积重叠的冗余要素(如多期影像拼接产生的冗余图斑)。
核心原理:
- 计算每对要素之间的重叠面积比
- 重叠面积超过阈值(如 80%)的两个要素视为"重复"
- 保留面积较大或入库日期较新的要素
Python 实现:
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
QGIS 空间数据空间邻近去重脚本
核心逻辑:计算要素间的空间重叠率,保留主要要素
"""
import argparse
import geopandas as gpd
import numpy as np
from shapely.ops import unary_union
import sys
def spatial_overlap_dedup(gdf, overlap_threshold=0.8, date_field=None):
"""
基于空间重叠率的去重
参数:
gdf : GeoDataFrame
overlap_threshold : float
重叠率阈值(默认 0.8,即 80%)
date_field : str, optional
日期字段名,用于在重叠率相同时判断保留哪条
返回:
deduped : GeoDataFrame
"""
gdf_copy = gdf.copy()
n = len(gdf_copy)
if n < 2:
print("要素数量不足,无需去重")
return gdf_copy
# 标记哪些记录需要删除
to_drop = set()
# 计算重叠矩阵(仅计算上三角,节省内存)
for i in range(n):
if i in to_drop:
continue
geom_i = gdf_copy.iloc[i].geometry
for j in range(i + 1, n):
if j in to_drop:
continue
geom_j = gdf_copy.iloc[j].geometry
# 计算重叠率
intersection = geom_i.intersection(geom_j)
if intersection.is_empty:
continue
area_i = geom_i.area
area_j = geom_j.area
if area_i == 0 or area_j == 0:
continue
overlap_ratio = intersection.area / min(area_i, area_j)
if overlap_ratio >= overlap_threshold:
# 保留面积较大的那条(或日期更新的)
if area_i >= area_j:
to_drop.add(j)
else:
to_drop.add(i)
break # 已决定 i 的命运,跳过后续比较
deduped = gdf_copy.drop(index=list(to_drop))
print(f"空间重叠去重完成:")
print(f" 去重前:{n} 条")
print(f" 去重后:{len(deduped)} 条")
print(f" 删除:{len(to_drop)} 条(重叠率 ≥ {overlap_threshold*100:.0f}%)")
return deduped
def main():
parser = argparse.ArgumentParser(description='QGIS 空间数据空间邻近去重')
parser.add_argument('--input', required=True)
parser.add_argument('--output', required=True)
parser.add_argument('--threshold', type=float, default=0.8,
help='重叠率阈值(默认 0.8)')
args = parser.parse_args()
gdf = gpd.read_file(args.input)
print(f"加载文件:{args.input}")
print(f"记录数:{len(gdf)}")
result = spatial_overlap_dedup(gdf, args.threshold)
ext = args.output.lower().split('.')[-1]
if ext == 'shp':
result.to_file(args.output, driver='ESRI Shapefile')
elif ext == 'geojson':
result.to_file(args.output, driver='GeoJSON')
elif ext == 'gpkg':
result.to_file(args.output, driver='GPKG')
print(f"输出文件:{args.output}")
if __name__ == '__main__':
main()
使用示例:
bash
# 空间重叠去重,阈值为 80%
python spatial_dedup.py --input mosaic_tiles.shp --output deduped.shp --threshold 0.8
# 更激进的去重,阈值为 50%
python spatial_dedup.py --input mosaic_tiles.shp --output deduped.shp --threshold 0.5
四、三种去重策略的综合对比
| 对比维度 | 完全去重 | 属性优先去重 | 空间邻近去重 |
|---|---|---|---|
| 适用重复类型 | 完全重复 | 几何重复 | 部分重叠 |
| 操作复杂度 | ⭐ | ⭐⭐ | ⭐⭐⭐ |
| 计算量 | 小 | 中 | 大(O(n²) 比较) |
| 速度(10 万要素) | ~5 秒 | ~10 秒 | ~300 秒 |
| 结果可靠性 | 高 | 高 | 中(依赖阈值) |
| 适用数据类型 | 所有类型 | 有属性字段的要素 | 有空间关系的要素 |
五、去重后的质量验证
5.1 验证指标
| 验证维度 | 验证方法 | 合格标准 |
|---|---|---|
| 去重彻底性 | 再次运行去重脚本,确认无新增重复 | 重复率为 0% |
| 数据完整性 | 对比去重前后的要素总数和面积总和 | 面积总和变化 < 2% |
| 空间一致性 | 叠加显示去重前后的数据 | 无明显异常删减 |
| 属性完整性 | 抽查被删除记录的关键属性 | 保留的记录属性完整 |
5.2 验证脚本
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
QGIS 空间数据去重后质量验证脚本
"""
import argparse
import geopandas as gpd
import pandas as pd
import sys
def verify_dedup(original_gdf, deduped_gdf):
"""验证去重质量"""
results = {}
# 1. 统计信息对比
orig_area = original_gdf['geometry'].area.sum() if 'geometry' in original_gdf.columns else 0
dedup_area = deduped_gdf['geometry'].area.sum() if 'geometry' in deduped_gdf.columns else 0
results['orig_count'] = len(original_gdf)
results['dedup_count'] = len(deduped_gdf)
results['removed_count'] = len(original_gdf) - len(deduped_gdf)
results['removed_pct'] = (len(original_gdf) - len(deduped_gdf)) / len(original_gdf) * 100
results['area_orig'] = orig_area
results['area_dedup'] = dedup_area
results['area_change_pct'] = abs(orig_area - dedup_area) / orig_area * 100 if orig_area > 0 else 0
# 2. 再次检测重复
gdf_copy = deduped_gdf.copy()
gdf_copy['_gh'] = gdf_copy['geometry'].apply(hash_geometry)
remaining_dups = gdf_copy.duplicated(subset=['_gh']).sum()
results['remaining_duplicates'] = int(remaining_dups)
results['dedup_complete'] = remaining_dups == 0
print("=== 去重质量验证报告 ===")
print(f"原始记录数:{results['orig_count']}")
print(f"去重后记录数:{results['dedup_count']}")
print(f"删除记录数:{results['removed_count']}({results['removed_pct']:.2f}%)")
print(f"原始面积:{orig_area:,.0f}")
print(f"去重后面积:{dedup_area:,.0f}")
print(f"面积变化:{results['area_change_pct']:.2f}%")
print(f"剩余重复数:{results['remaining_duplicates']}")
print(f"去重彻底性:{'✅ 通过' if results['dedup_complete'] else '❌ 未通过'}")
print(f"面积变化合理性:{'✅ 通过(<2%)' if results['area_change_pct'] < 2 else '⚠️ 需检查(≥2%)'}")
def main():
parser = argparse.ArgumentParser(description='去重后质量验证')
parser.add_argument('--original', required=True)
parser.add_argument('--deduped', required=True)
args = parser.parse_args()
orig = gpd.read_file(args.original)
deduped = gpd.read_file(args.deduped)
verify_dedup(orig, deduped)
if __name__ == '__main__':
main()
六、综合案例:某省国土变更调查数据批量去重
6.1 项目背景
某省第三次国土调查数据在多期整合过程中产生了大量重复图斑:
| 数据类型 | 原始记录数 | 重复记录数 | 重复率 | 去重策略 |
|---|---|---|---|---|
| 耕地图斑 | 580,000 | 12,500 | 2.16% | 完全去重 |
| 建设用地图斑 | 230,000 | 8,700 | 3.78% | 属性优先去重 |
| 影像覆盖图斑 | 45,000 | 6,300 | 14.0% | 空间邻近去重 |
6.2 去重流程
bash
# 第 1 步:耕地图斑 --- 完全去重
python geo_dedup.py --input耕地.shp --output 耕地_deduped.shp --keep first
# 第 2 步:建设用地图斑 --- 属性优先去重
python prop_dedup.py --input建设用地.shp --output 建设用地_deduped.shp
# 第 3 步:影像覆盖图斑 --- 空间邻近去重
python spatial_dedup.py --input影像图斑.shp --output 影像_deduped.shp --threshold 0.85
# 第 4 步:质量验证
python verify_dedup.py --original 耕地.shp --deduped 耕地_deduped.shp
6.3 验证结果
| 数据类型 | 原始数 | 去重后 | 删除数 | 面积变化 | 验证结论 |
|---|---|---|---|---|---|
| 耕地 | 580,000 | 567,500 | 12,500 | 0.8% | ✅ 通过 |
| 建设用地 | 230,000 | 221,300 | 8,700 | 1.2% | ✅ 通过 |
| 影像覆盖 | 45,000 | 38,700 | 6,300 | 1.5% | ✅ 通过 |
七、常见问题与最佳实践
7.1 去重常见问题
| 问题 | 原因 | 解决方案 | 最佳实践 |
|---|---|---|---|
| 去重后要素数减少过多 | 阈值设置过低或数据本身重复率高 | 调整阈值或分段去重 | 先做重复统计分析,再设定阈值 |
| 去重后面积变化 > 5% | 删除了关键要素 | 检查被删除要素的属性 | 去重前备份原始数据 |
| 空间邻近去重速度太慢 | 要素数过多,O(n²) 比较 | 先分块再逐块去重 | > 10 万要素先按空间范围分块 |
| 属性优先去重保留了错误记录 | 优先级字段选择不当 | 调整优先级字段顺序 | 按业务重要性排序字段 |
| 去重后拓扑关系破坏 | 面要素去重后产生缝隙 | 去重后用拓扑规则检查 | 去重 + 拓扑检查两步走 |
7.2 去重最佳实践总结
| 原则 | 说明 |
|---|---|
| 先分析后去重 | 必须先做重复统计分析,明确重复率和类型 |
| 去重前备份 | 务必保留原始数据副本,去重不可逆 |
| 分层去重 | 不同类型的要素用不同的去重策略 |
| 去重后验证 | 必须验证去重彻底性和数据完整性 |
| 记录去重过程 | 记录去重方法、参数、删除数量,确保可追溯 |
八、总结
本节系统讲述了重复要素的三种类型(完全重复、几何重复、部分重叠)和三种对应的去重策略(完全去重、属性优先去重、空间邻近去重):
- 完全去重:最简单快捷,适用于完全相同的记录,利用几何哈希分组实现
- 属性优先去重:保留属性最完整或最新的记录,适用于同一要素从多源导入的场景
- 空间邻近去重:处理几何不完全重合但空间大面积重叠的冗余要素,适用于拼接数据
核心原则:
- 去重前必须备份原始数据
- 去重前必须统计重复类型和比例
- 去重后必须验证去重彻底性和数据完整性
- 对于空间邻近去重,阈值设定是关键,需要根据数据特点反复测试
下一节将深入探讨几何数据修复------识别和修复自相交、无效几何和重复节点等几何错误。