[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