def test_items_as_collection(self):
     search = ItemSearch(
         url=SEARCH_URL,
         bbox=(-73.21, 43.99, -73.12, 44.05),
         collections='naip',
         limit=10,
         max_items=20,
     )
     item_collection = search.items_as_collection()
     assert len(item_collection.features) == 20
    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_method(self):
        # Default method should be POST...
        search = ItemSearch(url=ASTRAEA_URL)
        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_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_generator_of_collection_strings(self):
        # Generator of ID strings
        def collectioner():
            yield from ['naip', 'landsat8_l1tp']

        search = ItemSearch(url=ASTRAEA_URL, collections=collectioner())
        assert search.request.json['collections'] == ('naip', 'landsat8_l1tp')
    def test_three_datetimes(self):
        start = datetime(2020, 2, 1, 0, 0, 0, tzinfo=tzutc())
        middle = datetime(2020, 2, 2, 0, 0, 0, tzinfo=tzutc())
        end = datetime(2020, 2, 3, 0, 0, 0, tzinfo=tzutc())

        with pytest.raises(Exception):
            ItemSearch(url=ASTRAEA_URL, datetime=[start, middle, end])
 def test_many_datetimes(self):
     datetimes = [
         "1985-04-12T23:20:50.52Z"
         "1996-12-19T16:39:57-08:00"
         "1990-12-31T23:59:60Z"
         "1990-12-31T15:59:60-08:00"
         "1937-01-01T12:00:27.87+01:00"
         "1985-04-12T23:20:50.52Z"
         "1937-01-01T12:00:27.8710+01:00"
         "1937-01-01T12:00:27.8+01:00"
         "1937-01-01T12:00:27.8Z"
         "1985-04-12t23:20:50.5202020z"
         "2020-07-23T00:00:00Z"
         "2020-07-23T00:00:00.0Z"
         "2020-07-23T00:00:00.01Z"
         "2020-07-23T00:00:00.012Z"
         "2020-07-23T00:00:00.0123Z"
         "2020-07-23T00:00:00.01234Z"
         "2020-07-23T00:00:00.012345Z"
         "2020-07-23T00:00:00.000Z"
         "2020-07-23T00:00:00.000+03:00"
         "2020-07-23T00:00:00+03:00"
         "2020-07-23T00:00:00.000+03:00"
         "2020-07-23T00:00:00.000z"
     ]
     for date_time in datetimes:
         ItemSearch(url=ASTRAEA_URL, datetime=date_time)
 def test_range_string_datetime(self):
     # Timestamp range input
     search = ItemSearch(
         url=ASTRAEA_URL,
         datetime='2020-02-01T00:00:00Z/2020-02-02T00:00:00Z')
     assert search.request.json[
         'datetime'] == '2020-02-01T00:00:00Z/2020-02-02T00:00:00Z'
    def test_generator_bbox(self):
        # Generator Input
        def bboxer():
            yield from [-104.5, 44.0, -104.0, 45.0]

        search = ItemSearch(url=ASTRAEA_URL, bbox=bboxer())
        assert search.request.json['bbox'] == (-104.5, 44.0, -104.0, 45.0)
 def test_list_of_strings_datetime(self):
     # Timestamp list input
     search = ItemSearch(
         url=ASTRAEA_URL,
         datetime=['2020-02-01T00:00:00Z', '2020-02-02T00:00:00Z'])
     assert search.request.json[
         'datetime'] == '2020-02-01T00:00:00Z/2020-02-02T00:00:00Z'
    def test_list_of_datetimes(self):
        start = datetime(2020, 2, 1, 0, 0, 0, tzinfo=tzutc())
        end = datetime(2020, 2, 2, 0, 0, 0, tzinfo=tzutc())

        # Datetime range input
        search = ItemSearch(url=ASTRAEA_URL, datetime=[start, end])
        assert search.request.json[
            'datetime'] == '2020-02-01T00:00:00Z/2020-02-02T00:00:00Z'
 def test_multiple_id_string(self):
     # 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.request.json['ids'] == (
         'm_3510836_se_12_060_20180508_20190331',
         'm_3510840_se_12_060_20180504_20190331')
 def test_list_of_id_strings(self):
     # 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.request.json['ids'] == (
         'm_3510836_se_12_060_20180508_20190331',
         'm_3510840_se_12_060_20180504_20190331')
 def test_localized_datetime_converted_to_utc(self):
     # 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.request.json['datetime'] == '2020-02-01T05:00:00Z'
    def test_generator_of_id_string(self):
        # 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.request.json['ids'] == (
            'm_3510836_se_12_060_20180508_20190331',
            'm_3510840_se_12_060_20180504_20190331')
    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 test_list_bbox(self):
     # List input
     search = ItemSearch(url=ASTRAEA_URL, bbox=[-104.5, 44.0, -104.0, 45.0])
     assert search.request.json['bbox'] == (-104.5, 44.0, -104.0, 45.0)
    def test_single_datetime_object(self):
        start = datetime(2020, 2, 1, 0, 0, 0, tzinfo=tzutc())

        # Single datetime input
        search = ItemSearch(url=ASTRAEA_URL, datetime=start)
        assert search.request.json['datetime'] == '2020-02-01T00:00:00Z'
 def test_intersects_dict(self):
     # Dict input
     search = ItemSearch(url=SEARCH_URL, intersects=INTERSECTS_EXAMPLE)
     assert search.request.json['intersects'] == INTERSECTS_EXAMPLE
 def test_intersects_json_string(self):
     # JSON string input
     search = ItemSearch(url=SEARCH_URL,
                         intersects=json.dumps(INTERSECTS_EXAMPLE))
     assert search.request.json['intersects'] == INTERSECTS_EXAMPLE
 def test_single_string_datetime(self):
     # Single timestamp input
     search = ItemSearch(url=ASTRAEA_URL, datetime='2020-02-01T00:00:00Z')
     assert search.request.json['datetime'] == '2020-02-01T00:00:00Z'
 def test_single_id_string(self):
     # Single ID
     search = ItemSearch(url=ASTRAEA_URL,
                         ids='m_3510836_se_12_060_20180508_20190331')
     assert search.request.json['ids'] == (
         'm_3510836_se_12_060_20180508_20190331', )
 def test_string_bbox(self):
     # String Input
     search = ItemSearch(url=ASTRAEA_URL, bbox='-104.5,44.0,-104.0,45.0')
     assert search.request.json['bbox'] == (-104.5, 44.0, -104.0, 45.0)
    def test_collection_object(self, astraea_api):
        collection = astraea_api.get_child('landsat8_l1tp')

        # Single pystac.Collection
        search = ItemSearch(url=ASTRAEA_URL, collections=collection)
        assert search.request.json['collections'] == ('landsat8_l1tp', )
    def test_open_list_of_datetimes(self):
        start = datetime(2020, 2, 1, 0, 0, 0, tzinfo=tzutc())

        # Open datetime range input
        search = ItemSearch(url=ASTRAEA_URL, datetime=(start, None))
        assert search.request.json['datetime'] == '2020-02-01T00:00:00Z/..'
 def test_tuple_bbox(self):
     # Tuple input
     search = ItemSearch(url=ASTRAEA_URL, bbox=(-104.5, 44.0, -104.0, 45.0))
     assert search.request.json['bbox'] == (-104.5, 44.0, -104.0, 45.0)
