3. 矿区研究区划定与矢量数据管理

在矿区时序变化检测中,研究区边界不是一张“背景图”,而是后续所有统计、裁剪、采样与面积量算的空间基准。边界一旦错位,后面再精细的分类算法也会把变化量算到错误的位置上。本节围绕三件事展开:把矿区边界与开采面矢量导入 Earth Engine、用 FeatureCollection 组织并操作这些矢量、以及按矿区范围做裁剪与空间过滤,最后给出多矿区批量管理的组织方式。

一、矢量数据的导入与坐标基准

矿区边界常见的来源有三类:测绘或矿权管理部门提供的 SHP/KML、在 GIS 软件中手工勾绘的开采面、以及从高分辨率影像目视解译得到的范围。Earth Engine 支持直接上传 Shapefile(需打包为 zip,且同一压缩包内只放一组同名 .shp/.shx/.dbf/.prj)和 KML,上传后在 Assets 中生成一个 table 类型资产。需要特别注意的是坐标系:GEE 内部以 WGS84 经纬度存储几何,上传时若原始数据是 CGCS2000 高斯投影或地方独立坐标系,平台会做投影转换,但转换精度取决于 .prj 是否完整。因此导入后应先用面积做一次量纲校验——若某矿区在测绘报告中面积为 3.2 km²,而 geometry.area() 返回的数量级明显偏离,就说明投影或单位出了问题。

// 导入已上传的矿区边界资产
var mineBoundary = ee.FeatureCollection('projects/demo/assets/mine_boundary_2020');
// 面积校验(单位:平方米),与测绘面积对比
var areaCheck = mineBoundary.geometry().area(1);
print('边界面积(m²):', areaCheck);

开采面矢量通常按年份或按采区组织,导入时建议保留原始属性字段,例如 mine_id、mine_name、year、stage(剥离/采掘/复垦)。这些字段是后续批量管理和按时间筛选的依据,丢失后只能靠几何位置反推,代价很高。

二、FeatureCollection 的核心操作

FeatureCollection 是 GEE 中矢量数据的容器,一个矿区边界是一个 Feature,多个矿区合起来就是一个 FeatureCollection。日常最常用的操作可以归为四类:

  • 筛选:filterBounds() 按空间范围筛选,filterMetadata() 按属性筛选,例如只取 year == 2022 的开采面。
  • 合并与融合:merge() 把多个 FeatureCollection 拼在一起;union() 把几何合并为一个整体,适合把同一矿区多年的开采面合成总扰动范围。
  • 几何运算:geometry().buffer(30) 生成缓冲带,difference() 求差集,可用于扣除已复垦区域。
  • 遍历与聚合:map() 对每个 Feature 施加同一函数,aggregate_sum() 等做属性汇总。

一个容易踩的坑是:filterBounds() 默认只保留与范围相交的要素,但不会裁剪几何本身。若某开采面跨越矿区边界,筛选后它仍以完整几何存在,直接统计面积会偏大。此时应配合 intersection() 做真正的裁剪。

三、按矿区范围裁剪与空间过滤

裁剪分两个层次。第一层是影像裁剪:用 image.clip(mineBoundary) 把影像限制在矿区边界内,减少无效计算;第二层是矢量裁剪:用 feature.intersection(boundary) 把开采面裁到矿区范围内。两者配合,才能保证“统计的像元”和“统计的矢量”落在同一个空间口径上。

// 取某矿区边界
var oneMine = mineBoundary.filter(ee.Filter.eq('mine_id', 'M01'));
// 取该矿区 2022 年开采面,并裁剪到矿区边界内
var mining2022 = ee.FeatureCollection('projects/demo/assets/mining_faces')
    .filter(ee.Filter.eq('mine_id', 'M01'))
    .filter(ee.Filter.eq('year', 2022))
    .map(function(f){
      return f.setGeometry(f.geometry().intersection(oneMine.geometry(), 1));
    });
// 影像裁剪
var img = ee.Image('LANDSAT/LC08/C02/T1_L2/LC08_123032_20220715')
    .clip(oneMine);
print('裁剪后开采面积(m²):', mining2022.geometry().area(1));

空间过滤还有一个实用场景:当研究区跨越多景影像时,用 filterBounds() 从影像集合中挑出覆盖矿区的景,再逐景裁剪,比先裁后筛效率高得多。判断标准是影像的 system:footprint 与矿区边界相交,而不是影像中心点落在矿区内。

四、多矿区批量管理的组织方式

当矿区数量从 1 个增加到几十个时,逐个写变量名的方式会迅速失控。推荐的组织思路是“一张主表 + 统一字段 + 循环处理”。主表是一个 FeatureCollection,每个 Feature 代表一个矿区,字段至少包含 mine_id、mine_name、area_km2、start_year。所有开采面、复垦面矢量都带 mine_id 字段,通过这个键与主表关联。

批量处理时,用 mineBoundary.map() 对每个矿区执行同一套流程:筛选该矿区的开采面、裁剪影像、计算指标、把结果写回 Feature 属性。这样输出的仍是一个 FeatureCollection,可以直接导出为 CSV 或表格资产,每个矿区一行,便于横向对比。若矿区数量很大、单次计算超时,可改用 Export.table.toDrive() 分批导出,或在客户端用 ee.List 分块循环。

建议给每个矿区固定一个短编码(如 M01、M02),并在所有矢量、导出文件名、结果表中统一使用。编码比中文矿名更适合做筛选键,也避免导出时出现字符兼容问题。
多矿区矢量组织与批量处理结构 矿区主表 FeatureCollection mine_id / mine_name area_km2 / start_year geometry(矿区边界) 开采面 FeatureCollection mine_id / year stage(剥离/采掘/复垦) geometry(面) mine_id 关联 mineBoundary.map() ① filterMetadata ② intersection 裁剪 ③ image.clip ④ 计算指标并 set 结果 FeatureCollection 每矿区一行 扰动面积 / 复垦率 变化年份 / 指标值 → Export.table.toDrive 统一字段 mine_id 贯穿主表、开采面与结果表;空间过滤用 filterBounds,几何裁剪用 intersection,二者不可互相替代。

上图把“主表—开采面—批量处理—结果表”的关系画在一起,核心是 mine_id 这条关联键贯穿始终。实际执行时,先确认主表与开采面的 mine_id 取值完全一致(可用 aggregate_distinct 列出两边的编码做比对),再跑批量流程,能避免大量“筛不到要素”的静默错误。

五、结果如何判断是否可用

矢量管理做完后,至少做三项检查:其一,每个矿区的裁剪后开采面积不应大于其边界面积,若出现大于,说明 intersection 未生效或坐标系不一致;其二,多年开采面的并集面积应随时间单调不减(除非有复垦扣除),若某年突然缩小,多半是筛选条件写错;其三,导出结果表的行数应等于主表矿区数,缺行意味着某个矿区的筛选键不匹配。这三项检查不需要额外数据,用 GEE 自带的聚合函数即可完成,建议固化为每次批量运行后的固定动作。

上传的 Shapefile 若包含多个图层或字段名含中文,可能在解析时出现字段丢失。导入后务必用 print(collection.first()) 检查属性是否完整,确认 mine_id 等关键字段存在后再进入后续流程。