From 02c996dcc4976d3d206a5ae536c29372567f6edf Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Mon, 26 Aug 2024 16:04:16 -0400 Subject: [PATCH] Add notebook examples (#2) * Add notebook examples * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Improve notebook --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 33 ++++++++++++ notebooks/01_basic.ipynb | 72 +++++++++++++++++++++++++ notebooks/02_inspector.ipynb | 89 +++++++++++++++++++++++++++++++ notebooks/03_plotting.ipynb | 82 ++++++++++++++++++++++++++++ notebooks/04_split_map.ipynb | 100 +++++++++++++++++++++++++++++++++++ pages/00_home.py | 4 +- pages/01_basic.py | 30 +++++++++++ pages/02_inspector.py | 18 +++---- pages/03_plotting.py | 18 +++---- pages/04_split_map.py | 15 +++--- requirements.txt | 4 +- 11 files changed, 437 insertions(+), 28 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 notebooks/01_basic.ipynb create mode 100644 notebooks/02_inspector.ipynb create mode 100644 notebooks/03_plotting.ipynb create mode 100644 notebooks/04_split_map.ipynb create mode 100644 pages/01_basic.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..29de317 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,33 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + types: [python] + - id: trailing-whitespace + - id: requirements-txt-fixer + - id: check-added-large-files + args: ["--maxkb=500"] + + - repo: https://github.com/psf/black + rev: 24.8.0 + hooks: + - id: black-jupyter + language_version: python3 + + - repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + args: + [ + "--ignore-words-list=gis,timeseries,sav,slowy", + "--skip=*.json,*.csv", + ] + + - repo: https://github.com/kynan/nbstripout + rev: 0.7.1 + hooks: + - id: nbstripout diff --git a/notebooks/01_basic.ipynb b/notebooks/01_basic.ipynb new file mode 100644 index 0000000..acb4179 --- /dev/null +++ b/notebooks/01_basic.ipynb @@ -0,0 +1,72 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import geemap\n", + "import solara\n", + "\n", + "zoom = solara.reactive(4)\n", + "center = solara.reactive((40, -100))\n", + "bounds = solara.reactive(None)\n", + "\n", + "\n", + "@solara.component\n", + "def Page():\n", + " # Isolation is required to prevent the map from overlapping navigation (when screen width < 960px)\n", + " with solara.Column(\n", + " style={\"min-width\": \"500px\", \"height\": \"780px\", \"isolation\": \"isolate\"}\n", + " ):\n", + " # solara components support reactive variables\n", + " solara.SliderInt(label=\"Zoom level\", value=zoom, min=1, max=20)\n", + " # using 3rd party widget library require wiring up the events manually\n", + " # using zoom.value and zoom.set\n", + " geemap.Map.element( # type: ignore\n", + " zoom=zoom.value,\n", + " on_zoom=zoom.set,\n", + " center=center.value,\n", + " on_center=center.set,\n", + " on_bounds=bounds.set,\n", + " scroll_wheel_zoom=True,\n", + " height=\"600px\",\n", + " )\n", + " solara.Text(f\"Zoom: {zoom.value}\")\n", + " solara.Text(f\"Center: {center.value}\")\n", + " solara.Text(f\"Bounds: {bounds.value}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Page()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "geo", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/02_inspector.ipynb b/notebooks/02_inspector.ipynb new file mode 100644 index 0000000..8708912 --- /dev/null +++ b/notebooks/02_inspector.ipynb @@ -0,0 +1,89 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import ee\n", + "import geemap\n", + "\n", + "import solara\n", + "\n", + "\n", + "class Map(geemap.Map):\n", + " def __init__(self, **kwargs):\n", + " super().__init__(**kwargs)\n", + " self.add_ee_data()\n", + " self.add(\"layer_manager\")\n", + " self.add(\"inspector\")\n", + "\n", + " def add_ee_data(self):\n", + " # Add Earth Engine dataset\n", + " dem = ee.Image(\"USGS/SRTMGL1_003\")\n", + " landsat7 = ee.Image(\"LANDSAT/LE7_TOA_5YEAR/1999_2003\").select(\n", + " [\"B1\", \"B2\", \"B3\", \"B4\", \"B5\", \"B7\"]\n", + " )\n", + " states = ee.FeatureCollection(\"TIGER/2018/States\")\n", + "\n", + " # Set visualization parameters.\n", + " vis_params = {\n", + " \"min\": 0,\n", + " \"max\": 4000,\n", + " \"palette\": [\"006633\", \"E5FFCC\", \"662A00\", \"D8D8D8\", \"F5F5F5\"],\n", + " }\n", + "\n", + " # Add Earth Engine layers to Map\n", + " self.addLayer(\n", + " landsat7,\n", + " {\"bands\": [\"B4\", \"B3\", \"B2\"], \"min\": 20, \"max\": 200, \"gamma\": 2.0},\n", + " \"Landsat 7\",\n", + " True,\n", + " )\n", + " self.addLayer(dem, vis_params, \"SRTM DEM\", True, 1)\n", + " self.addLayer(states, {}, \"US States\")\n", + "\n", + "\n", + "@solara.component\n", + "def Page():\n", + " with solara.Column(style={\"min-width\": \"500px\"}):\n", + " Map.element(\n", + " center=[40, -100],\n", + " zoom=4,\n", + " height=\"600px\",\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Page()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "geo", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/03_plotting.ipynb b/notebooks/03_plotting.ipynb new file mode 100644 index 0000000..ad7df88 --- /dev/null +++ b/notebooks/03_plotting.ipynb @@ -0,0 +1,82 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import ee\n", + "import geemap\n", + "\n", + "import solara\n", + "\n", + "\n", + "class Map(geemap.Map):\n", + " def __init__(self, **kwargs):\n", + " super().__init__(**kwargs)\n", + " self.add_ee_data()\n", + " self.add_plot_gui()\n", + "\n", + " def add_ee_data(self):\n", + " landsat7 = ee.Image(\"LANDSAT/LE7_TOA_5YEAR/1999_2003\").select(\n", + " [\"B1\", \"B2\", \"B3\", \"B4\", \"B5\", \"B7\"]\n", + " )\n", + "\n", + " landsat_vis = {\"bands\": [\"B4\", \"B3\", \"B2\"], \"gamma\": 1.4}\n", + " self.addLayer(landsat7, landsat_vis, \"Landsat\")\n", + "\n", + " hyperion = ee.ImageCollection(\"EO1/HYPERION\").filter(\n", + " ee.Filter.date(\"2016-01-01\", \"2017-03-01\")\n", + " )\n", + "\n", + " hyperion_vis = {\n", + " \"min\": 1000.0,\n", + " \"max\": 14000.0,\n", + " \"gamma\": 2.5,\n", + " }\n", + " self.addLayer(hyperion, hyperion_vis, \"Hyperion\")\n", + "\n", + "\n", + "@solara.component\n", + "def Page():\n", + " with solara.Column(style={\"min-width\": \"500px\"}):\n", + " Map.element(\n", + " center=[40, -100],\n", + " zoom=4,\n", + " height=\"600px\",\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Page()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "geo", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/04_split_map.ipynb b/notebooks/04_split_map.ipynb new file mode 100644 index 0000000..f854da3 --- /dev/null +++ b/notebooks/04_split_map.ipynb @@ -0,0 +1,100 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import ee\n", + "import geemap\n", + "\n", + "import solara\n", + "\n", + "\n", + "class Map(geemap.Map):\n", + " def __init__(self, **kwargs):\n", + " super().__init__(**kwargs)\n", + " self.add_ee_data()\n", + "\n", + " def add_ee_data(self):\n", + " # Select the eight NLCD epochs after 2000.\n", + " years = [\"2001\", \"2004\", \"2006\", \"2008\", \"2011\", \"2013\", \"2016\", \"2019\"]\n", + "\n", + " # Get an NLCD image by year.\n", + " def getNLCD(year):\n", + " # Import the NLCD collection.\n", + " dataset = ee.ImageCollection(\"USGS/NLCD_RELEASES/2019_REL/NLCD\")\n", + "\n", + " # Filter the collection by year.\n", + " nlcd = dataset.filter(ee.Filter.eq(\"system:index\", year)).first()\n", + "\n", + " # Select the land cover band.\n", + " landcover = nlcd.select(\"landcover\")\n", + " return landcover\n", + "\n", + " ## Create an NLCD image collection for the selected years.\n", + " collection = ee.ImageCollection(ee.List(years).map(lambda year: getNLCD(year)))\n", + "\n", + " # Create a list of labels to populate the dropdown list.\n", + " labels = [f\"NLCD {year}\" for year in years]\n", + "\n", + " # Add a split-panel map for visualizing NLCD land cover change.\n", + " self.ts_inspector(\n", + " left_ts=collection,\n", + " right_ts=collection,\n", + " left_names=labels,\n", + " right_names=labels,\n", + " )\n", + "\n", + " # Add the NLCD legend to the map.\n", + " self.add_legend(\n", + " title=\"NLCD Land Cover Type\",\n", + " builtin_legend=\"NLCD\",\n", + " height=\"460px\",\n", + " add_header=False,\n", + " )\n", + "\n", + "\n", + "@solara.component\n", + "def Page():\n", + " with solara.Column(style={\"min-width\": \"500px\"}):\n", + " Map.element(\n", + " center=[40, -100],\n", + " zoom=4,\n", + " height=\"600px\",\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Page()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "geo", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/pages/00_home.py b/pages/00_home.py index 227eb74..ebd71b2 100644 --- a/pages/00_home.py +++ b/pages/00_home.py @@ -6,7 +6,7 @@ def Page(): with solara.Column(align="center"): markdown = """ ## Earth Engine Web Apps - + ### Introduction **A collection of Earth Engine web apps developed using [Solara](https://github.com/widgetti/solara) and geemap** @@ -15,7 +15,7 @@ def Page(): - GitHub: - Hugging Face: - + ### How to deploy this app on Hugging Face Spaces 1. Go to and duplicate the space to your own space. diff --git a/pages/01_basic.py b/pages/01_basic.py new file mode 100644 index 0000000..9cfa762 --- /dev/null +++ b/pages/01_basic.py @@ -0,0 +1,30 @@ +import geemap +import solara + +zoom = solara.reactive(4) +center = solara.reactive((40, -100)) +bounds = solara.reactive(None) + + +@solara.component +def Page(): + # Isolation is required to prevent the map from overlapping navigation (when screen width < 960px) + with solara.Column( + style={"min-width": "500px", "height": "780px", "isolation": "isolate"} + ): + # solara components support reactive variables + solara.SliderInt(label="Zoom level", value=zoom, min=1, max=20) + # using 3rd party widget library require wiring up the events manually + # using zoom.value and zoom.set + geemap.Map.element( # type: ignore + zoom=zoom.value, + on_zoom=zoom.set, + center=center.value, + on_center=center.set, + on_bounds=bounds.set, + scroll_wheel_zoom=True, + height="600px", + ) + solara.Text(f"Zoom: {zoom.value}") + solara.Text(f"Center: {center.value}") + solara.Text(f"Bounds: {bounds.value}") diff --git a/pages/02_inspector.py b/pages/02_inspector.py index ea4a51a..9a7b080 100644 --- a/pages/02_inspector.py +++ b/pages/02_inspector.py @@ -13,27 +13,27 @@ def __init__(self, **kwargs): def add_ee_data(self): # Add Earth Engine dataset - dem = ee.Image('USGS/SRTMGL1_003') - landsat7 = ee.Image('LANDSAT/LE7_TOA_5YEAR/1999_2003').select( - ['B1', 'B2', 'B3', 'B4', 'B5', 'B7'] + dem = ee.Image("USGS/SRTMGL1_003") + landsat7 = ee.Image("LANDSAT/LE7_TOA_5YEAR/1999_2003").select( + ["B1", "B2", "B3", "B4", "B5", "B7"] ) states = ee.FeatureCollection("TIGER/2018/States") # Set visualization parameters. vis_params = { - 'min': 0, - 'max': 4000, - 'palette': ['006633', 'E5FFCC', '662A00', 'D8D8D8', 'F5F5F5'], + "min": 0, + "max": 4000, + "palette": ["006633", "E5FFCC", "662A00", "D8D8D8", "F5F5F5"], } # Add Earth Engine layers to Map self.addLayer( landsat7, - {'bands': ['B4', 'B3', 'B2'], 'min': 20, 'max': 200, 'gamma': 2.0}, - 'Landsat 7', + {"bands": ["B4", "B3", "B2"], "min": 20, "max": 200, "gamma": 2.0}, + "Landsat 7", True, ) - self.addLayer(dem, vis_params, 'SRTM DEM', True, 1) + self.addLayer(dem, vis_params, "SRTM DEM", True, 1) self.addLayer(states, {}, "US States") diff --git a/pages/03_plotting.py b/pages/03_plotting.py index ac838da..6545f36 100644 --- a/pages/03_plotting.py +++ b/pages/03_plotting.py @@ -11,23 +11,23 @@ def __init__(self, **kwargs): self.add_plot_gui() def add_ee_data(self): - landsat7 = ee.Image('LANDSAT/LE7_TOA_5YEAR/1999_2003').select( - ['B1', 'B2', 'B3', 'B4', 'B5', 'B7'] + landsat7 = ee.Image("LANDSAT/LE7_TOA_5YEAR/1999_2003").select( + ["B1", "B2", "B3", "B4", "B5", "B7"] ) - landsat_vis = {'bands': ['B4', 'B3', 'B2'], 'gamma': 1.4} + landsat_vis = {"bands": ["B4", "B3", "B2"], "gamma": 1.4} self.addLayer(landsat7, landsat_vis, "Landsat") - hyperion = ee.ImageCollection('EO1/HYPERION').filter( - ee.Filter.date('2016-01-01', '2017-03-01') + hyperion = ee.ImageCollection("EO1/HYPERION").filter( + ee.Filter.date("2016-01-01", "2017-03-01") ) hyperion_vis = { - 'min': 1000.0, - 'max': 14000.0, - 'gamma': 2.5, + "min": 1000.0, + "max": 14000.0, + "gamma": 2.5, } - self.addLayer(hyperion, hyperion_vis, 'Hyperion') + self.addLayer(hyperion, hyperion_vis, "Hyperion") @solara.component diff --git a/pages/04_split_map.py b/pages/04_split_map.py index a9f0a67..acb71a9 100644 --- a/pages/04_split_map.py +++ b/pages/04_split_map.py @@ -11,25 +11,25 @@ def __init__(self, **kwargs): def add_ee_data(self): # Select the eight NLCD epochs after 2000. - years = ['2001', '2004', '2006', '2008', '2011', '2013', '2016', '2019'] + 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') + dataset = ee.ImageCollection("USGS/NLCD_RELEASES/2019_REL/NLCD") # Filter the collection by year. - nlcd = dataset.filter(ee.Filter.eq('system:index', year)).first() + nlcd = dataset.filter(ee.Filter.eq("system:index", year)).first() # Select the land cover band. - landcover = nlcd.select('landcover') + 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))) # Create a list of labels to populate the dropdown list. - labels = [f'NLCD {year}' for year in years] + labels = [f"NLCD {year}" for year in years] # Add a split-panel map for visualizing NLCD land cover change. self.ts_inspector( @@ -41,7 +41,10 @@ def getNLCD(year): # Add the NLCD legend to the map. self.add_legend( - title='NLCD Land Cover Type', builtin_legend='NLCD', height="460px", add_header=False + title="NLCD Land Cover Type", + builtin_legend="NLCD", + height="460px", + add_header=False, ) diff --git a/requirements.txt b/requirements.txt index 18012f6..695fb59 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ geemap -solara geopandas -pydantic< 2.0 \ No newline at end of file +pydantic< 2.0 +solara