Inner Joins

To enumerate all matches between the elements of two collections, use an ee.Join.inner(). The output of an inner join is a FeatureCollection (even if joining one ImageCollection to another ImageCollection). Each feature in the output represents a match, where the matching elements are stored in two properties of the feature. For example, feature.get('primary') is the element in the primary collection that matches the element from the secondary collection stored in feature.get('secondary'). (Different names for these properties can be specified as arguments to inner(), but ‘primary’ and ‘secondary’ are the defaults). One-to-many relationships are represented by multiple features in the output. If an element in either collection doesn’t have a match, it is not present in the output.

Join examples using ImageCollection inputs apply without modification to FeatureCollection inputs. It is also possible to join a FeatureCollection to an ImageCollection and vice versa. Consider the following toy example of inner join:

Code Editor (JavaScript)

// Create the primary collection.
var primaryFeatures = ee.FeatureCollection([
  ee.Feature(null, {foo: 0, label: 'a'}),
  ee.Feature(null, {foo: 1, label: 'b'}),
  ee.Feature(null, {foo: 1, label: 'c'}),
  ee.Feature(null, {foo: 2, label: 'd'}),
]);

// Create the secondary collection.
var secondaryFeatures = ee.FeatureCollection([
  ee.Feature(null, {bar: 1, label: 'e'}),
  ee.Feature(null, {bar: 1, label: 'f'}),
  ee.Feature(null, {bar: 2, label: 'g'}),
  ee.Feature(null, {bar: 3, label: 'h'}),
]);

// Use an equals filter to specify how the collections match.
var toyFilter = ee.Filter.equals({
  leftField: 'foo',
  rightField: 'bar'
});

// Define the join.
var innerJoin = ee.Join.inner('primary', 'secondary');

// Apply the join.
var toyJoin = innerJoin.apply(primaryFeatures, secondaryFeatures, toyFilter);

// Print the result.
print('Inner join toy example:', toyJoin);

In the previous example, notice that the relationship between the tables is defined in the filter, which indicates that fields ‘foo’ and ‘bar’ are the join fields. An inner join is then specified and applied to the collections. Inspect the output and observe that each possible match is represented as one Feature.

For a motivated example, consider joining MODIS ImageCollection objects. MODIS quality data are sometimes stored in a separate collection from the image data, so an inner join is convenient for joining the two collections in order to apply the quality data. In this case, the image acquisition times are identical, so an equals filter handles the job of specifying this relationship between the two collections:

Code Editor (JavaScript)

// Make a date filter to get images in this date range.
var dateFilter = ee.Filter.date('2014-01-01', '2014-02-01');

// Load a MODIS collection with EVI data.
var mcd43a4 = ee.ImageCollection('MODIS/MCD43A4_006_EVI')
    .filter(dateFilter);

// Load a MODIS collection with quality data.
var mcd43a2 = ee.ImageCollection('MODIS/006/MCD43A2')
    .filter(dateFilter);

// Define an inner join.
var innerJoin = ee.Join.inner();

// Specify an equals filter for image timestamps.
var filterTimeEq = ee.Filter.equals({
  leftField: 'system:time_start',
  rightField: 'system:time_start'
});

// Apply the join.
var innerJoinedMODIS = innerJoin.apply(mcd43a4, mcd43a2, filterTimeEq);

// Display the join result: a FeatureCollection.
print('Inner join output:', innerJoinedMODIS);

To make use of the joined images in the output FeatureCollection, map() a combining function over the output. For example, the matching images can be stacked together such that the quality bands are added to the image data:

Code Editor (JavaScript)

// Map a function to merge the results in the output FeatureCollection.
var joinedMODIS = innerJoinedMODIS.map(function(feature) {
  return ee.Image.cat(feature.get('primary'), feature.get('secondary'));
});

// Print the result of merging.
print('Inner join, merged bands:', joinedMODIS);

Although this function is mapped over a FeatureCollection, the result is an ImageCollection. Each image in the resultant ImageCollection has all the bands of the images in the primary collection (in this example just ‘EVI’) and all the bands of the matching image in the secondary collection (the quality bands).