Launching CQuest.Earth (Part 2)

After launching CQuest.Earth last week, we are now open-sourcing our first app: the MODIS Vegetation Index Time-Series App. As explained in our blogpost from last week, this App enables you to compare the vegetation indices of different places over time. This is only the first set of functionalities CQuest.Earth will offer. There are many more to come, so make sure to stay tuned. 

If you follow these instructions you will be able to recreate the current (December 2020) version, or you can go to CQuest.Earth, sign up, and check out the most recent version and use many useful features for free.

The code is provided in 5 separate javascript files on this gitlab repo. You can use this code in the Google Earth Engine (GEE) Code Editor to recreate the app. Let’s have a look under the hood (disclaimer: for the sake of brevity we have omitted some cleaning/maintenance code here):

The core functionality of the app resides in modis_vi_ts_chart_app.js. The other four scripts serve as supplementary reference files: collection_config.js contains configuration details of all the relevant MODIS vegetation image collections on GEE, geometries.js deals with the interactive drawing of geometries on the map, legend.js provides the configuration for the map legend, and styles.js holds the styling details for the user interface, i.e. control and chart panels.

Let’s walk through the main flow of the app:

First we import functions and configurations from the supporting scripts.

var collections = require("modis-vi-ts-chart:collection_config");
var geometries = require("modis-vi-ts-chart:geometries");
var legend = require("modis-vi-ts-chart:legend");
var style = require("modis-vi-ts-chart:styles");

Next we initialize the three main components of the app:

  1. the map panel where users can view the vegetation index layers and place/draw their areas of interest (AOIs).
var map_panel = ui.Map({"lat":15, "lon":0, "zoom":2});
var drawingTools = map_panel.drawingTools();
map_panel.addLayer(data_layer.visual, data_layer.vis_params, band);
  1. The control panel where users can select the vegetation index layer of  interest
var control_panel = ui.Panel({style: {width: "290px"}});
control_panel.add(ui.Label({value: 'MODIS Vegetation Time-Series Charts', style: style.control.title}));
  1. The chart panel where the spatio-temporal comparison of the vegetation index time-series will be shown
var chart_panel = ui.Panel({style: {width: "500px"}});
chart_panel.add(ui.Label({value: 'Follow the instructions on the left to create your time-series chart', style: style.control.title}));
var chart_options = style.chart.options[band];
var chart_panel_title = "MODIS NDVI Time-Series";

Not so spectacular yet- but now it gets interesting with three interactive widgets in the control panel. The first lets a user select the vegetation index layer of their choice. When a user changes the selection, the active layer displayed on the map and the one to be queried for the time-series chart changes.

var options = ["NDVI","EVI","GPP","NPP","LAI"];
var layer_selector = ui.Select({
items: options,
placeholder: "Select Layer",
onChange: function(value){
  data_layer = collections.modis[value];
  band =;
  chart_options = style.chart.options[band];
  chart_panel_title = "MODIS " + value + " Time-Series";
  if (value != map_panel.layers().get(0).getName()){
    map_panel.addLayer(data_layer.visual, data_layer.vis_params, value);
    control_panel.widgets().set(10, legend.title("MODIS " + value + " 2019"));
    control_panel.widgets().set(12, legend.labels(data_layer.min, data_layer.max));

The second are actually two instances of the same widget – geometry select widgets that let the user select a geometry type (point or polygon) to draw on the map and thus indicate the AOIs to query for the vegetation index time-series.

var select_and_draw_single_geometry = function(
drawingTools, name, color
){return ui.Select(
  { items: options,
    placeholder: 'Select Geometry Type',
    onChange: function(value){
        var geom_layer = ui.Map.GeometryLayer({geometries: null, name: name, color: color});
        drawingTools.onDraw(function(){drawingTools = drawingTools.stop()});

Last but not least, a button widget is presented to evaluate the time-series for the two chosen AOIs and display the chart comparing them over the last twenty years. The chart can be expanded and downloaded as raw data (csv), or image (png/svg). Don’t be afraid by the length of the code for this button, the functionality is quite straightforward

First the user drawn AOIs are extracted as GEE geometry objects

var aoi1 = drawingTools.layers().get(0).geometries().get(0);
var aoi2 = drawingTools.layers().get(1).geometries().get(0);

Secondly, the image time-series is stacked as a single image, with each image band representing one time step in the time-series

var timestamps = data_layer.collection.aggregate_array("system:time_start");
var date_strings =
{return ee.Date(timestamp).format("YYYY-MM-dd")});
var stacked_image =;

Then the time-series for both AOIs are extracted to dictionaries.

var ts_aoi1 = ee.Image(stacked_image).reduceRegion(
{geometry: aoi1, reducer: ee.Reducer.mean(), bestEffort: true});
var ts_aoi2 = ee.Image(stacked_image).reduceRegion(
{geometry: aoi2, reducer: ee.Reducer.mean(), bestEffort: true});

Finally, the time-series are concatenated to an GEE array and the chart is created and displayed in the chart panel.

var y_values =[ts_aoi1.values(), ts_aoi2.values()], 1);
var chart = ui.Chart.array.values(y_values, 0, ee.Array(timestamps))
.setSeriesNames(["AOI1", "AOI2"])

We hope you find this info and code useful for your own projects concerning time-series exploration on the GEE. Please, feel free to use this code and play around with it as much as you like. If you have any questions or feedback, please reach out via or write a gitlab issue to the repo.

%d bloggers like this: