您在 Earth Engine 中创建的算法会在 Google 云中运行,并分布在许多计算机上。调试可能很有挑战性,因为错误可能会出现在客户端代码或编码指令的服务器端执行中,并且可能是由扩缩问题以及语法错误或逻辑错误导致的。除非您请求,否则无法检查在云端某个位置运行的程序的位。本文档介绍了调试策略、工具和解决方案,可帮助您解决常见错误并调试 Earth Engine 脚本。
语法错误
如果代码违反编程语言(Earth Engine 中的 JavaScript 或 Python)的规则,就会出现语法错误。这些错误会阻止代码运行,通常会在执行前捕获。如果您遇到语法错误,请仔细检查突出显示的行或错误消息,并参阅 Python 语言参考文档或 Google JavaScript 样式指南等资源。代码 lint 工具还可以帮助您发现和解决这些问题。
客户端错误
即使代码语法正确,也可能存在与脚本的一致性或逻辑相关的错误。以下示例展示了使用不存在的变量和方法时会出现的错误。
错误 - 此代码无效!
// Load a Sentinel-2 image. var image = ee.Image('USGS/SRTMGL1_003'); // Error: "bandNames" is not defined in this scope. var display = image.visualize({bands: bandNames, min: 0, max: 9000}); // Error: image.selfAnalyze is not a function var silly = image.selfAnalyze();
import ee import geemap.core as geemap
# Load a Sentinel-2 image. image = ee.Image('USGS/SRTMGL1_003') # NameError: name 'band_names' is not defined. display = image.visualize(bands=band_names, min=0, max=9000) # AttributeError: 'Image' object has no attribute 'selfAnalyze'. silly = image.selfAnalyze()
第一个错误会告知您,bandNames
变量未在其被引用的范围内定义。解决方法是,设置该变量,或为 bands
参数提供列表参数。第二个错误演示了调用不存在的 selfAnalyze()
函数时会发生的情况。由于这不是图片上的真实方法,因此该错误表示它不是函数。在这两种情况下,错误都会描述相应问题。
未知对象类型转换
“...is not a function
”错误可能是由 Earth Engine 不知道变量类型所致。导致此问题的常见表现包括:
- 对
first()
返回的对象执行操作(集合中元素的类型未知)。 - 对
get()
返回的对象执行操作(存储在属性中的元素类型未知)。 - 在参数类型未知的情况下,对函数参数(在函数中)执行操作。
前者的示例如下:
错误 - 此代码无效!
var collection = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017'); // Error: collection.first(...).area is not a function var area = collection.first().area();
import ee import geemap.core as geemap
collection = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017') # AttributeError: 'Element' object has no attribute 'area'. area = collection.first().area()
在所有情况下,解决方案都是使用已知类型的构造函数将未知类型的对象转换为已知类型。继续上一个示例,解决方案是转换为 ee.Feature
:
解决方案 - 使用投屏功能!
var area = ee.Feature(collection.first()).area();
import ee import geemap.core as geemap
area = ee.Feature(collection.first()).area()
(值得注意的是,您可以在此处安全地调用 Element
上的任何方法,因为 Earth Engine 认为它就是这样)。
避免混用客户端和服务器函数
以下示例不太明显:
错误 - 此代码无法执行您想要的操作
// Don't mix EE objects and JavaScript objects. var image = ee.Image('USGS/SRTMGL1_003'); var nonsense = image + 2; // You can print this, but it's not what you were hoping for. print(nonsense); // Error: g.eeObject.name is not a function Map.addLayer(nonsense);
import ee import geemap.core as geemap
# Don't mix EE objects and Python objects. image = ee.Image('USGS/SRTMGL1_003') nonsense = image + 2 # TypeError: unsupported operand type(s) for +: 'Image' and 'int'. display(nonsense) # TypeError: unsupported operand type(s) for +: 'Image' and 'int'. m = geemap.Map() m.add_layer(nonsense) m
假设此代码的作者打算向图片中的每个像素添加 2
,但这并不是正确的方法。具体而言,此代码错误地将服务器端对象 (image
) 与客户端运算符 (+
) 混合使用。结果可能会出乎意料。在第一种情况下,在 JavaScript 代码编辑器中输出 nonsense
会通过将 image
和 2
转换为字符串,然后将它们串联起来,来执行请求的操作 (+
)。生成的字符串是意外的(在 Python 中会抛出 TypeError)。
在第二种情况下,向映射添加 nonsense
时,JavaScript 代码编辑器中会显示含糊的 g.eeObject.name is not a function
错误,因为要添加到映射中的对象 nonsense
是字符串,而不是 EE 对象(在 Python 中,系统会抛出 TypeError)。为避免可能出现的意外结果和信息不足的错误,请勿将服务器对象和函数与客户端对象、基元或函数混用。此示例的解决方案是使用服务器函数。
解决方案:使用服务器函数!
Map.addLayer(image.add(2));
import ee import geemap.core as geemap
m = geemap.Map() m.add_layer(image.add(2)) m
如需了解详情,请参阅客户端与服务器页面。
JavaScript 代码编辑器浏览器锁定
如果客户端中运行的 JavaScript 花费的时间过长,或者在等待 Earth Engine 提供内容时,浏览器可能会冻结或锁定。导致此错误的两个常见原因是 JavaScript 代码编辑器代码中的 for 循环和/或 getInfo()
,最糟糕的情况是 for 循环中的 getInfo()
。由于代码在您的机器上运行,因此 for 循环可能会导致浏览器锁定。另一方面,getInfo()
会同步请求 Earth Engine 的计算结果,并在收到结果之前阻塞。如果计算需要很长时间,阻塞可能会导致浏览器锁定。在代码编辑器中工作时,请避免使用 for 循环和 getInfo()
。如需了解详情,请参阅客户端与服务器页面。
服务器端错误
尽管客户端代码在逻辑上是一致的,但可能存在仅在服务器上运行时才会显现的 bug。以下示例演示了尝试获取不存在的频段时会发生的情况。
错误 - 此代码无效!
// Load a Sentinel-2 image. var s2image = ee.Image( 'COPERNICUS/S2_HARMONIZED/20160625T100617_20160625T170310_T33UVR'); // Error: Image.select: Pattern 'nonBand' did not match any bands. print(s2image.select(['nonBand']));
import ee import geemap.core as geemap
# Load a Sentinel-2 image. s2image = ee.Image( 'COPERNICUS/S2_HARMONIZED/20160625T100617_20160625T170310_T33UVR' ) # EEException: Image.select: Band pattern 'non_band' did not match any bands. print(s2image.select(['non_band']).getInfo())
在此示例中,错误会告知您没有名为 nonBand
的频段。可能很明显的解决方案是指定确实存在的乐队名称。您可以通过输出图片并在控制台中检查图片,或输出 image.bandNames()
返回的乐队名称列表,来发现乐队名称。
不变性
您在 Earth Engine 中创建的服务器端对象是immutable。(任何 ee.Object
都是服务器端 Object
)。这意味着,如果您想更改对象,则必须将更改后的状态保存到新变量中。例如,以下代码无法在 Sentinel-2 图片上设置属性:
错误 - 此代码无法执行您想要的操作!
var s2image = ee.Image( 'COPERNICUS/S2_HARMONIZED/20160625T100617_20160625T170310_T33UVR'); s2image.set('myProperty', 'This image is not assigned to a variable'); // This will not result in an error, but will not find 'myProperty'. print(s2image.get('myProperty')); // null
import ee import geemap.core as geemap
s2image = ee.Image( 'COPERNICUS/S2_HARMONIZED/20160625T100617_20160625T170310_T33UVR' ) s2image.set('my_property', 'This image is not assigned to a variable') # This will not result in an error, but will not find 'my_property'. display(s2image.get('my_property')) # None
在此示例中,s2image.set()
会返回具有新属性的图片副本,但存储在 s2image
变量中的图片保持不变。您需要将 s2image.set()
返回的图片保存在新变量中。例如:
解决方案 - 将结果捕获到变量中!
s2image = s2image.set('myProperty', 'OK'); print(s2image.get('myProperty')); // OK
import ee import geemap.core as geemap
s2image = s2image.set('my_property', 'OK') display(s2image.get('my_property')) # OK
映射的函数
客户端和服务器函数不混用的另一个上下文是映射的函数。具体而言,映射函数指定的操作会在云端运行,因此 getInfo
和 Export
等客户端函数(以及 JavaScript 代码编辑器中的 Map
和 Chart
上的 print
和方法)无法在映射函数中运行。例如:
错误 - 此代码无效!
var collection = ee.ImageCollection('MODIS/006/MOD44B'); // Error: A mapped function's arguments cannot be used in client-side operations var badMap3 = collection.map(function(image) { print(image); return image; });
import ee import geemap.core as geemap
collection = ee.ImageCollection('MODIS/006/MOD44B') # Error: A mapped function's arguments cannot be used in client-side operations. bad_map_3 = collection.map(lambda image: print(image.getInfo()))
这个有点神秘的错误是由于 Earth Engine 使用的将此代码转换为一组可在 Google 服务器上运行的指令的流程所致。客户端函数和控制结构无法用于对传递给映射函数的参数图片进行操作。为避免此错误,请避免在映射的函数中使用客户端函数。 如需详细了解客户端函数和服务器函数之间的区别,请参阅客户端与服务器页面。
映射的函数有额外的要求。例如,映射的函数必须返回某个值:
错误 - 此代码无效!
var collection = ee.ImageCollection('MODIS/006/MOD44B'); // Error: User-defined methods must return a value. var badMap1 = collection.map(function(image) { // Do nothing. });
import ee import geemap.core as geemap
collection = ee.ImageCollection('MODIS/006/MOD44B') # Error: User-defined methods must return a value. bad_map_1 = collection.map(lambda image: None)
一个可能很明显的解决方案是返回某个值。但它不能返回任何类型的东西。具体而言,映射到 ImageCollection
或 FeatureCollection
的函数必须返回 Image
或 Feature
。例如,您无法从映射到 ImageCollection
的函数返回日期:
错误 - 此代码无效!
var collection = ee.ImageCollection('MODIS/006/MOD44B'); var badMap2 = collection.map(function(image) { return image.date(); }); // Error: Collection.map: A mapped algorithm must return a Feature or Image. print(badMap2);
import ee import geemap.core as geemap
collection = ee.ImageCollection('MODIS/006/MOD44B') bad_map_2 = collection.map(lambda image: image.date()) # EEException: Collection.map: # A mapped algorithm must return a Feature or Image. print(bad_map_2.getInfo())
为避免出现这种情况,请返回具有新属性集的输入图片。然后,如果您需要获取合集中图片的日期列表,可以使用 aggregate_array()
:
解决方法 - 设置属性!
var collection = ee.ImageCollection('MODIS/006/MOD44B'); var okMap2 = collection.map(function(image) { return image.set('date', image.date()); }); print(okMap2); // Get a list of the dates. var datesList = okMap2.aggregate_array('date'); print(datesList);
import ee import geemap.core as geemap
collection = ee.ImageCollection('MODIS/006/MOD44B') ok_map_2 = collection.map(lambda image: image.set('date', image.date())) print(ok_map_2.getInfo()) # Get a list of the dates. dates_list = ok_map_2.aggregate_array('date') print(dates_list.getInfo())
程序错误
将图案应用于没有条纹的图片
"Pattern 'my_band' was applied to an Image with no bands"
错误表示针对带有空波段列表的图片进行了 ee.Image.select()
调用。您可以采取以下措施来解决此问题:
- 如果图片是使用 ImageCollection 和 reducer 或使用
first()
或toBands()
调用生成的,请确保来源集合不为空。 - 如果图片是使用
ee.Dictionary().toImage()
从字典生成的,请确保字典不为空。 - 如果图片是独立的,请确保其中包含数据(而不仅仅是
ee.Image(0)
)。
缩放错误
虽然脚本的语法可能正确无误,没有逻辑错误,并且代表了对服务器有效的一组指令,但在并行处理和执行计算时,生成的对象可能太大、太多或计算时间过长。在这种情况下,您会收到一条错误消息,指明该算法无法扩缩。这些错误通常最难诊断和解决。此类错误的示例包括:
- 计算超时
- 并发汇总过多
- 用户内存用量超出上限
- 发生内部错误
提高代码的扩缩能力有助于您更快地获得结果,并提高所有用户可用的计算资源。以下各部分将讨论每种类型的错误,首先简要介绍一下 reduceRegion()
,这是一个常用的函数,以能够导致各种类型的缩放错误而臭名昭著。
reduceRegion()
虽然 reduceRegion()
会贪婪地消耗足够的像素来触发各种各样的错误,但也有一些参数旨在控制计算,以便您克服这些错误。例如,请考虑以下不建议的缩减:
错误 - 此代码无效!
var absurdComputation = ee.Image(1).reduceRegion({ reducer: 'count', geometry: ee.Geometry.Rectangle([-180, -90, 180, 90], null, false), scale: 100, }); // Error: Image.reduceRegion: Too many pixels in the region. // Found 80300348117, but only 10000000 allowed. print(absurdComputation);
import ee import geemap.core as geemap
absurd_computation = ee.Image(1).reduceRegion( reducer='count', geometry=ee.Geometry.Rectangle([-180, -90, 180, 90], None, False), scale=100, ) # EEException: Image.reduceRegion: Too many pixels in the region. # Found 80300348117, but only 10000000 allowed. print(absurd_computation.getInfo())
这个愚蠢的示例仅作演示之用。此错误旨在询问您是否真的要减少 80300348117(即 800 亿)像素。如果不符合,请相应地增加 scale
(以米为单位的像素大小),或将 bestEffort
设置为 true,以自动重新计算较大的比例。如需详细了解这些参数,请参阅 reduceRegion()
页面。
计算超时
假设您需要在计算中使用所有这些像素。如果是这样,您可以增加 maxPixels
参数,以便计算成功。不过,Earth Engine 需要一些时间才能完成计算。因此,系统可能会抛出“计算超时”错误:
错误 - 请勿这样做!
var ridiculousComputation = ee.Image(1).reduceRegion({ reducer: 'count', geometry: ee.Geometry.Rectangle([-180, -90, 180, 90], null, false), scale: 100, maxPixels: 1e11 }); // Error: Computation timed out. print(ridiculousComputation);
import ee import geemap.core as geemap
ridiculous_computation = ee.Image(1).reduceRegion( reducer='count', geometry=ee.Geometry.Rectangle([-180, -90, 180, 90], None, False), scale=100, maxPixels=int(1e11), ) # Error: Computation timed out. print(ridiculous_computation.getInfo())
此错误表示 Earth Engine 等待了大约 5 分钟才停止计算。通过导出,Earth Engine 可以在允许的运行时间更长(但不是内存更大)的环境中执行计算。由于 reduceRegion()
的返回值是一个字典,因此您可以使用该字典设置具有 null 几何图形的特征的属性:
良好 - 使用 Export
!
Export.table.toDrive({ collection: ee.FeatureCollection([ ee.Feature(null, ridiculousComputation) ]), description: 'ridiculousComputation', fileFormat: 'CSV' });
import ee import geemap.core as geemap
task = ee.batch.Export.table.toDrive( collection=ee.FeatureCollection([ee.Feature(None, ridiculous_computation)]), description='ridiculous_computation', fileFormat='CSV', ) # task.start()
并发汇总过多
此错误的“汇总”部分是指分布在多台机器上的操作(例如跨多个功能块的求和)。Earth Engine 设有限制,以防止同时运行过多此类汇总。在此示例中,“并发聚合过多”错误是由映射中的缩减触发的:
错误 - 请勿这样做!
var collection = ee.ImageCollection('LANDSAT/LT05/C02/T1') .filterBounds(ee.Geometry.Point([-123, 43])); var terribleAggregations = collection.map(function(image) { return image.set(image.reduceRegion({ reducer: 'mean', geometry: image.geometry(), scale: 30, maxPixels: 1e9 })); }); // Error: Quota exceeded: Too many concurrent aggregations. print(terribleAggregations);
import ee import geemap.core as geemap
collection = ee.ImageCollection('LANDSAT/LT05/C02/T1').filterBounds( ee.Geometry.Point([-123, 43]) ) def apply_mean_aggregation(image): return image.set( image.reduceRegion( reducer='mean', geometry=image.geometry(), scale=30, maxPixels=int(1e9), ) ) terrible_aggregations = collection.map(apply_mean_aggregation) # EEException: Computation timed out. print(terrible_aggregations.getInfo())
假设此代码的目的是获取每张图片的图片统计信息,一种可能的解决方案是Export
结果。例如,利用 ImageCollection
也是 FeatureCollection
这一事实,可以将与图片关联的元数据导出为表格:
良好 - 使用 Export
!
Export.table.toDrive({ collection: terribleAggregations, description: 'terribleAggregations', fileFormat: 'CSV' });
import ee import geemap.core as geemap
task = ee.batch.Export.table.toDrive( collection=terrible_aggregations, description='terrible_aggregations', fileFormat='CSV', ) # task.start()
用户内存用量超出上限
在 Earth Engine 中并行执行算法的方法之一是将输入拆分为图块,在每个图块上单独运行相同的计算,然后合并结果。因此,计算输出功能块所需的所有输入都必须能放入内存中。例如,如果输入是具有多个波段的图片,那么如果在计算中使用所有波段,最终可能会占用大量内存。为了说明这一点,此示例通过(不必要地)将整个图片集合强制转换为功能块,从而使用了过多内存:
错误 - 请勿这样做!
var bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7']; var memoryHog = ee.ImageCollection('LANDSAT/LT05/C02/T1').select(bands) .toArray() .arrayReduce(ee.Reducer.mean(), [0]) .arrayProject([1]) .arrayFlatten([bands]) .reduceRegion({ reducer: 'mean', geometry: ee.Geometry.Point([-122.27, 37.87]).buffer(1000), scale: 1, bestEffort: true, }); // Error: User memory limit exceeded. print(memoryHog);
import ee import geemap.core as geemap
bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'] memory_hog = ( ee.ImageCollection('LANDSAT/LT05/C02/T1') .select(bands) .toArray() .arrayReduce(ee.Reducer.mean(), [0]) .arrayProject([1]) .arrayFlatten([bands]) .reduceRegion( reducer=ee.Reducer.mean(), geometry=ee.Geometry.Point([-122.27, 37.87]).buffer(1000), scale=1, bestEffort=True, ) ) # EEException: User memory limit exceeded. print(memory_hog.getInfo())
这段非常糟糕的代码展示了除非您确实需要,否则不要使用数组的一个原因(另请参阅“避免不必要地转换类型”部分)。将该集合转换为超大型数组时,必须将该数组一次性加载到内存中。由于这是长时间序列的图片,因此数组很大,无法放入内存中。
一种可能的解决方案是将 tileScale
参数设置为更高的值。tileScale 值越高,功能块的大小就会越小(缩小系数为 tileScale^2
)。例如,以下代码会允许计算成功:
var bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7']; var smallerHog = ee.ImageCollection('LANDSAT/LT05/C02/T1').select(bands) .toArray() .arrayReduce(ee.Reducer.mean(), [0]) .arrayProject([1]) .arrayFlatten([bands]) .reduceRegion({ reducer: 'mean', geometry: ee.Geometry.Point([-122.27, 37.87]).buffer(1000), scale: 1, bestEffort: true, tileScale: 16 }); print(smallerHog);
import ee import geemap.core as geemap
bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'] smaller_hog = ( ee.ImageCollection('LANDSAT/LT05/C02/T1') .select(bands) .toArray() .arrayReduce(ee.Reducer.mean(), [0]) .arrayProject([1]) .arrayFlatten([bands]) .reduceRegion( reducer=ee.Reducer.mean(), geometry=ee.Geometry.Point([-122.27, 37.87]).buffer(1000), scale=1, bestEffort=True, tileScale=16, ) ) print(smaller_hog.getInfo())
不过,更推荐的解决方案是避免不必要地使用数组,这样您就不必费心处理 tileScale
:
良好 - 避免使用数组!
var bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7']; var okMemory = ee.ImageCollection('LANDSAT/LT05/C02/T1').select(bands) .mean() .reduceRegion({ reducer: 'mean', geometry: ee.Geometry.Point([-122.27, 37.87]).buffer(1000), scale: 1, bestEffort: true, }); print(okMemory);
import ee import geemap.core as geemap
bands = ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'] ok_memory = ( ee.ImageCollection('LANDSAT/LT05/C02/T1') .select(bands) .mean() .reduceRegion( reducer=ee.Reducer.mean(), geometry=ee.Geometry.Point([-122.27, 37.87]).buffer(1000), scale=1, bestEffort=True, ) ) print(ok_memory.getInfo())
除非有必要解决内存错误,否则请勿设置 tileScale
,因为较小的功能块也会导致较大的并行化开销。
内部错误
您可能会遇到如下所示的错误:
如果您收到此错误,请点击 JavaScript 代码编辑器控制台中显示的“报告错误”链接。您还可以通过帮助按钮发送反馈。此错误可能是由脚本中仅在运行时才会明显显示的逻辑错误或 Earth Engine 内部运作方式问题所致。无论是哪种情况,错误都没有提供任何信息,因此应报告该错误以便进行修复。
内部错误包含 request
ID,如下所示:
这些字符串可用作唯一标识符,帮助 Earth Engine 团队确定具体问题。在 bug 报告中添加此字符串。
调试方法
您编写了分析代码并运行了该代码,但收到了错误消息。接下来该怎么办?本部分介绍了用于查明问题并加以解决的常规调试方法。
检查变量和地图图层
假设您有一个非常复杂的分析会产生错误。如果错误的来源不明显,一个不错的初始策略是输出或直观呈现中间对象并对其进行检查,以确保对象的结构与脚本中的逻辑一致。具体而言,您可以使用代码编辑器或 geemap 检查器工具检查添加到地图的图层的像素值。如果您要打印内容,请务必使用“拉链”图标 (▶) 展开其属性。需要检查的一些事项包括:
- 频段名称。图片的条状标签名称是否与您的代码一致?
- 像素值。您的数据范围是否正确?是否进行了适当的遮盖?
- Null。是否有任何不应为 null 的值?
- 尺寸。尺寸是否为零,而本不应如此?
aside()
将分析中的每个中间步骤都放入一个变量中,以便进行输出和检查,这可能很繁琐。如需输出长函数调用链中的中间值,您可以使用 aside()
方法。例如:
var image = ee.Image(ee.ImageCollection('COPERNICUS/S2') .filterBounds(ee.Geometry.Point([-12.29, 168.83])) .aside(print) .filterDate('2011-01-01', '2016-12-31') .first());
import ee import geemap.core as geemap
image = ee.Image( ee.ImageCollection('COPERNICUS/S2_HARMONIZED') .filterBounds(ee.Geometry.Point([-12.29, 168.83])) .aside(display) .filterDate('2011-01-01', '2016-12-31') .first() )
请注意,aside(print)
(JavaScript 代码编辑器)和 aside(display)
(Python geemap)会调用客户端函数,但仍会在映射的函数中失败。您还可以将 aside 与用户定义的函数搭配使用。例如:
var composite = ee.ImageCollection('LANDSAT/LC08/C02/T1_TOA') .filterBounds(ee.Geometry.Point([106.91, 47.91])) .map(function(image) { return image.addBands(image.normalizedDifference(['B5', 'B4'])); }) .aside(Map.addLayer, {bands: ['B4', 'B3', 'B2'], max: 0.3}, 'collection') .qualityMosaic('nd'); Map.setCenter(106.91, 47.91, 11); Map.addLayer(composite, {bands: ['B4', 'B3', 'B2'], max: 0.3}, 'composite');
import ee import geemap.core as geemap
m = geemap.Map() m.set_center(106.91, 47.91, 11) composite = ( ee.ImageCollection('LANDSAT/LC08/C02/T1_TOA') .filterBounds(ee.Geometry.Point([106.91, 47.91])) .map(lambda image: image.addBands(image.normalizedDifference(['B5', 'B4']))) .aside(m.add_layer, {'bands': ['B4', 'B3', 'B2'], 'max': 0.3}, 'collection') .qualityMosaic('nd') ) m.add_layer(composite, {'bands': ['B4', 'B3', 'B2'], 'max': 0.3}, 'composite') m
在 first()
上运行函数
在可用的情况下,输出和可视化功能对调试很有用,但在调试映射到集合的函数时,您无法在函数中输出,如“映射的函数”部分中所述。在这种情况下,最好隔离集合中存在问题的元素,并在单个元素上测试映射的函数。在测试函数时,如果不对其进行映射,您可以使用 print 语句来了解问题。请参考以下示例。
错误 - 此代码无效!
var image = ee.Image( 'COPERNICUS/S2_HARMONIZED/20150821T111616_20160314T094808_T30UWU'); var someFeatures = ee.FeatureCollection([ ee.Feature(ee.Geometry.Point([-2.02, 48.43])), ee.Feature(ee.Geometry.Point([-2.80, 48.37])), ee.Feature(ee.Geometry.Point([-1.22, 48.29])), ee.Feature(ee.Geometry.Point([-1.73, 48.65])), ]); var problem = someFeatures.map(function(feature) { var dictionary = image.reduceRegion({ reducer: 'first', geometry: feature.geometry(), scale: 10, }); return feature.set({ result: ee.Number(dictionary.get('B5')) .divide(dictionary.get('B4')) }); }); // Error in map(ID=2): // Number.divide: Parameter 'left' is required. print(problem);
import ee import geemap.core as geemap
image = ee.Image( 'COPERNICUS/S2_HARMONIZED/20150821T111616_20160314T094808_T30UWU' ) some_features = ee.FeatureCollection([ ee.Feature(ee.Geometry.Point([-2.02, 48.43])), ee.Feature(ee.Geometry.Point([-2.80, 48.37])), ee.Feature(ee.Geometry.Point([-1.22, 48.29])), ee.Feature(ee.Geometry.Point([-1.73, 48.65])), ]) # Define a function to be mapped over the collection. def function_to_map(feature): dictionary = image.reduceRegion( reducer=ee.Reducer.first(), geometry=feature.geometry(), scale=10 ) return feature.set( {'result': ee.Number(dictionary.get('B5')).divide(dictionary.get('B4'))} ) problem = some_features.map(function_to_map) # EEException: Error in map(ID=2): # Number.divide: Parameter 'left' is required. print(problem.getInfo())
如需对此进行调试,请检查错误。幸运的是,这个有用的错误消息会告知您 ID=2
的功能存在问题。如需进一步调查,不妨对代码进行一些重构。具体而言,当函数映射到集合时,您无法在函数中使用 print 语句,如本部分所述。调试目标是隔离有问题的功能,并运行包含一些输出语句的函数。使用之前使用的同一张图片和地图项:
// Define a function to be mapped over the collection. var functionToMap = function(feature) { var dictionary = image.reduceRegion({ reducer: 'first', geometry: feature.geometry(), scale: 10, }); // Debug: print(dictionary); return feature.set({ result: ee.Number(dictionary.get('B5')) .divide(dictionary.get('B4')) }); }; // Isolate the feature that's creating problems. var badFeature = ee.Feature(someFeatures .filter(ee.Filter.eq('system:index', '2')) .first()); // Test the function with print statements added. functionToMap(badFeature); // Inspect the bad feature in relation to the image. Map.centerObject(badFeature, 11); Map.addLayer(badFeature, {}, 'bad feature'); Map.addLayer(image, {bands: ['B4', 'B3', 'B2'], max: 3000}, 'image');
import ee import geemap.core as geemap
# Define a function to be mapped over the collection. def function_to_map(feature): dictionary = image.reduceRegion( reducer=ee.Reducer.first(), geometry=feature.geometry(), scale=10 ) # Debug: display(dictionary) return feature.set( {'result': ee.Number(dictionary.get('B5')).divide(dictionary.get('B4'))} ) # Isolate the feature that's creating problems. bad_feature = ee.Feature( some_features.filter(ee.Filter.eq('system:index', '2')).first() ) # Test the function with print statements added. function_to_map(bad_feature) # Inspect the bad feature in relation to the image. m = geemap.Map() m.center_object(bad_feature, 11) m.add_layer(bad_feature, {}, 'bad feature') m.add_layer(image, {'bands': ['B4', 'B3', 'B2'], 'max': 3000}, 'image') m
现在,由于该函数仅针对一个地图项运行,因此您可以在其中添加 print(对于 Python geemap 为“display”)调用。检查输出的对象,发现(啊哈!)reduceRegion()
返回的对象的每个频段都为 null。这说明了除法失败的原因:因为您无法将 null 除以 null。为什么它最初为 null?
如需进行调查,请将输入图片和错误地图项添加到地图中,并将地图中心定位到错误地图项。这样一来,您会发现问题是由于点超出了图片的边界。根据这一发现,调试后的代码如下所示:
var functionToMap = function(feature) { var dictionary = image.reduceRegion({ reducer: 'first', geometry: feature.geometry(), scale: 10, }); return feature.set({ result: ee.Number(dictionary.get('B5')) .divide(dictionary.get('B4')) }); }; var noProblem = someFeatures .filterBounds(image.geometry()) .map(functionToMap); print(noProblem);
import ee import geemap.core as geemap
def function_to_map(feature): dictionary = image.reduceRegion( reducer=ee.Reducer.first(), geometry=feature.geometry(), scale=10 ) return feature.set( {'result': ee.Number(dictionary.get('B5')).divide(dictionary.get('B4'))} ) no_problem = some_features.filterBounds(image.geometry()).map(function_to_map) display(no_problem)
Profiler
性能分析器可提供有关启用该分析器时执行的计算所产生的 EECU 时间和内存用量(按算法和素材资源)的信息。查看性能分析器顶部的条目,了解资源消耗量最大的操作。对于运行时间较长或效率较低的脚本,性能分析器顶部的条目会提供相关线索,帮助您确定应在哪些方面着力优化脚本。重要提示:性能分析器本身会影响脚本的性能,因此您应仅在必要时运行它。