def test_results(self): search = ItemSearch( url=SEARCH_URL, collections='naip', max_items=20, limit=10, ) results = search.items() assert all(isinstance(item, pystac.Item) for item in results)
def test_intersects_param(self): # Dict input search = ItemSearch(url=SEARCH_URL, intersects=INTERSECTS_EXAMPLE) assert search.search_parameters_post[ 'intersects'] == INTERSECTS_EXAMPLE # JSON string input search = ItemSearch(url=SEARCH_URL, intersects=json.dumps(INTERSECTS_EXAMPLE)) assert search.search_parameters_post[ 'intersects'] == INTERSECTS_EXAMPLE
def test_ids_results(self): ids = [ 'm_3510836_se_12_060_20180508_20190331', 'm_3510840_se_12_060_20180504_20190331' ] search = ItemSearch( url=SEARCH_URL, ids=ids, ) results = list(search.items()) assert len(results) == 2 assert all(item.id in ids for item in results)
def test_result_paging(self): search = ItemSearch( url=SEARCH_URL, bbox=(-73.21, 43.99, -73.12, 44.05), collections='naip', limit=10, max_items=20, ) # Check that the current page changes on the ItemSearch instance when a new page is requested pages = list(search.item_collections()) assert pages[1] != pages[2] assert pages[1].features != pages[2].features
def test_method(self): # Default method should be GET... search = ItemSearch(url=ASTRAEA_URL) assert search.method == 'GET' # ...unless the "intersects" argument is present. search = ItemSearch(url=ASTRAEA_URL, intersects=INTERSECTS_EXAMPLE) assert search.method == 'POST' # "method" argument should take precedence over presence of "intersects" search = ItemSearch(url=ASTRAEA_URL, method='GET', intersects=INTERSECTS_EXAMPLE) assert search.method == 'GET'
def test_datetime_param(self): # Single timestamp input search = ItemSearch(url=ASTRAEA_URL, datetime='2020-02-01T00:00:00Z') assert search.search_parameters_post['datetime'] == ( '2020-02-01T00:00:00Z', ) # Timestamp range input search = ItemSearch( url=ASTRAEA_URL, datetime='2020-02-01T00:00:00Z/2020-02-02T00:00:00Z') assert search.search_parameters_post['datetime'] == ( '2020-02-01T00:00:00Z', '2020-02-02T00:00:00Z') # Timestamp list input search = ItemSearch( url=ASTRAEA_URL, datetime=['2020-02-01T00:00:00Z', '2020-02-02T00:00:00Z']) assert search.search_parameters_post['datetime'] == ( '2020-02-01T00:00:00Z', '2020-02-02T00:00:00Z') # Open timestamp range input search = ItemSearch(url=ASTRAEA_URL, datetime='2020-02-01T00:00:00Z/..') assert search.search_parameters_post['datetime'] == ( '2020-02-01T00:00:00Z', '..') start = datetime(2020, 2, 1, 0, 0, 0, tzinfo=tzutc()) end = datetime(2020, 2, 2, 0, 0, 0, tzinfo=tzutc()) # Single datetime input search = ItemSearch(url=ASTRAEA_URL, datetime=start) assert search.search_parameters_post['datetime'] == ( '2020-02-01T00:00:00Z', ) # Datetime range input search = ItemSearch(url=ASTRAEA_URL, datetime=[start, end]) assert search.search_parameters_post['datetime'] == ( '2020-02-01T00:00:00Z', '2020-02-02T00:00:00Z') # Open datetime range input search = ItemSearch(url=ASTRAEA_URL, datetime=(start, None)) assert search.search_parameters_post['datetime'] == ( '2020-02-01T00:00:00Z', '..') # Localized datetime input (should be converted to UTC) start_localized = datetime(2020, 2, 1, 0, 0, 0, tzinfo=gettz('US/Eastern')) search = ItemSearch(url=ASTRAEA_URL, datetime=start_localized) assert search.search_parameters_post['datetime'] == ( '2020-02-01T05:00:00Z', )
def test_collections_param(self, astraea_api): # Single ID string search = ItemSearch(url=ASTRAEA_URL, collections='naip') assert search.search_parameters_post['collections'] == ('naip', ) # Comma-separated ID string search = ItemSearch(url=ASTRAEA_URL, collections='naip,landsat8_l1tp') assert search.search_parameters_post['collections'] == ( 'naip', 'landsat8_l1tp') # List of ID strings search = ItemSearch(url=ASTRAEA_URL, collections=['naip', 'landsat8_l1tp']) assert search.search_parameters_post['collections'] == ( 'naip', 'landsat8_l1tp') # Generator of ID strings def collectioner(): yield from ['naip', 'landsat8_l1tp'] search = ItemSearch(url=ASTRAEA_URL, collections=collectioner()) assert search.search_parameters_post['collections'] == ( 'naip', 'landsat8_l1tp') collection = astraea_api.get_child('landsat8_l1tp') # Single pystac.Collection search = ItemSearch(url=ASTRAEA_URL, collections=collection) assert search.search_parameters_post['collections'] == ( 'landsat8_l1tp', ) # Mixed list search = ItemSearch(url=ASTRAEA_URL, collections=[collection, 'naip']) assert search.search_parameters_post['collections'] == ( 'landsat8_l1tp', 'naip')
def test_ids_param(self): # Single ID search = ItemSearch(url=ASTRAEA_URL, ids='m_3510836_se_12_060_20180508_20190331') assert search.search_parameters_post['ids'] == ( 'm_3510836_se_12_060_20180508_20190331', ) # Comma-separated ID string search = ItemSearch( url=ASTRAEA_URL, ids= 'm_3510836_se_12_060_20180508_20190331,m_3510840_se_12_060_20180504_20190331' ) assert search.search_parameters_post['ids'] == ( 'm_3510836_se_12_060_20180508_20190331', 'm_3510840_se_12_060_20180504_20190331') # List of IDs search = ItemSearch(url=ASTRAEA_URL, ids=[ 'm_3510836_se_12_060_20180508_20190331', 'm_3510840_se_12_060_20180504_20190331' ]) assert search.search_parameters_post['ids'] == ( 'm_3510836_se_12_060_20180508_20190331', 'm_3510840_se_12_060_20180504_20190331') # Generator of IDs def ids(): yield from [ 'm_3510836_se_12_060_20180508_20190331', 'm_3510840_se_12_060_20180504_20190331' ] search = ItemSearch(url=ASTRAEA_URL, ids=ids()) assert search.search_parameters_post['ids'] == ( 'm_3510836_se_12_060_20180508_20190331', 'm_3510840_se_12_060_20180504_20190331')
def test_bbox_param(self): # Tuple input search = ItemSearch(url=ASTRAEA_URL, bbox=(-104.5, 44.0, -104.0, 45.0)) assert search.search_parameters_post['bbox'] == (-104.5, 44.0, -104.0, 45.0) # List input search = ItemSearch(url=ASTRAEA_URL, bbox=[-104.5, 44.0, -104.0, 45.0]) assert search.search_parameters_post['bbox'] == (-104.5, 44.0, -104.0, 45.0) # String Input search = ItemSearch(url=ASTRAEA_URL, bbox='-104.5,44.0,-104.0,45.0') assert search.search_parameters_post['bbox'] == (-104.5, 44.0, -104.0, 45.0) # Generator Input def bboxer(): yield from [-104.5, 44.0, -104.0, 45.0] search = ItemSearch(url=ASTRAEA_URL, bbox=bboxer()) assert search.search_parameters_post['bbox'] == (-104.5, 44.0, -104.0, 45.0)
def test_datetime_results(self): # Datetime range string datetime_ = '2019-01-01T00:00:01Z/2019-01-01T00:00:10Z' search = ItemSearch(url=SEARCH_URL, datetime=datetime_) results = list(search.items()) assert len(results) == 12 min_datetime = datetime(2019, 1, 1, 0, 0, 1, tzinfo=tzutc()) max_datetime = datetime(2019, 1, 1, 0, 0, 10, tzinfo=tzutc()) search = ItemSearch(url=SEARCH_URL, datetime=(min_datetime, max_datetime)) results = search.items() assert all(min_datetime <= item.datetime <= (max_datetime + timedelta(seconds=1)) for item in results)
def test_intersects_results(self): # GeoJSON-like dict intersects_dict = { 'type': 'Polygon', 'coordinates': [[[-73.21, 43.99], [-73.21, 44.05], [-73.12, 44.05], [-73.12, 43.99], [-73.21, 43.99]]] } search = ItemSearch(url=SEARCH_URL, intersects=intersects_dict, collections='naip') results = list(search.items()) assert len(results) == 30 # Geo-interface object class MockGeoObject: __geo_interface__ = intersects_dict intersects_obj = MockGeoObject() search = ItemSearch(url=SEARCH_URL, intersects=intersects_obj, collections='naip') results = search.items() assert all(isinstance(item, pystac.Item) for item in results)
def search(self, *, limit: Optional[int] = None, bbox: Optional[BBoxLike] = None, datetime: Optional[DatetimeLike] = None, intersects: Optional[IntersectsLike] = None, ids: Optional[IDsLike] = None, collections: Optional[CollectionsLike] = None, max_items: Optional[int] = None, method: Optional[str] = None, next_resolver: Optional[Callable] = None) -> ItemSearch: """Query the ``/search`` endpoint using the given parameters. This method returns an :class:`~pystac_api.ItemSearch` instance, see that class's documentation for details on how to get the number of matches and iterate over results. All keyword arguments are passed directly to the :class:`~pystac_api.ItemSearch` instance. .. warning:: This method is only implemented if the API conforms to the `STAC API - Item Search <https://github.com/radiantearth/stac-api-spec/tree/master/item-search>`__ spec *and* contains a link with a ``"rel"`` type of ``"search"`` in its root catalog. If the API does not meet either of these criteria, this method will raise a :exc:`NotImplementedError`. Parameters ---------- limit : int, optional The maximum number of items to return *per page*. Defaults to ``None``, which falls back to the limit set by the service. bbox: list or tuple or Iterator or str, optional May be a list, tuple, or iterator representing a bounding box of 2D or 3D coordinates. Results will be filtered to only those intersecting the bounding box. datetime: str or datetime.datetime or list or tuple or Iterator, optional Either a single datetime or datetime range used to filter results. You may express a single datetime using a :class:`datetime.datetime` instance or a `RFC 3339-compliant <https://tools.ietf.org/html/rfc3339>`__ timestamp. Instances of :class:`datetime.datetime` may be either timezone aware or unaware. Timezone aware instances will be converted to a UTC timestamp before being passed to the endpoint. Timezone unaware instances are assumed to represent UTC timestamps. You may represent a datetime range using a ``"/"`` separated string as described in the spec, or a list, tuple, or iterator of 2 timestamps or datetime instances. For open-ended ranges, use either ``".."`` (``'2020-01-01:00:00:00Z/..'``, ``['2020-01-01:00:00:00Z', '..']``) or a value of ``None`` (``['2020-01-01:00:00:00Z', None]``). intersects: str or dict, optional A GeoJSON-like dictionary or JSON string. Results will be filtered to only those intersecting the geometry ids: list, optional List of Item ids to return. All other filter parameters that further restrict the number of search results (except ``limit``) are ignored. collections: list, optional List of one or more Collection IDs or :class:`pystac.Collection` instances. Only Items in one of the provided Collections will be searched max_items : int or None, optional The maximum number of items to return from the search. *Note that this is not a STAC API - Item Search parameter and is instead used by the client to limit the total number of returned items*. method : str or None, optional The HTTP method to use when making a request to the service. This must be either ``"GET"``, ``"POST"``, or ``None``. If ``None``, this will default to ``"POST"`` if the ``intersects`` argument is present and ``"GET"`` if not. If a ``"POST"`` request receives a ``405`` status for the response, it will automatically retry with a ``"GET"`` request for all subsequent requests. next_resolver: Callable, optional A callable that will be used to construct the next request based on a "next" link and the previous request. Defaults to using the :func:`~pystac_api.paging.simple_stac_resolver`. Returns ------- results : ItemSearch Raises ------ NotImplementedError If the API does not conform to the `Item Search spec <https://github.com/radiantearth/stac-api-spec/tree/master/item-search>`__ or does not have a link with a ``"rel"`` type of ``"search"``. """ if not self.conforms_to(ConformanceClasses.STAC_API_ITEM_SEARCH): spec_name = ConformanceClasses.STAC_API_ITEM_SEARCH.name spec_uris = '\n\t'.join( ConformanceClasses.STAC_API_ITEM_SEARCH.all_uris) msg = f'This service does not conform to the {spec_name} spec and therefore the search method is not ' \ f'implemented. Services must publish one of the following conformance URIs in order to conform to ' \ f'this spec (preferably the first one):\n\t{spec_uris}' raise NotImplementedError(msg) search_link = self.get_single_link('search') if search_link is None: raise NotImplementedError( 'No link with a "rel" type of "search" could be found in this services\'s ' 'root catalog.') return ItemSearch(search_link.target, limit=limit, bbox=bbox, datetime=datetime, intersects=intersects, ids=ids, collections=collections, max_items=max_items, method=method, next_resolver=next_resolver, conformance=list(self.conformance))