geonode-pycsw 3.0.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geonode_pycsw-3.0.0b2.dist-info/METADATA +73 -0
- geonode_pycsw-3.0.0b2.dist-info/RECORD +382 -0
- geonode_pycsw-3.0.0b2.dist-info/WHEEL +5 -0
- geonode_pycsw-3.0.0b2.dist-info/entry_points.txt +2 -0
- geonode_pycsw-3.0.0b2.dist-info/licenses/LICENSE.txt +26 -0
- geonode_pycsw-3.0.0b2.dist-info/top_level.txt +1 -0
- pycsw/__init__.py +33 -0
- pycsw/core/__init__.py +29 -0
- pycsw/core/admin.py +703 -0
- pycsw/core/config.py +621 -0
- pycsw/core/etree.py +43 -0
- pycsw/core/formats/__init__.py +29 -0
- pycsw/core/formats/fmt_json.py +69 -0
- pycsw/core/log.py +71 -0
- pycsw/core/metadata.py +1996 -0
- pycsw/core/pygeofilter_evaluate.py +83 -0
- pycsw/core/repository.py +941 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/_wrapper.xsd +14 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswAll.xsd +33 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswCommon.xsd +71 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswGetCapabilities.xsd +80 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswGetDomain.xsd +146 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswGetRecordById.xsd +58 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswGetRecords.xsd +391 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswHarvest.xsd +95 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswTransaction.xsd +187 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/cswUnHarvest.xsd +77 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/rec-dcmes.xsd +245 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/rec-dcterms.xsd +101 -0
- pycsw/core/schemas/ogc/cat/csw/3.0/record.xsd +170 -0
- pycsw/core/schemas/ogc/csw/2.0.2/CSW-discovery.xsd +494 -0
- pycsw/core/schemas/ogc/csw/2.0.2/CSW-publication.xsd +242 -0
- pycsw/core/schemas/ogc/csw/2.0.2/rec-dcmes.xsd +199 -0
- pycsw/core/schemas/ogc/csw/2.0.2/rec-dcterms.xsd +94 -0
- pycsw/core/schemas/ogc/csw/2.0.2/record.xsd +138 -0
- pycsw/core/schemas/ogc/filter/1.1.0/expr.xsd +67 -0
- pycsw/core/schemas/ogc/filter/1.1.0/filter.xsd +265 -0
- pycsw/core/schemas/ogc/filter/1.1.0/filterCapabilities.xsd +171 -0
- pycsw/core/schemas/ogc/filter/1.1.0/sort.xsd +46 -0
- pycsw/core/schemas/ogc/filter/2.0/_wrapper.xsd +5 -0
- pycsw/core/schemas/ogc/filter/2.0/expr.xsd +44 -0
- pycsw/core/schemas/ogc/filter/2.0/filter.xsd +396 -0
- pycsw/core/schemas/ogc/filter/2.0/filterAll.xsd +23 -0
- pycsw/core/schemas/ogc/filter/2.0/filterCapabilities.xsd +286 -0
- pycsw/core/schemas/ogc/filter/2.0/query.xsd +70 -0
- pycsw/core/schemas/ogc/filter/2.0/sort.xsd +49 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/basicTypes.xsd +278 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateOperations.xsd +789 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateReferenceSystems.xsd +429 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateSystems.xsd +408 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/coverage.xsd +451 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/dataQuality.xsd +129 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/datums.xsd +484 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/defaultStyle.xsd +454 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/dictionary.xsd +137 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/direction.xsd +72 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/dynamicFeature.xsd +115 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/feature.xsd +199 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/geometryAggregates.xsd +430 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/geometryBasic0d1d.xsd +602 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/geometryBasic2d.xsd +213 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/geometryComplexes.xsd +141 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/geometryPrimitives.xsd +1609 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/gml.xsd +22 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/gmlBase.xsd +294 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/grids.xsd +76 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/measures.xsd +200 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/observation.xsd +96 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/referenceSystems.xsd +211 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/temporal.xsd +332 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/temporalReferenceSystems.xsd +251 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/temporalTopology.xsd +186 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/topology.xsd +459 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/units.xsd +170 -0
- pycsw/core/schemas/ogc/gml/3.1.1/base/valueObjects.xsd +361 -0
- pycsw/core/schemas/ogc/gml/3.1.1/smil/smil20-language.xsd +117 -0
- pycsw/core/schemas/ogc/gml/3.1.1/smil/smil20.xsd +234 -0
- pycsw/core/schemas/ogc/gml/3.2.1/basicTypes.xsd +268 -0
- pycsw/core/schemas/ogc/gml/3.2.1/coordinateOperations.xsd +525 -0
- pycsw/core/schemas/ogc/gml/3.2.1/coordinateReferenceSystems.xsd +373 -0
- pycsw/core/schemas/ogc/gml/3.2.1/coordinateSystems.xsd +297 -0
- pycsw/core/schemas/ogc/gml/3.2.1/coverage.xsd +292 -0
- pycsw/core/schemas/ogc/gml/3.2.1/datums.xsd +287 -0
- pycsw/core/schemas/ogc/gml/3.2.1/defaultStyle.xsd +453 -0
- pycsw/core/schemas/ogc/gml/3.2.1/deprecatedTypes.xsd +1133 -0
- pycsw/core/schemas/ogc/gml/3.2.1/dictionary.xsd +90 -0
- pycsw/core/schemas/ogc/gml/3.2.1/direction.xsd +84 -0
- pycsw/core/schemas/ogc/gml/3.2.1/dynamicFeature.xsd +109 -0
- pycsw/core/schemas/ogc/gml/3.2.1/feature.xsd +94 -0
- pycsw/core/schemas/ogc/gml/3.2.1/geometryAggregates.xsd +197 -0
- pycsw/core/schemas/ogc/gml/3.2.1/geometryBasic0d1d.xsd +277 -0
- pycsw/core/schemas/ogc/gml/3.2.1/geometryBasic2d.xsd +124 -0
- pycsw/core/schemas/ogc/gml/3.2.1/geometryComplexes.xsd +95 -0
- pycsw/core/schemas/ogc/gml/3.2.1/geometryPrimitives.xsd +846 -0
- pycsw/core/schemas/ogc/gml/3.2.1/gml.xsd +20 -0
- pycsw/core/schemas/ogc/gml/3.2.1/gmlBase.xsd +185 -0
- pycsw/core/schemas/ogc/gml/3.2.1/grids.xsd +64 -0
- pycsw/core/schemas/ogc/gml/3.2.1/measures.xsd +68 -0
- pycsw/core/schemas/ogc/gml/3.2.1/observation.xsd +95 -0
- pycsw/core/schemas/ogc/gml/3.2.1/referenceSystems.xsd +70 -0
- pycsw/core/schemas/ogc/gml/3.2.1/temporal.xsd +269 -0
- pycsw/core/schemas/ogc/gml/3.2.1/temporalReferenceSystems.xsd +189 -0
- pycsw/core/schemas/ogc/gml/3.2.1/temporalTopology.xsd +119 -0
- pycsw/core/schemas/ogc/gml/3.2.1/topology.xsd +386 -0
- pycsw/core/schemas/ogc/gml/3.2.1/units.xsd +162 -0
- pycsw/core/schemas/ogc/gml/3.2.1/valueObjects.xsd +205 -0
- pycsw/core/schemas/ogc/ogcapi/records/part1/1.0/ogcapi-records-1.yaml +932 -0
- pycsw/core/schemas/ogc/ows/1.0.0/ows19115subset.xsd +222 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsAll.xsd +20 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsCommon.xsd +155 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsDataIdentification.xsd +112 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsExceptionReport.xsd +67 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsGetCapabilities.xsd +108 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsOperationsMetadata.xsd +161 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsServiceIdentification.xsd +55 -0
- pycsw/core/schemas/ogc/ows/1.0.0/owsServiceProvider.xsd +46 -0
- pycsw/core/schemas/ogc/ows/1.1.0/ows19115subset.xsd +236 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsAll.xsd +23 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsCommon.xsd +158 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsContents.xsd +87 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsDataIdentification.xsd +128 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsDomainType.xsd +280 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsExceptionReport.xsd +77 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsGetCapabilities.xsd +113 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsGetResourceByID.xsd +52 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsInputOutputData.xsd +60 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsManifest.xsd +125 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsOperationsMetadata.xsd +141 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsServiceIdentification.xsd +61 -0
- pycsw/core/schemas/ogc/ows/1.1.0/owsServiceProvider.xsd +48 -0
- pycsw/core/schemas/ogc/ows/2.0/ows19115subset.xsd +364 -0
- pycsw/core/schemas/ogc/ows/2.0/owsAdditionalParameters.xsd +114 -0
- pycsw/core/schemas/ogc/ows/2.0/owsAll.xsd +29 -0
- pycsw/core/schemas/ogc/ows/2.0/owsCommon.xsd +275 -0
- pycsw/core/schemas/ogc/ows/2.0/owsContents.xsd +163 -0
- pycsw/core/schemas/ogc/ows/2.0/owsDataIdentification.xsd +202 -0
- pycsw/core/schemas/ogc/ows/2.0/owsDomainType.xsd +388 -0
- pycsw/core/schemas/ogc/ows/2.0/owsExceptionReport.xsd +126 -0
- pycsw/core/schemas/ogc/ows/2.0/owsGetCapabilities.xsd +220 -0
- pycsw/core/schemas/ogc/ows/2.0/owsGetResourceByID.xsd +83 -0
- pycsw/core/schemas/ogc/ows/2.0/owsInputOutputData.xsd +98 -0
- pycsw/core/schemas/ogc/ows/2.0/owsManifest.xsd +181 -0
- pycsw/core/schemas/ogc/ows/2.0/owsOperationsMetadata.xsd +234 -0
- pycsw/core/schemas/ogc/ows/2.0/owsServiceIdentification.xsd +98 -0
- pycsw/core/schemas/ogc/ows/2.0/owsServiceProvider.xsd +64 -0
- pycsw/core/schemas/w3c/1999/xlink.xsd +271 -0
- pycsw/core/schemas/w3c/2001/xml.xsd +287 -0
- pycsw/core/util.py +552 -0
- pycsw/oaipmh.py +311 -0
- pycsw/ogc/__init__.py +29 -0
- pycsw/ogc/api/__init__.py +28 -0
- pycsw/ogc/api/oapi.py +558 -0
- pycsw/ogc/api/records.py +1414 -0
- pycsw/ogc/api/templates/_base.html +76 -0
- pycsw/ogc/api/templates/collection.html +44 -0
- pycsw/ogc/api/templates/collections.html +45 -0
- pycsw/ogc/api/templates/conformance.html +21 -0
- pycsw/ogc/api/templates/exception.html +8 -0
- pycsw/ogc/api/templates/item.html +228 -0
- pycsw/ogc/api/templates/items.html +337 -0
- pycsw/ogc/api/templates/landing_page.html +27 -0
- pycsw/ogc/api/templates/openapi.html +58 -0
- pycsw/ogc/api/templates/queryables.html +50 -0
- pycsw/ogc/api/templates/stac_items.html +173 -0
- pycsw/ogc/api/templates/static/favicon.ico +0 -0
- pycsw/ogc/api/templates/static/logo-horizontal.png +0 -0
- pycsw/ogc/api/templates/static/logo-vertical-darkbg.png +0 -0
- pycsw/ogc/api/util.py +252 -0
- pycsw/ogc/csw/__init__.py +29 -0
- pycsw/ogc/csw/cql.py +133 -0
- pycsw/ogc/csw/csw2.py +2042 -0
- pycsw/ogc/csw/csw3.py +2193 -0
- pycsw/ogc/fes/__init__.py +29 -0
- pycsw/ogc/fes/fes1.py +433 -0
- pycsw/ogc/fes/fes2.py +451 -0
- pycsw/ogc/gml/__init__.py +29 -0
- pycsw/ogc/gml/gml3.py +240 -0
- pycsw/ogc/gml/gml32.py +243 -0
- pycsw/opensearch.py +857 -0
- pycsw/plugins/__init__.py +29 -0
- pycsw/plugins/outputschemas/__init__.py +31 -0
- pycsw/plugins/outputschemas/atom.py +143 -0
- pycsw/plugins/outputschemas/datacite.py +375 -0
- pycsw/plugins/outputschemas/dif.py +213 -0
- pycsw/plugins/outputschemas/fgdc.py +180 -0
- pycsw/plugins/outputschemas/gm03.py +240 -0
- pycsw/plugins/profiles/__init__.py +29 -0
- pycsw/plugins/profiles/apiso/__init__.py +29 -0
- pycsw/plugins/profiles/apiso/apiso.py +757 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/csw/2.0.2/profiles/apiso/1.0.0/apiso.xsd +13 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/basicTypes.xsd +429 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/gco.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/gcoBase.xsd +61 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/applicationSchema.xsd +42 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/citation.xsd +275 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/constraints.xsd +106 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/content.xsd +188 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/dataQuality.xsd +554 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/distribution.xsd +202 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/extent.xsd +205 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/freeText.xsd +122 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/gmd.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/identification.xsd +348 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/maintenance.xsd +86 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataApplication.xsd +175 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataEntity.xsd +70 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataExtension.xsd +99 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/portrayalCatalogue.xsd +36 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/referenceSystem.xsd +100 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/spatialRepresentation.xsd +237 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/basicTypes.xsd +267 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateOperations.xsd +639 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateReferenceSystems.xsd +526 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateSystems.xsd +401 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coverage.xsd +462 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/datums.xsd +342 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/dictionary.xsd +129 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/direction.xsd +80 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/dynamicFeature.xsd +142 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/feature.xsd +215 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryAggregates.xsd +216 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryBasic0d1d.xsd +304 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryBasic2d.xsd +123 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryComplexes.xsd +89 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryPrimitives.xsd +892 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/gml.xsd +14 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/gmlBase.xsd +305 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/grids.xsd +58 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/measures.xsd +167 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/observation.xsd +90 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/referenceSystems.xsd +69 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporal.xsd +263 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporalReferenceSystems.xsd +183 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporalTopology.xsd +124 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/topology.xsd +372 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/units.xsd +156 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/valueObjects.xsd +212 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/catalogues.xsd +112 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/codelistItem.xsd +168 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/crsItem.xsd +1030 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/extendedTypes.xsd +75 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/gmx.xsd +2 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/gmxUsage.xsd +127 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/uomItem.xsd +162 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gsr/gsr.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gsr/spatialReferencing.xsd +24 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gss/geometry.xsd +35 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gss/gss.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gts/gts.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gts/temporalObjects.xsd +34 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/serviceMetadata.xsd +197 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/serviceModel.xsd +220 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/srv.xsd +13 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/basicTypes.xsd +431 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/gco.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/gcoBase.xsd +63 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/applicationSchema.xsd +43 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/citation.xsd +276 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/constraints.xsd +107 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/content.xsd +190 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/dataQuality.xsd +556 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/distribution.xsd +203 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/extent.xsd +206 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/freeText.xsd +123 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/gmd.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/identification.xsd +349 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/maintenance.xsd +87 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataApplication.xsd +176 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataEntity.xsd +71 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataExtension.xsd +100 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/portrayalCatalogue.xsd +37 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/referenceSystem.xsd +101 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/spatialRepresentation.xsd +238 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/catalogues.xsd +113 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/codelistItem.xsd +169 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/crsItem.xsd +1031 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/extendedTypes.xsd +76 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/gmx.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/gmxUsage.xsd +128 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/uomItem.xsd +163 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gsr/gsr.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gsr/spatialReferencing.xsd +25 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gss/geometry.xsd +36 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gss/gss.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gts/gts.xsd +12 -0
- pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gts/temporalObjects.xsd +35 -0
- pycsw/plugins/profiles/ebrim/__init__.py +29 -0
- pycsw/plugins/profiles/ebrim/ebrim.py +174 -0
- pycsw/plugins/profiles/ebrim/schemas/ogc/csw/2.0.2/profiles/ebrim/1.0/csw-ebrim-iri.xsd +146 -0
- pycsw/plugins/profiles/ebrim/schemas/ogc/csw/2.0.2/profiles/ebrim/1.0/csw-ebrim.xsd +104 -0
- pycsw/plugins/profiles/iso19115p3/__init__.py +29 -0
- pycsw/plugins/profiles/iso19115p3/iso19115p3.py +854 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/cat.xsd +13 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/catalogues.xsd +53 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/codelistItem.xsd +54 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/crsItem.xsd +181 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/uomItem.xsd +38 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/1.0/cit.xsd +7 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/1.0/citation.xsd +514 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/2.0/cit.xsd +10 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/2.0/citation.xsd +523 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gco/1.0/baseTypes2014.xsd +530 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gco/1.0/gco.xsd +16 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/extendedTypes.xsd +92 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/extendedTypes_autoFromShapeChange.xsd +60 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/gcx.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gex/1.0/extent.xsd +241 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gex/1.0/gex.xsd +9 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gmw/1.0/gmlWrapperTypes2014.xsd +159 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gmw/1.0/gmw.xsd +14 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/lan/1.0/lan.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/lan/1.0/language.xsd +140 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/1.0/acquisitionInformationImagery.xsd +622 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/1.0/mac.xsd +11 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/acquisitionInformationImagery.xsd +590 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/event.xsd +108 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/mac.xsd +10 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mas/1.0/applicationSchema.xsd +61 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mas/1.0/mas.xsd +9 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/AbstractCommonClasses.xsd +358 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/commonClasses.xsd +220 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/mcc.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mco/1.0/constraints.xsd +188 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mco/1.0/mco.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md1/1.0/md1.xsd +9 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md1/1.0/metadataWExtendedType.xsd +4 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md2/1.0/md2.xsd +15 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md2/1.0/metadataWithExtensions.xsd +4 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mda/1.0/mda.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mda/1.0/metadataApplication.xsd +200 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/1.0/mdb.xsd +14 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/1.0/metadataBase.xsd +98 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/2.0/mdb.xsd +20 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/2.0/metadataBase.xsd +102 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mds/1.0/mds.xsd +22 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mds/1.0/metadataDataServices.xsd +4 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdt/1.0/mdt.xsd +13 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdt/1.0/metadataTransfer.xsd +111 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mex/1.0/metadataExtension.xsd +156 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mex/1.0/mex.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mmi/1.0/maintenance.xsd +76 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mmi/1.0/mmi.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mpc/1.0/mpc.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mpc/1.0/portrayalCatalogue.xsd +31 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/content.xsd +416 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/contentInformationImagery.xsd +185 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/mrc.xsd +11 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/content.xsd +419 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/contentInformationImagery.xsd +171 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/mrc.xsd +11 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrd/1.0/distribution.xsd +267 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrd/1.0/mrd.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mri/1.0/identification.xsd +517 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mri/1.0/mri.xsd +9 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/lineage.xsd +147 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/lineageImagery.xsd +236 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/mrl.xsd +9 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/lineage.xsd +147 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/lineageImagery.xsd +312 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/mrl.xsd +11 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrs/1.0/mrs.xsd +8 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrs/1.0/referenceSystem.xsd +47 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/msr.xsd +10 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/spatialRepresentation.xsd +375 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/spatialRepresentationImagery.xsd +119 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/msr.xsd +15 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/spatialRepresentation.xsd +381 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/spatialRepresentationImagery.xsd +120 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.0/serviceInformation.xsd +272 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.0/srv.xsd +6 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.1/serviceInformation.xsd +278 -0
- pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.1/srv.xsd +6 -0
- pycsw/plugins/profiles/profile.py +141 -0
- pycsw/plugins/repository/__init__.py +29 -0
- pycsw/plugins/repository/odc/__init__.py +29 -0
- pycsw/plugins/repository/odc/odc.py +153 -0
- pycsw/server.py +938 -0
- pycsw/sru.py +217 -0
- pycsw/stac/__init__.py +29 -0
- pycsw/stac/api.py +571 -0
- pycsw/wsgi.py +233 -0
- pycsw/wsgi_flask.py +345 -0
pycsw/core/repository.py
ADDED
|
@@ -0,0 +1,941 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# =================================================================
|
|
3
|
+
#
|
|
4
|
+
# Authors: Tom Kralidis <tomkralidis@gmail.com>
|
|
5
|
+
# Angelos Tzotsos <tzotsos@gmail.com>
|
|
6
|
+
# Ricardo Garcia Silva <ricardo.garcia.silva@gmail.com>
|
|
7
|
+
#
|
|
8
|
+
# Copyright (c) 2024 Tom Kralidis
|
|
9
|
+
# Copyright (c) 2015 Angelos Tzotsos
|
|
10
|
+
# Copyright (c) 2017 Ricardo Garcia Silva
|
|
11
|
+
#
|
|
12
|
+
# Permission is hereby granted, free of charge, to any person
|
|
13
|
+
# obtaining a copy of this software and associated documentation
|
|
14
|
+
# files (the "Software"), to deal in the Software without
|
|
15
|
+
# restriction, including without limitation the rights to use,
|
|
16
|
+
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
# copies of the Software, and to permit persons to whom the
|
|
18
|
+
# Software is furnished to do so, subject to the following
|
|
19
|
+
# conditions:
|
|
20
|
+
#
|
|
21
|
+
# The above copyright notice and this permission notice shall be
|
|
22
|
+
# included in all copies or substantial portions of the Software.
|
|
23
|
+
#
|
|
24
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
25
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
26
|
+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
27
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
28
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
29
|
+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
30
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
31
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
|
32
|
+
#
|
|
33
|
+
# =================================================================
|
|
34
|
+
|
|
35
|
+
import inspect
|
|
36
|
+
import logging
|
|
37
|
+
import os
|
|
38
|
+
from time import sleep
|
|
39
|
+
|
|
40
|
+
from shapely.wkt import loads
|
|
41
|
+
from shapely.errors import ShapelyError
|
|
42
|
+
|
|
43
|
+
from sqlalchemy import create_engine, func, __version__, select
|
|
44
|
+
from sqlalchemy.exc import OperationalError
|
|
45
|
+
from sqlalchemy.sql import text
|
|
46
|
+
from sqlalchemy.ext.declarative import declarative_base
|
|
47
|
+
from sqlalchemy.orm import create_session
|
|
48
|
+
|
|
49
|
+
from pycsw.core import util
|
|
50
|
+
from pycsw.core.etree import etree
|
|
51
|
+
from pycsw.core.etree import PARSER
|
|
52
|
+
|
|
53
|
+
LOGGER = logging.getLogger(__name__)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Repository(object):
|
|
57
|
+
_engines = {}
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def create_engine(clazz, url):
|
|
61
|
+
'''
|
|
62
|
+
SQL Alchemy engines are thread-safe and simple wrappers for connection pools
|
|
63
|
+
|
|
64
|
+
https://groups.google.com/forum/#!topic/sqlalchemy/t8i3RSKZGb0
|
|
65
|
+
|
|
66
|
+
To reduce startup time we can cache the engine as a class variable in the
|
|
67
|
+
repository object and do database initialization once
|
|
68
|
+
|
|
69
|
+
Engines are memoized by url
|
|
70
|
+
'''
|
|
71
|
+
if url not in clazz._engines:
|
|
72
|
+
LOGGER.info('creating new engine: %s', url)
|
|
73
|
+
engine = create_engine('%s' % url, echo=False, pool_pre_ping=True)
|
|
74
|
+
|
|
75
|
+
# load SQLite query bindings
|
|
76
|
+
# This can be directly bound via events
|
|
77
|
+
# for sqlite < 0.7, we need to to this on a per-connection basis
|
|
78
|
+
if engine.name in ['sqlite', 'sqlite3'] and __version__ >= '0.7':
|
|
79
|
+
from sqlalchemy import event
|
|
80
|
+
|
|
81
|
+
@event.listens_for(engine, "connect")
|
|
82
|
+
def connect(dbapi_connection, connection_rec):
|
|
83
|
+
create_custom_sql_functions(dbapi_connection)
|
|
84
|
+
|
|
85
|
+
clazz._engines[url] = engine
|
|
86
|
+
|
|
87
|
+
return clazz._engines[url]
|
|
88
|
+
|
|
89
|
+
''' Class to interact with underlying repository '''
|
|
90
|
+
def __init__(self, database, context, app_root=None, table='records', repo_filter=None):
|
|
91
|
+
''' Initialize repository '''
|
|
92
|
+
|
|
93
|
+
self.context = context
|
|
94
|
+
self.filter = repo_filter
|
|
95
|
+
self.fts = False
|
|
96
|
+
self.database = database
|
|
97
|
+
self.table = table
|
|
98
|
+
|
|
99
|
+
# Don't use relative paths, this is hack to get around
|
|
100
|
+
# most wsgi restriction...
|
|
101
|
+
if (app_root and self.database.startswith('sqlite:///') and
|
|
102
|
+
not self.database.startswith('sqlite:////')):
|
|
103
|
+
self.database = self.database.replace('sqlite:///', 'sqlite:///%s%s' % (app_root, os.sep))
|
|
104
|
+
|
|
105
|
+
self.engine = Repository.create_engine('%s' % self.database)
|
|
106
|
+
|
|
107
|
+
base = declarative_base(bind=self.engine)
|
|
108
|
+
|
|
109
|
+
LOGGER.info('binding ORM to existing database')
|
|
110
|
+
|
|
111
|
+
self.postgis_geometry_column = None
|
|
112
|
+
|
|
113
|
+
schema_name, table_name = table.rpartition(".")[::2]
|
|
114
|
+
|
|
115
|
+
default_table_args = {
|
|
116
|
+
"autoload": True,
|
|
117
|
+
"schema": schema_name or None
|
|
118
|
+
}
|
|
119
|
+
column_constraints = context.md_core_model.get("column_constraints")
|
|
120
|
+
|
|
121
|
+
# Note: according to the sqlalchemy docs available here:
|
|
122
|
+
#
|
|
123
|
+
# https://docs.sqlalchemy.org/en/14/orm/declarative_tables.html#declarative-table-configuration
|
|
124
|
+
#
|
|
125
|
+
# the __table_args__ attribute can either be a tuple or a dict
|
|
126
|
+
if column_constraints is not None:
|
|
127
|
+
table_args = tuple((*column_constraints, default_table_args))
|
|
128
|
+
else:
|
|
129
|
+
table_args = default_table_args
|
|
130
|
+
|
|
131
|
+
self.dataset = type(
|
|
132
|
+
'dataset',
|
|
133
|
+
(base,),
|
|
134
|
+
{
|
|
135
|
+
"__tablename__": table_name,
|
|
136
|
+
"__table_args__": table_args
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
self.dbtype = self.engine.name
|
|
141
|
+
|
|
142
|
+
self.session = create_session(self.engine)
|
|
143
|
+
self.func = func
|
|
144
|
+
|
|
145
|
+
temp_dbtype = None
|
|
146
|
+
|
|
147
|
+
self.query_mappings = {
|
|
148
|
+
'identifier': self.dataset.identifier,
|
|
149
|
+
'type': self.dataset.type,
|
|
150
|
+
'typename': self.dataset.typename,
|
|
151
|
+
'parentidentifier': self.dataset.parentidentifier,
|
|
152
|
+
'collections': self.dataset.parentidentifier,
|
|
153
|
+
'updated': self.dataset.insert_date,
|
|
154
|
+
'title': self.dataset.title,
|
|
155
|
+
'description': self.dataset.abstract,
|
|
156
|
+
'keywords': self.dataset.keywords,
|
|
157
|
+
'edition': self.dataset.edition,
|
|
158
|
+
'anytext': self.dataset.anytext,
|
|
159
|
+
'bbox': self.dataset.wkt_geometry,
|
|
160
|
+
'date': self.dataset.date,
|
|
161
|
+
'time_begin': self.dataset.time_begin,
|
|
162
|
+
'time_end': self.dataset.time_end,
|
|
163
|
+
'platform': self.dataset.platform,
|
|
164
|
+
'instrument': self.dataset.instrument,
|
|
165
|
+
'sensortype': self.dataset.sensortype
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if self.dbtype == 'postgresql':
|
|
169
|
+
# check if PostgreSQL is enabled with PostGIS 1.x
|
|
170
|
+
try:
|
|
171
|
+
self.session.execute(select([func.postgis_version()]))
|
|
172
|
+
temp_dbtype = 'postgresql+postgis+wkt'
|
|
173
|
+
LOGGER.debug('PostgreSQL+PostGIS1+WKT detected')
|
|
174
|
+
except Exception:
|
|
175
|
+
LOGGER.exception('PostgreSQL+PostGIS1+WKT detection failed')
|
|
176
|
+
|
|
177
|
+
# check if PostgreSQL is enabled with PostGIS 2.x
|
|
178
|
+
try:
|
|
179
|
+
self.session.execute('select(postgis_version())')
|
|
180
|
+
temp_dbtype = 'postgresql+postgis+wkt'
|
|
181
|
+
LOGGER.debug('PostgreSQL+PostGIS2+WKT detected')
|
|
182
|
+
except Exception:
|
|
183
|
+
LOGGER.exception('PostgreSQL+PostGIS2+WKT detection failed')
|
|
184
|
+
|
|
185
|
+
# check if a native PostGIS geometry column exists
|
|
186
|
+
try:
|
|
187
|
+
result = self.session.execute(
|
|
188
|
+
"select f_geometry_column "
|
|
189
|
+
"from geometry_columns "
|
|
190
|
+
"where f_table_name = '%s' "
|
|
191
|
+
"and f_geometry_column != 'wkt_geometry' "
|
|
192
|
+
"limit 1;" % table_name
|
|
193
|
+
)
|
|
194
|
+
row = result.fetchone()
|
|
195
|
+
self.postgis_geometry_column = str(row['f_geometry_column'])
|
|
196
|
+
temp_dbtype = 'postgresql+postgis+native'
|
|
197
|
+
LOGGER.debug('PostgreSQL+PostGIS+Native detected')
|
|
198
|
+
except Exception:
|
|
199
|
+
LOGGER.exception('PostgreSQL+PostGIS+Native not picked up: %s', table_name)
|
|
200
|
+
|
|
201
|
+
# check if a native PostgreSQL FTS GIN index exists
|
|
202
|
+
result = self.session.execute("select relname from pg_class where relname='fts_gin_idx'").scalar()
|
|
203
|
+
self.fts = bool(result)
|
|
204
|
+
LOGGER.debug('PostgreSQL FTS enabled: %r', self.fts)
|
|
205
|
+
|
|
206
|
+
if temp_dbtype is not None:
|
|
207
|
+
LOGGER.debug('%s support detected', temp_dbtype)
|
|
208
|
+
self.dbtype = temp_dbtype
|
|
209
|
+
|
|
210
|
+
if self.dbtype == 'postgresql+postgis+native':
|
|
211
|
+
LOGGER.debug('Adjusting to PostGIS geometry column (wkb_geometry)')
|
|
212
|
+
self.query_mappings['bbox'] = self.dataset.wkb_geometry
|
|
213
|
+
|
|
214
|
+
if self.dbtype in ['sqlite', 'sqlite3']: # load SQLite query bindings
|
|
215
|
+
# <= 0.6 behaviour
|
|
216
|
+
if not __version__ >= '0.7':
|
|
217
|
+
self.connection = self.engine.raw_connection()
|
|
218
|
+
create_custom_sql_functions(self.connection)
|
|
219
|
+
|
|
220
|
+
LOGGER.info('setting repository queryables')
|
|
221
|
+
# generate core queryables db and obj bindings
|
|
222
|
+
self.queryables = {}
|
|
223
|
+
for tname in self.context.model['typenames']:
|
|
224
|
+
for qname in self.context.model['typenames'][tname]['queryables']:
|
|
225
|
+
self.queryables[qname] = {}
|
|
226
|
+
|
|
227
|
+
for qkey, qvalue in \
|
|
228
|
+
self.context.model['typenames'][tname]['queryables'][qname].items():
|
|
229
|
+
self.queryables[qname][qkey] = qvalue
|
|
230
|
+
|
|
231
|
+
# flatten all queryables
|
|
232
|
+
# TODO smarter way of doing this
|
|
233
|
+
self.queryables['_all'] = {}
|
|
234
|
+
for qbl in self.queryables:
|
|
235
|
+
if qbl != '_all':
|
|
236
|
+
self.queryables['_all'].update(self.queryables[qbl])
|
|
237
|
+
|
|
238
|
+
self.queryables['_all'].update(self.context.md_core_model['mappings'])
|
|
239
|
+
|
|
240
|
+
def ping(self, max_tries=10, wait_seconds=10):
|
|
241
|
+
LOGGER.debug(f"Waiting for {self.database}...")
|
|
242
|
+
|
|
243
|
+
if self.database.startswith('sqlite'):
|
|
244
|
+
sql = 'SELECT sqlite_version();'
|
|
245
|
+
else:
|
|
246
|
+
sql = 'SELECT version();'
|
|
247
|
+
|
|
248
|
+
engine = create_engine(self.database)
|
|
249
|
+
current_try = 0
|
|
250
|
+
while current_try < max_tries:
|
|
251
|
+
try:
|
|
252
|
+
engine.execute(sql)
|
|
253
|
+
LOGGER.debug("Database is already up!")
|
|
254
|
+
break
|
|
255
|
+
except OperationalError as err:
|
|
256
|
+
LOGGER.debug(f"Database not responding yet {err}")
|
|
257
|
+
current_try += 1
|
|
258
|
+
sleep(wait_seconds)
|
|
259
|
+
else:
|
|
260
|
+
raise RuntimeError(
|
|
261
|
+
f"Database not responding at {self.database} after {max_tries} tries. ")
|
|
262
|
+
|
|
263
|
+
def rebuild_db_indexes(self):
|
|
264
|
+
"""Rebuild database indexes"""
|
|
265
|
+
|
|
266
|
+
LOGGER.info('Rebuilding database %s, table %s', self.database, self.table)
|
|
267
|
+
connection = self.engine.connect()
|
|
268
|
+
connection.autocommit = True
|
|
269
|
+
connection.execute('REINDEX %s' % self.table)
|
|
270
|
+
connection.close()
|
|
271
|
+
LOGGER.info('Done')
|
|
272
|
+
|
|
273
|
+
def optimize_db(self):
|
|
274
|
+
"""Optimize database"""
|
|
275
|
+
from sqlalchemy.exc import ArgumentError, OperationalError
|
|
276
|
+
|
|
277
|
+
LOGGER.info('Optimizing database %s', self.database)
|
|
278
|
+
connection = self.engine.connect()
|
|
279
|
+
try:
|
|
280
|
+
# PostgreSQL
|
|
281
|
+
connection.execution_options(isolation_level="AUTOCOMMIT")
|
|
282
|
+
connection.execute('VACUUM ANALYZE')
|
|
283
|
+
except (ArgumentError, OperationalError):
|
|
284
|
+
# SQLite
|
|
285
|
+
connection.autocommit = True
|
|
286
|
+
connection.execute('VACUUM')
|
|
287
|
+
connection.execute('ANALYZE')
|
|
288
|
+
finally:
|
|
289
|
+
connection.close()
|
|
290
|
+
LOGGER.info('Done')
|
|
291
|
+
|
|
292
|
+
def _create_values(self, values):
|
|
293
|
+
value_dict = {}
|
|
294
|
+
for num, value in enumerate(values):
|
|
295
|
+
value_dict['pvalue%d' % num] = value
|
|
296
|
+
return value_dict
|
|
297
|
+
|
|
298
|
+
def describe(self):
|
|
299
|
+
''' Derive table columns and types '''
|
|
300
|
+
|
|
301
|
+
type_mappings = {
|
|
302
|
+
'TEXT': 'string',
|
|
303
|
+
'VARCHAR': 'string'
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
properties = {
|
|
307
|
+
'geometry': {
|
|
308
|
+
'$ref': 'https://geojson.org/schema/Polygon.json',
|
|
309
|
+
'x-ogc-role': 'primary-geometry'
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
for i in self.dataset.__table__.columns:
|
|
314
|
+
if i.name in ['anytext', 'metadata', 'metadata_type', 'xml']:
|
|
315
|
+
continue
|
|
316
|
+
|
|
317
|
+
properties[i.name] = {
|
|
318
|
+
'title': i.name
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if i.name == 'identifier':
|
|
322
|
+
properties[i.name]['x-ogc-role'] = 'id'
|
|
323
|
+
|
|
324
|
+
try:
|
|
325
|
+
properties[i.name]['type'] = type_mappings[str(i.type)]
|
|
326
|
+
except Exception as err:
|
|
327
|
+
LOGGER.debug(f'Cannot determine type: {err}')
|
|
328
|
+
|
|
329
|
+
return properties
|
|
330
|
+
|
|
331
|
+
def query_ids(self, ids):
|
|
332
|
+
''' Query by list of identifiers '''
|
|
333
|
+
|
|
334
|
+
column = getattr(self.dataset,
|
|
335
|
+
self.context.md_core_model['mappings']['pycsw:Identifier'])
|
|
336
|
+
|
|
337
|
+
query = self.session.query(self.dataset).filter(column.in_(ids))
|
|
338
|
+
return self._get_repo_filter(query).all()
|
|
339
|
+
|
|
340
|
+
def query_collections(self, filters=None, limit=10):
|
|
341
|
+
''' Query for parent collections '''
|
|
342
|
+
|
|
343
|
+
column = getattr(self.dataset,
|
|
344
|
+
self.context.md_core_model['mappings']['pycsw:ParentIdentifier'])
|
|
345
|
+
|
|
346
|
+
collections = self.session.query(column).distinct()
|
|
347
|
+
|
|
348
|
+
results = self._get_repo_filter(collections).all()
|
|
349
|
+
|
|
350
|
+
ids = [res[0] for res in results if res[0] is not None]
|
|
351
|
+
|
|
352
|
+
column = getattr(self.dataset,
|
|
353
|
+
self.context.md_core_model['mappings']['pycsw:Identifier'])
|
|
354
|
+
|
|
355
|
+
query = self.session.query(self.dataset).filter(column.in_(ids))
|
|
356
|
+
|
|
357
|
+
if filters is not None:
|
|
358
|
+
LOGGER.debug('Querying repository with additional filters')
|
|
359
|
+
return self._get_repo_filter(query).filter(filters).limit(limit).all()
|
|
360
|
+
|
|
361
|
+
return self._get_repo_filter(query).limit(limit).all()
|
|
362
|
+
|
|
363
|
+
def query_domain(self, domain, typenames, domainquerytype='list',
|
|
364
|
+
count=False):
|
|
365
|
+
''' Query by property domain values '''
|
|
366
|
+
|
|
367
|
+
domain_value = getattr(self.dataset, domain)
|
|
368
|
+
|
|
369
|
+
if domainquerytype == 'range':
|
|
370
|
+
LOGGER.info('Generating property name range values')
|
|
371
|
+
query = self.session.query(func.min(domain_value),
|
|
372
|
+
func.max(domain_value))
|
|
373
|
+
else:
|
|
374
|
+
if count:
|
|
375
|
+
LOGGER.info('Generating property name frequency counts')
|
|
376
|
+
query = self.session.query(getattr(self.dataset, domain),
|
|
377
|
+
func.count(domain_value)).group_by(domain_value)
|
|
378
|
+
else:
|
|
379
|
+
query = self.session.query(domain_value).distinct()
|
|
380
|
+
return self._get_repo_filter(query).all()
|
|
381
|
+
|
|
382
|
+
def query_insert(self, direction='max'):
|
|
383
|
+
''' Query to get latest (default) or earliest update to repository '''
|
|
384
|
+
column = getattr(self.dataset,
|
|
385
|
+
self.context.md_core_model['mappings']['pycsw:InsertDate'])
|
|
386
|
+
|
|
387
|
+
if direction == 'min':
|
|
388
|
+
return self._get_repo_filter(self.session.query(func.min(column))).first()[0]
|
|
389
|
+
# else default max
|
|
390
|
+
return self._get_repo_filter(self.session.query(func.max(column))).first()[0]
|
|
391
|
+
|
|
392
|
+
def query_source(self, source):
|
|
393
|
+
''' Query by source '''
|
|
394
|
+
column = getattr(self.dataset,
|
|
395
|
+
self.context.md_core_model['mappings']['pycsw:Source'])
|
|
396
|
+
|
|
397
|
+
query = self.session.query(self.dataset).filter(column == source)
|
|
398
|
+
return self._get_repo_filter(query).all()
|
|
399
|
+
|
|
400
|
+
def query(self, constraint, sortby=None, typenames=None,
|
|
401
|
+
maxrecords=10, startposition=0):
|
|
402
|
+
''' Query records from underlying repository '''
|
|
403
|
+
|
|
404
|
+
# run the raw query and get total
|
|
405
|
+
if 'where' in constraint: # GetRecords with constraint
|
|
406
|
+
LOGGER.debug('constraint detected')
|
|
407
|
+
query = self.session.query(self.dataset).filter(
|
|
408
|
+
text(constraint['where'])).params(self._create_values(constraint['values']))
|
|
409
|
+
else: # GetRecords sans constraint
|
|
410
|
+
LOGGER.debug('No constraint detected')
|
|
411
|
+
query = self.session.query(self.dataset)
|
|
412
|
+
|
|
413
|
+
total = self._get_repo_filter(query).count()
|
|
414
|
+
|
|
415
|
+
if util.ranking_pass: # apply spatial ranking
|
|
416
|
+
# TODO: Check here for dbtype so to extract wkt from postgis native to wkt
|
|
417
|
+
LOGGER.debug('spatial ranking detected')
|
|
418
|
+
LOGGER.debug('Target WKT: %s', getattr(self.dataset, self.context.md_core_model['mappings']['pycsw:BoundingBox']))
|
|
419
|
+
LOGGER.debug('Query WKT: %s', util.ranking_query_geometry)
|
|
420
|
+
query = query.order_by(func.get_spatial_overlay_rank(getattr(self.dataset, self.context.md_core_model['mappings']['pycsw:BoundingBox']), util.ranking_query_geometry).desc())
|
|
421
|
+
# trying to make this wsgi safe
|
|
422
|
+
util.ranking_pass = False
|
|
423
|
+
util.ranking_query_geometry = ''
|
|
424
|
+
|
|
425
|
+
if sortby is not None: # apply sorting
|
|
426
|
+
LOGGER.debug('sorting detected')
|
|
427
|
+
# TODO: Check here for dbtype so to extract wkt from postgis native to wkt
|
|
428
|
+
sortby_column = getattr(self.dataset, sortby['propertyname'])
|
|
429
|
+
|
|
430
|
+
if sortby['order'] == 'DESC': # descending sort
|
|
431
|
+
if 'spatial' in sortby and sortby['spatial']: # spatial sort
|
|
432
|
+
query = query.order_by(func.get_geometry_area(sortby_column).desc())
|
|
433
|
+
else: # aspatial sort
|
|
434
|
+
query = query.order_by(sortby_column.desc())
|
|
435
|
+
else: # ascending sort
|
|
436
|
+
if 'spatial' in sortby and sortby['spatial']: # spatial sort
|
|
437
|
+
query = query.order_by(func.get_geometry_area(sortby_column))
|
|
438
|
+
else: # aspatial sort
|
|
439
|
+
query = query.order_by(sortby_column)
|
|
440
|
+
|
|
441
|
+
# always apply limit and offset
|
|
442
|
+
return [str(total), self._get_repo_filter(query).limit(
|
|
443
|
+
maxrecords).offset(startposition).all()]
|
|
444
|
+
|
|
445
|
+
def insert(self, record, source, insert_date):
|
|
446
|
+
''' Insert a record into the repository '''
|
|
447
|
+
|
|
448
|
+
if isinstance(record.xml, bytes):
|
|
449
|
+
LOGGER.debug('Decoding bytes to unicode')
|
|
450
|
+
record.xml = record.xml.decode()
|
|
451
|
+
|
|
452
|
+
try:
|
|
453
|
+
self.session.begin()
|
|
454
|
+
self.session.add(record)
|
|
455
|
+
self.session.commit()
|
|
456
|
+
except Exception as err:
|
|
457
|
+
LOGGER.exception(err)
|
|
458
|
+
self.session.rollback()
|
|
459
|
+
raise
|
|
460
|
+
|
|
461
|
+
def update(self, record=None, recprops=None, constraint=None):
|
|
462
|
+
''' Update a record in the repository based on identifier '''
|
|
463
|
+
|
|
464
|
+
if record is not None:
|
|
465
|
+
identifier = getattr(record,
|
|
466
|
+
self.context.md_core_model['mappings']['pycsw:Identifier'])
|
|
467
|
+
|
|
468
|
+
if isinstance(record.xml, bytes):
|
|
469
|
+
LOGGER.debug('Decoding bytes to unicode')
|
|
470
|
+
record.xml = record.xml.decode()
|
|
471
|
+
|
|
472
|
+
if recprops is None and constraint is None: # full update
|
|
473
|
+
LOGGER.debug('full update')
|
|
474
|
+
update_dict = dict([(getattr(self.dataset, key),
|
|
475
|
+
getattr(record, key))
|
|
476
|
+
for key in record.__dict__.keys() if key != '_sa_instance_state'])
|
|
477
|
+
|
|
478
|
+
try:
|
|
479
|
+
self.session.begin()
|
|
480
|
+
self._get_repo_filter(self.session.query(self.dataset)).filter_by(
|
|
481
|
+
identifier=identifier).update(update_dict, synchronize_session='fetch')
|
|
482
|
+
self.session.commit()
|
|
483
|
+
except Exception as err:
|
|
484
|
+
self.session.rollback()
|
|
485
|
+
msg = 'Cannot commit to repository'
|
|
486
|
+
LOGGER.exception(msg)
|
|
487
|
+
raise RuntimeError(msg) from err
|
|
488
|
+
else: # update based on record properties
|
|
489
|
+
LOGGER.debug('property based update')
|
|
490
|
+
try:
|
|
491
|
+
rows = rows2 = 0
|
|
492
|
+
self.session.begin()
|
|
493
|
+
for rpu in recprops:
|
|
494
|
+
# update queryable column and XML document via XPath
|
|
495
|
+
if 'xpath' not in rpu['rp']:
|
|
496
|
+
self.session.rollback()
|
|
497
|
+
raise RuntimeError('XPath not found for property %s' % rpu['rp']['name'])
|
|
498
|
+
if 'dbcol' not in rpu['rp']:
|
|
499
|
+
self.session.rollback()
|
|
500
|
+
raise RuntimeError('property not found for XPath %s' % rpu['rp']['name'])
|
|
501
|
+
rows += self._get_repo_filter(self.session.query(self.dataset)).filter(
|
|
502
|
+
text(constraint['where'])).params(self._create_values(constraint['values'])).update({
|
|
503
|
+
getattr(self.dataset, rpu['rp']['dbcol']): rpu['value'],
|
|
504
|
+
'xml': func.update_xpath(str(self.context.namespaces),
|
|
505
|
+
getattr(self.dataset, self.context.md_core_model['mappings']['pycsw:XML']), str(rpu)),
|
|
506
|
+
}, synchronize_session='fetch')
|
|
507
|
+
# then update anytext tokens
|
|
508
|
+
rows2 += self._get_repo_filter(self.session.query(self.dataset)).filter(
|
|
509
|
+
text(constraint['where'])).params(self._create_values(constraint['values'])).update({
|
|
510
|
+
'anytext': func.get_anytext(getattr(
|
|
511
|
+
self.dataset, self.context.md_core_model['mappings']['pycsw:XML']))
|
|
512
|
+
}, synchronize_session='fetch')
|
|
513
|
+
self.session.commit()
|
|
514
|
+
LOGGER.debug('Updated %d records', rows)
|
|
515
|
+
return rows
|
|
516
|
+
except Exception as err:
|
|
517
|
+
self.session.rollback()
|
|
518
|
+
msg = 'Cannot commit to repository'
|
|
519
|
+
LOGGER.exception(msg)
|
|
520
|
+
raise RuntimeError(msg) from err
|
|
521
|
+
|
|
522
|
+
def delete(self, constraint):
|
|
523
|
+
''' Delete a record from the repository '''
|
|
524
|
+
|
|
525
|
+
LOGGER.debug('Deleting record with constraint: %s', constraint)
|
|
526
|
+
try:
|
|
527
|
+
self.session.begin()
|
|
528
|
+
rows = self._get_repo_filter(self.session.query(self.dataset)).filter(
|
|
529
|
+
text(constraint['where'])).params(self._create_values(constraint['values']))
|
|
530
|
+
|
|
531
|
+
parentids = []
|
|
532
|
+
for row in rows: # get ids
|
|
533
|
+
parentids.append(getattr(row,
|
|
534
|
+
self.context.md_core_model['mappings']['pycsw:Identifier']))
|
|
535
|
+
|
|
536
|
+
rows = rows.delete(synchronize_session='fetch')
|
|
537
|
+
|
|
538
|
+
if rows > 0:
|
|
539
|
+
LOGGER.debug('Deleting all child records')
|
|
540
|
+
# delete any child records which had this record as a parent
|
|
541
|
+
rows += self._get_repo_filter(self.session.query(self.dataset)).filter(
|
|
542
|
+
getattr(self.dataset,
|
|
543
|
+
self.context.md_core_model['mappings']['pycsw:ParentIdentifier']).in_(parentids)).delete(
|
|
544
|
+
synchronize_session='fetch')
|
|
545
|
+
|
|
546
|
+
self.session.commit()
|
|
547
|
+
LOGGER.debug('Deleted %d records', rows)
|
|
548
|
+
except Exception as err:
|
|
549
|
+
self.session.rollback()
|
|
550
|
+
msg = 'Cannot commit to repository'
|
|
551
|
+
LOGGER.exception(msg)
|
|
552
|
+
raise RuntimeError(msg) from err
|
|
553
|
+
|
|
554
|
+
return rows
|
|
555
|
+
|
|
556
|
+
def exists(self):
|
|
557
|
+
if self.database.startswith('sqlite:'):
|
|
558
|
+
db_path = self.database.rpartition(":///")[-1]
|
|
559
|
+
if not os.path.isfile(db_path):
|
|
560
|
+
try:
|
|
561
|
+
os.makedirs(os.path.dirname(db_path))
|
|
562
|
+
except OSError as exc:
|
|
563
|
+
if exc.args[0] == 17: # directory already exists
|
|
564
|
+
pass
|
|
565
|
+
|
|
566
|
+
def _get_repo_filter(self, query):
|
|
567
|
+
''' Apply repository wide side filter / mask query '''
|
|
568
|
+
if self.filter is not None:
|
|
569
|
+
return query.filter(text(self.filter))
|
|
570
|
+
return query
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def create_custom_sql_functions(connection):
|
|
574
|
+
"""Register custom functions on the database connection."""
|
|
575
|
+
|
|
576
|
+
inspect_function = inspect.getfullargspec
|
|
577
|
+
|
|
578
|
+
for function_object in [
|
|
579
|
+
query_spatial,
|
|
580
|
+
update_xpath,
|
|
581
|
+
util.get_anytext,
|
|
582
|
+
get_geometry_area,
|
|
583
|
+
get_spatial_overlay_rank
|
|
584
|
+
]:
|
|
585
|
+
argspec = inspect_function(function_object)
|
|
586
|
+
connection.create_function(
|
|
587
|
+
function_object.__name__,
|
|
588
|
+
len(argspec.args),
|
|
589
|
+
function_object
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def query_spatial(bbox_data_wkt, bbox_input_wkt, predicate, distance):
|
|
594
|
+
"""Perform spatial query
|
|
595
|
+
|
|
596
|
+
Parameters
|
|
597
|
+
----------
|
|
598
|
+
bbox_data_wkt: str
|
|
599
|
+
Well-Known Text representation of the data being queried
|
|
600
|
+
bbox_input_wkt: str
|
|
601
|
+
Well-Known Text representation of the input being queried
|
|
602
|
+
predicate: str
|
|
603
|
+
Spatial predicate to use in query
|
|
604
|
+
distance: int or float or str
|
|
605
|
+
Distance parameter for when using either of ``beyond`` or ``dwithin``
|
|
606
|
+
predicates.
|
|
607
|
+
|
|
608
|
+
Returns
|
|
609
|
+
-------
|
|
610
|
+
str
|
|
611
|
+
Either ``true`` or ``false`` depending on the result of the spatial
|
|
612
|
+
query
|
|
613
|
+
|
|
614
|
+
Raises
|
|
615
|
+
------
|
|
616
|
+
RuntimeError
|
|
617
|
+
If an invalid predicate is used
|
|
618
|
+
|
|
619
|
+
"""
|
|
620
|
+
|
|
621
|
+
try:
|
|
622
|
+
bbox1 = loads(bbox_data_wkt.split(';')[-1])
|
|
623
|
+
bbox2 = loads(bbox_input_wkt)
|
|
624
|
+
if predicate == 'bbox':
|
|
625
|
+
result = bbox1.intersects(bbox2)
|
|
626
|
+
elif predicate == 'beyond':
|
|
627
|
+
result = bbox1.distance(bbox2) > float(distance)
|
|
628
|
+
elif predicate == 'contains':
|
|
629
|
+
result = bbox1.contains(bbox2)
|
|
630
|
+
elif predicate == 'crosses':
|
|
631
|
+
result = bbox1.crosses(bbox2)
|
|
632
|
+
elif predicate == 'disjoint':
|
|
633
|
+
result = bbox1.disjoint(bbox2)
|
|
634
|
+
elif predicate == 'dwithin':
|
|
635
|
+
result = bbox1.distance(bbox2) <= float(distance)
|
|
636
|
+
elif predicate == 'equals':
|
|
637
|
+
result = bbox1.equals(bbox2)
|
|
638
|
+
elif predicate == 'intersects':
|
|
639
|
+
result = bbox1.intersects(bbox2)
|
|
640
|
+
elif predicate == 'overlaps':
|
|
641
|
+
result = bbox1.intersects(bbox2) and not bbox1.touches(bbox2)
|
|
642
|
+
elif predicate == 'touches':
|
|
643
|
+
result = bbox1.touches(bbox2)
|
|
644
|
+
elif predicate == 'within':
|
|
645
|
+
result = bbox1.within(bbox2)
|
|
646
|
+
else:
|
|
647
|
+
raise RuntimeError(
|
|
648
|
+
'Invalid spatial query predicate: %s' % predicate)
|
|
649
|
+
except (AttributeError, ValueError, ShapelyError, TypeError):
|
|
650
|
+
result = False
|
|
651
|
+
return "true" if result else "false"
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def update_xpath(nsmap, xml, recprop):
|
|
655
|
+
"""Update XML document XPath values"""
|
|
656
|
+
|
|
657
|
+
if isinstance(xml, bytes) or isinstance(xml, str):
|
|
658
|
+
# serialize to lxml
|
|
659
|
+
xml = etree.fromstring(xml, PARSER)
|
|
660
|
+
|
|
661
|
+
recprop = eval(recprop)
|
|
662
|
+
nsmap = eval(nsmap)
|
|
663
|
+
try:
|
|
664
|
+
nodes = xml.xpath(recprop['rp']['xpath'], namespaces=nsmap)
|
|
665
|
+
if len(nodes) > 0: # matches
|
|
666
|
+
for node1 in nodes:
|
|
667
|
+
if node1.text != recprop['value']: # values differ, update
|
|
668
|
+
node1.text = recprop['value']
|
|
669
|
+
except Exception as err:
|
|
670
|
+
LOGGER.warning('update_xpath error', exc_info=True)
|
|
671
|
+
raise RuntimeError('ERROR: %s' % str(err)) from err
|
|
672
|
+
|
|
673
|
+
return etree.tostring(xml)
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def get_geometry_area(geometry):
|
|
677
|
+
"""Derive area of a given geometry"""
|
|
678
|
+
try:
|
|
679
|
+
if geometry is not None:
|
|
680
|
+
return str(loads(geometry).area)
|
|
681
|
+
return '0'
|
|
682
|
+
except Exception:
|
|
683
|
+
return '0'
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def get_spatial_overlay_rank(target_geometry, query_geometry):
|
|
687
|
+
"""Derive spatial overlay rank for geospatial search as per Lanfear (2006)
|
|
688
|
+
http://pubs.usgs.gov/of/2006/1279/2006-1279.pdf"""
|
|
689
|
+
|
|
690
|
+
# TODO: Add those parameters to config file
|
|
691
|
+
kt = 1.0
|
|
692
|
+
kq = 1.0
|
|
693
|
+
if target_geometry is not None and query_geometry is not None:
|
|
694
|
+
try:
|
|
695
|
+
q_geom = loads(query_geometry)
|
|
696
|
+
t_geom = loads(target_geometry)
|
|
697
|
+
Q = q_geom.area
|
|
698
|
+
T = t_geom.area
|
|
699
|
+
if any(item == 0.0 for item in [Q, T]):
|
|
700
|
+
LOGGER.warning('Geometry has no area')
|
|
701
|
+
return '0'
|
|
702
|
+
X = t_geom.intersection(q_geom).area
|
|
703
|
+
if kt == 1.0 and kq == 1.0:
|
|
704
|
+
LOGGER.debug('Spatial Rank: %s', str((X/Q)*(X/T)))
|
|
705
|
+
return str((X/Q)*(X/T))
|
|
706
|
+
else:
|
|
707
|
+
LOGGER.debug('Spatial Rank: %s', str(((X/Q)**kq)*((X/T)**kt)))
|
|
708
|
+
return str(((X/Q)**kq)*((X/T)**kt))
|
|
709
|
+
except Exception:
|
|
710
|
+
LOGGER.warning('Cannot derive spatial overlay ranking', exc_info=True)
|
|
711
|
+
return '0'
|
|
712
|
+
return '0'
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def setup(database, table, create_sfsql_tables=True, postgis_geometry_column='wkb_geometry',
|
|
716
|
+
extra_columns=[], language='english'):
|
|
717
|
+
"""Setup database tables and indexes"""
|
|
718
|
+
from sqlalchemy import Column, create_engine, Integer, MetaData, \
|
|
719
|
+
Table, Text, Unicode
|
|
720
|
+
from sqlalchemy.types import Float
|
|
721
|
+
from sqlalchemy.orm import create_session
|
|
722
|
+
|
|
723
|
+
LOGGER.info('Creating database %s', database)
|
|
724
|
+
if database.startswith('sqlite:///'):
|
|
725
|
+
_, filepath = database.split('sqlite:///')
|
|
726
|
+
dirname = os.path.dirname(filepath)
|
|
727
|
+
if not os.path.exists(dirname):
|
|
728
|
+
LOGGER.debug('SQLite directory %s does not exist' % dirname)
|
|
729
|
+
try:
|
|
730
|
+
db_path = database.rpartition(":///")[-1]
|
|
731
|
+
os.makedirs(os.path.dirname(db_path))
|
|
732
|
+
except OSError as exc:
|
|
733
|
+
if exc.args[0] == 17: # directory already exists
|
|
734
|
+
LOGGER.debug('Directory already exists')
|
|
735
|
+
|
|
736
|
+
dbase = create_engine(database)
|
|
737
|
+
schema_name, table_name = table.rpartition(".")[::2]
|
|
738
|
+
|
|
739
|
+
mdata = MetaData(dbase, schema=schema_name or None)
|
|
740
|
+
create_postgis_geometry = False
|
|
741
|
+
|
|
742
|
+
# If PostGIS 2.x detected, do not create sfsql tables.
|
|
743
|
+
if dbase.name == 'postgresql':
|
|
744
|
+
try:
|
|
745
|
+
dbsession = create_session(dbase)
|
|
746
|
+
for row in dbsession.execute('select(postgis_lib_version())'):
|
|
747
|
+
postgis_lib_version = row[0]
|
|
748
|
+
create_sfsql_tables = False
|
|
749
|
+
create_postgis_geometry = True
|
|
750
|
+
LOGGER.info('PostGIS %s detected: Skipping SFSQL tables creation', postgis_lib_version)
|
|
751
|
+
except Exception:
|
|
752
|
+
pass
|
|
753
|
+
|
|
754
|
+
if create_sfsql_tables:
|
|
755
|
+
LOGGER.info('Creating table spatial_ref_sys')
|
|
756
|
+
srs = Table(
|
|
757
|
+
'spatial_ref_sys', mdata,
|
|
758
|
+
Column('srid', Integer, nullable=False, primary_key=True),
|
|
759
|
+
Column('auth_name', Text),
|
|
760
|
+
Column('auth_srid', Integer),
|
|
761
|
+
Column('srtext', Text)
|
|
762
|
+
)
|
|
763
|
+
srs.create()
|
|
764
|
+
|
|
765
|
+
i = srs.insert()
|
|
766
|
+
i.execute(srid=4326, auth_name='EPSG', auth_srid=4326, srtext='GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]')
|
|
767
|
+
|
|
768
|
+
LOGGER.info('Creating table geometry_columns')
|
|
769
|
+
geom = Table(
|
|
770
|
+
'geometry_columns', mdata,
|
|
771
|
+
Column('f_table_catalog', Text, nullable=False),
|
|
772
|
+
Column('f_table_schema', Text, nullable=False),
|
|
773
|
+
Column('f_table_name', Text, nullable=False),
|
|
774
|
+
Column('f_geometry_column', Text, nullable=False),
|
|
775
|
+
Column('geometry_type', Integer),
|
|
776
|
+
Column('coord_dimension', Integer),
|
|
777
|
+
Column('srid', Integer, nullable=False),
|
|
778
|
+
Column('geometry_format', Text, nullable=False),
|
|
779
|
+
)
|
|
780
|
+
geom.create()
|
|
781
|
+
|
|
782
|
+
i = geom.insert()
|
|
783
|
+
i.execute(f_table_catalog='public', f_table_schema='public',
|
|
784
|
+
f_table_name=table_name, f_geometry_column='wkt_geometry',
|
|
785
|
+
geometry_type=3, coord_dimension=2,
|
|
786
|
+
srid=4326, geometry_format='WKT')
|
|
787
|
+
|
|
788
|
+
# abstract metadata information model
|
|
789
|
+
|
|
790
|
+
LOGGER.info('Creating table %s', table_name)
|
|
791
|
+
records = Table(
|
|
792
|
+
table_name, mdata,
|
|
793
|
+
# core; nothing happens without these
|
|
794
|
+
Column('identifier', Text, primary_key=True),
|
|
795
|
+
Column('typename', Text,
|
|
796
|
+
default='csw:Record', nullable=False, index=True),
|
|
797
|
+
Column('schema', Text,
|
|
798
|
+
default='http://www.opengis.net/cat/csw/2.0.2', nullable=False,
|
|
799
|
+
index=True),
|
|
800
|
+
Column('mdsource', Text, default='local', nullable=False,
|
|
801
|
+
index=True),
|
|
802
|
+
Column('insert_date', Text, nullable=False, index=True),
|
|
803
|
+
Column('xml', Unicode, nullable=False),
|
|
804
|
+
Column('anytext', Text, nullable=False),
|
|
805
|
+
Column('metadata', Unicode),
|
|
806
|
+
Column('metadata_type', Text, default='application/xml', nullable=False),
|
|
807
|
+
Column('language', Text, index=True),
|
|
808
|
+
|
|
809
|
+
# identification
|
|
810
|
+
Column('type', Text, index=True),
|
|
811
|
+
Column('title', Text, index=True),
|
|
812
|
+
Column('title_alternate', Text, index=True),
|
|
813
|
+
Column('abstract', Text, index=True),
|
|
814
|
+
Column('edition', Text, index=True),
|
|
815
|
+
Column('keywords', Text, index=True),
|
|
816
|
+
Column('keywordstype', Text, index=True),
|
|
817
|
+
Column('themes', Text, index=True),
|
|
818
|
+
Column('parentidentifier', Text, index=True),
|
|
819
|
+
Column('relation', Text, index=True),
|
|
820
|
+
Column('time_begin', Text, index=True),
|
|
821
|
+
Column('time_end', Text, index=True),
|
|
822
|
+
Column('topicategory', Text, index=True),
|
|
823
|
+
Column('resourcelanguage', Text, index=True),
|
|
824
|
+
|
|
825
|
+
# attribution
|
|
826
|
+
Column('creator', Text, index=True),
|
|
827
|
+
Column('publisher', Text, index=True),
|
|
828
|
+
Column('contributor', Text, index=True),
|
|
829
|
+
Column('organization', Text, index=True),
|
|
830
|
+
|
|
831
|
+
# security
|
|
832
|
+
Column('securityconstraints', Text, index=True),
|
|
833
|
+
Column('accessconstraints', Text, index=True),
|
|
834
|
+
Column('otherconstraints', Text, index=True),
|
|
835
|
+
|
|
836
|
+
# date
|
|
837
|
+
Column('date', Text, index=True),
|
|
838
|
+
Column('date_revision', Text, index=True),
|
|
839
|
+
Column('date_creation', Text, index=True),
|
|
840
|
+
Column('date_publication', Text, index=True),
|
|
841
|
+
Column('date_modified', Text, index=True),
|
|
842
|
+
|
|
843
|
+
Column('format', Text, index=True),
|
|
844
|
+
Column('source', Text, index=True),
|
|
845
|
+
|
|
846
|
+
# geospatial
|
|
847
|
+
Column('crs', Text, index=True),
|
|
848
|
+
Column('geodescode', Text, index=True),
|
|
849
|
+
Column('denominator', Text, index=True),
|
|
850
|
+
Column('distancevalue', Text, index=True),
|
|
851
|
+
Column('distanceuom', Text, index=True),
|
|
852
|
+
Column('wkt_geometry', Text),
|
|
853
|
+
Column('vert_extent_min', Float, index=True),
|
|
854
|
+
Column('vert_extent_max', Float, index=True),
|
|
855
|
+
|
|
856
|
+
# service
|
|
857
|
+
Column('servicetype', Text, index=True),
|
|
858
|
+
Column('servicetypeversion', Text, index=True),
|
|
859
|
+
Column('operation', Text, index=True),
|
|
860
|
+
Column('couplingtype', Text, index=True),
|
|
861
|
+
Column('operateson', Text, index=True),
|
|
862
|
+
Column('operatesonidentifier', Text, index=True),
|
|
863
|
+
Column('operatesoname', Text, index=True),
|
|
864
|
+
|
|
865
|
+
# inspire
|
|
866
|
+
Column('degree', Text, index=True),
|
|
867
|
+
Column('classification', Text, index=True),
|
|
868
|
+
Column('conditionapplyingtoaccessanduse', Text, index=True),
|
|
869
|
+
Column('lineage', Text, index=True),
|
|
870
|
+
Column('responsiblepartyrole', Text, index=True),
|
|
871
|
+
Column('specificationtitle', Text, index=True),
|
|
872
|
+
Column('specificationdate', Text, index=True),
|
|
873
|
+
Column('specificationdatetype', Text, index=True),
|
|
874
|
+
|
|
875
|
+
# eo
|
|
876
|
+
Column('platform', Text, index=True),
|
|
877
|
+
Column('instrument', Text, index=True),
|
|
878
|
+
Column('sensortype', Text, index=True),
|
|
879
|
+
Column('cloudcover', Text, index=True),
|
|
880
|
+
# bands: JSON list of dicts with properties: name, units, min, max
|
|
881
|
+
Column('bands', Text, index=True),
|
|
882
|
+
|
|
883
|
+
# distribution
|
|
884
|
+
# links: JSON list of dicts with properties: name, description, protocol, url
|
|
885
|
+
Column('links', Text, index=True),
|
|
886
|
+
# contacts: JSON list of dicts with owslib contact properties, name, organization, email, role, etc.
|
|
887
|
+
Column('contacts', Text, index=True),
|
|
888
|
+
)
|
|
889
|
+
|
|
890
|
+
# add extra columns that may have been passed via extra_columns
|
|
891
|
+
# extra_columns is a list of sqlalchemy.Column objects
|
|
892
|
+
if extra_columns:
|
|
893
|
+
LOGGER.info('Extra column definitions detected')
|
|
894
|
+
for extra_column in extra_columns:
|
|
895
|
+
LOGGER.info('Adding extra column: %s', extra_column)
|
|
896
|
+
records.append_column(extra_column)
|
|
897
|
+
|
|
898
|
+
records.create()
|
|
899
|
+
|
|
900
|
+
conn = dbase.connect()
|
|
901
|
+
|
|
902
|
+
if dbase.name == 'postgresql':
|
|
903
|
+
LOGGER.info('Creating PostgreSQL Free Text Search (FTS) GIN index')
|
|
904
|
+
tsvector_fts = "alter table %s add column anytext_tsvector tsvector" % table_name
|
|
905
|
+
conn.execute(tsvector_fts)
|
|
906
|
+
index_fts = "create index fts_gin_idx on %s using gin(anytext_tsvector)" % table_name
|
|
907
|
+
conn.execute(index_fts)
|
|
908
|
+
# This needs to run if records exist "UPDATE records SET anytext_tsvector = to_tsvector('english', anytext)"
|
|
909
|
+
trigger_fts = "create trigger ftsupdate before insert or update on %s for each row execute procedure tsvector_update_trigger('anytext_tsvector', 'pg_catalog.%s', 'anytext')" % (table_name, language)
|
|
910
|
+
conn.execute(trigger_fts)
|
|
911
|
+
|
|
912
|
+
if dbase.name == 'postgresql' and create_postgis_geometry:
|
|
913
|
+
# create native geometry column within db
|
|
914
|
+
LOGGER.info('Creating native PostGIS geometry column')
|
|
915
|
+
if postgis_lib_version < '2':
|
|
916
|
+
create_column_sql = "SELECT AddGeometryColumn('%s', '%s', 4326, 'POLYGON', 2)" % (table_name, postgis_geometry_column)
|
|
917
|
+
else:
|
|
918
|
+
create_column_sql = "ALTER TABLE %s ADD COLUMN %s geometry(Geometry,4326);" % (table_name, postgis_geometry_column)
|
|
919
|
+
create_insert_update_trigger_sql = '''
|
|
920
|
+
DROP TRIGGER IF EXISTS %(table)s_update_geometry ON %(table)s;
|
|
921
|
+
DROP FUNCTION IF EXISTS %(table)s_update_geometry();
|
|
922
|
+
CREATE FUNCTION %(table)s_update_geometry() RETURNS trigger AS $%(table)s_update_geometry$
|
|
923
|
+
BEGIN
|
|
924
|
+
IF NEW.wkt_geometry IS NULL THEN
|
|
925
|
+
RETURN NEW;
|
|
926
|
+
END IF;
|
|
927
|
+
NEW.%(geometry)s := ST_GeomFromText(NEW.wkt_geometry,4326);
|
|
928
|
+
RETURN NEW;
|
|
929
|
+
END;
|
|
930
|
+
$%(table)s_update_geometry$ LANGUAGE plpgsql;
|
|
931
|
+
|
|
932
|
+
CREATE TRIGGER %(table)s_update_geometry BEFORE INSERT OR UPDATE ON %(table)s
|
|
933
|
+
FOR EACH ROW EXECUTE PROCEDURE %(table)s_update_geometry();
|
|
934
|
+
''' % {'table': table_name, 'geometry': postgis_geometry_column}
|
|
935
|
+
|
|
936
|
+
create_spatial_index_sql = 'CREATE INDEX %(geometry)s_idx ON %(table)s USING GIST (%(geometry)s);' \
|
|
937
|
+
% {'table': table_name, 'geometry': postgis_geometry_column}
|
|
938
|
+
|
|
939
|
+
conn.execute(create_column_sql)
|
|
940
|
+
conn.execute(create_insert_update_trigger_sql)
|
|
941
|
+
conn.execute(create_spatial_index_sql)
|