Google Earth Engine – Random Sampling of Points Surrounded by Similar Pixels

google-earth-enginegoogle-earth-engine-python-apirastersamplepoints

I have a single-band raster (US landcover) where each pixel is assigned to one of a few classes. I want to get a random sample of points (lat-long) throughout an area such that each of those points is located within large neighborhoods of pixels of the same landcover class. Currently, I use EE's "randomPoints" to get a random sample of n points but many of those pixels may be isolated pixels belonging to one landcover class surrounded by pixels of other classes. What I need is a set of randomly selected points each surrounded by a neighborhood of radius r of pixels of the same class (as determined by the raster). Any ideas on how to approach this problem?

To clarify, I want points like the one on the right of the figure below (large yellow neighborhood) and not the point on the left.
enter image description here
Here is my code for the landcover raster within California:

import ee
ee.Initialize()
california = ee.FeatureCollection("TIGER/2018/States").filter(ee.Filter.eq("NAME", 'California')) 
landcover = ee.ImageCollection('USGS/NLCD_RELEASES/2016_REL')\
            .filter(ee.Filter.eq('system:index', '2004')).first().select('landcover')
landcover_ca = landcover.clip(california)
point_sample = ee.FeatureCollection.randomPoints(california, points=1000, seed=10)

Best Answer

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