[Git][debian-gis-team/owslib][master] 4 commits: New upstream version 0.30.0

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Mon Mar 11 04:43:03 GMT 2024



Bas Couwenberg pushed to branch master at Debian GIS Project / owslib


Commits:
cb720efd by Bas Couwenberg at 2024-03-11T05:36:42+01:00
New upstream version 0.30.0
- - - - -
dbad858e by Bas Couwenberg at 2024-03-11T05:36:46+01:00
Update upstream source from tag 'upstream/0.30.0'

Update to upstream version '0.30.0'
with Debian dir 429a8f4e6e0838f2331c96c5aff0cac83f50c4c5
- - - - -
9d19069b by Bas Couwenberg at 2024-03-11T05:39:24+01:00
New upstream release.

- - - - -
b7bf5bc2 by Bas Couwenberg at 2024-03-11T05:40:21+01:00
Set distribution to unstable.

- - - - -


14 changed files:

- debian/changelog
- docs/source/usage.rst
- owslib/__init__.py
- owslib/ogcapi/__init__.py
- owslib/ogcapi/coverages.py
- + owslib/ogcapi/edr.py
- owslib/wmts.py
- tests/test_ogcapi_coverages_pygeoapi.py
- + tests/test_ogcapi_edr_pygeoapi.py
- tests/test_ogcapi_features_pygeoapi.py
- tests/test_ogcapi_processes_pygeoapi.py
- tests/test_ogcapi_records_pycsw.py
- tests/test_opensearch_creodias.py
- tests/test_wmts.py


Changes:

=====================================
debian/changelog
=====================================
@@ -1,3 +1,10 @@
+owslib (0.30.0-1) unstable; urgency=medium
+
+  * Team upload.
+  * New upstream release.
+
+ -- Bas Couwenberg <sebastic at debian.org>  Mon, 11 Mar 2024 05:40:11 +0100
+
 owslib (0.29.3-1) unstable; urgency=medium
 
   * Team upload.


=====================================
docs/source/usage.rst
=====================================
@@ -218,17 +218,16 @@ OGC API - Coverages - Part 1: Core 1.0
   Global Deterministic Prediction System sample'
   >>> gdps['description']
   'Global Deterministic Prediction System sample'
-  >>> domainset = w.coverage_domainset('gdps-temperature')
-  >>> domainset['generalGrid']['axisLabels']
-  ['x', 'y']
-  >>> domainset['generalGrid']['gridLimits']['axisLabels']
-  ['i', 'j']
-  >>> rangetype = w.coverage_rangetype('gdps-temperature')
-  >>> len(rangetype['field'])
+  >>> gdps['extent']['spatial']['grid'][0]
+  >>> {"cellsCount": 2400, "resolution": 0.15000000000000002 }
+  >>> gdps['extent']['spatial']['grid'][1]
+  >>> {"cellsCount": 1201, "resolution": 0.15}
+  >>> schema = w.collection_schema('gdps-temperature')
+  >>> len(schema['properties'])
   1
-  >>> rangetype['field'][0]['definition']
-  'float64'
-  >> gdps_coverage_query = w.coverage('gdps-temperature', range_subset=[1])
+  >>> schema['properties']['1']['type']
+  'number'
+  >> gdps_coverage_data = w.coverage('gdps-temperature', range_subset=[1])
 
 OGC API - Records - Part 1: Core 1.0
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -288,8 +287,6 @@ OGC API - Records - Part 1: Core 1.0
 
   >>> w.collection_item_delete('my-catalogue', identifier)
 
-
-
 OGC API - Features - Part 4: Create, Replace, Update and Delete
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -300,34 +297,33 @@ OGC API - Features - Part 4: Create, Replace, Update and Delete
 
 .. code-block:: python
 
-   import json
-   from owslib.ogcapi.records import Records
+   >>> import json
+   >>> from owslib.ogcapi.records import Records
 
-   record_data = '/path/to/record.json'
+   >>> record_data = '/path/to/record.json'
 
-   url = 'http://localhost:8000'
-   collection_id = 'metadata:main'
+   >>> url = 'http://localhost:8000'
+   >>> collection_id = 'metadata:main'
 
-   r = Records(url)
+   >>> r = Records(url)
 
-   cat = r.collection(collection_id)
+   >>> cat = r.collection(collection_id)
 
-   with open(record_data) as fh:
-       data = json.load(fh)
+   >>> with open(record_data) as fh:
+   ...    data = json.load(fh)
 
-   identifier = data['id']
+   >>> identifier = data['id']
 
