Skip to content

Commit

Permalink
Add STAC API Browser GUI (opengeos#347)
Browse files Browse the repository at this point in the history
* Pin ipywidgets version opengeos#345

* Added STAC custom endpoint GUI

* Added support for custom STAC endpoint

* Fixed stac search time range bug

* Added STAC search footprints

* Set fit bounds to False

* Set default footprint style

* Added stac module to mkdocs

* Added support for Planetary Computer
  • Loading branch information
giswqs authored Jan 31, 2023
1 parent 4dcf2c1 commit 0caecd8
Show file tree
Hide file tree
Showing 7 changed files with 357 additions and 67 deletions.
6 changes: 5 additions & 1 deletion leafmap/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -996,11 +996,15 @@ def bbox_to_geojson(bounds):
"""Convert coordinates of a bounding box to a geojson.
Args:
bounds (list): A list of coordinates representing [left, bottom, right, top].
bounds (list | tuple): A list of coordinates representing [left, bottom, right, top] or m.bounds.
Returns:
dict: A geojson feature.
"""

if isinstance(bounds, tuple) and len(bounds) == 2:
bounds = [bounds[0][1], bounds[0][0], bounds[1][1], bounds[1][0]]

return {
"geometry": {
"type": "Polygon",
Expand Down
8 changes: 6 additions & 2 deletions leafmap/foliumap.py
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,7 @@ def add_stac_layer(
attribution=".",
opacity=1.0,
shown=True,
fit_bounds=True,
**kwargs,
):
"""Adds a STAC TileLayer to the map.
Expand All @@ -927,6 +928,7 @@ def add_stac_layer(
attribution (str, optional): The attribution to use. Defaults to ''.
opacity (float, optional): The opacity of the layer. Defaults to 1.
shown (bool, optional): A flag indicating whether the layer should be on by default. Defaults to True.
fit_bounds (bool, optional): A flag indicating whether the map should be zoomed to the layer extent. Defaults to True.
"""
tile_url = stac_tile(
url, collection, item, assets, bands, titiler_endpoint, **kwargs
Expand All @@ -939,8 +941,10 @@ def add_stac_layer(
opacity=opacity,
shown=shown,
)
self.fit_bounds([[bounds[1], bounds[0]], [bounds[3], bounds[2]]])
arc_zoom_to_extent(bounds[0], bounds[1], bounds[2], bounds[3])

if fit_bounds:
self.fit_bounds([[bounds[1], bounds[0]], [bounds[3], bounds[2]]])
arc_zoom_to_extent(bounds[0], bounds[1], bounds[2], bounds[3])

def add_mosaic_layer(
self,
Expand Down
7 changes: 5 additions & 2 deletions leafmap/leafmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,7 @@ def add_stac_layer(
attribution="",
opacity=1.0,
shown=True,
fit_bounds=True,
**kwargs,
):
"""Adds a STAC TileLayer to the map.
Expand All @@ -852,14 +853,16 @@ def add_stac_layer(
attribution (str, optional): The attribution to use. Defaults to ''.
opacity (float, optional): The opacity of the layer. Defaults to 1.
shown (bool, optional): A flag indicating whether the layer should be on by default. Defaults to True.
fit_bounds (bool, optional): A flag indicating whether the map should be zoomed to the layer extent. Defaults to True.
"""
tile_url = stac_tile(
url, collection, item, assets, bands, titiler_endpoint, **kwargs
)
bounds = stac_bounds(url, collection, item, titiler_endpoint)
self.add_tile_layer(tile_url, name, attribution, opacity, shown)
self.fit_bounds([[bounds[1], bounds[0]], [bounds[3], bounds[2]]])
arc_zoom_to_extent(bounds[0], bounds[1], bounds[2], bounds[3])
if fit_bounds:
self.fit_bounds([[bounds[1], bounds[0]], [bounds[3], bounds[2]]])
arc_zoom_to_extent(bounds[0], bounds[1], bounds[2], bounds[3])

if not hasattr(self, "cog_layer_dict"):
self.cog_layer_dict = {}
Expand Down
145 changes: 131 additions & 14 deletions leafmap/stac.py
Original file line number Diff line number Diff line change
Expand Up @@ -985,18 +985,29 @@ def stac_root_link(url, return_col_id=False):
collection_id = obj.id
href = obj.get_root_link().get_href()

if not url.startswith(href):
href = obj.get_self_href()

if return_col_id:
return href, collection_id
else:
return href

except Exception as e:
print(e)
return None
if return_col_id:
return None, None
else:
return None


def stac_client(
url, headers=None, parameters=None, ignore_conformance=False, modifier=None, return_col_id=False
url,
headers=None,
parameters=None,
ignore_conformance=False,
modifier=None,
return_col_id=False,
):
"""Get the STAC client. It wraps the pystac.Client.open() method. See
https://pystac-client.readthedocs.io/en/stable/api.html#pystac_client.Client.open
Expand Down Expand Up @@ -1026,11 +1037,15 @@ def stac_client(
root = stac_root_link(url, return_col_id=return_col_id)

if return_col_id:
client = Client.open(root[0], headers, parameters, ignore_conformance, modifier)
client = Client.open(
root[0], headers, parameters, ignore_conformance, modifier
)
collection_id = root[1]
return client, collection_id
else:
client = Client.open(root, headers, parameters, ignore_conformance, modifier)
client = Client.open(
root, headers, parameters, ignore_conformance, modifier
)
return client

except Exception as e:
Expand Down Expand Up @@ -1190,12 +1205,85 @@ 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


def stac_search_to_gdf(search, **kwargs):
"""Convert STAC search result to a GeoDataFrame.
Args:
search (pystac_client.ItemSearch): The search result returned by leafmap.stac_search().
**kwargs: Additional keyword arguments to pass to the GeoDataFrame.from_features() function.
Returns:
GeoDataFrame: A GeoPandas GeoDataFrame object.
"""
import geopandas as gpd

gdf = gpd.GeoDataFrame.from_features(
search.item_collection().to_dict(), crs="EPSG:4326", **kwargs
)
return gdf


def stac_search_to_df(search, **kwargs):
"""Convert STAC search result to a DataFrame.
Args:
search (pystac_client.ItemSearch): The search result returned by leafmap.stac_search().
**kwargs: Additional keyword arguments to pass to the DataFrame.drop() function.
Returns:
DataFrame: A Pandas DataFrame object.
"""
gdf = stac_search_to_gdf(search)
return gdf.drop(columns=["geometry"], **kwargs)


def stac_search_to_dict(search, **kwargs):
"""Convert STAC search result to a dictionary.
Args:
search (pystac_client.ItemSearch): The search result returned by leafmap.stac_search().
Returns:
dict: A dictionary of STAC items, with the stac item id as the key, and the stac item as the value.
"""

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(),
}
return info


def stac_search_to_list(search, **kwargs):

"""Convert STAC search result to a list.
Args:
search (pystac_client.ItemSearch): The search result returned by leafmap.stac_search().
Returns:
list: A list of STAC items.
"""

return search.item_collections()


def download_data_catalogs(out_dir=None, quiet=True, overwrite=False):
"""Download geospatial data catalogs from https://github.com/giswqs/geospatial-data-catalogs.
Expand Down Expand Up @@ -1224,15 +1312,35 @@ def download_data_catalogs(out_dir=None, quiet=True, overwrite=False):
if os.path.exists(work_dir) and not overwrite:
return work_dir
else:

gdown.download(url, out_file, quiet=quiet)
with zipfile.ZipFile(out_file, "r") as zip_ref:
zip_ref.extractall(out_dir)
return work_dir


def set_default_bands(bands):

excluded = [
"index",
"metadata",
"mtl.json",
"mtl.txt",
"mtl.xml",
"qa",
"qa-browse",
"QA",
"rendered_preview",
"tilejson",
"tir-browse",
"vnir-browse",
"xml",
]

for band in excluded:
if band in bands:
bands.remove(band)

if len(bands) == 0:
return [None, None, None]

Expand All @@ -1242,14 +1350,23 @@ def set_default_bands(bands):
if not isinstance(bands, list):
raise ValueError("bands must be a list or a string.")

if (set(['nir', 'red', 'green']) <= set(bands)):
return ['nir', 'red', 'green']
elif (set(['red', 'green', 'blue']) <= set(bands)):
return ['red', 'green', 'blue']
elif (set(["B3", "B2", "B1"]) <= set(bands)):
if set(["nir", "red", "green"]) <= set(bands):
return ["nir", "red", "green"]
elif set(["nir08", "red", "green"]) <= set(bands):
return ["nir08", "red", "green"]
elif set(["red", "green", "blue"]) <= set(bands):
return ["red", "green", "blue"]
elif set(["B8", "B4", "B3"]) <= set(bands):
return ["B8", "B4", "B3"]
elif set(["B4", "B3", "B2"]) <= set(bands):
return ["B4", "B3", "B2"]
elif set(["B3", "B2", "B1"]) <= set(bands):
return ["B3", "B2", "B1"]
elif set(["B08", "B04", "B03"]) <= set(bands):
return ["B08", "B04", "B03"]
elif set(["B04", "B03", "B02"]) <= set(bands):
return ["B04", "B03", "B02"]
elif len(bands) < 3:
return bands[0] * 3
return [bands[0]] * 3
else:
return bands[:3]

Loading

0 comments on commit 0caecd8

Please sign in to comment.