Esempio n. 1
0
 def __init__(self, provider, properties, *args, **kwargs):
     self.provider = provider
     self.product_type = kwargs.get("productType")
     self.location = self.remote_location = properties.get(
         "downloadLink", "")
     self.properties = {
         key: value
         for key, value in properties.items()
         if key != "geometry" and value not in [NOT_MAPPED, NOT_AVAILABLE]
     }
     product_geometry = properties["geometry"]
     # Let's try 'latmin lonmin latmax lonmax'
     if isinstance(product_geometry, str):
         bbox_pattern = re.compile(
             r"^(-?\d+\.?\d*) (-?\d+\.?\d*) (-?\d+\.?\d*) (-?\d+\.?\d*)$")
         found_bbox = bbox_pattern.match(product_geometry)
         if found_bbox:
             coords = found_bbox.groups()
             if len(coords) == 4:
                 product_geometry = geometry.box(
                     float(coords[1]),
                     float(coords[0]),
                     float(coords[3]),
                     float(coords[2]),
                 )
     # Best effort to understand provider specific geometry (the default is to
     # assume an object implementing the Geo Interface: see
     # https://gist.github.com/2217756)
     if isinstance(product_geometry, str):
         try:
             product_geometry = wkt.loads(product_geometry)
         except geos.WKTReadingError:
             try:
                 product_geometry = wkb.loads(product_geometry)
             # Also catching TypeError because product_geometry can be a
             # string and not a bytes string
             except (geos.WKBReadingError, TypeError):
                 # Giv up!
                 raise
     self.geometry = self.search_intersection = geometry.shape(
         product_geometry)
     self.search_args = args
     self.search_kwargs = kwargs
     if self.search_kwargs.get("geometry") is not None:
         searched_geom = get_geometry_from_various(
             **{"geometry": self.search_kwargs["geometry"]})
         try:
             self.search_intersection = self.geometry.intersection(
                 searched_geom)
         except TopologicalError:
             logger.warning(
                 "Unable to intersect the requested extent: %s with the product "
                 "geometry: %s",
                 searched_geom,
                 product_geometry,
             )
             self.search_intersection = None
     self.driver = DRIVERS.get(self.product_type, NoDriver())
     self.downloader = None
     self.downloader_auth = None
Esempio n. 2
0
    def proceed(self, products, **search_params):
        """Execute crunch: Filter products, retaining only those that are overlapping with the search_extent

        :param products: A list of products resulting from a search
        :type products: list(:class:`~eodag.api.product.EOProduct`)
        :param dict search_params: search criteria that must contain `geometry`
        :returns: The filtered products
        :rtype: list(:class:`~eodag.api.product.EOProduct`)
        """
        logger.debug("Start filtering for overlapping products")
        filtered = []
        add_to_filtered = filtered.append

        search_geom = get_geometry_from_various(**search_params)
        if not search_geom:
            logger.warning(
                "geometry not found in cruncher arguments, filtering disabled."
            )
            return products
        minimum_overlap = float(self.config.get("minimum_overlap", "0"))
        contains = self.config.get("contains", False)
        intersects = self.config.get("intersects", False)
        within = self.config.get("within", False)

        if contains and (within or intersects) or (within and intersects):
            logger.warning(
                "contains, intersects and within parameters are mutually exclusive"
            )
            return products
        elif (minimum_overlap > 0 and minimum_overlap < 100
              and (contains or within or intersects)):
            logger.warning(
                "minimum_overlap will be ignored because of contains/intersects/within usage"
            )
        elif not contains and not within and not intersects:
            logger.debug("Minimum overlap is: {} %".format(minimum_overlap))

        logger.debug("Initial requested extent area: %s", search_geom.area)
        if search_geom.area == 0:
            logger.debug(
                "No product can overlap a requested extent that is not a polygon (i.e with area=0)"
            )
        else:
            for product in products:
                logger.debug("Uncovered extent area: %s", search_geom.area)
                if product.search_intersection:
                    intersection = product.search_intersection
                    product_geometry = product.geometry
                else:  # Product geometry may be invalid
                    if not product.geometry.is_valid:
                        logger.debug(
                            "Trying our best to deal with invalid geometry on product: %r",
                            product,
                        )
                        product_geometry = product.geometry.buffer(0)
                        try:
                            intersection = search_geom.intersection(
                                product_geometry)
                        except TopologicalError:
                            logger.debug(
                                "Product geometry still invalid. Overlap test restricted to containment"
                            )
                            if search_geom.contains(product_geometry):
                                logger.debug(
                                    "Product %r overlaps the search extent. Adding it to filtered results"
                                )
                                add_to_filtered(product)
                            continue
                    else:
                        product_geometry = product.geometry
                        intersection = search_geom.intersection(
                            product_geometry)

                if ((contains and product_geometry.contains(search_geom))
                        or (within and product_geometry.within(search_geom)) or
                    (intersects and product_geometry.intersects(search_geom))):
                    add_to_filtered(product)
                    continue
                elif contains or within or intersects:
                    continue

                ipos = (intersection.area / search_geom.area) * 100
                ipop = (intersection.area / product_geometry.area) * 100
                logger.debug(
                    "Intersection of product extent and search extent covers %f percent of the search extent "
                    "area",
                    ipos,
                )
                logger.debug(
                    "Intersection of product extent and search extent covers %f percent of the product extent "
                    "area",
                    ipop,
                )
                if any((
                        search_geom.contains(product.geometry),
                        ipos >= minimum_overlap,
                        ipop >= minimum_overlap,
                )):
                    logger.debug(
                        "Product %r overlaps the search extent by the specified constraint. Adding it to "
                        "filtered results",
                        product,
                    )
                    add_to_filtered(product)
                else:
                    logger.debug(
                        "Product %r does not overlaps the search extent by the specified constraint. "
                        "Skipping it",
                        product,
                    )
        logger.info("Finished filtering products. %s resulting products",
                    len(filtered))
        return filtered
