├── .gitignore
├── LICENSE
├── Procfile
├── README.md
├── config_vars.py
├── notebooks
├── bqplot.ipynb
├── geemap.ipynb
├── ipyleaflet.ipynb
├── leafmap.ipynb
└── water_app.ipynb
└── requirements.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 |
3 | # Byte-compiled / optimized / DLL files
4 | __pycache__/
5 | *.py[cod]
6 | *$py.class
7 |
8 | # C extensions
9 | *.so
10 |
11 | # Distribution / packaging
12 | .Python
13 | build/
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | lib/
20 | lib64/
21 | parts/
22 | sdist/
23 | var/
24 | wheels/
25 | pip-wheel-metadata/
26 | share/python-wheels/
27 | *.egg-info/
28 | .installed.cfg
29 | *.egg
30 | MANIFEST
31 |
32 | # PyInstaller
33 | # Usually these files are written by a python script from a template
34 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
35 | *.manifest
36 | *.spec
37 |
38 | # Installer logs
39 | pip-log.txt
40 | pip-delete-this-directory.txt
41 |
42 | # Unit test / coverage reports
43 | htmlcov/
44 | .tox/
45 | .nox/
46 | .coverage
47 | .coverage.*
48 | .cache
49 | nosetests.xml
50 | coverage.xml
51 | *.cover
52 | *.py,cover
53 | .hypothesis/
54 | .pytest_cache/
55 |
56 | # Translations
57 | *.mo
58 | *.pot
59 |
60 | # Django stuff:
61 | *.log
62 | local_settings.py
63 | db.sqlite3
64 | db.sqlite3-journal
65 |
66 | # Flask stuff:
67 | instance/
68 | .webassets-cache
69 |
70 | # Scrapy stuff:
71 | .scrapy
72 |
73 | # Sphinx documentation
74 | docs/_build/
75 |
76 | # PyBuilder
77 | target/
78 |
79 | # Jupyter Notebook
80 | .ipynb_checkpoints
81 |
82 | # IPython
83 | profile_default/
84 | ipython_config.py
85 |
86 | # pyenv
87 | .python-version
88 |
89 | # pipenv
90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
93 | # install all needed dependencies.
94 | #Pipfile.lock
95 |
96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
97 | __pypackages__/
98 |
99 | # Celery stuff
100 | celerybeat-schedule
101 | celerybeat.pid
102 |
103 | # SageMath parsed files
104 | *.sage.py
105 |
106 | # Environments
107 | .env
108 | .venv
109 | env/
110 | venv/
111 | ENV/
112 | env.bak/
113 | venv.bak/
114 |
115 | # Spyder project settings
116 | .spyderproject
117 | .spyproject
118 |
119 | # Rope project settings
120 | .ropeproject
121 |
122 | # mkdocs documentation
123 | /site
124 |
125 | # mypy
126 | .mypy_cache/
127 | .dmypy.json
128 | dmypy.json
129 |
130 | # Pyre type checker
131 | .pyre/
132 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Qiusheng Wu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: voila --port=$PORT --Voila.ip=0.0.0.0 --no-browser --strip_sources=True --enable_nbextensions=True --MappingKernelManager.cull_interval=60 --MappingKernelManager.cull_idle_timeout=120 --NotebookClient.iopub_timeout=30 notebooks/
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # geemap-heroku
2 |
3 | Python scripts for deploying Earth Engine Apps to heroku, try it out:
4 |
5 | ## How to deploy your own Earth Engine Apps?
6 |
7 | - [Sign up](https://signup.heroku.com/) for a free heroku account.
8 | - Follow the [instructions](https://devcenter.heroku.com/articles/getting-started-with-python#set-up) to install [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and Heroku Command Line Interface (CLI).
9 | - Authenticate heroku using the `heroku login` command.
10 | - Clone this repository:
11 | - Create your own Earth Engine notebook and put it under the `notebooks` directory.
12 | - Add Python dependencies in the `requirements.txt` file if needed.
13 | - Edit the `Procfile` file by replacing `notebooks/geemap.ipynb` with the path to your own notebook.
14 | - Commit changes to the repository by using `git add . && git commit -am "message"`.
15 | - Create a heroku app: `heroku create`
16 | - Run the `config_vars.py` script to extract Earth Engine token from your computer and set it as an environment variable on heroku: `python config_vars.py`
17 | - Deploy your code to heroku: `git push heroku master`
18 | - Open your heroku app: `heroku open`
19 |
20 | ## Optional steps
21 |
22 | - To specify a name for your app, use `heroku apps:create example`
23 | - To preview your app locally, use `heroku local web`
24 | - To hide code cells from your app, you can edit the `Procfile` file and set `--strip_sources=True`
25 | - To periodically check for idle kernels, you can edit the `Procfile` file and set `--MappingKernelManager.cull_interval=60 --MappingKernelManager.cull_idle_timeout=120`
26 | - To view information about your running app, use `heroku logs --tail`
27 | - To set an environment variable on heroku, use `heroku config:set NAME=VALUE`
28 | - To view environment variables for your app, use `heroku config`
29 |
30 | ## Credits
31 |
32 | The instructions above on how to deploy a voila application on heroku are adapted from [voila-dashboards/voila-heroku](https://github.com/voila-dashboards/voila-heroku).
33 |
--------------------------------------------------------------------------------
/config_vars.py:
--------------------------------------------------------------------------------
1 | import os
2 | import platform
3 | import json
4 | from subprocess import DEVNULL, STDOUT, check_call
5 |
6 | def set_heroku_vars(token_name='EARTHENGINE_TOKEN'):
7 | """Extracts Earth Engine token from the local computer and sets it as an environment variable on heroku.
8 |
9 | Args:
10 | token_name (str, optional): Name of the Earth Engine token. Defaults to 'EARTHENGINE_TOKEN'.
11 | """
12 | try:
13 |
14 | ee_token_dir = os.path.expanduser("~/.config/earthengine/")
15 | ee_token_file = os.path.join(ee_token_dir, 'credentials')
16 |
17 | if not os.path.exists(ee_token_file):
18 | print('The credentials file does not exist.')
19 | else:
20 |
21 | with open(ee_token_file) as f:
22 | content = f.read()
23 | content_json = json.loads(content)
24 | token = content_json["refresh_token"]
25 | secret = '{}={}'.format(token_name, token)
26 | if platform.system() == 'Windows':
27 | check_call(['heroku', 'config:set', secret], stdout=DEVNULL, stderr=STDOUT, shell=True)
28 | else:
29 | check_call(['heroku', 'config:set', secret], stdout=DEVNULL, stderr=STDOUT)
30 |
31 | except Exception as e:
32 | print(e)
33 | return
34 |
35 | if __name__ == '__main__':
36 |
37 | set_heroku_vars(token_name='EARTHENGINE_TOKEN')
--------------------------------------------------------------------------------
/notebooks/bqplot.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "from bqplot import pyplot as plt\n",
10 | "import ipywidgets as widgets\n",
11 | "import numpy as np"
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": null,
17 | "metadata": {},
18 | "outputs": [],
19 | "source": [
20 | "# generate some fake \n",
21 | "n = 2000\n",
22 | "x = np.linspace(0.0, 10.0, n)\n",
23 | "np.random.seed(0)\n",
24 | "y = np.cumsum(np.random.randn(n)*10).astype(int)"
25 | ]
26 | },
27 | {
28 | "cell_type": "code",
29 | "execution_count": null,
30 | "metadata": {},
31 | "outputs": [],
32 | "source": [
33 | "fig_hist = plt.figure( title='Histogram')\n",
34 | "hist = plt.hist(y, bins=25)"
35 | ]
36 | },
37 | {
38 | "cell_type": "code",
39 | "execution_count": null,
40 | "metadata": {},
41 | "outputs": [],
42 | "source": [
43 | "hist.bins = 10;"
44 | ]
45 | },
46 | {
47 | "cell_type": "code",
48 | "execution_count": null,
49 | "metadata": {},
50 | "outputs": [],
51 | "source": [
52 | "slider = widgets.IntSlider(description='Bins number', min=1, max=100, v_model=30)"
53 | ]
54 | },
55 | {
56 | "cell_type": "code",
57 | "execution_count": null,
58 | "metadata": {
59 | "scrolled": false
60 | },
61 | "outputs": [],
62 | "source": [
63 | "widgets.link((hist, 'bins'), (slider, 'value'))\n",
64 | "\n",
65 | "fig_lines = plt.figure( title='Line Chart')\n",
66 | "lines = plt.plot(x, y)\n",
67 | "\n",
68 | "fig_lines.layout.width = 'auto'\n",
69 | "fig_lines.layout.height = 'auto'\n",
70 | "fig_hist.layout.width = 'auto'\n",
71 | "fig_hist.layout.height = 'auto'\n",
72 | "\n",
73 | "grid_layout = widgets.GridspecLayout(5, 3)\n",
74 | "\n",
75 | "grid_layout[:2, :] = fig_lines\n",
76 | "grid_layout[2:4, :] = fig_hist\n",
77 | "grid_layout[4, 1] = slider\n",
78 | "\n",
79 | "grid_layout.layout.height = '1000px'\n",
80 | "\n",
81 | "grid_layout"
82 | ]
83 | },
84 | {
85 | "cell_type": "code",
86 | "execution_count": null,
87 | "metadata": {},
88 | "outputs": [],
89 | "source": [
90 | "selector = plt.brush_int_selector()\n",
91 | "def update_range(*ignore):\n",
92 | " if selector.selected is not None and len(selector.selected) == 2:\n",
93 | " xmin, xmax = selector.selected\n",
94 | " mask = (x > xmin) & (x < xmax)\n",
95 | " hist.sample = y[mask]\n",
96 | "selector.observe(update_range, 'selected') "
97 | ]
98 | },
99 | {
100 | "cell_type": "code",
101 | "execution_count": null,
102 | "metadata": {},
103 | "outputs": [],
104 | "source": []
105 | }
106 | ],
107 | "metadata": {
108 | "kernelspec": {
109 | "display_name": "Python 3",
110 | "language": "python",
111 | "name": "python3"
112 | },
113 | "language_info": {
114 | "codemirror_mode": {
115 | "name": "ipython",
116 | "version": 3
117 | },
118 | "file_extension": ".py",
119 | "mimetype": "text/x-python",
120 | "name": "python",
121 | "nbconvert_exporter": "python",
122 | "pygments_lexer": "ipython3",
123 | "version": "3.7.3"
124 | },
125 | "widgets": {
126 | "application/vnd.jupyter.widget-state+json": {
127 | "state": {},
128 | "version_major": 2,
129 | "version_minor": 0
130 | }
131 | }
132 | },
133 | "nbformat": 4,
134 | "nbformat_minor": 4
135 | }
136 |
--------------------------------------------------------------------------------
/notebooks/geemap.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "import ee\n",
10 | "import geemap"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "metadata": {},
16 | "source": [
17 | "## Earth Engine App"
18 | ]
19 | },
20 | {
21 | "cell_type": "code",
22 | "execution_count": null,
23 | "metadata": {},
24 | "outputs": [],
25 | "source": [
26 | "Map = geemap.Map(center=(40, -100), zoom=4)"
27 | ]
28 | },
29 | {
30 | "cell_type": "code",
31 | "execution_count": null,
32 | "metadata": {},
33 | "outputs": [],
34 | "source": [
35 | "# Add Earth Engine dataset\n",
36 | "dem = ee.Image('USGS/SRTMGL1_003')\n",
37 | "landcover = ee.Image(\"ESA/GLOBCOVER_L4_200901_200912_V2_3\").select('landcover')\n",
38 | "landsat7 = ee.Image('LE7_TOA_5YEAR/1999_2003')\n",
39 | "states = ee.FeatureCollection(\"TIGER/2018/States\")\n",
40 | "\n",
41 | "# Set visualization parameters.\n",
42 | "vis_params = {\n",
43 | " 'min': 0,\n",
44 | " 'max': 4000,\n",
45 | " 'palette': ['006633', 'E5FFCC', '662A00', 'D8D8D8', 'F5F5F5']}\n",
46 | "\n",
47 | "# Add Earth Eninge layers to Map\n",
48 | "Map.addLayer(landsat7, {'bands': ['B4', 'B3', 'B2'], 'min': 20, 'max': 200}, 'Landsat 7')\n",
49 | "Map.addLayer(landcover, {}, 'Land cover')\n",
50 | "Map.addLayer(dem, vis_params, 'STRM DEM', True, 1)\n",
51 | "Map.addLayer(states, {}, \"US States\")\n",
52 | "\n",
53 | "Map"
54 | ]
55 | },
56 | {
57 | "cell_type": "code",
58 | "execution_count": null,
59 | "metadata": {},
60 | "outputs": [],
61 | "source": [
62 | "print('Change layer opacity:')\n",
63 | "dem_layer = Map.layers[-2]\n",
64 | "dem_layer.interact(opacity=(0, 1, 0.1))"
65 | ]
66 | }
67 | ],
68 | "metadata": {
69 | "kernelspec": {
70 | "display_name": "Python 3",
71 | "language": "python",
72 | "name": "python3"
73 | },
74 | "language_info": {
75 | "codemirror_mode": {
76 | "name": "ipython",
77 | "version": 3
78 | },
79 | "file_extension": ".py",
80 | "mimetype": "text/x-python",
81 | "name": "python",
82 | "nbconvert_exporter": "python",
83 | "pygments_lexer": "ipython3",
84 | "version": "3.9.10"
85 | }
86 | },
87 | "nbformat": 4,
88 | "nbformat_minor": 4
89 | }
90 |
--------------------------------------------------------------------------------
/notebooks/ipyleaflet.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "bcc1396c-3770-40f0-8cc5-22ed12469db0",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "import ipyleaflet"
11 | ]
12 | },
13 | {
14 | "cell_type": "code",
15 | "execution_count": null,
16 | "id": "bfdc2d9d-de54-41ae-a8b2-818571b2b7cf",
17 | "metadata": {},
18 | "outputs": [],
19 | "source": [
20 | "m = ipyleaflet.Map(center=[40, -100], zoom=4)\n",
21 | "m"
22 | ]
23 | }
24 | ],
25 | "metadata": {
26 | "kernelspec": {
27 | "display_name": "Python 3",
28 | "language": "python",
29 | "name": "python3"
30 | },
31 | "language_info": {
32 | "codemirror_mode": {
33 | "name": "ipython",
34 | "version": 3
35 | },
36 | "file_extension": ".py",
37 | "mimetype": "text/x-python",
38 | "name": "python",
39 | "nbconvert_exporter": "python",
40 | "pygments_lexer": "ipython3",
41 | "version": "3.9.10"
42 | }
43 | },
44 | "nbformat": 4,
45 | "nbformat_minor": 5
46 | }
47 |
--------------------------------------------------------------------------------
/notebooks/leafmap.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 6,
6 | "id": "bc362c3b-4854-4da0-bdac-4866de84da3f",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "import leafmap"
11 | ]
12 | },
13 | {
14 | "cell_type": "code",
15 | "execution_count": 7,
16 | "id": "b63a4fa5-0089-4128-a72e-27df26c09c42",
17 | "metadata": {},
18 | "outputs": [
19 | {
20 | "data": {
21 | "application/vnd.jupyter.widget-view+json": {
22 | "model_id": "9b677c790a784537b6fe4d35d84b3f91",
23 | "version_major": 2,
24 | "version_minor": 0
25 | },
26 | "text/plain": [
27 | "Map(center=[20, 0], controls=(ZoomControl(options=['position', 'zoom_in_text', 'zoom_in_title', 'zoom_out_text…"
28 | ]
29 | },
30 | "metadata": {},
31 | "output_type": "display_data"
32 | }
33 | ],
34 | "source": [
35 | "m = leafmap.Map()\n",
36 | "m.add_basemap('HYBRID')\n",
37 | "m"
38 | ]
39 | }
40 | ],
41 | "metadata": {
42 | "kernelspec": {
43 | "display_name": "Python 3",
44 | "language": "python",
45 | "name": "python3"
46 | },
47 | "language_info": {
48 | "codemirror_mode": {
49 | "name": "ipython",
50 | "version": 3
51 | },
52 | "file_extension": ".py",
53 | "mimetype": "text/x-python",
54 | "name": "python",
55 | "nbconvert_exporter": "python",
56 | "pygments_lexer": "ipython3",
57 | "version": "3.9.10"
58 | }
59 | },
60 | "nbformat": 4,
61 | "nbformat_minor": 5
62 | }
63 |
--------------------------------------------------------------------------------
/notebooks/water_app.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Using Earth Engine and geemap for mapping surface water dynamics\n",
8 | "\n",
9 | "**Steps to create Landsat timeseries:**\n",
10 | "\n",
11 | "1. Pan and zoom to your area of interest, and click on the map to select a polygon.\n",
12 | "2. Adjust the parameters (e.g., band combination, threshold, color) if needed.\n",
13 | "3. Click the `Submit` button to create timeseries of Landsat imagery and normalized difference indices.\n",
14 | "\n",
15 | "**Web Apps:** https://gishub.org/water-app, https://gishub.org/water-ngrok\n",
16 | "\n",
17 | "**Contact:** Dr. Qiusheng Wu ([Website](https://wetlands.io/), [LinkedIn](https://www.linkedin.com/in/qiushengwu), [Twitter](https://twitter.com/giswqs), [YouTube](https://www.youtube.com/c/QiushengWu))"
18 | ]
19 | },
20 | {
21 | "cell_type": "code",
22 | "execution_count": null,
23 | "metadata": {},
24 | "outputs": [],
25 | "source": [
26 | "# Check geemap installation\n",
27 | "import subprocess\n",
28 | "\n",
29 | "try:\n",
30 | " import geemap\n",
31 | "except ImportError:\n",
32 | " print('geemap package is not installed. Installing ...')\n",
33 | " subprocess.check_call([\"python\", '-m', 'pip', 'install', 'geemap'])"
34 | ]
35 | },
36 | {
37 | "cell_type": "code",
38 | "execution_count": null,
39 | "metadata": {},
40 | "outputs": [],
41 | "source": [
42 | "# Import libraries\n",
43 | "import os\n",
44 | "import ee\n",
45 | "import geemap\n",
46 | "import ipywidgets as widgets\n",
47 | "from bqplot import pyplot as plt\n",
48 | "from ipyleaflet import WidgetControl"
49 | ]
50 | },
51 | {
52 | "cell_type": "code",
53 | "execution_count": null,
54 | "metadata": {
55 | "scrolled": false
56 | },
57 | "outputs": [],
58 | "source": [
59 | "# Create an interactive map\n",
60 | "Map = geemap.Map(center=[37.71, 105.47], zoom=4, add_google_map=False)\n",
61 | "Map.add_basemap('HYBRID')\n",
62 | "Map.add_basemap('ROADMAP')\n",
63 | "\n",
64 | "# Add Earth Engine data\n",
65 | "fc = ee.FeatureCollection('users/giswqs/public/chn_admin_level2')\n",
66 | "Map.addLayer(fc, {}, '二级行政区')\n",
67 | "Map"
68 | ]
69 | },
70 | {
71 | "cell_type": "code",
72 | "execution_count": null,
73 | "metadata": {},
74 | "outputs": [],
75 | "source": [
76 | "# Designe interactive widgets\n",
77 | "\n",
78 | "style = {'description_width': 'initial'}\n",
79 | "\n",
80 | "output_widget = widgets.Output(layout={'border': '1px solid black'})\n",
81 | "output_control = WidgetControl(widget=output_widget, position='bottomright')\n",
82 | "Map.add_control(output_control)\n",
83 | "\n",
84 | "admin1_widget = widgets.Text(\n",
85 | " description='一级行政区:',\n",
86 | " value='广东省',\n",
87 | " width=200,\n",
88 | " style=style\n",
89 | ")\n",
90 | "\n",
91 | "admin2_widget = widgets.Text(\n",
92 | " description='二级行政区:',\n",
93 | " value='广州市',\n",
94 | " width=300,\n",
95 | " style=style\n",
96 | ")\n",
97 | "\n",
98 | "band_combo = widgets.Dropdown(\n",
99 | " description='显示影像波段组合:',\n",
100 | " options=['Red/Green/Blue', 'NIR/Red/Green', 'SWIR2/SWIR1/NIR', 'NIR/SWIR1/Red','SWIR2/NIR/Red', \n",
101 | " 'SWIR2/SWIR1/Red', 'SWIR1/NIR/Blue', 'NIR/SWIR1/Blue', 'SWIR2/NIR/Green', 'SWIR1/NIR/Red'],\n",
102 | " value='NIR/Red/Green',\n",
103 | " style=style\n",
104 | ")\n",
105 | "\n",
106 | "year_widget = widgets.IntSlider(min=1984, max=2020, value=2010, description='显示指定年份数据:', width=400, style=style)\n",
107 | "\n",
108 | "fmask_widget = widgets.Checkbox(\n",
109 | " value=True,\n",
110 | " description='应用fmask(去除云, 阴影, 雪)',\n",
111 | " style=style\n",
112 | ")\n",
113 | "\n",
114 | "\n",
115 | "# Normalized Satellite Indices: https://www.usna.edu/Users/oceano/pguth/md_help/html/norm_sat.htm\n",
116 | "\n",
117 | "nd_options = ['Vegetation Index (NDVI)', \n",
118 | " 'Water Index (NDWI)',\n",
119 | " 'Modified Water Index (MNDWI)',\n",
120 | " 'Snow Index (NDSI)',\n",
121 | " 'Soil Index (NDSI)',\n",
122 | " 'Burn Ratio (NBR)',\n",
123 | " 'Customized']\n",
124 | "nd_indices = widgets.Dropdown(options=nd_options, value='Modified Water Index (MNDWI)', description='归一化指数:', style=style)\n",
125 | "\n",
126 | "first_band = widgets.Dropdown(\n",
127 | " description='波段1:',\n",
128 | " options=['Blue', 'Green','Red','NIR', 'SWIR1', 'SWIR2'],\n",
129 | " value='Green',\n",
130 | " style=style\n",
131 | ")\n",
132 | "\n",
133 | "second_band = widgets.Dropdown(\n",
134 | " description='波段2:',\n",
135 | " options=['Blue', 'Green','Red','NIR', 'SWIR1', 'SWIR2'],\n",
136 | " value='SWIR1',\n",
137 | " style=style\n",
138 | ")\n",
139 | "\n",
140 | "nd_threshold = widgets.FloatSlider(\n",
141 | " value=0,\n",
142 | " min=-1,\n",
143 | " max=1,\n",
144 | " step=0.01,\n",
145 | " description='阈值:',\n",
146 | " orientation='horizontal',\n",
147 | " style=style\n",
148 | ")\n",
149 | "\n",
150 | "nd_color = widgets.ColorPicker(\n",
151 | " concise=False,\n",
152 | " description='颜色:',\n",
153 | " value='blue',\n",
154 | " style=style\n",
155 | ")\n",
156 | "\n",
157 | "def nd_index_change(change):\n",
158 | " if nd_indices.value == 'Vegetation Index (NDVI)':\n",
159 | " first_band.value = 'NIR'\n",
160 | " second_band.value = 'Red'\n",
161 | " elif nd_indices.value == 'Water Index (NDWI)':\n",
162 | " first_band.value = 'NIR'\n",
163 | " second_band.value = 'SWIR1' \n",
164 | " elif nd_indices.value == 'Modified Water Index (MNDWI)':\n",
165 | " first_band.value = 'Green'\n",
166 | " second_band.value = 'SWIR1' \n",
167 | " elif nd_indices.value == 'Snow Index (NDSI)':\n",
168 | " first_band.value = 'Green'\n",
169 | " second_band.value = 'SWIR1'\n",
170 | " elif nd_indices.value == 'Soil Index (NDSI)':\n",
171 | " first_band.value = 'SWIR1'\n",
172 | " second_band.value = 'NIR' \n",
173 | " elif nd_indices.value == 'Burn Ratio (NBR)':\n",
174 | " first_band.value = 'NIR'\n",
175 | " second_band.value = 'SWIR2'\n",
176 | " elif nd_indices.value == 'Customized':\n",
177 | " first_band.value = None\n",
178 | " second_band.value = None\n",
179 | " \n",
180 | "nd_indices.observe(nd_index_change, names='value')\n",
181 | "\n",
182 | "submit = widgets.Button(\n",
183 | " description='Submit',\n",
184 | " button_style='primary',\n",
185 | " tooltip='Click me',\n",
186 | " style=style\n",
187 | ")\n",
188 | "\n",
189 | "full_widget = widgets.VBox([\n",
190 | " widgets.HBox([admin1_widget, admin2_widget]),\n",
191 | " widgets.HBox([band_combo, year_widget, fmask_widget]),\n",
192 | " widgets.HBox([nd_indices, first_band, second_band, nd_threshold, nd_color]),\n",
193 | " submit\n",
194 | "])\n",
195 | "\n",
196 | "full_widget"
197 | ]
198 | },
199 | {
200 | "cell_type": "code",
201 | "execution_count": null,
202 | "metadata": {},
203 | "outputs": [],
204 | "source": [
205 | "# Capture user interaction with the map\n",
206 | "\n",
207 | "def handle_interaction(**kwargs):\n",
208 | " latlon = kwargs.get('coordinates')\n",
209 | " if kwargs.get('type') == 'click':\n",
210 | " Map.default_style = {'cursor': 'wait'}\n",
211 | " xy = ee.Geometry.Point(latlon[::-1])\n",
212 | " selected_fc = fc.filterBounds(xy)\n",
213 | " \n",
214 | " with output_widget:\n",
215 | " output_widget.clear_output()\n",
216 | " \n",
217 | " try:\n",
218 | " admin1_id = selected_fc.first().get('ADM1_ZH').getInfo()\n",
219 | " admin2_id = selected_fc.first().get('ADM2_ZH').getInfo()\n",
220 | " admin1_widget.value = admin1_id\n",
221 | " admin2_widget.value = admin2_id\n",
222 | " Map.layers = Map.layers[:4] \n",
223 | " geom = selected_fc.geometry()\n",
224 | " layer_name = admin1_id + '-' + admin2_id\n",
225 | " Map.addLayer(ee.Image().paint(geom, 0, 2), {'palette': 'red'}, layer_name) \n",
226 | " print(layer_name)\n",
227 | " except:\n",
228 | " print('找不到相关行政区')\n",
229 | " Map.layers = Map.layers[:4]\n",
230 | " \n",
231 | " Map.default_style = {'cursor': 'pointer'}\n",
232 | "\n",
233 | "Map.on_interaction(handle_interaction)"
234 | ]
235 | },
236 | {
237 | "cell_type": "code",
238 | "execution_count": null,
239 | "metadata": {},
240 | "outputs": [],
241 | "source": [
242 | "# Click event handler\n",
243 | "\n",
244 | "def submit_clicked(b):\n",
245 | " \n",
246 | " with output_widget:\n",
247 | " output_widget.clear_output()\n",
248 | " print('Computing...')\n",
249 | " Map.default_style = {'cursor': 'wait'}\n",
250 | "\n",
251 | " try:\n",
252 | " admin1_id = admin1_widget.value\n",
253 | " admin2_id = admin2_widget.value\n",
254 | " band1 = first_band.value\n",
255 | " band2 = second_band.value\n",
256 | " selected_year = year_widget.value\n",
257 | " threshold = nd_threshold.value\n",
258 | " bands = band_combo.value.split('/')\n",
259 | " apply_fmask = fmask_widget.value\n",
260 | " palette = nd_color.value\n",
261 | " \n",
262 | " roi = fc.filter(ee.Filter.And(ee.Filter.eq('ADM1_ZH', admin1_id), ee.Filter.eq('ADM2_ZH', admin2_id)))\n",
263 | " Map.layers = Map.layers[:4] \n",
264 | " geom = roi.geometry()\n",
265 | " layer_name = admin1_id + '-' + admin2_id\n",
266 | " Map.addLayer(ee.Image().paint(geom, 0, 2), {'palette': 'red'}, layer_name) \n",
267 | " \n",
268 | " images = geemap.landsat_timeseries(roi=roi, start_year=1984, end_year=2020, start_date='01-01', end_date='12-31', apply_fmask=apply_fmask)\n",
269 | " nd_images = images.map(lambda img: img.normalizedDifference([band1, band2]))\n",
270 | " result_images = nd_images.map(lambda img: img.gt(threshold))\n",
271 | "\n",
272 | " selected_image = ee.Image(images.toList(images.size()).get(selected_year - 1984))\n",
273 | " selected_result_image = ee.Image(result_images.toList(result_images.size()).get(selected_year - 1984)).selfMask()\n",
274 | " \n",
275 | " vis_params = {\n",
276 | " 'bands': bands,\n",
277 | " 'min': 0,\n",
278 | " 'max': 3000\n",
279 | " }\n",
280 | " \n",
281 | " Map.addLayer(selected_image, vis_params, 'Landsat ' + str(selected_year))\n",
282 | " Map.addLayer(selected_result_image, {'palette': palette}, 'Result ' + str(selected_year))\n",
283 | "\n",
284 | " \n",
285 | " def cal_area(img):\n",
286 | " pixel_area = img.multiply(ee.Image.pixelArea()).divide(1e6)\n",
287 | " img_area = pixel_area.reduceRegion(**{\n",
288 | " 'geometry': roi.geometry(),\n",
289 | " 'reducer': ee.Reducer.sum(),\n",
290 | " 'scale': 1000,\n",
291 | " 'maxPixels': 1e12,\n",
292 | " 'bestEffort': True\n",
293 | " })\n",
294 | " return img.set({'area': img_area})\n",
295 | " \n",
296 | " areas = result_images.map(cal_area)\n",
297 | " stats = areas.aggregate_array('area').getInfo()\n",
298 | " x = list(range(1984, 2021))\n",
299 | " y = [item.get('nd') for item in stats]\n",
300 | " \n",
301 | " fig = plt.figure(1)\n",
302 | " fig.layout.height = '270px'\n",
303 | " plt.clear()\n",
304 | " plt.plot(x, y)\n",
305 | " plt.title('Temporal trend (1984-2020)')\n",
306 | " plt.xlabel('Year')\n",
307 | " plt.ylabel('Area (km2)')\n",
308 | " \n",
309 | " output_widget.clear_output() \n",
310 | "\n",
311 | " plt.show()\n",
312 | " \n",
313 | " except Exception as e:\n",
314 | " print(e)\n",
315 | " print('An error occurred during computation.')\n",
316 | " \n",
317 | "\n",
318 | " Map.default_style = {'cursor': 'default'}\n",
319 | "\n",
320 | "submit.on_click(submit_clicked)"
321 | ]
322 | }
323 | ],
324 | "metadata": {
325 | "gist": {
326 | "data": {
327 | "description": "notebooks/wetland_mapping.ipynb",
328 | "public": true
329 | },
330 | "id": ""
331 | },
332 | "hide_input": true,
333 | "kernelspec": {
334 | "display_name": "Python 3",
335 | "language": "python",
336 | "name": "python3"
337 | },
338 | "language_info": {
339 | "codemirror_mode": {
340 | "name": "ipython",
341 | "version": 3
342 | },
343 | "file_extension": ".py",
344 | "mimetype": "text/x-python",
345 | "name": "python",
346 | "nbconvert_exporter": "python",
347 | "pygments_lexer": "ipython3",
348 | "version": "3.8.2"
349 | },
350 | "toc": {
351 | "base_numbering": 1,
352 | "nav_menu": {},
353 | "number_sections": true,
354 | "sideBar": true,
355 | "skip_h1_title": true,
356 | "title_cell": "Table of Contents",
357 | "title_sidebar": "Table of Contents",
358 | "toc_cell": false,
359 | "toc_position": {},
360 | "toc_section_display": true,
361 | "toc_window_display": false
362 | },
363 | "varInspector": {
364 | "cols": {
365 | "lenName": 16,
366 | "lenType": 16,
367 | "lenVar": 40
368 | },
369 | "kernels_config": {
370 | "python": {
371 | "delete_cmd_postfix": "",
372 | "delete_cmd_prefix": "del ",
373 | "library": "var_list.py",
374 | "varRefreshCmd": "print(var_dic_list())"
375 | },
376 | "r": {
377 | "delete_cmd_postfix": ") ",
378 | "delete_cmd_prefix": "rm(",
379 | "library": "var_list.r",
380 | "varRefreshCmd": "cat(var_dic_list()) "
381 | }
382 | },
383 | "types_to_exclude": [
384 | "module",
385 | "function",
386 | "builtin_function_or_method",
387 | "instance",
388 | "_Feature"
389 | ],
390 | "window_display": false
391 | }
392 | },
393 | "nbformat": 4,
394 | "nbformat_minor": 4
395 | }
396 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | voila
2 | geemap
3 | leafmap
4 | Jinja2==3.0.3
5 |
--------------------------------------------------------------------------------