sample() uses 1 region (either points or polygons) and does exhaustive sampling in that region (all pixels) unless you specify a smaller number of points. But the random sampling it does isn't optimal.
sampleRegions uses multiple regions (either points or polygons) and does exhaustive sampling in each region (all pixels). There are no options for randomness.
Unless you're just doing simple exhaustive sampling, you probably always want to use stratifiedSample as it has the most options and allows the most flexibility as you can always render your features into a "class band" even if it's just a sparse raster with a single value (using e.g.: ee.Image().paint(collection, 0))
I suppose you first could mask your landcover image, to remove "isolated" pixels, then sample that masked image. Here's an approach that masks based on ratio of pixels of the same class within a given radius:
var RADIUS = 300 // In meters
var MIN_RATIO = 0.5
var california = ee.FeatureCollection("TIGER/2018/States").filter(ee.Filter.eq("NAME", 'California'))
var landcover = ee.ImageCollection('USGS/NLCD_RELEASES/2016_REL')
.filter(ee.Filter.eq('system:index', '2004'))
.first()
.select('landcover')
var landcover_ca = landcover.clip(california)
// Hard code this if you have a an image without a class values property
var classValues = ee.List(landcover_ca.get('landcover_class_values'))
// Create a masked image for each class value, then combine into a single image
var maskedLandcover = ee.ImageCollection(
classValues.map(onlyWithLargeNeighborhood)
).mosaic()
var point_sample = maskedLandcover.sample({
region: california,
scale: 30,
numPixels: 1000,
seed: 10,
geometries: true
})
Map.addLayer(maskedLandcover.randomVisualizer(), null, 'landcover masked')
Map.addLayer(landcover_ca.randomVisualizer(), null, 'landcover', false)
Map.addLayer(point_sample, null, 'samples')
function onlyWithLargeNeighborhood(classValue) {
var classValueImage = landcover_ca.updateMask(
landcover_ca.eq(ee.Number(classValue))
)
var counts = classValueImage.reduceNeighborhood({
reducer: ee.Reducer.count().combine(ee.Reducer.countEvery(), 'total_', false),
kernel: ee.Kernel.circle({radius: RADIUS, units: 'meters'})
})
var ratio = counts.select('landcover_count').divide(counts.select('landcover_total_count'))
return classValueImage.updateMask(
ratio.gte(MIN_RATIO)
)
}
https://code.earthengine.google.com/3dcf1c81c18b4b979fb4e160d8c67383
UPDATE
If you are getting "Computation timed out" errors, you can try to split your sampling into smaller batches of points:
var BATCH_SIZE = 1000
var NUM_PIXELS = 10000
var point_sample = ee.FeatureCollection(
ee.List.sequence(1, NUM_PIXELS / BATCH_SIZE)
.map(function (batch) { // Samples batch of points
return maskedLandcover
.sample({
region: california,
scale: 30,
seed: batch,
numPixels: 2 * BATCH_SIZE, // Sample some extra to make sure enough points fall on non-masked pixels
geometries: true
})
.limit(BATCH_SIZE)
})
.iterate(function (acc, batchCollection) { // Merge batches into a single collection
return ee.FeatureCollection(acc).merge(
ee.FeatureCollection(batchCollection)
)
}, ee.FeatureCollection([]))
)
https://code.earthengine.google.com/0d602e28fd5f5b6d2e85ac358f382d5b
Best Answer
This is a Client vs. Server issue. You cannot use regular Python operators like
and
to act on Earth Engine images; you need to use the methods on the image type.Additionally, in the particular case of
and
, because it's a Python keyword (making it not allowed as a method name) you have to capitalize it. (This is a special case for the namesand
,or
, andnot
only.)