В этом документе описываются методы кодирования, призванные максимизировать шансы на успех сложных или дорогостоящих вычислений Earth Engine. Описанные здесь методы применимы как к интерактивным (например, Редактор кода), так и к пакетным ( Export
) вычислениям, хотя, как правило, длительные вычисления следует выполнять в пакетной системе.
Избегайте смешивания клиентских функций и объектов с серверными функциями и объектами.
Объекты сервера Earth Engine — это объекты, конструкторы которых начинаются с ee
(например, ee.Image
, ee.Reducer
), а любые методы таких объектов являются серверными функциями. Любой объект, созданный не таким образом, является клиентским объектом. Клиентские объекты могут поступать из редактора кода (например, 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, связана с настройкой пользовательского интерфейса, поскольку объекты и методы 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()
результат. Обратите внимание, что вы не можете распечатать коллекцию, содержащую более 5000 элементов. Если вы получаете сообщение об ошибке «Запрос к коллекции прерван после накопления более 5000 элементов», 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, и они будут удалены из результирующей коллекции. Это может быть полезно (без If()
), но здесь самое простое решение — использовать фильтр:
Используйте filter()
!
print(table.filter(ee.Filter.eq('country_na', 'Chad')));
Как показано в этом руководстве , подход функционального программирования с использованием фильтров — это правильный способ применить одну логику к некоторым элементам коллекции, а другую логику — к другим элементам коллекции.
Избегайте reproject()
Не используйте reproject без крайней необходимости. Одной из причин, по которой вы можете захотеть использовать reproject()
является принудительное выполнение вычислений в редакторе кода в определенном масштабе, чтобы вы могли изучить результаты в желаемом масштабе анализа. В следующем примере вычисляются участки горячих пикселей и вычисляется количество пикселей в каждом участке. Запустите пример и нажмите на один из патчей. Обратите внимание, что количество пикселей в перепроецированных данных и данных, которые не были перепроецированы, различается.
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()
вы устанавливаете масштаб вычислений вместо редактора кода. Используйте 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);
Комбинированные редукторы
Если вам нужно несколько статистических данных (например, среднее и стандартное отклонение) из одного входного сигнала (например, области изображения), более эффективно объединить редукторы. Например, чтобы получить статистику изображений, объедините редукторы следующим образом:
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, что позволяет выполнить один проход через входные пиксели. В выходном словаре имя редуктора добавляется к имени полосы. Чтобы получить изображения среднего и стандартного отклонения (например, для нормализации входного изображения), вы можете преобразовать значения в изображение и использовать регулярные выражения для индивидуального извлечения средних значений и SD, как показано в примере.
Использовать 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()
для больших и/или сложных коллекций, которые могут требовать больше памяти.
Не используйте сложную коллекцию в качестве региона для редуктора.
Если вам нужно выполнить пространственное сокращение, чтобы редуктор объединял входные данные из нескольких регионов в 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()
Не используйте FeatureCollection
, возвращаемую функцией reduceToVectors()
в качестве входных данных для функции 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));
Обратите внимание, что другие способы уменьшения пикселей одного изображения в зонах другого включают в себя сокращениеConnectedCompents() и/или группирование редукторов .
Используйте 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...
Обратите внимание, что в этом примере изображения экспортируются как плавающие. Не экспортируйте данные с двойной точностью без крайней необходимости. При этом экспорте обратите внимание, что ссылка на редактор кода (полученная непосредственно перед экспортом) встроена в имя файла для возможности воспроизведения.
После завершения экспорта перезагрузите ресурс и приступайте к выборке из него. Обратите внимание, что для отладки сначала выполняется очень маленькая выборка на очень маленькой тестовой территории. Когда окажется, что это удалось, возьмите образец большего размера и экспортируйте его. Такие большие выборки обычно необходимо экспортировать. Не ожидайте, что такие образцы будут доступны в интерактивном режиме (например, через 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
), иногда filter()
внутри map()
также может быть эффективным, особенно для очень больших коллекций.
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
Вызов метода reduceRegions()
с очень большим или сложным FeatureCollection
в качестве входных данных может привести к ужасной ошибке «Вычисленное значение слишком велико». Одним из потенциальных решений является сопоставление reduceRegion()
с FeatureCollection
. Другое потенциальное решение — использовать цикл for (вздох). Хотя это настоятельно не рекомендуется в Earth Engine, как описано здесь , здесь и здесь , reduceRegion()
можно реализовать в цикле for для выполнения больших сокращений.
Предположим, ваша цель — получить среднее значение пикселей (или любой статистики) в каждом объекте FeatureCollection
для каждого изображения в ImageCollection
. В следующем примере сравниваются три ранее описанных подхода:
// 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));
Осмотрите напечатанные коллекции и убедитесь, что дубликаты удалены.