-   r.collection_item_delete(collection_id, identifier)
+   >>> r.collection_item_delete(collection_id, identifier)
 
    # insert metadata
-   r.collection_item_create(collection_id, data)
+   >>> r.collection_item_create(collection_id, data)
 
    # update metadata
-   r.collection_item_update(collection_id, identifier, data)
+   >>> r.collection_item_update(collection_id, identifier, data)
 
    # delete metadata
-   r.collection_item_delete(collection_id, identifier)
-
+   >>> r.collection_item_delete(collection_id, identifier)
 
 OGC API - Processes - Part 1: Core 1.0
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -345,7 +341,6 @@ OGC API - Processes - Part 1: Core 1.0
   >>> hello_world['title']
   'Hello World'
 
-
 OGC API - Maps - Part 1: Core 1.0
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -358,6 +353,16 @@ OGC API - Maps - Part 1: Core 1.0
   >>> with open("output.png", "wb") as fh:
   ...     fh.write(data.getbuffer())
 
+OGC API - Environmental Data Retrieval - Part 1: Core 1.0
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: python
+
+  >>> from owslib.ogcapi.edr import EnvironmentalDataRetrieval
+  >>> e = EnvironmentalDataRetrieval('http://localhost:5000')
+  >>> icoads_sst = m.collection('icoads-sst')
+  >>> data = e.query_data('icoads_sst', 'position', coords='POINT(-75 45)', parameter_names=['SST', 'AIRT'])
+
 
 WCS
 ---


=====================================
owslib/__init__.py
=====================================
@@ -1 +1 @@
-__version__ = '0.29.3'
+__version__ = '0.30.0'


=====================================
owslib/ogcapi/__init__.py
=====================================
@@ -228,6 +228,19 @@ class Collections(API):
         path = f'collections/{collection_id}'
         return self._request(path=path)
 
+    def collection_schema(self, collection_id: str) -> dict:
+        """
+        implements /collections/{collectionId}/schema
+
+        @type collection_id: string
+        @param collection_id: id of collection
+
+        @returns: `dict` of feature collection schema
+        """
+
+        path = f'collections/{collection_id}/schema'
+        return self._request(path=path)
+
     def collection_queryables(self, collection_id: str) -> dict:
         """
         implements /collections/{collectionId}/queryables


=====================================
owslib/ogcapi/coverages.py
=====================================
@@ -42,32 +42,6 @@ class Coverages(Collections):
 
         return coverages_
 
-    def coverage_domainset(self, collection_id: str, **kwargs: dict) -> dict:
-        """
-        implements /collection/{collectionId}/coverage/domainset
-
-        @type collection_id: string
-        @param collection_id: id of collection
-
-        @returns: coverage domainset results
-        """
-
-        path = f'collections/{collection_id}/coverage/domainset'
-        return self._request(path=path, kwargs=kwargs)
-
-    def coverage_rangetype(self, collection_id: str, **kwargs: dict) -> dict:
-        """
-        implements /collection/{collectionId}/coverage/rangetype
-
-        @type collection_id: string
-        @param collection_id: id of collection
-
-        @returns: coverage rangetype results
-        """
-
-        path = f'collections/{collection_id}/coverage/rangetype'
-        return self._request(path=path, kwargs=kwargs)
-
     def coverage(self, collection_id: str, **kwargs: dict) -> BinaryIO:
         """
         implements /collection/{collectionId}/coverage/