Esempio n. 3
0
    def search(self,
               page=DEFAULT_PAGE,
               items_per_page=DEFAULT_ITEMS_PER_PAGE,
               raise_errors=False,
               start=None,
               end=None,
               geom=None,
               **kwargs):
        """Look for products matching criteria on known providers.

        The default behaviour is to look for products on the provider with the
        highest priority supporting the requested product type. These priorities
        are configurable through user configuration file or individual
        environment variable.

        :param page: The page number to return (default: 1)
        :type page: int
        :param items_per_page: The number of results that must appear in one single
                               page (default: 20)
        :type items_per_page: int
        :param raise_errors:  When an error occurs when searching, if this is set to
                              True, the error is raised (default: False)
        :type raise_errors: bool
        :param start: Start sensing time in iso format
        :type start: str
        :param end: End sensing time in iso format
        :type end: str
        :param geom: Search area that can be defined in different ways:

                    * with a Shapely geometry object:
                      ``class:`shapely.geometry.base.BaseGeometry```
                    * with a bounding box (dict with keys: "lonmin", "latmin", "lonmax", "latmax"):
                      ``dict.fromkeys(["lonmin", "latmin", "lonmax", "latmax"])``
                    * with a bounding box as list of float:
                      ``[lonmin, latmin, lonmax, latmax]``
                    * with a WKT str

                    The geometry can also be passed as ``<location_name>="<attr_regex>"`` in kwargs
        :type geom: Union[str, dict, shapely.geometry.base.BaseGeometry])
        :param dict kwargs: some other criteria that will be used to do the search,
                            using paramaters compatibles with the provider, or also
                            location filtering by name using locations configuration
                            ``<location_name>="<attr_regex>"`` (e.g.: ``country="PA.`` will use
                            the geometry of the features having the property ISO3 starting with
                            'PA' such as Panama and Pakistan in the shapefile configured with
                            name=country and attr=ISO3)
        :returns: A collection of EO products matching the criteria and the total
                  number of results found
        :rtype: tuple(:class:`~eodag.api.search_result.SearchResult`, int)

        .. versionchanged::
           1.6

                * Any search parameter supported by the provider can be passed as
                  kwargs. Each provider has a 'discover_metadata' configuration
                  with a metadata_pattern (to which the parameter must match) and a
                  search_param setting, defining the way the query will be built.

        .. versionchanged::
           1.0

                * The ``product_type`` parameter is no longer mandatory
                * Support new search parameters compliant with OpenSearch
                * Fails if a suitable product type could not be guessed (returns an
                  empty search result) and if the user is not querying a specific
                  product (by providing ``id`` as search parameter)
                * A search by ID is now performed if a product type can not be guessed
                  from the user input, and if the user has provided an ID as a search
                  criteria (in the keyword arguments)
                * It is now possible to pass in the name of the provider on which to
                  perform the request when searching a product by its ID,
                  for performance reasons. In that case, the search with the product ID
                  will be done directly on that provider

        .. versionchanged::
           0.7.1

                * The search now stops at the first provider that supports the
                  requested product type (removal of the partial mechanism)
                * The method now returns a tuple of 2 elements, the first one
                  being the result and the second one being the total number of
                  results satisfying the criteria on the provider. This is useful
                  for applications wrapping eodag and wishing to implement
                  pagination. An example of this kind of application is the
                  embedded eodag HTTP server. Also useful for informational purposes
                  in the CLI: the user is informed about the total number of results
                  available, and can ask for retrieving a given number of these
                  results (See the help message of the CLI for more information).

        .. note::
            The search interfaces, which are implemented as plugins, are required to
            return a list as a result of their processing. This requirement is
            enforced here.
        """
        product_type = kwargs.get("productType", None)
        if product_type is None:
            try:
                guesses = self.guess_product_type(**kwargs)
                # By now, only use the best bet
                product_type = guesses[0]
            except NoMatchingProductType:
                queried_id = kwargs.get("id", None)
                if queried_id is None:
                    logger.info(
                        "No product type could be guessed with provided arguments"
                    )
                else:
                    provider = kwargs.get("provider", None)
                    return self._search_by_id(kwargs["id"], provider=provider)

        kwargs["productType"] = product_type
        if start is not None:
            kwargs["startTimeFromAscendingNode"] = start
        if end is not None:
            kwargs["completionTimeFromAscendingNode"] = end
        if geom is not None:
            kwargs["geometry"] = geom
        box = kwargs.pop("box", None)
        box = kwargs.pop("bbox", box)
        if geom is None and box is not None:
            kwargs["geometry"] = box

        kwargs["geometry"] = get_geometry_from_various(self.locations_config,
                                                       **kwargs)
        # remove locations_args from kwargs now that they have been used
        locations_dict = {loc["name"]: loc for loc in self.locations_config}
        for arg in locations_dict.keys():
            kwargs.pop(arg, None)

        plugin = next(
            self._plugins_manager.get_search_plugins(
                product_type=product_type))
        logger.info("Searching product type '%s' on provider: %s",
                    product_type, plugin.provider)
        # add product_types_config to plugin config
        try:
            plugin.config.product_type_config = dict([
                p for p in self.list_product_types(plugin.provider)
                if p["ID"] == product_type
            ][0], **{"productType":
                     product_type})
        except IndexError:
            plugin.config.product_type_config = dict([
                p for p in self.list_product_types(plugin.provider)
                if p["ID"] == GENERIC_PRODUCT_TYPE
            ][0], **{"productType":
                     product_type})
        plugin.config.product_type_config.pop("ID", None)

        logger.debug("Using plugin class for search: %s",
                     plugin.__class__.__name__)
        auth = self._plugins_manager.get_auth_plugin(plugin.provider)
        return self._do_search(plugin,
                               auth=auth,
                               page=page,
                               items_per_page=items_per_page,
                               raise_errors=raise_errors,
                               **kwargs)