Spatial Joins

Collections can be joined by spatial location as well as by property values. To join based on spatial location, use a withinDistance() filter with .geo join fields specified. The .geo field indicates that the item's geometry is to be used to compute the distance metric. For example, consider the task of finding all power plants within 100 kilometers of Yosemite National Park, USA. For that purpose, use a filter on the geometry fields, with the maximum distance set to 100 kilometers using the distance parameter:

// Load a primary collection: protected areas (Yosemite National Park).
var primary = ee.FeatureCollection("WCMC/WDPA/current/polygons")
  .filter(ee.Filter.eq('NAME', 'Yosemite National Park'));

// Load a secondary collection: power plants.
var powerPlants = ee.FeatureCollection('WRI/GPPD/power_plants');

// Define a spatial filter, with distance 100 km.
var distFilter = ee.Filter.withinDistance({
  distance: 100000,
  leftField: '.geo',
  rightField: '.geo',
  maxError: 10
});

// Define a saveAll join.
var distSaveAll = ee.Join.saveAll({
  matchesKey: 'points',
  measureKey: 'distance'
});

// Apply the join.
var spatialJoined = distSaveAll.apply(primary, powerPlants, distFilter);

// Print the result.
print(spatialJoined);

Note that the previous example joins a FeatureCollection to another FeatureCollection. The saveAll() join sets a property (points) on each feature in the primary collection which stores a list of the points within 100 km of the feature. The distance of each point to the feature is stored in the distance property of each joined point.

Spatial joins can also be used to identify which features in one collection intersect those in another. For example, consider two feature collections: a primary collection containing polygons representing the boundaries of US states, a secondary collection containing point locations representing power plants. Suppose there is need to determine the number intersecting each state. This can be accomplished with a spatial join as follows:

// Load the primary collection: US state boundaries.
var states = ee.FeatureCollection('TIGER/2018/States');

// Load the secondary collection: power plants.
var powerPlants = ee.FeatureCollection('WRI/GPPD/power_plants');

// Define a spatial filter as geometries that intersect.
var spatialFilter = ee.Filter.intersects({
  leftField: '.geo',
  rightField: '.geo',
  maxError: 10
});

// Define a save all join.
var saveAllJoin = ee.Join.saveAll({
  matchesKey: 'power_plants',
});

// Apply the join.
var intersectJoined = saveAllJoin.apply(states, powerPlants, spatialFilter);

// Add power plant count per state as a property.
intersectJoined = intersectJoined.map(function(state) {
  // Get "power_plant" intersection list, count how many intersected this state.
  var nPowerPlants = ee.List(state.get('power_plants')).size();
  // Return the state feature with a new property: power plant count.
  return state.set('n_power_plants', nPowerPlants);
});

// Make a bar chart for the number of power plants per state.
var chart = ui.Chart.feature.byFeature(intersectJoined, 'NAME', 'n_power_plants')
  .setChartType('ColumnChart')
  .setSeriesNames({n_power_plants: 'Power plants'})
  .setOptions({
    title: 'Power plants per state',
    hAxis: {title: 'State'},
    vAxis: {title: 'Frequency'}});

// Print the chart to the console.
print(chart);

In the previous example, note that the intersects() filter doesn’t store a distance as the withinDistance() filter does. The output should look something like Figure 1.

CA WRS2 join
Figure 1. Bar chart showing the number of power plants intersecting each US state.