GEE批量下载Sentinel-1影像的工程化方案

发布时间:2026/10/18 14:42:18
GEE批量下载Sentinel-1影像的工程化方案
简介这是一份面向遥感与地理信息研究者的GEE项目代码包解决批量获取与导出Sentinel-1雷达影像的难题适合已掌握基础GEE操作、需要处理大批量SAR数据的开发者。包体共3个文件以HTML交互页面为主辅以项目配置与忽略规则文件便于在GEE代码编辑器或本地环境中直接运行与二次修改。资源大小仅5KB代码轻量精炼。目前已有340人学习下载。通过示例代码可掌握定义研究区域、按成像模式与时间范围过滤影像、将分贝值转换为线性单位、按感兴趣区裁剪以及批量导出至Google Drive的完整流程覆盖了从数据筛选到导出存储的关键环节。代码逻辑清晰适合作为批量处理Sentinel-1数据的参考模板可显著提升地表监测、后向散射分析等工作的数据处理效率。1. GEE 批量拉到本地我做 SAR 时序分析的一手方案有那么一阵子做水体时序分析需要某个区域三个月的 Sentinel-1 影像。手动去数据平台一圈圈画框下载一景两三个 GB前后耗了一周还没集齐。后来干脆从 GEE 批量导出把筛选、导出、去重、监控全部写进脚本剩下的事就是等云端生成 GeoTIFF实测能把一星期的活压缩到两三个小时。这套资源就是围绕这个场景封装的工程代码解决的是影像筛选靠肉眼、下载靠手工的痛点。适合做洪水监测、冻融分析、滑坡识别、变形监测这类需要 SAR 时序数据的人。前提是你已有 GEE 权限对 ImageCollection 基本过滤有概念没有的话顺着下文走一遍也能快速补上。2. 为什么在 GEE 里批量下 Sentinel-1数据组织与导出选型2.1 Sentinel-1 GRD 在 GEE 里的组织方式GEE 里的 Sentinel-1 数据被组织成一个 ImageCollection主要用 GRD 级别。GRD 是经过多视处理的地距产品幅度信息保留地理定位误差控制在数米级适合做后向散射和变化检测。要批量下载首先得理解每个 Image 是挂哪些属性上的——筛选全靠它们。import ee ee.Initialize() s1_col ee.ImageCollection(COPERNICUS/S1_GRD) probe s1_col.limit(1) attr_names probe.first().propertyNames() print(attr_names.getInfo())这段探针脚本会把所有可用属性名打出来。写筛选逻辑之前强烈建议先跑一次因为你想用的轨道号属性到底叫 relativeOrbitNumber_start 还是别的跑一遍直接确认后面就不用反复翻文档。我见过不少同学把属性名拼错导致 Collection 为空任务提交了等于白提。还要注意极化属性 transmitterReceiverPolarisation 是一个 list值是 [VV] 或 [VV,VH] 这种形式。筛选时不能直接用 eq 匹配整个 list而要用 listContains 判断目标极化是否被包含。这是新手最容易踩的第一个坑。2.2 导出到 Drive 还是 AssetGEE 导出有两个方向导出到 Drive 的 GeoTIFF或导出到 Asset。做数据分析的人优先选 Drive因为 Asset 里的数据在云端接着处理很方便但要下载到本地还得再从 Asset 走一次导出多一道手。Asset 的优势在于后续继续用 GEE 做时序处理时读取更快比如逐景计算 VH/VV 比值再生成新 Collection速度比从本地导回去快得多。存储配额上Asset 占用按字节计费Drive 导出走网络配额限制相对宽松。因为我大部分场景是下载到本地做后处理所以脚本默认全部走 Drive。只有一种情况会切换到 Asset导出之后还需要在 GEE 里和别的数据源做叠加分析或者需要频繁交互查询时才值得在云端留着。2.3 数据筛选的最小参数集写批量下载先别急着写循环把筛选参数拆成一张表参数齐了脚本自然就顺了。参数作用我的常用值region空间范围参与最小外接矩形过滤geometry 或 feature.geometry()startDate / endDate时间窗UTC 边界2024-01-01 / 2024-03-31relativeOrbitNumber_start相对轨道号先跑日期轨道对照表再定transmitterReceiverPolarisation极化方式VV 或 VV,VHinstrumentMode成像模式IWresolution成像分辨率等级10m这套参数落到代码里就是下面这段region ee.Geometry.Polygon( [[[...]]] # 用坐标数组占位实际替换成你的研究区 ) col (ee.ImageCollection(COPERNICUS/S1_GRD) .filterBounds(region) .filterDate(2024-01-01, 2024-03-31) .filter(ee.Filter.eq(instrumentMode, IW)) .filter(ee.Filter.eq(resolution, 10m)) .filter(ee.Filter.listContains(transmitterReceiverPolarisation, VV)))filterBounds 用的是 geometry注意别直接传 FeatureCollection否则会出现空 Collection 的诡异现象这个坑细节放到第五章单独讲。filterDate 的结束时间建议多含一天因为 GEE 的时间边界按 UTC 零点算对东八区用户容易少一天的景。listContains 是这段代码的核心它判断目标极化是否在属性列表中比 eq 匹配字符串可靠得多。后台影像的分辨率属性只有有限几个值GRD 产品主要就是 10m别自己发挥写个 15m 进去筛选出来的 Collection 十有八九是空的。3. 批量导出的第一版脚本循环、去重与任务队列3.1 按轨道和时间窗筛选核心代码前面构造好了 Collection这一章进入导出环节。批量导出的基本思路是逐景导出千万不要把整个 Collection 合并成一张大图导出去。Sentinel-1 是单景单景拼好的直接合并会把不同轨道、不同日期的数据熔在一起后面做时序分析直接没法用。逐景导出的另一个好处是断点续传某景失败重跑时只丢那一景不影响其他任务。import ee import time import json ee.Initialize() cfg { region: ee.Geometry.Polygon([[...]]), start_date: 2024-01-01, end_date: 2024-03-31, orbit: 55, folder: sentinel1_export, scale: 10 } col (ee.ImageCollection(COPERNICUS/S1_GRD) .filterBounds(cfg[region]) .filterDate(cfg[start_date], cfg[end_date]) .filter(ee.Filter.eq(instrumentMode, IW)) .filter(ee.Filter.eq(resolution, 10m)) .filter(ee.Filter.eq(relativeOrbitNumber_start, cfg[orbit])) .filter(ee.Filter.listContains(transmitterReceiverPolarisation, VV))) col_size col.size().getInfo() print(筛选后景数:, col_size)这是我的第一版脚本入口。cfg 字典把轨道号放进去后续时间窗、文件夹、分辨率改动只需要改一个字典。print 出来的景数要人工核对一下比如三个月一个轨道大概多少景心里有数再往下走别直接进 for 循环提交任务。我一般会先用这段脚本跑一遍确认输出的景数在预期范围内再执行后面的提交逻辑。3.2 导出任务批量提交的循环结构逐景导出就是把 Collection 转成 List遍历每个 Image 单独建一个导出任务。关键点image 导出之前先 select 出需要的波段再 clip 到 region这样 GeoTIFF 里不会有黑边也避免把区域外的无效值一起存下来。export_list [] img_list col.toList(col.size()) n col_size for i in range(n): img ee.Image(img_list.get(i)) idx img.get(system:index) date_str img.date().format(yyyyMMdd) image_out img.select([VV, VH]).clip(cfg[region]) filename fS1_{cfg[orbit]}_{date_str}_{i} task ee.Export.image.toDrive( imageimage_out, descriptionfilename, foldercfg[folder], fileNamePrefixfilename, regioncfg[region], scalecfg[scale], maxPixels1e13, fileFormatGeoTIFF ) task.start() export_list.append({ index: idx.getInfo(), filename: filename, task_id: task.id }) time.sleep(0.5) with open(task_list.json, w, encodingutf-8) as f: json.dump(export_list, f, ensure_asciiFalse, indent2)参数说明scale 写成 10对应 IW 模式 GRD 的原生采样间隔但实际不同轨道的像素间距存在轻微差异这个在第五章展开。maxPixels 设到 1e13是为了防止大面积任务在默认 1e8 上限下直接挂掉。sleep(0.5) 防止短时间提交太多任务把配额打满触发限流反而更慢。task_list.json 是给整个流程留的后悔药任务全部提交后靠这个文件逐个查状态不需要去网页端翻列表。3.3 防止重复导出的索引设计重复提交是批量脚本最容易犯的错同一轨道同一日期跑了两次Drive 里出现一堆同名文件GEE 任务队列也会堆积冗余任务。我的处理方式是用文件名作为唯一键规则是轨道、日期、序号三段拼接并发提交前先查一遍已有记录。existing set() try: with open(task_list.json, r, encodingutf-8) as f: for item in json.load(f): existing.add(item[filename]) except FileNotFoundError: pass每次提交前检查 filename 是否已经在 existing 里在就跳过。这个小检查看起来多余但在断网重跑、任务列表清空重新提交时能挡住一大部分重复任务。另外提交任务后不要立刻关闭脚本把 task_id 和文件名写回 task_list.json 这一步很关键。真丢了 task_id在 GEE 后台按文件名搜也能找回来但多花的时间没必要。4. 把脚本改成工程配置多区域、多时段与状态监控4.1 配置对象的统一管理脚本一旦跑起来就在那挂着不出现问题还好出现问题只能干等。所以第二版我把 cfg 扩展成了完整配置对象一次性管理区域、轨道、日期和导出文件夹。config { job_name: sim_project_x, folder: sentinel1_export, scale: 10, maxPixels: 1e13, polarisations: [VV, VH], date_ranges: [ {start: 2024-01-01, end: 2024-01-31}, {start: 2024-02-01, end: 2024-02-28} ], regions: [ {name: area_a, geometry: ee.Geometry.Polygon([[...]])}, {name: area_b, geometry: ee.Geometry.Polygon([[...]])} ] }把区域、时间窗都收进配置而不是散落在代码里。一个工程跑完配置字典、task_list.json 和导出数据放在一起后续复盘时一眼能看出这堆 GeoTIFF 来自哪个区域哪个轨道。这比改十处代码再猜参数强得多。代码可维护性在批量任务场景不是矫情而是真实的时间成本。4.2 多区域与多时段批量组合有的需求涉及多个不连通区域比如同一流域的上游、下游分属不同多边形。此时不要合并成一个 MultiPolygon 直接导出合并后边界框会变大把大片无关区域也裁出来白白浪费存储和处理时间。正确做法是逐区域、逐时段分别提交导出任务互相不干扰。def build_collection(geometry, start, end): return (ee.ImageCollection(COPERNICUS/S1_GRD) .filterBounds(geometry) .filterDate(start, end) .filter(ee.Filter.eq(instrumentMode, IW)) .filter(ee.Filter.eq(resolution, 10m)) .filter(ee.Filter.eq(relativeOrbitNumber_start, config.get(orbit, 0))) .filter(ee.Filter.listContains(transmitterReceiverPolarisation, VV))) for region_cfg in config[regions]: for dr in config[date_ranges]: col build_collection(region_cfg[geometry], dr[start], dr[end]) if col.size().getInfo() 0: print(f无数据: {region_cfg[name]} {dr[start]}) continue submit_export_tasks(col, region_cfg[name])这段代码的好处是输出管理非常清晰area_a 和 area_b 的文件前缀不同下载后只靠文件名就能归位不用打开影像看内容。多时段拆开跑还有另一个好处GEE 单次任务并发有上限拆成两个月两批任务数量好控制失败重试也不会波及全部。4.3 任务状态监控与失败重提提交完任务并不等于下载完成还差一个监控环节。EE 的 task 状态有 READY、RUNNING、COMPLETED、FAILED 四种轮询 task.status() 即可掌握全局。def monitor_tasks(task_list, check_interval600): pending {t[task_id]: t for t in task_list} fail_count 0 while pending: time.sleep(check_interval) for task_id in list(pending.keys()): status ee.batch.Task(task_id).status() state status.get(state, ) if state COMPLETED: pending.pop(task_id) elif state FAILED: print(失败:, task_id, status.get(error_message)) pending.pop(task_id) fail_count 1 print(f剩余任务: {len(pending)}, 失败: {fail_count})这个监控脚本里 fail_count 只统计数量具体失败的 task_id 要打印到日志里后续按文件名重提交。注意重提交之前先确认是真失败而不是Drive 配额超了否则提交多少失败多少状态很打击心态。常见做法是加一个重试函数只在 error_message 里包含 Image too large 时才调 maxPixels 参数重试其他错误不要盲目循环。比如轨道号写错导致的空 Collection重试一百次也还是空。5. 避坑Sentinel-1 批量导出最常见的五个坑以下都是实测里踩过不止一次的坑每条按现象到原因到解决来写照着排查能省半天时间。5.1 任务显示成功下载文件却是 0 字节现象导出任务状态显示 COMPLETEDDrive 里文件却是 0MB或者整个文件夹是空的。原因最常见是 filterBounds 里传了 FeatureCollection 而不是 geometryEE 对 FeatureCollection 过滤生效了但导出时 region 没跟着变区域对不上。另一个原因是筛选条件写得过严Collection 本身为空任务却照常提交导了个寂寞。解决提交任务前先打印 col.size().getInfo()为 0 就直接 return不进入导出流程。filterBounds 的参数一律用 .geometry() 提取边界别把整个 FeatureCollection 塞进去。5.2 scale 设为 10 却导出错位影像现象同一区域相邻轨道影像拼接时出现约十米量级的偏移边界处有明显的黑边或重叠。原因GRD 影像在不同轨道上原始像素间隔略有差异IW 模式大约在 10 米上下浮动。直接指定 scale10 会让 GEE 做重投影和插值逐景误差积累起来拼接时问题就暴露了。解决导单景时不指定 scale改用 crsTransform让 GEE 用场景自带的坐标变换导出。需要统一栅格时再固定 crs让 scale 承担重投影的职责。我现在写导出函数默认不带 scale只有需要标准化输出时才显式传。5.3 maxPixels 默认值导致大面积任务失败现象监控脚本打印 FAILED错误信息里出现 Image too large: computed image dimensions。原因Export 默认 maxPixels 是 1e8大面积区域或高分辨率导出必然超限。多见于选了 10m 分辨率但区域覆盖整个县级范围的时候。解决在导出参数里显式写 maxPixels1e13。如果区域实在太大按 UTM 分带切块导出避免单任务覆盖跨两个分带的大范围。另外要注意maxPixels 调大后处理时间和内存占用也会上升不是越大越好。5.4 UTC 时间窗导致少一天数据现象东八区用户设置日期窗为 2024-01-01 到 2024-01-31导出结果少了 1 月 31 日当天的景。原因GEE 的 filterDate 时间边界按 UTC 零点算东八区的 1 月 31 日从 UTC 1 月 31 日 16:00 才开始直接写 endDate 到 1 月 31 日零点等于把这一天截掉了。解决filterDate 的 end 统一写成截止日期加一天比如到 2024-02-01。文件名里保留实际日期下载后可以对照验证看看有没有日期边界问题。5.5 Drive 文件重名导致任务队列中断现象跑到第 50 个任务时一连串任务报错错误信息类似 File already exists。原因同一 fileNamePrefix 重复提交Drive 不允许覆盖同名文件新任务直接失败。解决文件名里加上轨道号、日期和序号提交前检查 task_list.json文件名已存在就直接跳过。我现在的命名规则统一为 S1_轨道号_日期_序号三字段组合撞名概率极低而且一眼能看出是哪批数据。6. 先小范围验证再全量跑批的收尾技巧6.1 单景验证脚本全量队列提交之前先导出一景。取最新日期的一景导出到单独文件夹下载后在本地打开看范围、极化和像素值是否正常再决定是否放开批量。validate_img col.sort(system:time_start, False).first() out ee.Image(validate_img.select([VV, VH])).clip(region) task ee.Export.image.toDrive( imageout, descriptionvalidation_scene, folders1_validate, fileNamePrefixvalidation_scene, regionregion, scale10, maxPixels1e13 ) task.start()这一步只花几分钟却能暴露选区、投影和波段选择的问题。尤其是刚开始用某个区域时边界画错、坐标系搞混单景验证一眼就能看出来。全量跑完再发现区域不对重下几十个文件时间和心情都搭进去了。6.2 下载后的快检手段文件下载回来后用本地工具快速检查别直接丢进分析管线。命令行里用 gdalinfo 看影像元数据gdalinfo S1_sim_project_x_20240101.tif | grep -E Size|Band|Coordinate主要看三个地方栅格尺寸是否与预期一致Band 数量是不是 2 个VV 和 VH坐标范围是否覆盖研究区。Band 数量对不上多半是 select 写错了极化边界偏了回头查 crs 设置。这套检查流程十秒钟就够但能挡掉大部分低级错误。从那以后我每次跑批量队列之前都强制走一遍单景验证 → 本地 gdalinfo 快检 → 再全量提交的流程。这套习惯帮我挡掉了好几次大翻车也让我对每批导出数据的质量心里有底。希望帮到你。本文还有配套的精品资源点击获取