10. Building Interactive Web Apps#

10.1. Introduction#

10.2. Technical requirements#

conda create -n gee python
conda activate gee
conda install -c conda-forge mamba
mamba install -c conda-forge pygis
pip install "geemap[apps]"
jupyter lab
# %pip install pygis
import ee
import geemap
geemap.ee_initialize()

10.3. Building JavaScript web apps#

var getNLCD = function (year) {
  var dataset = ee.ImageCollection("USGS/NLCD_RELEASES/2019_REL/NLCD");
  var nlcd = dataset.filter(ee.Filter.eq("system:index", year)).first();
  var landcover = nlcd.select("landcover");
  return ui.Map.Layer(landcover, {}, year);
};
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"),
};
var leftMap = ui.Map();
leftMap.setControlVisibility(false);
var leftSelector = addLayerSelector(leftMap, 0, "top-left");

var rightMap = ui.Map();
rightMap.setControlVisibility(true);
var rightSelector = addLayerSelector(rightMap, 7, "top-right");

function addLayerSelector(mapToChange, defaultValue, position) {
  var label = ui.Label("Select a year:");

  function updateMap(selection) {
    mapToChange.layers().set(0, images[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);
}
var title = "NLCD Land Cover Classification";
var position = "bottom-right";
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",
  ],
};
var legend = ui.Panel({
  style: {
    position: position,
    padding: "8px 15px",
  },
});

function addCategoricalLegend(panel, dict, 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);

  var makeRow = function (color, name) {
    var colorBox = ui.Label({
      style: {
        backgroundColor: color,
        padding: "8px",
        margin: "0 0 4px 0",
      },
    });
    var description = ui.Label({
      value: name,
      style: { margin: "0 0 4px 6px" },
    });

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

  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);
var splitPanel = ui.SplitPanel({
  firstPanel: leftMap,
  secondPanel: rightMap,
  wipe: true,
  style: { stretch: "both" },
});

ui.root.widgets().reset([splitPanel]);
var linker = ui.Map.Linker([leftMap, rightMap]);
leftMap.setCenter(-100, 40, 4);

10.4. Publishing JavaScript web apps#

10.5. Building Python Web Apps#

import ee
import geemap
Map = geemap.Map(center=[40, -100], zoom=4)
dataset = ee.ImageCollection('USGS/NLCD_RELEASES/2019_REL/NLCD')
nlcd2019 = dataset.filter(ee.Filter.eq('system:index', '2019')).first()
landcover = nlcd2019.select('landcover')
Map.addLayer(landcover, {}, 'NLCD 2019')
Map
title = 'NLCD Land Cover Classification'
Map.add_legend(title=title, builtin_legend='NLCD')
dataset.aggregate_array("system:id")
years = ['2001', '2004', '2006', '2008', '2011', '2013', '2016', '2019']
def getNLCD(year):
    dataset = ee.ImageCollection('USGS/NLCD_RELEASES/2019_REL/NLCD')
    nlcd = dataset.filter(ee.Filter.eq('system:index', year)).first()
    landcover = nlcd.select('landcover')
    return landcover
collection = ee.ImageCollection(ee.List(years).map(lambda year: getNLCD(year)))
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. Using Voila to deploy web apps#

cd /path/to/ngrok/dir
ngrok config add-authtoken <token>
./ngrok config add-authtoken <token>
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. Building Streamlit web apps#

streamlit hello
pip install streamlit
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
def getNLCD(year):
    dataset = ee.ImageCollection("USGS/NLCD_RELEASES/2019_REL/NLCD")
    nlcd = dataset.filter(ee.Filter.eq("system:index", year)).first()
    landcover = nlcd.select("landcover")
    return landcover
st.header("National Land Cover Database (NLCD)")
row1_col1, row1_col2 = st.columns([3, 1])
Map = geemap.Map()
years = ["2001", "2004", "2006", "2008", "2011", "2013", "2016", "2019"]
with row1_col2:
    selected_year = st.multiselect("Select a year", years)
    add_legend = st.checkbox("Show legend")
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)
conda activate gee
streamlit run app.py

10.8. Building Solara web apps#

pip install solara
import ee
import geemap
import solara

class Map(geemap.Map):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.add_ee_data()

    def add_ee_data(self):
        years = ['2001', '2004', '2006', '2008', '2011', '2013', '2016', '2019']
        def getNLCD(year):
            dataset = ee.ImageCollection('USGS/NLCD_RELEASES/2019_REL/NLCD')
            nlcd = dataset.filter(ee.Filter.eq('system:index', year)).first()
            landcover = nlcd.select('landcover')
            return landcover

        collection = ee.ImageCollection(ee.List(years).map(lambda year: getNLCD(year)))
        labels = [f'NLCD {year}' for year in years]
        self.ts_inspector(
            left_ts=collection,
            right_ts=collection,
            left_names=labels,
            right_names=labels,
        )
        self.add_legend(
            title='NLCD Land Cover Type',
            builtin_legend='NLCD',
            height="460px",
            add_header=False
        )

@solara.component
def Page():
    with solara.Column(style={"min-width": "500px"}):
        Map.element(
            center=[40, -100],
            zoom=4,
            height="800px",
        )
conda activate gee
solara run ./pages

10.9. Deploying web apps on Hugging Face#

import geemap
geemap.get_ee_token()

10.10. Summary#