Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for ISO 19115 Part 3 XML #933

Merged
merged 43 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
9e01f78
Added initial code to ingest ISO 19115-3 XML
vjf Nov 3, 2023
149f2ef
Merge branch 'master' of https://github.com/geopython/pycsw into ISO1…
vjf Nov 3, 2023
5474b87
Add ISO 19115-3 XML plugin
vjf Nov 28, 2023
311d4ba
Remove old files
vjf Nov 28, 2023
41b5305
Rename profile
vjf Dec 2, 2023
a8cbd89
Add support for output of vertical extent and funder
vjf Dec 2, 2023
8453d14
Fix bugs in output code
vjf Dec 3, 2023
9e39aff
Fix incorrect namespace issue
vjf Dec 3, 2023
7b15880
Merge branch 'master' of https://github.com/geopython/pycsw into ISO1…
vjf Dec 3, 2023
2bf1050
Get address info from contacts when GetCapabilities not supplied
vjf Dec 9, 2023
122f035
Add funder parsing, fix vert extent path
vjf Dec 15, 2023
16072d9
Add initial tests
vjf Dec 17, 2023
fb04399
Fix numerous path bugs
vjf Dec 26, 2023
fdeba1a
Fix errors in constraints and spatial resolution
vjf Dec 27, 2023
9bb1f37
Allowed anchor tag for identifiers
vjf Dec 28, 2023
2c08d47
Updated processing of image description and bands
vjf Dec 28, 2023
95a1ca0
Add functional tests
vjf Dec 28, 2023
638080c
Add funder column to db
vjf Dec 28, 2023
a806bcd
Fix error im gml namespace path
vjf Dec 28, 2023
7128d4b
Merge branch 'master' of https://github.com/geopython/pycsw into ISO1…
vjf Dec 28, 2023
cd405a2
Add mdb unit test
vjf Dec 28, 2023
d29d66a
Cleaning up and adding comments
vjf Dec 29, 2023
bd01953
Add mdb function comments
vjf Dec 29, 2023
64029c6
Add db columns for vert extent
vjf Dec 30, 2023
580eb8a
Add function comments to profile code
vjf Dec 30, 2023
678d382
Fix vert extent and gml paths in tests
vjf Dec 30, 2023
cb9fcfc
Fix services bugs and add tests
vjf Dec 30, 2023
1ba47e1
Update functional tests for new mdb XML test record
vjf Dec 30, 2023
e15a22d
Update comments and readme for ISO 19115 Part 3 XML
vjf Dec 30, 2023
f1a8e89
Add mdb transaction tests
vjf Dec 31, 2023
3d7be63
Fixes:
vjf Dec 31, 2023
50bad2c
Update due to functionality moved to OWSLib
vjf Apr 13, 2024
e20ac93
Merge branch 'master' of https://github.com/geopython/pycsw into ISO1…
vjf Apr 13, 2024
83aed4e
Updated tests and modifications for changed config format
vjf Apr 14, 2024
2e9dfa8
Update expected test responses after OWSLib funder changes
vjf May 13, 2024
93069a3
Restore correct version of cite.db
vjf May 13, 2024
9b449c9
Remove cite.db from pull request
vjf Aug 12, 2024
cd73b8e
Remove unnecessary trailing comma in setup.py
vjf Aug 12, 2024
2126d73
Merge branch 'master' of https://github.com/geopython/pycsw into ISO1…
vjf Aug 12, 2024
30f6cc9
Renamed func. test suites from 'mdb' to 'iso19115p3'
vjf Aug 12, 2024
e2ebdbc
Remove 'Funder' field and tests
vjf Sep 19, 2024
932a339
Correct the profile name in the iso19115p3 test suite
vjf Sep 20, 2024
b85edd6
Update iso19115p3 func. tests for profile name change
vjf Sep 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion pycsw/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def __init__(self, prefix='csw30'):
'gmd': 'http://www.isotc211.org/2005/gmd',
'gml': 'http://www.opengis.net/gml',
'gml32': 'http://www.opengis.net/gml/3.2',
'mdb': 'http://standards.iso.org/iso/19115/-3/mdb/2.0',
'ogc': 'http://www.opengis.net/ogc',
'os': 'http://a9.com/-/spec/opensearch/1.1/',
'ows': 'http://www.opengis.net/ows',
Expand Down Expand Up @@ -118,7 +119,7 @@ def __init__(self, prefix='csw30'):
'pycsw:Metadata': 'metadata',
# raw metadata payload type, xml as default for now
'pycsw:MetadataType': 'metadata_type',
# bag of metadata element and attributes ONLY, no XML tages
# bag of metadata element and attributes ONLY, no XML tags
'pycsw:AnyText': 'anytext',
'pycsw:Language': 'language',
'pycsw:Title': 'title',
Expand All @@ -134,6 +135,8 @@ def __init__(self, prefix='csw30'):
'pycsw:Type': 'type',
# geometry, specified in OGC WKT
'pycsw:BoundingBox': 'wkt_geometry',
'pycsw:VertExtentMin': 'vert_extent_min',
'pycsw:VertExtentMax': 'vert_extent_max',
vjf marked this conversation as resolved.
Show resolved Hide resolved
'pycsw:CRS': 'crs',
'pycsw:AlternateTitle': 'title_alternate',
'pycsw:RevisionDate': 'date_revision',
Expand Down Expand Up @@ -170,6 +173,7 @@ def __init__(self, prefix='csw30'):
'pycsw:Creator': 'creator',
'pycsw:Publisher': 'publisher',
'pycsw:Contributor': 'contributor',
'pycsw:Funder': 'funder',
vjf marked this conversation as resolved.
Show resolved Hide resolved
'pycsw:Relation': 'relation',
'pycsw:Platform': 'platform',
'pycsw:Instrument': 'instrument',
Expand Down
58 changes: 43 additions & 15 deletions pycsw/core/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ def _parse_metadata(context, repos, record):
return [_parse_dc(context, repos, exml)]
elif root == '{%s}DIF' % context.namespaces['dif']: # DIF
pass # TODO
elif root == '{%s}MD_Metadata' % context.namespaces['mdb']:
# ISO 19115p3 XML
return [_parse_iso(context, repos, exml)]
else:
raise RuntimeError('Unsupported metadata format')

