本文档介绍了一些编码做法,旨在最大限度地提高复杂或耗时的 Earth Engine 计算成功的几率。此处介绍的方法适用于交互式(例如 Code Editor)和批处理 (Export
) 计算,但通常长时间运行的计算应在批处理系统中运行。
避免将客户端函数和对象与服务器函数和对象混用
Earth Engine 服务器对象是指构造函数以 ee
开头(例如 ee.Image
、ee.Reducer
)的对象,并且此类对象上的任何方法都是服务器函数。任何未以这种方式构建的对象都是客户端对象。客户端对象可以来自 Code Editor(例如 Map
、Chart
)或 JavaScript 语言(例如 Date
、Math
、[]
、{}
)。
如此处、此处和此处所述,为避免意外行为,请勿在脚本中混用客户端和服务器函数。如需深入了解 Earth Engine 中的客户端与服务器,请参阅此页面和/或此教程。以下示例说明了混合使用客户端和服务器功能的危险性:
错误 - 此代码无效!
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
// Won't work.
for(var i=0; i<table.size(); i++) {
print('No!');
}
您能发现错误吗?请注意,table.size()
是服务器对象上的服务器方法,不能与客户端功能(例如 <
条件)搭配使用。
您可能需要使用 for 循环的情况是界面设置,因为 Code Editor ui
对象和方法位于客户端。(详细了解如何在 Earth Engine 中创建界面)。例如:
使用客户端函数进行界面设置。
var panel = ui.Panel();
for(var i=1; i<8; i++) {
panel.widgets().set(i, ui.Button('button ' + i))
}
print(panel);
反之,map()
是服务器函数,客户端功能无法在传递给 map()
的函数内运行。例如:
错误 - 此代码无效!
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
// Error:
var foobar = table.map(function(f) {
print(f); // Can't use a client function here.
// Can't Export, either.
});
如需对集合中的每个元素执行操作,请对集合 map()
一个函数并 set()
一个属性:
使用 map()
和 set()
!
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
print(table.first());
// Do something to every element of a collection.
var withMoreProperties = table.map(function(f) {
// Set a property.
return f.set('area_sq_meters', f.area())
});
print(withMoreProperties.first());
您还可以根据计算属性或现有属性filter()
集合,并print()
结果。请注意,您无法输出包含 5,000 多个元素的集合。如果您收到“Collection query aborted after accumulating over 5000 elements”错误,请在输出之前对集合执行 filter()
或 limit()
。
避免不必要地转换为列表
Earth Engine 中的集合会使用优化进行处理,但将集合转换为 List
或 Array
类型会破坏这些优化。除非您需要对集合元素进行随机访问(即需要获取集合的 i 个元素),否则请对集合使用过滤器来访问各个集合元素。以下示例展示了使用类型转换(不推荐)和过滤(推荐)来访问集合中元素之间的区别:
请勿不必要地转换为列表!
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
// Do NOT do this!!
var list = table.toList(table.size());
print(list.get(13)); // User memory limit exceeded.
请注意,如果不必要地将集合转换为列表,则很容易触发错误。更安全的方法是使用 filter()
:
使用 filter()
!
print(table.filter(ee.Filter.eq('country_na', 'Niger')).first());
请注意,您应尽早在分析中使用过滤条件。
避免使用 ee.Algorithms.If()
请勿使用 ee.Algorithms.If()
来实现分支逻辑,尤其是在映射函数中。如以下示例所示,ee.Algorithms.If()
可能会占用大量内存,因此不建议使用:
请勿使用 If()
!
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
// Do NOT do this!
var veryBad = table.map(function(f) {
return ee.Algorithms.If({
condition: ee.String(f.get('country_na')).compareTo('Chad').gt(0),
trueCase: f, // Do something.
falseCase: null // Do something else.
});
}, true);
print(veryBad); // User memory limit exceeded.
// If() may evaluate both the true and false cases.
请注意,map()
的第二个实参为 true
。这意味着映射的函数可能会返回 null,而这些 null 将在生成的集合中被丢弃。这可能很有用(无需 If()
),但在这里,最简单的解决方案是使用过滤器:
使用 filter()
!
print(table.filter(ee.Filter.eq('country_na', 'Chad')));
如本教程所示,使用过滤器的函数式编程方法是将一种逻辑应用于集合的某些元素,将另一种逻辑应用于集合的其他元素的正确方法。
避免使用 reproject()
除非绝对必要,否则请勿使用重新投影。您可能想要使用 reproject()
的一个原因是,强制在特定规模下进行 Code Editor 计算,以便您能够在所需的分析规模下检查结果。在下一个示例中,系统会计算热点补丁,并计算每个补丁中的像素数。运行该示例,然后点击其中一个补丁。请注意,经过重新投影的数据与未经过重新投影的数据之间的像素数不同。
var l8sr = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var sf = ee.Geometry.Point([-122.405, 37.786]);
Map.centerObject(sf, 13);
// A reason to reproject - counting pixels and exploring interactively.
var image = l8sr.filterBounds(sf)
.filterDate('2019-06-01', '2019-12-31')
.first();
image = image.multiply(0.00341802).add(149); // Apply scale factors.
Map.addLayer(image, {bands: ['ST_B10'], min: 280, max: 317}, 'image');
var hotspots = image.select('ST_B10').gt(317)
.selfMask()
.rename('hotspots');
var objectSize = hotspots.connectedPixelCount(256);
Map.addLayer(objectSize, {min: 1, max: 256}, 'Size No Reproject', false);
// Beware of reproject! Don't zoom out on reprojected data.
var reprojected = objectSize.reproject(hotspots.projection());
Map.addLayer(reprojected, {min: 1, max: 256}, 'Size Reproject', false);
出现差异的原因在于,分析的缩放比例由代码编辑器的缩放级别设置。通过调用 reproject()
,您可以设置计算的规模,而不是通过 Code Editor 设置。请务必谨慎使用 reproject()
,原因详见此文档。
先过滤和 select()
一般而言,请先按时间、地理位置和/或元数据过滤输入集合,然后再对集合执行任何其他操作。先应用选择性更高的过滤条件,再应用选择性较低的过滤条件。空间过滤器和/或时间过滤器通常更具选择性。例如,请注意 select()
和 filter()
是在 map()
之前应用的:
var images = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');
var sf = ee.Geometry.Point([-122.463, 37.768]);
// Expensive function to reduce the neighborhood of an image.
var reduceFunction = function(image) {
return image.reduceNeighborhood({
reducer: ee.Reducer.mean(),
kernel: ee.Kernel.square(4)
});
};
var bands = ['B4', 'B3', 'B2'];
// Select and filter first!
var reasonableComputation = images
.select(bands)
.filterBounds(sf)
.filterDate('2018-01-01', '2019-02-01')
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 1))
.aside(print) // Useful for debugging.
.map(reduceFunction)
.reduce('mean')
.rename(bands);
var viz = {bands: bands, min: 0, max: 10000};
Map.addLayer(reasonableComputation, viz, 'reasonableComputation');
使用 updateMask()
,而不要使用 mask()
updateMask()
和 mask()
之间的区别在于,前者会对参数(新遮罩)和现有图片遮罩执行逻辑 and()
,而 mask()
只会将图片遮罩替换为参数。后者的危险在于,您可能会无意中揭露像素。在此示例中,目标是掩盖海拔高度小于或等于 300 米的像素。如您所见(缩小),使用 mask()
会导致许多像素(不属于感兴趣的图片的像素)取消遮罩:
var l8sr = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var sf = ee.Geometry.Point([-122.40554461769182, 37.786807309873716]);
var aw3d30 = ee.Image('JAXA/ALOS/AW3D30_V1_1');
Map.centerObject(sf, 7);
var image = l8sr.filterBounds(sf)
.filterDate('2019-06-01', '2019-12-31')
.first();
image = image.multiply(0.0000275).subtract(0.2); // Apply scale factors.
var vis = {bands: ['SR_B4', 'SR_B3', 'SR_B2'], min: 0, max: 0.3};
Map.addLayer(image, vis, 'image', false);
var mask = aw3d30.select('AVE').gt(300);
Map.addLayer(mask, {}, 'mask', false);
// NO! Don't do this!
var badMask = image.mask(mask);
Map.addLayer(badMask, vis, 'badMask');
var goodMask = image.updateMask(mask);
Map.addLayer(goodMask, vis, 'goodMask', false);
组合 reducer
如果您需要从单个输入(例如图片区域)获取多个统计信息(例如平均值和标准差),则组合使用多个 reducer 会更高效。例如,如需获取图片统计信息,请按如下方式组合 reducer:
var image = ee.Image(
'COPERNICUS/S2_HARMONIZED/20150821T111616_20160314T094808_T30UWU');
// Get mean and SD in every band by combining reducers.
var stats = image.reduceRegion({
reducer: ee.Reducer.mean().combine({
reducer2: ee.Reducer.stdDev(),
sharedInputs: true
}),
geometry: ee.Geometry.Rectangle([-2.15, 48.55, -1.83, 48.72]),
scale: 10,
bestEffort: true // Use maxPixels if you care about scale.
});
print(stats);
// Extract means and SDs to images.
var meansImage = stats.toImage().select('.*_mean');
var sdsImage = stats.toImage().select('.*_stdDev');
请注意,在此示例中,平均值缩减器与标准差缩减器相结合,并且 sharedInputs
为 true,以便对输入像素进行单次传递。在输出字典中,会将 reducer 的名称附加到组名称。如需获取均值和标准差图片(例如对输入图片进行标准化处理),您可以将这些值转换为图片,然后使用正则表达式分别提取均值和标准差,如示例所示。
使用 Export
对于在代码编辑器中导致“用户内存用量超限”或“计算超时”错误的计算,使用 Export
可能可以成功执行相同的计算。这是因为在批处理系统(导出操作运行的位置)中运行时,超时时间更长,允许的内存占用量更大。(您可能需要先尝试其他方法,详情请参阅调试文档)。继续上例,假设字典返回了错误。您可以通过执行以下操作来获取结果:
var link = '86836482971a35a5e735a17e93c23272';
Export.table.toDrive({
collection: ee.FeatureCollection([ee.Feature(null, stats)]),
description: 'exported_stats_demo_' + link,
fileFormat: 'CSV'
});
请注意,该链接会嵌入到资源名称中,以便重现。另请注意,如果您想导出 toAsset
,则需要提供几何图形,它可以是任何内容,例如计算量小且便宜的图片重心。(即,如果不需要复杂的几何图形,请勿使用)。
如需查看使用 Export
解决计算超时和并发汇总过多的示例,请参阅调试页面。如需详细了解导出功能,请参阅此文档。
如果您不需要剪辑,请勿使用 clip()
不必要地使用 clip()
会增加计算时间。除非您的分析需要,否则请避免使用 clip()
。如果您不确定,请勿剪辑。剪辑的错误使用示例:
请勿不必要地剪裁输入!
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
var l8sr = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var belgium = table.filter(ee.Filter.eq('country_na', 'Belgium')).first();
// Do NOT clip unless you need to.
var unnecessaryClip = l8sr
.select('SR_B4') // Good.
.filterBounds(belgium.geometry()) // Good.
.filterDate('2019-01-01', '2019-04-01') // Good.
.map(function(image) {
return image.clip(belgium.geometry()); // NO! Bad! Not necessary.
})
.median()
.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: belgium.geometry(),
scale: 30,
maxPixels: 1e10,
});
print(unnecessaryClip);
可以完全跳过剪裁输入图片,因为区域是在 reduceRegion()
调用中指定的:
请指定输出区域!
var noClipNeeded = l8sr
.select('SR_B4') // Good.
.filterBounds(belgium.geometry()) // Good.
.filterDate('2019-01-01', '2019-12-31') // Good.
.median()
.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: belgium.geometry(), // Geometry is specified here.
scale: 30,
maxPixels: 1e10,
});
print(noClipNeeded);
如果此计算超时,请按此示例所示对其执行 Export
操作。
如果您需要使用复杂集合进行剪辑,请使用 clipToCollection()
如果您确实需要剪裁某些内容,并且您要用于剪裁的几何图形位于集合中,请使用 clipToCollection()
:
var ecoregions = ee.FeatureCollection('RESOLVE/ECOREGIONS/2017');
var image = ee.Image('JAXA/ALOS/AW3D30_V1_1');
var complexCollection = ecoregions
.filter(ee.Filter.eq('BIOME_NAME',
'Tropical & Subtropical Moist Broadleaf Forests'));
Map.addLayer(complexCollection, {}, 'complexCollection');
var clippedTheRightWay = image.select('AVE')
.clipToCollection(complexCollection);
Map.addLayer(clippedTheRightWay, {}, 'clippedTheRightWay', false);
请勿对大型和/或复杂的集合使用 featureCollection.geometry()
或 featureCollection.union()
,因为它们可能需要更多内存。
请勿将复杂集合用作 reducer 的区域
如果您需要进行空间缩减,以便缩减器将 FeatureCollection
中的多个区域的输入汇总到一起,请勿将 featureCollection.geometry()
作为缩减器的 geometry
输入提供。而是使用 clipToCollection()
和足以涵盖集合边界的区域。例如:
var ecoregions = ee.FeatureCollection('RESOLVE/ECOREGIONS/2017');
var image = ee.Image('JAXA/ALOS/AW3D30_V1_1');
var complexCollection = ecoregions
.filter(ee.Filter.eq('BIOME_NAME', 'Tropical & Subtropical Moist Broadleaf Forests'));
var clippedTheRightWay = image.select('AVE')
.clipToCollection(complexCollection);
Map.addLayer(clippedTheRightWay, {}, 'clippedTheRightWay');
var reduction = clippedTheRightWay.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: ee.Geometry.Rectangle({
coords: [-179.9, -50, 179.9, 50], // Almost global.
geodesic: false
}),
scale: 30,
maxPixels: 1e12
});
print(reduction); // If this times out, export it.
使用非零 errorMargin
对于可能需要大量计算的几何图形运算,请根据计算所需的精度使用尽可能大的误差范围。误差边界指定了在对几何图形执行操作(例如重新投影)期间允许的最大误差(以米为单位)。指定较小的误差范围可能会导致需要对几何图形(含坐标)进行重合,这可能会占用大量内存。最好为计算指定尽可能大的误差范围:
var ecoregions = ee.FeatureCollection('RESOLVE/ECOREGIONS/2017');
var complexCollection = ecoregions.limit(10);
Map.centerObject(complexCollection);
Map.addLayer(complexCollection);
var expensiveOps = complexCollection.map(function(f) {
return f.buffer(10000, 200).bounds(200);
});
Map.addLayer(expensiveOps, {}, 'expensiveOps');
请勿将 reduceToVectors()
与极小的比例搭配使用
如果您想将光栅图像转换为矢量图像,请使用适当的比例。指定非常小的比例可能会大幅增加计算开销。将比例设置得尽可能高,以提供所需的精度。例如,如需获取表示全球陆地的多边形,请执行以下操作:
var etopo = ee.Image('NOAA/NGDC/ETOPO1');
// Approximate land boundary.
var bounds = etopo.select(0).gt(-100);
// Non-geodesic polygon.
var almostGlobal = ee.Geometry.Polygon({
coords: [[-180, -80], [180, -80], [180, 80], [-180, 80], [-180, -80]],
geodesic: false
});
Map.addLayer(almostGlobal, {}, 'almostGlobal');
var vectors = bounds.selfMask().reduceToVectors({
reducer: ee.Reducer.countEvery(),
geometry: almostGlobal,
// Set the scale to the maximum possible given
// the required precision of the computation.
scale: 50000,
});
Map.addLayer(vectors, {}, 'vectors');
在前面的示例中,请注意使用非测地多边形在全局求和中进行求值。
请勿将 reduceToVectors()
与 reduceRegions()
搭配使用
请勿将 reduceToVectors()
返回的 FeatureCollection
用作 reduceRegions()
的输入。请改为在调用 reduceToVectors()
之前添加要缩减的频段:
var etopo = ee.Image('NOAA/NGDC/ETOPO1');
var mod11a1 = ee.ImageCollection('MODIS/006/MOD11A1');
// Approximate land boundary.
var bounds = etopo.select(0).gt(-100);
// Non-geodesic polygon.
var almostGlobal = ee.Geometry.Polygon({
coords: [[-180, -80], [180, -80], [180, 80], [-180, 80], [-180, -80]],
geodesic: false
});
var lst = mod11a1.first().select(0);
var means = bounds.selfMask().addBands(lst).reduceToVectors({
reducer: ee.Reducer.mean(),
geometry: almostGlobal,
scale: 1000,
maxPixels: 1e10
});
print(means.limit(10));
请注意,在另一个图像的区域内减少一个图像的像素的其他方法包括 reduceConnectedCommponents() 和/或分组 reducer。
使用 fastDistanceTransform()
执行邻域操作
对于某些卷积运算,fastDistanceTransform()
可能比 reduceNeighborhood()
或 convolve()
更高效。例如,如需对二进制输入进行侵蚀和/或膨胀,请执行以下操作:
var aw3d30 = ee.Image('JAXA/ALOS/AW3D30_V1_1');
// Make a simple binary layer from a threshold on elevation.
var mask = aw3d30.select('AVE').gt(300);
Map.setCenter(-122.0703, 37.3872, 11);
Map.addLayer(mask, {}, 'mask');
// Distance in pixel units.
var distance = mask.fastDistanceTransform().sqrt();
// Threshold on distance (three pixels) for a dilation.
var dilation = distance.lt(3);
Map.addLayer(dilation, {}, 'dilation');
// Do the reverse for an erosion.
var notDistance = mask.not().fastDistanceTransform().sqrt();
var erosion = notDistance.gt(3);
Map.addLayer(erosion, {}, 'erosion');
使用 reduceNeighborhood()
中的优化
如果您需要执行卷积,但无法使用 fastDistanceTransform()
,请使用 reduceNeighborhood()
中的优化。
var l8raw = ee.ImageCollection('LANDSAT/LC08/C02/T1_RT');
var composite = ee.Algorithms.Landsat.simpleComposite(l8raw);
var bands = ['B4', 'B3', 'B2'];
var optimizedConvolution = composite.select(bands).reduceNeighborhood({
reducer: ee.Reducer.mean(),
kernel: ee.Kernel.square(3),
optimization: 'boxcar' // Suitable optimization for mean.
}).rename(bands);
var viz = {bands: bands, min: 0, max: 72};
Map.setCenter(-122.0703, 37.3872, 11);
Map.addLayer(composite, viz, 'composite');
Map.addLayer(optimizedConvolution, viz, 'optimizedConvolution');
不要采样超出所需的数据
不要不必要地增加训练数据集的大小。虽然在某些情况下,增加训练数据量是一种有效的机器学习策略,但这也可能会增加计算开销,而准确性却没有相应提高。(如需了解何时增加训练数据集大小,请参阅此参考文档)。以下示例演示了如何请求过多训练数据可能会导致令人恐惧的“计算值过大”错误:
请勿抽取太多数据!
var l8raw = ee.ImageCollection('LANDSAT/LC08/C02/T1_RT');
var composite = ee.Algorithms.Landsat.simpleComposite(l8raw);
var labels = ee.FeatureCollection('projects/google/demo_landcover_labels');
// No! Not necessary. Don't do this:
labels = labels.map(function(f) { return f.buffer(100000, 1000); });
var bands = ['B2', 'B3', 'B4', 'B5', 'B6', 'B7'];
var training = composite.select(bands).sampleRegions({
collection: labels,
properties: ['landcover'],
scale: 30
});
var classifier = ee.Classifier.smileCart().train({
features: training,
classProperty: 'landcover',
inputProperties: bands
});
print(classifier.explain()); // Computed value is too large
更好的方法是先使用适量的数据,然后调整分类器的超参数,以确定能否达到所需的准确性:
调整超参数!
var l8raw = ee.ImageCollection('LANDSAT/LC08/C02/T1_RT');
var composite = ee.Algorithms.Landsat.simpleComposite(l8raw);
var labels = ee.FeatureCollection('projects/google/demo_landcover_labels');
// Increase the data a little bit, possibly introducing noise.
labels = labels.map(function(f) { return f.buffer(100, 10); });
var bands = ['B2', 'B3', 'B4', 'B5', 'B6', 'B7'];
var data = composite.select(bands).sampleRegions({
collection: labels,
properties: ['landcover'],
scale: 30
});
// Add a column of uniform random numbers called 'random'.
data = data.randomColumn();
// Partition into training and testing.
var training = data.filter(ee.Filter.lt('random', 0.5));
var testing = data.filter(ee.Filter.gte('random', 0.5));
// Tune the minLeafPopulation parameter.
var minLeafPops = ee.List.sequence(1, 10);
var accuracies = minLeafPops.map(function(p) {
var classifier = ee.Classifier.smileCart({minLeafPopulation: p})
.train({
features: training,
classProperty: 'landcover',
inputProperties: bands
});
return testing
.classify(classifier)
.errorMatrix('landcover', 'classification')
.accuracy();
});
print(ui.Chart.array.values({
array: ee.Array(accuracies),
axis: 0,
xLabels: minLeafPops
}));
在此示例中,分类器已经非常准确,因此不需要进行太多调整。您可能需要选择一个仍能达到所需准确度的尽可能小的树(即最大的 minLeafPopulation
)。
Export
中间结果
假设您的目标是从相对复杂的计算图像中提取样本。通常,Export
图片 toAsset()
、加载导出的图片,然后进行抽样,这样更高效。例如:
var image = ee.Image('UMD/hansen/global_forest_change_2018_v1_6');
var geometry = ee.Geometry.Polygon(
[[[-76.64069800085349, 5.511777325802095],
[-76.64069800085349, -20.483938229362376],
[-35.15632300085349, -20.483938229362376],
[-35.15632300085349, 5.511777325802095]]], null, false);
var testRegion = ee.Geometry.Polygon(
[[[-48.86726050085349, -3.0475996402515717],
[-48.86726050085349, -3.9248707849303295],
[-47.46101050085349, -3.9248707849303295],
[-47.46101050085349, -3.0475996402515717]]], null, false);
// Forest loss in 2016, to stratify a sample.
var loss = image.select('lossyear');
var loss16 = loss.eq(16).rename('loss16');
// Scales and masks Landsat 8 surface reflectance images.
function prepSrL8(image) {
var qaMask = image.select('QA_PIXEL').bitwiseAnd(parseInt('11111', 2)).eq(0);
var opticalBands = image.select('SR_B.').multiply(0.0000275).add(-0.2);
var thermalBands = image.select('ST_B.*').multiply(0.00341802).add(149.0);
return image.addBands(opticalBands, null, true)
.addBands(thermalBands, null, true)
.updateMask(qaMask);
}
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
.map(prepSrL8);
// Create two annual cloud-free composites.
var composite1 = collection.filterDate('2015-01-01', '2015-12-31').median();
var composite2 = collection.filterDate('2017-01-01', '2017-12-31').median();
// We want a strtatified sample of this stack.
var stack = composite1.addBands(composite2)
.float(); // Export the smallest size possible.
// Export the image. This block is commented because the export is complete.
/*
var link = '0b8023b0af6c1b0ac7b5be649b54db06'
var desc = 'Logistic_regression_stack_' + link;
Export.image.toAsset({
image: stack,
description: desc,
assetId: desc,
region: geometry,
scale: 30,
maxPixels: 1e10
})
*/
// Load the exported image.
var exportedStack = ee.Image(
'projects/google/Logistic_regression_stack_0b8023b0af6c1b0ac7b5be649b54db06');
// Take a very small sample first, to debug.
var testSample = exportedStack.addBands(loss16).stratifiedSample({
numPoints: 1,
classBand: 'loss16',
region: testRegion,
scale: 30,
geometries: true
});
print(testSample); // Check this in the console.
// Take a large sample.
var sample = exportedStack.addBands(loss16).stratifiedSample({
numPoints: 10000,
classBand: 'loss16',
region: geometry,
scale: 30,
});
// Export the large sample...
请注意,在此示例中,图像以浮点值导出。除非绝对必要,否则请勿以双精度导出。进行此导出操作时,请注意,为了实现可重现性,文件名中嵌入了 Code Editor 链接(在导出前立即获取)。
导出完成后,重新加载资源并继续从中抽样。请注意,系统会先运行一小部分测试区域中的一小部分样本,以进行调试。当系统显示导出成功后,请获取较大的样本并进行导出。此类大型样本通常需要导出。请勿期望在未先导出这些示例的情况下,能够以交互方式(例如通过 print()
)使用或使用这些示例(例如将其用作分类器的输入)。
联接与映射-过滤
假设您想根据时间、位置或某个元数据属性联接合集。通常,使用联接是最有效的方式。以下示例在 Landsat 8 和 Sentinel-2 集合之间执行时空联接:
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
.filterBounds(ee.Geometry.Point([-2.0205, 48.647]));
var l8 = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var joined = ee.Join.saveAll('landsat').apply({
primary: s2,
secondary: l8,
condition: ee.Filter.and(
ee.Filter.maxDifference({
difference: 1000 * 60 * 60 * 24, // One day in milliseconds
leftField: 'system:time_start',
rightField: 'system:time_start',
}),
ee.Filter.intersects({
leftField: '.geo',
rightField: '.geo',
})
)
});
print(joined);
虽然您应先尝试联接(如有需要,请使用 Export
),但有时 map()
中的 filter()
也能发挥作用,尤其是对于非常大的集合。
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
.filterBounds(ee.Geometry.Point([-2.0205, 48.647]));
var l8 = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var mappedFilter = s2.map(function(image) {
var date = image.date();
var landsat = l8
.filterBounds(image.geometry())
.filterDate(date.advance(-1, 'day'), date.advance(1, 'day'));
// Return the input image with matching scenes in a property.
return image.set({
landsat: landsat,
size: landsat.size()
});
}).filter(ee.Filter.gt('size', 0));
print(mappedFilter);
reduceRegion()
与 reduceRegions()
与 for 循环
使用非常大或非常复杂的 FeatureCollection
作为输入调用 reduceRegions()
可能会导致令人恐惧的“计算值过大”错误。一种可能的解决方案是改为将 reduceRegion()
映射到 FeatureCollection
。另一种可能的解决方案是使用(惊恐)for 循环。虽然我们强烈建议您不要在 Earth Engine 中这样做(如此处、此处和此处所述),但您可以在 for 循环中实现 reduceRegion()
以执行大量求交运算。
假设您的目标是为 ImageCollection
中的每张图片获取 FeatureCollection
中的每个地图项中的像素均值(或任何统计信息)。以下示例比较了前面介绍的三种方法:
// Table of countries.
var countriesTable = ee.FeatureCollection("USDOS/LSIB_SIMPLE/2017");
// Time series of images.
var mod13a1 = ee.ImageCollection("MODIS/006/MOD13A1");
// MODIS vegetation indices (always use the most recent version).
var band = 'NDVI';
var imagery = mod13a1.select(band);
// Option 1: reduceRegions()
var testTable = countriesTable.limit(1); // Do this outside map()s and loops.
var data = imagery.map(function(image) {
return image.reduceRegions({
collection: testTable,
reducer: ee.Reducer.mean(),
scale: 500
}).map(function(f) {
return f.set({
time: image.date().millis(),
date: image.date().format()
});
});
}).flatten();
print(data.first());
// Option 2: mapped reduceRegion()
var data = countriesTable.map(function(feature) {
return imagery.map(function(image) {
return ee.Feature(feature.geometry().centroid(100),
image.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: feature.geometry(),
scale: 500
})).set({
time: image.date().millis(),
date: image.date().format()
}).copyProperties(feature);
});
}).flatten();
print(data.first());
// Option 3: for-loop (WATCH OUT!)
var size = countriesTable.size();
// print(size); // 312
var countriesList = countriesTable.toList(1); // Adjust size.
var data = ee.FeatureCollection([]); // Empty table.
for (var j=0; j<1; j++) { // Adjust size.
var feature = ee.Feature(countriesList.get(j));
// Convert ImageCollection > FeatureCollection
var fc = ee.FeatureCollection(imagery.map(function(image) {
return ee.Feature(feature.geometry().centroid(100),
image.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: feature.geometry(),
scale: 500
})).set({
time: image.date().millis(),
date: image.date().format()
}).copyProperties(feature);
}));
data = data.merge(fc);
}
print(data.first());
请注意,系统会输出每个集合中的 first()
实体,以便进行调试。您不应期望能够以交互方式查看完整结果:您需要执行 Export
。另请注意,应谨慎使用 for 循环,并且只应在万不得已的情况下使用。最后,for 循环需要手动获取输入集合的大小,并将其硬编码到适当的位置。如果您对上述任何内容感到不清楚,请勿使用 for 循环。
对时间邻居使用正向差分
假设您有一个按时间排序的 ImageCollection
(即时间序列),并且您想将每张图片与前一张(或下一张)图片进行比较。为此,使用基于数组的前向差分可能比使用 iterate()
更高效。以下示例使用此方法对 Sentinel-2 集合进行去重,其中重复项定义为同一日期的图片:
var sentinel2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var sf = ee.Geometry.Point([-122.47555371521855, 37.76884708376152]);
var s2 = sentinel2
.filterBounds(sf)
.filterDate('2018-01-01', '2019-12-31');
var withDoys = s2.map(function(image) {
var ndvi = image.normalizedDifference(['B4', 'B8']).rename('ndvi');
var date = image.date();
var doy = date.getRelative('day', 'year');
var time = image.metadata('system:time_start');
var doyImage = ee.Image(doy)
.rename('doy')
.int();
return ndvi.addBands(doyImage).addBands(time)
.clip(image.geometry()); // Appropriate use of clip.
});
var array = withDoys.toArray();
var timeAxis = 0;
var bandAxis = 1;
var dedupe = function(array) {
var time = array.arraySlice(bandAxis, -1);
var sorted = array.arraySort(time);
var doy = sorted.arraySlice(bandAxis, -2, -1);
var left = doy.arraySlice(timeAxis, 1);
var right = doy.arraySlice(timeAxis, 0, -1);
var mask = ee.Image(ee.Array([[1]]))
.arrayCat(left.neq(right), timeAxis);
return array.arrayMask(mask);
};
var deduped = dedupe(array);
// Inspect these outputs to confirm that duplicates have been removed.
print(array.reduceRegion('first', sf, 10));
print(deduped.reduceRegion('first', sf, 10));
检查打印的照片集,确认已移除重复照片。