10. Building Interactive Web Apps#

10.1. Introduction#

10.2. Technical requirements#

conda install -n base mamba -c conda-forge
mamba create -n gee -c conda-forge geemap pygis
conda activate gee
jupyter lab
# %pip install pygis
import ee
import geemap
geemap.ee_initialize()

10.3. Building an Earth Engine App using JavaScript#

// Get an NLCD image by year.
var getNLCD = function (year) {
  // Import the NLCD collection.
  var dataset = ee.ImageCollection("USGS/NLCD_RELEASES/2019_REL/NLCD");

  // Filter the collection by year.
  var nlcd = dataset.filter(ee.Filter.eq("system:index", year)).first();

  // Select the land cover band.
  var landcover = nlcd.select("landcover");
  return ui.Map.Layer(landcover, {}, year);
};
// Create a dictionary with each year as the key
// and its corresponding NLCD image layer as the value.
var images = {
  2001: getNLCD("2001"),
  2004: getNLCD("2004"),
  2006: getNLCD("2006"),
  2008: getNLCD("2008"),
  2011: getNLCD("2011"),
  2013: getNLCD("2013"),
  2016: getNLCD("2016"),
  2019: getNLCD("2019"),
};
// Create the left map, and have it display the first layer.
var leftMap = ui.Map();
leftMap.setControlVisibility(false);
var leftSelector = addLayerSelector(leftMap, 0, "top-left");

// Create the right map, and have it display the last layer.
var rightMap = ui.Map();
rightMap.setControlVisibility(true);
var rightSelector = addLayerSelector(rightMap, 7, "top-right");

// Adds a layer selection widget to the given map, to allow users to
// change which image is displayed in the associated map.
function addLayerSelector(mapToChange, defaultValue, position) {
  var label = ui.Label("Select a year:");

  // This function changes the given map to show the selected image.
  function updateMap(selection) {
    // mapToChange.layers().set(0, ui.Map.Layer(images[selection]));
    mapToChange.layers().set(0, images[selection]);
  }

  // Configure a selection dropdown to allow the user to choose
  // between images, and set the map to update when a user
  // makes a selection.
  var select = ui.Select({ items: Object.keys(images), onChange: updateMap });
  select.setValue(Object.keys(images)[defaultValue], true);

  var controlPanel = ui.Panel({
    widgets: [label, select],
    style: { position: position },
  });

  mapToChange.add(controlPanel);
}
// Set the legend title.
var title = "NLCD Land Cover Classification";

// Set the legend position.
var position = "bottom-right";

// Define a dictionary that will be used to make a legend
var dict = {
  names: [
    "11 Open Water",
    "12 Perennial Ice/Snow",
    "21 Developed, Open Space",
    "22 Developed, Low Intensity",
    "23 Developed, Medium Intensity",
    "24 Developed, High Intensity",
    "31 Barren Land (Rock/Sand/Clay)",
    "41 Deciduous Forest",
    "42 Evergreen Forest",
    "43 Mixed Forest",
    "51 Dwarf Scrub",
    "52 Shrub/Scrub",
    "71 Grassland/Herbaceous",
    "72 Sedge/Herbaceous",
    "73 Lichens",
    "74 Moss",
    "81 Pasture/Hay",
    "82 Cultivated Crops",
    "90 Woody Wetlands",
    "95 Emergent Herbaceous Wetlands",
  ],

  colors: [
    "#466b9f",
    "#d1def8",
    "#dec5c5",
    "#d99282",
    "#eb0000",
    "#ab0000",
    "#b3ac9f",
    "#68ab5f",
    "#1c5f2c",
    "#b5c58f",
    "#af963c",
    "#ccb879",
    "#dfdfc2",
    "#d1d182",
    "#a3cc51",
    "#82ba9e",
    "#dcd939",
    "#ab6c28",
    "#b8d9eb",
    "#6c9fb8",
  ],
};
// Create a panel to hold the legend widget.
var legend = ui.Panel({
  style: {
    position: position,
    padding: "8px 15px",
  },
});

// Function to generate the legend.
function addCategoricalLegend(panel, dict, title) {
  // Create and add the legend title.
  var legendTitle = ui.Label({
    value: title,
    style: {
      fontWeight: "bold",
      fontSize: "18px",
      margin: "0 0 4px 0",
      padding: "0",
    },
  });
  panel.add(legendTitle);

  var loading = ui.Label("Loading legend...", { margin: "2px 0 4px 0" });
  panel.add(loading);

  // Creates and styles 1 row of the legend.
  var makeRow = function (color, name) {
    // Create the label that is actually the colored box.
    var colorBox = ui.Label({
      style: {
        backgroundColor: color,
        // Use padding to give the box height and width.
        padding: "8px",
        margin: "0 0 4px 0",
      },
    });

    // Create the label filled with the description text.
    var description = ui.Label({
      value: name,
      style: { margin: "0 0 4px 6px" },
    });

    return ui.Panel({
      widgets: [colorBox, description],
      layout: ui.Panel.Layout.Flow("horizontal"),
    });
  };

  // Get the list of palette colors and class names from the image.
  var palette = dict.colors;
  var names = dict.names;
  loading.style().set("shown", false);

  for (var i = 0; i < names.length; i++) {
    panel.add(makeRow(palette[i], names[i]));
  }

  rightMap.add(panel);
}

addCategoricalLegend(legend, dict, title);
// Create a SplitPanel to hold the adjacent, linked maps.
var splitPanel = ui.SplitPanel({
  firstPanel: leftMap,
  secondPanel: rightMap,
  wipe: true,
  style: { stretch: "both" },
});

