From 7007026ba64aaf6ac645a72191636c5baa0bd6bb Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Wed, 24 Jul 2024 00:10:45 -0400 Subject: [PATCH] Add timeseries app --- pages/02_timeseries.py | 283 +++++++++++++++++++++++++ pages/{02_jrc.py => 03_jrc.py} | 0 pages/03_plotting.py | 41 ---- pages/{03_landsat.py => 04_compare.py} | 0 pages/04_split_map.py | 59 ------ 5 files changed, 283 insertions(+), 100 deletions(-) create mode 100644 pages/02_timeseries.py rename pages/{02_jrc.py => 03_jrc.py} (100%) delete mode 100644 pages/03_plotting.py rename pages/{03_landsat.py => 04_compare.py} (100%) delete mode 100644 pages/04_split_map.py diff --git a/pages/02_timeseries.py b/pages/02_timeseries.py new file mode 100644 index 0000000..25588a7 --- /dev/null +++ b/pages/02_timeseries.py @@ -0,0 +1,283 @@ +import os +import ee +import geemap +import ipywidgets as widgets +from IPython.display import display +import solara +from geemap import get_current_year, jslink_slider_label + + +class Map(geemap.Map): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.add_basemap("Esri.WorldImagery") + self.add_ts_gui(position="topright") + + def clean_up(self): + if hasattr(self, "slider_ctrl") and self.slider_ctrl is not None: + self.remove(self.slider_ctrl) + delattr(self, "slider_ctrl") + + layer = self.find_layer("Time series") + if layer is not None: + self.remove(layer) + layer = self.find_layer("Image X") + if layer is not None: + self.remove(layer) + + draw_layer = self.find_layer("Drawn Features") + if draw_layer is not None: + self.remove(draw_layer) + + def add_ts_gui(self, position="topright", **kwargs): + + widget_width = "350px" + padding = "0px 0px 0px 5px" # upper, right, bottom, left + style = {"description_width": "initial"} + current_year = get_current_year() + + collection = widgets.Dropdown( + options=[ + "Landsat TM-ETM-OLI Surface Reflectance", + ], + value="Landsat TM-ETM-OLI Surface Reflectance", + description="Collection:", + layout=widgets.Layout(width=widget_width, padding=padding), + style=style, + ) + bands = widgets.Dropdown( + description="Bands:", + options=[ + "Red/Green/Blue", + "NIR/Red/Green", + "SWIR2/SWIR1/NIR", + "NIR/SWIR1/Red", + "SWIR2/NIR/Red", + "SWIR2/SWIR1/Red", + "SWIR1/NIR/Blue", + "NIR/SWIR1/Blue", + "SWIR2/NIR/Green", + "SWIR1/NIR/Red", + ], + value="SWIR1/NIR/Red", + style=style, + layout=widgets.Layout(width="195px", padding=padding), + ) + + frequency = widgets.Dropdown( + description="Frequency:", + options=["year", "quarter", "month"], + value="year", + style=style, + layout=widgets.Layout(width="150px", padding=padding), + ) + + start_year = widgets.IntSlider( + description="Start Year:", + value=1984, + min=1984, + max=current_year, + readout=False, + style=style, + layout=widgets.Layout(width="138px", padding=padding), + ) + + start_year_label = widgets.Label("1984") + jslink_slider_label(start_year, start_year_label) + + end_year = widgets.IntSlider( + description="End Year:", + value=current_year, + min=1984, + max=current_year, + readout=False, + style=style, + layout=widgets.Layout(width="138px", padding=padding), + ) + end_year_label = widgets.Label(str(current_year)) + jslink_slider_label(end_year, end_year_label) + + start_month = widgets.IntSlider( + description="Start Month:", + value=5, + min=1, + max=12, + readout=False, + style=style, + layout=widgets.Layout(width="145px", padding=padding), + ) + + start_month_label = widgets.Label( + "5", + layout=widgets.Layout(width="20px", padding=padding), + ) + jslink_slider_label(start_month, start_month_label) + + end_month = widgets.IntSlider( + description="End Month:", + value=10, + min=1, + max=12, + readout=False, + style=style, + layout=widgets.Layout(width="155px", padding=padding), + ) + + end_month_label = widgets.Label("10") + jslink_slider_label(end_month, end_month_label) + + output = widgets.Output() + + button_width = "113px" + apply_btn = widgets.Button( + description="Time slider", + button_style="primary", + tooltip="Click to create timeseries", + style=style, + layout=widgets.Layout(padding="0px", width=button_width), + ) + + split_btn = widgets.Button( + description="Split map", + button_style="primary", + tooltip="Click to create timeseries", + style=style, + layout=widgets.Layout(padding="0px", width=button_width), + ) + + reset_btn = widgets.Button( + description="Reset", + button_style="primary", + style=style, + layout=widgets.Layout(padding="0px", width=button_width), + ) + + vbox = widgets.VBox( + [ + collection, + widgets.HBox([bands, frequency]), + widgets.HBox([start_year, start_year_label, end_year, end_year_label]), + widgets.HBox( + [start_month, start_month_label, end_month, end_month_label] + ), + widgets.HBox([apply_btn, split_btn, reset_btn]), + output, + ] + ) + self.add_widget(vbox, position=position, add_header=True) + + def apply_btn_click(change): + + if hasattr(self, "slider_ctrl") and self.slider_ctrl is not None: + self.remove(self.slider_ctrl) + delattr(self, "slider_ctrl") + + with output: + output.clear_output() + if self.user_roi is None: + output.append_stdout("Please draw a ROI first.") + else: + output.append_stdout("Creating time series...") + collection = geemap.landsat_timeseries( + roi=self.user_roi, + start_year=start_year.value, + end_year=end_year.value, + start_date=str(start_month.value).zfill(2) + "-01", + end_date=str(end_month.value).zfill(2) + "-01", + frequency=frequency.value, + ) + vis_params = { + "bands": bands.value.split("/"), + "min": 0, + "max": 0.4, + } + + if frequency.value == "year": + date_format = "YYYY" + elif frequency.value == "quarter": + date_format = "YYYY-MM" + elif frequency.value == "month": + date_format = "YYYY-MM" + + self.add_time_slider( + collection, + region=self.user_roi, + vis_params=vis_params, + date_format=date_format, + ) + self._draw_control.clear() + draw_layer = self.find_layer("Drawn Features") + if draw_layer is not None: + self.remove(draw_layer) + output.clear_output() + + apply_btn.on_click(apply_btn_click) + + def split_btn_click(change): + + if hasattr(self, "slider_ctrl") and self.slider_ctrl is not None: + self.remove(self.slider_ctrl) + delattr(self, "slider_ctrl") + + with output: + output.clear_output() + if self.user_roi is None: + output.append_stdout("Please draw a ROI first.") + else: + output.append_stdout("Creating time series...") + collection = geemap.landsat_timeseries( + roi=self.user_roi, + start_year=start_year.value, + end_year=end_year.value, + start_date=str(start_month.value).zfill(2) + "-01", + end_date=str(end_month.value).zfill(2) + "-01", + frequency=frequency.value, + ) + vis_params = { + "bands": bands.value.split("/"), + "min": 0, + "max": 0.4, + } + + if frequency.value == "year": + date_format = "YYYY" + dates = geemap.image_dates(collection, date_format).getInfo() + elif frequency.value == "quarter": + date_format = "YYYY-MM" + dates = geemap.image_dates(collection, date_format).getInfo() + elif frequency.value == "month": + date_format = "YYYY-MM" + dates = geemap.image_dates(collection, date_format).getInfo() + + self.ts_inspector( + collection, + left_names=dates, + left_vis=vis_params, + add_close_button=True, + ) + output.clear_output() + + self._draw_control.clear() + draw_layer = self.find_layer("Drawn Features") + if draw_layer is not None: + self.remove(draw_layer) + + split_btn.on_click(split_btn_click) + + def reset_btn_click(change): + output.clear_output() + self.clean_up() + + 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, + ) diff --git a/pages/02_jrc.py b/pages/03_jrc.py similarity index 100% rename from pages/02_jrc.py rename to pages/03_jrc.py diff --git a/pages/03_plotting.py b/pages/03_plotting.py deleted file mode 100644 index 9d9dbfe..0000000 --- a/pages/03_plotting.py +++ /dev/null @@ -1,41 +0,0 @@ -import os -import ee -import geemap - -import solara - - -class Map(geemap.Map): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.add_ee_data() - self.add_plot_gui() - - def add_ee_data(self): - landsat7 = ee.Image("LANDSAT/LE7_TOA_5YEAR/1999_2003").select( - ["B1", "B2", "B3", "B4", "B5", "B7"] - ) - - landsat_vis = {"bands": ["B4", "B3", "B2"], "gamma": 1.4} - self.addLayer(landsat7, landsat_vis, "Landsat") - - hyperion = ee.ImageCollection("EO1/HYPERION").filter( - ee.Filter.date("2016-01-01", "2017-03-01") - ) - - hyperion_vis = { - "min": 1000.0, - "max": 14000.0, - "gamma": 2.5, - } - self.addLayer(hyperion, hyperion_vis, "Hyperion") - - -@solara.component -def Page(): - with solara.Column(style={"min-width": "500px"}): - Map.element( - center=[40, -100], - zoom=4, - height="750px", - ) diff --git a/pages/03_landsat.py b/pages/04_compare.py similarity index 100% rename from pages/03_landsat.py rename to pages/04_compare.py diff --git a/pages/04_split_map.py b/pages/04_split_map.py deleted file mode 100644 index 1b6f1b6..0000000 --- a/pages/04_split_map.py +++ /dev/null @@ -1,59 +0,0 @@ -import os -import ee -import geemap - -import solara - - -class Map(geemap.Map): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.add_ee_data() - - def add_ee_data(self): - # Select the eight NLCD epochs after 2000. - years = ["2001", "2004", "2006", "2008", "2011", "2013", "2016", "2019"] - - # Get an NLCD image by year. - def getNLCD(year): - # Import the NLCD collection. - dataset = ee.ImageCollection("USGS/NLCD_RELEASES/2019_REL/NLCD") - - # Filter the collection by year. - nlcd = dataset.filter(ee.Filter.eq("system:index", year)).first() - - # Select the land cover band. - landcover = nlcd.select("landcover") - return landcover - - ## Create an NLCD image collection for the selected years. - collection = ee.ImageCollection(ee.List(years).map(lambda year: getNLCD(year))) - - # Create a list of labels to populate the dropdown list. - labels = [f"NLCD {year}" for year in years] - - # Add a split-panel map for visualizing NLCD land cover change. - self.ts_inspector( - left_ts=collection, - right_ts=collection, - left_names=labels, - right_names=labels, - ) - - # Add the NLCD legend to the map. - self.add_legend( - title="NLCD Land Cover Type", - builtin_legend="NLCD", - height="460px", - add_header=False, - ) - - -@solara.component -def Page(): - with solara.Column(style={"min-width": "500px"}): - Map.element( - center=[40, -100], - zoom=4, - height="750px", - )