Spaces:
Sleeping
Sleeping
Initial commit
Browse files- .github/workflows/sync-hf.yml +20 -0
- .gitignore +164 -0
- Dockerfile +25 -0
- LICENSE +21 -0
- README.md +1 -0
- pages/00_home.py +19 -0
- pages/01_depressions.py +272 -0
- pages/02_lidar.py +61 -0
- pages/03_timelapse.py +25 -0
- pages/04_timeseries.py +283 -0
- pages/05_jrc.py +141 -0
- pages/06_compare.py +279 -0
- requirements.txt +4 -0
.github/workflows/sync-hf.yml
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Sync to Hugging Face hub
|
2 |
+
on:
|
3 |
+
push:
|
4 |
+
branches: [main]
|
5 |
+
|
6 |
+
# to run this workflow manually from the Actions tab
|
7 |
+
workflow_dispatch:
|
8 |
+
|
9 |
+
jobs:
|
10 |
+
sync-to-hub:
|
11 |
+
runs-on: ubuntu-latest
|
12 |
+
steps:
|
13 |
+
- uses: actions/checkout@v4
|
14 |
+
with:
|
15 |
+
fetch-depth: 0
|
16 |
+
lfs: true
|
17 |
+
- name: Push to hub
|
18 |
+
env:
|
19 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
20 |
+
run: git push --force https://giswqs:$HF_TOKEN@huggingface.co/spaces/giswqs/playa-wetlands main
|
.gitignore
ADDED
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Byte-compiled / optimized / DLL files
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
private/
|
6 |
+
|
7 |
+
# C extensions
|
8 |
+
*.so
|
9 |
+
**/*.tif
|
10 |
+
**/*.csv
|
11 |
+
notebooks/
|
12 |
+
|
13 |
+
# Distribution / packaging
|
14 |
+
.Python
|
15 |
+
build/
|
16 |
+
develop-eggs/
|
17 |
+
dist/
|
18 |
+
downloads/
|
19 |
+
eggs/
|
20 |
+
.eggs/
|
21 |
+
lib/
|
22 |
+
lib64/
|
23 |
+
parts/
|
24 |
+
sdist/
|
25 |
+
var/
|
26 |
+
wheels/
|
27 |
+
share/python-wheels/
|
28 |
+
*.egg-info/
|
29 |
+
.installed.cfg
|
30 |
+
*.egg
|
31 |
+
MANIFEST
|
32 |
+
|
33 |
+
# PyInstaller
|
34 |
+
# Usually these files are written by a python script from a template
|
35 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
36 |
+
*.manifest
|
37 |
+
*.spec
|
38 |
+
|
39 |
+
# Installer logs
|
40 |
+
pip-log.txt
|
41 |
+
pip-delete-this-directory.txt
|
42 |
+
|
43 |
+
# Unit test / coverage reports
|
44 |
+
htmlcov/
|
45 |
+
.tox/
|
46 |
+
.nox/
|
47 |
+
.coverage
|
48 |
+
.coverage.*
|
49 |
+
.cache
|
50 |
+
nosetests.xml
|
51 |
+
coverage.xml
|
52 |
+
*.cover
|
53 |
+
*.py,cover
|
54 |
+
.hypothesis/
|
55 |
+
.pytest_cache/
|
56 |
+
cover/
|
57 |
+
|
58 |
+
# Translations
|
59 |
+
*.mo
|
60 |
+
*.pot
|
61 |
+
|
62 |
+
# Django stuff:
|
63 |
+
*.log
|
64 |
+
local_settings.py
|
65 |
+
db.sqlite3
|
66 |
+
db.sqlite3-journal
|
67 |
+
|
68 |
+
# Flask stuff:
|
69 |
+
instance/
|
70 |
+
.webassets-cache
|
71 |
+
|
72 |
+
# Scrapy stuff:
|
73 |
+
.scrapy
|
74 |
+
|
75 |
+
# Sphinx documentation
|
76 |
+
docs/_build/
|
77 |
+
|
78 |
+
# PyBuilder
|
79 |
+
.pybuilder/
|
80 |
+
target/
|
81 |
+
|
82 |
+
# Jupyter Notebook
|
83 |
+
.ipynb_checkpoints
|
84 |
+
|
85 |
+
# IPython
|
86 |
+
profile_default/
|
87 |
+
ipython_config.py
|
88 |
+
|
89 |
+
# pyenv
|
90 |
+
# For a library or package, you might want to ignore these files since the code is
|
91 |
+
# intended to run in multiple environments; otherwise, check them in:
|
92 |
+
# .python-version
|
93 |
+
|
94 |
+
# pipenv
|
95 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
96 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
97 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
98 |
+
# install all needed dependencies.
|
99 |
+
#Pipfile.lock
|
100 |
+
|
101 |
+
# poetry
|
102 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
103 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
104 |
+
# commonly ignored for libraries.
|
105 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
106 |
+
#poetry.lock
|
107 |
+
|
108 |
+
# pdm
|
109 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
110 |
+
#pdm.lock
|
111 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
112 |
+
# in version control.
|
113 |
+
# https://pdm.fming.dev/#use-with-ide
|
114 |
+
.pdm.toml
|
115 |
+
|
116 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
117 |
+
__pypackages__/
|
118 |
+
|
119 |
+
# Celery stuff
|
120 |
+
celerybeat-schedule
|
121 |
+
celerybeat.pid
|
122 |
+
|
123 |
+
# SageMath parsed files
|
124 |
+
*.sage.py
|
125 |
+
|
126 |
+
# Environments
|
127 |
+
.env
|
128 |
+
.venv
|
129 |
+
env/
|
130 |
+
venv/
|
131 |
+
ENV/
|
132 |
+
env.bak/
|
133 |
+
venv.bak/
|
134 |
+
|
135 |
+
# Spyder project settings
|
136 |
+
.spyderproject
|
137 |
+
.spyproject
|
138 |
+
|
139 |
+
# Rope project settings
|
140 |
+
.ropeproject
|
141 |
+
|
142 |
+
# mkdocs documentation
|
143 |
+
/site
|
144 |
+
|
145 |
+
# mypy
|
146 |
+
.mypy_cache/
|
147 |
+
.dmypy.json
|
148 |
+
dmypy.json
|
149 |
+
|
150 |
+
# Pyre type checker
|
151 |
+
.pyre/
|
152 |
+
|
153 |
+
# pytype static type analyzer
|
154 |
+
.pytype/
|
155 |
+
|
156 |
+
# Cython debug symbols
|
157 |
+
cython_debug/
|
158 |
+
|
159 |
+
# PyCharm
|
160 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
161 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
162 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
163 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
164 |
+
#.idea/
|
Dockerfile
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM jupyter/base-notebook:latest
|
2 |
+
|
3 |
+
RUN mamba install -c conda-forge leafmap geopandas localtileserver -y && \
|
4 |
+
fix-permissions "${CONDA_DIR}" && \
|
5 |
+
fix-permissions "/home/${NB_USER}"
|
6 |
+
|
7 |
+
USER root
|
8 |
+
RUN apt-get update && apt-get install -y git
|
9 |
+
RUN pip install -U git+https://github.com/gee-community/geemap.git
|
10 |
+
|
11 |
+
COPY requirements.txt .
|
12 |
+
RUN pip install -r requirements.txt
|
13 |
+
|
14 |
+
RUN mkdir ./pages
|
15 |
+
COPY /pages ./pages
|
16 |
+
|
17 |
+
ENV PROJ_LIB='/opt/conda/share/proj'
|
18 |
+
|
19 |
+
USER root
|
20 |
+
RUN chown -R ${NB_UID} ${HOME}
|
21 |
+
USER ${NB_USER}
|
22 |
+
|
23 |
+
EXPOSE 8765
|
24 |
+
|
25 |
+
CMD ["solara", "run", "./pages", "--host=0.0.0.0"]
|
LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2023 Open Geospatial Solutions
|
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.
|
README.md
CHANGED
@@ -1,2 +1,3 @@
|
|
1 |
# playa-wetlands
|
|
|
2 |
Mapping Playa Wetlands
|
|
|
1 |
# playa-wetlands
|
2 |
+
|
3 |
Mapping Playa Wetlands
|
pages/00_home.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import solara
|
2 |
+
|
3 |
+
|
4 |
+
@solara.component
|
5 |
+
def Page():
|
6 |
+
markdown = """
|
7 |
+
## Solara for Geospatial Applications
|
8 |
+
|
9 |
+
### Introduction
|
10 |
+
|
11 |
+
An interactive web app for mapping Playa wetlands.
|
12 |
+
|
13 |
+
- Web App: <https://giswqs-playa-wetlands.hf.space>
|
14 |
+
- GitHub: <https://github.com/giswqs/playa-wetlands>
|
15 |
+
- Hugging Face: <https://huggingface.co/spaces/giswqs/playa-wetlands>
|
16 |
+
|
17 |
+
"""
|
18 |
+
|
19 |
+
solara.Markdown(markdown)
|
pages/01_depressions.py
ADDED
@@ -0,0 +1,272 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import ee
|
2 |
+
import geemap
|
3 |
+
|
4 |
+
import solara
|
5 |
+
import ipywidgets as widgets
|
6 |
+
import ipyleaflet
|
7 |
+
|
8 |
+
zoom = solara.reactive(4)
|
9 |
+
center = solara.reactive([40, -100])
|
10 |
+
|
11 |
+
|
12 |
+
class Map(geemap.Map):
|
13 |
+
def __init__(self, **kwargs):
|
14 |
+
super().__init__(**kwargs)
|
15 |
+
self.add_layer_manager(opened=False)
|
16 |
+
# self.add_layer_manager(opened=False)
|
17 |
+
self.add_data()
|
18 |
+
names = ["NED 10m", "Depressions", "NWI Vector"]
|
19 |
+
# self.add_buttons()
|
20 |
+
self.add_inspector(names=names, visible=False, opened=True)
|
21 |
+
|
22 |
+
def add_data(self):
|
23 |
+
|
24 |
+
url = "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}"
|
25 |
+
self.add_tile_layer(url, name="Google Satellite", attribution="Google")
|
26 |
+
|
27 |
+
n_layers = 9
|
28 |
+
|
29 |
+
self.setCenter(-99.00, 47.01, 8)
|
30 |
+
|
31 |
+
ned = ee.Image("USGS/3DEP/10m")
|
32 |
+
hillshade = ee.Terrain.hillshade(ned)
|
33 |
+
# Map.addLayer(hillshade, {}, "CONUS Hillshade", False)
|
34 |
+
|
35 |
+
conus = ee.Geometry.BBox(-127.18, 19.39, -62.75, 51.29)
|
36 |
+
|
37 |
+
huc8 = ee.FeatureCollection("USGS/WBD/2017/HUC08").filterBounds(conus)
|
38 |
+
pipestem_hu8 = ee.FeatureCollection("users/giswqs/Pipestem/Pipestem_HUC8")
|
39 |
+
|
40 |
+
style = {"color": "00000088", "fillColor": "00000000", "width": 1}
|
41 |
+
|
42 |
+
palette = ["006633", "E5FFCC", "662A00", "D8D8D8", "F5F5F5"]
|
43 |
+
self.addLayer(
|
44 |
+
ned, {"min": 0, "max": 4000, "palette": palette}, "NED 10m", False
|
45 |
+
)
|
46 |
+
self.addLayer(hillshade, {}, "NED Hillshade", False)
|
47 |
+
|
48 |
+
states = ee.FeatureCollection("TIGER/2018/States")
|
49 |
+
floodplain = ee.FeatureCollection("users/giswqs/floodplain/GFP250m")
|
50 |
+
|
51 |
+
gfplain250 = ee.Image("projects/sat-io/open-datasets/GFPLAIN250/NA")
|
52 |
+
states = ee.FeatureCollection("users/giswqs/public/us_states")
|
53 |
+
fp_image = gfplain250.clipToCollection(states)
|
54 |
+
self.addLayer(fp_image, {"palette": "#002B4D"}, "Floodplain raster", False)
|
55 |
+
|
56 |
+
fp_style = {"fillColor": "0000ff88"}
|
57 |
+
self.addLayer(floodplain.style(**fp_style), {}, "Floodplain vector", False)
|
58 |
+
self.addLayer(huc8, {}, "NHD-HU8 Vector", False)
|
59 |
+
self.addLayer(huc8.style(**style), {}, "NHD-HU8 Raster")
|
60 |
+
self.addLayer(
|
61 |
+
pipestem_hu8.style(
|
62 |
+
**{"color": "ffff00ff", "fillColor": "00000000", "width": 2}
|
63 |
+
),
|
64 |
+
{},
|
65 |
+
"Pipestem HU8",
|
66 |
+
)
|
67 |
+
|
68 |
+
self.add_legend(
|
69 |
+
builtin_legend="NWI", position="bottomleft", title="NWI Wetland Type"
|
70 |
+
)
|
71 |
+
|
72 |
+
def add_buttons(self, opened=True):
|
73 |
+
|
74 |
+
widget_width = "250px"
|
75 |
+
padding = "0px 0px 0px 5px" # upper, right, bottom, left
|
76 |
+
style = {"description_width": "initial"}
|
77 |
+
|
78 |
+
toolbar_button = widgets.ToggleButton(
|
79 |
+
value=False,
|
80 |
+
tooltip="Toolbar",
|
81 |
+
icon="hand-o-up",
|
82 |
+
layout=widgets.Layout(
|
83 |
+
width="28px", height="28px", padding="0px 0px 0px 4px"
|
84 |
+
),
|
85 |
+
)
|
86 |
+
|
87 |
+
close_button = widgets.ToggleButton(
|
88 |
+
value=False,
|
89 |
+
tooltip="Close the tool",
|
90 |
+
icon="times",
|
91 |
+
button_style="primary",
|
92 |
+
layout=widgets.Layout(
|
93 |
+
height="28px", width="28px", padding="0px 0px 0px 4px"
|
94 |
+
),
|
95 |
+
)
|
96 |
+
|
97 |
+
buttons = widgets.ToggleButtons(
|
98 |
+
value=None,
|
99 |
+
options=["Watershed", "Inspector", "Deactivate"],
|
100 |
+
tooltips=["Apply", "Reset", "Close"],
|
101 |
+
button_style="primary",
|
102 |
+
)
|
103 |
+
buttons.style.button_width = "87px"
|
104 |
+
# buttons.value = "Watershed"
|
105 |
+
|
106 |
+
label = widgets.Label(
|
107 |
+
"Click on the map to select a watershed",
|
108 |
+
padding="0px 0px 0px 4px",
|
109 |
+
style=style,
|
110 |
+
)
|
111 |
+
|
112 |
+
output = widgets.Output(
|
113 |
+
layout=widgets.Layout(width=widget_width, padding=padding)
|
114 |
+
)
|
115 |
+
toolbar_widget = widgets.VBox()
|
116 |
+
toolbar_widget.children = [toolbar_button]
|
117 |
+
toolbar_header = widgets.HBox()
|
118 |
+
toolbar_header.children = [close_button, toolbar_button]
|
119 |
+
toolbar_footer = widgets.VBox()
|
120 |
+
toolbar_footer.children = [
|
121 |
+
buttons,
|
122 |
+
label,
|
123 |
+
output,
|
124 |
+
]
|
125 |
+
|
126 |
+
def toolbar_btn_click(change):
|
127 |
+
if change["new"]:
|
128 |
+
close_button.value = False
|
129 |
+
toolbar_widget.children = [toolbar_header, toolbar_footer]
|
130 |
+
else:
|
131 |
+
if not close_button.value:
|
132 |
+
toolbar_widget.children = [toolbar_button]
|
133 |
+
|
134 |
+
toolbar_button.observe(toolbar_btn_click, "value")
|
135 |
+
|
136 |
+
def close_btn_click(change):
|
137 |
+
if change["new"]:
|
138 |
+
toolbar_button.value = False
|
139 |
+
self.toolbar_reset()
|
140 |
+
if (
|
141 |
+
self.tool_control is not None
|
142 |
+
and self.tool_control in self.controls
|
143 |
+
):
|
144 |
+
self.remove_control(self.tool_control)
|
145 |
+
self.tool_control = None
|
146 |
+
toolbar_widget.close()
|
147 |
+
|
148 |
+
close_button.observe(close_btn_click, "value")
|
149 |
+
|
150 |
+
def button_clicked(change):
|
151 |
+
if change["new"] == "Watershed":
|
152 |
+
label.value = "Click on the map to select a watershed"
|
153 |
+
|
154 |
+
def handle_interaction(**kwargs):
|
155 |
+
latlon = kwargs.get("coordinates")
|
156 |
+
if (
|
157 |
+
kwargs.get("type") == "click"
|
158 |
+
and buttons.value == "Watershed"
|
159 |
+
):
|
160 |
+
self.layers = self.layers[:n_layers]
|
161 |
+
self.default_style = {"cursor": "wait"}
|
162 |
+
clicked_point = ee.Geometry.Point(latlon[::-1])
|
163 |
+
selected = huc8.filterBounds(clicked_point)
|
164 |
+
watershed_bbox = selected.geometry().bounds()
|
165 |
+
huc_id = selected.first().get("huc8").getInfo()
|
166 |
+
self.addLayer(
|
167 |
+
selected.style(
|
168 |
+
**{"color": "ff0000ff", "fillColor": "00000000"}
|
169 |
+
),
|
170 |
+
{},
|
171 |
+
"HU8-" + huc_id,
|
172 |
+
)
|
173 |
+
|
174 |
+
label.value = f"Watershed HU8: {huc_id}"
|
175 |
+
|
176 |
+
hillshade_clip = hillshade.clipToCollection(selected)
|
177 |
+
self.addLayer(hillshade_clip, {}, "Hillshade")
|
178 |
+
depression_id = "users/giswqs/depressions/" + huc_id
|
179 |
+
depressions = ee.FeatureCollection(depression_id)
|
180 |
+
try:
|
181 |
+
self.addLayer(depressions, {}, "Depressions")
|
182 |
+
except Exception as e:
|
183 |
+
print(e)
|
184 |
+
nwi_id = "users/giswqs/NWI-HU8/HU8_" + huc_id + "_Wetlands"
|
185 |
+
try:
|
186 |
+
nwi = ee.FeatureCollection(nwi_id)
|
187 |
+
self.addLayer(nwi, {}, "NWI Vector")
|
188 |
+
except Exception as e:
|
189 |
+
print(e)
|
190 |
+
|
191 |
+
types = [
|
192 |
+
"Freshwater Forested/Shrub Wetland",
|
193 |
+
"Freshwater Emergent Wetland",
|
194 |
+
"Freshwater Pond",
|
195 |
+
"Estuarine and Marine Wetland",
|
196 |
+
"Riverine",
|
197 |
+
"Lake",
|
198 |
+
"Estuarine and Marine Deepwater",
|
199 |
+
"Other",
|
200 |
+
]
|
201 |
+
|
202 |
+
colors = [
|
203 |
+
"#008837",
|
204 |
+
"#7FC31C",
|
205 |
+
"#688CC0",
|
206 |
+
"#66C2A5",
|
207 |
+
"#0190BF",
|
208 |
+
"#13007C",
|
209 |
+
"#007C88",
|
210 |
+
"#B28653",
|
211 |
+
]
|
212 |
+
|
213 |
+
fillColor = [c + "A8" for c in colors]
|
214 |
+
nwi_color = geemap.ee_vector_style(
|
215 |
+
nwi,
|
216 |
+
column="WETLAND_TY",
|
217 |
+
labels=types,
|
218 |
+
fillColor=fillColor,
|
219 |
+
color="00000000",
|
220 |
+
)
|
221 |
+
|
222 |
+
self.addLayer(nwi_color, {}, "NWI Raster")
|
223 |
+
# sel_state = states.filterBounds(clicked_point).first().get('STUSPS').getInfo()
|
224 |
+
# asset = 'projects/sat-io/open-datasets/NHD/NHD_' + sel_state + '/'
|
225 |
+
# nhd_water = ee.FeatureCollection(asset + 'NHDWaterbody')
|
226 |
+
# nhd_area = ee.FeatureCollection(asset + 'NHDArea')
|
227 |
+
# nhd_flowline = ee.FeatureCollection(asset + 'NHDFlowline')
|
228 |
+
# nhd_line = ee.FeatureCollection(asset + 'NHDLine')
|
229 |
+
|
230 |
+
self.default_style = {"cursor": "default"}
|
231 |
+
|
232 |
+
self.on_interaction(handle_interaction)
|
233 |
+
|
234 |
+
with output:
|
235 |
+
output.clear_output()
|
236 |
+
print("Running ...")
|
237 |
+
elif change["new"] == "Inspector":
|
238 |
+
label.value = "Click on the map to inspect data"
|
239 |
+
output.clear_output()
|
240 |
+
elif change["new"] == "Deactivate":
|
241 |
+
buttons.value = None
|
242 |
+
label.value = "Click on the map to select a watershed"
|
243 |
+
|
244 |
+
# buttons.value = None
|
245 |
+
|
246 |
+
buttons.observe(button_clicked, "value")
|
247 |
+
buttons.value = "Watershed"
|
248 |
+
|
249 |
+
toolbar_button.value = opened
|
250 |
+
buttons_control = ipyleaflet.WidgetControl(
|
251 |
+
widget=toolbar_widget, position="topright"
|
252 |
+
)
|
253 |
+
|
254 |
+
if buttons_control not in self.controls:
|
255 |
+
self.add_control(buttons_control)
|
256 |
+
self.tool_control = buttons_control
|
257 |
+
|
258 |
+
add_buttons(self, opened=True)
|
259 |
+
|
260 |
+
|
261 |
+
@solara.component
|
262 |
+
def Page():
|
263 |
+
with solara.Column(style={"min-width": "500px"}):
|
264 |
+
Map.element( # type: ignore
|
265 |
+
zoom=zoom.value,
|
266 |
+
on_zoom=zoom.set,
|
267 |
+
center=center.value,
|
268 |
+
on_center=center.set,
|
269 |
+
height="750px",
|
270 |
+
# scroll_wheel_zoom=True,
|
271 |
+
# height="600px",
|
272 |
+
)
|
pages/02_lidar.py
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import geemap
|
2 |
+
import solara
|
3 |
+
import ee
|
4 |
+
|
5 |
+
zoom = solara.reactive(4)
|
6 |
+
center = solara.reactive((40, -100))
|
7 |
+
|
8 |
+
|
9 |
+
class Map(geemap.Map):
|
10 |
+
def __init__(self, **kwargs):
|
11 |
+
super().__init__(**kwargs)
|
12 |
+
# Add what you want below
|
13 |
+
self.add_data()
|
14 |
+
self.add_layer_manager(opened=False)
|
15 |
+
|
16 |
+
def add_data(self, **kwargs):
|
17 |
+
url = "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}"
|
18 |
+
self.add_tile_layer(url, name="Google Satellite", attribution="Google")
|
19 |
+
|
20 |
+
dem_wms = "https://elevation.nationalmap.gov/arcgis/services/3DEPElevation/ImageServer/WMSServer"
|
21 |
+
layer = "3DEPElevation:Hillshade Multidirectional"
|
22 |
+
self.add_wms_layer(
|
23 |
+
url=dem_wms, layers=layer, name="Hillshade", format="image/png", shown=True
|
24 |
+
)
|
25 |
+
|
26 |
+
huc12 = ee.FeatureCollection("USGS/WBD/2017/HUC12")
|
27 |
+
collection = ee.ImageCollection("USGS/3DEP/1m")
|
28 |
+
style = {"color": "0000ff88", "fillColor": "00000000", "width": 1}
|
29 |
+
|
30 |
+
self.add_layer(
|
31 |
+
collection, {"min": 0, "max": 4000, "palette": "terrain"}, "3DEP", True, 0.5
|
32 |
+
)
|
33 |
+
|
34 |
+
nwi_wms = "https://fwspublicservices.wiself.usgs.gov/wetlandsmapservice/services/Wetlands/MapServer/WMSServer"
|
35 |
+
self.add_wms_layer(url=nwi_wms, layers="1", name="NWI", format="image/png")
|
36 |
+
|
37 |
+
# self.add_layer(huc12, {}, "HU-12 Vector", False)
|
38 |
+
self.add_layer(
|
39 |
+
huc12.style(**style),
|
40 |
+
{},
|
41 |
+
"HU-12",
|
42 |
+
)
|
43 |
+
|
44 |
+
|
45 |
+
@solara.component
|
46 |
+
def Page():
|
47 |
+
with solara.Column(style={"min-width": "500px"}):
|
48 |
+
# solara components support reactive variables
|
49 |
+
# solara.SliderInt(label="Zoom level", value=zoom, min=1, max=20)
|
50 |
+
# using 3rd party widget library require wiring up the events manually
|
51 |
+
# using zoom.value and zoom.set
|
52 |
+
Map.element( # type: ignore
|
53 |
+
zoom=zoom.value,
|
54 |
+
on_zoom=zoom.set,
|
55 |
+
center=center.value,
|
56 |
+
on_center=center.set,
|
57 |
+
scroll_wheel_zoom=True,
|
58 |
+
height="750px",
|
59 |
+
)
|
60 |
+
# solara.Text(f"Zoom: {zoom.value}")
|
61 |
+
# solara.Text(f"Center: {center.value}")
|
pages/03_timelapse.py
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import ee
|
3 |
+
import geemap
|
4 |
+
import ipywidgets as widgets
|
5 |
+
from IPython.display import display
|
6 |
+
import solara
|
7 |
+
|
8 |
+
|
9 |
+
class Map(geemap.Map):
|
10 |
+
def __init__(self, **kwargs):
|
11 |
+
super().__init__(**kwargs)
|
12 |
+
self.add_basemap("Esri.WorldImagery")
|
13 |
+
self.add_gui("timelapse", basemap=None)
|
14 |
+
|
15 |
+
|
16 |
+
@solara.component
|
17 |
+
def Page():
|
18 |
+
with solara.Column(style={"min-width": "500px"}):
|
19 |
+
Map.element(
|
20 |
+
center=[20, -0],
|
21 |
+
zoom=2,
|
22 |
+
height="750px",
|
23 |
+
zoom_ctrl=False,
|
24 |
+
measure_ctrl=False,
|
25 |
+
)
|
pages/04_timeseries.py
ADDED
@@ -0,0 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import ee
|
3 |
+
import geemap
|
4 |
+
import ipywidgets as widgets
|
5 |
+
from IPython.display import display
|
6 |
+
import solara
|
7 |
+
from geemap import get_current_year, jslink_slider_label
|
8 |
+
|
9 |
+
|
10 |
+
class Map(geemap.Map):
|
11 |
+
def __init__(self, **kwargs):
|
12 |
+
super().__init__(**kwargs)
|
13 |
+
self.add_basemap("Esri.WorldImagery")
|
14 |
+
self.add_ts_gui(position="topright")
|
15 |
+
|
16 |
+
def clean_up(self):
|
17 |
+
if hasattr(self, "slider_ctrl") and self.slider_ctrl is not None:
|
18 |
+
self.remove(self.slider_ctrl)
|
19 |
+
delattr(self, "slider_ctrl")
|
20 |
+
|
21 |
+
layer = self.find_layer("Time series")
|
22 |
+
if layer is not None:
|
23 |
+
self.remove(layer)
|
24 |
+
layer = self.find_layer("Image X")
|
25 |
+
if layer is not None:
|
26 |
+
self.remove(layer)
|
27 |
+
|
28 |
+
draw_layer = self.find_layer("Drawn Features")
|
29 |
+
if draw_layer is not None:
|
30 |
+
self.remove(draw_layer)
|
31 |
+
|
32 |
+
def add_ts_gui(self, position="topright", **kwargs):
|
33 |
+
|
34 |
+
widget_width = "350px"
|
35 |
+
padding = "0px 0px 0px 5px" # upper, right, bottom, left
|
36 |
+
style = {"description_width": "initial"}
|
37 |
+
current_year = get_current_year()
|
38 |
+
|
39 |
+
collection = widgets.Dropdown(
|
40 |
+
options=[
|
41 |
+
"Landsat TM-ETM-OLI Surface Reflectance",
|
42 |
+
],
|
43 |
+
value="Landsat TM-ETM-OLI Surface Reflectance",
|
44 |
+
description="Collection:",
|
45 |
+
layout=widgets.Layout(width=widget_width, padding=padding),
|
46 |
+
style=style,
|
47 |
+
)
|
48 |
+
bands = widgets.Dropdown(
|
49 |
+
description="Bands:",
|
50 |
+
options=[
|
51 |
+
"Red/Green/Blue",
|
52 |
+
"NIR/Red/Green",
|
53 |
+
"SWIR2/SWIR1/NIR",
|
54 |
+
"NIR/SWIR1/Red",
|
55 |
+
"SWIR2/NIR/Red",
|
56 |
+
"SWIR2/SWIR1/Red",
|
57 |
+
"SWIR1/NIR/Blue",
|
58 |
+
"NIR/SWIR1/Blue",
|
59 |
+
"SWIR2/NIR/Green",
|
60 |
+
"SWIR1/NIR/Red",
|
61 |
+
],
|
62 |
+
value="SWIR1/NIR/Red",
|
63 |
+
style=style,
|
64 |
+
layout=widgets.Layout(width="195px", padding=padding),
|
65 |
+
)
|
66 |
+
|
67 |
+
frequency = widgets.Dropdown(
|
68 |
+
description="Frequency:",
|
69 |
+
options=["year", "quarter", "month"],
|
70 |
+
value="year",
|
71 |
+
style=style,
|
72 |
+
layout=widgets.Layout(width="150px", padding=padding),
|
73 |
+
)
|
74 |
+
|
75 |
+
start_year = widgets.IntSlider(
|
76 |
+
description="Start Year:",
|
77 |
+
value=1984,
|
78 |
+
min=1984,
|
79 |
+
max=current_year,
|
80 |
+
readout=False,
|
81 |
+
style=style,
|
82 |
+
layout=widgets.Layout(width="138px", padding=padding),
|
83 |
+
)
|
84 |
+
|
85 |
+
start_year_label = widgets.Label("1984")
|
86 |
+
jslink_slider_label(start_year, start_year_label)
|
87 |
+
|
88 |
+
end_year = widgets.IntSlider(
|
89 |
+
description="End Year:",
|
90 |
+
value=current_year,
|
91 |
+
min=1984,
|
92 |
+
max=current_year,
|
93 |
+
readout=False,
|
94 |
+
style=style,
|
95 |
+
layout=widgets.Layout(width="138px", padding=padding),
|
96 |
+
)
|
97 |
+
end_year_label = widgets.Label(str(current_year))
|
98 |
+
jslink_slider_label(end_year, end_year_label)
|
99 |
+
|
100 |
+
start_month = widgets.IntSlider(
|
101 |
+
description="Start Month:",
|
102 |
+
value=5,
|
103 |
+
min=1,
|
104 |
+
max=12,
|
105 |
+
readout=False,
|
106 |
+
style=style,
|
107 |
+
layout=widgets.Layout(width="145px", padding=padding),
|
108 |
+
)
|
109 |
+
|
110 |
+
start_month_label = widgets.Label(
|
111 |
+
"5",
|
112 |
+
layout=widgets.Layout(width="20px", padding=padding),
|
113 |
+
)
|
114 |
+
jslink_slider_label(start_month, start_month_label)
|
115 |
+
|
116 |
+
end_month = widgets.IntSlider(
|
117 |
+
description="End Month:",
|
118 |
+
value=10,
|
119 |
+
min=1,
|
120 |
+
max=12,
|
121 |
+
readout=False,
|
122 |
+
style=style,
|
123 |
+
layout=widgets.Layout(width="155px", padding=padding),
|
124 |
+
)
|
125 |
+
|
126 |
+
end_month_label = widgets.Label("10")
|
127 |
+
jslink_slider_label(end_month, end_month_label)
|
128 |
+
|
129 |
+
output = widgets.Output()
|
130 |
+
|
131 |
+
button_width = "113px"
|
132 |
+
apply_btn = widgets.Button(
|
133 |
+
description="Time slider",
|
134 |
+
button_style="primary",
|
135 |
+
tooltip="Click to create timeseries",
|
136 |
+
style=style,
|
137 |
+
layout=widgets.Layout(padding="0px", width=button_width),
|
138 |
+
)
|
139 |
+
|
140 |
+
split_btn = widgets.Button(
|
141 |
+
description="Split map",
|
142 |
+
button_style="primary",
|
143 |
+
tooltip="Click to create timeseries",
|
144 |
+
style=style,
|
145 |
+
layout=widgets.Layout(padding="0px", width=button_width),
|
146 |
+
)
|
147 |
+
|
148 |
+
reset_btn = widgets.Button(
|
149 |
+
description="Reset",
|
150 |
+
button_style="primary",
|
151 |
+
style=style,
|
152 |
+
layout=widgets.Layout(padding="0px", width=button_width),
|
153 |
+
)
|
154 |
+
|
155 |
+
vbox = widgets.VBox(
|
156 |
+
[
|
157 |
+
collection,
|
158 |
+
widgets.HBox([bands, frequency]),
|
159 |
+
widgets.HBox([start_year, start_year_label, end_year, end_year_label]),
|
160 |
+
widgets.HBox(
|
161 |
+
[start_month, start_month_label, end_month, end_month_label]
|
162 |
+
),
|
163 |
+
widgets.HBox([apply_btn, split_btn, reset_btn]),
|
164 |
+
output,
|
165 |
+
]
|
166 |
+
)
|
167 |
+
self.add_widget(vbox, position=position, add_header=True)
|
168 |
+
|
169 |
+
def apply_btn_click(change):
|
170 |
+
|
171 |
+
if hasattr(self, "slider_ctrl") and self.slider_ctrl is not None:
|
172 |
+
self.remove(self.slider_ctrl)
|
173 |
+
delattr(self, "slider_ctrl")
|
174 |
+
|
175 |
+
with output:
|
176 |
+
output.clear_output()
|
177 |
+
if self.user_roi is None:
|
178 |
+
output.append_stdout("Please draw a ROI first.")
|
179 |
+
else:
|
180 |
+
output.append_stdout("Creating time series...")
|
181 |
+
collection = geemap.landsat_timeseries(
|
182 |
+
roi=self.user_roi,
|
183 |
+
start_year=start_year.value,
|
184 |
+
end_year=end_year.value,
|
185 |
+
start_date=str(start_month.value).zfill(2) + "-01",
|
186 |
+
end_date=str(end_month.value).zfill(2) + "-01",
|
187 |
+
frequency=frequency.value,
|
188 |
+
)
|
189 |
+
vis_params = {
|
190 |
+
"bands": bands.value.split("/"),
|
191 |
+
"min": 0,
|
192 |
+
"max": 0.4,
|
193 |
+
}
|
194 |
+
|
195 |
+
if frequency.value == "year":
|
196 |
+
date_format = "YYYY"
|
197 |
+
elif frequency.value == "quarter":
|
198 |
+
date_format = "YYYY-MM"
|
199 |
+
elif frequency.value == "month":
|
200 |
+
date_format = "YYYY-MM"
|
201 |
+
|
202 |
+
self.add_time_slider(
|
203 |
+
collection,
|
204 |
+
region=self.user_roi,
|
205 |
+
vis_params=vis_params,
|
206 |
+
date_format=date_format,
|
207 |
+
)
|
208 |
+
self._draw_control.clear()
|
209 |
+
draw_layer = self.find_layer("Drawn Features")
|
210 |
+
if draw_layer is not None:
|
211 |
+
self.remove(draw_layer)
|
212 |
+
output.clear_output()
|
213 |
+
|
214 |
+
apply_btn.on_click(apply_btn_click)
|
215 |
+
|
216 |
+
def split_btn_click(change):
|
217 |
+
|
218 |
+
if hasattr(self, "slider_ctrl") and self.slider_ctrl is not None:
|
219 |
+
self.remove(self.slider_ctrl)
|
220 |
+
delattr(self, "slider_ctrl")
|
221 |
+
|
222 |
+
with output:
|
223 |
+
output.clear_output()
|
224 |
+
if self.user_roi is None:
|
225 |
+
output.append_stdout("Please draw a ROI first.")
|
226 |
+
else:
|
227 |
+
output.append_stdout("Creating time series...")
|
228 |
+
collection = geemap.landsat_timeseries(
|
229 |
+
roi=self.user_roi,
|
230 |
+
start_year=start_year.value,
|
231 |
+
end_year=end_year.value,
|
232 |
+
start_date=str(start_month.value).zfill(2) + "-01",
|
233 |
+
end_date=str(end_month.value).zfill(2) + "-01",
|
234 |
+
frequency=frequency.value,
|
235 |
+
)
|
236 |
+
vis_params = {
|
237 |
+
"bands": bands.value.split("/"),
|
238 |
+
"min": 0,
|
239 |
+
"max": 0.4,
|
240 |
+
}
|
241 |
+
|
242 |
+
if frequency.value == "year":
|
243 |
+
date_format = "YYYY"
|
244 |
+
dates = geemap.image_dates(collection, date_format).getInfo()
|
245 |
+
elif frequency.value == "quarter":
|
246 |
+
date_format = "YYYY-MM"
|
247 |
+
dates = geemap.image_dates(collection, date_format).getInfo()
|
248 |
+
elif frequency.value == "month":
|
249 |
+
date_format = "YYYY-MM"
|
250 |
+
dates = geemap.image_dates(collection, date_format).getInfo()
|
251 |
+
|
252 |
+
self.ts_inspector(
|
253 |
+
collection,
|
254 |
+
left_names=dates,
|
255 |
+
left_vis=vis_params,
|
256 |
+
add_close_button=True,
|
257 |
+
)
|
258 |
+
output.clear_output()
|
259 |
+
|
260 |
+
self._draw_control.clear()
|
261 |
+
draw_layer = self.find_layer("Drawn Features")
|
262 |
+
if draw_layer is not None:
|
263 |
+
self.remove(draw_layer)
|
264 |
+
|
265 |
+
split_btn.on_click(split_btn_click)
|
266 |
+
|
267 |
+
def reset_btn_click(change):
|
268 |
+
output.clear_output()
|
269 |
+
self.clean_up()
|
270 |
+
|
271 |
+
reset_btn.on_click(reset_btn_click)
|
272 |
+
|
273 |
+
|
274 |
+
@solara.component
|
275 |
+
def Page():
|
276 |
+
with solara.Column(style={"min-width": "500px"}):
|
277 |
+
Map.element(
|
278 |
+
center=[20, -0],
|
279 |
+
zoom=2,
|
280 |
+
height="750px",
|
281 |
+
zoom_ctrl=False,
|
282 |
+
measure_ctrl=False,
|
283 |
+
)
|
pages/05_jrc.py
ADDED
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import ee
|
3 |
+
import geemap
|
4 |
+
import ipywidgets as widgets
|
5 |
+
from IPython.display import display
|
6 |
+
import solara
|
7 |
+
|
8 |
+
|
9 |
+
class Map(geemap.Map):
|
10 |
+
def __init__(self, **kwargs):
|
11 |
+
super().__init__(**kwargs)
|
12 |
+
self.add_basemap("Esri.WorldImagery")
|
13 |
+
self.add_ee_data()
|
14 |
+
self.add_buttons(add_header=True)
|
15 |
+
|
16 |
+
def add_ee_data(self):
|
17 |
+
|
18 |
+
dataset = ee.Image("JRC/GSW1_4/GlobalSurfaceWater")
|
19 |
+
image = dataset.select(["occurrence"])
|
20 |
+
vis_params = {
|
21 |
+
"min": 0.0,
|
22 |
+
"max": 100.0,
|
23 |
+
"palette": ["ffffff", "ffbbbb", "0000ff"],
|
24 |
+
}
|
25 |
+
self.addLayer(image, vis_params, "Occurrence")
|
26 |
+
self.add_colorbar(
|
27 |
+
vis_params, label="Water occurrence (%)", layer_name="Occurrence"
|
28 |
+
)
|
29 |
+
|
30 |
+
def add_buttons(self, position="topright", **kwargs):
|
31 |
+
padding = "0px 5px 0px 5px"
|
32 |
+
widget = widgets.VBox(layout=widgets.Layout(padding=padding))
|
33 |
+
layout = widgets.Layout(width="auto")
|
34 |
+
style = {"description_width": "initial"}
|
35 |
+
hist_btn = widgets.Button(description="Occurrence", layout=layout)
|
36 |
+
bar_btn = widgets.Button(description="Monthly history", layout=layout)
|
37 |
+
reset_btn = widgets.Button(description="Reset", layout=layout)
|
38 |
+
scale = widgets.IntSlider(
|
39 |
+
min=30, max=1000, value=90, description="Scale", layout=layout, style=style
|
40 |
+
)
|
41 |
+
month_slider = widgets.IntRangeSlider(
|
42 |
+
description="Months",
|
43 |
+
value=[5, 10],
|
44 |
+
min=1,
|
45 |
+
max=12,
|
46 |
+
step=1,
|
47 |
+
layout=layout,
|
48 |
+
style=style,
|
49 |
+
)
|
50 |
+
widget.children = [
|
51 |
+
widgets.HBox([hist_btn, bar_btn, reset_btn]),
|
52 |
+
month_slider,
|
53 |
+
scale,
|
54 |
+
]
|
55 |
+
self.add_widget(widget, position=position, **kwargs)
|
56 |
+
output = widgets.Output()
|
57 |
+
self.add_widget(output, position="bottomleft", add_header=False)
|
58 |
+
|
59 |
+
def hist_btn_click(b):
|
60 |
+
region = self.user_roi
|
61 |
+
if region is not None:
|
62 |
+
output.clear_output()
|
63 |
+
output.append_stdout("Computing histogram...")
|
64 |
+
image = ee.Image("JRC/GSW1_4/GlobalSurfaceWater").select(["occurrence"])
|
65 |
+
self.default_style = {"cursor": "wait"}
|
66 |
+
hist = geemap.image_histogram(
|
67 |
+
image,
|
68 |
+
region,
|
69 |
+
scale=scale.value,
|
70 |
+
height=350,
|
71 |
+
width=550,
|
72 |
+
x_label="Water Occurrence (%)",
|
73 |
+
y_label="Pixel Count",
|
74 |
+
layout_args={
|
75 |
+
"title": dict(x=0.5),
|
76 |
+
"margin": dict(l=0, r=0, t=10, b=0),
|
77 |
+
},
|
78 |
+
return_df=False,
|
79 |
+
)
|
80 |
+
|
81 |
+
with output:
|
82 |
+
output.clear_output()
|
83 |
+
display(hist)
|
84 |
+
self.default_style = {"cursor": "default"}
|
85 |
+
else:
|
86 |
+
output.clear_output()
|
87 |
+
with output:
|
88 |
+
output.append_stdout("Please draw a region of interest first.")
|
89 |
+
|
90 |
+
hist_btn.on_click(hist_btn_click)
|
91 |
+
|
92 |
+
def bar_btn_click(b):
|
93 |
+
region = self.user_roi
|
94 |
+
if region is not None:
|
95 |
+
self.default_style = {"cursor": "wait"}
|
96 |
+
output.clear_output()
|
97 |
+
output.append_stdout("Computing monthly history...")
|
98 |
+
bar = geemap.jrc_hist_monthly_history(
|
99 |
+
region=region,
|
100 |
+
scale=scale.value,
|
101 |
+
height=350,
|
102 |
+
width=550,
|
103 |
+
layout_args={
|
104 |
+
"title": dict(x=0.5),
|
105 |
+
"margin": dict(l=0, r=0, t=10, b=0),
|
106 |
+
},
|
107 |
+
frequency="month",
|
108 |
+
start_month=month_slider.value[0],
|
109 |
+
end_month=month_slider.value[1],
|
110 |
+
denominator=1e4,
|
111 |
+
y_label="Area (ha)",
|
112 |
+
)
|
113 |
+
|
114 |
+
with output:
|
115 |
+
output.clear_output()
|
116 |
+
display(bar)
|
117 |
+
self.default_style = {"cursor": "default"}
|
118 |
+
else:
|
119 |
+
output.clear_output()
|
120 |
+
with output:
|
121 |
+
output.append_stdout("Please draw a region of interest first.")
|
122 |
+
|
123 |
+
bar_btn.on_click(bar_btn_click)
|
124 |
+
|
125 |
+
def reset_btn_click(b):
|
126 |
+
self._draw_control.clear()
|
127 |
+
output.clear_output()
|
128 |
+
|
129 |
+
reset_btn.on_click(reset_btn_click)
|
130 |
+
|
131 |
+
|
132 |
+
@solara.component
|
133 |
+
def Page():
|
134 |
+
with solara.Column(style={"min-width": "500px"}):
|
135 |
+
Map.element(
|
136 |
+
center=[20, -0],
|
137 |
+
zoom=2,
|
138 |
+
height="750px",
|
139 |
+
zoom_ctrl=False,
|
140 |
+
measure_ctrl=False,
|
141 |
+
)
|
pages/06_compare.py
ADDED
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import ee
|
3 |
+
import geemap
|
4 |
+
import ipywidgets as widgets
|
5 |
+
from IPython.display import display
|
6 |
+
import solara
|
7 |
+
from datetime import date
|
8 |
+
|
9 |
+
|
10 |
+
class Map(geemap.Map):
|
11 |
+
def __init__(self, **kwargs):
|
12 |
+
super().__init__(**kwargs)
|
13 |
+
self.add_basemap("Esri.WorldImagery")
|
14 |
+
self.add_gui_widget(add_header=True)
|
15 |
+
|
16 |
+
def clean_up(self):
|
17 |
+
|
18 |
+
layers = [
|
19 |
+
"Pre-event Image",
|
20 |
+
"Post-event Image",
|
21 |
+
"Pre-event NDWI",
|
22 |
+
"Post-event NDWI",
|
23 |
+
"Pre-event Water",
|
24 |
+
"Post-event Water",
|
25 |
+
"Disappeared Water",
|
26 |
+
"New Water",
|
27 |
+
]
|
28 |
+
for layer_name in layers:
|
29 |
+
layer = self.find_layer(layer_name)
|
30 |
+
if layer is not None:
|
31 |
+
self.remove(layer)
|
32 |
+
|
33 |
+
def add_gui_widget(self, position="topright", **kwargs):
|
34 |
+
|
35 |
+
widget = widgets.VBox(layout=widgets.Layout(padding="0px 5px 0px 5px"))
|
36 |
+
pre_widget = widgets.HBox()
|
37 |
+
post_widget = widgets.HBox()
|
38 |
+
layout = widgets.Layout(width="auto")
|
39 |
+
style = {"description_width": "initial"}
|
40 |
+
padding = "0px 5px 0px 5px"
|
41 |
+
pre_start_date = widgets.DatePicker(
|
42 |
+
description="Start",
|
43 |
+
value=date(2014, 1, 1),
|
44 |
+
style=style,
|
45 |
+
layout=widgets.Layout(padding=padding, width="160px"),
|
46 |
+
)
|
47 |
+
pre_end_date = widgets.DatePicker(
|
48 |
+
description="End",
|
49 |
+
value=date(2014, 12, 31),
|
50 |
+
style=style,
|
51 |
+
layout=widgets.Layout(padding=padding, width="160px"),
|
52 |
+
)
|
53 |
+
pre_cloud_cover = widgets.IntSlider(
|
54 |
+
description="Cloud",
|
55 |
+
min=0,
|
56 |
+
max=100,
|
57 |
+
value=25,
|
58 |
+
step=1,
|
59 |
+
readout=False,
|
60 |
+
style=style,
|
61 |
+
layout=widgets.Layout(padding=padding, width="130px"),
|
62 |
+
)
|
63 |
+
pre_cloud_label = widgets.Label(value=str(pre_cloud_cover.value))
|
64 |
+
geemap.jslink_slider_label(pre_cloud_cover, pre_cloud_label)
|
65 |
+
pre_widget.children = [
|
66 |
+
pre_start_date,
|
67 |
+
pre_end_date,
|
68 |
+
pre_cloud_cover,
|
69 |
+
pre_cloud_label,
|
70 |
+
]
|
71 |
+
post_start_date = widgets.DatePicker(
|
72 |
+
description="Start",
|
73 |
+
value=date(2024, 1, 1),
|
74 |
+
style=style,
|
75 |
+
layout=widgets.Layout(padding=padding, width="160px"),
|
76 |
+
)
|
77 |
+
post_end_date = widgets.DatePicker(
|
78 |
+
description="End",
|
79 |
+
value=date(2024, 12, 31),
|
80 |
+
style=style,
|
81 |
+
layout=widgets.Layout(padding=padding, width="160px"),
|
82 |
+
)
|
83 |
+
post_cloud_cover = widgets.IntSlider(
|
84 |
+
description="Cloud",
|
85 |
+
min=0,
|
86 |
+
max=100,
|
87 |
+
value=30,
|
88 |
+
step=1,
|
89 |
+
readout=False,
|
90 |
+
style=style,
|
91 |
+
layout=widgets.Layout(padding=padding, width="130px"),
|
92 |
+
)
|
93 |
+
post_cloud_label = widgets.Label(value=str(post_cloud_cover.value))
|
94 |
+
geemap.jslink_slider_label(post_cloud_cover, post_cloud_label)
|
95 |
+
post_widget.children = [
|
96 |
+
post_start_date,
|
97 |
+
post_end_date,
|
98 |
+
post_cloud_cover,
|
99 |
+
post_cloud_label,
|
100 |
+
]
|
101 |
+
|
102 |
+
apply_btn = widgets.Button(description="Apply", layout=layout)
|
103 |
+
reset_btn = widgets.Button(description="Reset", layout=layout)
|
104 |
+
buttons = widgets.HBox([apply_btn, reset_btn])
|
105 |
+
output = widgets.Output()
|
106 |
+
|
107 |
+
use_split = widgets.Checkbox(
|
108 |
+
value=False,
|
109 |
+
description="Split map",
|
110 |
+
style=style,
|
111 |
+
layout=widgets.Layout(padding=padding, width="100px"),
|
112 |
+
)
|
113 |
+
|
114 |
+
use_ndwi = widgets.Checkbox(
|
115 |
+
value=False,
|
116 |
+
description="Compute NDWI",
|
117 |
+
style=style,
|
118 |
+
layout=widgets.Layout(padding=padding, width="160px"),
|
119 |
+
)
|
120 |
+
|
121 |
+
ndwi_threhold = widgets.FloatSlider(
|
122 |
+
description="Threshold",
|
123 |
+
min=-1,
|
124 |
+
max=1,
|
125 |
+
value=0,
|
126 |
+
step=0.05,
|
127 |
+
readout=True,
|
128 |
+
style=style,
|
129 |
+
layout=widgets.Layout(padding=padding, width="230px"),
|
130 |
+
)
|
131 |
+
|
132 |
+
options = widgets.HBox(
|
133 |
+
[
|
134 |
+
use_split,
|
135 |
+
use_ndwi,
|
136 |
+
ndwi_threhold,
|
137 |
+
]
|
138 |
+
)
|
139 |
+
|
140 |
+
widget.children = [pre_widget, post_widget, options, buttons, output]
|
141 |
+
self.add_widget(widget, position=position, **kwargs)
|
142 |
+
|
143 |
+
def apply_btn_click(b):
|
144 |
+
|
145 |
+
marker_layer = self.find_layer("Search location")
|
146 |
+
if marker_layer is not None:
|
147 |
+
self.remove(marker_layer)
|
148 |
+
self.clean_up()
|
149 |
+
|
150 |
+
if self.user_roi is None:
|
151 |
+
output.clear_output()
|
152 |
+
output.append_stdout("Please draw a ROI first.")
|
153 |
+
elif (
|
154 |
+
pre_start_date.value is None
|
155 |
+
or pre_end_date.value is None
|
156 |
+
or post_start_date.value is None
|
157 |
+
or post_end_date.value is None
|
158 |
+
):
|
159 |
+
output.clear_output()
|
160 |
+
output.append_stdout("Please select start and end dates.")
|
161 |
+
|
162 |
+
elif self.user_roi is not None:
|
163 |
+
output.clear_output()
|
164 |
+
output.append_stdout("Computing... Please wait.")
|
165 |
+
roi = ee.FeatureCollection(self.user_roi)
|
166 |
+
vis_params = {"bands": ["B6", "B5", "B4"], "min": 0, "max": 0.4}
|
167 |
+
if pre_start_date.value.strftime("%Y-%m-%d") < "2013-04-11":
|
168 |
+
pre_col = geemap.landsat_timeseries(
|
169 |
+
roi,
|
170 |
+
start_year=pre_start_date.value.year,
|
171 |
+
end_year=pre_end_date.value.year,
|
172 |
+
).select(["SWIR1", "NIR", "Red", "Green"], ["B6", "B5", "B4", "B3"])
|
173 |
+
else:
|
174 |
+
pre_col = (
|
175 |
+
ee.ImageCollection("NASA/HLS/HLSL30/v002")
|
176 |
+
.filterBounds(roi)
|
177 |
+
.filterDate(
|
178 |
+
pre_start_date.value.strftime("%Y-%m-%d"),
|
179 |
+
pre_end_date.value.strftime("%Y-%m-%d"),
|
180 |
+
)
|
181 |
+
.filter(ee.Filter.lt("CLOUD_COVERAGE", pre_cloud_cover.value))
|
182 |
+
)
|
183 |
+
|
184 |
+
if post_start_date.value.strftime("%Y-%m-%d") < "2013-04-11":
|
185 |
+
post_col = geemap.landsat_timeseries(
|
186 |
+
roi,
|
187 |
+
start_year=post_start_date.value.year,
|
188 |
+
end_year=post_end_date.value.year,
|
189 |
+
).select(["SWIR1", "NIR", "Red", "Green"], ["B6", "B5", "B4", "B3"])
|
190 |
+
else:
|
191 |
+
post_col = (
|
192 |
+
ee.ImageCollection("NASA/HLS/HLSL30/v002")
|
193 |
+
.filterBounds(roi)
|
194 |
+
.filterDate(
|
195 |
+
post_start_date.value.strftime("%Y-%m-%d"),
|
196 |
+
post_end_date.value.strftime("%Y-%m-%d"),
|
197 |
+
)
|
198 |
+
.filter(ee.Filter.lt("CLOUD_COVERAGE", post_cloud_cover.value))
|
199 |
+
)
|
200 |
+
|
201 |
+
pre_img = pre_col.median().clip(roi)
|
202 |
+
post_img = post_col.median().clip(roi)
|
203 |
+
|
204 |
+
if use_split.value:
|
205 |
+
left_layer = geemap.ee_tile_layer(
|
206 |
+
pre_img, vis_params, "Pre-event Image"
|
207 |
+
)
|
208 |
+
right_layer = geemap.ee_tile_layer(
|
209 |
+
post_img, vis_params, "Post-event Image"
|
210 |
+
)
|
211 |
+
self.split_map(
|
212 |
+
left_layer,
|
213 |
+
right_layer,
|
214 |
+
add_close_button=True,
|
215 |
+
left_label="Pre-event",
|
216 |
+
right_label="Post-event",
|
217 |
+
)
|
218 |
+
else:
|
219 |
+
pre_img = pre_col.median().clip(roi)
|
220 |
+
post_img = post_col.median().clip(roi)
|
221 |
+
self.add_layer(pre_img, vis_params, "Pre-event Image")
|
222 |
+
self.add_layer(post_img, vis_params, "Post-event Image")
|
223 |
+
|
224 |
+
if use_ndwi.value and (not use_split.value):
|
225 |
+
pre_ndwi = pre_img.normalizedDifference(["B3", "B6"]).rename("NDWI")
|
226 |
+
post_ndwi = post_img.normalizedDifference(["B3", "B6"]).rename(
|
227 |
+
"NDWI"
|
228 |
+
)
|
229 |
+
ndwi_vis = {"min": -1, "max": 1, "palette": "ndwi"}
|
230 |
+
self.add_layer(pre_ndwi, ndwi_vis, "Pre-event NDWI", False)
|
231 |
+
self.add_layer(post_ndwi, ndwi_vis, "Post-event NDWI", False)
|
232 |
+
|
233 |
+
pre_water = pre_ndwi.gt(ndwi_threhold.value)
|
234 |
+
post_water = post_ndwi.gt(ndwi_threhold.value)
|
235 |
+
self.add_layer(
|
236 |
+
pre_water.selfMask(), {"palette": "blue"}, "Pre-event Water"
|
237 |
+
)
|
238 |
+
self.add_layer(
|
239 |
+
post_water.selfMask(), {"palette": "red"}, "Post-event Water"
|
240 |
+
)
|
241 |
+
new_water = post_water.subtract(pre_water).gt(0)
|
242 |
+
disappear_water = pre_water.subtract(post_water).gt(0)
|
243 |
+
self.add_layer(
|
244 |
+
disappear_water.selfMask(),
|
245 |
+
{"palette": "brown"},
|
246 |
+
"Disappeared Water",
|
247 |
+
)
|
248 |
+
self.add_layer(
|
249 |
+
new_water.selfMask(), {"palette": "cyan"}, "New Water"
|
250 |
+
)
|
251 |
+
|
252 |
+
with output:
|
253 |
+
output.clear_output()
|
254 |
+
|
255 |
+
output.clear_output()
|
256 |
+
|
257 |
+
apply_btn.on_click(apply_btn_click)
|
258 |
+
|
259 |
+
def reset_btn_click(b):
|
260 |
+
self.clean_up()
|
261 |
+
self._draw_control.clear()
|
262 |
+
draw_layer = self.find_layer("Drawn Features")
|
263 |
+
if draw_layer is not None:
|
264 |
+
self.remove(draw_layer)
|
265 |
+
output.clear_output()
|
266 |
+
|
267 |
+
reset_btn.on_click(reset_btn_click)
|
268 |
+
|
269 |
+
|
270 |
+
@solara.component
|
271 |
+
def Page():
|
272 |
+
with solara.Column(style={"min-width": "500px"}):
|
273 |
+
Map.element(
|
274 |
+
center=[20, -0],
|
275 |
+
zoom=2,
|
276 |
+
height="750px",
|
277 |
+
zoom_ctrl=False,
|
278 |
+
measure_ctrl=False,
|
279 |
+
)
|
requirements.txt
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
git+https://github.com/gee-community/geemap.git
|
2 |
+
git+https://github.com/opengeos/leafmap.git
|
3 |
+
solara
|
4 |
+
geopandas
|