=====================================
owslib/ogcapi/edr.py
=====================================
@@ -0,0 +1,98 @@
+# =============================================================================
+# Copyright (c) 2023 Tom Kralidis
+#
+# Author: Tom Kralidis <tomkralidis at gmail.com>
+#
+# Contact email: tomkralidis at gmail.com
+# =============================================================================
+
+from io import BytesIO
+import logging
+from typing import BinaryIO
+
+from owslib.ogcapi.features import Features
+from owslib.util import Authentication
+
+LOGGER = logging.getLogger(__name__)
+
+
+class EnvironmentalDataRetrieval(Features):
+    """Abstraction for OGC API - Environmental Data Retrieval"""
+
+    def __init__(self, url: str, json_: str = None, timeout: int = 30,
+                 headers: dict = None, auth: Authentication = None):
+        __doc__ = Features.__doc__  # noqa
+        super().__init__(url, json_, timeout, headers, auth)
+
+    def data(self) -> list:
+        """
+        implements /collections filtered on EDR data resources
+
+        @returns: `list` of filtered collections object
+        """
+
+        datas = []
+        collections_ = super().collections()
+
+        for c_ in collections_['collections']:
+            for l_ in c_['links']:
+                if 'data' in l_['rel']:
+                    datas.append(c_['id'])
+                    break
+
+        return datas
+
+    def query_data(self, collection_id: str,
+                   query_type: str, **kwargs: dict) -> BinaryIO:
+        """
+        implements /collection/{collectionId}/coverage/
+
+        @type collection_id: string
+        @param collection_id: id of collection
+        @type query_type: string
+        @param query_type: query type
+        @type bbox: list
+        @param bbox: list of minx,miny,maxx,maxy
+        @type coords: string
+        @param coords: well-known text geometry
+        @type datetime_: string
+        @type datetime_: string
+        @param datetime_: time extent or time instant
+        @type parameter_names: list
+        @param parameter_names: list of parameter names
+
+        @returns: coverage data
+        """
+
+        kwargs_ = {}
+
+        if 'bbox' in kwargs:
+            kwargs_['bbox'] = ','.join(list(map(str, kwargs['bbox'])))
+        if 'parameter_names' in kwargs:
+            kwargs_['parameter_names'] = ','.join(kwargs['parameter_names'])
+
+        query_args_map = {
+            'coords': 'coords',
+            'corridor_width': 'corridor-width',
+            'corridor_height': 'corridor-height',
+            'crs': 'crs',
+            'cube-z': 'cube-z',
+            'datetime_': 'datetime',
+            'height': 'height',
+            'height_units': 'height-units',
+            'resolution_x': 'resolution-x',
+            'resolution_y': 'resolution-y',
+            'resolution_z': 'resolution-z',
+            'width': 'width',
+            'width_units': 'width-units',
+            'within': 'within',
+            'z': 'z'
+        }
+
+        for key, value in query_args_map.items():
+            if key in kwargs:
+                kwargs_[value] = kwargs[key]
+
+        path = f'collections/{collection_id}/{query_type}'
+
+        return self._request(path=path, kwargs=kwargs_)


=====================================
owslib/wmts.py
=====================================
@@ -78,6 +78,14 @@ _SERVICE_METADATA_URL_TAG = _WMTS_NS + 'ServiceMetadataURL'
 _STYLE_TAG = _WMTS_NS + 'Style'
 _STYLE_LEGEND_URL = _WMTS_NS + 'LegendURL'
 
+# Table 9, page 22-23, Parts of Dimensions data structure
+_DIMENSION_TAG = _WMTS_NS + 'Dimension'
+_UOM_TAG = _OWS_NS + 'UOM'
+_DIMENSION_UNIT_SYMBOL_TAG = _WMTS_NS + 'UnitSymbol'
+_DIMENSION_DEFAULT_TAG = _WMTS_NS + 'Default'
+_DIMENSION_CURRENT_TAG = _WMTS_NS + 'Current'
+_DIMENSION_VALUE_TAG = _WMTS_NS + 'Value'
+
 _THEME_TAG = _WMTS_NS + 'Theme'
 _THEMES_TAG = _WMTS_NS + 'Themes'
 _TILE_HEIGHT_TAG = _WMTS_NS + 'TileHeight'
@@ -793,6 +801,65 @@ class ContentMetadata:
                          _KEYWORDS_TAG + '/' + _KEYWORD_TAG)]
         self.infoformats = [f.text for f in elem.findall(_INFO_FORMAT_TAG)]
 
