Repository: CS-SI/eodag

Issue:
implement STAC API Query fragment
For STAC providers, STAC API Item Search has limited filtering capabilities (`collections`, `ids`, `bbox` / `intersects`, `datetime`).

However, it does not contain a formalized way to filter based on arbitrary fields in an Item. For example, there is no way to express the filter *"`item.properties.eo:cloud_cover` is less than 10"*

This can be improved using properties filtering provided by the [STAC API - Query Fragment](https://github.com/radiantearth/stac-api-spec/blob/master/fragments/query/README.md)

*Note that it is planned to be deprecated in favor of [STAC API - Filter Fragment](https://github.com/radiantearth/stac-api-spec/blob/master/fragments/filter/README.md), providing CQL filtering. But for the moment, available STAC API provider do not support  it.*

Discussion:

Patch:
diff --git a/docs/_static/params_mapping_extra.csv b/docs/_static/params_mapping_extra.csv
index b677c5ad..cf6647c4 100644
--- a/docs/_static/params_mapping_extra.csv
+++ b/docs/_static/params_mapping_extra.csv
@@ -10,7 +10,7 @@ herbaceousCover,,,:green:`queryable metadata`,,,,,,,,,
 iceCover,,,:green:`queryable metadata`,,,,,,,,,
 id,:green:`queryable metadata`,,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,,:green:`queryable metadata`
 orderLink,,,,,,,metadata only,,,,,
-polarizationChannels,metadata only,,metadata only,metadata only,metadata only,metadata only,metadata only,,metadata only,metadata only,,metadata only
+polarizationChannels,metadata only,,metadata only,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,metadata only,,metadata only,metadata only,,:green:`queryable metadata`
 polarizationMode,,,,,metadata only,metadata only,,:green:`queryable metadata`,,metadata only,,
 processingBaseline,,,:green:`queryable metadata`,,,,,,,,,
 publishedAfter,,,:green:`queryable metadata`,,,,,,,,,
diff --git a/docs/_static/params_mapping_opensearch.csv b/docs/_static/params_mapping_opensearch.csv
index 562f7eec..dc598e7c 100644
--- a/docs/_static/params_mapping_opensearch.csv
+++ b/docs/_static/params_mapping_opensearch.csv
@@ -1,56 +1,56 @@
 parameter,astraea_eod,aws_eos,creodias,earth_search,earth_search_cog,mundi,onda,peps,sobloo,theia,usgs,usgs_satapi_aws
 :abbr:`abstract ([Additional INSPIRE obligated OpenSearch Parameters for Collection Search] Abstract. (String))`,metadata only,,metadata only,metadata only,metadata only,metadata only,,metadata only,,,,metadata only
 ":abbr:`accessConstraint ([Additional INSPIRE obligated OpenSearch Parameters for Collection Search] Applied to assure the protection of privacy or intellectual property, and any special restrictions or limitations on obtaining the resource (String ))`",,,metadata only,,,metadata only,,metadata only,,metadata only,,
-acquisitionStation,metadata only,,,metadata only,metadata only,metadata only,,,,metadata only,,metadata only
-acquisitionSubType,metadata only,,,metadata only,metadata only,metadata only,,,,metadata only,,metadata only
+acquisitionStation,metadata only,,,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,,,,metadata only,,:green:`queryable metadata`
+acquisitionSubType,metadata only,,,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,,,,metadata only,,:green:`queryable metadata`
 acquisitionType,,,,,,metadata only,,metadata only,,,,
 antennaLookDirection,,,,,,metadata only,,,,metadata only,,
 archivingCenter,,,,,,metadata only,,,,metadata only,,
-availabilityTime,metadata only,,,metadata only,metadata only,metadata only,,,metadata only,metadata only,,metadata only
+availabilityTime,metadata only,,,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,,,metadata only,metadata only,,:green:`queryable metadata`
 :abbr:`classification ([Additional INSPIRE obligated OpenSearch Parameters for Collection Search] Name of the handling restrictions on the resource or metadata (String ))`,,,,,,metadata only,,,,,,
-cloudCover,metadata only,,:green:`queryable metadata`,metadata only,metadata only,:green:`queryable metadata`,metadata only,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,,metadata only
+cloudCover,metadata only,,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,,:green:`queryable metadata`
 completionTimeFromAscendingNode,:green:`queryable metadata`,,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,,:green:`queryable metadata`
 :abbr:`compositeType ([OpenSearch Parameters for Collection Search] Type of composite product expressed as time period that the composite product covers (e.g. P10D for a 10 day composite) (String))`,,,,,,metadata only,,,,,,
-creationDate,,,,,,metadata only,metadata only,metadata only,metadata only,metadata only,,
+creationDate,metadata only,,,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,metadata only,metadata only,metadata only,metadata only,,:green:`queryable metadata`
 ":abbr:`dissemination ([OpenSearch Parameters for Collection Search] A string identifying the dissemination method (e.g. EUMETCast, EUMETCast-Europe, DataCentre) (String))`",,,,,,metadata only,,,,,,
-:abbr:`doi ([OpenSearch Parameters for Collection Search] Digital Object Identifier identifying the product (see http://www.doi.org) (String))`,metadata only,,,metadata only,metadata only,metadata only,,,,,,metadata only
-dopplerFrequency,metadata only,,,metadata only,metadata only,metadata only,,,,metadata only,,metadata only
+:abbr:`doi ([OpenSearch Parameters for Collection Search] Digital Object Identifier identifying the product (see http://www.doi.org) (String))`,metadata only,,,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,,,,,,:green:`queryable metadata`
+dopplerFrequency,metadata only,,,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,,,,metadata only,,:green:`queryable metadata`
 frame,,,,,,metadata only,,,,,,
 ":abbr:`hasSecurityConstraints ([OpenSearch Parameters for Collection Search] A string informing if the resource has any security constraints. Possible values: TRUE, FALSE (String))`",,,,,,metadata only,,,,,,
 highestLocation,,,,,,metadata only,,,,,,
-illuminationAzimuthAngle,metadata only,,,metadata only,metadata only,metadata only,,,,metadata only,,metadata only
-illuminationElevationAngle,metadata only,,,metadata only,metadata only,metadata only,,,,metadata only,,metadata only
+illuminationAzimuthAngle,metadata only,,,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,,,,metadata only,,:green:`queryable metadata`
+illuminationElevationAngle,metadata only,,,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,,,,metadata only,,:green:`queryable metadata`
 illuminationZenithAngle,,,,,,metadata only,,,,metadata only,,
 incidenceAngleVariation,,,,,,metadata only,,,,metadata only,,
-":abbr:`instrument ([OpenSearch Parameters for Collection Search] A string identifying the instrument (e.g. MERIS, AATSR, ASAR, HRVIR. SAR). (String))`",metadata only,,:green:`queryable metadata`,metadata only,metadata only,:green:`queryable metadata`,metadata only,:green:`queryable metadata`,metadata only,:green:`queryable metadata`,,metadata only
+":abbr:`instrument ([OpenSearch Parameters for Collection Search] A string identifying the instrument (e.g. MERIS, AATSR, ASAR, HRVIR. SAR). (String))`",metadata only,,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,:green:`queryable metadata`,metadata only,:green:`queryable metadata`,,:green:`queryable metadata`
 :abbr:`keyword ([Additional INSPIRE obligated OpenSearch Parameters for Collection Search] Commonly used word(s) or formalised word(s) or phrase(s) used to describe the subject. (String))`,,,metadata only,,,metadata only,,metadata only,,metadata only,,
 :abbr:`language ([Additional INSPIRE obligated OpenSearch Parameters for Collection Search] Language of the intellectual content of the metadata record (String ))`,,,,,,metadata only,,,,,,
 :abbr:`lineage ([Additional INSPIRE obligated OpenSearch Parameters for Collection Search] General explanation of the data producer’s knowledge about the lineage of a dataset. (String))`,,,,,,metadata only,,,,,,
 lowestLocation,,,,,,metadata only,,,,,,
 maximumIncidenceAngle,,,,,,metadata only,,,,metadata only,,
 minimumIncidenceAngle,,,,,,metadata only,,,,metadata only,,
-modificationDate,,,metadata only,,,metadata only,,metadata only,,metadata only,,
-orbitDirection,metadata only,,:green:`queryable metadata`,metadata only,metadata only,metadata only,metadata only,:green:`queryable metadata`,metadata only,,,metadata only
-orbitNumber,metadata only,,:green:`queryable metadata`,metadata only,metadata only,metadata only,metadata only,:green:`queryable metadata`,metadata only,metadata only,,metadata only
+modificationDate,metadata only,,metadata only,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,,metadata only,,metadata only,,:green:`queryable metadata`
+orbitDirection,metadata only,,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,metadata only,:green:`queryable metadata`,metadata only,,,:green:`queryable metadata`
+orbitNumber,metadata only,,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,metadata only,:green:`queryable metadata`,metadata only,metadata only,,:green:`queryable metadata`
 ":abbr:`orbitType ([OpenSearch Parameters for Collection Search] A string identifying the platform orbit type (e.g. LEO, GEO) (String))`",,,,,,metadata only,,,,,,
 :abbr:`organisationName ([Additional INSPIRE obligated OpenSearch Parameters for Collection Search] A string identifying the name of the organization responsible for the resource (String))`,,,:green:`queryable metadata`,,,metadata only,,:green:`queryable metadata`,,metadata only,,
 :abbr:`organisationRole ([Additional INSPIRE obligated OpenSearch Parameters for Collection Search] The function performed by the responsible party (String ))`,,,,,,metadata only,,,,,,
 :abbr:`otherConstraint ([Additional INSPIRE obligated OpenSearch Parameters for Collection Search] Other restrictions and legal prerequisites for accessing and using the resource or metadata. (String))`,,,,,,metadata only,,,,,,
 parentIdentifier,,,:green:`queryable metadata`,,,metadata only,,:green:`queryable metadata`,,metadata only,,
-:abbr:`platform ([OpenSearch Parameters for Collection Search] A string with the platform short name (e.g. Sentinel-1) (String))`,metadata only,,metadata only,metadata only,metadata only,metadata only,:green:`queryable metadata`,metadata only,metadata only,:green:`queryable metadata`,,metadata only
-:abbr:`platformSerialIdentifier ([OpenSearch Parameters for Collection Search] A string with the Platform serial identifier (String))`,metadata only,,:green:`queryable metadata`,metadata only,metadata only,metadata only,metadata only,:green:`queryable metadata`,metadata only,:green:`queryable metadata`,,metadata only
+:abbr:`platform ([OpenSearch Parameters for Collection Search] A string with the platform short name (e.g. Sentinel-1) (String))`,metadata only,,metadata only,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,:green:`queryable metadata`,metadata only,metadata only,:green:`queryable metadata`,,:green:`queryable metadata`
+:abbr:`platformSerialIdentifier ([OpenSearch Parameters for Collection Search] A string with the Platform serial identifier (String))`,metadata only,,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,metadata only,metadata only,:green:`queryable metadata`,metadata only,:green:`queryable metadata`,,:green:`queryable metadata`
 processingCenter,,,,,,metadata only,,metadata only,,,,
 processingDate,,,,,,metadata only,metadata only,,,metadata only,,
-:abbr:`processingLevel ([OpenSearch Parameters for Collection Search] A string identifying the processing level applied to the entry (String))`,,,:green:`queryable metadata`,,,:green:`queryable metadata`,metadata only,:green:`queryable metadata`,metadata only,:green:`queryable metadata`,,
+:abbr:`processingLevel ([OpenSearch Parameters for Collection Search] A string identifying the processing level applied to the entry (String))`,metadata only,,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,:green:`queryable metadata`,metadata only,:green:`queryable metadata`,,:green:`queryable metadata`
 processingMode,,,,,,metadata only,,,,metadata only,,
 processorName,,,,,,metadata only,,metadata only,,,,
 productQualityDegradationTag,,,,,,metadata only,,,,,,
 productQualityStatus,,,,,,metadata only,metadata only,metadata only,,,,
-":abbr:`productType ([OpenSearch Parameters for Collection Search] A string identifying the entry type (e.g. ER02_SAR_IM__0P, MER_RR__1P, SM_SLC__1S, GES_DISC_AIRH3STD_V005) (String ))`",:green:`queryable metadata`,,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,,metadata only
-productVersion,,,,,,metadata only,metadata only,metadata only,,metadata only,,
-:abbr:`publicationDate ([Additional INSPIRE obligated OpenSearch Parameters for Collection Search] The date when the resource was issued (Date time))`,,,metadata only,,,metadata only,,metadata only,metadata only,metadata only,,
-resolution,metadata only,,:green:`queryable metadata`,metadata only,metadata only,metadata only,,:green:`queryable metadata`,,metadata only,,metadata only
-sensorMode,metadata only,,:green:`queryable metadata`,metadata only,metadata only,metadata only,metadata only,:green:`queryable metadata`,metadata only,metadata only,,metadata only
+":abbr:`productType ([OpenSearch Parameters for Collection Search] A string identifying the entry type (e.g. ER02_SAR_IM__0P, MER_RR__1P, SM_SLC__1S, GES_DISC_AIRH3STD_V005) (String ))`",:green:`queryable metadata`,,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,,:green:`queryable metadata`
+productVersion,metadata only,,,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,metadata only,metadata only,,metadata only,,:green:`queryable metadata`
+:abbr:`publicationDate ([Additional INSPIRE obligated OpenSearch Parameters for Collection Search] The date when the resource was issued (Date time))`,metadata only,,metadata only,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,,metadata only,metadata only,metadata only,,:green:`queryable metadata`
+resolution,metadata only,,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,,:green:`queryable metadata`,,metadata only,,:green:`queryable metadata`
+sensorMode,metadata only,,:green:`queryable metadata`,:green:`queryable metadata`,:green:`queryable metadata`,metadata only,metadata only,:green:`queryable metadata`,metadata only,metadata only,,:green:`queryable metadata`
 ":abbr:`sensorType ([OpenSearch Parameters for Collection Search] A string identifying the sensor type. Suggested values are: OPTICAL, RADAR, ALTIMETRIC, ATMOSPHERIC, LIMB (String))`",,,,,,metadata only,,,,,,
 snowCover,,,:green:`queryable metadata`,,,metadata only,,:green:`queryable metadata`,,metadata only,,
 ":abbr:`spectralRange ([OpenSearch Parameters for Collection Search] A string identifying the sensor spectral range (e.g. INFRARED, NEAR-INFRARED, UV, VISIBLE) (String))`",,,,,,metadata only,,,,,,
diff --git a/eodag/api/product/metadata_mapping.py b/eodag/api/product/metadata_mapping.py
index e4bfa1aa..7f05a177 100644
--- a/eodag/api/product/metadata_mapping.py
+++ b/eodag/api/product/metadata_mapping.py
@@ -199,44 +199,6 @@ def format_metadata(search_param, *args, **kwargs):
             """
             return int(1e3 * get_timestamp(date_time))
 
-        @staticmethod
-        def convert_to_rounded_wkt(value):
-            wkt_value = wkt.dumps(value, rounding_precision=COORDS_ROUNDING_PRECISION)
-            # If needed, simplify WKT to prevent too long request failure
-            tolerance = 0.1
-            while len(wkt_value) > WKT_MAX_LEN and tolerance <= 1:
-                logger.debug(
-                    "Geometry WKT is too long (%s), trying to simplify it with tolerance %s",
-                    len(wkt_value),
-                    tolerance,
-                )
-                wkt_value = wkt.dumps(
-                    value.simplify(tolerance),
-                    rounding_precision=COORDS_ROUNDING_PRECISION,
-                )
-                tolerance += 0.1
-            if len(wkt_value) > WKT_MAX_LEN and tolerance > 1:
-                logger.warning("Failed to reduce WKT length lower than %s", WKT_MAX_LEN)
-            return wkt_value
-
-        @staticmethod
-        def convert_to_bounds_lists(input_geom):
-            if isinstance(input_geom, MultiPolygon):
-                geoms = [geom for geom in input_geom]
-                # sort with larger one at first (stac-browser only plots first one)
-                geoms.sort(key=lambda x: x.area, reverse=True)
-                return [list(x.bounds[0:4]) for x in geoms]
-            else:
-                return [list(input_geom.bounds[0:4])]
-
-        @staticmethod
-        def convert_to_geo_interface(geom):
-            return geojson.dumps(geom.__geo_interface__)
-
-        @staticmethod
-        def convert_csv_list(values_list):
-            return ",".join([str(x) for x in values_list])
-
         @staticmethod
         def convert_to_iso_utc_datetime_from_milliseconds(timestamp):
             """Convert a timestamp in milliseconds (int) to its ISO8601 UTC format
@@ -281,6 +243,44 @@ def format_metadata(search_param, *args, **kwargs):
                 dt = dt.astimezone(UTC)
             return dt.isoformat()[:10]
 
+        @staticmethod
+        def convert_to_rounded_wkt(value):
+            wkt_value = wkt.dumps(value, rounding_precision=COORDS_ROUNDING_PRECISION)
+            # If needed, simplify WKT to prevent too long request failure
+            tolerance = 0.1
+            while len(wkt_value) > WKT_MAX_LEN and tolerance <= 1:
+                logger.debug(
+                    "Geometry WKT is too long (%s), trying to simplify it with tolerance %s",
+                    len(wkt_value),
+                    tolerance,
+                )
+                wkt_value = wkt.dumps(
+                    value.simplify(tolerance),
+                    rounding_precision=COORDS_ROUNDING_PRECISION,
+                )
+                tolerance += 0.1
+            if len(wkt_value) > WKT_MAX_LEN and tolerance > 1:
+                logger.warning("Failed to reduce WKT length lower than %s", WKT_MAX_LEN)
+            return wkt_value
+
+        @staticmethod
+        def convert_to_bounds_lists(input_geom):
+            if isinstance(input_geom, MultiPolygon):
+                geoms = [geom for geom in input_geom]
+                # sort with larger one at first (stac-browser only plots first one)
+                geoms.sort(key=lambda x: x.area, reverse=True)
+                return [list(x.bounds[0:4]) for x in geoms]
+            else:
+                return [list(input_geom.bounds[0:4])]
+
+        @staticmethod
+        def convert_to_geo_interface(geom):
+            return geojson.dumps(geom.__geo_interface__)
+
+        @staticmethod
+        def convert_csv_list(values_list):
+            return ",".join([str(x) for x in values_list])
+
         @staticmethod
         def convert_remove_extension(string):
             parts = string.split(".")
@@ -365,6 +365,13 @@ def format_metadata(search_param, *args, **kwargs):
                 logger.error("Could not extract title infos from %s" % string)
                 return NOT_AVAILABLE
 
+    # if stac extension colon separator `:` is in search search params, parse it to prevent issues with vformat
+    if re.search(r"{[a-zA-Z0-9_-]*:[a-zA-Z0-9_-]*}", search_param):
+        search_param = re.sub(
+            r"{([a-zA-Z0-9_-]*):([a-zA-Z0-9_-]*)}", r"{\1_COLON_\2}", search_param
+        )
+        kwargs = {k.replace(":", "_COLON_"): v for k, v in kwargs.items()}
+
     return MetadataFormatter().vformat(search_param, args, kwargs)
 
 
diff --git a/eodag/resources/providers.yml b/eodag/resources/providers.yml
index d1e1181a..c62a2efc 100644
--- a/eodag/resources/providers.yml
+++ b/eodag/resources/providers.yml
@@ -1489,6 +1489,35 @@
       # This provider doesn't implement any pagination, let's just try to get the maximum number of
       # products available at once then, so we stick to 10_000.
       max_items_per_page: 10_000
+    metadata_mapping:
+      # redefine the following mapppings as the provider does not support advanced queries/filtering,
+      # these parameters will not be queryable
+      doi: '$.properties."sci:doi"'
+      processingLevel: '$.properties."processing:level"'
+      platform: '$.properties.constellation'
+      platformSerialIdentifier: '$.properties.platform'
+      instrument: '$.properties.instruments'
+      # INSPIRE obligated OpenSearch Parameters for Collection Search (Table 4)
+      title: '$.id'
+      abstract: '$.properties.description'
+      resolution: '$.properties.gsd'
+      publicationDate: '$.properties.published'
+      # OpenSearch Parameters for Product Search (Table 5)
+      orbitNumber: '$.properties."sat:relative_orbit"'
+      orbitDirection: '$.properties."sat:orbit_state"'
+      cloudCover: '$.properties."eo:cloud_cover"'
+      sensorMode: '$.properties."sar:instrument_mode"'
+      creationDate: '$.properties.created'
+      modificationDate: '$.properties.updated'
+      productVersion: '$.properties.version'
+      # OpenSearch Parameters for Acquistion Parameters Search (Table 6)
+      availabilityTime: '$.properties.availabilityTime'
+      acquisitionStation: '$.properties.acquisitionStation'
+      acquisitionSubType: '$.properties.acquisitionSubType'
+      illuminationAzimuthAngle: '$.properties."view:sun_azimuth"'
+      illuminationElevationAngle: '$.properties."view:sun_elevation"'
+      polarizationChannels: '$.properties."sar:polarizations"'
+      dopplerFrequency: '$.properties."sar:frequency_band"'
   products:
     S1_SAR_GRD:
       productType: sentinel1_l1c_grd
@@ -1564,39 +1593,43 @@
   roles:
     - host
   description: USGS Landsatlook SAT API
-  url: https://landsatlook.usgs.gov/sat-api/
+  url: https://landsatlook.usgs.gov/stac-server
   search: !plugin
     type: StacSearch
-    api_endpoint: https://landsatlook.usgs.gov/sat-api/collections/{collection}/items
+    api_endpoint: https://landsatlook.usgs.gov/stac-server/search
     pagination:
-      total_items_nb_key_path: '$.meta.found'
       # 2021/03/19: no more than 10_000 (if greater, returns a 500 error code)
       # but in practive if an Internal Server Error is returned for more than
       # about 500 products.
       max_items_per_page: 500
     metadata_mapping:
-      productType: '$.collection'
+      cloudCover:
+        - '{{"query":{{"eo:cloud_cover":{{"lte":{cloudCover}}}}}}}'
+        - '$.properties."eo:cloud_cover"'
+      platformSerialIdentifier:
+        - '{{"query":{{"platform":{{"eq":"{platformSerialIdentifier}"}}}}}}'
+        - '$.properties.platform'
       assets: '{$.assets#recursive_sub_str(r"https?(.*)landsatlook.usgs.gov/data/",r"s3\1usgs-landsat/")}'
   products:
     LANDSAT_C2L1:
-      collection: landsat-c2l1
+      productType: landsat-c2l1
     LANDSAT_C2L2_SR:
-      collection: landsat-c2l2-sr
+      productType: landsat-c2l2-sr
     LANDSAT_C2L2_ST:
-      collection: landsat-c2l2-st
+      productType: landsat-c2l2-st
     LANDSAT_C2L2ALB_BT:
-      collection: landsat-c2l2alb-bt
+      productType: landsat-c2l2alb-bt
     LANDSAT_C2L2ALB_SR:
-      collection: landsat-c2l2alb-sr
+      productType: landsat-c2l2alb-sr
     LANDSAT_C2L2ALB_ST:
-      collection: landsat-c2l2alb-st
+      productType: landsat-c2l2alb-st
     LANDSAT_C2L2ALB_TA:
-      collection: landsat-c2l2alb-ta
+      productType: landsat-c2l2alb-ta
     GENERIC_PRODUCT_TYPE:
-      collection: '{productType}'
+      productType: '{productType}'
   download: !plugin
     type: AwsDownload
-    base_uri: https://landsatlook.usgs.gov/sat-api/
+    base_uri: https://landsatlook.usgs.gov/stac-server
     flatten_top_dirs: True
   auth: !plugin
     type: AwsAuth
diff --git a/eodag/resources/stac_provider.yml b/eodag/resources/stac_provider.yml
index 8121a70a..1708438c 100644
--- a/eodag/resources/stac_provider.yml
+++ b/eodag/resources/stac_provider.yml
@@ -26,7 +26,7 @@ search:
   discover_metadata:
     auto_discovery: true
     metadata_pattern: '^[a-zA-Z0-9_:-]+$'
-    search_param: '{{"{metadata}"="{{{metadata}}}"}}'
+    search_param: '{{{{"query":{{{{"{metadata}":{{{{"eq":"{{{metadata}}}" }}}} }}}} }}}}'
     metadata_path: '$.properties.*'
   metadata_mapping:
     # OpenSearch Parameters for Collection Search (Table 3)
@@ -34,76 +34,77 @@ search:
       - '{{"collections":["{productType}"]}}'
       - '$.collection'
     doi:
-      - 'sci:doi'
+      - '{{"query":{{"sci:doi":{{"eq":"{doi}"}}}}}}'
       - '$.properties."sci:doi"'
     processingLevel:
-      - 'processing:level'
+      - '{{"query":{{"processing:level":{{"eq":"{processingLevel}"}}}}}}'
       - '$.properties."processing:level"'
     platform:
-      - 'constellation'
+      - '{{"query":{{"constellation":{{"eq":"{platform}"}}}}}}'
       - '$.properties.constellation'
     platformSerialIdentifier:
-      - 'platform'
+      - '{{"query":{{"platform":{{"eq":"{platformSerialIdentifier}"}}}}}}'
       - '$.properties.platform'
     instrument:
-      - 'instruments'
+    # to test
+      - '{{"query":{{"instruments":{{"eq":"{instrument}"}}}}}}'
       - '$.properties.instruments'
     # INSPIRE obligated OpenSearch Parameters for Collection Search (Table 4)
     title: '$.id'
     abstract: '$.properties.description'
     resolution:
-      - 'gsd'
+      - '{{"query":{{"gsd":{{"eq":"{resolution}"}}}}}}'
       - '$.properties.gsd'
     publicationDate:
-      - 'published'
+      - '{{"query":{{"published":{{"eq":"{publicationDate}"}}}}}}'
       - '$.properties.published'
     # OpenSearch Parameters for Product Search (Table 5)
     orbitNumber:
-      - 'sat:relative_orbit'
+      - '{{"query":{{"sat:relative_orbit":{{"eq":"{orbitNumber}"}}}}}}'
       - '$.properties."sat:relative_orbit"'
     orbitDirection:
-      - 'sat:orbit_state'
+      - '{{"query":{{"sat:orbit_state":{{"eq":"{orbitDirection}"}}}}}}'
       - '$.properties."sat:orbit_state"'
     cloudCover:
-      - 'eo:cloud_cover'
+      - '{{"query":{{"eo:cloud_cover":{{"lte":"{cloudCover}"}}}}}}'
       - '$.properties."eo:cloud_cover"'
     sensorMode:
-      - 'sar:instrument_mode'
+      - '{{"query":{{"sar:instrument_mode":{{"eq":"{sensorMode}"}}}}}}'
       - '$.properties."sar:instrument_mode"'
     creationDate:
-      - 'created'
+      - '{{"query":{{"created":{{"eq":"{creationDate}"}}}}}}'
       - '$.properties.created'
     modificationDate:
-      - 'updated'
+      - '{{"query":{{"updated":{{"eq":"{modificationDate}"}}}}}}'
       - '$.properties.updated'
     productVersion:
-      - 'version'
+      - '{{"query":{{"version":{{"eq":"{productVersion}"}}}}}}'
       - '$.properties.version'
     # OpenSearch Parameters for Acquistion Parameters Search (Table 6)
     availabilityTime:
-      - 'availabilityTime'
+      - '{{"query":{{"availabilityTime":{{"eq":"{availabilityTime}"}}}}}}'
       - '$.properties.availabilityTime'
     acquisitionStation:
-      - 'acquisitionStation'
+      - '{{"query":{{"acquisitionStation":{{"eq":"{acquisitionStation}"}}}}}}'
       - '$.properties.acquisitionStation'
     acquisitionSubType:
-      - 'acquisitionSubType'
+      - '{{"query":{{"acquisitionSubType":{{"eq":"{acquisitionSubType}"}}}}}}'
       - '$.properties.acquisitionSubType'
     startTimeFromAscendingNode: '$.properties.datetime'
     completionTimeFromAscendingNode:
       - '{{"datetime":"{startTimeFromAscendingNode#to_iso_utc_datetime}/{completionTimeFromAscendingNode#to_iso_utc_datetime}"}}'
       - '$.properties.end_datetime'
     illuminationAzimuthAngle:
-      - 'view:sun_azimuth'
+      - '{{"query":{{"view:sun_azimuth":{{"eq":"{illuminationAzimuthAngle}"}}}}}}'
       - '$.properties."view:sun_azimuth"'
     illuminationElevationAngle:
-      - 'view:sun_elevation'
+      - '{{"query":{{"view:sun_elevation":{{"eq":"{illuminationElevationAngle}"}}}}}}'
       - '$.properties."view:sun_elevation"'
     polarizationChannels:
-      - 'sar:polarizations'
+      - '{{"query":{{"sar:polarizations":{{"eq":"{polarizationChannels}"}}}}}}'
       - '$.properties."sar:polarizations"'
     dopplerFrequency:
-      - 'sar:frequency_band'
+      - '{{"query":{{"sar:frequency_band":{{"eq":"{dopplerFrequency}"}}}}}}'
       - '$.properties."sar:frequency_band"'
     # Custom parameters (not defined in the base document referenced above)
     id:
@@ -116,8 +117,6 @@ search:
     quicklook: '$.assets.quicklook.href'
     thumbnail: '$.assets.thumbnail.href'
     # storageStatus set to ONLINE for consistency between providers
-    storageStatus:
-      - 'storageStatus'
-      - '{$.null#replace_str("Not Available","ONLINE")}'
+    storageStatus: '{$.null#replace_str("Not Available","ONLINE")}'
     # Normalization code moves assets from properties to product's attr
     assets: '$.assets'


Test patch:
diff --git a/tests/units/test_metadata_mapping.py b/tests/units/test_metadata_mapping.py
index e510ed38..fc19611f 100644
--- a/tests/units/test_metadata_mapping.py
+++ b/tests/units/test_metadata_mapping.py
@@ -17,7 +17,7 @@
 # limitations under the License.
 import unittest
 
-from tests.context import format_metadata
+from tests.context import format_metadata, get_geometry_from_various
 
 
 class TestMetadataFormatter(unittest.TestCase):
@@ -39,6 +39,17 @@ class TestMetadataFormatter(unittest.TestCase):
             "1618956000000",
         )
 
+    def test_convert_to_iso_utc_datetime_from_milliseconds(self):
+        to_format = "{fieldname#to_iso_utc_datetime_from_milliseconds}"
+        self.assertEqual(
+            format_metadata(to_format, fieldname=1619029639123),
+            "2021-04-21T18:27:19.123Z",
+        )
+        self.assertEqual(
+            format_metadata(to_format, fieldname=1618963200000),
+            "2021-04-21T00:00:00.000Z",
+        )
+
     def test_convert_to_iso_utc_datetime(self):
         to_format = "{fieldname#to_iso_utc_datetime}"
         self.assertEqual(
@@ -76,13 +87,111 @@ class TestMetadataFormatter(unittest.TestCase):
             "2021-04-20",
         )
 
-    def test_convert_to_iso_utc_datetime_from_milliseconds(self):
-        to_format = "{fieldname#to_iso_utc_datetime_from_milliseconds}"
+    def test_convert_to_rounded_wkt(self):
+        to_format = "{fieldname#to_rounded_wkt}"
+        geom = get_geometry_from_various(geometry="POINT (0.11111 1.22222222)")
         self.assertEqual(
-            format_metadata(to_format, fieldname=1619029639123),
-            "2021-04-21T18:27:19.123Z",
+            format_metadata(to_format, fieldname=geom),
+            "POINT (0.1111 1.2222)",
+        )
+
+    def test_convert_to_bounds_lists(self):
+        to_format = "{fieldname#to_bounds_lists}"
+        geom = get_geometry_from_various(
+            geometry="""MULTIPOLYGON (
+                ((1.23 43.42, 1.23 43.76, 1.68 43.76, 1.68 43.42, 1.23 43.42)),
+                ((2.23 43.42, 2.23 43.76, 3.68 43.76, 3.68 43.42, 2.23 43.42))
+            )"""
         )
         self.assertEqual(
-            format_metadata(to_format, fieldname=1618963200000),
-            "2021-04-21T00:00:00.000Z",
+            format_metadata(to_format, fieldname=geom),
+            "[[2.23, 43.42, 3.68, 43.76], [1.23, 43.42, 1.68, 43.76]]",
+        )
+
+    def test_convert_to_geo_interface(self):
+        to_format = "{fieldname#to_geo_interface}"
+        geom = get_geometry_from_various(geometry="POINT (0.11 1.22)")
+        self.assertEqual(
+            format_metadata(to_format, fieldname=geom),
+            '{"type": "Point", "coordinates": [0.11, 1.22]}',
+        )
+
+    def test_convert_csv_list(self):
+        to_format = "{fieldname#csv_list}"
+        self.assertEqual(
+            format_metadata(to_format, fieldname=[1, 2, 3]),
+            "1,2,3",
+        )
+
+    def test_convert_remove_extension(self):
+        to_format = "{fieldname#remove_extension}"
+        self.assertEqual(
+            format_metadata(to_format, fieldname="foo.bar"),
+            "foo",
+        )
+
+    def test_convert_get_group_name(self):
+        to_format = (
+            "{fieldname#get_group_name((?P<this_is_foo>foo)|(?P<that_is_bar>bar))}"
+        )
+        self.assertEqual(
+            format_metadata(to_format, fieldname="foo"),
+            "this_is_foo",
+        )
+
+    def test_convert_replace_str(self):
+        to_format = r"{fieldname#replace_str(r'(.*) is (.*)',r'\1 was \2...')}"
+        self.assertEqual(
+            format_metadata(to_format, fieldname="this is foo"),
+            "this was foo...",
+        )
+
+    def test_convert_recursive_sub_str(self):
+        to_format = r"{fieldname#recursive_sub_str(r'(.*) is (.*)',r'\1 was \2...')}"
+        self.assertEqual(
+            format_metadata(
+                to_format, fieldname=[{"a": "this is foo", "b": [{"c": "that is bar"}]}]
+            ),
+            "[{'a': 'this was foo...', 'b': [{'c': 'that was bar...'}]}]",
+        )
+
+    def test_convert_dict_update(self):
+        to_format = '{fieldname#dict_update([["b",[["href","bar"],["title","baz"]]]])}'
+        self.assertEqual(
+            format_metadata(to_format, fieldname={"a": {"title": "foo"}}),
+            "{'a': {'title': 'foo'}, 'b': {'href': 'bar', 'title': 'baz'}}",
+        )
+
+    def test_convert_slice_str(self):
+        to_format = "{fieldname#slice_str(1,12,2)}"
+        self.assertEqual(
+            format_metadata(to_format, fieldname="abcdefghijklmnop"),
+            "bdfhjl",
+        )
+
+    def test_convert_fake_l2a_title_from_l1c(self):
+        to_format = "{fieldname#fake_l2a_title_from_l1c}"
+        self.assertEqual(
+            format_metadata(
+                to_format,
+                fieldname="S2B_MSIL1C_20210427T103619_N0300_R008_T31TCJ_20210427T124539",
+            ),
+            "S2B_MSIL2A_20210427T103619____________T31TCJ________________",
+        )
+
+    def test_convert_s2msil2a_title_to_aws_productinfo(self):
+        to_format = "{fieldname#s2msil2a_title_to_aws_productinfo}"
+        self.assertEqual(
+            format_metadata(
+                to_format,
+                fieldname="S2A_MSIL2A_20201201T100401_N0214_R122_T32SNA_20201201T114520",
+            ),
+            "https://roda.sentinel-hub.com/sentinel-s2-l2a/tiles/32/S/NA/2020/12/1/0/{collection}.json",
+        )
+
+    def test_format_stac_extension_parameter(self):
+        to_format = "{some_extension:a_parameter}"
+        self.assertEqual(
+            format_metadata(to_format, **{"some_extension:a_parameter": "value"}),
+            "value",
         )