From 238b82ab4e990ce7791fc63a20628220387d1c98 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Tue, 7 Jun 2022 14:00:13 -0400 Subject: [PATCH 1/2] add tutorial about using __geo_interface__ objects for search intersects --- docs/tutorials.rst | 10 + docs/tutorials/item-search-intersects.ipynb | 257 ++++++++++++++++++++ pystac_client/collection_client.py | 4 +- requirements-docs.txt | 4 +- 4 files changed, 272 insertions(+), 3 deletions(-) create mode 100644 docs/tutorials/item-search-intersects.ipynb diff --git a/docs/tutorials.rst b/docs/tutorials.rst index a6d2f33a..f2a73978 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -28,6 +28,16 @@ CQL2 Filtering This tutorial gives an introduction to using CQL2-JSON filtering in searches to search by arbitrary STAC Item properties. +Item Search with Intersects +----------------------------------------------------- + +- :tutorial:`GitHub version ` +- :ref:`Docs version ` + +This tutorial demonstrates the use of different Python library +object types that can be used for the `intersects` +search parameter. + Calculating Coverage Percentage of the AOI by an Item ----------------------------------------------------- diff --git a/docs/tutorials/item-search-intersects.ipynb b/docs/tutorials/item-search-intersects.ipynb new file mode 100644 index 00000000..6b8c9a83 --- /dev/null +++ b/docs/tutorials/item-search-intersects.ipynb @@ -0,0 +1,257 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e06a27bf", + "metadata": {}, + "source": [ + "# Item Search with Intersects\n", + "\n", + "This notebook shows the use of pystac-client to perform item search with the `intersects` parameter, to restrict the results to an Area of Interest (AOI)." + ] + }, + { + "cell_type": "markdown", + "id": "1e16077c", + "metadata": {}, + "source": [ + "# Client\n", + "\n", + "We first connect to an API by retrieving the root catalog, or landing page, of the API with the `Client.open` function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98942e75", + "metadata": {}, + "outputs": [], + "source": [ + "from pystac_client import Client\n", + "from typing import Any, Dict\n", + "import json\n", + "\n", + "# STAC API root URL\n", + "URL = 'https://planetarycomputer.microsoft.com/api/stac/v1'\n", + "\n", + "client = Client.open(URL)" + ] + }, + { + "cell_type": "markdown", + "id": "62e26114", + "metadata": {}, + "source": [ + "# Item Search\n", + "\n", + "When the Catalog is a STAC API, we have the ability to search for items based on spatio-temporal properties.\n", + "\n", + "The STAC API endpoint `/search` accepts a parameter `intersects` that is a GeoJSON Geometry representing the AOI of the search.\n", + "\n", + "The `search` method of the pystac_client `Client` class accepts several different types of objects:\n", + "\n", + "1. a string representing a GeoJSON geometry\n", + "2. a dictionary representing a GeoJSON geometry\n", + "3. any object that implements a ``__geo_interface__`` property, [an informal specification](https://gist.github.com/sgillies/2217756) \n", + " supported by several libraries for generating a GeoJSON representation from an object. Several prominent libraries support this\n", + " protocol for their objects that represent geometries, including [Shapely](https://shapely.readthedocs.io), [ArcPy](https://pro.arcgis.com/en/pro-app/2.8/arcpy/get-started/what-is-arcpy-.htm), [PySAL](https://pysal.org/), [geojson](https://github.com/jazzband/geojson), [pyshp](https://pypi.org/project/pyshp/), [descartes](https://docs.descarteslabs.com/), and [pygeoif](https://github.com/cleder/pygeoif)" + ] + }, + { + "cell_type": "markdown", + "id": "0d27fabf", + "metadata": {}, + "source": [ + "## Item Search with AOI as a Dictionary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d8af6334", + "metadata": {}, + "outputs": [], + "source": [ + "# AOI around Delfzijl, in northern Netherlands\n", + "aoi_as_dict: Dict[str, Any] = {\n", + " \"type\": \"Polygon\",\n", + " \"coordinates\": [\n", + " [\n", + " [\n", + " 6,\n", + " 53\n", + " ],\n", + " [\n", + " 7,\n", + " 53\n", + " ],\n", + " [\n", + " 7,\n", + " 54\n", + " ],\n", + " [\n", + " 6,\n", + " 54\n", + " ],\n", + " [\n", + " 6,\n", + " 53\n", + " ]\n", + " ]\n", + " ]\n", + "}\n", + "\n", + "search = client.search(\n", + " max_items = 25,\n", + " collections = \"aster-l1t\",\n", + " intersects = aoi_as_dict,\n", + ")\n", + "\n", + "print(f\"AOI as dictionary, found {len(list(search.items()))} items\")" + ] + }, + { + "cell_type": "markdown", + "id": "9da4956b", + "metadata": {}, + "source": [ + "## Item Search with AOI as a String" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9a7c9336", + "metadata": {}, + "outputs": [], + "source": [ + "aoi_as_str: str = json.dumps(aoi_as_dict)\n", + "\n", + "search = client.search(\n", + " max_items = 25,\n", + " collections = \"aster-l1t\",\n", + " intersects = aoi_as_str,\n", + ")\n", + "\n", + "print(f\"AOI as string, found {len(list(search.items()))} items\")" + ] + }, + { + "cell_type": "markdown", + "id": "9da4956b", + "metadata": {}, + "source": [ + "## Item Search with AOI as a Shapely Geometry Object" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9a7c9336", + "metadata": {}, + "outputs": [], + "source": [ + "import shapely.geometry\n", + "\n", + "aoi_as_shapely_shape = shapely.geometry.shape(aoi_as_dict)\n", + "\n", + "search = client.search(\n", + " max_items = 25,\n", + " collections = \"aster-l1t\",\n", + " intersects = aoi_as_shapely_shape,\n", + ")\n", + "\n", + "print(f\"AOI as Shapely Geometry object from shape(), found {len(list(search.items()))} items\")\n", + "\n", + "aoi_as_shapely_polygon = shapely.geometry.Polygon(aoi_as_dict[\"coordinates\"][0])\n", + "\n", + "search = client.search(\n", + " max_items = 25,\n", + " collections = \"aster-l1t\",\n", + " intersects = aoi_as_shapely_polygon,\n", + ")\n", + "\n", + "print(f\"AOI as Shapely Geometry object with Polygon, found {len(list(search.items()))} items\")" + ] + }, + { + "cell_type": "markdown", + "id": "9da4956b", + "metadata": {}, + "source": [ + "## Item Search with AOI as a \"geojson\" library object" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9a7c9336", + "metadata": {}, + "outputs": [], + "source": [ + "import geojson\n", + "\n", + "aoi_as_geojson_polygon = geojson.Polygon(aoi_as_dict[\"coordinates\"])\n", + "\n", + "search = client.search(\n", + " max_items = 25,\n", + " collections = \"aster-l1t\",\n", + " intersects = aoi_as_geojson_polygon,\n", + ")\n", + "\n", + "print(f\"AOI as geojson Polygon, found {len(list(search.items()))} items\")" + ] + }, + { + "cell_type": "markdown", + "id": "9da4956b", + "metadata": {}, + "source": [ + "## Item Search with AOI as a pygeoif Object" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a27dcb72", + "metadata": {}, + "outputs": [], + "source": [ + "import pygeoif\n", + "aoi_as_pygeoif_polygon = pygeoif.geometry.Polygon(aoi_as_dict[\"coordinates\"])\n", + "\n", + "search = client.search(\n", + " max_items = 25,\n", + " collections = \"aster-l1t\",\n", + " intersects = aoi_as_pygeoif_polygon,\n", + ")\n", + "\n", + "print(f\"AOI as pygeoif Polygon, found {len(list(search.items()))} items\")" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "6b6313dbab648ff537330b996f33bf845c0da10ea77ae70864d6ca8e2699c7ea" + }, + "kernelspec": { + "display_name": "Python 3.9.11 ('.venv': venv)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pystac_client/collection_client.py b/pystac_client/collection_client.py index a66fb4c5..bce6bca6 100644 --- a/pystac_client/collection_client.py +++ b/pystac_client/collection_client.py @@ -30,8 +30,8 @@ def get_items(self) -> Iterable["Item_Type"]: root = self.get_root() if link is not None and root is not None: search = ItemSearch( - url=link.href, method="GET", stac_io=root._stac_io - ) # type: ignore + url=link.href, method="GET", stac_io=root._stac_io # type: ignore + ) yield from search.items() else: yield from super().get_items() diff --git a/requirements-docs.txt b/requirements-docs.txt index bf7a9915..fd61faf4 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -7,4 +7,6 @@ nbsphinx~=0.8 jinja2<4.0 geopandas~=0.10.2 hvplot~=0.8.0 -matplotlib~=3.5.2 \ No newline at end of file +matplotlib~=3.5.2 +geojson~=2.5.0 +pygeoif~=0.7 From aa852319e5193299125b24377aeb454be564ba12 Mon Sep 17 00:00:00 2001 From: Phil Varner Date: Tue, 7 Jun 2022 14:02:11 -0400 Subject: [PATCH 2/2] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48937640..7add4c46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - lru_cache to several methods [#167](https://github.com/stac-utils/pystac-client/pull/167) - Direct item GET via ogcapi-features, if conformant [#166](https://github.com/stac-utils/pystac-client/pull/166) - `py.typed` for downstream type checking [#163](https://github.com/stac-utils/pystac-client/pull/163) +- Added tutorial for using various geometry objects (e.g., shapely, geojson) as an Item Search intersects argument [#232](https://github.com/stac-utils/pystac-client/pull/232) ### Changed