+        self.dimensions = {}
+        for dim in elem.findall(_DIMENSION_TAG):
+            dimension = {}
+
+            identifier = dim.find(_IDENTIFIER_TAG)
+            if identifier is None:
+                # mandatory parameter
+                raise ValueError('%s missing identifier' % (dim,))
+            if identifier.text in self.dimensions:
+                # domain identifier SHALL be unique
+                warnings.warn('%s identifier duplicated, taking first occurence' % (dim,))
+                continue
+
+            values = [f.text for f in dim.findall(_DIMENSION_VALUE_TAG)]
+            if len(values) == 0:
+                raise ValueError(
+                    '%s list of values can not be empty' % (dim,)
+                )
+            dimension['values'] = values
+
+            title = dim.find(_TITLE_TAG)
+            if title is not None:
+                dimension['title'] = title.text
+
+            abstract = dim.find(_ABSTRACT_TAG)
+            if abstract is not None:
+                dimension['abstract'] = abstract.text
+
+            keywords = [
+                f.text for f in dim.findall(_KEYWORDS_TAG + '/' + _KEYWORD_TAG)
+            ]
+            if keywords:
+                dimension['keywords'] = keywords
+
+            uom = dim.find(_UOM_TAG)
+            if uom is not None:
+                dimension['UOM'] = uom.text
+
+            unit_symbol = dim.find(_DIMENSION_UNIT_SYMBOL_TAG)
+            if unit_symbol is not None:
+                dimension['unit_symbol'] = unit_symbol.text
+
+            default_value = dim.find(_DIMENSION_DEFAULT_TAG)
+            if default_value in ['default', 'current', '', None]:
+                # mandatory parameter
+                raise ValueError(
+                    '%s default value must not be empty or \'default\' or \'current\''
+                    % (dim,)
+                )
+            dimension['default'] = default_value.text
+
+            current = dim.find(_DIMENSION_CURRENT_TAG)
+            if current and current.text == 'true':
+                dimension['current'] = True
+            else:
+                dimension['current'] = False
+
+            self.dimensions[identifier.text] = dimension
+
         self.layers = []
         for child in elem.findall(_LAYER_TAG):
             self.layers.append(ContentMetadata(child, self))


=====================================
tests/test_ogcapi_coverages_pygeoapi.py
=====================================
@@ -35,18 +35,16 @@ def test_ogcapi_coverages_pygeoapi():
     assert gdps['id'] == 'gdps-temperature'
     assert gdps['title'] == 'Global Deterministic Prediction System sample'
     assert gdps['description'] == 'Global Deterministic Prediction System sample'  # noqa
-
-    domainset = w.coverage_domainset('gdps-temperature')
-
-    assert domainset['generalGrid']['axisLabels'] == ['Long', 'Lat']
-
-    assert domainset['generalGrid']['gridLimits']['axisLabels'] == ['i', 'j']
-
-    rangetype = w.coverage_rangetype('gdps-temperature')
-    assert len(rangetype['field']) == 1
-    assert rangetype['field'][0]['name'] == 'Temperature [C]'
-    assert rangetype['field'][0]['uom']['code'] == '[C]'
-    assert rangetype['field'][0]['encodingInfo']['dataType'] == 'http://www.opengis.net/def/dataType/OGC/0/float64'  # noqa
+    assert gdps['extent']['spatial']['grid'][0]['cellsCount'] == 2400
+    assert gdps['extent']['spatial']['grid'][0]['resolution'] == 0.15000000000000002  # noqa
+    assert gdps['extent']['spatial']['grid'][1]['cellsCount'] == 1201
+    assert gdps['extent']['spatial']['grid'][1]['resolution'] == 0.15
+
+    schema = w.collection_schema('gdps-temperature')
+    assert len(schema['properties']) == 1
+    assert schema['properties']['1']['title'] == 'Temperature [C]'
+    assert schema['properties']['1']['type'] == 'number'
+    assert schema['properties']['1']['x-ogc-unit'] == '[C]'
 
     with pytest.raises(RuntimeError):
         w.coverage('gdps-temperature', properties=[8])


=====================================
tests/test_ogcapi_edr_pygeoapi.py
=====================================
@@ -0,0 +1,43 @@
+from tests.utils import service_ok
+
+import pytest
+
+from owslib.ogcapi.edr import EnvironmentalDataRetrieval
+
+SERVICE_URL = 'https://demo.pygeoapi.io/master/'
+
+
+ at pytest.mark.online
+ at pytest.mark.skipif(not service_ok(SERVICE_URL),
+                    reason='service is unreachable')
+def test_ogcapi_coverages_pygeoapi():
+    w = EnvironmentalDataRetrieval(SERVICE_URL)
+
+    assert w.url == SERVICE_URL
+    assert w.url_query_string is None
+
+    api = w.api()
+    assert api['components']['parameters'] is not None
+    paths = api['paths']
+    assert paths is not None
+    assert paths['/collections/icoads-sst'] is not None
+
+    conformance = w.conformance()
+    assert len(conformance['conformsTo']) > 1
+
+    collections = w.collections()
+    assert len(collections) > 0
+
+    datas = w.data()
+    assert len(datas) > 0
+
+    icoads = w.collection('icoads-sst')
+    assert icoads['id'] == 'icoads-sst'
+    assert icoads['title'] == 'International Comprehensive Ocean-Atmosphere Data Set (ICOADS)'  # noqa
+    assert icoads['description'] == 'International Comprehensive Ocean-Atmosphere Data Set (ICOADS)'  # noqa
+
+    parameter_names = icoads['parameter_names'].keys()
+    assert sorted(parameter_names) == ['AIRT', 'SST', 'UWND', 'VWND']
+
+    response = w.query_data('icoads-sst', 'position', coords='POINT(-75 45)')
+    assert isinstance(response, dict)


