diff --git a/pywcmp/wcmp2/ets.py b/pywcmp/wcmp2/ets.py index 09202f5..d5172b8 100644 --- a/pywcmp/wcmp2/ets.py +++ b/pywcmp/wcmp2/ets.py @@ -35,6 +35,7 @@ from jsonschema import FormatChecker from jsonschema.validators import Draft202012Validator from shapely.geometry import shape +from shapely.validation import explain_validity from pywis_topics.topics import TopicHierarchy @@ -287,6 +288,11 @@ def test_requirement_extent_geospatial(self): status['code'] = 'FAILED' status['messsage'] = 'Invalid geometry' + if not geometry.is_valid: + msg = f'Invalid geometry: {explain_validity(geometry)}' + status['code'] = 'FAILED' + status['messsage'] = msg + return status def test_requirement_extent_temporal(self): diff --git a/tests/data/wcmp2-failing-invalid-geometry-validity.json b/tests/data/wcmp2-failing-invalid-geometry-validity.json new file mode 100644 index 0000000..0bff3c0 --- /dev/null +++ b/tests/data/wcmp2-failing-invalid-geometry-validity.json @@ -0,0 +1,213 @@ +{ + "id": "urn:wmo:md:eu-eumetnet-gnss:weather.surface-based-observations.gnss-total-delay", + "type": "Feature", + "conformsTo": [ + "http://wis.wmo.int/spec/wcmp/2/conf/core" + ], + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + -70, + 10 + ], + [ + 40, + 10 + ], + [ + 40, + 90 + ], + [ + -70, + 90 + ], + [ + -70, + 10 + ] + ], + [ + [ + 113, + -10 + ], + [ + 154, + -10 + ], + [ + 154, + -44 + ], + [ + 113, + -44 + ], + [ + 113, + -10 + ] + ], + [ + [ + 73, + 54 + ], + [ + 135, + 54 + ], + [ + 135, + 18 + ], + [ + 73, + 18 + ], + [ + 73, + 54 + ] + ] + ] + ] + }, + "time": { + "interval": [ + "T00Z", + "T23Z" + ], + "resolution": "PT1H" + }, + "properties": { + "title": "Vertically integrated water vapour data", + "description": "Vertical integrated water vapour derived from GNSS sensors operated by national geodetic agencies.", + "contacts": [ + { + "identifier": "MO", + "name": "Met Office", + "organization": "Met Office", + "phones": [ + { + "value": "+443301350000" + } + ], + "emails": [ + { + "value": "servicedesk@metoffice.gov.uk" + } + ], + "addresses": [ + { + "deliveryPoint": [ + "FitzRoy Road" + ], + "name": "Met Office", + "city": "Exeter", + "administrativeArea": "Devon", + "postalCode": "EX1 3PB", + "country": "United Kingdom" + } + ], + "links": [ + { + "rel": "canonical", + "type": "text/html", + "href": "https://www.metoffice.gov.uk/about-us/contact" + } + ], + "roles": [ + "host", + "producer" + ] + } + ], + "keywords": [ + "weather", + "surface-based observations", + "Water vapour", + "integrated water vapour", + "gnss" + ], + "themes": [ + { + "concepts": [ + { + "id": "Application-ready", + "title": "dataReadiness", + "url": "http://reference.metoffice.gov.uk/DataCategories/data-readiness/Application-ready" + } + ], + "scheme": "http://reference.metoffice.gov.uk/DataCategories/data-readiness" + }, + { + "concepts": [ + { + "id": "Observations", + "title": "dataType", + "description": "Observations data are defined as measurements of the physical, chemical and/or biological environment, measured at a specific time in the past.\u202f They may be direct or indirect measurements and may be feature or coverage data.\u202f In order to be valid, observations data must carry geospatial and temporal metadata as well as variable values (or equivalent such as pixel values for a faster coverage).", + "url": "http://reference.metoffice.gov.uk/DataCategories/data-types/Observations" + } + ], + "scheme": "http://reference.metoffice.gov.uk/DataCategories/data-types" + }, + { + "concepts": [ + { + "id": "weather", + "title": "Weather", + "url": "https://github.com/wmo-im/wis2-topic-hierarchy/blob/main/topic-hierarchy/earth-system-discipline/weather" + } + ], + "scheme": "https://github.com/wmo-im/wis2-topic-hierarchy/blob/main/topic-hierarchy/earth-system-discipline" + }, + { + "concepts": [ + { + "id": "031", + "title": "Vertically integrated liquid-water content", + "url": "http://codes.wmo.int/bufr4/b/21/031" + } + ], + "scheme": "http://codes.wmo.int/bufr4/b" + } + ], + "created": "2025-05-06T14:00:00Z", + "updated": "2026-03-16T13:10:00Z", + "wmo:dataPolicy": "core", + "rights": "Users are granted free and unrestricted access to this data, without charge and with no conditions on use. Users are requested to attribute the producer of this data. WMO Unified Data Policy (Resolution 1 (Cg-Ext 2021))", + "type": "dataset", + "formats": [ + { + "name": "bufr4", + "mediaType": "application/bufr", + "edition": "4" + } + ], + "status": { + "id": "operational", + "title": "dataset is in 24/7 operation", + "url": "https://www.metoffice.gov.uk/services/data/business-data/observational" + } + }, + "links": [ + { + "rel": "related", + "href": "https://www.eumetnet.eu/observations/gnss-water-vapour-observations/", + "type": "text/html", + "title": "Documentation" + }, + { + "rel": "items", + "href": "mqtts://broker.metoffice.gov.uk", + "channel": "origin/a/wis2/eu-eumetnet-gnss/data/core/weather/surface-based-observations", + "type": "application/geo+json", + "title": "Data notifications broker" + } + ] +} diff --git a/tests/run_tests.py b/tests/run_tests.py index 62d4da2..eb0aff6 100644 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -277,6 +277,23 @@ def test_fail_invalid_geometry_range(self): self.assertEqual(codes.count('PASSED'), 11) self.assertEqual(codes.count('SKIPPED'), 0) + def test_fail_invalid_geometry_validity(self): + """ + Simple tests for a failing record with an invalid + geometry definition (self-intersection) + """ + + with open(get_test_file_path('data/wcmp2-failing-invalid-geometry-validity.json')) as fh: # noqa + record = json.load(fh) + ts = WMOCoreMetadataProfileTestSuite2(record) + results = ts.run_tests() + + codes = [r['code'] for r in results['tests']] + + self.assertEqual(codes.count('FAILED'), 1) + self.assertEqual(codes.count('PASSED'), 11) + self.assertEqual(codes.count('SKIPPED'), 0) + def test_fail_invalid_link_channel_centre_id(self): """ Simple tests for a failing record with an invalid