Expand Down Expand Up @@ -1361,23 +1364,29 @@ def get_value_by_language(pt_group, language, pt_type='text'):
return recobj

def _parse_iso(context, repos, exml):
""" Parses ISO 19139, ISO 19115p3 """

from owslib.iso import MD_ImageDescription, MD_Metadata, SV_ServiceIdentification
from owslib.iso_che import CHE_MD_Metadata

recobj = repos.dataset()
bbox = None
links = []
mdmeta_ns = 'gmd'

if exml.tag == '{http://www.geocat.ch/2008/che}CHE_MD_Metadata':
md = CHE_MD_Metadata(exml)
elif exml.tag == '{http://standards.iso.org/iso/19115/-3/mdb/2.0}MD_Metadata':
from owslib.iso3 import MD_Metadata
md = MD_Metadata(exml)
mdmeta_ns = 'mdb'
else:
md = MD_Metadata(exml)

md_identification = md.identification[0]

_set(context, recobj, 'pycsw:Identifier', md.identifier)
_set(context, recobj, 'pycsw:Typename', 'gmd:MD_Metadata')
_set(context, recobj, 'pycsw:Typename', f'{mdmeta_ns}:MD_Metadata')
_set(context, recobj, 'pycsw:Schema', context.namespaces['gmd'])
_set(context, recobj, 'pycsw:MdSource', 'local')
_set(context, recobj, 'pycsw:InsertDate', util.get_today_and_now())
Expand All @@ -1391,7 +1400,7 @@ def _parse_iso(context, repos, exml):
_set(context, recobj, 'pycsw:Modified', md.datestamp)
_set(context, recobj, 'pycsw:Source', md.dataseturi)

if md.referencesystem is not None:
if md.referencesystem is not None and md.referencesystem.code is not None:
try:
code_ = 'urn:ogc:def:crs:EPSG::%d' % int(md.referencesystem.code)
except ValueError:
Expand All @@ -1418,11 +1427,21 @@ def _parse_iso(context, repos, exml):
elif len(md_identification.resourcelanguagecode) > 0:
_set(context, recobj, 'pycsw:ResourceLanguage', md_identification.resourcelanguagecode[0])

# Geographic bounding box
if hasattr(md_identification, 'bbox'):
bbox = md_identification.bbox
else:
bbox = None

# Vertical extent of a bounding box
if hasattr(md_identification, 'extent'):
if hasattr(md_identification.extent, 'vertExtMin') and \
md_identification.extent.vertExtMin is not None:
_set(context, recobj, 'pycsw:VertExtentMin', md_identification.extent.vertExtMin)
if hasattr(md_identification.extent, 'vertExtMax') and \
md_identification.extent.vertExtMax is not None:
_set(context, recobj, 'pycsw:VertExtentMax', md_identification.extent.vertExtMax)

if (hasattr(md_identification, 'keywords') and
len(md_identification.keywords) > 0):
all_keywords = [item for sublist in md_identification.keywords for item in sublist.keywords if item is not None]
Expand All @@ -1432,18 +1451,27 @@ def _parse_iso(context, repos, exml):
json.dumps([t for t in md_identification.keywords if t.thesaurus is not None],
default=lambda o: o.__dict__))