=====================================
tests/test_ogcapi_features_pygeoapi.py
=====================================
@@ -36,8 +36,8 @@ def test_ogcapi_features_pygeoapi():
     assert lakes['title'] == 'Large Lakes'
     assert lakes['description'] == 'lakes of the world, public domain'
 
-    #lakes_queryables = w.collection_queryables('lakes')
-    #assert len(lakes_queryables['queryables']) == 6
+    # lakes_queryables = w.collection_queryables('lakes')
+    # assert len(lakes_queryables['queryables']) == 6
 
     # Minimum of limit param is 1
     with pytest.raises(RuntimeError):


=====================================
tests/test_ogcapi_processes_pygeoapi.py
=====================================
@@ -29,7 +29,7 @@ def test_ogcapi_processes_pygeoapi():
     assert len(collections) > 0
 
     processes = p.processes()
-    assert len(processes) == 5
+    assert len(processes) == 6
 
     hello_world = p.process('hello-world')
     assert hello_world['id'] == 'hello-world'


=====================================
tests/test_ogcapi_records_pycsw.py
=====================================
@@ -23,7 +23,7 @@ def test_ogcapi_records_pycsw():
     assert paths['/collections/{collectionId}'] is not None
 
     conformance = w.conformance()
-    assert len(conformance['conformsTo']) == 18
+    assert len(conformance['conformsTo']) == 14
 
     collections = w.collections()
     assert len(collections) > 0
@@ -40,7 +40,7 @@ def test_ogcapi_records_pycsw():
     assert isinstance(w.response, dict)
 
     pycsw_cite_demo_queryables = w.collection_queryables('metadata:main')
-    assert len(pycsw_cite_demo_queryables['properties'].keys()) == 12
+    assert len(pycsw_cite_demo_queryables['properties'].keys()) == 13
 
     # Minimum of limit param is 1
     with pytest.raises(RuntimeError):


=====================================
tests/test_opensearch_creodias.py
=====================================
@@ -19,7 +19,7 @@ def test_opensearch_creodias():
     assert o.description.description == 'Sentinel-1 Collection'
     assert o.description.language == 'en'
 
-    assert len(o.description.urls) == 2
+    assert len(o.description.urls) == 1
 
     assert len(o.description.urls['application/json']['parameters']) > 0
 


=====================================
tests/test_wmts.py
=====================================
@@ -27,6 +27,8 @@ def test_wmts():
     # Available Layers:
     assert len(wmts.contents.keys()) > 0
     assert sorted(list(wmts.contents))[0] == 'AIRS_CO_Total_Column_Day'
+    # at least one of the layers have a time dimension parsed
+    assert wmts_dimensions_time_domain_exists(wmts.contents), "No layer has time dimension parsed"
     # Fetch a tile (using some defaults):
     tile = wmts.gettile(layer='MODIS_Terra_CorrectedReflectance_TrueColor',
                         tilematrixset='EPSG4326_250m', tilematrix='0',
@@ -115,3 +117,15 @@ def test_wmts_rest_only():
     wmts = WebMapTileService(SERVICE_URL_REST)
     tile = wmts.gettile(layer="bmaporthofoto30cm", tilematrix="10", row=357, column=547)
     assert tile.info()['Content-Type'] == 'image/jpeg'
+
+def wmts_dimensions_time_domain_exists(input_dict):
+    # returns True if there is a layer with a 'time' dimension
+    # parsed and contains a non-empty default value
+    return any(
+        hasattr(value, 'dimensions') and
+        isinstance(getattr(value, 'dimensions'), dict) and
+        isinstance(value.dimensions['time'], dict) and
+        'default' in value.dimensions['time'] and 
+        len(value.dimensions['time']['default']) > 0
+        for value in input_dict.values()
+    )



View it on GitLab: https://salsa.debian.org/debian-gis-team/owslib/-/compare/a2202094a18ac1bc62071c372f31bb14ae6aa7bf...b7bf5bc21bc54c9c98c8bff37194cb17e1a47019

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/owslib/-/compare/a2202094a18ac1bc62071c372f31bb14ae6aa7bf...b7bf5bc21bc54c9c98c8bff37194cb17e1a47019
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20240311/ae318243/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list