<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def query(self, area=None, date=None, raw=None, area_relation='Intersects', order_by=None, limit=None, offset=0, **keywords):
 """Query the OpenSearch API with the coordinates of an area, a date interval and any other search keywords accepted by the API. Parameters area : str, optional The area of interest formatted as a Well-Known Text string. date : tuple of (str or datetime) or str, optional A time interval filter based on the Sensing Start Time of the products. Expects a tuple of (start, end), e.g. ("NOW-1DAY", "NOW"). The timestamps can be either a Python datetime or a string in one of the following formats: - yyyyMMdd - yyyy-MM-ddThh:mm:ss.SSSZ (ISO-8601) - yyyy-MM-ddThh:mm:ssZ - NOW - NOW-<n>DAY(S) (or HOUR(S), MONTH(S), etc.) - NOW+<n>DAY(S) - yyyy-MM-ddThh:mm:ssZ-<n>DAY(S) - NOW/DAY (or HOUR, MONTH etc.) - rounds the value to the given unit Alternatively, an already fully formatted string such as "[NOW-1DAY TO NOW]" can be used as well. raw : str, optional Additional query text that will be appended to the query. area_relation : {'Intersects', 'Contains', 'IsWithin'}, optional What relation to use for testing the AOI. Case insensitive. - Intersects: true if the AOI and the footprint intersect (default) - Contains: true if the AOI is inside the footprint - IsWithin: true if the footprint is inside the AOI order_by: str, optional A comma-separated list of fields to order by (on server side). Prefix the field name by '+' or '-' to sort in ascending or descending order, respectively. Ascending order is used if prefix is omitted. Example: "cloudcoverpercentage, -beginposition". limit: int, optional Maximum number of products returned. Defaults to no limit. offset: int, optional The number of results to skip. Defaults to 0. **keywords Additional keywords can be used to specify other query parameters, e.g. `relativeorbitnumber=70`. See https://scihub.copernicus.eu/twiki/do/view/SciHubUserGuide/3FullTextSearch for a full list. Range values can be passed as two-element tuples, e.g. `cloudcoverpercentage=(0, 30)`. `None` can be used in range values for one-sided ranges, e.g. `orbitnumber=(16302, None)`. Ranges with no bounds (`orbitnumber=(None, None)`) will not be included in the query. The time interval formats accepted by the `date` parameter can also be used with any other parameters that expect time intervals (that is: 'beginposition', 'endposition', 'date', 'creationdate', and 'ingestiondate'). Returns ------- dict[string, dict] Products returned by the query as a dictionary with the product ID as the key and the product's attributes (a dictionary) as the value. """

        query = self.format_query(area, date, raw, area_relation, **keywords)

        self.logger.debug("Running query: order_by=%s, limit=%s, offset=%s, query=%s",
                          order_by, limit, offset, query)
        formatted_order_by = _format_order_by(order_by)
        response, count = self._load_query(query, formatted_order_by, limit, offset)
        self.logger.info("Found %s products", count)
        return _parse_opensearch_response(response)