From cdcfce7a01343697b7787e3d88ad9ebb3818a078 Mon Sep 17 00:00:00 2001 From: Vincent Fazio Date: Sun, 24 Mar 2024 01:06:50 +1100 Subject: [PATCH] Add support for ISO 19115 Part 3 XML (#900) * Add ISO 19115 Part 3 XML parsing, including unit test * Improved ISO 19115 Part 3 XML parsing - Added ArcGIS synthetic sample - Added tests for DQ_DataQuality, MD_FeatureCatalogueDescription and MD_Bands - Ability to parse older mdb v1.0 XML * Add iso_3 service tests * Fix missing thesaurus keyword bug * Add test for get_all_contacts() * Make iso_3 DQ_DataQuality more consistent * Fix bug in iso_3 legal constraints * Fix iso_3 identifier resourcelanguage xpath error * Fix xpath bug in iso_3 distribution * Add ISO 19115 Part 3 XML gfc:FC_FeatureCatalogue * Fix up metadata URL tests - Some 'Offline' tests were still calling on external services, monkeypatch of 'OpenURL' not working - Fixed up monkeypatch of 'OpenURL' calls - Added function to detect outer tag for iso_3.py - Added code to utilise iso_3 in WFS/WMS GetCapabilities responses - Added ISO 19115 Part 3 XML tests using pytest parameters * Remove redundant test file * Add CSW geonetwork tests for iso_3.py * Add spaces to inline comments for flake8 * Add spaces to inline comments for flake8 * Fix error in path description * Rename 'iso_3' to 'iso3' etc. --- owslib/catalogue/csw2.py | 8 + owslib/catalogue/csw3.py | 8 + owslib/feature/wfs100.py | 7 +- owslib/feature/wfs110.py | 10 +- owslib/feature/wfs200.py | 8 + owslib/iso3.py | 1479 +++++++ owslib/map/wms111.py | 8 + owslib/map/wms130.py | 7 + tests/resources/iso3_examples/README.txt | 10 + .../resources/iso3_examples/arcgis-sample.xml | 3762 +++++++++++++++++ .../iso3_examples/auscope-3d-model.xml | 341 ++ .../metawal.wallonie.be-catchments.xml | 946 +++++ .../iso3_examples/metawal.wallonie.be-srv.xml | 885 ++++ tests/test_csw_geonetwork.py | 21 + tests/test_iso3_parsing.py | 610 +++ tests/test_remote_metadata.py | 123 +- 16 files changed, 8196 insertions(+), 37 deletions(-) create mode 100644 owslib/iso3.py create mode 100644 tests/resources/iso3_examples/README.txt create mode 100644 tests/resources/iso3_examples/arcgis-sample.xml create mode 100644 tests/resources/iso3_examples/auscope-3d-model.xml create mode 100644 tests/resources/iso3_examples/metawal.wallonie.be-catchments.xml create mode 100644 tests/resources/iso3_examples/metawal.wallonie.be-srv.xml create mode 100644 tests/test_iso3_parsing.py diff --git a/owslib/catalogue/csw2.py b/owslib/catalogue/csw2.py index 690e5782a..193d831e3 100644 --- a/owslib/catalogue/csw2.py +++ b/owslib/catalogue/csw2.py @@ -20,6 +20,8 @@ from owslib import util from owslib import ows from owslib.iso import MD_Metadata, FC_FeatureCatalogue +from owslib.iso3 import MD_Metadata as MD_Metadata3 # ISO 19115 Part 3 XML +from owslib.iso3 import FC_FeatureCatalogue as FC_FeatureCatalogue3 # ISO 19115 Part 3 XML from owslib.fgdc import Metadata from owslib.dif import DIF from owslib.gm03 import GM03 @@ -578,6 +580,12 @@ def _parserecords(self, outputschema, esn): val = i.find(util.nspath_eval('gm03:fileIdentifier', namespaces)) identifier = self._setidentifierkey(util.testXMLValue(val)) self.records[identifier] = GM03(i) + elif MD_Metadata3.handles(outputschema): # ISO 19115 Part 3 XML + for elem, id in MD_Metadata3.find_ids(self._exml): + self.records[self._setidentifierkey(id)] = MD_Metadata3(elem) + for i in self._exml.findall('.//' + util.nspath_eval('gfc:FC_FeatureCatalogue', namespaces)): + identifier = self._setidentifierkey(util.testXMLValue(i.attrib['uuid'], attrib=True)) + self.records[identifier] = FC_FeatureCatalogue3(i) else: # process default for i in self._exml.findall('.//' + util.nspath_eval('csw:%s' % self._setesnel(esn), namespaces)): val = i.find(util.nspath_eval('dc:identifier', namespaces)) diff --git a/owslib/catalogue/csw3.py b/owslib/catalogue/csw3.py index 38fb22e0c..64e9ea589 100644 --- a/owslib/catalogue/csw3.py +++ b/owslib/catalogue/csw3.py @@ -20,6 +20,8 @@ from owslib import util from owslib import ows from owslib.iso import MD_Metadata, FC_FeatureCatalogue +from owslib.iso3 import MD_Metadata as MD_Metadata3 # ISO 19115 Part 3 XML +from owslib.iso3 import FC_FeatureCatalogue as FC_FeatureCatalogue3 # ISO 19115 Part 3 XML from owslib.fgdc import Metadata from owslib.dif import DIF from owslib.gm03 import GM03 @@ -477,6 +479,12 @@ def _parserecords(self, outputschema, esn): val = i.find(util.nspath_eval('gm03:fileIdentifier', namespaces)) identifier = self._setidentifierkey(util.testXMLValue(val)) self.records[identifier] = GM03(i) + elif MD_Metadata3.handles(outputschema): # ISO 19115 Part 3 XML + for elem, id in MD_Metadata3.find_ids(self._exml): + self.records[self._setidentifierkey(id)] = MD_Metadata3(elem) + for i in self._exml.findall('.//' + util.nspath_eval('gfc:FC_FeatureCatalogue', namespaces)): + identifier = self._setidentifierkey(util.testXMLValue(i.attrib['uuid'], attrib=True)) + self.records[identifier] = FC_FeatureCatalogue3(i) else: # process default for i in self._exml.findall('.//' + util.nspath_eval('csw30:%s' % self._setesnel(esn), namespaces)): val = i.find(util.nspath_eval('dc:identifier', namespaces)) diff --git a/owslib/feature/wfs100.py b/owslib/feature/wfs100.py index e96880703..0484affd9 100644 --- a/owslib/feature/wfs100.py +++ b/owslib/feature/wfs100.py @@ -25,6 +25,7 @@ from owslib.etree import etree from owslib.fgdc import Metadata from owslib.iso import MD_Metadata +from owslib.iso3 import MD_Metadata as MD_Metadata3 # ISO 19115 Part 3 XML from owslib.crs import Crs from owslib.namespaces import Namespaces from owslib.feature.schema import get_schema @@ -470,7 +471,11 @@ def parse_remote_metadata(self, timeout=30): if mdelem is not None: metadataUrl["metadata"] = MD_Metadata(mdelem) else: - metadataUrl["metadata"] = None + mdelem = MD_Metadata3.find_start(doc) + if mdelem is not None: + metadataUrl["metadata"] = MD_Metadata3(mdelem) + else: + metadataUrl["metadata"] = None except Exception: metadataUrl["metadata"] = None diff --git a/owslib/feature/wfs110.py b/owslib/feature/wfs110.py index c58decc44..ab28e922a 100644 --- a/owslib/feature/wfs110.py +++ b/owslib/feature/wfs110.py @@ -13,12 +13,12 @@ testXMLValue, nspath_eval, ServiceException, - Authentication, - # openURL, + Authentication ) from owslib.etree import etree from owslib.fgdc import Metadata from owslib.iso import MD_Metadata +from owslib.iso3 import MD_Metadata as MD_Metadata3 # ISO 19115 Part 3 XML from owslib.ows import ( OwsCommon, ServiceIdentification, @@ -488,6 +488,10 @@ def parse_remote_metadata(self, timeout=30): if mdelem is not None: metadataUrl["metadata"] = MD_Metadata(mdelem) else: - metadataUrl["metadata"] = None + mdelem = MD_Metadata3.find_start(doc) + if mdelem is not None: + metadataUrl["metadata"] = MD_Metadata3(mdelem) + else: + metadataUrl["metadata"] = None except Exception: metadataUrl["metadata"] = None diff --git a/owslib/feature/wfs200.py b/owslib/feature/wfs200.py index c820ec64a..296548b08 100644 --- a/owslib/feature/wfs200.py +++ b/owslib/feature/wfs200.py @@ -10,6 +10,7 @@ from owslib import util from owslib.fgdc import Metadata from owslib.iso import MD_Metadata +from owslib.iso3 import MD_Metadata as MD_Metadata3 # ISO 19115 Part 3 XML from owslib.ows import Constraint, ServiceIdentification, ServiceProvider, OperationsMetadata from owslib.etree import etree from owslib.util import nspath, testXMLValue, openURL, Authentication @@ -611,5 +612,12 @@ def parse_remote_metadata(self, timeout=30): if mdelem is not None: metadataUrl["metadata"] = MD_Metadata(mdelem) continue + else: # ISO 19115 Part 3 XML + mdelem = MD_Metadata3.find_start(doc) + if mdelem is not None: + metadataUrl["metadata"] = MD_Metadata3(mdelem) + else: + metadataUrl["metadata"] = None + continue except Exception: metadataUrl["metadata"] = None diff --git a/owslib/iso3.py b/owslib/iso3.py new file mode 100644 index 000000000..4e3689d8e --- /dev/null +++ b/owslib/iso3.py @@ -0,0 +1,1479 @@ +# -*- coding: ISO-8859-15 -*- +# ============================================================================= +# Copyright (c) 2023 CSIRO Australia +# +# Author : Vincent Fazio +# +# Contact email: vincent.fazio@csiro.au +# ============================================================================= + +# flake8: noqa: E501 + +""" ISO 19115 Part 3 XML metadata parser + + Parsing is initiated by passing in etree root Element to the 'MD_Metadata' constructor: + + from owslib.etree import etree + from owslib.iso3 import MD_Metadata + + exml = etree.fromstring(xml_bytes) + mdb = MD_Metadata(exml) +""" + +from owslib.etree import etree +from owslib import util + + +# ISO 19115 Part 3 XML namespaces +NAMESPACES_V2 = { + "mdb":"http://standards.iso.org/iso/19115/-3/mdb/2.0", + "cat":"http://standards.iso.org/iso/19115/-3/cat/1.0", + "gfc":"http://standards.iso.org/iso/19110/gfc/1.1", + "cit":"http://standards.iso.org/iso/19115/-3/cit/2.0", + "gcx":"http://standards.iso.org/iso/19115/-3/gcx/1.0", + "gex":"http://standards.iso.org/iso/19115/-3/gex/1.0", + "lan":"http://standards.iso.org/iso/19115/-3/lan/1.0", + "srv":"http://standards.iso.org/iso/19115/-3/srv/2.1", + "mas":"http://standards.iso.org/iso/19115/-3/mas/1.0", + "mcc":"http://standards.iso.org/iso/19115/-3/mcc/1.0", + "mco":"http://standards.iso.org/iso/19115/-3/mco/1.0", + "mda":"http://standards.iso.org/iso/19115/-3/mda/1.0", + "mds":"http://standards.iso.org/iso/19115/-3/mds/2.0", + "mdt":"http://standards.iso.org/iso/19115/-3/mdt/2.0", + "mex":"http://standards.iso.org/iso/19115/-3/mex/1.0", + "mmi":"http://standards.iso.org/iso/19115/-3/mmi/1.0", + "mpc":"http://standards.iso.org/iso/19115/-3/mpc/1.0", + "mrc":"http://standards.iso.org/iso/19115/-3/mrc/2.0", + "mrd":"http://standards.iso.org/iso/19115/-3/mrd/1.0", + "mri":"http://standards.iso.org/iso/19115/-3/mri/1.0", + "mrl":"http://standards.iso.org/iso/19115/-3/mrl/2.0", + "mrs":"http://standards.iso.org/iso/19115/-3/mrs/1.0", + "msr":"http://standards.iso.org/iso/19115/-3/msr/2.0", + "mdq":"http://standards.iso.org/iso/19157/-2/mdq/1.0", + "mac":"http://standards.iso.org/iso/19115/-3/mac/2.0", + "gco":"http://standards.iso.org/iso/19115/-3/gco/1.0", + "gml":"http://www.opengis.net/gml", + "xlink":"http://www.w3.org/1999/xlink", + "xsi":"http://www.w3.org/2001/XMLSchema-instance" +} + +NAMESPACES_V1 = { + "xsi":"http://www.w3.org/2001/XMLSchema-instance", + "cat":"http://standards.iso.org/iso/19115/-3/cat/1.0", + "cit":"http://standards.iso.org/iso/19115/-3/cit/1.0", + "gcx":"http://standards.iso.org/iso/19115/-3/gcx/1.0", + "gex":"http://standards.iso.org/iso/19115/-3/gex/1.0", + "gfc":"http://standards.iso.org/iso/19110/gfc/1.1", + "lan":"http://standards.iso.org/iso/19115/-3/lan/1.0", + "srv":"http://standards.iso.org/iso/19115/-3/srv/2.0", + "mac":"http://standards.iso.org/iso/19115/-3/mac/1.0", + "mas":"http://standards.iso.org/iso/19115/-3/mas/1.0", + "mcc":"http://standards.iso.org/iso/19115/-3/mcc/1.0", + "mco":"http://standards.iso.org/iso/19115/-3/mco/1.0", + "mda":"http://standards.iso.org/iso/19115/-3/mda/1.0", + "mdb":"http://standards.iso.org/iso/19115/-3/mdb/1.0", + "mdt":"http://standards.iso.org/iso/19115/-3/mdt/1.0", + "mex":"http://standards.iso.org/iso/19115/-3/mex/1.0", + "mrl":"http://standards.iso.org/iso/19115/-3/mrl/1.0", + "mds":"http://standards.iso.org/iso/19115/-3/mds/1.0", + "mmi":"http://standards.iso.org/iso/19115/-3/mmi/1.0", + "mpc":"http://standards.iso.org/iso/19115/-3/mpc/1.0", + "mrc":"http://standards.iso.org/iso/19115/-3/mrc/1.0", + "mrd":"http://standards.iso.org/iso/19115/-3/mrd/1.0", + "mri":"http://standards.iso.org/iso/19115/-3/mri/1.0", + "mrs":"http://standards.iso.org/iso/19115/-3/mrs/1.0", + "msr":"http://standards.iso.org/iso/19115/-3/msr/1.0", + "mdq":"http://standards.iso.org/iso/19157/-2/mdq/1.0", + "dqc":"http://standards.iso.org/iso/19157/-2/dqc/1.0", + "gco":"http://standards.iso.org/iso/19115/-3/gco/1.0", + "gml":"http://www.opengis.net/gml/3.2", + "xlink":"http://www.w3.org/1999/xlink" +} + +class printable(): + """ A super class used to roughly pretty print class members + + Usage: + + mdb = MD_Metadata(exml) + print(mdb.format_me()) + + """ + + def format_me(self, idx=0): + """ Returns a formatted string version of class + + :param idx: optional indentation index, internal use only, used for 'printable' member classes + :returns: string version of class and members + """ + repr_str = "\n" + for d in dir(self): + if not d.startswith("__") and not callable(getattr(self,d)): + if isinstance(getattr(self,d), (str, bytes)): + repr_str += " " * idx + f"{self.__class__.__name__}:{d}='{getattr(self,d)[:100]}'\n" + elif isinstance(getattr(self,d), list): + repr_str += " " * idx + f"{self.__class__.__name__}:{d}=[\n" + for item in getattr(self,d): + if isinstance(item, printable): + repr_str += " " * idx + f" {item.format_me(idx+1)}" + elif item is not None: + repr_str += " " * idx + f" {item}\n" + repr_str += " " * idx + "]\n" + elif isinstance(getattr(self,d), printable): + repr_str += " " * idx + f"{self.__class__.__name__}:{d}={getattr(self,d).format_me(idx+1)}" + return repr_str + + +class MD_Metadata(printable): + """ Process mdb:MD_Metadata + """ + def __init__(self, md=None): + """ + Parses XML root tree + + :param md: etree.Element root + """ + self.namespaces = NAMESPACES_V2 + if md is None: + self.md = None + self.xml = None + self.identifier = None + self.parentidentifier = None + self.language = None + self.dataseturi = None + self.languagecode = None + self.datestamp = None + self.charset = None + self.hierarchy = None + self.contact = [] + self.datetimestamp = None + self.stdname = None + self.stdver = None + self.locales = [] + self.referencesystem = None + self.identification = [] + self.contentinfo = [] + self.distribution = None + self.dataquality = None + self.acquisition = None + else: + self.md = md + if hasattr(md, 'getroot'): # standalone document + self.xml = etree.tostring(md.getroot()) + else: # part of a larger document + self.xml = etree.tostring(md) + + # Test mdb version + if md.find(util.nspath_eval('mdb:metadataIdentifier', NAMESPACES_V2)) is None and \ + md.find(util.nspath_eval('mdb:metadataIdentifier', NAMESPACES_V1)) is not None: + self.namespaces = NAMESPACES_V1 + + val = md.find(util.nspath_eval('mdb:metadataIdentifier/mcc:MD_Identifier/mcc:code/gco:CharacterString', self.namespaces)) + self.identifier = util.testXMLValue(val) + + val = md.find(util.nspath_eval('mdb:parentMetadata/cit:CI_Citation/cit:identifier/mcc:MD_Identifier/mcc:code/gco:CharacterString', self.namespaces)) + self.parentidentifier = util.testXMLValue(val) + + val = md.find(util.nspath_eval('lan:language/gco:CharacterString', self.namespaces)) + self.language = util.testXMLValue(val) + + val = md.find(util.nspath_eval('mdb:identificationInfo/mri:MD_DataIdentification/mri:citation/cit:CI_Citation/cit:identifier/mcc:MD_Identifier/mcc:code/gco:CharacterString', self.namespaces)) + self.dataseturi = util.testXMLValue(val) + + val = md.find(util.nspath_eval('mdb:defaultLocale/lan:PT_Locale/lan:language/lan:LanguageCode', self.namespaces)) + self.languagecode = util.testXMLAttribute(val, 'codeListValue') + + val = md.find(util.nspath_eval('mdb:dateInfo/cit:CI_Date/cit:date/gco:DateTime', self.namespaces)) + self.datestamp = util.testXMLValue(val) + + val = md.find( + util.nspath_eval('mdb:defaultLocale/lan:PT_Locale/lan:characterEncoding/lan:MD_CharacterSetCode', self.namespaces)) + self.charset = util.testXMLAttribute(val, 'codeListValue') + + val = md.find( + util.nspath_eval('mdb:metadataScope/mdb:MD_MetadataScope/mdb:resourceScope/mcc:MD_ScopeCode', self.namespaces)) + self.hierarchy = util.testXMLAttribute(val, 'codeListValue') + + self.contact = [] + for i in md.findall(util.nspath_eval('mdb:contact/cit:CI_Responsibility', self.namespaces)): + o = CI_Responsibility(self.namespaces, i) + self.contact.append(o) + + self.datetimestamp = self.datestamp + + val = md.find(util.nspath_eval('mdb:metadataStandard/cit:CI_Citation/cit:title/gco:CharacterString', self.namespaces)) + self.stdname = util.testXMLValue(val) + + val = md.find(util.nspath_eval('mdb:metadataStandard/cit:CI_Citation/cit:edition/gco:CharacterString', self.namespaces)) + self.stdver = util.testXMLValue(val) + + self.locales = [] + for i in md.findall(util.nspath_eval('mdb:defaultLocale/lan:PT_Locale', self.namespaces)): + self.locales.append(PT_Locale(self.namespaces, i)) + + val = md.find(util.nspath_eval('mdb:referenceSystemInfo/mrs:MD_ReferenceSystem', self.namespaces)) + if val is not None: + self.referencesystem = MD_ReferenceSystem(self.namespaces, val) + else: + self.referencesystem = None + + self.identification = [] + + for idinfo in md.findall(util.nspath_eval('mdb:identificationInfo', self.namespaces)): + if len(idinfo) > 0: + val = list(idinfo)[0] + tagval = util.xmltag_split(val.tag) + if tagval == 'MD_DataIdentification': + self.identification.append(MD_DataIdentification(self.namespaces, val, 'dataset')) + elif tagval == 'MD_ServiceIdentification': + self.identification.append(MD_DataIdentification(self.namespaces, val, 'service')) + elif tagval == 'SV_ServiceIdentification': + self.identification.append(SV_ServiceIdentification(self.namespaces, val)) + + self.contentinfo = [] + for contentinfo in md.findall( + util.nspath_eval('mdb:contentInfo/mrc:MD_FeatureCatalogueDescription', self.namespaces)): + self.contentinfo.append(MD_FeatureCatalogueDescription(self.namespaces, contentinfo)) + for contentinfo in md.findall( + util.nspath_eval('mdb:contentInfo/mrc:MD_ImageDescription', self.namespaces)): + self.contentinfo.append(MD_ImageDescription(self.namespaces, contentinfo)) + for contentinfo in md.findall( + util.nspath_eval('mdb:contentInfo/mrc:MD_FeatureCatalogue/mrc:featureCatalogue/gfc:FC_FeatureCatalogue', + self.namespaces)): + self.contentinfo.append(FC_FeatureCatalogue(self.namespaces, contentinfo)) + + val = md.find(util.nspath_eval('mdb:distributionInfo/mrd:MD_Distribution', self.namespaces)) + + if val is not None: + self.distribution = MD_Distribution(self.namespaces, val) + else: + self.distribution = None + + val = md.find(util.nspath_eval('mdb:dataQualityInfo/mdq:DQ_DataQuality', self.namespaces)) + if val is not None: + self.dataquality = DQ_DataQuality(self.namespaces, val) + else: + self.dataquality = None + + @staticmethod + def find_start(doc): + """ Tests for valid ISO 19115 Part 3 XML and returns the starting tag + + :param doc: lxml Element object + :returns: 'mdb:MD_Metadata' lxml Element object + """ + mtags = doc.xpath("//mdb:MD_Metadata", namespaces=NAMESPACES_V2) + if len(mtags) > 0: + return mtags[0] + mtags = doc.xpath("//mdb:MD_Metadata", namespaces=NAMESPACES_V1) + if len(mtags) > 0: + return mtags[0] + return None + + @staticmethod + def handles(outputschema): + """ Returns True iff the outputschema is handled by this class + + :param outputschema: outputschema parameter string + :returns: True iff the outputschema is handled by this class + """ + return outputschema == NAMESPACES_V1['mdb'] or \ + outputschema == NAMESPACES_V2['mdb'] + + @staticmethod + def find_ids(elemtree): + """ Finds identifer strings and outer 'mdb:MD_Metadata' Elements + + :param elemtree: lxml.ElementTree to search in + :returns: a list of tuples (id string, 'mdb:MD_Metadata' lxml.Element) + """ + for ns in [NAMESPACES_V2, NAMESPACES_V1]: + elems = elemtree.findall('.//' + util.nspath_eval('mdb:MD_Metadata', ns)) + if len(elems) > 0: + ret_list = [] + for i in elems: + val = i.find(util.nspath_eval('mdb:metadataIdentifier/mcc:MD_Identifier/mcc:code/gco:CharacterString', NAMESPACES_V2)) + ret_list.append((i, util.testXMLValue(val))) + return ret_list + return [] + + def get_all_contacts(self): + """ Get all contacts in identification part of document + + :returns: list of CI_Responsibility contacts + """ + contacts = [] + for ii in self.identification: + for iic in ii.contact: + contacts.append(iic) + + for ct in ['creator', 'publisher', 'contributor', 'funder']: + iict = getattr(ii, ct) + if iict: + contacts.append(iict) + + return list(filter(None, contacts)) + + def get_default_locale(self): + """ Get default lan:PT_Locale based on lan:language + + :returns: default PT_Locale instance or None if not found + """ + + for loc in self.locales: + if loc.languagecode == self.language: + return loc + return None + + +class PT_Locale(printable): + """ Process PT_Locale + """ + + def __init__(self, namespaces, md=None): + """ + Parses PT_Locale XML subtree + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param md: PT_Locale etree.Element + """ + self.namespaces = namespaces + if md is None: + self.id = None + self.languagecode = None + self.charset = None + else: + self.id = md.attrib.get('id') + self.languagecode = md.find( + util.nspath_eval('lan:language/lan:LanguageCode', self.namespaces)).attrib.get('codeListValue') + self.charset = md.find( + util.nspath_eval('lan:characterEncoding/lan:MD_CharacterSetCode', self.namespaces)).attrib.get( + 'codeListValue') + + + +class CI_Date(printable): + """ Process CI_Date + """ + def __init__(self, namespaces, md=None): + """ + Parses CI_Date XML subtree + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param md: CI_Date etree.Element + """ + self.namespaces = namespaces + if md is None: + self.date = None + self.type = None + else: + val = md.find(util.nspath_eval('cit:date/gco:Date', self.namespaces)) + if val is not None: + self.date = util.testXMLValue(val) + else: + val = md.find(util.nspath_eval('cit:date/gco:DateTime', self.namespaces)) + if val is not None: + self.date = util.testXMLValue(val) + else: + self.date = None + + val = md.find(util.nspath_eval('cit:dateType/cit:CI_DateTypeCode', self.namespaces)) + self.type = _testCodeListValue(val) + + +class CI_Responsibility(printable): + """ Process CI_Responsibility + """ + def __init__(self, namespaces, md=None): + """ + Parses CI_Responsibility XML subtree + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param md: CI_Responsibility etree.Element + """ + self.namespaces = namespaces + self.phone = None + self.fax = None + if md is None: + self.name = None + self.organization = None + self.position = None + self.address = None + self.city = None + self.region = None + self.postcode = None + self.country = None + self.email = None + self.onlineresource = None + self.role = None + else: + val = md.find(util.nspath_eval('cit:party/cit:CI_Organisation/cit:individual/cit:CI_Individual/cit:name/gco:CharacterString', self.namespaces)) + self.name = util.testXMLValue(val) + + val = md.find(util.nspath_eval('cit:party/cit:CI_Organisation/cit:name/gco:CharacterString', self.namespaces)) + self.organization = util.testXMLValue(val) + + val = md.find(util.nspath_eval('cit:party/cit:CI_Organisation/cit:individual/cit:CI_Individual/cit:positionName/gco:CharacterString', self.namespaces)) + self.position = util.testXMLValue(val) + + # Telephone + val_list = md.xpath('cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:phone/cit:CI_Telephone[cit:numberType/cit:CI_TelephoneTypeCode/@codeListValue="voice"]/cit:number/gco:CharacterString', namespaces=self.namespaces) + if len(val_list) > 0: + self.phone = util.testXMLValue(val_list[0]) + + # Facsimile (Telephone and fax are differentiated by telephone type codes) + val_list = md.xpath('cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:phone/cit:CI_Telephone[cit:numberType/cit:CI_TelephoneTypeCode/@codeListValue="facsimile"]/cit:number/gco:CharacterString', namespaces=self.namespaces) + if len(val_list) > 0: + self.fax = util.testXMLValue(val_list[0]) + + val = md.find(util.nspath_eval( + 'cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:address/cit:CI_Address/cit:deliveryPoint/gco:CharacterString', + self.namespaces)) + self.address = util.testXMLValue(val) + + val = md.find(util.nspath_eval( + 'cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:address/cit:CI_Address/cit:city/gco:CharacterString', self.namespaces)) + self.city = util.testXMLValue(val) + + val = md.find(util.nspath_eval( + 'cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:address/cit:CI_Address/cit:administrativeArea/gco:CharacterString', + self.namespaces)) + self.region = util.testXMLValue(val) + + val = md.find(util.nspath_eval( + 'cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:address/cit:CI_Address/cit:postalCode/gco:CharacterString', + self.namespaces)) + self.postcode = util.testXMLValue(val) + + val = md.find(util.nspath_eval( + 'cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:address/cit:CI_Address/cit:country/gco:CharacterString', + self.namespaces)) + self.country = util.testXMLValue(val) + + val = md.find(util.nspath_eval( + 'cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:address/cit:CI_Address/cit:electronicMailAddress/gco:CharacterString', + self.namespaces)) + self.email = util.testXMLValue(val) + + val = md.find(util.nspath_eval( + 'cit:party/cit:CI_Organisation/cit:contactInfo/cit:CI_Contact/cit:onlineResource/cit:CI_OnlineResource', self.namespaces)) + if val is not None: + self.onlineresource = CI_OnlineResource(self.namespaces, val) + else: + self.onlineresource = None + val = md.find(util.nspath_eval('cit:role/cit:CI_RoleCode', self.namespaces)) + self.role = _testCodeListValue(val) + + +class Keyword(printable): + """ Class for complex keywords, with labels and URLs + """ + def __init__(self, namespaces, kw=None): + """ + Parses keyword Element + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param kw: keyword 'gco:CharacterString' or 'gcx:Anchor' etree.Element + """ + self.namespaces = namespaces + if kw is None: + self.name = None + self.url = None + else: + self.name = util.testXMLValue(kw) + self.url = kw.attrib.get(util.nspath_eval('xlink:href', self.namespaces)) + + +class MD_Keywords(printable): + """ + Class for the metadata MD_Keywords element + """ + def __init__(self, namespaces, md=None): + """ + Parses keyword Element + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param md: keyword etree.Element + """ + self.namespaces = namespaces + self.thesaurus = None + self.keywords = [] + self.type = None + self.kwdtype_codeList = 'http://standards.iso.org/iso/19115/-3/resources/Codelist/gmxCodelists.xml#MD_KeywordTypeCode' + + if md is not None: + val = md.findall(util.nspath_eval('mri:keyword/gco:CharacterString', self.namespaces)) + if len(val) == 0: + val = md.findall(util.nspath_eval('mri:keyword/gcx:Anchor', self.namespaces)) + for word in val: + self.keywords.append(Keyword(self.namespaces, word)) + + val = md.find(util.nspath_eval('mri:type/mri:MD_KeywordTypeCode', self.namespaces)) + self.type = util.testXMLAttribute(val, 'codeListValue') + + cit = md.find(util.nspath_eval('mri:thesaurusName/cit:CI_Citation', self.namespaces)) + if cit is not None: + self.thesaurus = {} + + title = cit.find(util.nspath_eval('cit:title/gco:CharacterString', self.namespaces)) + self.thesaurus['title'] = util.testXMLValue(title) + self.thesaurus['url'] = None + + if self.thesaurus['title'] is None: # try gmx:Anchor + t = cit.find(util.nspath_eval('cit:title/gcx:Anchor', self.namespaces)) + if t is not None: + self.thesaurus['title'] = util.testXMLValue(t) + self.thesaurus['url'] = t.attrib.get(util.nspath_eval('xlink:href', self.namespaces)) + + date_ = cit.find(util.nspath_eval('cit:date/cit:CI_Date/cit:date/gco:Date', self.namespaces)) + self.thesaurus['date'] = util.testXMLValue(date_) + + datetype = cit.find( + util.nspath_eval('cit:date/cit:CI_Date/cit:dateType/cit:CI_DateTypeCode', self.namespaces)) + self.thesaurus['datetype'] = util.testXMLAttribute(datetype, 'codeListValue') + +class MD_DataIdentification(printable): + """ Process MD_DataIdentification + """ + def __init__(self, namespaces, md=None, identtype=None): + """ + Parses MD_DataIdentification XML subtree + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param md: MD_DataIdentification etree.Element + :param identtype: identitication type e.g. 'dataset' if MD_DataIdentification, + 'service' if MD_ServiceIdentification + """ + self.namespaces = namespaces + self.aggregationinfo = None + self.bbox = None + self.temporalextent_start = None + self.temporalextent_end = None + self.extent = None + if md is None: + self.identtype = None + self.title = None + self.alternatetitle = None + self.uricode = [] + self.uricodespace = [] + self.date = [] + self.datetype = [] + self.uselimitation = [] + self.uselimitation_url = [] + self.accessconstraints = [] + self.classification = [] # Left empty - no legal classification equivalent + self.otherconstraints = [] + self.securityconstraints = [] + self.useconstraints = [] + self.denominators = [] + self.distance = [] + self.uom = [] + self.resourcelanguage = [] + self.resourcelanguagecode = [] + self.creator = [] + self.publisher = [] + self.funder = [] + self.contributor = [] + self.edition = None + self.abstract = None + self.abstract_url = None + self.purpose = None + self.status = None + self.graphicoverview = [] + self.contact = [] + self.keywords = [] + self.topiccategory = [] + self.supplementalinformation = None + self.spatialrepresentationtype = [] + else: + self.identtype = identtype + val = md.find(util.nspath_eval( + 'mri:citation/cit:CI_Citation/cit:title/gco:CharacterString', self.namespaces)) + self.title = util.testXMLValue(val) + + val = md.find(util.nspath_eval( + 'mri:citation/cit:CI_Citation/cit:alternateTitle/gco:CharacterString', self.namespaces)) + self.alternatetitle = util.testXMLValue(val) + + self.uricode = [] + for end_tag in ['gco:CharacterString', 'gcx:Anchor']: + _values = md.findall(util.nspath_eval( + f"mri:citation/cit:CI_Citation/cit:identifier/mcc:MD_Identifier/mcc:code/{end_tag}", + self.namespaces)) + for i in _values: + val = util.testXMLValue(i) + if val is not None: + self.uricode.append(val) + + self.uricodespace = [] + for i in md.findall(util.nspath_eval( + 'mri:citation/cit:CI_Citation/cit:identifier/mcc:MD_Identifier/mcc:codeSpace/gco:CharacterString', + self.namespaces)): + val = util.testXMLValue(i) + if val is not None: + self.uricodespace.append(val) + + self.date = [] + self.datetype = [] + + for i in md.findall(util.nspath_eval('mri:citation/cit:CI_Citation/cit:date/cit:CI_Date', self.namespaces)): + self.date.append(CI_Date(self.namespaces, i)) + + self.uselimitation = [] + self.uselimitation_url = [] + uselimit_values = md.findall(util.nspath_eval( + 'mri:resourceConstraints/mco:MD_LegalConstraints/mco:useLimitation/gco:CharacterString', self.namespaces)) + for i in uselimit_values: + val = util.testXMLValue(i) + if val is not None: + self.uselimitation.append(val) + + self.accessconstraints = [] + for i in md.findall(util.nspath_eval( + 'mri:resourceConstraints/mco:MD_LegalConstraints/mco:accessConstraints/mco:MD_RestrictionCode', + self.namespaces)): + val = _testCodeListValue(i) + if val is not None: + self.accessconstraints.append(val) + + self.classification = [] # Left empty - no legal classification equivalent + + self.otherconstraints = [] + for end_tag in ['gco:CharacterString', 'gcx:Anchor']: + for i in md.findall(util.nspath_eval( + f"mri:resourceConstraints/mco:MD_LegalConstraints/mco:otherConstraints/{end_tag}", + self.namespaces)): + val = util.testXMLValue(i) + if val is not None: + self.otherconstraints.append(val) + + self.securityconstraints = [] + for i in md.findall(util.nspath_eval( + 'mri:resourceConstraints/mco:MD_SecurityConstraints/mco:classification/mco:MD_ClassificationCode', + self.namespaces)): + val = _testCodeListValue(i) + if val is not None: + self.securityconstraints.append(val) + + self.useconstraints = [] + for i in md.findall(util.nspath_eval( + 'mri:resourceConstraints/mco:MD_LegalConstraints/mco:useConstraints/mco:MD_RestrictionCode', + self.namespaces)): + val = _testCodeListValue(i) + if val is not None: + self.useconstraints.append(val) + + self.denominators = [] + for i in md.findall(util.nspath_eval( + 'mri:spatialResolution/mri:MD_Resolution/mri:equivalentScale/mri:MD_RepresentativeFraction/mri:denominator/gco:Integer', + self.namespaces)): + val = util.testXMLValue(i) + if val is not None: + self.denominators.append(val) + + self.distance = [] + self.uom = [] + for i in md.findall(util.nspath_eval( + 'mri:spatialResolution/mri:MD_Resolution/mri:distance/gco:Distance', self.namespaces)): + val = util.testXMLValue(i) + if val is not None: + self.distance.append(val) + self.uom.append(i.get("uom")) + + self.resourcelanguagecode = [] + for i in md.findall(util.nspath_eval('mri:defaultLocale/lan:PT_Locale/lan:language/lan:LanguageCode', self.namespaces)): + val = _testCodeListValue(i) + if val is not None: + self.resourcelanguagecode.append(val) + + self.resourcelanguage = [] + for i in md.findall(util.nspath_eval('mri:defaultLocale/lan:PT_Locale/lan:language/gco:CharacterString', self.namespaces)): + val = util.testXMLValue(i) + if val is not None: + self.resourcelanguage.append(val) + + self.creator = [] + self.publisher = [] + self.contributor = [] + self.funder = [] + for val in md.findall(util.nspath_eval('mri:pointOfContact/cit:CI_Responsibility', self.namespaces)): + role = val.find(util.nspath_eval('cit:role/cit:CI_RoleCode', self.namespaces)) + if role is not None: + clv = _testCodeListValue(role) + rp = CI_Responsibility(self.namespaces, val) + if clv == 'originator': + self.creator.append(rp) + elif clv == 'publisher': + self.publisher.append(rp) + elif clv == 'author': + self.contributor.append(rp) + elif clv == 'funder': + self.funder.append(rp) + + val = md.find(util.nspath_eval('cit:CI_Citation/cit:edition/gco:CharacterString', self.namespaces)) + self.edition = util.testXMLValue(val) + + val = md.find(util.nspath_eval('mri:abstract/gco:CharacterString', self.namespaces)) + self.abstract = util.testXMLValue(val) + + val = md.find(util.nspath_eval('mri:abstract/gcx:Anchor', self.namespaces)) + + self.abstract_url = None + if val is not None: + self.abstract = util.testXMLValue(val) + self.abstract_url = val.attrib.get(util.nspath_eval('xlink:href', self.namespaces)) + + val = md.find(util.nspath_eval('mri:purpose/gco:CharacterString', self.namespaces)) + self.purpose = util.testXMLValue(val) + + self.status = _testCodeListValue(md.find(util.nspath_eval('mri:status/mri:MD_ProgressCode', self.namespaces))) + + self.graphicoverview = [] + for val in md.findall(util.nspath_eval( + 'mri:graphicOverview/mcc:MD_BrowseGraphic/mcc:fileName/gco:CharacterString', self.namespaces)): + if val is not None: + val2 = util.testXMLValue(val) + if val2 is not None: + self.graphicoverview.append(val2) + + self.contact = [] + for i in md.findall(util.nspath_eval('mri:pointOfContact/cit:CI_Responsibility', self.namespaces)): + o = CI_Responsibility(self.namespaces, i) + self.contact.append(o) + + self.spatialrepresentationtype = [] + for val in md.findall(util.nspath_eval( + 'mri:spatialRepresentationType/mcc:MD_SpatialRepresentationTypeCode', self.namespaces)): + val = util.testXMLAttribute(val, 'codeListValue') + if val: + self.spatialrepresentationtype.append(val) + + self.keywords = [] + for mdkw in md.findall(util.nspath_eval('mri:descriptiveKeywords/mri:MD_Keywords', self.namespaces)): + self.keywords.append(MD_Keywords(self.namespaces, mdkw)) + + self.topiccategory = [] + for i in md.findall(util.nspath_eval('mri:topicCategory/mri:MD_TopicCategoryCode', self.namespaces)): + val = util.testXMLValue(i) + if val is not None: + self.topiccategory.append(val) + + val = md.find(util.nspath_eval('mri:supplementalInformation/gco:CharacterString', self.namespaces)) + self.supplementalinformation = util.testXMLValue(val) + + # There may be multiple geographicElement, create an extent + # from the one containing either an EX_GeographicBoundingBox or EX_BoundingPolygon. + # The schema also specifies an EX_GeographicDescription. This is not implemented yet. + val = None + val2 = None + val3 = None + extents = md.findall(util.nspath_eval('mri:extent', self.namespaces)) + for extent in extents: + # Parse bounding box and vertical extents + if val is None: + for e in extent.findall(util.nspath_eval('gex:EX_Extent/gex:geographicElement', self.namespaces)): + if e.find(util.nspath_eval('gex:EX_GeographicBoundingBox', self.namespaces)) is not None or \ + e.find(util.nspath_eval('gex:EX_BoundingPolygon', self.namespaces)) is not None: + val = e + break + vert_elem = extent.find(util.nspath_eval('gex:EX_Extent/gex:verticalElement', self.namespaces)) + self.extent = EX_Extent(self.namespaces, val, vert_elem) + self.bbox = self.extent.boundingBox # for backwards compatibility + + # Parse temporal extent begin + if val2 is None: + val2 = extent.find(util.nspath_eval( + 'gex:EX_Extent/gex:temporalElement/gex:EX_TemporalExtent/gex:extent/gml:TimePeriod/gml:beginPosition', + self.namespaces)) + self.temporalextent_start = util.testXMLValue(val2) + + # Parse temporal extent end + if val3 is None: + val3 = extent.find(util.nspath_eval( + 'gex:EX_Extent/gex:temporalElement/gex:EX_TemporalExtent/gex:extent/gml:TimePeriod/gml:endPosition', + self.namespaces)) + self.temporalextent_end = util.testXMLValue(val3) + + +class MD_Distributor(printable): + """ Process MD_Distributor + """ + def __init__(self, namespaces, md=None): + """ + Parses MD_Distributor XML subtree + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param md: MD_Distributor etree.Element + """ + self.namespaces = namespaces + if md is None: + self.contact = None + self.online = [] + else: + self.contact = None + val = md.find(util.nspath_eval( + 'mrd:MD_Distributor/mrd:distributorContact/cit:CI_Responsibility', self.namespaces)) + if val is not None: + self.contact = CI_Responsibility(self.namespaces, val) + + self.online = [] + + for ol in md.findall(util.nspath_eval( + 'mrd:MD_Distributor/mrd:distributorTransferOptions/mrd:MD_DigitalTransferOptions/mrd:onLine/cit:CI_OnlineResource', + self.namespaces)): + self.online.append(CI_OnlineResource(self.namespaces, ol)) + + +class MD_Distribution(printable): + """ Process MD_Distribution + """ + def __init__(self, namespaces, md=None): + """ + Parses MD_Distribution XML subtree + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param md: MD_Distribution etree.Element + """ + self.namespaces = namespaces + if md is None: + self.format = None + self.version = None + self.distributor = [] + self.online = [] + else: + val = md.find(util.nspath_eval( + 'mrd:distributionFormat/mrd:MD_Format/mrd:formatSpecificationCitation/cit:CI_Citation/cit:title/gco:CharacterString', self.namespaces)) + self.format = util.testXMLValue(val) + + val = md.find(util.nspath_eval( + 'mrd:distributionFormat/mrd:MD_Format/mrd:formatSpecificationCitation/cit:CI_Citation/cit:edition/gco:CharacterString', self.namespaces)) + self.version = util.testXMLValue(val) + + self.distributor = [] + for dist in md.findall(util.nspath_eval('mrd:distributor', self.namespaces)): + self.distributor.append(MD_Distributor(self.namespaces, dist)) + + self.online = [] + + for ol in md.findall(util.nspath_eval( + 'mrd:transferOptions/mrd:MD_DigitalTransferOptions/mrd:onLine/cit:CI_OnlineResource', + self.namespaces)): + self.online.append(CI_OnlineResource(self.namespaces, ol)) + + +class DQ_DataQuality(printable): + """ Process DQ_DataQuality + """ + def __init__(self, namespaces, md=None): + """ + Parse a portion of DQ_DataQuality XML subtree only taking the first value found + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param md: DQ_DataQuality etree.Element + """ + self.namespaces = namespaces + self.conformancetitle = [] + self.conformancedate = [] + self.conformancedatetype = [] + self.conformancedegree = [] + self.lineage = None + self.lineage_url = None + self.specificationtitle = None + self.specificationdate = [] + if md is not None: + + for conftitle in md.xpath( + 'mdq:report/mdq:DQ_DomainConsistency/mdq:result/mdq:DQ_ConformanceResult/mdq:specification/cit:CI_Citation/cit:title/gco:CharacterString', + namespaces=self.namespaces): + self.conformancetitle.append(util.testXMLValue(conftitle)) + + for confdate in md.xpath( + 'mdq:report/mdq:DQ_DomainConsistency/mdq:result/mdq:DQ_ConformanceResult/mdq:specification/cit:CI_Citation/cit:date/cit:CI_Date/cit:date/gco:DateTime', + namespaces=self.namespaces): + self.conformancedate.append(util.testXMLValue(confdate)) + + for confdatetype in md.xpath( + 'mdq:report/mdq:DQ_DomainConsistency/mdq:result/mdq:DQ_ConformanceResult/mdq:specification/cit:CI_Citation/cit:date/cit:CI_Date/cit:dateType/cit:CI_DateTypeCode', + namespaces=self.namespaces): + self.conformancedatetype.append(util.testXMLValue(confdatetype)) + + for confdegree in md.xpath( + 'mdq:report/mdq:DQ_DomainConsistency/mdq:result/mdq:DQ_ConformanceResult/mdq:pass/gco:Boolean', + namespaces=self.namespaces): + self.conformancedegree.append(util.testXMLValue(confdegree)) + + lins = md.xpath( + 'mdq:lineage/mrl:LI_Lineage/mrl:statement/*[self::gco:CharacterString or self::gcx:Anchor]', + namespaces=self.namespaces) + if len(lins) > 0: + self.lineage = util.testXMLValue(lins[0]) + self.lineage_url = lins[0].attrib.get(util.nspath_eval('xlink:href', self.namespaces)) + + val = md.find(util.nspath_eval( + 'mdq:report/mdq:DQ_DomainConsistency/mdq:result/mdq:DQ_ConformanceResult/mdq:specification/cit:CI_Citation/cit:title/gco:CharacterString', + self.namespaces)) + self.specificationtitle = util.testXMLValue(val) + + self.specificationdate = [] + for i in md.findall(util.nspath_eval( + 'mdq:report/mdq:DQ_DomainConsistency/mdq:result/mdq:DQ_ConformanceResult/mdq:specification/cit:CI_Citation/cit:date/cit:CI_Date/cit:date/gco:DateTime', + self.namespaces)): + val = util.testXMLValue(i) + if val is not None: + self.specificationdate.append(val) + + +class SV_ServiceIdentification(MD_DataIdentification, printable): + """ Process SV_ServiceIdentification + """ + def __init__(self, namespaces, md=None): + """ + Parses SV_ServiceIdentification XML subtree + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param md: SV_ServiceIdentification etree.Element + """ + super().__init__(namespaces, md, 'service') + self.namespaces = namespaces + + if md is None: + self.type = None + self.version = None + self.fees = None + self.couplingtype = None + self.operations = [] + self.operateson = [] + else: + val = md.xpath('srv:serviceType/*[self::gco:LocalName or self::gco:ScopedName]', namespaces=self.namespaces) + if len(val) > 0: + self.type = util.testXMLValue(val[0]) + + val = md.find(util.nspath_eval('srv:serviceTypeVersion/gco:CharacterString', self.namespaces)) + self.version = util.testXMLValue(val) + + val = md.find(util.nspath_eval( + 'srv:accessProperties/mrd:MD_StandardOrderProcess/mrd:fees/gco:CharacterString', self.namespaces)) + self.fees = util.testXMLValue(val) + + self.couplingtype = _testCodeListValue(md.find(util.nspath_eval( + 'srv:couplingType/srv:SV_CouplingType', self.namespaces))) + + self.operations = [] + + for i in md.findall(util.nspath_eval('srv:containsOperations', self.namespaces)): + tmp = {} + val = i.find(util.nspath_eval( + 'srv:SV_OperationMetadata/srv:operationName/gco:CharacterString', self.namespaces)) + tmp['name'] = util.testXMLValue(val) + tmp['dcplist'] = [] + for d in i.findall(util.nspath_eval('srv:SV_OperationMetadata/srv:distributedComputingPlatform', self.namespaces)): + tmp2 = _testCodeListValue(d.find(util.nspath_eval('srv:DCPList', self.namespaces))) + tmp['dcplist'].append(tmp2) + + tmp['connectpoint'] = [] + + for d in i.findall(util.nspath_eval('srv:SV_OperationMetadata/srv:connectPoint', self.namespaces)): + tmp3 = d.find(util.nspath_eval('cit:CI_OnlineResource', self.namespaces)) + tmp['connectpoint'].append(CI_OnlineResource(self.namespaces, tmp3)) + self.operations.append(tmp) + + self.operateson = [] + + for i in md.findall(util.nspath_eval('srv:operatesOn', self.namespaces)): + tmp = {} + tmp['uuidref'] = i.attrib.get('uuidref') + tmp['href'] = i.attrib.get(util.nspath_eval('xlink:href', self.namespaces)) + tmp['title'] = i.attrib.get(util.nspath_eval('xlink:title', self.namespaces)) + self.operateson.append(tmp) + + +class CI_OnlineResource(printable): + """ Process CI_OnlineResource + """ + def __init__(self, namespaces, md=None): + """ + Parses CI_OnlineResource XML subtree + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param md: CI_OnlineResource etree.Element + """ + self.namespaces = namespaces + if md is None: + self.url = None + self.protocol = None + self.name = None + self.description = None + self.function = None + else: + val = md.find(util.nspath_eval('cit:linkage/gco:CharacterString', self.namespaces)) + self.url = util.testXMLValue(val) + + val = md.find(util.nspath_eval('cit:protocol/gco:CharacterString', self.namespaces)) + self.protocol = util.testXMLValue(val) + + val = md.find(util.nspath_eval('cit:name/gco:CharacterString', self.namespaces)) + self.name = util.testXMLValue(val) + + val = md.find(util.nspath_eval('cit:description/gco:CharacterString', self.namespaces)) + self.description = util.testXMLValue(val) + + self.function = _testCodeListValue(md.find(util.nspath_eval( + 'cit:function/cit:CI_OnLineFunctionCode', self.namespaces))) + + +class EX_GeographicBoundingBox(printable): + """ Process gex:EX_GeographicBoundingBox + """ + def __init__(self, namespaces, md=None): + """ + Parses EX_GeographicBoundingBox XML subtree + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param md: EX_GeographicBoundingBox etree.Element + """ + self.namespaces = namespaces + if md is None: + self.minx = None + self.maxx = None + self.miny = None + self.maxy = None + else: + val = md.find(util.nspath_eval('gex:westBoundLongitude/gco:Decimal', self.namespaces)) + self.minx = util.testXMLValue(val) + val = md.find(util.nspath_eval('gex:eastBoundLongitude/gco:Decimal', self.namespaces)) + self.maxx = util.testXMLValue(val) + val = md.find(util.nspath_eval('gex:southBoundLatitude/gco:Decimal', self.namespaces)) + self.miny = util.testXMLValue(val) + val = md.find(util.nspath_eval('gex:northBoundLatitude/gco:Decimal', self.namespaces)) + self.maxy = util.testXMLValue(val) + + +class EX_Polygon(printable): + """ Process gml32:Polygon + """ + def __init__(self, namespaces, md=None): + """ + Parses EX_Polygon XML subtree + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param md: EX_Polygon etree.Element + """ + self.namespaces = namespaces + if md is None: + self.exterior_ring = None + self.interior_rings = [] + else: + linear_ring = md.find(util.nspath_eval('gml32:Polygon/gml32:exterior/gml32:LinearRing', self.namespaces)) + if linear_ring is not None: + self.exterior_ring = self._coordinates_for_ring(linear_ring) + + interior_ring_elements = md.findall(util.nspath_eval('gml32:Polygon/gml32:interior', self.namespaces)) + self.interior_rings = [] + for iring_element in interior_ring_elements: + linear_ring = iring_element.find(util.nspath_eval('gml32:LinearRing', self.namespaces)) + self.interior_rings.append(self._coordinates_for_ring(linear_ring)) + + def _coordinates_for_ring(self, linear_ring): + """ Get coordinates for gml coordinate ring + + :param linear_ring: etree.Element position list + :returns: coordinate list of float tuples + """ + coordinates = [] + positions = linear_ring.findall(util.nspath_eval('gml32:pos', self.namespaces)) + for pos in positions: + tokens = pos.text.split() + coords = tuple([float(t) for t in tokens]) + coordinates.append(coords) + return coordinates + + +class EX_BoundingPolygon(printable): + """ Process EX_BoundingPolygon + """ + def __init__(self, namespaces, md=None): + """ + Parses EX_BoundingPolygon XML subtree + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param md: EX_BoundingPolygon etree.Element + """ + self.namespaces = namespaces + if md is None: + self.is_extent = None + self.polygons = [] + else: + val = md.find(util.nspath_eval('gex:extentTypeCode', self.namespaces)) + self.is_extent = util.testXMLValue(val) + + md_polygons = md.findall(util.nspath_eval('gex:polygon', self.namespaces)) + + self.polygons = [] + for val in md_polygons: + self.polygons.append(EX_Polygon(self.namespaces, val)) + + +class EX_Extent(printable): + """ Process EX_Extent + """ + def __init__(self, namespaces, md=None, vert_elem=None): + """ + Parses EX_Extent XML subtree + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param md: EX_Extent etree.Element + :param vert_elem: vertical extent 'gex:verticalElement' etree.Element + """ + self.namespaces = namespaces + self.boundingBox = None + self.boundingPolygon = None + self.description_code = None + self.vertExtMin = None + self.vertExtMax = None + if md is not None: + # Parse bounding box + bboxElement = md.find(util.nspath_eval('gex:EX_GeographicBoundingBox', self.namespaces)) + if bboxElement is not None: + self.boundingBox = EX_GeographicBoundingBox(self.namespaces, bboxElement) + + polygonElement = md.find(util.nspath_eval('gex:EX_BoundingPolygon', self.namespaces)) + if polygonElement is not None: + self.boundingPolygon = EX_BoundingPolygon(self.namespaces, polygonElement) + + code = md.find(util.nspath_eval( + 'gex:EX_GeographicDescription/gex:geographicIdentifier/mcc:MD_Identifier/mcc:code/gco:CharacterString', + self.namespaces)) + self.description_code = util.testXMLValue(code) + + # Parse vertical extent + if vert_elem is not None: + # Get vertical extent max + vertext_max = vert_elem.find(util.nspath_eval( + 'gex:EX_VerticalExtent/gex:maximumValue/gco:Real', + self.namespaces)) + self.vertExtMax = util.testXMLValue(vertext_max) + + # Get vertical extent min + vertext_min = vert_elem.find(util.nspath_eval( + 'gex:EX_VerticalExtent/gex:minimumValue/gco:Real', + self.namespaces)) + self.vertExtMin = util.testXMLValue(vertext_min) + + +class MD_ReferenceSystem(printable): + """ Process MD_ReferenceSystem + """ + def __init__(self, namespaces, md=None): + """ + Parses MD_ReferenceSystem XML subtree + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param md: MD_ReferenceSystem etree.Element + """ + self.namespaces = namespaces + if md is None: + self.code = None + self.codeSpace = None + self.version = None + else: + val = md.find(util.nspath_eval( + 'mrs:referenceSystemIdentifier/mcc:MD_Identifier/mcc:code/gco:CharacterString', self.namespaces)) + if val is not None: + self.code = util.testXMLValue(val) + else: + val = md.find(util.nspath_eval( + 'mrs:referenceSystemIdentifier/mcc:MD_Identifier/mcc:code/gcx:Anchor', self.namespaces)) + if val is not None: + self.code = util.testXMLValue(val) + else: + self.code = None + + val = md.find(util.nspath_eval( + 'mrs:referenceSystemIdentifier/mcc:MD_Identifier/mcc:codeSpace/gco:CharacterString', self.namespaces)) + if val is not None: + self.codeSpace = util.testXMLValue(val) + else: + self.codeSpace = None + + val = md.find(util.nspath_eval( + 'mrs:referenceSystemIdentifier/mcc:MD_Identifier/mcc:version/gco:CharacterString', self.namespaces)) + if val is not None: + self.version = util.testXMLValue(val) + else: + self.version = None + + +def _testCodeListValue(elpath): + """ Get codeListValue attribute, else get text content + + :param elpath: Element path + :returns: 'codeListValue' attribute of Element or text value or None if elpath is None + """ + if elpath is not None: # try to get @codeListValue + val = util.testXMLValue(elpath.attrib.get('codeListValue'), True) + if val is not None: + return val + # see if there is element text + return util.testXMLValue(elpath) + + return None + + +class MD_FeatureCatalogueDescription(printable): + """Process mrc:MD_FeatureCatalogueDescription + """ + def __init__(self, namespaces, fcd=None): + """ + Parses MD_FeatureCatalogueDescription XML subtree + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param fcd: MD_FeatureCatalogueDescription etree.Element + """ + self.namespaces = namespaces + self.featuretypenames = [] + self.featurecatalogues = [] + if fcd is None: + self.xml = None + self.compliancecode = None + self.language = [] + self.includedwithdataset = None + self.featuretypenames = [] + self.featurecatalogues = [] + + else: + if hasattr(fcd, 'getroot'): # standalone document + self.xml = etree.tostring(fcd.getroot()) + else: # part of a larger document + self.xml = etree.tostring(fcd) + + self.compliancecode = None + comp = fcd.find(util.nspath_eval('mrc:complianceCode/gco:Boolean', self.namespaces)) + val = util.testXMLValue(comp) + if val is not None: + self.compliancecode = util.getTypedValue('boolean', val) + + self.language = [] + for i in fcd.findall(util.nspath_eval('mrc:locale/lan:PT_Locale/lan:language/lan:LanguageCode', self.namespaces)): + val = _testCodeListValue(i) + if val is not None: + self.language.append(val) + + self.includedwithdataset = None + comp = fcd.find(util.nspath_eval('mrc:includedWithDataset/gco:Boolean', self.namespaces)) + val = util.testXMLValue(comp) + if val is not None: + self.includedwithdataset = util.getTypedValue('boolean', val) + + self.featuretypenames = [] + for name in fcd.xpath('mrc:featureTypes/mrc:MD_FeatureTypeInfo/mrc:featureTypeName/*[self::gco:LocalName or self::gco:ScopedName]', + namespaces=self.namespaces): + val = util.testXMLValue(name) + if ValueError is not None: + self.featuretypenames.append(val) + + # Gather feature catalogue titles + self.featurecatalogues = [] + for cit in fcd.findall(util.nspath_eval( + 'mrc:featureCatalogueCitation/cit:CI_Citation/cit:title/gco:CharacterString', self.namespaces)): + val = util.testXMLValue(cit) + if val is not None: + self.featurecatalogues.append(val) + + +class FC_FeatureCatalogue(object): + """Process gfc:FC_FeatureCatalogue""" + def __init__(self, fc=None): + if fc is None: + self.xml = None + self.identifier = None + self.name = None + self.versiondate = None + self.producer = None + self.featuretypes = [] + else: + if hasattr(fc, 'getroot'): # standalone document + self.xml = etree.tostring(fc.getroot()) + else: # part of a larger document + self.xml = etree.tostring(fc) + + val = fc.attrib['uuid'] + self.identifier = util.testXMLValue(val, attrib=True) + + val = fc.find(util.nspath_eval('cat:name/gco:CharacterString', self.namespaces)) + self.name = util.testXMLValue(val) + + val = fc.find(util.nspath_eval('cat:versionDate/gco:Date', self.namespaces)) + self.versiondate = util.testXMLValue(val) + + if not self.versiondate: + val = fc.find(util.nspath_eval('cat:versionDate/gco:DateTime', self.namespaces)) + self.versiondate = util.testXMLValue(val) + + self.producer = None + prod = fc.find(util.nspath_eval('gfc:producer/cit:CI_Responsiblility', self.namespaces)) + if prod is not None: + self.producer = CI_Responsibility(prod) + + self.featuretypes = [] + for i in fc.findall(util.nspath_eval('gfc:featureType/gfc:FC_FeatureType', self.namespaces)): + self.featuretypes.append(FC_FeatureType(i)) + +class FC_FeatureType(object): + """Process gfc:FC_FeatureType""" + def __init__(self, ft=None): + if ft is None: + self.xml = None + self.identifier = None + self.typename = None + self.definition = None + self.isabstract = None + self.aliases = [] + self.attributes = [] + else: + if hasattr(ft, 'getroot'): # standalone document + self.xml = etree.tostring(ft.getroot()) + else: # part of a larger document + self.xml = etree.tostring(ft) + + val = ft.attrib['uuid'] + self.identifier = util.testXMLValue(val, attrib=True) + + val = ft.find(util.nspath_eval('gfc:typeName/gco:LocalName', self.namespaces)) + self.typename = util.testXMLValue(val) + + val = ft.find(util.nspath_eval('gfc:definition/gco:CharacterString', self.namespaces)) + self.definition = util.testXMLValue(val) + + self.isabstract = None + val = ft.find(util.nspath_eval('gfc:isAbstract/gco:Boolean', self.namespaces)) + val = util.testXMLValue(val) + if val is not None: + self.isabstract = util.getTypedValue('boolean', val) + + self.aliases = [] + for i in ft.findall(util.nspath_eval('gfc:aliases/gco:LocalName', self.namespaces)): + self.aliases.append(util.testXMLValue(i)) + + self.attributes = [] + for i in ft.findall(util.nspath_eval('gfc:carrierOfCharacteristics/gfc:FC_FeatureAttribute', namespaces)): + self.attributes.append(FC_FeatureAttribute(i)) + +class FC_FeatureAttribute(object): + """Process gfc:FC_FeatureAttribute""" + def __init__(self, fa=None): + if fa is None: + self.xml = None + self.membername = None + self.definition = None + self.code = None + self.valuetype = None + self.listedvalues = [] + else: + if hasattr(fa, 'getroot'): # standalone document + self.xml = etree.tostring(fa.getroot()) + else: # part of a larger document + self.xml = etree.tostring(fa) + + val = fa.find(util.nspath_eval('gfc:memberName/gco:ScopedName', self.namespaces)) + self.membername = util.testXMLValue(val) + + val = fa.find(util.nspath_eval('gfc:definition/gco:CharacterString', self.namespaces)) + self.definition = util.testXMLValue(val) + + val = fa.find(util.nspath_eval('gfc:code/gco:CharacterString', self.namespaces)) + self.code = util.testXMLValue(val) + + val = fa.find(util.nspath_eval('gfc:valueType/gco:TypeName/gco:aName/gco:CharacterString', self.namespaces)) + self.valuetype = util.testXMLValue(val) + + self.listedvalues = [] + for i in fa.findall(util.nspath_eval('gfc:listedValue/gfc:FC_ListedValue', self.namespaces)): + self.listedvalues.append(FC_ListedValue(i)) + +class FC_ListedValue(object): + """Process gfc:FC_ListedValue""" + def __init__(self, lv=None): + if lv is None: + self.xml = None + self.label = None + self.code = None + self.definition = None + else: + if hasattr(lv, 'getroot'): # standalone document + self.xml = etree.tostring(lv.getroot()) + else: # part of a larger document + self.xml = etree.tostring(lv) + + val = lv.find(util.nspath_eval('gfc:label/gco:CharacterString', self.namespaces)) + self.label = util.testXMLValue(val) + + val = lv.find(util.nspath_eval('gfc:code/gco:CharacterString', self.namespaces)) + self.code = util.testXMLValue(val) + + val = lv.find(util.nspath_eval('gfc:definition/gco:CharacterString', self.namespaces)) + self.definition = util.testXMLValue(val) + +class MD_ImageDescription(printable): + """Process mrc:MD_ImageDescription + """ + def __init__(self, namespaces, img_desc=None): + """ + Parses MD_ImageDescription XML subtree + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param img_desc: MD_ImageDescription etree.Element + """ + self.namespaces = namespaces + self.type = 'image' + self.bands = [] + + if img_desc is None: + self.attributedescription = None + self.cloudcover = None + self.processinglevel = None + else: + attdesc = img_desc.find(util.nspath_eval('mrc:attributeDescription/gco:RecordType', self.namespaces)) + self.attributedescription = util.testXMLValue(attdesc) + + ctype = img_desc.find(util.nspath_eval('mrc:attributeGroup/mrc:MD_AttributeGroup/mrc:contentType/mrc:MD_CoverageContentTypeCode', self.namespaces)) + self.type = util.testXMLAttribute(ctype, 'codeListValue') + + cloudcov = img_desc.find(util.nspath_eval('mrc:cloudCoverPercentage/gco:Real', self.namespaces)) + self.cloudcover = util.testXMLValue(cloudcov) + + proclvl = img_desc.find(util.nspath_eval( + 'mrc:processingLevelCode/mcc:MD_Identifier/mcc:code/gco:CharacterString', self.namespaces)) + self.processinglevel = util.testXMLValue(proclvl) + + for i in img_desc.findall(util.nspath_eval('mrc:attributeGroup/mrc:MD_AttributeGroup/mrc:attribute/mrc:MD_Band', self.namespaces)): + self.bands.append(MD_Band(self.namespaces, i)) + + +class MD_Band(printable): + """Process mrc:MD_Band + """ + def __init__(self, namespaces, band): + """ + Parses MD_Band XML subtree + + :param namespaces: dict of XML namespaces, key is namespace, val is path + :param band: MD_Band etree.Element + """ + self.namespaces = namespaces + if band is None: + self.id = None + self.units = None + self.min = None + self.max = None + else: + seq_id = band.find(util.nspath_eval('mrc:sequenceIdentifier/gco:MemberName/gco:aName/gco:CharacterString', self.namespaces)) + self.id = util.testXMLValue(seq_id) + + units = band.find(util.nspath_eval('mrc:units/gml:UnitDefinition/gml:identifier', self.namespaces)) + self.units = util.testXMLValue(units) + + bmin = band.find(util.nspath_eval('mrc:minValue/gco:Real', self.namespaces)) + self.min = util.testXMLValue(bmin) + + bmax = band.find(util.nspath_eval('mrc:maxValue/gco:Real', self.namespaces)) + self.max = util.testXMLValue(bmax) diff --git a/owslib/map/wms111.py b/owslib/map/wms111.py index 6ccc7a106..24a9b2d3e 100644 --- a/owslib/map/wms111.py +++ b/owslib/map/wms111.py @@ -25,6 +25,7 @@ bind_url, nspath_eval, Authentication) from owslib.fgdc import Metadata from owslib.iso import MD_Metadata +from owslib.iso3 import MD_Metadata as MD_Metadata3 # ISO 19115 Part 3 XML from owslib.map.common import WMSCapabilitiesReader, AbstractContentMetadata from owslib.namespaces import Namespaces @@ -617,6 +618,13 @@ def parse_remote_metadata(self, timeout=30): if mdelem is not None: metadataUrl['metadata'] = MD_Metadata(mdelem) continue + else: # ISO 19115 Part 3 XML + mdelem = MD_Metadata3.find_start(doc) + if mdelem is not None: + metadataUrl["metadata"] = MD_Metadata3(mdelem) + else: + metadataUrl["metadata"] = None + continue except Exception: metadataUrl['metadata'] = None diff --git a/owslib/map/wms130.py b/owslib/map/wms130.py index d085c3b39..130ecd7a0 100644 --- a/owslib/map/wms130.py +++ b/owslib/map/wms130.py @@ -23,6 +23,7 @@ nspath_eval, bind_url, Authentication) from owslib.fgdc import Metadata from owslib.iso import MD_Metadata +from owslib.iso3 import MD_Metadata as MD_Metadata3 # ISO 19115 Part 3 XML from owslib.crs import Crs from owslib.namespaces import Namespaces from owslib.map.common import WMSCapabilitiesReader, AbstractContentMetadata @@ -709,6 +710,12 @@ def parse_remote_metadata(self, timeout=30): if mdelem is not None: metadataUrl['metadata'] = MD_Metadata(mdelem) continue + else: + mdelem = MD_Metadata3.find_start(doc) + if mdelem is not None: + metadataUrl["metadata"] = MD_Metadata3(mdelem) + else: + metadataUrl["metadata"] = None except Exception: metadataUrl['metadata'] = None diff --git a/tests/resources/iso3_examples/README.txt b/tests/resources/iso3_examples/README.txt new file mode 100644 index 000000000..7e7dec36e --- /dev/null +++ b/tests/resources/iso3_examples/README.txt @@ -0,0 +1,10 @@ +'iso_3.py' Test Examples +======================== + +This directory provides data used to check conformance against OWSLib's ISO 19115 Part 3 XML support. + +Acknowledged sources: + +https://portal.auscope.org.au/geonetwork: auscope-3d-model.xml +https://metawal.wallonie.be/geonetwork/: metawal.wallonie.be-catchments.xml, metawal.wallonie.be-srv.xml +https://github.com/Esri/arcgis-pro-metadata-toolkit: arcgis-sample.xml \ No newline at end of file diff --git a/tests/resources/iso3_examples/arcgis-sample.xml b/tests/resources/iso3_examples/arcgis-sample.xml new file mode 100644 index 000000000..399cf17c1 --- /dev/null +++ b/tests/resources/iso3_examples/arcgis-sample.xml @@ -0,0 +1,3762 @@ + + + + + + Metadata > Details > File Identifier + + + + + + + eng + + + US + + + utf8 + + + + + + + + service + + + Metadata > Details > Hierarchy Level Name + + + + + + + pointOfContact + + + + + Metadata > Contacts > Contact > Organization + + + + + + + Metadata > Contacts > Contact > Contact Information > Phone + + + voice + + + + + + + Metadata > Contacts > Contact > Contact Information > Phone + TDD/TTY + + + voice + + + + + + + Metadata > Contacts > Contact > Contact Information > Fax + + + facsimile + + + + + + + Metadata > Contacts > Contact > Contact Information > Address + + + Metadata > Contacts > Contact > Contact Information > City + + + Metadata > Contacts > Contact > Contact Information > State + + + Metadata > Contacts > Contact > Contact Information > Postal Code + + + US + + + Metadata > Contacts > Contact > Contact Information > Email + + + + + + + http://Metadata_Contacts_Contact_Contact_Information_Online_Resource_Linkage + + + Metadata > Contacts > Contact > Contact Information > Online Resource > Protocol + + + Metadata > Contacts > Contact > Contact Information > Online Resource > Profile + + + Metadata > Contacts > Contact > Contact Information > Online Resource > Name + + + Metadata > Contacts > Contact > Contact Information > Online Resource > Description + + + information + + + + + Metadata > Contacts > Contact > Contact Information > Hours + + + Metadata > Contacts > Contact > Contact Information > Instructions + + + + + + + Metadata > Contacts > Contact > Name + + + Metadata > Contacts > Contact > Position + + + + + + + + + + + 2021-12-07T00:00:00 + + + revision + + + + + + + ISO 19115-3 Geographic Information - Metadata - Part 1: Fundamentals + + + 2014 + + + + + + + fre + + + FR + + + utf8 + + + + + + + fullSurfaceGraph + + + + + complex + + + 27249 + + + + + + + + + 1 + + + + + track + + + 35 + + + 1.0 + + + + + point + + + true + + + + + + + 3 + + + + + row + + + 150 + + + 5.0 + + + + + + + column + + + 259 + + + 10.0 + + + + + + + vertical + + + 20 + + + 0.1 + + + + + area + + + true + + + true + + + Resource > Spatial Data Representation > Georectified > Check Point Description + + + + Resource > Spatial Data Representation > Georectified > Corner Point > Description + + Resource > Spatial Data Representation > Georectified > Corner Point > Identifier + 1.0 1.0 + + + + + Resource > Spatial Data Representation > Georectified > Corner Point > Description + + Resource > Spatial Data Representation > Georectified > Corner Point > Identifier + 7.0 7.0 + + + + + Resource > Spatial Data Representation > Georectified > Center Point > Description + + Resource > Spatial Data Representation > Georectified > Center Point > Identifier + 4.0 4.0 + + + + upperLeft + + + Resource > Spatial Data Representation > Georectified > Transformation Dimension Description + + + Resource > Spatial Data Representation > Georectified > Transformation Dimension Mapping + + + + + + + 1 + + + + + time + + + 140 + + + 1 + + + + + point + + + true + + + true + + + true + + + Resource > Spatial Data Representation > Georeferenceable > Orientation Parameter Description + + + Resource > Spatial Data Representation > Georeferenceable > Georeferenced Parameters + + + + + Resource > Spatial Data Representation > Georeferenceable > Parameter Citation > Titles > Title + + + + + 2011-01-01T00:00:00 + + + revision + + + + + + + publisher + + + + + Resource > Spatial Data Representation > Georeferenceable > Parameter Citation > Contact > Organization + + + + + + + Resource > Spatial Data Representation > Georeferenceable > Parameter Citation > Contact > Contact Information > Email + + + + + + + + + + + + + http://Resource_Spatial_Data_Representation_Georeferenceable_Parameter_Citation_Online_Resource_Linkage + + + Resource > Spatial Data Representation > Georeferenceable > Parameter Citation > Online Resource > Protocol + + + + + + + + + + + + + + + Resource > Spatial Reference > Reference System > Authority Citation > Titles > Title + + + + + 2011-07-01T00:00:00 + + + publication + + + + + + + publisher + + + + + Resource > Spatial Reference > Reference System > Authority Citation > Contact > Organization + + + + + + + Resource > Spatial Reference > Reference System > Authority Citation > Contact > Contact Information > Email + + + + + + + + + + + + + http://Resource_Spatial_Reference_Reference_System_Authority_Citation_Online_Resource_Linkage + + + Resource > Spatial Reference > Reference System > Authority Citation > Online Resource > Protocol + + + + + + + Resource > Spatial Reference > Reference System > Code + + + Resource > Spatial Reference > Reference System > Code Space + + + Resource > Spatial Reference > Reference System > Version + + + + + + + + + + + Overview > Item Description > Title + Overview > Citation > Titles > Title + + + Overview > Citation > Titles > Alternate Title + + + + + 1994-01-01T06:00:00 + + + publication + + + + + + + 1993-01-01T23:59:59 + + + creation + + + + + + + 1995-01-01T18:30:59 + + + revision + + + + + + + 2011-09-01T00:00:00 + + + notAvailable + + + + + + + 2011-09-03T00:00:00 + + + adopted + + + + + + + 2011-09-02T00:00:00 + + + inforceDate + + + + + + + 2011-09-04T00:00:00 + + + deprecated + + + + + + + 2011-09-05T00:00:00 + + + superseded + + + + + Overview > Citation > Edition > Edition + + + 2011-01-01T00:00:00 + + + + + + + Overview > Citation > Identifier > Authority Citation > Titles > Title + + + + + 2010-01-01T00:00:00 + + + publication + + + + + + + publisher + + + + + Overview > Citation > Identifier > Authority Citation > Contact > Organization + + + + + + + Overview > Citation > Identifier > Authority Citation > Contact > Contact Information > Email + + + + + + + + + + + + + http://Overview_Citation_Identifier_Authority_Citation_Online_Resource_Linkage + + + Overview > Citation > Identifier > Authority Citation > Online Resource > Protocol + + + + + + + Overview > Citation > Identifier > Code + + + + + + + Metadata > Details > Dataset URI (NAP Function Code=Web Map Service) + + + + + + + originator + + + + + Overview > Citation Contacts > Contact > Organization (Role=Originator) + + + + + + + Overview > Citation Contacts > Contact > Contact Information > Phone (Role=Originator) + + + voice + + + + + + + Overview > Citation Contacts > Contact > Contact Information > Address (Role=Originator) + + + Overview > Citation Contacts > Contact > Contact Information > City (Role=Originator) + + + Overview > Citation Contacts > Contact > Contact Information > State (Role=Originator) + + + Overview > Citation Contacts > Contact > Contact Information > Postal Code (Role=Originator) + + + US + + + Overview > Citation Contacts > Contact > Contact Information > Email (Role=Originator) + + + + + + + + + Overview > Citation Contacts > Contact > Name (Role=Originator) + + + Overview > Citation Contacts > Contact > Position (Role=Originator) + + + + + + + + + + + publisher + + + + + Overview > Citation Contacts > Contact > Organization (Role=Publisher) + + + + + + + Overview > Citation Contacts > Contact > Contact Information > Address (Role=Publisher) + + + Overview > Citation Contacts > Contact > Contact Information > City (Role=Publisher) + + + Overview > Citation Contacts > Contact > Contact Information > State (Role=Publisher) + + + Overview > Citation Contacts > Contact > Contact Information > Postal Code (Role=Publisher) + + + US + + + Overview > Citation Contacts > Contact > Contact Information > Email (Role=Publisher) + + + + + + + + + Overview > Citation Contacts > Contact > Name (Role=Publisher) + + + Overview > Citation Contacts > Contact > Position (Role=Publisher) + + + + + + + + + + + rightsHolder + + + + + Overview > Citation Contacts > Contact > Organization (NAP Role=Rights Holder) + + + + + + + Overview > Citation Contacts > Contact > Contact Information > Phone (NAP Role=Rights Holder) + + + voice + + + + + + + Overview > Citation Contacts > Contact > Contact Information > Address (NAP Role=Rights Holder) + + + Overview > Citation Contacts > Contact > Contact Information > City (NAP Role=Rights Holder) + + + Overview > Citation Contacts > Contact > Contact Information > State (NAP Role=Rights Holder) + + + Overview > Citation Contacts > Contact > Contact Information > Postal Code (NAP Role=Rights Holder) + + + US + + + Overview > Citation Contacts > Contact > Contact Information > Email (NAP Role=Rights Holder) + + + + + + + + + Overview > Citation Contacts > Contact > Name (NAP Role=Rights Holder) + + + Overview > Citation Contacts > Contact > Position (NAP Role=Rights Holder) + + + + + + + + + mapDigital + + + + + Overview > Citation > Series > Name + + + Overview > Citation > Series > Issue + + + Overview > Citation > Series > Page + + + + + Overview > Citation > Other Details + + + Overview > Citation > ISBN + + + Overview > Citation > ISSN + + + + + http://Resource_Distribution_Digital_Transfer_Options_Online_Resource_Linkage + + + Resource > Distribution > Digital Transfer Options > Online Resource > Protocol + + + + + + + Overview > Item Description > Description/Abstract + + + Overview > Item Description > Summary/Purpose + + + Overview > Item Description > Credits + Resource > Details > Credit + + + onGoing + + + + + pointOfContact + + + + + Resource > Points of Contact > Contact > Name + + + + + + + Resource > Points of Contact > Contact > Contact Information > Phone + + + voice + + + + + + + Resource > Points of Contact > Contact > Contact Information > Address + + + Resource > Points of Contact > Contact > Contact Information > City + + + Resource > Points of Contact > Contact > Contact Information > State + + + Resource > Points of Contact > Contact > Contact Information > Zip + + + US + + + Resource > Points of Contact > Contact > Contact Information > Email + + + + + + + http://Resource_Points_of_Contact_Contact_Contact_Information_Online_Resource_Linkage + + + Resource > Points of Contact > Contact > Contact Information > Online Resource > Protocol + + + + + + + + + + + vector + + + grid + + + + + + + 24000 + + + + + + + + + 0.001 + + + + + geoscientificInformation + + + imageryBaseMapsEarthCover + + + + + Resource > Extents > Extent > Description Bounding boxes created on the Extents page do not have the esriExtentType attribute. + + + + + true + + + -155 + + + -135 + + + 35 + + + 42 + + + + + + + true + + + + + + + Resource > Extents > Extent > Geographic Description > Authority Citation > Titles > Title + + + + + 2011-01-01T00:00:00 + + + publication + + + + + + + publisher + + + + + Resource > Extents > Extent > Geographic Description > Authority Citation > Contact > Organization + + + + + + + Resource > Extents > Extent > Geographic Description > Authority Citation > Contact > Contact Information > Email + + + + + + + + + + + + + http://Resource_Extents_Extent_Geographic_Description_Authority_Citation_Online_Resource_Linkage + + + Resource > Extents > Extent > Geographic Description > Authority Citation > Online Resource > Protocol + + + + + + + Resource > Extents > Extent > Geographic Description > Code + + + + + + + + + + 2011-01-01T00:00:01 + 2011-12-31T23:59:59 + + + + + + + + 0.01 + + + 599.99 + + + + + + + + + + A bounding box created on the Item Description page has the esriExtentType attribute set with the value "search". The Item Description page only supports editing a bounding box with that attribute. The bounding box added by the synchronization process has the esriExtentType attribute set with the value "search". This bounding box is preferred when exporting to a standard that only allows one bounding box, such as the FGDC CSDGM. + + + + + true + + + -180 + + + 180 + + + -90 + + + 90 + + + + + + + + + asNeeded + + + + + 2012-01-01T00:00:00 + + + revision + + + + + P36DT12H + + + + + service + + + + + Resource > Maintenance > Scope Description > Dataset + + + + + + + Resource > Maintenance > Maintenance Note + + + + + custodian + + + + + Resource > Maintenance > Contact > Name + + + + + + + Resource > Maintenance > Contact > Contact Information > Email + + + + + + + + + + + + + + + Resource > Details > Browse Graphic > File Name + + + Resource > Details > Browse Graphic > Description + + + Resource > Details > Browse Graphic > File Type + + + + + + + + + Resource > Details > Distribution Format > Format Name + + + Resource > Details > Distribution Format > Specification + + + Resource > Details > Distribution Format > Format Version + + + + + Resource > Details > Distribution Format > Amendment Number + + + Resource > Details > Distribution Format > Decompression Technique + + + + + + + Overview > Topics & Keywords > Place Keywords > Keyword1 + + + Overview > Topics & Keywords > Place Keywords > Keyword2 + + + place + + + + + Overview > Topics & Keywords > Place Keywords > Thesaurus Citation > Titles > Title + + + + + 2010-01-01T00:00:00 + + + publication + + + + + + + publisher + + + + + Overview > Topics & Keywords > Place Keywords > Thesaurus Citation > Contact > Organization + + + + + + + Overview > Topics & Keywords > Place Keywords > Thesaurus Citation > Contact > Contact Information > Email + + + + + + + + + + + + + http://Overview_Topics_Keywords_Place_Keywords_Thesaurus_Citation_Online_Resource_Linkage + + + Overview > Topics & Keywords > Place Keywords > Thesaurus Citation > Online Resource > Protocol + + + + + + + + + + + Overview > Topics & Keywords > Stratum Keywords > Keyword1 + + + Overview > Topics & Keywords > Stratum Keywords > Keyword2 + + + stratum + + + + + Overview > Topics & Keywords > Stratum Keywords > Thesaurus Citation > Titles > Title + + + + + 1988-08-26T00:00:00 + + + revision + + + + + + + publisher + + + + + Overview > Topics & Keywords > Stratum Keywords > Thesaurus Citation > Contact > Organization + + + + + + + Overview > Topics & Keywords > Stratum Keywords > Thesaurus Citation > Contact > Contact Information > Email + + + + + + + + + + + + + + + + + Overview > Topics & Keywords > Temporal Keywords > Keyword1 + + + Overview > Topics & Keywords > Temporal Keywords > Keyword2 + + + temporal + + + + + Overview > Topics & Keywords > Temporal Keywords > Thesaurus Citation > Titles > Title + + + + + 1996-08-22T00:00:00 + + + revision + + + + + + + publisher + + + + + Overview > Topics & Keywords > Temporal Keywords > Thesaurus Citation > Contact > Organization + + + + + + + Overview > Topics & Keywords > Temporal Keywords > Thesaurus Citation > Contact > Contact Information > Email + + + + + + + + + + + + + http://Overview_Topics_Keywords_Temporal_Keywords_Thesaurus_Citation_Online_Resource_Linkage + + + Overview > Topics & Keywords > Temporal Keywords > Thesaurus Citation > Online Resource > Protocol + + + + + + + + + + + Overview > Topics & Keywords > Theme Keywords > Keyword1 + + + Overview > Topics & Keywords > Theme Keywords > Keyword2 + + + theme + + + + + Overview > Topics & Keywords > Theme Keywords > Thesaurus Citation > Titles > Title + + + + + 2010-01-01T00:00:00 + + + publication + + + + + + + publisher + + + + + Overview > Topics & Keywords > Theme Keywords > Thesaurus Citation > Contact > Organization + + + + + + + Overview > Topics & Keywords > Theme Keywords > Thesaurus Citation > Contact > Contact Information > Email + + + + + + + + + + + + + http://Overview_Topics_Keywords_Theme_Keywords_Thesaurus_Citation_Online_Resource_Linkage + + + Overview > Topics & Keywords > Theme Keywords > Thesaurus Citation > Online Resource > Protocol + + + + + + + + + + + Overview > Topics & Keywords > Discipline Keywords > Keyword1 + + + Overview > Topics & Keywords > Discipline Keywords > Keyword2 + + + discipline + + + + + Overview > Topics & Keywords > Discipline Keywords > Thesaurus Citation > Titles > Title + + + + + 2007-02-12T00:00:00 + + + revision + + + + + + + publisher + + + + + Overview > Topics & Keywords > Discipline Keywords > Thesaurus Citation > Contact > Organization + + + + + + + Overview > Topics & Keywords > Discipline Keywords > Thesaurus Citation > Contact > Contact Information > Email + + + + + + + + + + + + + + + + + Overview > Topics & Keywords > Other Keywords > Keyword1 + + + Overview > Topics & Keywords > Other Keywords > Keyword2 + + + + + Overview > Topics & Keywords > Other Keywords > Thesaurus Citation > Titles > Title + + + + + 2010-05-10T00:00:00 + + + revision + + + + + + + publisher + + + + + Overview > Topics & Keywords > Other Keywords > Thesaurus Citation > Contact > Organization + + + + + + + Overview > Topics & Keywords > Other Keywords > Thesaurus Citation > Contact > Contact Information > Email + + + + + + + + + + + + + http://Overview_Topics_Keywords_Other_Keywords_Thesaurus_Citation_Online_Resource_Linkage + + + Overview > Topics & Keywords > Other Keywords > Thesaurus Citation > Online Resource > Protocol + + + + + + + + + + + Overview > Topics & Keywords > Subtopic Keywords > Keyword1 + + + Overview > Topics & Keywords > Subtopic Keywords > Keyword2 + + + subTopicCategory + + + + + Overview > Topics & Keywords > Subtopic Keywords > Thesaurus Citation > Titles > Title + + + + + 2011-10-14T00:00:00 + + + creation + + + + + + + publisher + + + + + Overview > Topics & Keywords > Subtopic Keywords > Thesaurus Citation > Contact > Organization + + + + + + + Overview > Topics & Keywords > Subtopic Keywords > Thesaurus Citation > Contact > Contact Information > Email + + + + + + + + + + + + + + + + + Overview > Topics & Keywords > Product Keywords > Keyword1 + + + Overview > Topics & Keywords > Product Keywords > Keyword2 + + + product + + + + + Overview > Topics & Keywords > Product Keywords > Thesaurus Citation > Titles > Title + + + + + 2011-10-13T00:00:00 + + + creation + + + + + + + publisher + + + + + Overview > Topics & Keywords > Product Keywords > Thesaurus Citation > Contact > Organization + + + + + + + Overview > Topics & Keywords > Product Keywords > Thesaurus Citation > Contact > Contact Information > Email + + + + + + + + + + + + + + + + + Live Data and Maps + + + + + + + + Resource > Details > Usage > Specific Usage + + + + 2011-10-01T10:00:00 + + + + Resource > Details > Usage > Limitations + + + + + user + + + + + Resource > Details > Usage > Contact > Organization + + + + + + + Resource > Details > Usage > Contact > Contact Information > Email + + + + + + + + + Resource > Details > Usage > Contact > Position + + + + + + + + + + + + + Overview > Item Description > Use Limitation + Resource > Constraints > General Constraints > Use Limitation + + + + + + + Resource > Constraints > Legal Constraints > Use Limitation + + + license + + + otherRestrictions + + + Resource > Constraints > Legal Constraints > Other Constraints + + + + + + + Resource > Constraints > Security Constraints > Use Limitation + + + restricted + + + Resource > Constraints > Security Constraints > User Note + + + Resource > Constraints > Security Constraints > Classification System + + + Resource > Constraints > Security Constraints > Handling Description + + + + + + + + + Resource > References > Aggregate Information > Dataset Citation > Titles > Title (Association=Cross Reference) + + + + + 2006-06-01T00:00:00 + + + publication + + + + + + + originator + + + + + Resource > References > Aggregate Information > Dataset Citation > Contact > Organization (Association=Cross Reference) + + + + + + + Resource > References > Aggregate Information > Dataset Citation > Contact > Contact Information > Email (Association=Cross Reference) + + + + + + + + + Resource > References > Aggregate Information > Dataset Citation > Contact > Name (Association=Cross Reference) + + + + + + + + + + + http://Resource_References_Aggregate_Information_Dataset_Citation_Online_Resource_Linkage_Association=Cross_Reference + + + Resource > References > Aggregate Information > Dataset Citation > Online Resource > Protocol (Association=Cross Reference) + + + + + + + crossReference + + + investigation + + + + + + + + + Resource > References > Aggregate Information > Dataset Citation > Titles > Title (Association=Larger Work) + + + + + 2003-09-18T00:00:00 + + + publication + + + + + + + originator + + + + + Resource > References > Aggregate Information > Dataset Citation > Contact > Organization (Association=Larger Work) + + + + + + + Resource > References > Aggregate Information > Dataset Citation > Contact > Contact Information > Email (Association=Larger Work) + + + + + + + + + Resource > References > Aggregate Information > Dataset Citation > Contact > Name (Association=Larger Work) + + + + + + + + + + + http://Resource_References_Aggregate_Information_Dataset_Citation_Online_Resource_Linkage_Association=Larger_Work + + + Resource > References > Aggregate Information > Dataset Citation > Online Resource > Protocol (Association=Larger Work) + + + + + + + largerWorkCitation + + + + + + + + + Overview > Citation > Titles > Collective Title + + + + + collectiveTitle + + + + + Resource > Service Details > Service Type > Name + + + Resource > Service Details > Service Type Version + + + + + Resource > Service Details > Access Properties > Fees + + + 2011-11-01T06:00:00 + + + Resource > Service Details > Access Properties > Ordering Instructions + + + Resource > Service Details > Access Properties > Turnaround + + + + + loose + + + + + Resource > Service Details > Coupled Resource > Identifier + + + + + + Resource > Service Details > Coupled Resource > Operation Name + + + + + + + + + + + + + true + + + + + fre + + + CA + + + utf8 + + + + + true + + + + + Resource > Content > Feature Catalogue > Feature Type > Name + + + + + + + Resource > Content > Feature Catalogue > Feature Catalogue Citation > Titles > Title + + + + + 2011-03-15T00:00:00 + + + creation + + + + + + + publisher + + + + + Resource > Content > Feature Catalogue > Feature Catalogue Citation > Contact > Organization + + + + + + + Resource > Content > Feature Catalogue > Feature Catalogue Citation > Contact > Contact Information > Email + + + + + + + + + + + + + http://Resource_Content_Feature_Catalogue_Feature_Catalogue_Citation_Online_Resource_Linkage + + + Resource > Content > Feature Catalogue > Feature Catalogue Citation > Online Resource > Protocol + + + + + + + + + + + Resource > Content > Coverage Description > Attribute Description + + + + + physicalMeasurement + + + + + + + Resource > Content > Coverage Description > Range Dimension > Sequence Identifier + + + + + Resource > Content > Coverage Description > Range Dimension > Sequence Identifier > Type + + + + + + + Resource > Content > Coverage Description > Range Dimension > Descriptor + + + + + + + + + + + Resource > Content > Image Description > Attribute Description + + + + + + + Resource > Content > Image Description > Processing Level Code > Authority Citation > Titles > Title + + + + + 2008-09-18T00:00:00 + + + revision + + + + + + + publisher + + + + + Resource > Content > Image Description > Processing Level Code > Authority Citation > Contact > Organization + + + + + + + Resource > Content > Image Description > Processing Level Code > Authority Citation > Contact > Contact Information > Email + + + + + + + + + + + + + http://Resource_Content_Image_Description_Processing_Level_Code_Authority_Citation_Online_Resource_Linkage + + + Resource > Content > Image Description > Processing Level Code > Authority Citation > Online Resource > Protocol + + + + + + + Resource > Content > Image Description > Processing Level Code > Code + + + + + + + image + + + + + + + Resource > Content > Image Description > Band > Sequence Identifier + + + + + Resource > Content > Image Description > Band > Sequence Identifier > Type + + + + + + + Resource > Content > Image Description > Band > Descriptor + + + 255.99 + + + 0.01 + + + + Unified Code of Units of Measure + m + + + + 0.98 + + + 19.3 + + + 8 + + + 225.4 + + + 4 + + + + + + + 78.5 + + + 135.5 + + + shadow + + + + + + + Resource > Content > Image Description > Quality Code > Authority Citation > Titles > Title + + + + + 2008-09-18T00:00:00 + + + revision + + + + + + + publisher + + + + + Resource > Content > Image Description > Quality Code > Authority Citation > Contact > Organization + + + + + + + Resource > Content > Image Description > Quality Code > Authority Citation > Contact > Contact Information > Email + + + + + + + + + + + + + http://Resource_Content_Image_Description_Quality_Code_Authority_Citation_Online_Resource_Linkage + + + Resource > Content > Image Description > Quality Code > Authority Citation > Online Resource > Protocol + + + + + + + Resource > Content > Image Description > Quality Code > Code + + + + + 6.5 + + + 2 + + + true + + + true + + + true + + + true + + + true + + + + + + + + + + + Resource > Distribution > Distribution Format > Format Name + + + Resource > Distribution > Distribution Format > Format Version + + + + + + + + + + + Resource > Distribution > Distributor > Distribution Format > Format Name + + + Resource > Distribution > Distributor > Distribution Format > Specification + + + Resource > Distribution > Distributor > Distribution Format > Format Version + + + + + Resource > Distribution > Distributor > Distribution Format > Amendment Number + + + Resource > Distribution > Distributor > Distribution Format > Decompression Technique + + + + + + + + + distributor + + + + + Resource > Distribution > Distributor > Organization + + + + + + + Resource > Distribution > Distributor > Contact Information > Phone + + + voice + + + + + + + Resource > Distribution > Distributor > Contact Information > Address + + + Resource > Distribution > Distributor > Contact Information > City + + + Resource > Distribution > Distributor > Contact Information > State + + + Resource > Distribution > Distributor > Contact Information > Postal Code + + + US + + + Resource > Distribution > Distributor > Contact Information > Email + + + + + + + + + + + + + Resource > Distribution > Distributor > Ordering Process > Fees + + + 2011-10-15T13:00:00 + + + Resource > Distribution > Distributor > Ordering Process > Ordering Instructions + + + Resource > Distribution > Distributor > Ordering Process > Turnaround + + + + + + + + + Resource > Distribution > Distributor > Distribution Format > Format Name + + + Resource > Distribution > Distributor > Distribution Format > Specification + + + Resource > Distribution > Distributor > Distribution Format > Format Version + + + + + Resource > Distribution > Distributor > Distribution Format > Amendment Number + + + Resource > Distribution > Distributor > Distribution Format > Decompression Technique + + + + + + + Resource > Distribution > Distributor > Digital Transfer Options > Units of Distribution + + + 1 + + + + + http://Resource_Distribution_Distributor_Digital_Transfer_Options_Online_Resource_Linkage + + + Resource > Distribution > Distributor > Digital Transfer Options > Online Resource > Protocol + + + Resource > Distribution > Distributor > Digital Transfer Options > Online Resource > Profile + + + Resource > Distribution > Distributor > Digital Transfer Options > Online Resource > Name + + + Resource > Distribution > Distributor > Digital Transfer Options > Online Resource > Description (not a geoportal content type, e.g. Offline Data) + + + download + + + + + + + + + dvdRom + + + + + 1 + + + Resource > Distribution > Distributor > Digital Transfer Options > Offline Medium > Density Units (dvdRom Medium Name) + + + 1 + + + iso9660 + + + Resource > Distribution > Distributor > Digital Transfer Options > Offline Medium > Medium Note (dvdRom Medium Name) + + + + + + + + + + + + + hardcopy + + + + + Resource > Distribution > Distributor > Digital Transfer Options > Offline Medium > Medium Note (hardcopy Medium Name) + + + + + + + + + + + + + USBFlashDrive + + + + + UDF + + + Resource > Distribution > Distributor > Digital Transfer Options > Offline Medium > Medium Note (NAP USBFlashDrive Medium Name + UDF Format Code) + + + + + + + + + + + 2.12 + + + + + http://Resource_Distribution_Digital_Transfer_Options_Online_Resource_Linkage + + + Resource > Distribution > Digital Transfer Options > Online Resource > Protocol + + + + + + + + + + + + + service + + + + + Resource > Data Quality > Extent > Description + + + + + + 2011-10-12T00:00:00 + + + + + + + + + + Resource > Data Quality > Level Description > Dataset + + + + + + + + + + + + + + + Resource > Data Quality > Report > Measure > Identifier > Authority Citation > Titles > Title (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + + 2011-08-31T00:00:00 + + + revision + + + + + + + publisher + + + + + Resource > Data Quality > Report > Measure > Identifier > Authority Citation > Contact > Organization (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + + + + Resource > Data Quality > Report > Measure > Identifier > Authority Citation > Contact > Contact Information > Email (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + + + + + + + + + + http://Resource_Data_Quality_Report_Measure_Identifier_Authority_Citation_Online_Resource_Linkage_Type=Absolute_External_Positional_Accuracy_Dimension=Horizontal + + + Resource > Data Quality > Report > Measure > Identifier > Authority Citation > Online Resource > Protocol (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + + + + Resource > Data Quality > Report > Measure > Identifier > Code (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + + Resource > Data Quality > Report > Measure > Name (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + Resource > Data Quality > Report > Measure > Description (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + + + + 2011-10-12T00:00:00 + + + Resource > Data Quality > Report > Evaluation Method > Description (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + + Resource > Data Quality > Report > Evaluation Method > Procedure Citation > Titles > Title (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + + 2011-08-01T00:00:00 + + + revision + + + + + + + publisher + + + + + Resource > Data Quality > Report > Evaluation Method > Procedure Citation > Contact > Organization (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + + + + Resource > Data Quality > Report > Evaluation Method > Procedure Citation > Contact > Contact Information > Email (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + + + + + + + + + + http://Resource_Data_Quality_Report_Evaluation_Method_Procedure_Citation_Online_Resource_Linkage_Type=Absolute_External_Positional_Accuracy_Dimension=Horizontal + + + Resource > Data Quality > Report > Evaluation Method > Procedure Citation > Online Resource > Protocol (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + + + + indirect + + + + + + + + + Resource > Data Quality > Report > Conformance Result > Specification > Titles > Title (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + + 2009-06-20T00:00:00 + + + creation + + + + + + + publisher + + + + + Resource > Data Quality > Report > Conformance Result > Specification > Contact > Organization (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + + + + Resource > Data Quality > Report > Conformance Result > Specification > Contact > Contact Information > Email (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + + + + + + + + + + http://Resource_Data_Quality_Report_Conformance_Result_Specification_Online_Resource_Linkage_Type=Absolute_External_Positional_Accuracy_Dimension=Horizontal + + + Resource > Data Quality > Report > Conformance Result > Specification > Online Resource > Protocol (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + + + + Resource > Data Quality > Report > Conformance Result > Explanation (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + true + + + + + + + Resource > Data Quality > Report > Quantitative Result > Value (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + Unified Code of Units of Measure + m + + + + Resource > Data Quality > Report > Quantitative Result > Value Type (Type=Absolute External Positional Accuracy + Dimension=Horizontal) + + + + + + + + + + + Resource > Data Quality > Report > Measure > Description (Type=Absolute External Positional Accuracy + Dimension=Vertical) + + + + + + + Resource > Data Quality > Report > Evaluation Method > Description (Type=Absolute External Positional Accuracy + Dimension=Vertical) + + + + + + + Resource > Data Quality > Report > Quantitative Result > Value (Type=Absolute External Positional Accuracy + Dimension=Vertical) + + + + Unified Code of Units of Measure + m + + + + + + + + + + + + Resource > Data Quality > Report > Measure > Description (Type=Completeness Omission) + + + + + + + Resource > Data Quality > Report > Quantitative Result > Value (Type=Completeness Omission) + + + + Unified Code of Units of Measure + m + + + + + + + + + + + + Resource > Data Quality > Report > Measure > Description (Type=Conceptual Consistency) + + + + + + + Resource > Data Quality > Report > Quantitative Result > Value (Type=Conceptual Consistency) + + + + Unified Code of Units of Measure + m + + + + + + + + + + + + Resource > Data Quality > Report > Measure > Description (Type=Quantitative Attribute Accuracy) + + + + + + + Resource > Data Quality > Report > Evaluation Method > Description (Type=Quantitative Attribute Accuracy) + + + + + + + Resource > Data Quality > Report > Quantitative Result > Value (Type=Quantitative Attribute Accuracy) + + + + Unified Code of Units of Measure + Cel + + + + + + + + + + + + + + Resource > Data Quality > Report > Conformance Result > Specification > Titles > Title (Type=Domain Consistency) + + + + + 2010-07-01T00:00:00 + + + creation + + + + + + + publisher + + + + + Resource > Data Quality > Report > Conformance Result > Specification > Contact > Organization (Type=Domain Consistency) + + + + + + + Resource > Data Quality > Report > Conformance Result > Specification > Contact > Contact Information > Email (Type=Domain Consistency) + + + + + + + + + + + + + http://Resource_Data_Quality_Report_Conformance_Result_Specification_Online_Resource_Linkage_Type=Domain_Consistency + + + Resource > Data Quality > Report > Conformance Result > Specification > Online Resource > Protocol (Type=Domain Consistency) + + + + + + + Resource > Data Quality > Report > Conformance Result > Explanation (Type=Domain Consistency) + + + true + + + + + + + + + + + Resource > Lineage > Statement + + + + + service + + + + + Resource > Data Quality > Extent > Description + + + + + + 2011-10-12T00:00:00 + + + + + + + + + + Resource > Data Quality > Level Description > Dataset + + + + + + + + + Resource > Lineage > Data Source > Source Description + + + + + + + 250000 + + + + + + + + + + + + + Resource > Lineage > Data Source > Reference System > Authority Citation > Titles > Title + + + + + 2011-06-15T00:00:00 + + + revision + + + + + + + custodian + + + + + Resource > Lineage > Data Source > Reference System > Authority Citation > Contact > Organization + + + + + + + Resource > Lineage > Data Source > Reference System > Authority Citation > Contact > Contact Information > Email + + + + + + + + + + + + + http://Resource_Lineage_Data_Source_Reference_System_Authority_Citation_Online_Resource_Linkage + + + Resource > Lineage > Data Source > Reference System > Authority Citation > Online Resource > Protocol + + + + + + + Resource > Lineage > Data Source > Reference System > Code + + + Resource > Lineage > Data Source > Reference System > Code Space + + + Resource > Lineage > Data Source > Reference System > Version + + + + + + + + + Resource > Lineage > Data Source > Source Citation > Titles > Title + + + Resource > Lineage > Data Source > Source Citation > Titles > Alternate Title + + + + + 2011-07-15T00:00:00 + + + publication + + + + + + + originator + + + + + Resource > Lineage > Data Source > Source Citation > Contact > Organization + + + + + + + Resource > Lineage > Data Source > Source Citation > Contact > Contact Information > Email + + + + + + + + + + + + + http://Resource_Lineage_Data_Source_Source_Citation_Online_Resource_Linkage + + + Resource > Lineage > Data Source > Source Citation > Online Resource > Protocol + + + + + + + + + Resource > Lineage > Data Source > Source Extent > Description + + + + + + 2011-06-06T00:00:00 + + + + + + + + + 2011-07-14T14:20:00 + + + + + + + + + 2011-08-05T06:05:00 + + + + + + + + + + + + Resource > Lineage > Process Step > Process Description + + + Resource > Lineage > Process Step > Rationale + + + + 2011-10-01T16:10:32 + + + + + + processor + + + + + Resource > Lineage > Process Step > Processor > Organization + + + + + + + Resource > Lineage > Process Step > Processor > Contact Information > Phone + + + voice + + + + + + + Resource > Lineage > Process Step > Processor > Contact Information > Address + + + Resource > Lineage > Process Step > Processor > Contact Information > City + + + Resource > Lineage > Process Step > Processor > Contact Information > State + + + Resource > Lineage > Process Step > Processor > Contact Information > Postal Code + + + US + + + Resource > Lineage > Process Step > Processor > Contact Information > Email + + + + + + + + + Resource > Lineage > Process Step > Processor > Name + + + + + + + + + + + Resource > Lineage > Process Step > Data Source > Source Description (Source Type=Used) + + + + + + + + + Resource > Lineage > Process Step > Data Source > Reference System > Authority Citation > Titles > Title (Source Type=Used) + + + + + 2012-07-20T00:00:00 + + + publication + + + + + + + resourceProvider + + + + + Resource > Lineage > Process Step > Data Source > Reference System > Authority Citation > Contact > Organization (Source Type=Used) + + + + + + + Resource > Lineage > Process Step > Data Source > Reference System > Authority Citation > Contact > Contact Information > Email (Source Type=Used) + + + + + + + + + + + + + http://Resource_Lineage_Process_Step_Data_Source_Reference_System_Authority_Citation_Online_Resource_Linkage_Source_Type=Used + + + Resource > Lineage > Process Step > Data Source > Reference System > Authority Citation > Online Resource > Protocol (Source Type=Used) + + + + + + + Resource > Lineage > Process Step > Data Source > Reference System > Code (Source Type=Used) + + + Resource > Lineage > Process Step > Data Source > Reference System > Code Space (Source Type=Used) + + + Resource > Lineage > Process Step > Data Source > Reference System > Version (Source Type=Used) + + + + + + + + + Resource > Lineage > Process Step > Data Source > Source Citation > Titles > Title (Source Type=Used) + + + Resource > Lineage > Process Step > Data Source > Source Citation > Titles > Alternate Title (Source Type=Used) + + + + + 2011-09-14T00:00:00 + + + revision + + + + + + + publisher + + + + + Resource > Lineage > Process Step > Data Source > Source Citation > Contact > Organization (Source Type=Used Role=Publisher) + + + + + + + Resource > Lineage > Process Step > Data Source > Source Citation > Contact > Contact Information > City (Source Type=Used Role=Publisher) + + + Resource > Lineage > Process Step > Data Source > Source Citation > Contact > Contact Information > State (Source Type=Used Role=Publisher) + + + US + + + Resource > Lineage > Process Step > Data Source > Source Citation > Contact > Contact Information > Email (Source Type=Used Role=Publisher) + + + + + + + + + + + + + originator + + + + + Resource > Lineage > Process Step > Data Source > Source Citation > Contact > Organization (Source Type=Used Role=Originator) + + + + + + + Resource > Lineage > Process Step > Data Source > Source Citation > Contact > Contact Information > Email (Source Type=Used Role=Originator) + + + + + + + + + + + + + http://Resource_Lineage_Process_Step_Data_Source_Source_Citation_Online_Resource_Linkage_Source_Type=Used + + + Resource > Lineage > Process Step > Data Source > Source Citation > Online Resource > Protocol (Source Type=Used) + + + + + + + + + + + true + + + -100 + + + -80 + + + 21.5 + + + 42.5 + + + + + + + + + + + Resource > Lineage > Process Step > Data Source > Source Description (Source Type=Produced) + + + + + Resource > Lineage > Process Step > Data Source > Source Citation > Titles > Title (Source Type=Produced) + + + Resource > Lineage > Process Step > Data Source > Source Citation > Titles > Alternate Title (Source Type=Produced) + + + + + 2011-10-14T00:00:00 + + + creation + + + + + + + originator + + + + + Resource > Lineage > Process Step > Data Source > Source Citation > Contact > Organization (Source Type=Produced Role=Originator) + + + + + + + Resource > Lineage > Process Step > Data Source > Source Citation > Contact > Contact Information > Email (Source Type=Produced Role=Originator) + + + + + + + + + + + + + + + + + + + + + + + Resource > References > Portrayal Citation > Titles > Title + + + + + 2008-01-01T00:00:00 + + + revision + + + + + + + custodian + + + + + Resource > References > Portrayal Citation > Contact > Organization + + + + + + + Resource > References > Portrayal Citation > Contact > Contact Information > Email + + + + + + + + + Resource > References > Portrayal Citation > Contact > Position + + + + + + + + + + + http://Resource_References_Portrayal_Citation_Online_Resource_Linkage + + + Resource > References > Portrayal Citation > Online Resource > Protocol + + + + + + + + + + + Metadata > Constraints > General Constraints > Use Limitation + + + + + + + Metadata > Constraints > Legal Constraints > Use Limitation + + + otherRestrictions + + + restricted + + + Metadata > Constraints > Legal Constraints > Other Constraints + + + + + + + Metadata > Constraints > Security Constraints > Use Limitation + + + unclassified + + + Metadata > Constraints > Security Constraints > User Note + + + Metadata > Constraints > Security Constraints > Classification System + + + Metadata > Constraints > Security Constraints > Handling Description + + + + + + + + + Resource > References > Application Schema Information > Citation > Titles > Title + + + + + 2011-04-08T00:00:00 + + + creation + + + + + + + publisher + + + + + Resource > References > Application Schema Information > Citation > Contact > Organization + + + + + + + Resource > References > Application Schema Information > Citation > Contact > Contact Information > Email + + + + + + + + + + + + + http://Resource_References_Application_Schema_Information_Citation_Online_Resource_Linkage + + + Resource > References > Application Schema Information > Citation > Online Resource > Protocol + + + + + + + Resource > References > Application Schema Information > Schema Language + + + Resource > References > Application Schema Information > Constraint Language + + + Resource > References > Application Schema Information > ASCII + + + + + Resource > References > Application Schema Information > Graphics File Source + + + Resource > References > Application Schema Information > Graphics File + + + + + + + Resource > References > Application Schema Information > Software Development File Source + + + Resource > References > Application Schema Information > Software Development File + + + + + Resource > References > Application Schema Information > Software Development File Format + + + + + + + unknown + + + + + 2012-11-01T12:00:00 + + + revision + + + + + P1Y2M3DT4H5M6S + + + + + service + + + + + Metadata > Maintenance > Scope Description > Attributes + + + + + + + Metadata > Maintenance > Scope Description > Features + + + + + + + Metadata > Maintenance > Scope Description > Feature Instances + + + + + + + Metadata > Maintenance > Scope Description > Attribute Instances + + + + + + + Metadata > Maintenance > Scope Description > Dataset + + + + + + + Metadata > Maintenance > Scope Description > Other Instances + + + + + + + Last metadata review date: 20211207 + + + Metadata > Maintenance > Maintenance Note + + + + + custodian + + + + + Metadata > Maintenance > Contact > Organization + + + + + + + Metadata > Maintenance > Contact > Contact Information > Email + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/resources/iso3_examples/auscope-3d-model.xml b/tests/resources/iso3_examples/auscope-3d-model.xml new file mode 100644 index 000000000..59ab17381 --- /dev/null +++ b/tests/resources/iso3_examples/auscope-3d-model.xml @@ -0,0 +1,341 @@ + + + + + + 5ebc3cb7-a3b5-4760-a8ff-851d5d5beb32 + + + urn:uuid + + + + + + + + + + + + + + + + + + + + + + Earth Resources Victoria + + + + + + + 1300 366 356 + + + + + + + + + + GPO Box 2392 + + + Melbourne + + + Victoria + + + 3001 + + + Australia + + + customer.service@ecodev.vic.gov.au + + + + + + + + + Anthony Hurst + + + Executive Director, Earth Resources Policy and Programs + + + + + + + + + + + 2022-11-03T06:16:21 + + + + + + + + + + 2022-11-03T06:17:02 + + + + + + + + + + ISO 19115-3 + + + + + + + http://portal.auscope.org/geonetwork/srv/api/records/5ebc3cb7-a3b5-4760-a8ff-851d5d5beb32 + + + + + + + + + + + + 3D geological model of the Otway and Torquay Basin 2011 + + + + + 2010-01-01 + + + + + + + https://geology.data.vic.gov.au/searchAssistant/document.php?q=parent_id:107513 + + + + Reference + + + + + + + + A 3D model of the Otway and Torquay basins has been produced at 1:250 000 scale as part of GeoScience Victorias state-wide 3D geological model. To date there has been a “knowledge gap” in the transition between the basement and basin environments. This regional scale integration of the basement and basin models addresses this gap and provides a regional framework within which more detailed work can be carried out in the future. + +The construction and integration of the basin model has involved both the interpretation and building of new faults and stratigraphic surfaces, as well as utilising existing stratigraphic surfaces and structural interpretations from previous studies, predominantly the Otway Basin HSA SEEBASE project by FrOG Tech (Jorand et. al., 2010). + + + + + + + 143.00 + + + 144.00 + + + -39.40 + + + -38.40 + + + + + + + -400 + + + 300 + + + + + + + + + Victoria + + + Otway Basin + + + Torquay Basin + + + + + + + + + + 3D Geological Models + + + + + + + + + + https://creativecommons.org/licenses/by/4.0/ + + + + + + + + + + + + + + + + + + + + + + + + + + AuScope + + + + + + + + + Level 2, 700 Swanston Street + + + + Carlton + + + Victoria + + + 3053 + + + Australia + + + info@auscope.org.au + + + + + + + + + https://ror.org/04s1m4564 + + + + Research Organization Registry (ROR) Entry + + + + + + + + + + + + + + + + + + http://geology.data.vic.gov.au/searchAssistant/document.php?q=parent_id:37363 + + + WWW:LINK-1.0-http--link + + + View Reports + + + + + + + http://geology.data.vic.gov.au/searchAssistant/document.php?q=parent_id:107513 + + + WWW:LINK-1.0-http--link + + + Download Metadata and 3D model data + + + + + + + + + + + http://geomodels.auscope.org/model/otway + + + WWW:LINK-1.0-http--link + + + 3D Geological Model + + + + + + + + + + + \ No newline at end of file diff --git a/tests/resources/iso3_examples/metawal.wallonie.be-catchments.xml b/tests/resources/iso3_examples/metawal.wallonie.be-catchments.xml new file mode 100644 index 000000000..500b9b3e8 --- /dev/null +++ b/tests/resources/iso3_examples/metawal.wallonie.be-catchments.xml @@ -0,0 +1,946 @@ + + + + + + 74f81503-8d39-4ec8-a49a-c76e0cd74946 + + + urn:uuid + + + + + + + + + + + + + + + + + + + + Collection de données thématiques + + + + + + + + + + + + Direction des Eaux souterraines (SPW - Agriculture, Ressources naturelles et Environnement - Département de l'Environnement et de l'Eau - Direction des Eaux souterraines) + + + + + + + +32 (0)81/335923 + + + + + + + + + + veronique.willame@spw.wallonie.be + + + + + + + + + Véronique Willame + + + + + + + + + + + 2023-08-08T07:34:11.366Z + + + + + + + + + + 2019-04-02T12:32:13 + + + + + + + + + + ISO 19115 + + + 2003/Cor 1:2006 + + + + + + + https://metawal.wallonie.be/geonetwork/srv/api/records/74f81503-8d39-4ec8-a49a-c76e0cd74946 + + + + + + + + + + + + EPSG:31370 + + + Belge 1972 / Belgian Lambert 72 (EPSG:31370) + + + + + + + + + + + + + + Protection des captages - Série + + + PROTECT_CAPT + + + + + 2000-01-01 + + + + + + + + + + 2023-07-31 + + + + + + + + + + 2022-11-08 + + + + + + + + + + PROTECT_CAPT + + + BE.SPW.INFRASIG.GINET + + + + + + + 74f81503-8d39-4ec8-a49a-c76e0cd74946 + + + http://geodata.wallonie.be/id/ + + + + + + + Cette collection de données comprend les zones de surveillance arrêtées, de prévention forfaitaires et de prévention arrêtées ou à l'enquête publique autour des captages. + +Cet ensemble de classes d'entités est constitué de trois couches distinctes. +- Les zones de surveillance arrêtées (PROTECT_CAPT__ZONE_III_ARRETEE) +- les zones de prévention arrêtées (PROTECT_CAPT__ZONE_II_ARRETEE) +- les zones de prévention forfaitaires (PROTECT_CAPT__ZONE_II_FORFAIT) + +La classe d'entités "zones de surveillance arrêtées" contient l'ensemble des zones de surveillance délimitées autour de certaines zones de prévention (approuvées par arrêté ministériel) des captages d'eau de distribution publique d'eau potable les plus importants de par les volumes exploités. Actuellement, on en compte cinq sur toute la Wallonie. + +Ces zones de surveillance III sont délimitées par le bassin d'alimentation et le bassin hydrogéologique et fournies à la Directions des Eaux souterraines (SPW - Agriculture, Ressources naturelles et Environnement - Département de l'Environnement et de l'Eau - Direction des Eaux souterraines) sur document papier par le producteur d'eau publique qui a réalisé l'étude. + +La classe d'entités "zones de prévention arrêtées" contient l'ensemble des zones de prévention (autour des captages d'eau souterraine) rapprochées (IIa) et éloignées (IIb) approuvées par arrêté ministériel, à l'enquête publique (en cours ou terminée) ou à l'instruction. Cette couche couvre toute la Wallonie. + +La classe d'entités "zones de prévention forfaitaires" autour des captages contient l'ensemble des zones de prévention forfaitaires (ou théoriques), autour des captages d'eau souterraine de distribution publique. Ces zones de prévention sont provisoires. Elles sont de forme circulaire et seront définies et officialisées, dans le futur, après une étude de délimitation par le producteur d'eau qui l'exploite le captage. + +Les diamètres des zones de prévention forfaitaires rapprochées (IIa) et éloignées (IIb) sont fonction de la nature du terrain. La méthode des distances théoriques tient compte essentiellement de la perméabilité des terrains: zone de prise d'eau (10 m minimum autour des installations), zone de prévention rapprochée IIa (35 m autour des installations de la prise d'eau), zone de prévention IIb (100 m dans les aquifères sableux, 500 m dans les aquifères graveleux et 1000 m dans les aquifères fissurés et karstiques autour de la zone de prévention rapprochée). Cette couche couvre toute la Wallonie. + +Suite au décalage entre la mise à jour des Zones de prévention forfaitaires autour des captages (décembre 2018) et des Zones de prévention arrêtée ou à l'enquête publique autour des captages, mises à jour en mai 2020, et lorsqu’une zone arrêtée est présente pour un captage, c’est celle-ci qui prime au détriment de la zone forfaitaire. La mise à jour des Zones de prévention forfaitaires autour des captages est prévue début juillet 2020. + + + + + + + + + + Helpdesk carto du SPW (SPW - Secrétariat général - SPW Digital - Département Données transversales - Gestion et valorisation de la donnée) + + + + + + + helpdesk.carto@spw.wallonie.be + + + + + + + + + + + + + + + + + + Direction des Eaux souterraines (SPW - Agriculture, Ressources naturelles et Environnement - Département de l'Environnement et de l'Eau - Direction des Eaux souterraines) + + + + + + + +32 (0)81/335923 + + + + + + + + + + veronique.willame@spw.wallonie.be + + + + + + + + + Véronique Willame + + + + + + + + + + + + + + + + Service public de Wallonie (SPW) + + + + + + + + + + https://geoportail.wallonie.be + + + WWW:LINK + + + Géoportail de la Wallonie + + + Géoportail de la Wallonie + + + + + + + + + + + + + + + + + + + + + 10000 + + + + + + + geoscientificInformation + + + inlandWaters + + + + + Région wallonne + + + + + 2.75 + + + 6.50 + + + 49.45 + + + 50.85 + + + + + + + + + https://metawal.wallonie.be/geonetwork/srv/api/records/74f81503-8d39-4ec8-a49a-c76e0cd74946/attachments/PROTECT_CAPT.png + + + protect_capt_pic + + + png + + + + + + + https://metawal.wallonie.be/geonetwork/srv/api/records/74f81503-8d39-4ec8-a49a-c76e0cd74946/attachments/PROTECT_CAPT_s.png + + + protect_capt_pic_small + + + png + + + + + + + Sol et sous-sol + + + Eau + + + + + + + + Thèmes du géoportail wallon + + + + + 2014-01-01 + + + + + + + + + + 2014-06-26 + + + + + + + + + + geonetwork.thesaurus.external.theme.Themes_geoportail_wallon_hierarchy + + + + + + + + + + + eau + + + politique environnementale + + + + + + + + GEMET themes + + + + + 2009-01-01 + + + + + + + + + + 2009-09-22 + + + + + + + + + + geonetwork.thesaurus.external.theme.gemet-theme + + + + + + + + + + + eau potable + + + surveillance de l'environnement + + + surveillance de l'eau + + + eau de surface + + + captage + + + eaux souterraines + + + zone de captage d'eau potable + + + zone protégée de captage d'eau + + + protection de zone de captage de l'eau + + + captage d'eau + + + + + + + + GEMET + + + + + 2009-01-01 + + + + + + + + + + 2009-09-22 + + + + + + + + + + geonetwork.thesaurus.external.theme.gemet + + + + + + + + + + + DGO3_BDREF + + + WalOnMap + + + Extraction_DIG + + + DGO3_CIGALE + + + Reporting INSPIRENO + + + Open Data + + + BDInfraSIGNO + + + PanierTelechargementGeoportail + + + + + + + + Mots-clés InfraSIG + + + + + 2022-10-03 + + + + + + + + + + 2022-10-03 + + + + + + + + + + geonetwork.thesaurus.external.theme.infraSIG + + + + + + + + + + + zone forfaitaire + + + prévention rapprochée + + + prévention éloignée + + + prévention + + + IIa + + + IIb + + + surveillance + + + III + + + + + + + + + + + + + + + + Les conditions générales d'accès s’appliquent. + + + Les conditions générales d'utilisation s'appliquent. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Modèle de données + + + + + http://environnement.wallonie.be/cartosig/Inventaire_Donnees/Modeles_SIG3/PROTECT_CAPT.pdf + + + WWW:LINK + + + application/pdf + + + Modèle de données (document pdf) + + + + + + + + + + + + + + + + + + ESRI Shapefile (.shp) + + + + - + + + + + + + + + + + ESRI File Geodatabase (.fgdb) + + + + 10.x + + + + + + + + + + + + + + + + Service public de Wallonie (SPW) + + + + + + + helpdesk.carto@spw.wallonie.be + + + + + + + + + + + + + Cette ressource est une collection de données. En la commandant, l'ensemble des géodonnées constitutives de cette collection vous sera automatiquement fourni. + +Les instructions pour obtenir une copie physique d’une donnée sont détaillées sur https://geoportail.wallonie.be/telecharger. + + + + + + + + + + + + + + + + Cellule SIG de la DGARNE (SPW - Agriculture, Ressources naturelles et Environnement - Département de l'Étude du milieu naturel et agricole - Direction de la Coordination des Données) + + + + + + + sig.dgarne@spw.wallonie.be + + + + + + + + + + + + + + + + + https://geoportail.wallonie.be/walonmap/#ADU=https://geoservices.wallonie.be/arcgis/rest/services/EAU/PROTECT_CAPT/MapServer + + + WWW:LINK + + + Application WalOnMap - Toute la Wallonie à la carte + + + Application cartographique du Geoportail (WalOnMap) qui permet de découvrir les données géographiques de la Wallonie. + + + + + + + + + + http://geoapps.wallonie.be/Cigale/Public/#CTX=EAUX_SOUT + + + WWW:LINK + + + Protection des eaux souteraines (CIGALE) - Application + + + Application de consultation des principales données cartographiques du SPW Agriculture, Ressources naturelles et Environnements + + + + + + + + + + https://geoservices.wallonie.be/arcgis/rest/services/EAU/PROTECT_CAPT/MapServer + + + ESRI:REST + + + Service de visualisation ESRI-REST + + + Ce service ESRI-REST permet de visualiser le jeu de données "Protection des captages" + + + + + + + + + + https://geoservices.wallonie.be/arcgis/services/EAU/PROTECT_CAPT/MapServer/WMSServer?request=GetCapabilities&service=WMS + + + OGC:WMS + + + Service de visualisation WMS + + + Ce service WMS permet de visualiser le jeu de données "Protection des captages" + + + + + + + + + + http://environnement.wallonie.be/de/eso/atlas/index.htm#4.1a + + + WWW:LINK + + + Site web Etat des nappes d'eau souterraine-chapitre IV.1a. Zones de prévention programmées ou en cours d'étude et IV.1b. Zones de protection définies par arrêté ministériel + + + Pages web Etat des nappes d'eau souterraine-chapitre IV.1a. Zones de prévention programmées ou en cours d'étude +et IV.1b Zones de protection définies par arrêté ministériel + + + + + + + + + + + + + + Pour les zones arrêtées (les zones de surveillance arrêtées et les zones de prévention arrêtées ou à l'enquête publique), les entités sont numérisées sur base des plans papier des producteurs d'eau qui réalisent l'étude de délimitation des zones de prévention. Le dossier est déposé au centre extérieur de la DESO pour réception, vérification, officialisation par arrêté ministériel et publication au Moniteur belge. + +Chaque zone de prévention est numérisée par digitalisation (à la Direction des Eaux souterraines (SPW - Agriculture, Ressources naturelles et Environnement - Département de l'Environnement et de l'Eau - Direction des Eaux souterraines)) sur base de plans papier sur fond IGN et sur Fond cadastral. + +Pour les zones forfaitaires, les entités de cette couche sont des zones de prévention circulaires, générées autour de la position exacte de chacun des captages d'eau de distribution publique d'eau potable (qui n'ont pas encore de zones de prévention approuvée par arrêté ministériel), en créant un buffer de diamètres différents en fonction de la nature de l'aquifère et de la zone rapprochée ou éloignée. + + + + + + + + + + Collection de données thématiques + + + + + + + + \ No newline at end of file diff --git a/tests/resources/iso3_examples/metawal.wallonie.be-srv.xml b/tests/resources/iso3_examples/metawal.wallonie.be-srv.xml new file mode 100644 index 000000000..bf1806d67 --- /dev/null +++ b/tests/resources/iso3_examples/metawal.wallonie.be-srv.xml @@ -0,0 +1,885 @@ + + + + + + 1714cd1e-6685-4dea-a6f4-b51612a15ed0 + + + urn:uuid + + + + + + + + + + + + + + + + + + + + Service + + + + + + + + + + + + Gestion et valorisation de la donnée (SPW - Secrétariat général - SPW Digital - Département Données transversales - Gestion et valorisation de la donnée) + + + + + + + helpdesk.carto@spw.wallonie.be + + + + + + + + + + + + + 2023-12-11T13:33:59.133Z + + + + + + + + + + 2019-04-02T12:32:33 + + + + + + + + + + ISO 19119 + + + 2005/Amd.1:2008 + + + + + + + https://metawal.wallonie.be/geonetwork/srv/api/records/1714cd1e-6685-4dea-a6f4-b51612a15ed0 + + + + + + + + + + + + EPSG:31370 + + + Belge 1972 / Belgian Lambert 72 (EPSG:31370) + + + + + + + + + + + + + + EPSG:4326 + + + WGS 84 (EPSG:4326) + + + + + + + + + + + + + + EPSG:3857 + + + WGS 84 / Pseudo-Mercator (EPSG:3857) + + + + + + + + + + + + + + EPSG:3035 + + + ETRS89 / LAEA Europe (EPSG:3035) + + + + + + + + + + + + + + EPSG:4258 + + + ETRS89 (EPSG:4258) + + + + + + + + + + + + + + EPSG:3812 + + + ETRS89 / Belgian Lambert 2008 (EPSG:3812) + + + + + + + + + + + + + + INSPIRE - Santé et sécurité des personnes en Wallonie (BE) - Service de visualisation WMS + + + + + 2018-03-01 + + + + + + + + + + 1714cd1e-6685-4dea-a6f4-b51612a15ed0 + + + http://geodata.wallonie.be/id/ + + + + + + + Ce service de visualisation WMS INSPIRE permet de consulter les couches de données du thème "Santé et sécurité des personnes" au sein du territoire wallon (Belgique). + +Ce service de visualisation WMS est fourni par le Service public de Wallonie (SPW) et expose les couches de données géographiques constitutives du thème "Santé et sécurité des personnes" de la Directive (Annexe 3.5) sur l'ensemble du territoire wallon. + +Ce service permet donc de visualiser les couches de données sélectionnées comme faisant partie du thème "Bâtiments". Ces couches de données sont présentées "telles quelles", c'est-à-dire dans leur modèle de données initial, non conforme aux spécifications de données définies par la Directive. Les couches de données seront progressivement adaptées aux modèles de donnée commun INSPIRE suivant les spécifications du thème. + +Le service de visualisation est conforme aux spécifications de la Directive INSPIRE en la matière. + + + + + + + + + + Helpdesk carto du SPW (SPW - Secrétariat général - SPW Digital - Département de la Géomatique - Direction de l'Intégration des géodonnées) + + + + + + + helpdesk.carto@spw.wallonie.be + + + + + + + + + + + + + + + + + + Gestion et valorisation de la donnée (SPW - Secrétariat général - SPW Digital - Département Données transversales - Gestion et valorisation de la donnée) + + + + + + + helpdesk.carto@spw.wallonie.be + + + + + + + + + + + + + + + + + + Service public de Wallonie (SPW) + + + + + + + helpdesk.carto@spw.wallonie.be + + + + + + + https://geoportail.wallonie.be + + + WWW:LINK + + + Géoportail de la Wallonie + + + Géoportail de la Wallonie + + + + + + + + + + + + + + + + Région wallonne + + + + + 2.75 + + + 6.51 + + + 49.45 + + + 50.85 + + + + + + + + + https://metawal.wallonie.be/geonetwork/inspire/api/records/1714cd1e-6685-4dea-a6f4-b51612a15ed0/attachments/wms_inspire_20190430.png + + + + + + + Industrie et services + + + Société et activités + + + + + + + + Thèmes du géoportail wallon + + + + + 2014-01-01 + + + + + + + + + + 2014-06-26 + + + + + + + + + + geonetwork.thesaurus.external.theme.Themes_geoportail_wallon_hierarchy + + + + + + + + + + + Santé et sécurité des personnes + + + + + + + + GEMET - INSPIRE themes, version 1.0 + + + + + 2008-01-01 + + + + + + + + + + 2008-06-01 + + + + + + + + + + geonetwork.thesaurus.external.theme.httpinspireeceuropaeutheme-theme + + + + + + + + + + + aspects sociaux, population + + + santé humaine + + + + + + + + GEMET themes + + + + + 2009-01-01 + + + + + + + + + + 2009-09-22 + + + + + + + + + + geonetwork.thesaurus.external.theme.gemet-theme + + + + + + + + + + + santé + + + sécurité + + + + + + + + GEMET + + + + + 2009-01-01 + + + + + + + + + + 2009-09-22 + + + + + + + + + + geonetwork.thesaurus.external.theme.gemet + + + + + + + + + + + Reporting INSPIRE + + + + + + + + Mots-clés InfraSIG + + + + + 2022-10-03 + + + + + + + + + + 2022-10-03 + + + + + + + + + + geonetwork.thesaurus.external.theme.infraSIG + + + + + + + + + + + Human health and safety + + + HH + + + health + + + inspire + + + WMS + + + View + + + validationtest + + + + + + + + + + Service d’accès aux cartes + + + + + + + + Classification of spatial data services + + + + + 2008-01-01 + + + + + + + + + + 2008-12-03 + + + + + + + + + + geonetwork.thesaurus.external.theme.httpinspireeceuropaeumetadatacodelistSpatialDataServiceCategory-SpatialDataServiceCategory + + + + + + + + + + + Régional + + + + + + + + Champ géographique + + + + + 2019-01-01 + + + + + + + + + + 2019-05-22 + + + + + + + + + + geonetwork.thesaurus.external.theme.httpinspireeceuropaeumetadatacodelistSpatialScope-SpatialScope + + + + + + + + + + + + + + No limitations to public access + + + + + + + Conditions d'utilisation spécifiques + + + + + + Les conditions d'utilisation du service sont régies par les conditions d’accès et d’utilisation des services web géographiques de visualisation du Service public de Wallonie. + + + + + view + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Service public de Wallonie (SPW) + + + + + + + helpdesk.carto@spw.wallonie.be + + + + + + + + + + + + + + + + + https://geoservices.test.wallonie.be/geoserver/inspire_hh/ows?service=WMS&version=1.3.0&request=GetCapabilities + + + OGC:WMS + + + INSPIRE Santé et sécurité des personnes - Service de visualisation WMS + + + Adresse de connexion au service de visualisation WMS-Inspire des couches de données du thème "Santé et sécurité des personnes". + + + + + + + + + + + + + + + + + + + + + Service + + + + + + + + + + + + + Règlement (CE) n o 976/2009 de la Commission du 19 octobre 2009 portant modalités d’application de la directive 2007/2/CE du Parlement européen et du Conseil en ce qui concerne les services en réseau + + + + + 2009-10-19 + + + + + + + + + + Voir la spécification référencée + + + true + + + + + + + + + + + + + RÈGLEMENT (UE) N o 1089/2010 DE LA COMMISSION du 23 novembre 2010 portant modalités d'application de la directive 2007/2/CE du Parlement européen et du Conseil en ce qui concerne l'interopérabilité des séries et des services de données géographiques + + + + + 2010-12-08 + + + + + + + + + + Voir la spécification référencée + + + true + + + + + + + + \ No newline at end of file diff --git a/tests/test_csw_geonetwork.py b/tests/test_csw_geonetwork.py index 0768b0f27..ac59c1d27 100644 --- a/tests/test_csw_geonetwork.py +++ b/tests/test_csw_geonetwork.py @@ -16,3 +16,24 @@ def test_csw_geonetwork(): assert c.results.get('returned') > 0 assert c.results.get('nextrecord') > 0 assert c.results.get('matches') > 0 + +SERVICE_URL3 = 'https://metawal.wallonie.be/geonetwork/srv/eng/csw' + +@pytest.mark.skipif(not service_ok(SERVICE_URL3), + reason='service is unreachable') +@pytest.mark.parametrize("esn_in", ['full', 'summary']) +def test_csw_geonetwork_iso3(esn_in): + """ Test retrieving records from Belgian geonetwork, + specifically requesting ISO 19115 Part 3 XML records. + """ + c = CatalogueServiceWeb(SERVICE_URL3) + c.getrecords2(outputschema='http://standards.iso.org/iso/19115/-3/mdb/2.0', + typenames='mdb:MD_Metadata', + esn=esn_in) + + # Valid results were returned + assert c.results.get('returned') > 0 + assert c.results.get('nextrecord') > 0 + assert c.results.get('matches') > 0 + for id in c.records.keys(): + assert c.records[id].identifier == id diff --git a/tests/test_iso3_parsing.py b/tests/test_iso3_parsing.py new file mode 100644 index 000000000..eba56cdf6 --- /dev/null +++ b/tests/test_iso3_parsing.py @@ -0,0 +1,610 @@ +# ================================================================= +# +# Author: Vincent Fazio +# +# Copyright (c) 2023 CSIRO Australia +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# ================================================================= +""" Unit tests for owslib.iso3 +This tests its ability to parse ISO19115-3 XML + +""" + +from pathlib import Path +import pytest + +from owslib.etree import etree + +from owslib.iso3 import (MD_Metadata, SV_ServiceIdentification, PT_Locale, + CI_Date, CI_Responsibility, Keyword, MD_Keywords, + MD_DataIdentification, MD_Distributor, MD_Distribution, + DQ_DataQuality, SV_ServiceIdentification, + CI_OnlineResource, EX_GeographicBoundingBox, + EX_Polygon, EX_BoundingPolygon, EX_Extent, + MD_ReferenceSystem, MD_FeatureCatalogueDescription, + MD_ImageDescription, MD_Band) + +pytestmark = pytest.mark.unit + + +@pytest.fixture +def ns(): + """ Create a V2 namespace + """ + md = MD_Metadata() + return md.namespaces + +def test_md_metadata_empty(): + """ Test empty MD_Metadata + """ + mdb = MD_Metadata() + assert mdb.md == None + assert mdb.xml == None + assert mdb.identifier == None + assert mdb.parentidentifier == None + assert mdb.language == None + assert mdb.dataseturi == None + assert mdb.languagecode == None + assert mdb.datestamp == None + assert mdb.charset == None + assert mdb.hierarchy == None + assert mdb.contact == [] + assert mdb.datetimestamp == None + assert mdb.stdname == None + assert mdb.stdver == None + assert mdb.locales == [] + assert mdb.referencesystem == None + assert mdb.identification == [] + assert mdb.contentinfo == [] + assert mdb.distribution == None + assert mdb.dataquality == None + assert mdb.acquisition == None + +def test_pt_locale_empty(ns): + """ Test empty PT_Locale + """ + loc = PT_Locale(ns) + assert loc.id == None + assert loc.languagecode == None + assert loc.charset == None + +def test_ci_date_empty(ns): + """ Test empty CI_Date + """ + dat = CI_Date(ns) + assert dat.date == None + assert dat.type == None + +def test_ci_responsibility_empty(ns): + """ Test empty + """ + resp = CI_Responsibility(ns) + assert resp.name == None + assert resp.organization == None + assert resp.position == None + assert resp.phone == None + assert resp.fax == None + assert resp.address == None + assert resp.city == None + assert resp.region == None + assert resp.postcode == None + assert resp.country == None + assert resp.email == None + assert resp.onlineresource == None + assert resp.role == None + +def test_keyword_empty(ns): + """ Test empty Keyword + """ + keyw = Keyword(ns) + assert keyw.name == None + assert keyw.url == None + +def test_md_keywords_empty(ns): + """ Test empty MD_Keywords + """ + mdk = MD_Keywords(ns) + assert mdk.keywords == [] + assert mdk.type == None + assert mdk.thesaurus == None + assert mdk.kwdtype_codeList == 'http://standards.iso.org/iso/19115/-3/resources/Codelist/gmxCodelists.xml#MD_KeywordTypeCode' + +def test_md_dataidentification_empty(ns): + """ Test empty MD_DataIdentification + """ + mdi = MD_DataIdentification(ns) + mdi.identtype == None + assert mdi.title == None + assert mdi.alternatetitle == None + assert mdi.uricode == [] + assert mdi.uricodespace == [] + assert mdi.date == [] + assert mdi.datetype == [] + assert mdi.uselimitation == [] + assert mdi.uselimitation_url == [] + assert mdi.accessconstraints == [] + assert mdi.classification == [] # Left empty - no legal classification equivalent + assert mdi.otherconstraints == [] + assert mdi.securityconstraints == [] + assert mdi.useconstraints == [] + assert mdi.denominators == [] + assert mdi.distance == [] + assert mdi.uom == [] + assert mdi.resourcelanguage == [] + assert mdi.resourcelanguagecode == [] + assert mdi.creator == [] + assert mdi.publisher == [] + assert mdi.funder == [] + assert mdi.contributor == [] + assert mdi.edition == None + assert mdi.abstract == None + assert mdi.abstract_url == None + assert mdi.purpose == None + assert mdi.status == None + assert mdi.graphicoverview == [] + assert mdi.contact == [] + assert mdi.keywords == [] + assert mdi.topiccategory == [] + assert mdi.supplementalinformation == None + assert mdi.extent == None + assert mdi.bbox == None + assert mdi.temporalextent_start == None + assert mdi.temporalextent_end == None + assert mdi.spatialrepresentationtype == [] + +def test_md_distributor_empty(ns): + """ Test empty MD_Distributor + """ + mdd = MD_Distributor(ns) + assert mdd.contact == None + assert mdd.online == [] + +def test_md_distribution_empty(ns): + """ Test empty MD_Distribution + """ + mdd = MD_Distribution(ns) + assert mdd.format == None + assert mdd.version == None + assert mdd.distributor == [] + assert mdd.online == [] + +def test_dq_dataquality_empty(ns): + """ Test empty DQ_DataQuality + """ + dqd = DQ_DataQuality(ns) + assert dqd.conformancetitle == [] + assert dqd.conformancedate == [] + assert dqd.conformancedatetype == [] + assert dqd.conformancedegree == [] + assert dqd.lineage == None + assert dqd.lineage_url == None + assert dqd.specificationtitle == None + assert dqd.specificationdate == [] + +def test_sv_serviceidentification_empty(ns): + """ Test empty SV_ServiceIdentification + """ + svs = SV_ServiceIdentification(ns) + assert svs.type == None + assert svs.version == None + assert svs.fees == None + assert svs.couplingtype == None + assert svs.operations == [] + assert svs.operateson == [] + +def test_ci_onlineresource_empty(ns): + """ Test empty CI_OnlineResource + """ + cio = CI_OnlineResource(ns) + assert cio.url == None + assert cio.protocol == None + assert cio.name == None + assert cio.description == None + assert cio.function == None + +def test_ex_geographicboundingbox_empty(ns): + """ Test empty EX_GeographicBoundingBox + """ + exg = EX_GeographicBoundingBox(ns) + assert exg.minx == None + assert exg.maxx == None + assert exg.miny == None + assert exg.maxy == None + +def test_ex_polygon_empty(ns): + """ Test empty EX_Polygon + """ + exp = EX_Polygon(ns) + assert exp.exterior_ring == None + assert exp.interior_rings == [] + +def test_ex_boundingpolygon_empty(ns): + """ Test empty EX_BoundingPolygon + """ + exb = EX_BoundingPolygon(ns) + assert exb.is_extent == None + assert exb.polygons == [] + +def test_ex_extent_empty(ns): + """ Test empty EX_Extent + """ + exe = EX_Extent(ns) + assert exe.boundingBox == None + assert exe.boundingPolygon == None + assert exe.description_code == None + assert exe.vertExtMin == None + assert exe.vertExtMax == None + +def test_md_referencesystem_empty(ns): + """ Test empty MD_ReferenceSystem + """ + mdr = MD_ReferenceSystem(ns) + assert mdr.code == None + assert mdr.codeSpace == None + assert mdr.version == None + +def test_md_featurecataloguedescription_empty(ns): + """ Test empty MD_FeatureCatalogueDescription + """ + mdf = MD_FeatureCatalogueDescription(ns) + assert mdf.xml == None + assert mdf.compliancecode == None + assert mdf.language == [] + assert mdf.includedwithdataset == None + assert mdf.featuretypenames == [] + assert mdf.featurecatalogues == [] + +def test_md_imagedescription_empty(ns): + """ Test empty MD_ImageDescription + """ + mdi = MD_ImageDescription(ns) + assert mdi.type == 'image' + assert mdi.bands == [] + assert mdi.attributedescription == None + assert mdi.cloudcover == None + assert mdi.processinglevel == None + +def test_md_band_empty(ns): + """ Test empty MD_Band + """ + mdb = MD_Band(ns, None) + assert mdb.id == None + assert mdb.units == None + assert mdb.min == None + assert mdb.max == None + + +@pytest.fixture +def bmd(): + """ Create an MD_Metadata instance from Belgian ISO 19115 Part 3 XML sample + + Source: https://metawal.wallonie.be/geonetwork + """ + belgian_sample = str(Path(__file__).parent.parent / "tests" / "resources" / "iso3_examples" / "metawal.wallonie.be-catchments.xml") + with open(belgian_sample, "r") as f_d: + xml_list = f_d.readlines() + xml_str = ''.join(xml_list) + xml_bytes = bytes(xml_str, encoding='utf-8') + exml = etree.fromstring(xml_bytes) + assert exml is not None + return MD_Metadata(exml) + +def test_metadata(bmd): + """ Tests MD_Metadata class + """ + assert bmd is not None + assert bmd.charset == 'utf8' + assert bmd.hierarchy == 'series' + assert bmd.identifier == '74f81503-8d39-4ec8-a49a-c76e0cd74946' + assert bmd.languagecode == 'fre' + assert bmd.locales[0].charset == 'utf8' + assert bmd.locales[0].id == 'FR' + assert bmd.locales[0].languagecode == 'fre' + assert bmd.referencesystem.code == 'EPSG:31370' + assert bmd.stdname == 'ISO 19115' + assert bmd.stdver == '2003/Cor 1:2006' + assert bytes(bmd.contentinfo[0].featurecatalogues[0], 'utf-8') == b'Mod\xc3\xa8le de donn\xc3\xa9es' + assert bmd.dataseturi == 'PROTECT_CAPT' + assert bmd.datestamp == '2023-08-08T07:34:11.366Z' + assert bmd.datestamp == '2023-08-08T07:34:11.366Z' + +def test_responsibility(bmd): + """ Tests CI_Responsibility class as 'pointOfContact' + """ + ct0 = bmd.contact[0] + assert ct0.email == 'veronique.willame@spw.wallonie.be' + assert bytes(ct0.name, 'utf-8') == b'V\xc3\xa9ronique Willame' + assert bytes(ct0.organization, 'utf-8') == b"Direction des Eaux souterraines (SPW - Agriculture, Ressources naturelles et Environnement - D\xc3\xa9partement de l'Environnement et de l'Eau - Direction des Eaux souterraines)" + assert ct0.phone == '+32 (0)81/335923' + assert ct0.role == 'pointOfContact' + +def test_distributor(bmd): + """ Tests MD_Distributor class + """ + distor = bmd.distribution.distributor[0] + assert distor.contact.email == 'helpdesk.carto@spw.wallonie.be' + assert distor.contact.organization == 'Service public de Wallonie (SPW)' + assert distor.contact.role == 'distributor' + +def test_online_distribution(bmd): + """ Tests MD_Distribution class + """ + online = bmd.distribution.online[0] + assert online.description[:65] == 'Application cartographique du Geoportail (WalOnMap) qui permet de' + assert online.function == 'information' + assert bytes(online.name, 'utf=8') == b'Application WalOnMap - Toute la Wallonie \xc3\xa0 la carte' + assert online.protocol == 'WWW:LINK' + assert online.url == 'https://geoportail.wallonie.be/walonmap/#ADU=https://geoservices.wallonie.be/arcgis/rest/services/EAU/PROTECT_CAPT/MapServer' + assert len(bmd.distribution.online) == 5 + assert bmd.distribution.version == '-' + +def test_identification(bmd): + """ Tests MD_DataIdentification class + """ + ident = bmd.identification[0] + assert bytes(ident.abstract[:62], 'utf-8') == b'Cette collection de donn\xc3\xa9es comprend les zones de surveillance' + assert ident.accessconstraints[0] == 'license' + assert ident.uselimitation == [] + assert ident.resourcelanguage == [] + assert ident.resourcelanguagecode[0] == 'fre' + assert ident.alternatetitle == 'PROTECT_CAPT' + assert ident.graphicoverview[0] == 'https://metawal.wallonie.be/geonetwork/srv/api/records/74f81503-8d39-4ec8-a49a-c76e0cd74946/attachments/PROTECT_CAPT.png' + assert ident.graphicoverview[1] == 'https://metawal.wallonie.be/geonetwork/srv/api/records/74f81503-8d39-4ec8-a49a-c76e0cd74946/attachments/PROTECT_CAPT_s.png' + assert ident.identtype =='dataset' + assert bytes(ident.otherconstraints[0], 'utf-8') == b"Les conditions g\xc3\xa9n\xc3\xa9rales d'acc\xc3\xa8s s\xe2\x80\x99appliquent." + assert len(ident.otherconstraints) == 2 + assert ident.spatialrepresentationtype[0] == 'vector' + assert bytes(ident.title, 'utf-8') == b'Protection des captages - S\xc3\xa9rie' + assert ident.topiccategory == ['geoscientificInformation', 'inlandWaters'] + assert ident.uricode == ['PROTECT_CAPT', '74f81503-8d39-4ec8-a49a-c76e0cd74946'] + assert ident.uricodespace == ['BE.SPW.INFRASIG.GINET', 'http://geodata.wallonie.be/id/'] + assert ident.useconstraints[0] == 'license' + +def test_identification_contact(bmd): + """ Tests CI_Responsibility class in indentification section + """ + contact = bmd.identification[0].contact + + assert contact[0].email == 'helpdesk.carto@spw.wallonie.be' + assert contact[0].organization[:21] == 'Helpdesk carto du SPW' + assert contact[0].role == 'pointOfContact' + + assert bytes(contact[1].name, 'utf-8') == b'V\xc3\xa9ronique Willame' + assert contact[1].organization[:31] == 'Direction des Eaux souterraines' + assert contact[1].phone == '+32 (0)81/335923' + assert contact[1].role == 'custodian' + + assert bytes(contact[2].onlineresource.description, 'utf-8') == b'G\xc3\xa9oportail de la Wallonie' + assert contact[2].onlineresource.function == 'information' + assert bytes(contact[2].onlineresource.name, 'utf-8') == b'G\xc3\xa9oportail de la Wallonie' + assert contact[2].onlineresource.protocol == 'WWW:LINK' + assert contact[2].onlineresource.url == 'https://geoportail.wallonie.be' + assert contact[2].organization == 'Service public de Wallonie (SPW)' + assert contact[2].role == 'owner' + +def test_identification_date(bmd): + """ Tests CI_Date class + """ + date = bmd.identification[0].date + assert date[0].date == '2000-01-01' + assert date[0].type == 'creation' + assert date[1].date == '2023-07-31' + assert date[1].type == 'revision' + assert date[2].date == '2022-11-08' + assert date[2].type == 'publication' + +def test_identification_extent(bmd): + """ Tests EX_GeographicBoundingBox class + """ + ident = bmd.identification[0] + assert ident.denominators[0] == '10000' + + assert ident.extent.boundingBox.maxx == '6.50' + assert ident.extent.boundingBox.maxy == '50.85' + assert ident.extent.boundingBox.minx == '2.75' + assert ident.extent.boundingBox.miny == '49.45' + + assert ident.bbox.maxx == '6.50' + assert ident.bbox.maxy == '50.85' + assert ident.bbox.minx == '2.75' + assert ident.bbox.miny == '49.45' + +def test_identification_keywords(bmd): + """ Tests Keywords class + """ + keyw = bmd.identification[0].keywords + assert keyw[0].keywords[0].name == 'Sol et sous-sol' + assert keyw[0].keywords[0].url == 'https://metawal.wallonie.be/thesaurus/theme-geoportail-wallon#SubThemesGeoportailWallon/1030' + assert keyw[0].thesaurus['date'] == '2014-01-01' + assert keyw[0].thesaurus['datetype'] =='publication' + assert keyw[0].thesaurus['title'] == 'Thèmes du géoportail wallon' + assert keyw[0].thesaurus['url'] == 'https://metawal.wallonie.be/thesaurus/theme-geoportail-wallon' + assert keyw[0].type == 'theme' + assert len(keyw[0].keywords) == 2 + assert len(keyw) == 5 + +def test_get_all_contacts(bmd): + """ Test get_all_contacts() + """ + conts = bmd.get_all_contacts() + assert(len(conts) == 3) + assert conts[0].role == 'pointOfContact' + assert conts[1].role == 'custodian' + assert conts[2].role == 'owner' + + +@pytest.fixture +def amd(): + """ + Create an MD_Metadata instance from AuScope 3D Models ISO 19115 Part 3 XML sample + + Source: https://portal.auscope.org.au/geonetwork + """ + aust_sample = str(Path(__file__).parent.parent / "tests" / "resources" / "iso3_examples" / "auscope-3d-model.xml") + with open(aust_sample, "r") as f_d: + xml_list = f_d.readlines() + xml_str = ''.join(xml_list) + xml_bytes = bytes(xml_str, encoding='utf-8') + exml = etree.fromstring(xml_bytes) + assert exml is not None + return MD_Metadata(exml) + +def test_aus(amd): + """ Tests elements that are mostly not present in Belgian catchments sample + """ + assert amd is not None + ident = amd.identification[0] + assert ident.extent.vertExtMax == '300' + assert ident.extent.vertExtMin == '-400' + assert ident.securityconstraints[0] == 'unclassified' + assert ident.uselimitation[0] == 'https://creativecommons.org/licenses/by/4.0/' + assert ident.accessconstraints[0] == 'license' + assert ident.useconstraints[0] == 'license' + assert ident.funder[0].organization == 'AuScope' + assert ident.uricode[0] == 'https://geology.data.vic.gov.au/searchAssistant/document.php?q=parent_id:107513' + + +@pytest.fixture +def smd(): + """ + Create an MD_Metadata instance from Belgian health & safety ISO 19115 Part 3 XML services sample + + Source: https://metawal.wallonie.be/geonetwork + """ + belgian_srv_sample = str(Path(__file__).parent.parent / "tests" / "resources" / "iso3_examples" / "metawal.wallonie.be-srv.xml") + with open(belgian_srv_sample, "r") as f_d: + xml_list = f_d.readlines() + xml_str = ''.join(xml_list) + xml_bytes = bytes(xml_str, encoding='utf-8') + exml = etree.fromstring(xml_bytes) + assert exml is not None + return MD_Metadata(exml) + +def test_service(smd): + """ Tests Belgian health & safety XML service record sample + """ + assert smd is not None + srv_ident = smd.identification[0] + assert(isinstance(srv_ident, SV_ServiceIdentification)) + assert(srv_ident.type == 'view') + assert(srv_ident.couplingtype == 'tight') + assert(len(srv_ident.operations) == 0) + assert(len(srv_ident.operateson) == 11) + rec_2 = srv_ident.operateson[1] + assert(rec_2['uuidref'] == '91f9ebb0-9bea-48b4-8572-da17450913b6') + assert(rec_2['href'] == 'https://metawal.wallonie.be/geonetwork/srv/api/records/91f9ebb0-9bea-48b4-8572-da17450913b6') + rec_9 = srv_ident.operateson[8] + assert(rec_9['uuidref'] == '401a1ac7-7222-4cf8-a7bb-f68090614056') + assert(rec_9['title'] == '[Brouillon] INSPIRE - Bruit des aéroports wallons (Charleroi et Liège) - Plan d’exposition au bruit en Wallonie (BE)') + assert(rec_9['href'] == 'https://metawal.wallonie.be/geonetwork/srv/api/records/401a1ac7-7222-4cf8-a7bb-f68090614056') + +@pytest.fixture +def emd(): + """ + Create a MD_Metadata instance from ESRI ArcGIS ISO 19115 Part 3 XML artificial sample + This uses the older mdb v1 namespaces and has elements not present in other samples + e.g. MD_Band + + Source: https://github.com/Esri/arcgis-pro-metadata-toolkit + """ + arcgis_sample = str(Path(__file__).parent.parent / "tests" / "resources" / "iso3_examples" / "arcgis-sample.xml") + with open(arcgis_sample, "r") as f_d: + xml_list = f_d.readlines() + xml_str = ''.join(xml_list) + xml_bytes = bytes(xml_str, encoding='utf-8') + exml = etree.fromstring(xml_bytes) + assert exml is not None + return MD_Metadata(exml) + +def test_md_featurecataloguedesc(emd): + """ Tests MD_FeatureCatalogueDescription + """ + assert emd is not None + cont_info = emd.contentinfo[0] + assert cont_info.compliancecode == True + assert cont_info.includedwithdataset == True + assert cont_info.featurecatalogues[0] == "Resource > Content > Feature Catalogue > Feature Catalogue Citation > Titles > Title" + assert cont_info.featuretypenames[0] == "Resource > Content > Feature Catalogue > Feature Type > Name" + assert cont_info.language == ['fre'] + +def test_md_imagedescription(emd): + """ Tests MD_ImageDescription and MD_Band + """ + img_desc = emd.contentinfo[1] + assert img_desc.type == 'image' + assert img_desc.attributedescription == "Resource > Content > Image Description > Attribute Description" + assert img_desc.cloudcover == '6.5' + assert img_desc.processinglevel == "Resource > Content > Image Description > Processing Level Code > Code" + assert img_desc.bands[0].id == "Resource > Content > Image Description > Band > Sequence Identifier" + assert img_desc.bands[0].units == "Unified Code of Units of Measure" + assert img_desc.bands[0].max == '255.99' + assert img_desc.bands[0].min == '0.01' + +def test_dq_dataquality(emd): + """ Tests DQ_DataQuality + """ + dq = emd.dataquality + assert dq.conformancetitle[0][:90] == "Resource > Data Quality > Report > Conformance Result > Specification > Titles > Title (Ty" + assert dq.conformancedate[0] == '2010-07-01T00:00:00' + assert dq.conformancedatetype[0] == 'creation' + assert dq.conformancedegree[0] == 'true' + assert dq.lineage == None # emd does not have lineage within DQ_DataQuality + assert dq.lineage_url == None + assert dq.specificationtitle == 'Resource > Data Quality > Report > Conformance Result > Specification > Titles > Title (Type=Domain Consistency)' + assert dq.specificationdate[0] == '2010-07-01T00:00:00' + +def test_md_reference_system(emd): + """ Tests MD_ReferenceSystem + """ + assert emd.referencesystem.code == 'Resource > Spatial Reference > Reference System > Code' + assert emd.referencesystem.codeSpace == 'Resource > Spatial Reference > Reference System > Code Space' + assert emd.referencesystem.version == 'Resource > Spatial Reference > Reference System > Version' + +def test_service2(emd): + """ Tests SV_ServiceIdentification fields not present in other sources + """ + srv_ident = emd.identification[0] + assert(isinstance(srv_ident, SV_ServiceIdentification)) + assert(srv_ident.type == "Resource > Service Details > Service Type > Name") + assert(srv_ident.version == "Resource > Service Details > Service Type Version") + assert(srv_ident.couplingtype == 'loose') + assert(srv_ident.fees == "Resource > Service Details > Access Properties > Fees") + +def test_md_distribution(emd): + """ Test MD_Distribution + """ + contact = emd.distribution.distributor[0].contact + assert contact.address =='Resource > Distribution > Distributor > Contact Information > Address' + assert contact.city =='Resource > Distribution > Distributor > Contact Information > City' + assert contact.email =='Resource > Distribution > Distributor > Contact Information > Email' + assert contact.organization =='Resource > Distribution > Distributor > Organization' + assert contact.phone =='Resource > Distribution > Distributor > Contact Information > Phone' + assert contact.postcode =='Resource > Distribution > Distributor > Contact Information > Postal Code' + assert contact.region =='Resource > Distribution > Distributor > Contact Information > State' + assert contact.role =='distributor' + online = emd.distribution.online[0] + assert online.protocol == 'Resource > Distribution > Digital Transfer Options > Online Resource > Protocol' + assert online.url == 'http://Resource_Distribution_Digital_Transfer_Options_Online_Resource_Linkage' + + diff --git a/tests/test_remote_metadata.py b/tests/test_remote_metadata.py index 92c934417..85c36b052 100644 --- a/tests/test_remote_metadata.py +++ b/tests/test_remote_metadata.py @@ -203,18 +203,16 @@ def read(*args, **kwargs): monkeypatch.setattr( owslib.map.common.WMSCapabilitiesReader, 'read', read) +def openURL(*args, **kwargs): + """ Used to patch the 'openURL' call, returning ISO 19139 XML + """ + return open('tests/resources/csw_dov_getrecordbyid.xml', 'rb') -@pytest.fixture -def mp_remote_md(monkeypatch): - def openURL(*args, **kwargs): - with open('tests/resources/csw_dov_getrecordbyid.xml', 'r') as f: - data = f.read() - if type(data) is not bytes: - data = data.encode('utf-8') - data = etree.fromstring(data) - return data - monkeypatch.setattr(owslib.util, 'openURL', openURL) +def openURL3(*args, **kwargs): + """ Used to patch the 'openURL' call, returning ISO 19115 Part 3 XML + """ + return open('tests/resources/iso3_examples/auscope-3d-model.xml', 'rb') class TestOffline(object): @@ -314,7 +312,11 @@ def test_wfs_110_noremotemd_parse_all(self, mp_wfs_110_nometadata): assert type(mdrecords) is list assert len(mdrecords) == 0 - def test_wfs_110_noremotemd_parse_single(self, mp_wfs_110_nometadata): + @pytest.mark.parametrize("openURL_in, lib_in", [ + (openURL, owslib.iso.MD_Metadata), + (openURL3, owslib.iso3.MD_Metadata), + ]) + def test_wfs_110_noremotemd_parse_single(self, mp_wfs_110_nometadata, monkeypatch, openURL_in, lib_in): """Test the remote metadata parsing for WFS 1.1.0. Tests parsing the remote metadata for a single layer. @@ -328,6 +330,9 @@ def test_wfs_110_noremotemd_parse_single(self, mp_wfs_110_nometadata): Monkeypatch the call to the remote GetCapabilities request. """ + import owslib.feature.wfs110 + monkeypatch.setattr(owslib.feature.wfs110, 'openURL', openURL_in) + wfs = WebFeatureService(url='http://localhost/not_applicable', version='1.1.0', parse_remote_metadata=False) @@ -339,6 +344,9 @@ def test_wfs_110_noremotemd_parse_single(self, mp_wfs_110_nometadata): assert type(mdrecords) is list assert len(mdrecords) == 0 + for m in mdrecords: + assert type(m) is lib_in + def test_wfs_110_noremotemd_parse_none(self, mp_wfs_110_nometadata): """Test the remote metadata parsing for WFS 1.1.0. @@ -362,7 +370,12 @@ def test_wfs_110_noremotemd_parse_none(self, mp_wfs_110_nometadata): assert type(mdrecords) is list assert len(mdrecords) == 0 - def test_wfs_110_remotemd_parse_all(self, mp_wfs_110, mp_remote_md): + + @pytest.mark.parametrize("openURL_in, lib_in", [ + (openURL, owslib.iso.MD_Metadata), + (openURL3, owslib.iso3.MD_Metadata), + ]) + def test_wfs_110_remotemd_parse_all(self, mp_wfs_110, monkeypatch, openURL_in, lib_in): """Test the remote metadata parsing for WFS 1.1.0. Tests parsing the remote metadata for all layers. @@ -374,10 +387,13 @@ def test_wfs_110_remotemd_parse_all(self, mp_wfs_110, mp_remote_md): ---------- mp_wfs_110 : pytest.fixture Monkeypatch the call to the remote GetCapabilities request. - mp_remote_md : pytest.fixture - Monkeypatch the call to the remote metadata. + monkeypatch : pytest.fixture + patch the call to the remote openURL. """ + import owslib.feature.wfs110 + monkeypatch.setattr(owslib.feature.wfs110, 'openURL', openURL_in) + wfs = WebFeatureService(url='http://localhost/not_applicable', version='1.1.0', parse_remote_metadata=True) @@ -389,9 +405,13 @@ def test_wfs_110_remotemd_parse_all(self, mp_wfs_110, mp_remote_md): assert len(mdrecords) == 1 for m in mdrecords: - assert type(m) is owslib.iso.MD_Metadata + assert type(m) is lib_in - def test_wfs_110_remotemd_parse_single(self, mp_wfs_110, mp_remote_md): + @pytest.mark.parametrize("openURL_in, lib_in", [ + (openURL, owslib.iso.MD_Metadata), + (openURL3, owslib.iso3.MD_Metadata), + ]) + def test_wfs_110_remotemd_parse_single(self, mp_wfs_110, monkeypatch, openURL_in, lib_in): """Test the remote metadata parsing for WFS 1.1.0. Tests parsing the remote metadata for a single layer. @@ -403,10 +423,13 @@ def test_wfs_110_remotemd_parse_single(self, mp_wfs_110, mp_remote_md): ---------- mp_wfs_110 : pytest.fixture Monkeypatch the call to the remote GetCapabilities request. - mp_remote_md : pytest.fixture - Monkeypatch the call to the remote metadata. + monkeypatch : pytest.fixture + patch the call to the remote openURL. """ + import owslib.feature.wfs110 + monkeypatch.setattr(owslib.feature.wfs110, 'openURL', openURL_in) + wfs = WebFeatureService(url='http://localhost/not_applicable', version='1.1.0', parse_remote_metadata=False) @@ -419,7 +442,7 @@ def test_wfs_110_remotemd_parse_single(self, mp_wfs_110, mp_remote_md): assert len(mdrecords) == 1 for m in mdrecords: - assert type(m) is owslib.iso.MD_Metadata + assert type(m) is lib_in def test_wfs_110_remotemd_parse_none(self, mp_wfs_110): """Test the remote metadata parsing for WFS 1.1.0. @@ -516,7 +539,12 @@ def test_wfs_200_noremotemd_parse_none(self, mp_wfs_200_nometadata): assert type(mdrecords) is list assert len(mdrecords) == 0 - def test_wfs_200_remotemd_parse_all(self, mp_wfs_200, mp_remote_md): + + @pytest.mark.parametrize("openURL_in, lib_in", [ + (openURL, owslib.iso.MD_Metadata), + (openURL3, owslib.iso3.MD_Metadata), + ]) + def test_wfs_200_remotemd_parse_all(self, mp_wfs_200, monkeypatch, openURL_in, lib_in): """Test the remote metadata parsing for WFS 2.0.0. Tests parsing the remote metadata for all layers. @@ -528,10 +556,13 @@ def test_wfs_200_remotemd_parse_all(self, mp_wfs_200, mp_remote_md): ---------- mp_wfs_200 : pytest.fixture Monkeypatch the call to the remote GetCapabilities request. - mp_remote_md : pytest.fixture - Monkeypatch the call to the remote metadata. + monkeypatch : pytest.fixture + patch the call to the remote openURL. """ + import owslib.feature.wfs200 + monkeypatch.setattr(owslib.feature.wfs200, 'openURL', openURL_in) + wfs = WebFeatureService(url='http://localhost/not_applicable', version='2.0.0', parse_remote_metadata=True) @@ -540,12 +571,17 @@ def test_wfs_200_remotemd_parse_all(self, mp_wfs_200, mp_remote_md): mdrecords = layer.get_metadata() assert type(mdrecords) is list - assert len(mdrecords) == 1 + assert len(mdrecords) == 2 for m in mdrecords: - assert type(m) is owslib.iso.MD_Metadata + assert type(m) is lib_in + - def test_wfs_200_remotemd_parse_single(self, mp_wfs_200, mp_remote_md): + @pytest.mark.parametrize("openURL_in, lib_in", [ + (openURL, owslib.iso.MD_Metadata), + (openURL3, owslib.iso3.MD_Metadata), + ]) + def test_wfs_200_remotemd_parse_single(self, mp_wfs_200, monkeypatch, openURL_in, lib_in): """Test the remote metadata parsing for WFS 2.0.0. Tests parsing the remote metadata for a single layer. @@ -557,10 +593,13 @@ def test_wfs_200_remotemd_parse_single(self, mp_wfs_200, mp_remote_md): ---------- mp_wfs_200 : pytest.fixture Monkeypatch the call to the remote GetCapabilities request. - mp_remote_md : pytest.fixture - Monkeypatch the call to the remote metadata. + monkeypatch : pytest.fixture + patch the call to the remote openURL. """ + import owslib.feature.wfs200 + monkeypatch.setattr(owslib.feature.wfs200, 'openURL', openURL_in) + wfs = WebFeatureService(url='http://localhost/not_applicable', version='2.0.0', parse_remote_metadata=False) @@ -570,10 +609,10 @@ def test_wfs_200_remotemd_parse_single(self, mp_wfs_200, mp_remote_md): mdrecords = layer.get_metadata() assert type(mdrecords) is list - assert len(mdrecords) == 1 + assert len(mdrecords) == 2 for m in mdrecords: - assert type(m) is owslib.iso.MD_Metadata + assert type(m) is lib_in def test_wfs_200_remotemd_parse_none(self, mp_wfs_200): """Test the remote metadata parsing for WFS 2.0.0. @@ -670,7 +709,11 @@ def test_wms_111_noremotemd_parse_none(self, mp_wms_111_nometadata): assert type(mdrecords) is list assert len(mdrecords) == 0 - def test_wms_130_remotemd_parse_all(self, mp_wms_130): + @pytest.mark.parametrize("openURL_in, lib_in", [ + (openURL, owslib.iso.MD_Metadata), + (openURL3, owslib.iso3.MD_Metadata), + ]) + def test_wms_130_remotemd_parse_all(self, mp_wms_130, monkeypatch, openURL_in, lib_in): """Test the remote metadata parsing for WMS 1.3.0. Tests parsing the remote metadata for all layers. @@ -682,8 +725,13 @@ def test_wms_130_remotemd_parse_all(self, mp_wms_130): ---------- mp_wms_130 : pytest.fixture Monkeypatch the call to the remote GetCapabilities request. + monkeypatch : pytest.fixture + patch the call to the remote openURL. """ + import owslib.map.wms130 + monkeypatch.setattr(owslib.map.wms130, 'openURL', openURL_in) + wms = WebMapService(url='http://localhost/not_applicable', version='1.3.0', parse_remote_metadata=True) @@ -695,9 +743,13 @@ def test_wms_130_remotemd_parse_all(self, mp_wms_130): assert len(mdrecords) == 1 for m in mdrecords: - assert type(m) is owslib.iso.MD_Metadata + assert type(m) is lib_in - def test_wms_130_remotemd_parse_single(self, mp_wms_130): + @pytest.mark.parametrize("openURL_in, lib_in", [ + (openURL, owslib.iso.MD_Metadata), + (openURL3, owslib.iso3.MD_Metadata), + ]) + def test_wms_130_remotemd_parse_single(self, mp_wms_130, monkeypatch, openURL_in, lib_in): """Test the remote metadata parsing for WMS 1.3.0. Tests parsing the remote metadata for a single layer. @@ -709,8 +761,13 @@ def test_wms_130_remotemd_parse_single(self, mp_wms_130): ---------- mp_wms_130 : pytest.fixture Monkeypatch the call to the remote GetCapabilities request. + monkeypatch : pytest.fixture + patch the call to the remote openURL. """ + import owslib.map.wms130 + monkeypatch.setattr(owslib.map.wms130, 'openURL', openURL_in) + wms = WebMapService(url='http://localhost/not_applicable', version='1.3.0', parse_remote_metadata=False) @@ -723,7 +780,7 @@ def test_wms_130_remotemd_parse_single(self, mp_wms_130): assert len(mdrecords) == 1 for m in mdrecords: - assert type(m) is owslib.iso.MD_Metadata + assert type(m) is lib_in def test_wms_130_remotemd_parse_none(self, mp_wms_130): """Test the remote metadata parsing for WMS 1.3.0.