giswqs commited on
Commit
77fd6cb
·
1 Parent(s): 3f47af8

Initial commit

Browse files
.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