From 5e75013c8ae422ab0b84d5b633eae69c18db352a Mon Sep 17 00:00:00 2001 From: Emma Paz <emma@developmentseed.org> Date: Tue, 25 Jul 2023 13:46:35 +0000 Subject: [PATCH 1/4] add filter component and functionality --- stac_ipyleaflet/stac_discovery/stac.py | 73 ++- stac_ipyleaflet/stac_discovery/stac_widget.py | 592 ++++++++++++------ 2 files changed, 472 insertions(+), 193 deletions(-) diff --git a/stac_ipyleaflet/stac_discovery/stac.py b/stac_ipyleaflet/stac_discovery/stac.py index 2975ec3..1e652c1 100644 --- a/stac_ipyleaflet/stac_discovery/stac.py +++ b/stac_ipyleaflet/stac_discovery/stac.py @@ -5,9 +5,11 @@ from stac_ipyleaflet.constants import RESCALE from typing import TypedDict, Optional + class OutputCollectionObj(TypedDict): id: str title: str + has_cog: bool start_date: str end_date: str bbox: str @@ -16,14 +18,19 @@ class OutputCollectionObj(TypedDict): description: str license: str -class Stac(): +class Stac: @staticmethod def organize_collections(collections=[]): output_collections = [] for collection in collections: try: data = collection.to_dict() + + has_cog = False + if data["item_assets"]: + has_cog = True + id = data["id"].strip() title = data["title"].strip() @@ -61,14 +68,27 @@ def organize_collections(collections=[]): ) license = data["license"] - collection_obj = OutputCollectionObj({'id': id, 'title': title, 'start_date': start_date, 'end_date': end_date, 'bbox': bbox, 'metadata': metadata, 'href': href, 'description': description, 'license': license}) + collection_obj = OutputCollectionObj( + { + "id": id, + "title": title, + "has_cog": has_cog, + "start_date": start_date, + "end_date": end_date, + "bbox": bbox, + "metadata": metadata, + "href": href, + "description": description, + "license": license, + } + ) output_collections.append(collection_obj) except Exception as err: - error = {'error': err, 'collection': collection} + error = {"error": err, "collection": collection} logging.error(error) return None if len(output_collections) > 0: - output_collections.sort(key= lambda x:x['title']) + output_collections.sort(key=lambda x: x["title"]) return output_collections @staticmethod @@ -84,9 +104,8 @@ def get_item_info(url=None, **kwargs): if isinstance(url, str): r = make_get_request(url).json() - + return r - @staticmethod def stac_tile( @@ -144,13 +163,19 @@ def stac_tile( kwargs.pop("TileMatrixSetId") if isinstance(titiler_stac_endpoint, str): - r = make_get_request(f"{titiler_stac_endpoint}/stac/{TileMatrixSetId}/tilejson.json", kwargs).json() + r = make_get_request( + f"{titiler_stac_endpoint}/stac/{TileMatrixSetId}/tilejson.json", kwargs + ).json() else: - r = make_get_request(titiler_stac_endpoint.url_for_stac_item(), kwargs).json() + r = make_get_request( + titiler_stac_endpoint.url_for_stac_item(), kwargs + ).json() return r["tiles"][0] @staticmethod - def stac_bounds(url=None, collection=None, item=None, titiler_stac_endpoint=None, **kwargs): + def stac_bounds( + url=None, collection=None, item=None, titiler_stac_endpoint=None, **kwargs + ): """Get the bounding box of a single SpatialTemporal Asset Catalog (STAC) item. Args: url (str): HTTP URL to a STAC item @@ -173,12 +198,14 @@ def stac_bounds(url=None, collection=None, item=None, titiler_stac_endpoint=None if isinstance(titiler_stac_endpoint, str): r = make_get_request(f"{titiler_stac_endpoint}/stac/bounds", kwargs).json() else: - r = make_get_request(titiler_stac_endpoint.url_for_stac_bounds(), kwargs).json() + r = make_get_request( + titiler_stac_endpoint.url_for_stac_bounds(), kwargs + ).json() bounds = r["bounds"] return bounds - # QUESTION: Is this being or planning to be used? + # QUESTION: Is this being or planning to be used? def add_stac_layer( self, url=None, @@ -242,7 +269,7 @@ def set_default_bands(bands): if len(bands) == 1: return bands - + if not isinstance(bands, list): raise ValueError("bands must be a list or a string.") @@ -286,13 +313,18 @@ def stac_search( items = list(search.item_collection()) info = {} for item in items: - info[item.id] = {'id': item.id, 'href': item.get_self_href(), 'bands': list(item.get_assets().keys()), 'assets': item.get_assets()} + info[item.id] = { + "id": item.id, + "href": item.get_self_href(), + "bands": list(item.get_assets().keys()), + "assets": item.get_assets(), + } return info else: return search - + @staticmethod - def get_metadata( + def get_metadata( data_type="cog", titiler_stac_endpoint=None, url=None, @@ -305,7 +337,9 @@ def get_metadata( kwargs["max_size"] = max_size if isinstance(titiler_stac_endpoint, str): - r = make_get_request(f"{titiler_stac_endpoint}/{data_type}/metadata", kwargs).json() + r = make_get_request( + f"{titiler_stac_endpoint}/{data_type}/metadata", kwargs + ).json() return r else: return "Cannot process request: titiler stac endpoint not provided." @@ -369,7 +403,10 @@ def get_tile_url( kwargs.pop("TileMatrixSetId") if isinstance(titiler_stac_endpoint, str): - r = make_get_request(f"{titiler_stac_endpoint}/{data_type}/{TileMatrixSetId}/tilejson.json", kwargs).json() + r = make_get_request( + f"{titiler_stac_endpoint}/{data_type}/{TileMatrixSetId}/tilejson.json", + kwargs, + ).json() return r else: - return "STAC ENDPOINT IS NECESSARY." \ No newline at end of file + return "STAC ENDPOINT IS NECESSARY." diff --git a/stac_ipyleaflet/stac_discovery/stac_widget.py b/stac_ipyleaflet/stac_discovery/stac_widget.py index fb40012..c9132b7 100644 --- a/stac_ipyleaflet/stac_discovery/stac_widget.py +++ b/stac_ipyleaflet/stac_discovery/stac_widget.py @@ -1,63 +1,114 @@ from datetime import datetime -from ipywidgets import Box, DatePicker, Dropdown, HBox, HTML -from ipywidgets import Layout, Output, RadioButtons, SelectionSlider, Tab, ToggleButtons, VBox +from ipywidgets import Box, Checkbox, DatePicker, Dropdown, HBox, HTML +from ipywidgets import ( + Label, + Layout, + Output, + RadioButtons, + SelectionSlider, + Tab, + ToggleButtons, + VBox, +) +import logging from pystac_client import Client from stac_ipyleaflet.constants import TITILER_ENDPOINT from stac_ipyleaflet.stac_discovery.stac import Stac -class StacDiscoveryWidget(): - def template(self) -> Box( style={"max_height: 200px"}): - opacity_values = [i*10 for i in range(10+1)] # [0.001, 0.002, ...] + +class StacDiscoveryWidget: + def template(self) -> Box(style={"max_height: 200px"}): + opacity_values = [i * 10 for i in range(10 + 1)] # [0.001, 0.002, ...] standard_width = "440px" styles = { - "init": {"description_width": "initial",}, - "desc": "white-space:normal;font-size:smaller; max-height:80px;" + "init": { + "description_width": "initial", + }, + "desc": "white-space:normal;font-size:smaller; max-height:80px;", + "label": "font-weight:bold;", } layouts = { "default": Layout(width=standard_width, padding="2px 6px"), - "header": Layout(width=standard_width, padding="2px 6px", margin="2px 2px -6px 2px"), - "buttons": Layout(display="flex", flex_flow="row", align_items="flex-end", justify_content="flex-end", margin="0.5rem 1.5rem"), + "checkbox": Layout(width="auto", padding="2px 0px 2px 6px"), + "header": Layout( + width=standard_width, padding="2px 6px", margin="2px 2px -6px 2px" + ), + "subtitle": Layout( + width=standard_width, padding="0px 0px", margin="-12px 2px 2px 34px" + ), + "buttons": Layout( + display="flex", + flex_flow="row", + align_items="flex-end", + justify_content="flex-end", + margin="0.5rem 1.5rem", + ), "radio": Layout(display="flex", width="max-content", padding="2px 6px"), } output = Output( - layout=Layout(width=standard_width, height="200px", padding="4px 8px 4px 8px", overflow="auto") + layout=Layout( + width=standard_width, + height="200px", + padding="4px 8px 4px 8px", + overflow="auto", + ) ) # Templates for the STAC Discovery Widget stac_widget = VBox() - stac_widget.layout.width="480px" - stac_widget.layout.height="400px" - stac_widget.layout.flex_flow="column" - stac_widget.layout.overflow="auto" + stac_widget.layout.width = "480px" + stac_widget.layout.height = "400px" + stac_widget.layout.flex_flow = "column" + stac_widget.layout.overflow = "auto" stac_catalogs = [ {"name": "MAAP STAC", "url": "https://stac.maap-project.org"}, # {"name": "VEDA STAC", "url": "https://staging-stac.delta-backend.com"}, - # {"name": "MAAP STAC", "url": "https://wssn144yw1.execute-api.us-west-2.amazonaws.com/"}, - {"name": "Element84 Earth Search", "url": "https://earth-search.aws.element84.com/v1"}, + # {"name": "MAAP STAC", "url": "https://wssn144yw1.execute-api.us-west-2.amazonaws.com/"}, + { + "name": "Element84 Earth Search", + "url": "https://earth-search.aws.element84.com/v1", + }, # {"name": "Microsoft Planetary Computer", "url": "https://planetarycomputer.microsoft.com/api/stac/v1"}, ] # make list of name values from stac_catalogs - catalog_options = sorted([c['name'] for c in stac_catalogs]) - selected_catalog = stac_catalogs[0] + catalog_options = sorted([c["name"] for c in stac_catalogs]) for cat in stac_catalogs: - # print(cat) stac_client = Client.open(cat["url"], headers=[]) collections_object = stac_client.get_all_collections() collections = Stac.organize_collections(collections_object) cat["collections"] = collections + + selected_catalog = [cat for cat in stac_catalogs if "MAAP" in cat["name"]][0] + if "collections" not in selected_catalog: - print("COLLECTIONS NOT FOUND") - return None - # else: - selected_collection_options = sorted([c for c in selected_catalog["collections"]], key=lambda c: c["id"]) + logging.warn("NO COLLECTIONS FOUND") + + collections_filter_checkbox = Checkbox( + value=True, layout=layouts["checkbox"], indent=False + ) + + def get_available_collections(catalog): + if collections_filter_checkbox.value: + return sorted( + [c for c in catalog["collections"] if c["has_cog"]], + key=lambda c: c["id"], + ) + else: + return sorted( + [c for c in catalog["collections"]], key=lambda c: c["id"] + ) + + selected_collection_options = get_available_collections( + catalog=selected_catalog + ) selected_collection = selected_collection_options[0] - self.stac_data = { + self.stac_data = { "catalog": selected_catalog, "collection": selected_collection, "items": [], - "layer_added": False + "layer_added": False, } # STAC Widget Items @@ -65,13 +116,17 @@ def template(self) -> Box( style={"max_height: 200px"}): options=catalog_options, value=self.stac_data["catalog"]["name"], style=styles["init"], - disabled=True, + # disabled=True, layout=layouts["default"], ) catalogs_box = VBox( [ - HTML(value="<b>Catalog</b>", style=styles["init"], layout=layouts["header"],), - catalogs_dropdown + HTML( + value="<b>Catalog</b>", + style=styles["init"], + layout=layouts["header"], + ), + catalogs_dropdown, ] ) collections_dropdown = Dropdown( @@ -80,10 +135,30 @@ def template(self) -> Box( style={"max_height: 200px"}): style=styles["init"], layout=layouts["default"], ) + collections_filter_label = HTML(value="<b>Only Show Displayable Items</b>") + collections_filter_desc = HTML( + value="<em>Currently, only Cloud-Optimized GeoTiffs are supported</em>", + style=styles["init"], + layout=layouts["subtitle"], + ) + collections_checkbox_box = HBox( + [collections_filter_checkbox, collections_filter_label] + ) + collections_filter_box = VBox( + [ + collections_checkbox_box, + collections_filter_desc, + ] + ) collections_box = VBox( [ - HTML(value="<b>Collection</b>", style=styles["init"], layout=layouts["header"],), - collections_dropdown + HTML( + value="<b>Collection</b>", + style=styles["init"], + layout=layouts["default"], + ), + collections_dropdown, + collections_filter_box, ] ) collection_description = HTML( @@ -93,8 +168,12 @@ def template(self) -> Box( style={"max_height: 200px"}): ) collection_description_box = VBox( [ - HTML(value="<b>Description</b>", style=styles["init"], layout=layouts["header"],), - collection_description + HTML( + value="<b>Description</b>", + style=styles["init"], + layout=layouts["header"], + ), + collection_description, ] ) collection_url = HTML( @@ -102,7 +181,9 @@ def template(self) -> Box( style={"max_height: 200px"}): style=styles["init"], layout=layouts["default"], ) - stac_browser_url = self.stac_data["collection"]["href"].replace("https://", "https://stac-browser.maap-project.org/external/") + stac_browser_url = self.stac_data["collection"]["href"].replace( + "https://", "https://stac-browser.maap-project.org/external/" + ) collection_url_browser = HTML( value=f'<a href={stac_browser_url} target="_blank"><b>View in STAC Browser</b></a>', style=styles["init"], @@ -112,19 +193,28 @@ def template(self) -> Box( style={"max_height: 200px"}): collection_url_browser.style.text_color = "blue" collection_url_box = VBox( [ - HTML(value="<b>URL</b>", style=styles["init"], layout=layouts["header"],), - collection_url, collection_url_browser + HTML( + value="<b>URL</b>", + style=styles["init"], + layout=layouts["header"], + ), + collection_url, + collection_url_browser, ] ) collection_start_date = DatePicker( - value=datetime.strptime(self.stac_data["collection"]["start_date"], "%Y-%m-%d"), + value=datetime.strptime( + self.stac_data["collection"]["start_date"], "%Y-%m-%d" + ), description="Start", disabled=False if collections_dropdown.value else True, style=styles["init"], layout=layouts["default"], ) collection_end_date = DatePicker( - value=datetime.strptime(self.stac_data["collection"]["end_date"], "%Y-%m-%d"), + value=datetime.strptime( + self.stac_data["collection"]["end_date"], "%Y-%m-%d" + ), description="End", disabled=False if collections_dropdown.value else True, style=styles["init"], @@ -132,30 +222,28 @@ def template(self) -> Box( style={"max_height: 200px"}): ) collection_dates_box = VBox( [ - HTML(value="<b>Date Range</b>", style=styles["init"], layout=layouts["header"],), - HBox([collection_start_date, collection_end_date]) + HTML( + value="<b>Date Range</b>", + style=styles["init"], + layout=layouts["header"], + ), + HBox([collection_start_date, collection_end_date]), ] ) defaultItemsDropdownText = "Select an Item" items_dropdown = Dropdown( - options=[], - value=None, - style=styles["init"], - layout=layouts["default"] + options=[], value=None, style=styles["init"], layout=layouts["default"] ) items_box = VBox( [ - HTML(value="<b>Items</b>", style=styles["init"], layout=layouts["header"],), - items_dropdown + HTML( + value="<b>Items</b>", + style=styles["init"], + layout=layouts["header"], + ), + items_dropdown, ] ) - # layer_name = Text( - # value="STAC Layer", - # description="Layer name:", - # tooltip="Enter a layer name for the selected file", - # style=styles["init"], - # layout=layouts["default"], - # ) band_width = "125px" @@ -168,8 +256,12 @@ def template(self) -> Box( style={"max_height: 200px"}): singular_band_dropdown_box = VBox( [ - HTML(value="<b>Band(s)</b>", style=styles["init"], layout=layouts["header"],), - singular_band_dropdown + HTML( + value="<b>Band(s)</b>", + style=styles["init"], + layout=layouts["header"], + ), + singular_band_dropdown, ] ) @@ -195,30 +287,114 @@ def template(self) -> Box( style={"max_height: 200px"}): layout=Layout(width=band_width, padding="4px 8px"), ) - cmaps = [('Perceptually Uniform Sequential', [ - 'viridis', 'plasma', 'inferno', 'magma', 'cividis']), - ('Sequential', [ - 'Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds', - 'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu', - 'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn']), - ('Sequential (2)', [ - 'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink', - 'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia', - 'hot', 'afmhot', 'gist_heat', 'copper']), - ('Diverging', [ - 'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu', - 'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic']), - ('Cyclic', ['twilight', 'twilight_shifted', 'hsv']), - ('Qualitative', [ - 'Pastel1', 'Pastel2', 'Paired', 'Accent', - 'Dark2', 'Set1', 'Set2', 'Set3', - 'tab10', 'tab20', 'tab20b', 'tab20c']), - ('Miscellaneous', [ - 'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern', - 'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg', - 'gist_rainbow', 'rainbow', 'jet', 'turbo', 'nipy_spectral', - 'gist_ncar'])] - + cmaps = [ + ( + "Perceptually Uniform Sequential", + ["viridis", "plasma", "inferno", "magma", "cividis"], + ), + ( + "Sequential", + [ + "Greys", + "Purples", + "Blues", + "Greens", + "Oranges", + "Reds", + "YlOrBr", + "YlOrRd", + "OrRd", + "PuRd", + "RdPu", + "BuPu", + "GnBu", + "PuBu", + "YlGnBu", + "PuBuGn", + "BuGn", + "YlGn", + ], + ), + ( + "Sequential (2)", + [ + "binary", + "gist_yarg", + "gist_gray", + "gray", + "bone", + "pink", + "spring", + "summer", + "autumn", + "winter", + "cool", + "Wistia", + "hot", + "afmhot", + "gist_heat", + "copper", + ], + ), + ( + "Diverging", + [ + "PiYG", + "PRGn", + "BrBG", + "PuOr", + "RdGy", + "RdBu", + "RdYlBu", + "RdYlGn", + "Spectral", + "coolwarm", + "bwr", + "seismic", + ], + ), + ("Cyclic", ["twilight", "twilight_shifted", "hsv"]), + ( + "Qualitative", + [ + "Pastel1", + "Pastel2", + "Paired", + "Accent", + "Dark2", + "Set1", + "Set2", + "Set3", + "tab10", + "tab20", + "tab20b", + "tab20c", + ], + ), + ( + "Miscellaneous", + [ + "flag", + "prism", + "ocean", + "gist_earth", + "terrain", + "gist_stern", + "gnuplot", + "gnuplot2", + "CMRmap", + "cubehelix", + "brg", + "gist_rainbow", + "rainbow", + "jet", + "turbo", + "nipy_spectral", + "gist_ncar", + ], + ), + ] + def list_palettes(add_extra=False, lowercase=False, category=""): """List all available colormaps. See a complete lost of colormaps at https://matplotlib.org/stable/tutorials/colors/colormaps.html. Returns: @@ -228,7 +404,9 @@ def list_palettes(add_extra=False, lowercase=False, category=""): if not category == "": all_colormap_options = plt.colormaps() - filtered_color_options = list(filter(lambda x: x[0].startswith(category), cmaps)) + filtered_color_options = list( + filter(lambda x: x[0].startswith(category), cmaps) + ) palette_options = list(map(lambda x: x[1], filtered_color_options))[0] if add_extra: palette_options += ["dem", "ndvi", "ndwi"] @@ -236,26 +414,31 @@ def list_palettes(add_extra=False, lowercase=False, category=""): palette_options = [i.lower() for i in palette_options] palette_options.sort() return palette_options - - def list_palette_categories(): + + def list_palette_categories(): palette_categories = list(map(lambda x: x[0], cmaps)) return palette_categories - palette_category_options = list_palette_categories() palette_categories_dropdown = Dropdown( options=palette_category_options, value=palette_category_options[0], layout=layouts["default"], style=styles["init"], - ) + ) palette_categories_dropdown_box = VBox( [ - HTML(value="<b>Palette Category</b>", style=styles["init"], layout=layouts["header"],), - palette_categories_dropdown + HTML( + value="<b>Palette Category</b>", + style=styles["init"], + layout=layouts["header"], + ), + palette_categories_dropdown, ] ) - palette_options = list_palettes(lowercase=True, category=palette_categories_dropdown.value) + palette_options = list_palettes( + lowercase=True, category=palette_categories_dropdown.value + ) # palettes_dropdown = Dropdown( # options=palette_options, # value=palette_options[0], @@ -271,8 +454,12 @@ def list_palette_categories(): ) palettes_radiobuttons_box = VBox( [ - HTML(value="<b>Palette</b>", style=styles["init"], layout=layouts["header"],), - palette_radiobuttons + HTML( + value="<b>Palette</b>", + style=styles["init"], + layout=layouts["header"], + ), + palette_radiobuttons, ] ) # TODO: Add STAC layers to LayerGroup instead of base @@ -293,11 +480,13 @@ def list_palette_categories(): # style=styles["init"], # ) # params_widget = VBox([checkbox, add_params]) - raster_options = VBox([ - HBox([singular_band_dropdown_box]), - HBox([palette_categories_dropdown_box]), - HBox([palettes_radiobuttons_box]), - ]) + raster_options = VBox( + [ + HBox([singular_band_dropdown_box]), + HBox([palette_categories_dropdown_box]), + HBox([palettes_radiobuttons_box]), + ] + ) stac_buttons = ToggleButtons( value=None, options=["Display "], @@ -307,33 +496,33 @@ def list_palette_categories(): ) stac_opacity_slider = SelectionSlider( value=1, - options=[("%g"%i, i/100) for i in opacity_values], + options=[("%g" % i, i / 100) for i in opacity_values], description="% Opacity:", continuous_update=False, - orientation='horizontal', - layout=Layout(margin="-12px 0 4px 0") + orientation="horizontal", + layout=Layout(margin="-12px 0 4px 0"), ) - buttons_box = Box([ stac_opacity_slider, stac_buttons], layout=layouts["buttons"]) - stac_tab_labels = ['Catalog', 'Visualization'] + buttons_box = Box( + [stac_opacity_slider, stac_buttons], layout=layouts["buttons"] + ) + stac_tab_labels = ["Catalog", "Visualization"] tab_widget_children = [] stac_tab_widget = Tab() - for label in stac_tab_labels: + for label in stac_tab_labels: tab_content = VBox() - if label == 'Catalog': + if label == "Catalog": tab_content.children = [ catalogs_box, collections_box, collection_description_box, collection_url_box, collection_dates_box, - items_box - ] - elif label == 'Visualization': - tab_content.children = [ - raster_options + items_box, ] + elif label == "Visualization": + tab_content.children = [raster_options] tab_widget_children.append(tab_content) stac_tab_widget.children = tab_widget_children stac_tab_widget.titles = stac_tab_labels @@ -348,36 +537,43 @@ def list_palette_categories(): # raster_options, stac_tab_widget, buttons_box, - output + output, ] - + def handle_stac_layer_opacity(change): if self.stac_data["layer_added"] == True: l = self.find_layer(items_dropdown.value) if l.name: - l.opacity = change["new"] + l.opacity = change["new"] def prep_data_display_settings(): is_displayable = False stac_opacity_slider.disabled = True - assets = [i for i in self.stac_data["items"] if i["id"] == items_dropdown.value][0]["assets"] - item_href = [i for i in self.stac_data["items"] if i["id"] == items_dropdown.value][0]["href"] + assets = [ + i for i in self.stac_data["items"] if i["id"] == items_dropdown.value + ][0]["assets"] + item_href = [ + i for i in self.stac_data["items"] if i["id"] == items_dropdown.value + ][0]["href"] metadata = Stac.get_item_info(url=item_href) if "assets" in metadata: self.stac_data["metadata"] = metadata - + # with output: # output.clear_output() # print("SELECTED ITEM", [i for i in self.stac_data["items"] if i["id"] == items_dropdown.value][0]) # print("METADATA", json.dumps(metadata)) - + for asset in assets: data_asset = assets[asset] - self.stac_data["data_href"] = data_asset.get_absolute_href() + self.stac_data["data_href"] = data_asset.get_absolute_href() data_types = data_asset.media_type - # print(f"{asset} data type:", data_types) - if "application=geotiff" in data_types and "profile=cloud-optimized" in data_types: - is_displayable = True + # print(f"{asset} data type:", data_types) + if ( + "application=geotiff" in data_types + and "profile=cloud-optimized" in data_types + ): + is_displayable = True # if "statistics" in metadata: # minv, maxv = metadata["statistics"]["1"]["min"], metadata["statistics"]["1"]["max"] # print("MIN/MAX", minv, maxv) @@ -397,21 +593,23 @@ def prep_data_display_settings(): singular_band_dropdown.value = default_bands[0] # stac_tab_widget.selected_index = 1 else: - raster_options.children = [] + raster_options.children = [] singular_band_dropdown.value = None - + if is_displayable: stac_buttons.disabled = False with output: output.clear_output() - print("Item is ready for display.") + print("Item is ready for display.") else: stac_buttons.disabled = True stac_opacity_slider.disabled = True with output: output.clear_output() - print("This item cannot displayed. Only Cloud-Optimized GeoTIFFs are supported at this time.") - + print( + "This item cannot displayed. Only Cloud-Optimized GeoTIFFs are supported at this time." + ) + def query_collection_items(selected_collection): # print("SELECTED TO QUERY", selected_collection) items_dropdown.options = [] @@ -423,7 +621,9 @@ def query_collection_items(selected_collection): # geometries = [self.draw_control.last_draw['geometry']] # print(geometries) if isinstance(collection_start_date.value, datetime): - start_date_query = collection_start_date.value.strftime("%Y-%m-%d") + start_date_query = collection_start_date.value.strftime( + "%Y-%m-%d" + ) else: start_date_query = str(collection_start_date.value) @@ -432,13 +632,13 @@ def query_collection_items(selected_collection): else: end_date_query = str(collection_end_date.value) - _datetime = start_date_query + _datetime = start_date_query if collection_end_date.value is not None: _datetime = _datetime + "/" + end_date_query url = selected_collection["href"] _query_url = url if url.endswith("/items") else url + "/items" - - print("from ",_query_url, "...") + + print("from ", _query_url, "...") collection_items = Stac.stac_search( url=_query_url, @@ -450,70 +650,99 @@ def query_collection_items(selected_collection): ) result_items = list(collection_items.values()) self.stac_data["items"] = result_items - items = list(collection_items.keys()) - default = [defaultItemsDropdownText] + items = list(collection_items.keys()) + default = [defaultItemsDropdownText] if len(items) > 0: - options = [*default, *items] - items_dropdown.options = options - items_dropdown.value = options[0] - output.clear_output() - print(f"{len(items)} items were found - please select 1 to determine if it can be displayed.") + options = [*default, *items] + items_dropdown.options = options + items_dropdown.value = options[0] + output.clear_output() + print( + f"{len(items)} items were found - please select 1 to determine if it can be displayed." + ) else: output.clear_output() - print("No items were found within this Collection. Please select another.") + print( + "No items were found within this Collection. Please select another." + ) except Exception as err: output.clear_output() print("COLLECTION QUERY ERROR", err) + def set_collection_options(): + selected_catalog = [ + cat for cat in stac_catalogs if cat["name"] == catalogs_dropdown.value + ][0] + selected_collection_options = get_available_collections( + catalog=selected_catalog + ) + collections_dropdown.options = [ + c["id"] for c in selected_collection_options + ] + + selected_collection = [ + c + for c in selected_collection_options + if c["id"] == collections_dropdown.value + ][0] + collections_dropdown.value = selected_collection["id"] + + collection_description.value = f'<div style="{styles["desc"]}">{selected_collection["description"]}</div>' + collection_url.value = f'<a href={selected_collection["href"]} target="_blank">{selected_collection["href"]}</a>' + stac_browser_url = selected_collection["href"].replace( + "https://", "https://stac-browser.maap-project.org/external/" + ) + collection_url_browser.value = f'<a href={stac_browser_url} target="_blank"><b>View in STAC Browser</b></a>' + if selected_collection["start_date"] != "": + collection_start_date.value = datetime.strptime( + selected_collection["start_date"], "%Y-%m-%d" + ) + else: + collection_start_date.value = None + if selected_collection["end_date"] != "": + collection_end_date.value = datetime.strptime( + selected_collection["end_date"], "%Y-%m-%d" + ) + else: + collection_end_date.value = None + + self.stac_data["catalog"] = selected_catalog + self.stac_data["collection"] = selected_collection + query_collection_items(selected_collection) # Event Watchers def catalogs_changed(change): - if change["new"]: - selected_catalog = [cat for cat in stac_catalogs if cat["name"] == catalogs_dropdown.value][0] - selected_collection_options = sorted([c for c in selected_catalog["collections"]], key=lambda c: c["id"]) - selected_collection = selected_collection_options[0] - collections_dropdown.options = [c["id"] for c in selected_collection_options] - collections_dropdown.value = selected_collection["id"] - self.stac_data["catalog"] = selected_catalog - self.stac_data["collection"] = selected_collection - query_collection_items(selected_collection) - - # with output: - # output.clear_output() - # print(selected_collection["id"]) + if change["new"]: + set_collection_options() catalogs_dropdown.observe(catalogs_changed, names="value") - + def collection_changed(change): if change["new"]: - selected_collection = [c for c in selected_collection_options if c["id"] == collections_dropdown.value][0] - collection_description.value = f'<div style="{styles["desc"]}">{selected_collection["description"]}</div>' - collection_url.value = f'<a href={selected_collection["href"]} target="_blank">{selected_collection["href"]}</a>' - stac_browser_url = selected_collection["href"].replace("https://", "https://stac-browser.maap-project.org/external/") - collection_url_browser.value = f'<a href={stac_browser_url} target="_blank"><b>View in STAC Browser</b></a>' - if selected_collection["start_date"] != "": - collection_start_date.value = datetime.strptime(selected_collection["start_date"], "%Y-%m-%d") - else: - collection_start_date.value = None - if selected_collection["end_date"] != "": - collection_end_date.value = datetime.strptime(selected_collection["end_date"], "%Y-%m-%d") - else: - collection_end_date.value = None - self.stac_data["collection"] = selected_collection - query_collection_items(selected_collection) + set_collection_options() collections_dropdown.observe(collection_changed, names="value") - - def items_changed(change): - if change["new"] and change["new"] != defaultItemsDropdownText: + + def collections_filtered_checkbox_changed(change): + if change["type"] == "change": + set_collection_options() + + collections_filter_checkbox.observe( + collections_filtered_checkbox_changed, names="value" + ) + + def items_changed(change): + if change["new"] and change["new"] != defaultItemsDropdownText: prep_data_display_settings() items_dropdown.observe(items_changed, names="value") def palette_category_changed(change): if change["new"]: - new_palettes = list_palettes(lowercase=True, category=palette_categories_dropdown.value) + new_palettes = list_palettes( + lowercase=True, category=palette_categories_dropdown.value + ) palette_radiobuttons.options = new_palettes palette_radiobuttons.value = new_palettes[0] @@ -550,8 +779,13 @@ def button_clicked(change): # vis_params = eval(add_params.value) # else: vis_params = {} - - if (palette_radiobuttons.value and singular_band_dropdown.options) or (palette_radiobuttons.value and "expression" in vis_params): + + if ( + palette_radiobuttons.value + and singular_band_dropdown.options + ) or ( + palette_radiobuttons.value and "expression" in vis_params + ): vis_params["colormap_name"] = palette_radiobuttons.value if vmin.value and vmax.value: @@ -571,7 +805,7 @@ def button_clicked(change): item=items_dropdown.value, assets=assets, palette=vis_params["colormap_name"], - titiler_stac_endpoint=TITILER_ENDPOINT + titiler_stac_endpoint=TITILER_ENDPOINT, ) print("stac url:", stac_url) if "tiles" in stac_url: @@ -588,14 +822,22 @@ def button_clicked(change): tile_url = self.stac_data["tiles_url"] if self.stac_data["layer_added"] == True: - self.layers = self.layers[:len(self.layers)-1] + self.layers = self.layers[: len(self.layers) - 1] self.stac_data["layer_added"] = False - self.add_tile_layer(url=tile_url, name=items_dropdown.value, attribution=items_dropdown.value) - stac_opacity_slider.observe(handle_stac_layer_opacity, names="value") + self.add_tile_layer( + url=tile_url, + name=items_dropdown.value, + attribution=items_dropdown.value, + ) + stac_opacity_slider.observe( + handle_stac_layer_opacity, names="value" + ) self.stac_data["layer_added"] = True reset_stac_opacity_slider() if len(bounds) > 0: - self.fit_bounds([[bounds[1], bounds[0]], [bounds[3], bounds[2]]]) + self.fit_bounds( + [[bounds[1], bounds[0]], [bounds[3], bounds[2]]] + ) output.clear_output() # print("STAC URL", stac_url["tiles"][0]) except Exception as err: @@ -614,6 +856,6 @@ def button_clicked(change): query_collection_items(selected_collection) - stac_widget.layout.display = 'none' - + stac_widget.layout.display = "none" + return stac_widget From 5ce70f03053cf0fe8cd7f957697423419ada7559 Mon Sep 17 00:00:00 2001 From: Emma Paz <emma@developmentseed.org> Date: Tue, 25 Jul 2023 15:58:08 +0000 Subject: [PATCH 2/4] add comments --- stac_ipyleaflet/stac_discovery/stac.py | 1 + stac_ipyleaflet/stac_discovery/stac_widget.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/stac_ipyleaflet/stac_discovery/stac.py b/stac_ipyleaflet/stac_discovery/stac.py index 1e652c1..f3edf2a 100644 --- a/stac_ipyleaflet/stac_discovery/stac.py +++ b/stac_ipyleaflet/stac_discovery/stac.py @@ -28,6 +28,7 @@ def organize_collections(collections=[]): data = collection.to_dict() has_cog = False + # determine if collection has compatible items by looking to its item_assets property if data["item_assets"]: has_cog = True diff --git a/stac_ipyleaflet/stac_discovery/stac_widget.py b/stac_ipyleaflet/stac_discovery/stac_widget.py index c9132b7..20f9b52 100644 --- a/stac_ipyleaflet/stac_discovery/stac_widget.py +++ b/stac_ipyleaflet/stac_discovery/stac_widget.py @@ -79,7 +79,7 @@ def template(self) -> Box(style={"max_height: 200px"}): collections_object = stac_client.get_all_collections() collections = Stac.organize_collections(collections_object) cat["collections"] = collections - + # set default catalog based on name selected_catalog = [cat for cat in stac_catalogs if "MAAP" in cat["name"]][0] if "collections" not in selected_catalog: @@ -89,6 +89,7 @@ def template(self) -> Box(style={"max_height: 200px"}): value=True, layout=layouts["checkbox"], indent=False ) + # available collections have been tagged as `has_cog: True` when reviewing item_assets def get_available_collections(catalog): if collections_filter_checkbox.value: return sorted( @@ -670,6 +671,7 @@ def query_collection_items(selected_collection): output.clear_output() print("COLLECTION QUERY ERROR", err) + # sets and refreshes which collections are set based on selected catalog def set_collection_options(): selected_catalog = [ cat for cat in stac_catalogs if cat["name"] == catalogs_dropdown.value From 759776b3965549fec2ca896f96ede975a2bc282c Mon Sep 17 00:00:00 2001 From: Emma Paz <emma@developmentseed.org> Date: Wed, 26 Jul 2023 13:53:33 +0000 Subject: [PATCH 3/4] remove unused property --- stac_ipyleaflet/stac_discovery/stac_widget.py | 1 - 1 file changed, 1 deletion(-) diff --git a/stac_ipyleaflet/stac_discovery/stac_widget.py b/stac_ipyleaflet/stac_discovery/stac_widget.py index 20f9b52..1de03c6 100644 --- a/stac_ipyleaflet/stac_discovery/stac_widget.py +++ b/stac_ipyleaflet/stac_discovery/stac_widget.py @@ -117,7 +117,6 @@ def get_available_collections(catalog): options=catalog_options, value=self.stac_data["catalog"]["name"], style=styles["init"], - # disabled=True, layout=layouts["default"], ) catalogs_box = VBox( From 570b49ffd39e4fb3709cf3bc2e57273efcdf3dad Mon Sep 17 00:00:00 2001 From: Emma Paz <emma@developmentseed.org> Date: Wed, 26 Jul 2023 13:54:37 +0000 Subject: [PATCH 4/4] set has_cog var in ternary declaration --- stac_ipyleaflet/stac_discovery/stac.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/stac_ipyleaflet/stac_discovery/stac.py b/stac_ipyleaflet/stac_discovery/stac.py index f3edf2a..3dfe8e6 100644 --- a/stac_ipyleaflet/stac_discovery/stac.py +++ b/stac_ipyleaflet/stac_discovery/stac.py @@ -27,10 +27,7 @@ def organize_collections(collections=[]): try: data = collection.to_dict() - has_cog = False - # determine if collection has compatible items by looking to its item_assets property - if data["item_assets"]: - has_cog = True + has_cog = True if data["item_assets"] else False id = data["id"].strip() title = data["title"].strip() @@ -206,7 +203,6 @@ def stac_bounds( bounds = r["bounds"] return bounds - # QUESTION: Is this being or planning to be used? def add_stac_layer( self, url=None,