|
import ee |
|
import geemap |
|
import ipywidgets as widgets |
|
import solara |
|
from datetime import date |
|
|
|
|
|
class Map(geemap.Map): |
|
def __init__(self, **kwargs): |
|
super().__init__(**kwargs) |
|
self.add_basemap("Esri.WorldImagery") |
|
self.add_gui_widget(add_header=True) |
|
|
|
def clean_up(self): |
|
|
|
layers = [ |
|
"Pre-event Image", |
|
"Post-event Image", |
|
"Pre-event NDWI", |
|
"Post-event NDWI", |
|
"Pre-event Water", |
|
"Post-event Water", |
|
"Disappeared Water", |
|
"New Water", |
|
] |
|
for layer_name in layers: |
|
layer = self.find_layer(layer_name) |
|
if layer is not None: |
|
self.remove(layer) |
|
|
|
def add_gui_widget(self, position="topright", **kwargs): |
|
|
|
widget = widgets.VBox(layout=widgets.Layout(padding="0px 5px 0px 5px")) |
|
pre_widget = widgets.HBox() |
|
post_widget = widgets.HBox() |
|
layout = widgets.Layout(width="auto") |
|
style = {"description_width": "initial"} |
|
padding = "0px 5px 0px 5px" |
|
pre_start_date = widgets.DatePicker( |
|
description="Start", |
|
value=date(2014, 1, 1), |
|
style=style, |
|
layout=widgets.Layout(padding=padding, width="160px"), |
|
) |
|
pre_end_date = widgets.DatePicker( |
|
description="End", |
|
value=date(2014, 12, 31), |
|
style=style, |
|
layout=widgets.Layout(padding=padding, width="160px"), |
|
) |
|
pre_cloud_cover = widgets.IntSlider( |
|
description="Cloud", |
|
min=0, |
|
max=100, |
|
value=25, |
|
step=1, |
|
readout=False, |
|
style=style, |
|
layout=widgets.Layout(padding=padding, width="130px"), |
|
) |
|
pre_cloud_label = widgets.Label(value=str(pre_cloud_cover.value)) |
|
geemap.jslink_slider_label(pre_cloud_cover, pre_cloud_label) |
|
pre_widget.children = [ |
|
pre_start_date, |
|
pre_end_date, |
|
pre_cloud_cover, |
|
pre_cloud_label, |
|
] |
|
post_start_date = widgets.DatePicker( |
|
description="Start", |
|
value=date(2024, 1, 1), |
|
style=style, |
|
layout=widgets.Layout(padding=padding, width="160px"), |
|
) |
|
post_end_date = widgets.DatePicker( |
|
description="End", |
|
value=date(2024, 12, 31), |
|
style=style, |
|
layout=widgets.Layout(padding=padding, width="160px"), |
|
) |
|
post_cloud_cover = widgets.IntSlider( |
|
description="Cloud", |
|
min=0, |
|
max=100, |
|
value=30, |
|
step=1, |
|
readout=False, |
|
style=style, |
|
layout=widgets.Layout(padding=padding, width="130px"), |
|
) |
|
post_cloud_label = widgets.Label(value=str(post_cloud_cover.value)) |
|
geemap.jslink_slider_label(post_cloud_cover, post_cloud_label) |
|
post_widget.children = [ |
|
post_start_date, |
|
post_end_date, |
|
post_cloud_cover, |
|
post_cloud_label, |
|
] |
|
|
|
apply_btn = widgets.Button(description="Apply", layout=layout) |
|
reset_btn = widgets.Button(description="Reset", layout=layout) |
|
buttons = widgets.HBox([apply_btn, reset_btn]) |
|
output = widgets.Output() |
|
|
|
use_split = widgets.Checkbox( |
|
value=False, |
|
description="Split map", |
|
style=style, |
|
layout=widgets.Layout(padding=padding, width="100px"), |
|
) |
|
|
|
use_ndwi = widgets.Checkbox( |
|
value=False, |
|
description="Compute NDWI", |
|
style=style, |
|
layout=widgets.Layout(padding=padding, width="160px"), |
|
) |
|
|
|
ndwi_threhold = widgets.FloatSlider( |
|
description="Threshold", |
|
min=-1, |
|
max=1, |
|
value=0, |
|
step=0.05, |
|
readout=True, |
|
style=style, |
|
layout=widgets.Layout(padding=padding, width="230px"), |
|
) |
|
|
|
options = widgets.HBox( |
|
[ |
|
use_split, |
|
use_ndwi, |
|
ndwi_threhold, |
|
] |
|
) |
|
|
|
widget.children = [pre_widget, post_widget, options, buttons, output] |
|
self.add_widget(widget, position=position, **kwargs) |
|
|
|
def apply_btn_click(b): |
|
|
|
marker_layer = self.find_layer("Search location") |
|
if marker_layer is not None: |
|
self.remove(marker_layer) |
|
self.clean_up() |
|
|
|
if self.user_roi is None: |
|
output.clear_output() |
|
output.append_stdout("Please draw a ROI first.") |
|
elif ( |
|
pre_start_date.value is None |
|
or pre_end_date.value is None |
|
or post_start_date.value is None |
|
or post_end_date.value is None |
|
): |
|
output.clear_output() |
|
output.append_stdout("Please select start and end dates.") |
|
|
|
elif self.user_roi is not None: |
|
output.clear_output() |
|
output.append_stdout("Computing... Please wait.") |
|
roi = ee.FeatureCollection(self.user_roi) |
|
vis_params = {"bands": ["B6", "B5", "B4"], "min": 0, "max": 0.4} |
|
if pre_start_date.value.strftime("%Y-%m-%d") < "2013-04-11": |
|
pre_col = geemap.landsat_timeseries( |
|
roi, |
|
start_year=pre_start_date.value.year, |
|
end_year=pre_end_date.value.year, |
|
).select(["SWIR1", "NIR", "Red", "Green"], ["B6", "B5", "B4", "B3"]) |
|
else: |
|
pre_col = ( |
|
ee.ImageCollection("NASA/HLS/HLSL30/v002") |
|
.filterBounds(roi) |
|
.filterDate( |
|
pre_start_date.value.strftime("%Y-%m-%d"), |
|
pre_end_date.value.strftime("%Y-%m-%d"), |
|
) |
|
.filter(ee.Filter.lt("CLOUD_COVERAGE", pre_cloud_cover.value)) |
|
) |
|
|
|
if post_start_date.value.strftime("%Y-%m-%d") < "2013-04-11": |
|
post_col = geemap.landsat_timeseries( |
|
roi, |
|
start_year=post_start_date.value.year, |
|
end_year=post_end_date.value.year, |
|
).select(["SWIR1", "NIR", "Red", "Green"], ["B6", "B5", "B4", "B3"]) |
|
else: |
|
post_col = ( |
|
ee.ImageCollection("NASA/HLS/HLSL30/v002") |
|
.filterBounds(roi) |
|
.filterDate( |
|
post_start_date.value.strftime("%Y-%m-%d"), |
|
post_end_date.value.strftime("%Y-%m-%d"), |
|
) |
|
.filter(ee.Filter.lt("CLOUD_COVERAGE", post_cloud_cover.value)) |
|
) |
|
|
|
pre_img = pre_col.median().clip(roi) |
|
post_img = post_col.median().clip(roi) |
|
|
|
if use_split.value: |
|
left_layer = geemap.ee_tile_layer( |
|
pre_img, vis_params, "Pre-event Image" |
|
) |
|
right_layer = geemap.ee_tile_layer( |
|
post_img, vis_params, "Post-event Image" |
|
) |
|
self.split_map( |
|
left_layer, |
|
right_layer, |
|
add_close_button=True, |
|
left_label="Pre-event", |
|
right_label="Post-event", |
|
) |
|
else: |
|
pre_img = pre_col.median().clip(roi) |
|
post_img = post_col.median().clip(roi) |
|
self.add_layer(pre_img, vis_params, "Pre-event Image") |
|
self.add_layer(post_img, vis_params, "Post-event Image") |
|
|
|
if use_ndwi.value and (not use_split.value): |
|
pre_ndwi = pre_img.normalizedDifference(["B3", "B6"]).rename("NDWI") |
|
post_ndwi = post_img.normalizedDifference(["B3", "B6"]).rename( |
|
"NDWI" |
|
) |
|
ndwi_vis = {"min": -1, "max": 1, "palette": "ndwi"} |
|
self.add_layer(pre_ndwi, ndwi_vis, "Pre-event NDWI", False) |
|
self.add_layer(post_ndwi, ndwi_vis, "Post-event NDWI", False) |
|
|
|
pre_water = pre_ndwi.gt(ndwi_threhold.value) |
|
post_water = post_ndwi.gt(ndwi_threhold.value) |
|
self.add_layer( |
|
pre_water.selfMask(), {"palette": "blue"}, "Pre-event Water" |
|
) |
|
self.add_layer( |
|
post_water.selfMask(), {"palette": "red"}, "Post-event Water" |
|
) |
|
new_water = post_water.subtract(pre_water).gt(0) |
|
disappear_water = pre_water.subtract(post_water).gt(0) |
|
self.add_layer( |
|
disappear_water.selfMask(), |
|
{"palette": "brown"}, |
|
"Disappeared Water", |
|
) |
|
self.add_layer( |
|
new_water.selfMask(), {"palette": "cyan"}, "New Water" |
|
) |
|
|
|
with output: |
|
output.clear_output() |
|
|
|
output.clear_output() |
|
|
|
apply_btn.on_click(apply_btn_click) |
|
|
|
def reset_btn_click(b): |
|
self.clean_up() |
|
self._draw_control.clear() |
|
draw_layer = self.find_layer("Drawn Features") |
|
if draw_layer is not None: |
|
self.remove(draw_layer) |
|
output.clear_output() |
|
|
|
reset_btn.on_click(reset_btn_click) |
|
|
|
|
|
@solara.component |
|
def Page(): |
|
with solara.Column(style={"min-width": "500px"}): |
|
Map.element( |
|
center=[20, -0], |
|
zoom=2, |
|
height="750px", |
|
zoom_ctrl=False, |
|
measure_ctrl=False, |
|
) |
|
|