Example #29
0
    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,
               query: Optional[QueryLike] = None,
               max_items: Optional[int] = None,
               method: Optional[str] = 'POST',
               next_resolver: Optional[Callable] = None) -> ItemSearch:
        """Query the ``/search`` endpoint using the given parameters.

        This method returns an :class:`~pystac_client.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_client.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, a `RFC 3339-compliant <https://tools.ietf.org/html/rfc3339>`__ timestamp,
            or a simple date string (see below). 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]``).

            If using a simple date string, the datetime can be specified in ``YYYY-mm-dd`` format, optionally truncating
            to ``YYYY-mm`` or just ``YYYY``. Simple date strings will be expanded to include the entire time period, for
            example:

            - ``2017`` expands to ``2017-01-01T00:00:00Z/2017-12-31T23:59:59Z``
            - ``2017-06`` expands to ``2017-06-01T00:00:00Z/2017-06-30T23:59:59Z``
            - ``2017-06-10`` expands to ``2017-06-10T00:00:00Z/2017-06-10T23:59:59Z``

            If used in a range, the end of the range expands to the end of that day/month/year, for example:

            - ``2017/2018`` expands to ``2017-01-01T00:00:00Z/2018-12-31T23:59:59Z``
            - ``2017-06/2017-07`` expands to ``2017-06-01T00:00:00Z/2017-07-31T23:59:59Z``
            - ``2017-06-10/2017-06-11`` expands to ``2017-06-10T00:00:00Z/2017-06-11T23:59:59Z``
        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_client.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 self.conformance is not None and 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,
                          query=query,
                          max_items=max_items,
                          method=method,
                          headers=self.headers,
                          conformance=self.conformance,
                          next_resolver=next_resolver)
    def test_mixed_collection_object_and_string(self, astraea_api):
        collection = astraea_api.get_child('landsat8_l1tp')

        # Mixed list
        search = ItemSearch(url=ASTRAEA_URL, collections=[collection, 'naip'])
        assert search.request.json['collections'] == ('landsat8_l1tp', 'naip')