# Creator
if (hasattr(md_identification, 'creator') and
len(md_identification.creator) > 0):
all_orgs = set([item.organization for item in md_identification.creator if hasattr(item, 'organization') and item.organization is not None])
_set(context, recobj, 'pycsw:Creator', ';'.join(all_orgs))
# Publisher
if (hasattr(md_identification, 'publisher') and
len(md_identification.publisher) > 0):
all_orgs = set([item.organization for item in md_identification.publisher if hasattr(item, 'organization') and item.organization is not None])
_set(context, recobj, 'pycsw:Publisher', ';'.join(all_orgs))
# Contributor
if (hasattr(md_identification, 'contributor') and
len(md_identification.contributor) > 0):
all_orgs = set([item.organization for item in md_identification.contributor if hasattr(item, 'organization') and item.organization is not None])
_set(context, recobj, 'pycsw:Contributor', ';'.join(all_orgs))
# Funder
if (hasattr(md_identification, 'funder') and
len(md_identification.funder) > 0):
all_orgs = set([item.organization for item in md_identification.funder if hasattr(item, 'organization') and item.organization is not None])
_set(context, recobj, 'pycsw:Funder', ';'.join(all_orgs))


if (hasattr(md_identification, 'contact') and
len(md_identification.contact) > 0):
Expand Down Expand Up @@ -1524,18 +1552,18 @@ def _parse_iso(context, repos, exml):

_set(context, recobj, 'pycsw:Keywords', keywords)

bands = []
for band in ci.bands:
band_info = {
'id': band.id,
'units': band.units,
'min': band.min,
'max': band.max
}
bands.append(band_info)
bands = []
for band in ci.bands:
band_info = {
'id': band.id,
'units': band.units,
'min': band.min,
'max': band.max
}
bands.append(band_info)

if len(bands) > 0:
_set(context, recobj, 'pycsw:Bands', json.dumps(bands))
if len(bands) > 0:
_set(context, recobj, 'pycsw:Bands', json.dumps(bands))

if hasattr(md, 'acquisition') and md.acquisition is not None:
platform = md.acquisition.platforms[0]
Expand All @@ -1554,10 +1582,10 @@ def _parse_iso(context, repos, exml):
if hasattr(md, 'distribution'):
dist_links = []
if hasattr(md.distribution, 'online'):
LOGGER.debug('Scanning for gmd:transferOptions element(s)')
LOGGER.debug(f'Scanning for {mdmeta_ns}:transferOptions element(s)')
dist_links.extend(md.distribution.online)
if hasattr(md.distribution, 'distributor'):
LOGGER.debug('Scanning for gmd:distributorTransferOptions element(s)')
LOGGER.debug(f'Scanning for {mdmeta_ns}:distributorTransferOptions element(s)')
for dist_member in md.distribution.distributor:
dist_links.extend(dist_member.online)
for link in dist_links:
Expand Down
8 changes: 6 additions & 2 deletions pycsw/core/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,6 @@ def __init__(self, database, context, app_root=None, table='records', repo_filte
LOGGER.info('setting repository queryables')
# generate core queryables db and obj bindings
self.queryables = {}

for tname in self.context.model['typenames']:
for qname in self.context.model['typenames'][tname]['queryables']:
self.queryables[qname] = {}
Expand All @@ -234,7 +233,8 @@ def __init__(self, database, context, app_root=None, table='records', repo_filte
# TODO smarter way of doing this
self.queryables['_all'] = {}
for qbl in self.queryables:
self.queryables['_all'].update(self.queryables[qbl])
if qbl != '_all':
self.queryables['_all'].update(self.queryables[qbl])

self.queryables['_all'].update(self.context.md_core_model['mappings'])

Expand Down Expand Up @@ -707,6 +707,7 @@ def setup(database, table, create_sfsql_tables=True, postgis_geometry_column='wk
"""Setup database tables and indexes"""
from sqlalchemy import Column, create_engine, Integer, MetaData, \
Table, Text, Unicode
from sqlalchemy.types import Float
from sqlalchemy.orm import create_session

LOGGER.info('Creating database %s', database)
Expand Down Expand Up @@ -816,6 +817,7 @@ def setup(database, table, create_sfsql_tables=True, postgis_geometry_column='wk
Column('publisher', Text, index=True),
Column('contributor', Text, index=True),
Column('organization', Text, index=True),
Column('funder', Text, index=True),
vjf marked this conversation as resolved.
Show resolved Hide resolved

# security
Column('securityconstraints', Text, index=True),
Expand All @@ -839,6 +841,8 @@ def setup(database, table, create_sfsql_tables=True, postgis_geometry_column='wk
Column('distancevalue', Text, index=True),
Column('distanceuom', Text, index=True),
Column('wkt_geometry', Text),
Column('vert_extent_min', Float, index=True),
vjf marked this conversation as resolved.
Show resolved Hide resolved
Column('vert_extent_max', Float, index=True),

# service
Column('servicetype', Text, index=True),
Expand Down
29 changes: 29 additions & 0 deletions pycsw/plugins/profiles/iso19115p3/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# =================================================================
#
# Authors: Tom Kralidis <tomkralidis@gmail.com>
#
# Copyright (c) 2015 Tom Kralidis
#
# 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.
#
# =================================================================
Loading
Loading