// Set the SplitPanel as the only thing in the UI root.
ui.root.widgets().reset([splitPanel]);
var linker = ui.Map.Linker([leftMap, rightMap]);
leftMap.setCenter(-100, 40, 4);

10.4. Publishing an Earth Engine App from the Code Editor#

10.5. Developing an Earth Engine App using geemap#

conda create -n gee python
conda activate gee
conda install mamba
mamba install -c conda-forge geospatial
conda activate gee
jupyter notebook
import ee
import geemap
Map = geemap.Map(center=[40, -100], zoom=4)
Map
# Import the NLCD collection
dataset = ee.ImageCollection('USGS/NLCD_RELEASES/2019_REL/NLCD')

# Filter the collection to the 2019 product
nlcd2019 = dataset.filter(ee.Filter.eq('system:index', '2019')).first()

# Select the land cover band
landcover = nlcd2019.select('landcover')

# Display land cover on the map
Map.addLayer(landcover, {}, 'NLCD 2019')
Map
title = 'NLCD Land Cover Classification'
Map.add_legend(title=title, builtin_legend='NLCD')
legend_dict = {
    '11 Open Water': '466b9f',
    '12 Perennial Ice/Snow': 'd1def8',
    '21 Developed, Open Space': 'dec5c5',
    '22 Developed, Low Intensity': 'd99282',
    '23 Developed, Medium Intensity': 'eb0000',
    '24 Developed High Intensity': 'ab0000',
    '31 Barren Land (Rock/Sand/Clay)': 'b3ac9f',
    '41 Deciduous Forest': '68ab5f',
    '42 Evergreen Forest': '1c5f2c',
    '43 Mixed Forest': 'b5c58f',
    '51 Dwarf Scrub': 'af963c',
    '52 Shrub/Scrub': 'ccb879',
    '71 Grassland/Herbaceous': 'dfdfc2',
    '72 Sedge/Herbaceous': 'd1d182',
    '73 Lichens': 'a3cc51',
    '74 Moss': '82ba9e',
    '81 Pasture/Hay': 'dcd939',
    '82 Cultivated Crops': 'ab6c28',
    '90 Woody Wetlands': 'b8d9eb',
    '95 Emergent Herbaceous Wetlands': '6c9fb8',
}
title = 'NLCD Land Cover Classification'
Map.add_legend(title=title, legend_dict=legend_dict)
dataset.aggregate_array("system:id").getInfo()
years = ['2001', '2004', '2006', '2008', '2011', '2013', '2016', '2019']
# Get an NLCD image by year
def getNLCD(year): # Import the NLCD collection.
dataset = ee.ImageCollection('USGS/NLCD_RELEASES/2019_REL/NLCD')

    # Filter the collection by year.
    nlcd = dataset.filter(ee.Filter.eq('system:index', year)).first()

    # Select the land cover band.
    landcover = nlcd.select('landcover')
    return landcover
# Create an NLCD image collection for the selected years
collection = ee.ImageCollection(ee.List(years).map(lambda year: getNLCD(year)))
collection.aggregate_array('system:id').getInfo()
labels = [f'NLCD {year}' for year in years]
labels
Map.ts_inspector(
    left_ts=collection, right_ts=collection, left_names=labels, right_names=labels
)
Map

10.6. Publishing an Earth Engine App using a local web server#

cd /path/to/ngrok/dir
conda activate gee
voila --no-browser nlcd_app.ipynb
cd /path/to/ngrok/dir
ngrok http 8866
voila --no-browser --strip_sources=False nlcd_app.ipynb
ngrok http -auth="username:password" 8866

10.7. Publishing an Earth Engine App using cloud platforms#

web: voila --port=$PORT --no-browser --strip_sources=True --enable_nbextensions=True --MappingKernelManager.cull_interval=60 --MappingKernelManager.cull_idle_timeout=120 notebooks/

10.8. Building an Earth Engine App Using Streamlit#

conda activate gee
mamba install -c conda-forge streamlit
streamlit hello
git config --global user.name "Firstname Lastname"
git config --global user.email user@example.com
git clone https://github.com/USERNAME/geemap-apps.git
import ee
import streamlit as st
import geemap.foliumap as geemap
# Get an NLCD image by year.
def getNLCD(year):
    # Import the NLCD collection.
    dataset = ee.ImageCollection("USGS/NLCD_RELEASES/2019_REL/NLCD")

    # Filter the collection by year.
    nlcd = dataset.filter(ee.Filter.eq("system:index", year)).first()

    # Select the land cover band.
    landcover = nlcd.select("landcover")
    return landcover
# The main app.
def app():

    st.header("National Land Cover Database (NLCD)")

    # Create a layout containing two columns
    row1_col1, row1_col2 = st.columns([3, 1])

    # Create an interactive map
    Map = geemap.Map()

    # Select the eight NLCD epochs after 2000.
    years = ["2001", "2004", "2006", "2008", "2011", "2013", "2016", "2019"]

    # Add a dropdown list and checkbox to the second column.
    with row1_col2:
        selected_year = st.multiselect("Select a year", years)
        add_legend = st.checkbox("Show legend")

    # Add selected NLCD image to the map based on the selected year.
    if selected_year:
        for year in selected_year:
            Map.addLayer(getNLCD(year), {}, "NLCD " + year)

        if add_legend:
            Map.add_legend(title="NLCD Land Cover", builtin_legend="NLCD")
        with row1_col1:
            Map.to_streamlit(height=600)

    else:
        with row1_col1:
            Map.to_streamlit(height=600)
from apps import home, basemaps, customize, datasets, opacity, nlcd
apps.add_app("NLCD", nlcd.app)
conda activate gee
streamlit run app.py

10.9. Conclusions#