| 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, |
| ) |
|
|