diff --git a/HISTORY.rst b/HISTORY.rst index 37cbd7f..d37cabc 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,4 +5,12 @@ History 0.1.0 (2023-01-19) ------------------ -* First release on PyPI. +* First release (beta). + + +0.2.0 (2023-05-19) +------------------ + +* First public release (beta). + + diff --git a/MANIFEST.in b/MANIFEST.in index d45689c..49ae174 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,6 +3,7 @@ include HISTORY.rst include LICENSE include README.rst include stac_ipyleaflet/data/* +include stac_ipyleaflet/widgets/* include stac_ipyleaflet/stac_discovery/catalogs/* recursive-include tests * diff --git a/README.md b/README.md index 74afa51..eb137fe 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # STAC ipyleaflet -WORK IN PROGRESS. Right now this does nothing with STAC. Currently this provides a module on top of ipyleaflet demonstrating how to load tile layers (from `biomass-layers.csv` and create histograms from a bounding box and visible layers. +WORK IN PROGRESS. Right now this connects to the MAAP STAC - providing a module on top of ipyleaflet demonstrating how to load & control opacity for tile layers (from `biomass-layers.csv`), view pre-determined Basemaps, and derive coordinates from a user-defined bounding box. -Much of this is inspired and copied from [leafmap](https://leafmap.org/) +Much of this project is inspired from [leafmap](https://leafmap.org/) ![Jupyter Lab ScreenShot](jlab-screenshot.png) @@ -24,3 +24,6 @@ jupyter lab Note this library currently includes `rio.open` so must be run with an AWS identity that has access to the bucket the biomass products are in. +**Styling Notes** +- By default, ipywidget icons can be set to any from the font-awesome library v4: https://fontawesome.com/v4/icons/ +- By default, ipywidget buttons can be styled to any html colors: https://htmlcolorcodes.com/color-names/ diff --git a/README.rst b/README.rst index bfcf2ea..4300feb 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ ipyleaflet customized for discovering, visualizing and interacting with STAC and * Free software: MIT license -* Documentation: https://stac-ipyleaflet.readthedocs.io. +* Documentation: https://docs.maap-project.org/en/latest/technical_tutorials/visualization/stac_ipyleaflet.html. Features diff --git a/demo.ipynb b/demo.ipynb index 1ae9fd1..1c309fa 100644 --- a/demo.ipynb +++ b/demo.ipynb @@ -1,74 +1,109 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "4f7d5321-d82b-4ed4-8136-832f932b30b5", - "metadata": {}, - "outputs": [ - { - "ename": "ImportError", - "evalue": "libpoppler.so.126: cannot open shared object file: No such file or directory", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mImportError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[1], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mstac_ipyleaflet\u001b[39;00m\n", - "File \u001b[0;32m/workspaces/stac_ipyleaflet/stac_ipyleaflet/__init__.py:7\u001b[0m\n\u001b[1;32m 4\u001b[0m __email__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124maimee@developmentseed.org\u001b[39m\u001b[38;5;124m'\u001b[39m\n\u001b[1;32m 5\u001b[0m __version__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m0.1.0\u001b[39m\u001b[38;5;124m'\u001b[39m\n\u001b[0;32m----> 7\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mcore\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;241m*\u001b[39m\n", - "File \u001b[0;32m/workspaces/stac_ipyleaflet/stac_ipyleaflet/core.py:15\u001b[0m\n\u001b[1;32m 13\u001b[0m \u001b[38;5;66;03m#from pydantic import BaseModel\u001b[39;00m\n\u001b[1;32m 14\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mshapely\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mgeometry\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Polygon\n\u001b[0;32m---> 15\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mrioxarray\u001b[39;00m\n\u001b[1;32m 16\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mxarray\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mxr\u001b[39;00m\n\u001b[1;32m 17\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mnumpy\u001b[39;00m\n", - "File \u001b[0;32m/opt/conda/lib/python3.10/site-packages/rioxarray/__init__.py:5\u001b[0m\n\u001b[1;32m 2\u001b[0m __author__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\"\"\u001b[39m\u001b[38;5;124mrioxarray Contributors\u001b[39m\u001b[38;5;124m\"\"\"\u001b[39m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mimportlib\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mmetadata\u001b[39;00m\n\u001b[0;32m----> 5\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mrioxarray\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mraster_array\u001b[39;00m \u001b[38;5;66;03m# noqa\u001b[39;00m\n\u001b[1;32m 6\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mrioxarray\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mraster_dataset\u001b[39;00m \u001b[38;5;66;03m# noqa\u001b[39;00m\n\u001b[1;32m 7\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mrioxarray\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01m_io\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m open_rasterio \u001b[38;5;66;03m# noqa\u001b[39;00m\n", - "File \u001b[0;32m/opt/conda/lib/python3.10/site-packages/rioxarray/raster_array.py:19\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtyping\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Any, Literal, Optional, Union\n\u001b[1;32m 18\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mnumpy\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mnp\u001b[39;00m\n\u001b[0;32m---> 19\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mrasterio\u001b[39;00m\n\u001b[1;32m 20\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mrasterio\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mmask\u001b[39;00m\n\u001b[1;32m 21\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mrasterio\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mwarp\u001b[39;00m\n", - "File \u001b[0;32m/opt/conda/lib/python3.10/site-packages/rasterio/__init__.py:28\u001b[0m\n\u001b[1;32m 24\u001b[0m os\u001b[38;5;241m.\u001b[39madd_dll_directory(os\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mabspath(p))\n\u001b[1;32m 27\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mrasterio\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01m_show_versions\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m show_versions\n\u001b[0;32m---> 28\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mrasterio\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01m_version\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m gdal_version, get_geos_version, get_proj_version\n\u001b[1;32m 29\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mrasterio\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mcrs\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m CRS\n\u001b[1;32m 30\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mrasterio\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mdrivers\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m driver_from_extension, is_blacklisted\n", - "\u001b[0;31mImportError\u001b[0m: libpoppler.so.126: cannot open shared object file: No such file or directory" - ] - } - ], - "source": [ - "import stac_ipyleaflet" - ] - }, { "cell_type": "markdown", "id": "9648b56f-dbf3-4aac-af19-643067596220", - "metadata": {}, + "metadata": { + "tags": [] + }, "source": [ - "# Use ipyleaflet to visually explore MAAP data\n", + "# Use stac_ipyleaflet to visually explore MAAP data\n", "\n", - "Inspired by leafmap." + "Inspired by Leafmap." ] }, { "cell_type": "code", - "execution_count": 2, - "id": "8e4a59f2-1aa6-4b82-86e5-90d0c3c7ffe2", - "metadata": {}, + "execution_count": 1, + "id": "4f7d5321-d82b-4ed4-8136-832f932b30b5", + "metadata": { + "tags": [] + }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "3a03ad2140da4cd198721f5ba3e089c6", + "model_id": "a889bd5030ec400ebf2bd0ee01bded38", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "StacIpyleaflet(center=[20, 0], controls=(ZoomControl(options=['position', 'zoom_in_text', 'zoom_in_title', 'zo…" + "HBox(children=(ToggleButton(value=False, description='Layers', icon='map-o', layout=Layout(border_bottom='1px …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "03a7d6cea86b4f2ab0de29781bb1942c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" ] }, "metadata": {}, "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "a0f8457e810d46f284779d9d6b201f24", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "20b7d3527fcf4a4384ffd3df79e25d44", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "StacIpyleaflet(center=[20, 0], controls=(ZoomControl(options=['position', 'zoom_in_text', 'zoom_in_title', 'zo…" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "import ipywidgets\n", - "m = stac_ipyleaflet.StacIpyleaflet(zoom=4, layout=ipywidgets.Layout(height=\"800px\"))\n", - "m.draw_biomass_map()\n", + "import stac_ipyleaflet\n", + "# from ipywidgets import Layout\n", + "m = stac_ipyleaflet.StacIpyleaflet()\n", "m" ] }, { "cell_type": "code", "execution_count": null, - "id": "a6b9c37b-58d9-4d17-8891-8eed125eb996", + "id": "d7a3e19b-6e6c-4a4d-95ef-b253d28af4ad", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d2c9bb0-8435-449e-bf44-03e628556239", "metadata": {}, "outputs": [], "source": [] @@ -90,7 +125,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.8" + "version": "3.10.10" } }, "nbformat": 4, diff --git a/requirements.txt b/requirements.txt index 956469d..c8f691e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -ipyleaflet==0.17.2 -ipywidgets==8.0.4 +ipyleaflet>=0.17.2 +ipywidgets>=8.0.4 ipyevents matplotlib pydantic diff --git a/setup.cfg b/setup.cfg index 2f3da0e..bfccf40 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,16 +1,16 @@ [metadata] name = stac_ipyleaflet -version=0.1.0 +version=0.2.0 author = Aimee Barciauskas -description=ipyleaflet customized for discovering, visualizing and interacting with STAC and workspace data. +description=ipyleaflet customized for discovering, visualizing and interacting with STAC data. long_description = file: README.md url = https://github.com/abarciauskas-bgse/stac_ipyleaflet keywords = stac_ipyleaflet author_email=aimee@developmentseed.org license=MIT license classifiers = - Development Status :: 2 - Pre-Alpha - Intended Audience :: Developers + Development Status :: 2 - Beta + Intended Audience :: NASA Scientists License :: OSI Approved :: MIT License Natural Language :: English Programming Language :: Python :: 3.8 @@ -18,7 +18,7 @@ classifiers = Programming Language :: Python :: 3.10 [bumpversion] -current_version = 0.1.0 +current_version = 0.2.0 commit = True tag = True @@ -44,8 +44,8 @@ include_package_data=True zip_safe=False tests_require=[pytest>=3] install_requires = - ipyleaflet==0.17.2 - ipywidgets==8.0.4 + ipyleaflet>=0.17.2 + ipywidgets>=8.0.4 ipyevents matplotlib pydantic diff --git a/stac_ipyleaflet/__init__.py b/stac_ipyleaflet/__init__.py index 107d8a6..c2d6487 100644 --- a/stac_ipyleaflet/__init__.py +++ b/stac_ipyleaflet/__init__.py @@ -1,7 +1 @@ -"""Top-level package for stac ipyleaflet.""" - -__author__ = """Aimee Barciauskas""" -__email__ = 'aimee@developmentseed.org' -__version__ = '0.1.0' - -from .core import * +from stac_ipyleaflet.core import * diff --git a/stac_ipyleaflet/core.py b/stac_ipyleaflet/core.py index eaa7c54..a77a3f3 100644 --- a/stac_ipyleaflet/core.py +++ b/stac_ipyleaflet/core.py @@ -1,168 +1,341 @@ """Main module.""" -import os import csv -from io import BytesIO -import re -import requests - -from ipyleaflet import Map, DrawControl, WidgetControl, TileLayer, Popup -from stac_ipyleaflet.stac_discovery.stac_widget import StacDiscoveryWidget from importlib.resources import files +from ipyleaflet import Map, DrawControl, Popup, TileLayer, WidgetControl from IPython.display import display -import ipywidgets -from ipywidgets import HTML +from ipywidgets import Box, HBox, VBox, Layout, SelectionSlider, HTML, IntSlider, Image +from ipywidgets import Checkbox, Dropdown, Tab, ToggleButton, Button +from ipywidgets import HTML, Output, jslink import matplotlib.pyplot as plt -#from pydantic import BaseModel -from shapely.geometry import Polygon -import rioxarray -import xarray as xr import numpy -import numpy.ma as ma +import re +import requests from rio_tiler.io import Reader from rio_tiler.mosaic import mosaic_reader from rio_tiler.models import ImageData +import rioxarray +from shapely.geometry import Polygon +import xarray as xr + +from stac_ipyleaflet.stac_discovery.stac_widget import StacDiscoveryWidget +from stac_ipyleaflet.widgets.basemaps import BasemapsWidget +from stac_ipyleaflet.widgets.draw import DrawControlWidget class StacIpyleaflet(Map): """ Stac ipyleaflet is an extension to ipyleaflet `Map`. """ + + raw_input = input + draw_control: DrawControl - # TODO(aimee): right now this is specific to MAAP but we should make it generic. - titiler_endpoint: str = "https://titiler.maap-project.org" - titiler_stac_endpoint: str = "https://titiler-stac.maap-project.org" histogram_layer: Popup warning_layer: Popup = None loading_widget_layer: Popup = None bbox_centroid: list = [] + titiler_endpoint = "https://titiler.maap-project.org" + titiler_stac_endpoint = "https://titiler-stac.maap-project.org" + def __init__(self, **kwargs): if "center" not in kwargs: kwargs["center"] = [20, 0] if "zoom" not in kwargs: - kwargs["zoom"] = 2 + kwargs["zoom"] = 4 + + if "layout" not in kwargs: + kwargs["layout"] = Layout(height="600px") + + if "scroll_wheel_zoom" not in kwargs: + kwargs["scroll_wheel_zoom"] = True # Create map - super().__init__(**kwargs) + super().__init__(**kwargs) + + self.accent_color = "SteelBlue" + self.layers = BasemapsWidget.template(self) - # Add rectangle draw control for bounding box - # TODO(aimee): Remove the other draw controls + self.buttons = {} self.selected_data = [] - self.draw_control = None self.histogram_layer = None - draw_control = DrawControl( - edit=True, - remove=True, - ) - draw_control.rectangle = { - "shapeOptions": { - "fillColor": "transparent", - "color": "#333", - "fillOpacity": 1.0 - } - } - self.add_control(draw_control) - self.draw_control = draw_control - + self.draw_control_added = False + self.aoi_coordinates = [] + self.aoi_bbox = () + gif_file = files('stac_ipyleaflet.data').joinpath('loading.gif') with open(gif_file, "rb") as f: - gif_widget=ipywidgets.Image( + gif_widget=Image( value=f.read(), format='png', width=200, height=200, ) - loading_widget=ipywidgets.VBox() + loading_widget=VBox() loading_widget.children=[gif_widget] - loading_location = self.bbox_centroid or self.center self.loading_widget_layer = Popup(child=loading_widget, min_width=200, min_height=200) + main_button_layout = Layout(width="120px", height="35px", border="1px solid #4682B4") + draw_btn = ToggleButton(description="Draw", icon="square-o", layout=main_button_layout) + draw_btn.style.text_color = self.accent_color + draw_btn.style.button_color = "transparent" + draw_btn.tooltip = "Draw Area of Interest" + draw_btn.observe(self.toggle_draw_widget_display, type="change", names=["value"]) + self.buttons["draw"] = draw_btn + layers_btn = ToggleButton(description="Layers", icon="map-o", layout=main_button_layout) + layers_btn.style.text_color = self.accent_color + layers_btn.style.button_color = "transparent" + layers_btn.tooltip = "Open/Close Layers Menu" + layers_btn.observe(self.toggle_layers_widget_display, type="change", names=["value"]) + self.buttons["layers"] = layers_btn + """ histogram_btn = Button(description="Histogram", icon="bar-chart") + histogram_btn.style.text_color = "white" + histogram_btn.style.button_color = self.accent_color + histogram_btn.on_click(self.create_histograms) """ + stac_btn = ToggleButton(description="STAC Data", icon="search", layout=main_button_layout) + stac_btn.style.text_color = self.accent_color + stac_btn.style.button_color = "white" + stac_btn.tooltip = "Open/Close STAC Data Search" + stac_btn.observe(self.toggle_stac_widget_display, type="change", names=["value"]) + self.buttons["stac"] = stac_btn + + buttons_box_layout = Layout(display='flex', + flex_flow='row', + align_items='center', + justify_content='center', + width='100%', + height="50px") + buttons_box = HBox(children=[draw_btn, layers_btn, stac_btn],layout=buttons_box_layout) + display(buttons_box) + + self.add_biomass_layers() + self.add_custom_tools() + self.draw_control = DrawControlWidget.template(self) + return None - def layers_button_clicked(self, b): - layers_widget = self.layers_widget - if layers_widget.layout.display == 'none': - layers_widget.layout.display = 'block' - elif layers_widget.layout.display == 'block': - layers_widget.layout.display = 'none' - - def stac_widget_display(self, b): - stac_widget = self.stac_widget - if stac_widget.layout.display == 'none': - stac_widget.layout.display = 'block' - elif stac_widget.layout.display == 'block': - stac_widget.layout.display = 'none' - - def add_layers_widget(self): - # Adds a list of layers to toggle on and off via checkboxes - layers_widget = ipywidgets.VBox() - layers_hbox = [] - - for layer in self.layers: - layer_chk = ipywidgets.Checkbox( - value=layer.visible, - description=layer.name, - indent=False - ) - ipywidgets.jslink((layer_chk, "value"), (layer, "visible")) - hbox = ipywidgets.HBox( - [layer_chk] - ) - layers_hbox.append(hbox) - layers_widget.children = layers_hbox + #logic to handle main menu toggle buttons + def toggle_layers_widget_display(self, b): + if b["new"]: + if self.layers_widget.layout.display == 'none': + self.layers_widget.layout.display = 'block' + self.stac_widget.layout.display = 'none' + self.aoi_widget.layout.display = 'none' + self.buttons["stac"].value = False + self.buttons["draw"].value = False + if self.draw_control_added: + self.remove(self.draw_control) + if not b["new"]: + if self.layers_widget.layout.display == 'block': + self.layers_widget.layout.display = 'none' + + def toggle_stac_widget_display(self, b): + if b["new"]: + if self.stac_widget.layout.display == 'none': + self.stac_widget.layout.display = 'block' + self.layers_widget.layout.display = 'none' + self.aoi_widget.layout.display = 'none' + self.buttons["layers"].value = False + self.buttons["draw"].value = False + if self.draw_control_added: + self.remove(self.draw_control) + if not b["new"]: + if self.stac_widget.layout.display == 'block': + self.stac_widget.layout.display = 'none' + + def toggle_draw_widget_display(self, b): + if b["new"]: + if self.aoi_widget.layout.display == 'none': + self.aoi_widget.layout.display = 'block' + self.add_control(self.draw_control) + self.draw_control_added = True + self.stac_widget.layout.display = 'none' + self.layers_widget.layout.display = 'none' + self.buttons["stac"].value = False + self.buttons["layers"].value = False + if not b["new"]: + if self.aoi_widget.layout.display == 'block': + self.aoi_widget.layout.display = 'none' + if self.draw_control_added: + self.remove(self.draw_control) + self.draw_control_added = False + + def create_aoi_widget(self): + aoi_widget = HBox(layout=Layout(width="300px", padding="0px 6px 2px 6px", margin="0px 2px 2px 2px")) + aoi_widget.layout.flex_flow="column" + aoi_widget.layout.min_width="300px" + aoi_widget.layout.max_height="360px" + aoi_widget.layout.overflow="auto" + + aoi_widget_desc = HTML( + value="
Waiting for area of interest...
",
+ description="",
+ )
+ aoi_clear_button = Button(
+ description="Clear AOI Polygon",
+ tooltip="Clear AOI Polygon",
+ icon="trash",
+ disabled=True,
+ # layout=Layout(margin="4px 0 8px 0")
+ )
+
+ aoi_widget.children = [aoi_widget_desc, aoi_html, aoi_clear_button]
+ aoi_widget.layout.display ='none'
+
+ return aoi_widget
+
+ def create_layers_widget(self):
+
+ layers_widget = Box(style= { "max-width: 420px" })
+ layers_widget.layout.flex_flow="column"
+ layers_widget.layout.max_height="360px"
+ layers_widget.layout.overflow="auto"
+
+ tab_headers = ['Biomass Layers', 'Basemaps']
+ tab_children = []
+ tab_widget = Tab()
+
+ out = Output()
+ display(out)
+
+
+ opacity_values = [i*10 for i in range(10+1)] # [0.001, 0.002, ...]
+
+ def handle_basemap_opacity_change(change):
+ selected_bm = self.basemap_selection_dd.value
+ for l in self.layers:
+ if l.base:
+ if l.name == selected_bm:
+ l.opacity = change['new']/100
+
+ def handle_layer_opacity_change(change):
+ selected_layer = change.owner.description
+ for l in self.layers:
+ if l.name == selected_layer:
+ l.opacity = change['new']
+
+ for tab in tab_headers:
+ tab_content = VBox()
+ listed_layers = []
+ # sort layers by name property
+ layers_in_drawing_order = [l for l in self.layers]
+ layerlist_layers = sorted(layers_in_drawing_order, key=lambda x: x.name, reverse=False)
+ if tab == "Biomass Layers":
+ layers_hbox = []
+ for layer in layerlist_layers:
+ # check if layer name is a basemap
+ if not layer.base:
+ layer_checkbox = Checkbox(
+ value=layer.visible,
+ description=layer.name,
+ indent=False
+ )
+ jslink((layer_checkbox, "value"), (layer, "visible"))
+ hbox = HBox(
+ [layer_checkbox]
+ )
+ layer_opacity_slider = SelectionSlider(
+ value=1,
+ options=[("%g"%i, i/100) for i in opacity_values],
+ description=f"{layer.name}",
+ continuous_update=False,
+ orientation='horizontal',
+ layout=Layout(margin="-12px 0 4px 0")
+ )
+ layer_opacity_slider.style.description_width = "0px"
+ layer_opacity_slider.style.handle_color = self.accent_color
+ layer_opacity_slider.observe(handle_layer_opacity_change, names="value")
+ layers_hbox.append(hbox)
+ layers_hbox.append(layer_opacity_slider)
+ listed_layers.append(layer.name)
+ tab_content.children = [VBox(layers_hbox)]
+ tab_children.append(tab_content)
+ elif tab == "Basemaps":
+ basemaps = []
+ for layer in layerlist_layers:
+ # check if layer is a basemap
+ if layer.base:
+ basemaps.append((f"{layer.name}", f"{layer.name}"))
+
+ def on_change(change):
+ if change['type'] == 'change' and change['name'] == 'value':
+ with out:
+ out.clear_output()
+ #print("changed to %s" % change['new'])
+ for l in self.layers:
+ if l.base:
+ if l.name == change['new']:
+ l.opacity = basemap_opacity_slider.value/100
+ l.visible = True
+ else:
+ l.visible = False
+ return
+ dropdown = Dropdown(options=basemaps, value="Open Street Map")
+ self.basemap_selection_dd = dropdown
+ dropdown.observe(on_change)
+
+ basemap_opacity_slider = IntSlider(
+ value=100,
+ min=0,
+ max=100,
+ step=10,
+ description='% Opacity:',
+ #disabled=False,
+ style={'bar_color': 'maroon'},
+ continuous_update=False,
+ orientation='horizontal',
+ readout=True,
+ readout_format='d'
+ )
+
+ basemap_opacity_slider.style.handle_color = self.accent_color
+ basemap_opacity_slider.observe(handle_basemap_opacity_change, names="value")
+ tab_content.children = [dropdown, basemap_opacity_slider]
+ tab_children.append(tab_content)
+
+ tab_widget.children = tab_children
+ tab_widget.titles = tab_headers
+ print(tab_widget.box_style)
+ layers_widget.children = [tab_widget]
layers_widget.layout.display ='none'
return layers_widget
+
- def add_toolbar(self):
- # Add a widget for the layers
- self.layers_widget = self.add_layers_widget()
+ def add_custom_tools(self):
+ # Create custom map widgets
+ self.layers_widget = self.create_layers_widget()
self.stac_widget = StacDiscoveryWidget.template(self)
+ self.aoi_widget = self.create_aoi_widget()
- # Add a button to toggle the layers checkbox widget on and off
- stac_widget_button = ipywidgets.Button(
- tooltip="STAC Discovery",
- icon="stack-exchange",
- layout=ipywidgets.Layout(height="28px", width="38px"),
- )
- stac_widget_button.on_click(self.stac_widget_display)
-
- layers_button = ipywidgets.Button(
- tooltip="Open Layers List",
- icon="map-o",
- layout=ipywidgets.Layout(height="28px", width="38px"),
- )
+ layers_widget = VBox([self.layers_widget])
+ stac_widget = VBox([self.stac_widget])
+ aoi_widget = VBox([self.aoi_widget])
- layers_button.on_click(self.layers_button_clicked)
-
- hist_button = ipywidgets.Button(
- tooltip="Create histogram",
- icon="bar-chart",
- layout=ipywidgets.Layout(height="28px", width="38px"),
- )
- hist_button.on_click(self.create_histograms)
- toolbar_widget = ipywidgets.VBox()
- toolbar_widget.children = [layers_button, hist_button, self.layers_widget]
- toolbar_control = WidgetControl(widget=toolbar_widget, position="topright")
- self.add(toolbar_control)
-
- stac_widget = ipywidgets.VBox()
- stac_widget.children = [stac_widget_button, self.stac_widget]
- self.add(WidgetControl(widget=stac_widget, position="topright"))
+ layers_control = WidgetControl(widget=layers_widget, position="topright", id="layers_widget")
+ stack_control = WidgetControl(widget=stac_widget, position="topright", id="stac_widget")
+ aoi_control = WidgetControl(widget=aoi_widget, position="topright", id="aoi_widget")
+
+ self.add(layers_control)
+ self.add(stack_control)
+ self.add(aoi_control)
def add_biomass_layers(self):
biomass_file = files('stac_ipyleaflet.data').joinpath('biomass-layers.csv')
with open(biomass_file, newline='') as f:
csv_reader = csv.reader(f)
next(csv_reader, None) # skip the headers
- for row in csv_reader:
+ sorted_csv = sorted(csv_reader, key=lambda row: row[0], reverse=True)
+ for row in sorted_csv:
name, tile_url = row[0], row[1]
tile_layer = TileLayer(url=tile_url, attribution=name, name=name, visible=False)
self.add_layer(tile_layer)
def find_layer(self, name: str):
layers = self.layers
-
for layer in layers:
if layer.name == name:
return layer
@@ -210,7 +383,7 @@ def add_tile_layer(
print("Failed to add the specified TileLayer.")
raise Exception(e)
- def gen_mosaic_dataset_reader(self, assets, bounds):
+ """ def gen_mosaic_dataset_reader(self, assets, bounds):
# see https://github.com/cogeotiff/rio-tiler/blob/main/rio_tiler/io/rasterio.py#L368-L380
def _part_read(src_path: str, *args, **kwargs) -> ImageData:
with Reader(src_path) as src:
@@ -233,9 +406,9 @@ def _part_read(src_path: str, *args, **kwargs) -> ImageData:
# for ii, b in enumerate(img.count):
# h_counts, h_keys = numpy.histogram(data[b].compressed())
# hist[f"b{ii + 1}"] = [h_counts.tolist(), h_keys.tolist()]
- return xr.DataArray(data)
+ return xr.DataArray(data) """
- def update_selected_data(self):
+ """ def update_selected_data(self):
layers = self.layers
# TODO(aimee): if geometry hasn't changed and a previously selected layer is still selected, don't re-fetch it.
self.selected_data = []
@@ -255,7 +428,7 @@ def update_selected_data(self):
else:
self.loading_widget_layer.open_popup()
- for idx, layer in enumerate(visible_layers):
+ for layer in visible_layers:
layer_url = layer.url
ds = None
title = layer.name.replace('_', ' ').upper()
@@ -269,7 +442,7 @@ def update_selected_data(self):
ds = xds.sel(x=slice(bounds[0], bounds[2]), y=slice(bounds[3], bounds[1]))
else:
uuid_pattern = r'([a-f\d]{8}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{12})'
- match = re.search(f"({self.titiler_endpoint}/mosaics/{uuid_pattern})/tiles", layer_url)
+ match = re.search(f"({titiler_endpoint}/mosaics/{uuid_pattern})/tiles", layer_url)
if match:
mosaic_url = match.groups()[0]
# From titiler docs http://titiler.maap-project.org/docs
@@ -278,35 +451,37 @@ def update_selected_data(self):
assets_endpoint = f"{self.titiler_stac_endpoint}/mosaicjson/{str_bounds}/assets?url={mosaic_url}/mosaicjson"
# create a dataset from multiple COGs
assets_response = requests.get(assets_endpoint)
- datasets = []
- assets = assets_response.json()
- ds = self.gen_mosaic_dataset_reader(assets, bounds)
+ if assets_response.status_code == 200:
+ assets = assets_response.json()
+ ds = self.gen_mosaic_dataset_reader(assets, bounds)
if ds.any():
ds.attrs["title"] = title
self.selected_data.append(ds)
- return self.selected_data
+ return self.selected_data """
- def error_message(self, msg):
- out = ipywidgets.Output()
+ """ def error_message(self, msg):
+ out = Output()
with out:
print(msg)
self.gen_popup_icon(msg)
display()
- return
+ return """
# TODO(aimee): if you try and create a histogram for more than one layer, it creates duplicates in the popup
- def create_histograms(self, b):
+ """ def create_histograms(self, b):
+ print(self, b)
if self.histogram_layer in self.layers:
self.remove_layer(self.histogram_layer)
# TODO(aimee): make this configurable
minx, maxx = [0, 500]
plot_args = {"range": (minx, maxx)}
fig = plt.figure()
- hist_widget = ipywidgets.VBox()
+ hist_widget = VBox()
try:
self.update_selected_data()
except Exception as e:
return self.error_message(e)
+
if len(self.selected_data) == 0:
return self.error_message("No data or bounding box selected.")
else:
@@ -314,7 +489,7 @@ def create_histograms(self, b):
axes = fig.add_subplot(int(f"22{idx+1}"))
plot_args['ax'] = axes
# create a histogram
- out = ipywidgets.Output()
+ out = Output()
with out:
out.clear_output()
try:
@@ -332,17 +507,12 @@ def create_histograms(self, b):
self.histogram_layer = histogram_layer
self.remove_layer(self.loading_widget_layer)
self.add_layer(histogram_layer)
- return None
-
- def draw_biomass_map(self):
- self.add_biomass_layers()
- self.add_toolbar()
- return None
-
+ return None """
+
# generates warning/error popup
- def gen_popup_icon(self, msg):
+ """ def gen_popup_icon(self, msg):
warning_msg = HTML()
warning_msg.value=f"{msg}"
popup_warning = Popup(location=self.bbox_centroid or self.center, draggable=True, child=warning_msg)
self.warning_layer=popup_warning
- self.add_layer(popup_warning);
+ self.add_layer(popup_warning) """
diff --git a/stac_ipyleaflet/data/biomass-layers.csv b/stac_ipyleaflet/data/biomass-layers.csv
index 6b61b22..1272038 100644
--- a/stac_ipyleaflet/data/biomass-layers.csv
+++ b/stac_ipyleaflet/data/biomass-layers.csv
@@ -9,6 +9,6 @@ GEDI L4B,"https://titiler.dit.maap-project.org/cog/tiles/{z}/{x}/{y}.png?url=s3:
GEDI L4B SE,"https://titiler.dit.maap-project.org/cog/tiles/{z}/{x}/{y}.png?url=s3://ornl-cumulus-prod-protected/gedi/GEDI_L4B_Gridded_Biomass/data/GEDI04_B_MW019MW138_02_002_05_R01000M_SE.tif&rescale=0,310&colormap_name=reds"
CCI Biomass,"https://titiler.maap-project.org/mosaics/f343756d-bf15-4095-a8f3-f4fcbb26b5f9/tiles/{z}/{x}/{y}?rescale=0,400&bidx=1&colormap_name=gist_earth_r"
CCI Biomass SD,"https://titiler.maap-project.org/mosaics/f343756d-bf15-4095-a8f3-f4fcbb26b5f9/tiles/{z}/{x}/{y}?rescale=0,500&bidx=2&colormap_name=reds"
-Paraguay Estimated Biomass,"https://titiler.maap-project.org/cog/tiles/{z}/{x}/{y}.png?url=s3://maap-landing-zone-gccops/user-added/uploaded_objects/0bfec58c-45fb-464e-b301-b1afbdf5249e/5_Biomass_cog.masked.tif&nodata=0&bidx=1&rescale=0,400&colormap_name=gist_earth_r"
+Paraguay Estimated Biomass,"https://titiler.maap-project.org/cog/tiles/{z}/{x}/{y}.png?url=s3://maap-landing-zone-gccops/user-added/uploaded_objects/0bfec58c-45fb-464e-b301-b1afbdf5249e/5_biomass_cog.masked.tif&nodata=0&bidx=1&rescale=0,400&colormap_name=gist_earth_r"
Paraguay Forest Mask,"https://titiler.maap-project.org/cog/tiles/{z}/{x}/{y}.png?url=s3://maap-landing-zone-gccops/user-added/uploaded_objects/45fe2e6f-2007-4cb1-964a-f337f39f4fdc/1_forest_cog.masked.tif&rescale=0,1&nodata=0&colormap_name=greens"
Paraguay Tree Cover,"https://titiler.maap-project.org/cog/tiles/{z}/{x}/{y}.png?url=s3://maap-landing-zone-gccops/user-added/uploaded_objects/ee5eb60c-3c01-4789-ae8e-c03f1d719440/4_tree_cover_cog.masked.tif&rescale=0,75&nodata=0&colormap_name=greens"
diff --git a/stac_ipyleaflet/stac_discovery/stac.py b/stac_ipyleaflet/stac_discovery/stac.py
index 327b1f2..e396d71 100644
--- a/stac_ipyleaflet/stac_discovery/stac.py
+++ b/stac_ipyleaflet/stac_discovery/stac.py
@@ -1,38 +1,74 @@
-# Code taken from https://github.com/giswqs/leafmap/blob/master/leafmap/stac.py
-
+# [SOME] code taken from https://github.com/giswqs/leafmap/blob/master/leafmap/stac.py
from pystac_client import ItemSearch
-import os
-import pystac
import requests
class Stac():
- def stac_bands(url=None, collection=None, item=None, titiler_stac_endpoint=None, **kwargs):
- """Get band names of a single SpatialTemporal Asset Catalog (STAC) item.
+ def organize_collections(collections=[]):
+ output_collections = []
+ # print(type(collections), collections)
+ for collection in collections:
+ try:
+ data = collection.to_dict()
+ # print(data)
+ collection_obj = {}
+ collection_obj["id"] = data["id"].strip()
+ collection_obj["title"] = data["title"].strip()
+
+ start_date = data["extent"]["temporal"]["interval"][0][0]
+ end_date = data["extent"]["temporal"]["interval"][0][1]
+
+ if start_date is not None:
+ collection_obj["start_date"] = start_date.split("T")[0]
+ else:
+ collection_obj["start_date"] = ""
+
+ if end_date is not None:
+ collection_obj["end_date"] = end_date.split("T")[0]
+ else:
+ collection_obj["end_date"] = ""
+ collection_obj["bbox"] = ", ".join(
+ [str(coord) for coord in data["extent"]["spatial"]["bbox"][0]]
+ )
+
+ for l in data["links"]:
+ if l["rel"] == "about":
+ collection_obj["metadata"] = l["href"]
+ if l["rel"] == "self":
+ collection_obj["href"] = l["href"]
+
+ collection_obj["description"] = (
+ data["description"]
+ .replace("\n", " ")
+ .replace("\r", " ")
+ .replace("\\u", " ")
+ .replace(" ", " ")
+ )
+ collection_obj["license"] = data["license"]
+ output_collections.append(collection_obj)
+ except Exception as err:
+ print("Error: ", collection)
+ print(err)
+ return None
+ if len(output_collections) > 0:
+ output_collections.sort(key= lambda x:x['title'])
+ return output_collections
+
+ def get_item_info(url=None, **kwargs):
+ """Get INFO of a single SpatialTemporal Asset Catalog (STAC) **COG** item.
Args:
url (str): HTTP URL to a STAC item
- collection (str): STAC collection ID, e.g., landsat-8-c2-l2.
- item (str): STAC item ID, e.g., LC08_L2SP_047027_20201204_02_T1.
- titiler_stac_endpoint (str, optional): Titiler endpoint. Defaults to None.
Returns:
- list: A list of band names
+ json: Response with Item info.
"""
- if url is None and collection is None:
- raise ValueError("Either url or collection must be specified. stac_bands")
-
- if url is not None:
- kwargs["url"] = url
- if collection is not None:
- kwargs["collection"] = collection
- if item is not None:
- kwargs["item"] = item
-
- if isinstance(titiler_stac_endpoint, str):
- r = requests.get(f"{titiler_stac_endpoint}/stac/assets", params=kwargs).json()
- else:
- r = requests.get(titiler_stac_endpoint.url_for_stac_assets(), params=kwargs).json()
+ if url is None:
+ raise ValueError("Item url must be specified to get stac_bands")
+ if isinstance(url, str):
+ r = requests.get(f"{url}", ).json()
+
return r
+
def stac_tile(
url=None,
@@ -155,6 +191,7 @@ def add_stac_layer(
tile_url = Stac.stac_tile(
url, collection, item, assets, bands, titiler_stac_endpoint, **kwargs
)
+ return tile_url
bounds = Stac.stac_bounds(url, collection, item, titiler_stac_endpoint)
self.add_tile_layer(tile_url, name, attribution, opacity, shown)
self.fit_bounds([[bounds[1], bounds[0]], [bounds[3], bounds[2]]])
@@ -233,4 +270,87 @@ def stac_search(
return info
else:
return search
+
+ def get_metadata(
+ data_type="cog",
+ titiler_stac_endpoint=None,
+ url=None,
+ max_size=None,
+ **kwargs,
+ ):
+ if url is not None:
+ kwargs["url"] = url
+ if max_size is not None:
+ kwargs["max_size"] = max_size
+
+ if isinstance(titiler_stac_endpoint, str):
+ r = requests.get(f"{titiler_stac_endpoint}/{data_type}/metadata", params=kwargs).json()
+ return r
+ else:
+ return "Cannot process request: titiler stac endpoint not provided."
+
+ def get_tile_url(
+ data_type="cog",
+ url=None,
+ collection=None,
+ item=None,
+ assets=None,
+ bands=None,
+ palette=None,
+ titiler_stac_endpoint=None,
+ **kwargs,
+ ):
+ """Get a tile layer url from a single SpatialTemporal Asset Catalog (STAC) item.
+ Args:
+ url (str): HTTP URL to a STAC item
+ collection (str): STAC collection ID, e.g., landsat-8-c2-l2.
+ item (str): STAC item ID, e.g., LC08_L2SP_047027_20201204_02_T1.
+ assets (str | list): STAC asset ID, e.g., ["SR_B7", "SR_B5", "SR_B4"].
+ bands (list): A list of band names, e.g., ["SR_B7", "SR_B5", "SR_B4"]
+ titiler_stac_endpoint (str, optional): Titiler endpoint, Defaults to None.
+ Returns:
+ str: Returns the STAC Tile layer URL.
+ """
+ if url is None and collection is None:
+ raise ValueError("Either url or collection must be specified. stac_tile")
+
+ kwargs["rescale"] = "0,50"
+
+ if url is not None:
+ kwargs["url"] = url
+ if collection is not None:
+ kwargs["collection"] = collection
+ if item is not None:
+ kwargs["item"] = item
+
+ if palette is not None:
+ # kwargs["colormap_name"] = kwargs["palette"].lower()
+ kwargs["colormap_name"] = palette
+ # del kwargs["palette"]
+
+ if isinstance(bands, list) and len(set(bands)) == 1:
+ bands = bands[0]
+
+ if isinstance(assets, list) and len(set(assets)) == 1:
+ assets = assets[0]
+
+ if isinstance(bands, str):
+ bands = bands.split(",")
+ if isinstance(assets, str):
+ assets = assets.split(",")
+
+ kwargs["assets"] = assets
+ TileMatrixSetId = "WebMercatorQuad"
+ if "TileMatrixSetId" in kwargs.keys():
+ TileMatrixSetId = kwargs["TileMatrixSetId"]
+ kwargs.pop("TileMatrixSetId")
+
+ if isinstance(titiler_stac_endpoint, str):
+ r = requests.get(
+ f"{titiler_stac_endpoint}/{data_type}/{TileMatrixSetId}/tilejson.json",
+ params=kwargs,
+ ).json()
+ return r
+ else:
+ return "STAC ENDPOINT IS NECESSARY."
\ No newline at end of file
diff --git a/stac_ipyleaflet/stac_discovery/stac_widget.py b/stac_ipyleaflet/stac_discovery/stac_widget.py
index d232c89..e140b6f 100644
--- a/stac_ipyleaflet/stac_discovery/stac_widget.py
+++ b/stac_ipyleaflet/stac_discovery/stac_widget.py
@@ -1,151 +1,278 @@
-from ipywidgets import VBox, Output, Layout, Text, ToggleButtons, Dropdown, DatePicker, HBox, Textarea, Checkbox
-import pandas as pd
-from importlib.resources import files
from datetime import datetime
-from pathlib import Path
-from typing import Any
-from .stac import Stac
+from ipywidgets import Box, Combobox, DatePicker, Dropdown, HBox, HTML
+from ipywidgets import Layout, Output, RadioButtons, Tab, ToggleButtons, VBox
+from pystac_client import Client
+from stac_ipyleaflet.stac_discovery.stac import Stac
class StacDiscoveryWidget():
- def template(self) -> VBox():
- standard_width = "400px"
- padding = "0px 0px 0px 5px"
- style = {"description_width": "initial"}
+ def template(self) -> Box( style={"max_height: 200px"}):
+ titiler_stac_endpoint = "https://titiler.maap-project.org"
+ standard_width = "440px"
+ styles = {
+ "init": {"description_width": "initial",},
+ "desc": "white-space:normal;font-size:smaller; max-height:80px;"
+ }
+ 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", 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, padding=padding, overflow="auto")
+ layout=Layout(width=standard_width, height="200px", padding="4px 8px 4px 8px", overflow="auto")
)
- stac_data = []
# Templates for the STAC Discovery Widget
stac_widget = VBox()
- padding = "0px 0px 0px 5px"
- style = {"description_width": "initial"}
-
- nasa_cmr_path = files("stac_ipyleaflet") / "stac_discovery" / "catalogs" / "nasa_maap_stac.tsv"
- stac_info = {
- "MAAP STAC": {
- "filename": nasa_cmr_path,
- "name": "id",
- "url": "href",
- "description": "title",
- }
+ 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": "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]
+ 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
+ if "collections" not in selected_catalog:
+ print("COLLECTIONS NOT FOUND")
+ return
+ # else:
+ selected_collection_options = sorted([c for c in selected_catalog["collections"]], key=lambda c: c["id"])
+ selected_collection = selected_collection_options[0]
+ self.stac_data = {
+ "catalog": selected_catalog,
+ "collection": selected_collection,
+ "items": [],
+ "layer_added": False
}
- connections = list(stac_info.keys())
-
- # Template
- catalogs = Dropdown(
- options=connections,
- value="MAAP STAC",
- description="Catalog:",
- style=style,
- layout=Layout(width="450px", padding=padding),
+ # STAC Widget Items
+ catalogs_dropdown = Dropdown(
+ options=catalog_options,
+ value=self.stac_data["catalog"]["name"],
+ style=styles["init"],
+ disabled=True,
+ layout=layouts["default"],
)
-
- df = pd.read_csv(stac_info[catalogs.value]["filename"], sep="\t")
- datasets = df[stac_info[catalogs.value]["name"]].tolist()
- default = df.iloc[0] # values mapped to .tsv
-
- collection = Dropdown(
- options=datasets,
- value=default["id"],
- description="Collection:",
- style=style,
- layout=Layout(width="450px", padding=padding),
+ catalogs_box = VBox(
+ [
+ HTML(value="Catalog", style=styles["init"], layout=layouts["header"],),
+ catalogs_dropdown
+ ]
+ )
+ collections_dropdown = Dropdown(
+ options=[c["id"] for c in selected_collection_options],
+ value=self.stac_data["collection"]["id"],
+ style=styles["init"],
+ layout=layouts["default"],
+ )
+ collections_box = VBox(
+ [
+ HTML(value="Collection", style=styles["init"], layout=layouts["header"],),
+ collections_dropdown
+ ]
)
- collection_description = Text(
- value=default["description"],
- description="Description:",
- style=style,
- layout=Layout(width="450px", padding=padding),
+ collection_description = HTML(
+ value=f'Waiting for area of interest...
"
+ aoi_clear_button.disabled = True
+
+ def handle_draw(self, action, geo_json, **kwargs):
+ main.aoi_coordinates = []
+ main.aoi_bbox = ()
+
+
+ if action == "created":
+ if geo_json["geometry"]:
+ geojson_layer = GeoJSON(
+ name="draw_layer",
+ data=geo_json,
+ style={
+ "fillColor": "transparent",
+ "color": "#333",
+ "weight": 3
+ }
+ )
+ main.add_layer(geojson_layer)
+ raw_coordinates = geo_json["geometry"]["coordinates"][0]
+ def bounding_box(points):
+ x_coordinates, y_coordinates = zip(*points)
+ return (min(x_coordinates), min(y_coordinates), max(x_coordinates), max(y_coordinates))
+ bbox = bounding_box(raw_coordinates)
+ main.aoi_coordinates = raw_coordinates
+ main.aoi_bbox = bbox
+ coords_list = [coord for coord in raw_coordinates]
+ coords = (",Coordinates:
{coords}
BBox:
{bbox}
"
+ self.clear()
+ aoi_clear_button.disabled = False
+ aoi_clear_button.on_click(handle_clear)
+
+ return
+
+ def value_changed(change):
+ print("CHANGE", change)
+
+ draw_control.on_draw(callback=handle_draw)
+ draw_control.observe(value_changed, names=["value"])
+
+ # Add rectangle draw control for bounding box
+ draw_control.rectangle = {
+ "shapeOptions": {
+ "fillColor": "transparent",
+ "color": "#333",
+ "fillOpacity": 1.0
+ },
+ "repeatMode": False
+ }
+ draw_control.output = bbox_out
+
+ return draw_control