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.
Files changed (382) hide show
  1. geonode_pycsw-3.0.0b2.dist-info/METADATA +73 -0
  2. geonode_pycsw-3.0.0b2.dist-info/RECORD +382 -0
  3. geonode_pycsw-3.0.0b2.dist-info/WHEEL +5 -0
  4. geonode_pycsw-3.0.0b2.dist-info/entry_points.txt +2 -0
  5. geonode_pycsw-3.0.0b2.dist-info/licenses/LICENSE.txt +26 -0
  6. geonode_pycsw-3.0.0b2.dist-info/top_level.txt +1 -0
  7. pycsw/__init__.py +33 -0
  8. pycsw/core/__init__.py +29 -0
  9. pycsw/core/admin.py +703 -0
  10. pycsw/core/config.py +621 -0
  11. pycsw/core/etree.py +43 -0
  12. pycsw/core/formats/__init__.py +29 -0
  13. pycsw/core/formats/fmt_json.py +69 -0
  14. pycsw/core/log.py +71 -0
  15. pycsw/core/metadata.py +1996 -0
  16. pycsw/core/pygeofilter_evaluate.py +83 -0
  17. pycsw/core/repository.py +941 -0
  18. pycsw/core/schemas/ogc/cat/csw/3.0/_wrapper.xsd +14 -0
  19. pycsw/core/schemas/ogc/cat/csw/3.0/cswAll.xsd +33 -0
  20. pycsw/core/schemas/ogc/cat/csw/3.0/cswCommon.xsd +71 -0
  21. pycsw/core/schemas/ogc/cat/csw/3.0/cswGetCapabilities.xsd +80 -0
  22. pycsw/core/schemas/ogc/cat/csw/3.0/cswGetDomain.xsd +146 -0
  23. pycsw/core/schemas/ogc/cat/csw/3.0/cswGetRecordById.xsd +58 -0
  24. pycsw/core/schemas/ogc/cat/csw/3.0/cswGetRecords.xsd +391 -0
  25. pycsw/core/schemas/ogc/cat/csw/3.0/cswHarvest.xsd +95 -0
  26. pycsw/core/schemas/ogc/cat/csw/3.0/cswTransaction.xsd +187 -0
  27. pycsw/core/schemas/ogc/cat/csw/3.0/cswUnHarvest.xsd +77 -0
  28. pycsw/core/schemas/ogc/cat/csw/3.0/rec-dcmes.xsd +245 -0
  29. pycsw/core/schemas/ogc/cat/csw/3.0/rec-dcterms.xsd +101 -0
  30. pycsw/core/schemas/ogc/cat/csw/3.0/record.xsd +170 -0
  31. pycsw/core/schemas/ogc/csw/2.0.2/CSW-discovery.xsd +494 -0
  32. pycsw/core/schemas/ogc/csw/2.0.2/CSW-publication.xsd +242 -0
  33. pycsw/core/schemas/ogc/csw/2.0.2/rec-dcmes.xsd +199 -0
  34. pycsw/core/schemas/ogc/csw/2.0.2/rec-dcterms.xsd +94 -0
  35. pycsw/core/schemas/ogc/csw/2.0.2/record.xsd +138 -0
  36. pycsw/core/schemas/ogc/filter/1.1.0/expr.xsd +67 -0
  37. pycsw/core/schemas/ogc/filter/1.1.0/filter.xsd +265 -0
  38. pycsw/core/schemas/ogc/filter/1.1.0/filterCapabilities.xsd +171 -0
  39. pycsw/core/schemas/ogc/filter/1.1.0/sort.xsd +46 -0
  40. pycsw/core/schemas/ogc/filter/2.0/_wrapper.xsd +5 -0
  41. pycsw/core/schemas/ogc/filter/2.0/expr.xsd +44 -0
  42. pycsw/core/schemas/ogc/filter/2.0/filter.xsd +396 -0
  43. pycsw/core/schemas/ogc/filter/2.0/filterAll.xsd +23 -0
  44. pycsw/core/schemas/ogc/filter/2.0/filterCapabilities.xsd +286 -0
  45. pycsw/core/schemas/ogc/filter/2.0/query.xsd +70 -0
  46. pycsw/core/schemas/ogc/filter/2.0/sort.xsd +49 -0
  47. pycsw/core/schemas/ogc/gml/3.1.1/base/basicTypes.xsd +278 -0
  48. pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateOperations.xsd +789 -0
  49. pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateReferenceSystems.xsd +429 -0
  50. pycsw/core/schemas/ogc/gml/3.1.1/base/coordinateSystems.xsd +408 -0
  51. pycsw/core/schemas/ogc/gml/3.1.1/base/coverage.xsd +451 -0
  52. pycsw/core/schemas/ogc/gml/3.1.1/base/dataQuality.xsd +129 -0
  53. pycsw/core/schemas/ogc/gml/3.1.1/base/datums.xsd +484 -0
  54. pycsw/core/schemas/ogc/gml/3.1.1/base/defaultStyle.xsd +454 -0
  55. pycsw/core/schemas/ogc/gml/3.1.1/base/dictionary.xsd +137 -0
  56. pycsw/core/schemas/ogc/gml/3.1.1/base/direction.xsd +72 -0
  57. pycsw/core/schemas/ogc/gml/3.1.1/base/dynamicFeature.xsd +115 -0
  58. pycsw/core/schemas/ogc/gml/3.1.1/base/feature.xsd +199 -0
  59. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryAggregates.xsd +430 -0
  60. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryBasic0d1d.xsd +602 -0
  61. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryBasic2d.xsd +213 -0
  62. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryComplexes.xsd +141 -0
  63. pycsw/core/schemas/ogc/gml/3.1.1/base/geometryPrimitives.xsd +1609 -0
  64. pycsw/core/schemas/ogc/gml/3.1.1/base/gml.xsd +22 -0
  65. pycsw/core/schemas/ogc/gml/3.1.1/base/gmlBase.xsd +294 -0
  66. pycsw/core/schemas/ogc/gml/3.1.1/base/grids.xsd +76 -0
  67. pycsw/core/schemas/ogc/gml/3.1.1/base/measures.xsd +200 -0
  68. pycsw/core/schemas/ogc/gml/3.1.1/base/observation.xsd +96 -0
  69. pycsw/core/schemas/ogc/gml/3.1.1/base/referenceSystems.xsd +211 -0
  70. pycsw/core/schemas/ogc/gml/3.1.1/base/temporal.xsd +332 -0
  71. pycsw/core/schemas/ogc/gml/3.1.1/base/temporalReferenceSystems.xsd +251 -0
  72. pycsw/core/schemas/ogc/gml/3.1.1/base/temporalTopology.xsd +186 -0
  73. pycsw/core/schemas/ogc/gml/3.1.1/base/topology.xsd +459 -0
  74. pycsw/core/schemas/ogc/gml/3.1.1/base/units.xsd +170 -0
  75. pycsw/core/schemas/ogc/gml/3.1.1/base/valueObjects.xsd +361 -0
  76. pycsw/core/schemas/ogc/gml/3.1.1/smil/smil20-language.xsd +117 -0
  77. pycsw/core/schemas/ogc/gml/3.1.1/smil/smil20.xsd +234 -0
  78. pycsw/core/schemas/ogc/gml/3.2.1/basicTypes.xsd +268 -0
  79. pycsw/core/schemas/ogc/gml/3.2.1/coordinateOperations.xsd +525 -0
  80. pycsw/core/schemas/ogc/gml/3.2.1/coordinateReferenceSystems.xsd +373 -0
  81. pycsw/core/schemas/ogc/gml/3.2.1/coordinateSystems.xsd +297 -0
  82. pycsw/core/schemas/ogc/gml/3.2.1/coverage.xsd +292 -0
  83. pycsw/core/schemas/ogc/gml/3.2.1/datums.xsd +287 -0
  84. pycsw/core/schemas/ogc/gml/3.2.1/defaultStyle.xsd +453 -0
  85. pycsw/core/schemas/ogc/gml/3.2.1/deprecatedTypes.xsd +1133 -0
  86. pycsw/core/schemas/ogc/gml/3.2.1/dictionary.xsd +90 -0
  87. pycsw/core/schemas/ogc/gml/3.2.1/direction.xsd +84 -0
  88. pycsw/core/schemas/ogc/gml/3.2.1/dynamicFeature.xsd +109 -0
  89. pycsw/core/schemas/ogc/gml/3.2.1/feature.xsd +94 -0
  90. pycsw/core/schemas/ogc/gml/3.2.1/geometryAggregates.xsd +197 -0
  91. pycsw/core/schemas/ogc/gml/3.2.1/geometryBasic0d1d.xsd +277 -0
  92. pycsw/core/schemas/ogc/gml/3.2.1/geometryBasic2d.xsd +124 -0
  93. pycsw/core/schemas/ogc/gml/3.2.1/geometryComplexes.xsd +95 -0
  94. pycsw/core/schemas/ogc/gml/3.2.1/geometryPrimitives.xsd +846 -0
  95. pycsw/core/schemas/ogc/gml/3.2.1/gml.xsd +20 -0
  96. pycsw/core/schemas/ogc/gml/3.2.1/gmlBase.xsd +185 -0
  97. pycsw/core/schemas/ogc/gml/3.2.1/grids.xsd +64 -0
  98. pycsw/core/schemas/ogc/gml/3.2.1/measures.xsd +68 -0
  99. pycsw/core/schemas/ogc/gml/3.2.1/observation.xsd +95 -0
  100. pycsw/core/schemas/ogc/gml/3.2.1/referenceSystems.xsd +70 -0
  101. pycsw/core/schemas/ogc/gml/3.2.1/temporal.xsd +269 -0
  102. pycsw/core/schemas/ogc/gml/3.2.1/temporalReferenceSystems.xsd +189 -0
  103. pycsw/core/schemas/ogc/gml/3.2.1/temporalTopology.xsd +119 -0
  104. pycsw/core/schemas/ogc/gml/3.2.1/topology.xsd +386 -0
  105. pycsw/core/schemas/ogc/gml/3.2.1/units.xsd +162 -0
  106. pycsw/core/schemas/ogc/gml/3.2.1/valueObjects.xsd +205 -0
  107. pycsw/core/schemas/ogc/ogcapi/records/part1/1.0/ogcapi-records-1.yaml +932 -0
  108. pycsw/core/schemas/ogc/ows/1.0.0/ows19115subset.xsd +222 -0
  109. pycsw/core/schemas/ogc/ows/1.0.0/owsAll.xsd +20 -0
  110. pycsw/core/schemas/ogc/ows/1.0.0/owsCommon.xsd +155 -0
  111. pycsw/core/schemas/ogc/ows/1.0.0/owsDataIdentification.xsd +112 -0
  112. pycsw/core/schemas/ogc/ows/1.0.0/owsExceptionReport.xsd +67 -0
  113. pycsw/core/schemas/ogc/ows/1.0.0/owsGetCapabilities.xsd +108 -0
  114. pycsw/core/schemas/ogc/ows/1.0.0/owsOperationsMetadata.xsd +161 -0
  115. pycsw/core/schemas/ogc/ows/1.0.0/owsServiceIdentification.xsd +55 -0
  116. pycsw/core/schemas/ogc/ows/1.0.0/owsServiceProvider.xsd +46 -0
  117. pycsw/core/schemas/ogc/ows/1.1.0/ows19115subset.xsd +236 -0
  118. pycsw/core/schemas/ogc/ows/1.1.0/owsAll.xsd +23 -0
  119. pycsw/core/schemas/ogc/ows/1.1.0/owsCommon.xsd +158 -0
  120. pycsw/core/schemas/ogc/ows/1.1.0/owsContents.xsd +87 -0
  121. pycsw/core/schemas/ogc/ows/1.1.0/owsDataIdentification.xsd +128 -0
  122. pycsw/core/schemas/ogc/ows/1.1.0/owsDomainType.xsd +280 -0
  123. pycsw/core/schemas/ogc/ows/1.1.0/owsExceptionReport.xsd +77 -0
  124. pycsw/core/schemas/ogc/ows/1.1.0/owsGetCapabilities.xsd +113 -0
  125. pycsw/core/schemas/ogc/ows/1.1.0/owsGetResourceByID.xsd +52 -0
  126. pycsw/core/schemas/ogc/ows/1.1.0/owsInputOutputData.xsd +60 -0
  127. pycsw/core/schemas/ogc/ows/1.1.0/owsManifest.xsd +125 -0
  128. pycsw/core/schemas/ogc/ows/1.1.0/owsOperationsMetadata.xsd +141 -0
  129. pycsw/core/schemas/ogc/ows/1.1.0/owsServiceIdentification.xsd +61 -0
  130. pycsw/core/schemas/ogc/ows/1.1.0/owsServiceProvider.xsd +48 -0
  131. pycsw/core/schemas/ogc/ows/2.0/ows19115subset.xsd +364 -0
  132. pycsw/core/schemas/ogc/ows/2.0/owsAdditionalParameters.xsd +114 -0
  133. pycsw/core/schemas/ogc/ows/2.0/owsAll.xsd +29 -0
  134. pycsw/core/schemas/ogc/ows/2.0/owsCommon.xsd +275 -0
  135. pycsw/core/schemas/ogc/ows/2.0/owsContents.xsd +163 -0
  136. pycsw/core/schemas/ogc/ows/2.0/owsDataIdentification.xsd +202 -0
  137. pycsw/core/schemas/ogc/ows/2.0/owsDomainType.xsd +388 -0
  138. pycsw/core/schemas/ogc/ows/2.0/owsExceptionReport.xsd +126 -0
  139. pycsw/core/schemas/ogc/ows/2.0/owsGetCapabilities.xsd +220 -0
  140. pycsw/core/schemas/ogc/ows/2.0/owsGetResourceByID.xsd +83 -0
  141. pycsw/core/schemas/ogc/ows/2.0/owsInputOutputData.xsd +98 -0
  142. pycsw/core/schemas/ogc/ows/2.0/owsManifest.xsd +181 -0
  143. pycsw/core/schemas/ogc/ows/2.0/owsOperationsMetadata.xsd +234 -0
  144. pycsw/core/schemas/ogc/ows/2.0/owsServiceIdentification.xsd +98 -0
  145. pycsw/core/schemas/ogc/ows/2.0/owsServiceProvider.xsd +64 -0
  146. pycsw/core/schemas/w3c/1999/xlink.xsd +271 -0
  147. pycsw/core/schemas/w3c/2001/xml.xsd +287 -0
  148. pycsw/core/util.py +552 -0
  149. pycsw/oaipmh.py +311 -0
  150. pycsw/ogc/__init__.py +29 -0
  151. pycsw/ogc/api/__init__.py +28 -0
  152. pycsw/ogc/api/oapi.py +558 -0
  153. pycsw/ogc/api/records.py +1414 -0
  154. pycsw/ogc/api/templates/_base.html +76 -0
  155. pycsw/ogc/api/templates/collection.html +44 -0
  156. pycsw/ogc/api/templates/collections.html +45 -0
  157. pycsw/ogc/api/templates/conformance.html +21 -0
  158. pycsw/ogc/api/templates/exception.html +8 -0
  159. pycsw/ogc/api/templates/item.html +228 -0
  160. pycsw/ogc/api/templates/items.html +337 -0
  161. pycsw/ogc/api/templates/landing_page.html +27 -0
  162. pycsw/ogc/api/templates/openapi.html +58 -0
  163. pycsw/ogc/api/templates/queryables.html +50 -0
  164. pycsw/ogc/api/templates/stac_items.html +173 -0
  165. pycsw/ogc/api/templates/static/favicon.ico +0 -0
  166. pycsw/ogc/api/templates/static/logo-horizontal.png +0 -0
  167. pycsw/ogc/api/templates/static/logo-vertical-darkbg.png +0 -0
  168. pycsw/ogc/api/util.py +252 -0
  169. pycsw/ogc/csw/__init__.py +29 -0
  170. pycsw/ogc/csw/cql.py +133 -0
  171. pycsw/ogc/csw/csw2.py +2042 -0
  172. pycsw/ogc/csw/csw3.py +2193 -0
  173. pycsw/ogc/fes/__init__.py +29 -0
  174. pycsw/ogc/fes/fes1.py +433 -0
  175. pycsw/ogc/fes/fes2.py +451 -0
  176. pycsw/ogc/gml/__init__.py +29 -0
  177. pycsw/ogc/gml/gml3.py +240 -0
  178. pycsw/ogc/gml/gml32.py +243 -0
  179. pycsw/opensearch.py +857 -0
  180. pycsw/plugins/__init__.py +29 -0
  181. pycsw/plugins/outputschemas/__init__.py +31 -0
  182. pycsw/plugins/outputschemas/atom.py +143 -0
  183. pycsw/plugins/outputschemas/datacite.py +375 -0
  184. pycsw/plugins/outputschemas/dif.py +213 -0
  185. pycsw/plugins/outputschemas/fgdc.py +180 -0
  186. pycsw/plugins/outputschemas/gm03.py +240 -0
  187. pycsw/plugins/profiles/__init__.py +29 -0
  188. pycsw/plugins/profiles/apiso/__init__.py +29 -0
  189. pycsw/plugins/profiles/apiso/apiso.py +757 -0
  190. pycsw/plugins/profiles/apiso/schemas/ogc/csw/2.0.2/profiles/apiso/1.0.0/apiso.xsd +13 -0
  191. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/basicTypes.xsd +429 -0
  192. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/gco.xsd +12 -0
  193. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gco/gcoBase.xsd +61 -0
  194. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/applicationSchema.xsd +42 -0
  195. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/citation.xsd +275 -0
  196. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/constraints.xsd +106 -0
  197. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/content.xsd +188 -0
  198. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/dataQuality.xsd +554 -0
  199. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/distribution.xsd +202 -0
  200. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/extent.xsd +205 -0
  201. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/freeText.xsd +122 -0
  202. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/gmd.xsd +12 -0
  203. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/identification.xsd +348 -0
  204. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/maintenance.xsd +86 -0
  205. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataApplication.xsd +175 -0
  206. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataEntity.xsd +70 -0
  207. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/metadataExtension.xsd +99 -0
  208. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/portrayalCatalogue.xsd +36 -0
  209. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/referenceSystem.xsd +100 -0
  210. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmd/spatialRepresentation.xsd +237 -0
  211. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/basicTypes.xsd +267 -0
  212. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateOperations.xsd +639 -0
  213. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateReferenceSystems.xsd +526 -0
  214. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coordinateSystems.xsd +401 -0
  215. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/coverage.xsd +462 -0
  216. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/datums.xsd +342 -0
  217. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/dictionary.xsd +129 -0
  218. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/direction.xsd +80 -0
  219. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/dynamicFeature.xsd +142 -0
  220. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/feature.xsd +215 -0
  221. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryAggregates.xsd +216 -0
  222. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryBasic0d1d.xsd +304 -0
  223. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryBasic2d.xsd +123 -0
  224. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryComplexes.xsd +89 -0
  225. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/geometryPrimitives.xsd +892 -0
  226. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/gml.xsd +14 -0
  227. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/gmlBase.xsd +305 -0
  228. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/grids.xsd +58 -0
  229. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/measures.xsd +167 -0
  230. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/observation.xsd +90 -0
  231. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/referenceSystems.xsd +69 -0
  232. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporal.xsd +263 -0
  233. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporalReferenceSystems.xsd +183 -0
  234. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/temporalTopology.xsd +124 -0
  235. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/topology.xsd +372 -0
  236. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/units.xsd +156 -0
  237. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gml/valueObjects.xsd +212 -0
  238. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/catalogues.xsd +112 -0
  239. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/codelistItem.xsd +168 -0
  240. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/crsItem.xsd +1030 -0
  241. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/extendedTypes.xsd +75 -0
  242. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/gmx.xsd +2 -0
  243. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/gmxUsage.xsd +127 -0
  244. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gmx/uomItem.xsd +162 -0
  245. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gsr/gsr.xsd +12 -0
  246. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gsr/spatialReferencing.xsd +24 -0
  247. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gss/geometry.xsd +35 -0
  248. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gss/gss.xsd +12 -0
  249. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gts/gts.xsd +12 -0
  250. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/gts/temporalObjects.xsd +34 -0
  251. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/serviceMetadata.xsd +197 -0
  252. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/serviceModel.xsd +220 -0
  253. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20060504/srv/srv.xsd +13 -0
  254. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/basicTypes.xsd +431 -0
  255. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/gco.xsd +12 -0
  256. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gco/gcoBase.xsd +63 -0
  257. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/applicationSchema.xsd +43 -0
  258. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/citation.xsd +276 -0
  259. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/constraints.xsd +107 -0
  260. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/content.xsd +190 -0
  261. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/dataQuality.xsd +556 -0
  262. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/distribution.xsd +203 -0
  263. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/extent.xsd +206 -0
  264. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/freeText.xsd +123 -0
  265. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/gmd.xsd +12 -0
  266. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/identification.xsd +349 -0
  267. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/maintenance.xsd +87 -0
  268. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataApplication.xsd +176 -0
  269. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataEntity.xsd +71 -0
  270. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/metadataExtension.xsd +100 -0
  271. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/portrayalCatalogue.xsd +37 -0
  272. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/referenceSystem.xsd +101 -0
  273. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmd/spatialRepresentation.xsd +238 -0
  274. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/catalogues.xsd +113 -0
  275. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/codelistItem.xsd +169 -0
  276. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/crsItem.xsd +1031 -0
  277. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/extendedTypes.xsd +76 -0
  278. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/gmx.xsd +12 -0
  279. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/gmxUsage.xsd +128 -0
  280. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gmx/uomItem.xsd +163 -0
  281. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gsr/gsr.xsd +12 -0
  282. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gsr/spatialReferencing.xsd +25 -0
  283. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gss/geometry.xsd +36 -0
  284. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gss/gss.xsd +12 -0
  285. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gts/gts.xsd +12 -0
  286. pycsw/plugins/profiles/apiso/schemas/ogc/iso/19139/20070417/gts/temporalObjects.xsd +35 -0
  287. pycsw/plugins/profiles/ebrim/__init__.py +29 -0
  288. pycsw/plugins/profiles/ebrim/ebrim.py +174 -0
  289. pycsw/plugins/profiles/ebrim/schemas/ogc/csw/2.0.2/profiles/ebrim/1.0/csw-ebrim-iri.xsd +146 -0
  290. pycsw/plugins/profiles/ebrim/schemas/ogc/csw/2.0.2/profiles/ebrim/1.0/csw-ebrim.xsd +104 -0
  291. pycsw/plugins/profiles/iso19115p3/__init__.py +29 -0
  292. pycsw/plugins/profiles/iso19115p3/iso19115p3.py +854 -0
  293. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/cat.xsd +13 -0
  294. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/catalogues.xsd +53 -0
  295. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/codelistItem.xsd +54 -0
  296. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/crsItem.xsd +181 -0
  297. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cat/1.0/uomItem.xsd +38 -0
  298. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/1.0/cit.xsd +7 -0
  299. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/1.0/citation.xsd +514 -0
  300. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/2.0/cit.xsd +10 -0
  301. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/cit/2.0/citation.xsd +523 -0
  302. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gco/1.0/baseTypes2014.xsd +530 -0
  303. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gco/1.0/gco.xsd +16 -0
  304. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/extendedTypes.xsd +92 -0
  305. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/extendedTypes_autoFromShapeChange.xsd +60 -0
  306. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gcx/1.0/gcx.xsd +8 -0
  307. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gex/1.0/extent.xsd +241 -0
  308. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gex/1.0/gex.xsd +9 -0
  309. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gmw/1.0/gmlWrapperTypes2014.xsd +159 -0
  310. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/gmw/1.0/gmw.xsd +14 -0
  311. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/lan/1.0/lan.xsd +8 -0
  312. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/lan/1.0/language.xsd +140 -0
  313. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/1.0/acquisitionInformationImagery.xsd +622 -0
  314. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/1.0/mac.xsd +11 -0
  315. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/acquisitionInformationImagery.xsd +590 -0
  316. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/event.xsd +108 -0
  317. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mac/2.0/mac.xsd +10 -0
  318. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mas/1.0/applicationSchema.xsd +61 -0
  319. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mas/1.0/mas.xsd +9 -0
  320. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/AbstractCommonClasses.xsd +358 -0
  321. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/commonClasses.xsd +220 -0
  322. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mcc/1.0/mcc.xsd +8 -0
  323. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mco/1.0/constraints.xsd +188 -0
  324. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mco/1.0/mco.xsd +8 -0
  325. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md1/1.0/md1.xsd +9 -0
  326. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md1/1.0/metadataWExtendedType.xsd +4 -0
  327. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md2/1.0/md2.xsd +15 -0
  328. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/md2/1.0/metadataWithExtensions.xsd +4 -0
  329. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mda/1.0/mda.xsd +8 -0
  330. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mda/1.0/metadataApplication.xsd +200 -0
  331. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/1.0/mdb.xsd +14 -0
  332. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/1.0/metadataBase.xsd +98 -0
  333. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/2.0/mdb.xsd +20 -0
  334. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdb/2.0/metadataBase.xsd +102 -0
  335. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mds/1.0/mds.xsd +22 -0
  336. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mds/1.0/metadataDataServices.xsd +4 -0
  337. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdt/1.0/mdt.xsd +13 -0
  338. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mdt/1.0/metadataTransfer.xsd +111 -0
  339. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mex/1.0/metadataExtension.xsd +156 -0
  340. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mex/1.0/mex.xsd +8 -0
  341. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mmi/1.0/maintenance.xsd +76 -0
  342. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mmi/1.0/mmi.xsd +8 -0
  343. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mpc/1.0/mpc.xsd +8 -0
  344. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mpc/1.0/portrayalCatalogue.xsd +31 -0
  345. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/content.xsd +416 -0
  346. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/contentInformationImagery.xsd +185 -0
  347. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/1.0/mrc.xsd +11 -0
  348. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/content.xsd +419 -0
  349. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/contentInformationImagery.xsd +171 -0
  350. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrc/2.0/mrc.xsd +11 -0
  351. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrd/1.0/distribution.xsd +267 -0
  352. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrd/1.0/mrd.xsd +8 -0
  353. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mri/1.0/identification.xsd +517 -0
  354. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mri/1.0/mri.xsd +9 -0
  355. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/lineage.xsd +147 -0
  356. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/lineageImagery.xsd +236 -0
  357. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/1.0/mrl.xsd +9 -0
  358. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/lineage.xsd +147 -0
  359. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/lineageImagery.xsd +312 -0
  360. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrl/2.0/mrl.xsd +11 -0
  361. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrs/1.0/mrs.xsd +8 -0
  362. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/mrs/1.0/referenceSystem.xsd +47 -0
  363. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/msr.xsd +10 -0
  364. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/spatialRepresentation.xsd +375 -0
  365. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/1.0/spatialRepresentationImagery.xsd +119 -0
  366. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/msr.xsd +15 -0
  367. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/spatialRepresentation.xsd +381 -0
  368. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/msr/2.0/spatialRepresentationImagery.xsd +120 -0
  369. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.0/serviceInformation.xsd +272 -0
  370. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.0/srv.xsd +6 -0
  371. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.1/serviceInformation.xsd +278 -0
  372. pycsw/plugins/profiles/iso19115p3/schemas/ogc/iso/iso19115-3/srv/2.1/srv.xsd +6 -0
  373. pycsw/plugins/profiles/profile.py +141 -0
  374. pycsw/plugins/repository/__init__.py +29 -0
  375. pycsw/plugins/repository/odc/__init__.py +29 -0
  376. pycsw/plugins/repository/odc/odc.py +153 -0
  377. pycsw/server.py +938 -0
  378. pycsw/sru.py +217 -0
  379. pycsw/stac/__init__.py +29 -0
  380. pycsw/stac/api.py +571 -0
  381. pycsw/wsgi.py +233 -0
  382. pycsw/wsgi_flask.py +345 -0
pycsw/core/metadata.py ADDED
@@ -0,0 +1,1996 @@
1
+ # -*- coding: utf-8 -*-
2
+ # =================================================================
3
+ #
4
+ # Authors: Tom Kralidis <tomkralidis@gmail.com>
5
+ # Ricardo Garcia Silva <ricardo.garcia.silva@gmail.com>
6
+ #
7
+ # Copyright (c) 2023 Tom Kralidis
8
+ # Copyright (c) 2016 James F. Dickens
9
+ # Copyright (c) 2017 Ricardo Garcia Silva
10
+ #
11
+ # Permission is hereby granted, free of charge, to any person
12
+ # obtaining a copy of this software and associated documentation
13
+ # files (the "Software"), to deal in the Software without
14
+ # restriction, including without limitation the rights to use,
15
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ # copies of the Software, and to permit persons to whom the
17
+ # Software is furnished to do so, subject to the following
18
+ # conditions:
19
+ #
20
+ # The above copyright notice and this permission notice shall be
21
+ # included in all copies or substantial portions of the Software.
22
+ #
23
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
25
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
27
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
28
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
29
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
30
+ # OTHER DEALINGS IN THE SOFTWARE.
31
+ #
32
+ # =================================================================
33
+
34
+ import json
35
+ import logging
36
+ import uuid
37
+ from urllib.parse import urlparse
38
+
39
+ from geolinks import sniff_link
40
+ from owslib.util import build_get_url
41
+ from shapely.wkt import loads
42
+ from shapely.geometry import MultiPolygon
43
+
44
+ from pycsw.core.etree import etree
45
+ from pycsw.core import util
46
+ from pycsw.plugins.outputschemas import atom
47
+
48
+ LOGGER = logging.getLogger(__name__)
49
+
50
+ def parse_record(context, record, repos=None,
51
+ mtype='http://www.opengis.net/cat/csw/2.0.2',
52
+ identifier=None, pagesize=10):
53
+ ''' parse metadata '''
54
+
55
+ if identifier is None:
56
+ identifier = uuid.uuid4().urn
57
+
58
+ # parse web services
59
+ if (mtype == 'http://www.opengis.net/cat/csw/2.0.2' and
60
+ isinstance(record, str) and record.startswith('http')):
61
+ LOGGER.info('CSW service detected, fetching via HTTP')
62
+ # CSW service, not csw:Record
63
+ try:
64
+ return _parse_csw(context, repos, record, identifier, pagesize)
65
+ except Exception as err:
66
+ # TODO: implement better exception handling
67
+ if str(err).find('ExceptionReport') != -1:
68
+ msg = 'CSW harvesting error: %s' % str(err)
69
+ LOGGER.exception(msg)
70
+ raise RuntimeError(msg)
71
+ LOGGER.debug('Not a CSW, attempting to fetch Dublin Core')
72
+ try:
73
+ content = util.http_request('GET', record)
74
+ except Exception as err:
75
+ raise RuntimeError('HTTP error: %s' % str(err))
76
+ return [_parse_dc(context, repos, etree.fromstring(content, context.parser))]
77
+
78
+ elif mtype == 'urn:geoss:waf': # WAF
79
+ LOGGER.info('WAF detected, fetching via HTTP')
80
+ return _parse_waf(context, repos, record, identifier)
81
+
82
+ elif mtype == 'http://www.opengis.net/wms': # WMS
83
+ LOGGER.info('WMS detected, fetching via OWSLib')
84
+ return _parse_wms(context, repos, record, identifier)
85
+
86
+ elif mtype == 'http://www.opengis.net/wmts/1.0': # WMTS
87
+ LOGGER.info('WMTS 1.0.0 detected, fetching via OWSLib')
88
+ return _parse_wmts(context, repos, record, identifier)
89
+
90
+ elif mtype == 'http://www.opengis.net/wps/1.0.0': # WPS
91
+ LOGGER.info('WPS detected, fetching via OWSLib')
92
+ return _parse_wps(context, repos, record, identifier)
93
+
94
+ elif mtype == 'http://www.opengis.net/wfs': # WFS 1.1.0
95
+ LOGGER.info('WFS detected, fetching via OWSLib')
96
+ return _parse_wfs(context, repos, record, identifier, '1.1.0')
97
+
98
+ elif mtype == 'http://www.opengis.net/wfs/2.0': # WFS 2.0.0
99
+ LOGGER.info('WFS detected, fetching via OWSLib')
100
+ return _parse_wfs(context, repos, record, identifier, '2.0.0')
101
+
102
+ elif mtype == 'http://www.opengis.net/wcs': # WCS
103
+ LOGGER.info('WCS detected, fetching via OWSLib')
104
+ return _parse_wcs(context, repos, record, identifier)
105
+
106
+ elif mtype == 'http://www.opengis.net/sos/1.0': # SOS 1.0.0
107
+ LOGGER.info('SOS 1.0.0 detected, fetching via OWSLib')
108
+ return _parse_sos(context, repos, record, identifier, '1.0.0')
109
+
110
+ elif mtype == 'http://www.opengis.net/sos/2.0': # SOS 2.0.0
111
+ LOGGER.info('SOS 2.0.0 detected, fetching via OWSLib')
112
+ return _parse_sos(context, repos, record, identifier, '2.0.0')
113
+
114
+ elif (mtype == 'http://www.opengis.net/cat/csw/csdgm' and
115
+ record.startswith('http')): # FGDC
116
+ LOGGER.info('FGDC detected, fetching via HTTP')
117
+ record = util.http_request('GET', record)
118
+
119
+ return _parse_metadata(context, repos, record)
120
+
121
+ def _get(context, obj, name):
122
+ ''' convenience method to get values '''
123
+ return getattr(obj, context.md_core_model['mappings'][name])
124
+
125
+ def _set(context, obj, name, value):
126
+ ''' convenience method to set values '''
127
+ setattr(obj, context.md_core_model['mappings'][name], value)
128
+
129
+ def _parse_metadata(context, repos, record):
130
+ """parse metadata formats"""
131
+
132
+ if isinstance(record, dict): # JSON
133
+ LOGGER.debug('Record is JSON based')
134
+ return [_parse_json_record(context, repos, record)]
135
+
136
+ if isinstance(record, bytes) or isinstance(record, str):
137
+ exml = etree.fromstring(record, context.parser)
138
+ else: # already serialized to lxml
139
+ if hasattr(record, 'getroot'): # standalone document
140
+ exml = record.getroot()
141
+ else: # part of a larger document
142
+ exml = record
143
+
144
+ root = exml.tag
145
+
146
+ LOGGER.info('Serialized metadata, parsing content model')
147
+
148
+ if root == '{%s}MD_Metadata' % context.namespaces['gmd']: # ISO
149
+ return [_parse_iso(context, repos, exml)]
150
+ elif root == '{http://www.isotc211.org/2005/gmi}MI_Metadata':
151
+ # ISO Metadata for Imagery
152
+ return [_parse_iso(context, repos, exml)]
153
+ elif root == 'metadata': # FGDC
154
+ return [_parse_fgdc(context, repos, exml)]
155
+ elif root == '{%s}TRANSFER' % context.namespaces['gm03']: # GM03
156
+ return [_parse_gm03(context, repos, exml)]
157
+ elif root == '{http://www.geocat.ch/2008/che}CHE_MD_Metadata': # GM03 ISO profile
158
+ return [_parse_iso(context, repos, exml)]
159
+ elif root == '{%s}Record' % context.namespaces['csw']: # Dublin Core CSW
160
+ return [_parse_dc(context, repos, exml)]
161
+ elif root == '{%s}RDF' % context.namespaces['rdf']: # Dublin Core RDF
162
+ return [_parse_dc(context, repos, exml)]
163
+ elif root == '{%s}DIF' % context.namespaces['dif']: # DIF
164
+ pass # TODO
165
+ elif root == '{%s}MD_Metadata' % context.namespaces['mdb']:
166
+ # ISO 19115p3 XML
167
+ return [_parse_iso(context, repos, exml)]
168
+ else:
169
+ raise RuntimeError('Unsupported metadata format')
170
+
171
+
172
+ def _parse_csw(context, repos, record, identifier, pagesize=10):
173
+
174
+ from owslib.csw import CatalogueServiceWeb
175
+
176
+ recobjs = [] # records
177
+ serviceobj = repos.dataset()
178
+
179
+ # if init raises error, this might not be a CSW
180
+ md = CatalogueServiceWeb(record, timeout=60)
181
+
182
+ LOGGER.info('Setting CSW service metadata')
183
+ # generate record of service instance
184
+ _set(context, serviceobj, 'pycsw:Identifier', identifier)
185
+ _set(context, serviceobj, 'pycsw:Typename', 'csw:Record')
186
+ _set(context, serviceobj, 'pycsw:Schema', 'http://www.opengis.net/cat/csw/2.0.2')
187
+ _set(context, serviceobj, 'pycsw:MdSource', record)
188
+ _set(context, serviceobj, 'pycsw:InsertDate', util.get_today_and_now())
189
+ _set(
190
+ context,
191
+ serviceobj,
192
+ 'pycsw:AnyText',
193
+ util.get_anytext(md._exml)
194
+ )
195
+ _set(context, serviceobj, 'pycsw:Type', 'service')
196
+ _set(context, serviceobj, 'pycsw:Title', md.identification.title)
197
+ _set(context, serviceobj, 'pycsw:Abstract', md.identification.abstract)
198
+ _set(context, serviceobj, 'pycsw:Keywords', ','.join(md.identification.keywords))
199
+ _set(context, serviceobj, 'pycsw:Creator', md.provider.contact.name)
200
+ _set(context, serviceobj, 'pycsw:Publisher', md.provider.name)
201
+ _set(context, serviceobj, 'pycsw:Contributor', md.provider.contact.name)
202
+ _set(context, serviceobj, 'pycsw:OrganizationName', md.provider.contact.name)
203
+ _set(context, serviceobj, 'pycsw:AccessConstraints', md.identification.accessconstraints)
204
+ _set(context, serviceobj, 'pycsw:OtherConstraints', md.identification.fees)
205
+ _set(context, serviceobj, 'pycsw:Source', record)
206
+ _set(context, serviceobj, 'pycsw:Format', md.identification.type)
207
+
208
+ _set(context, serviceobj, 'pycsw:ServiceType', 'OGC:CSW')
209
+ _set(context, serviceobj, 'pycsw:ServiceTypeVersion', md.identification.version)
210
+ _set(context, serviceobj, 'pycsw:Operation', ','.join([d.name for d in md.operations]))
211
+ _set(context, serviceobj, 'pycsw:CouplingType', 'tight')
212
+
213
+ links = [{
214
+ 'name': identifier,
215
+ 'description': 'OGC-CSW Catalogue Service for the Web',
216
+ 'protocol': 'OGC:CSW',
217
+ 'url': md.url
218
+ }]
219
+
220
+ _set(context, serviceobj, 'pycsw:Links', json.dumps(links))
221
+ _set(context, serviceobj, 'pycsw:XML', caps2iso(serviceobj, md, context))
222
+ _set(context, serviceobj, 'pycsw:MetadataType', 'application/xml')
223
+
224
+ recobjs.append(serviceobj)
225
+
226
+ # get all supported typenames of metadata
227
+ # so we can harvest the entire CSW
228
+
229
+ # try for ISO, settle for Dublin Core
230
+ csw_typenames = 'csw:Record'
231
+ csw_outputschema = 'http://www.opengis.net/cat/csw/2.0.2'
232
+
233
+ grop = md.get_operation_by_name('GetRecords')
234
+ if all(['gmd:MD_Metadata' in grop.parameters['typeNames']['values'],
235
+ 'http://www.isotc211.org/2005/gmd' in grop.parameters['outputSchema']['values']]):
236
+ LOGGER.debug('CSW supports ISO')
237
+ csw_typenames = 'gmd:MD_Metadata'
238
+ csw_outputschema = 'http://www.isotc211.org/2005/gmd'
239
+
240
+
241
+ # now get all records
242
+ # get total number of records to loop against
243
+
244
+ try:
245
+ md.getrecords2(typenames=csw_typenames, resulttype='hits',
246
+ outputschema=csw_outputschema)
247
+ matches = md.results['matches']
248
+ except Exception: # this is a CSW, but server rejects query
249
+ LOGGER.debug('CSW query failed')
250
+ raise RuntimeError(md.response)
251
+
252
+ if pagesize > matches:
253
+ pagesize = matches
254
+
255
+ LOGGER.info('Harvesting %d CSW records', matches)
256
+
257
+ # loop over all catalogue records incrementally
258
+ for r in range(1, matches+1, pagesize):
259
+ try:
260
+ md.getrecords2(typenames=csw_typenames, startposition=r,
261
+ maxrecords=pagesize, outputschema=csw_outputschema, esn='full')
262
+ except Exception as err: # this is a CSW, but server rejects query
263
+ raise RuntimeError(md.response)
264
+ for k, v in md.records.items():
265
+ # try to parse metadata
266
+ try:
267
+ LOGGER.info('Parsing metadata record: %s', v.xml)
268
+ if csw_typenames == 'gmd:MD_Metadata':
269
+ recobjs.append(_parse_iso(context, repos,
270
+ etree.fromstring(v.xml, context.parser)))
271
+ else:
272
+ recobjs.append(_parse_dc(context, repos,
273
+ etree.fromstring(v.xml, context.parser)))
274
+ except Exception as err: # parsing failed for some reason
275
+ LOGGER.exception('Metadata parsing failed')
276
+
277
+ return recobjs
278
+
279
+ def _parse_waf(context, repos, record, identifier):
280
+
281
+ recobjs = []
282
+
283
+ content = util.http_request('GET', record)
284
+
285
+ LOGGER.debug(content)
286
+
287
+ try:
288
+ parser = etree.HTMLParser()
289
+ tree = etree.fromstring(content, parser)
290
+ except Exception as err:
291
+ raise Exception('Could not parse WAF: %s' % str(err))
292
+
293
+ up = urlparse(record)
294
+ links = []
295
+
296
+ LOGGER.info('collecting links')
297
+ for link in tree.xpath('//a/@href'):
298
+ link = link.strip()
299
+ if not link:
300
+ continue
301
+ if link.find('?') != -1:
302
+ continue
303
+ if not link.endswith('.xml'):
304
+ LOGGER.debug('Skipping, not .xml')
305
+ continue
306
+ if '/' in link: # path is embedded in link
307
+ if link[-1] == '/': # directory, skip
308
+ continue
309
+ if link[0] == '/':
310
+ # strip path of WAF URL
311
+ link = '%s://%s%s' % (up.scheme, up.netloc, link)
312
+ else: # tack on href to WAF URL
313
+ link = '%s/%s' % (record, link)
314
+ LOGGER.debug('URL is: %s', link)
315
+ links.append(link)
316
+
317
+ LOGGER.debug('%d links found', len(links))
318
+ for link in links:
319
+ LOGGER.info('Processing link %s', link)
320
+ # fetch and parse
321
+ linkcontent = util.http_request('GET', link)
322
+ recobj = _parse_metadata(context, repos, linkcontent)[0]
323
+ recobj.source = link
324
+ recobj.mdsource = link
325
+ recobjs.append(recobj)
326
+
327
+ return recobjs
328
+
329
+ def _parse_wms(context, repos, record, identifier):
330
+
331
+ from owslib.wms import WebMapService
332
+
333
+ recobjs = []
334
+ serviceobj = repos.dataset()
335
+
336
+ md = WebMapService(record)
337
+ try:
338
+ md = WebMapService(record, version='1.3.0')
339
+ except Exception as err:
340
+ LOGGER.info('Looks like WMS 1.3.0 is not supported; trying 1.1.1', err)
341
+ md = WebMapService(record)
342
+
343
+ # generate record of service instance
344
+ _set(context, serviceobj, 'pycsw:Identifier', identifier)
345
+ _set(context, serviceobj, 'pycsw:Typename', 'csw:Record')
346
+ _set(context, serviceobj, 'pycsw:Schema', 'http://www.opengis.net/wms')
347
+ _set(context, serviceobj, 'pycsw:MdSource', record)
348
+ _set(context, serviceobj, 'pycsw:InsertDate', util.get_today_and_now())
349
+ _set(
350
+ context,
351
+ serviceobj,
352
+ 'pycsw:AnyText',
353
+ util.get_anytext(md.getServiceXML())
354
+ )
355
+ _set(context, serviceobj, 'pycsw:Type', 'service')
356
+ _set(context, serviceobj, 'pycsw:Title', md.identification.title)
357
+ _set(context, serviceobj, 'pycsw:Abstract', md.identification.abstract)
358
+ _set(context, serviceobj, 'pycsw:Keywords', ','.join(md.identification.keywords))
359
+ _set(context, serviceobj, 'pycsw:Creator', md.provider.contact.name)
360
+ _set(context, serviceobj, 'pycsw:Publisher', md.provider.name)
361
+ _set(context, serviceobj, 'pycsw:Contributor', md.provider.contact.name)
362
+ _set(context, serviceobj, 'pycsw:OrganizationName', md.provider.contact.name)
363
+
364
+ contacts = [vars(md.provider.contact)]
365
+ contacts[0]['role'] = 'pointOfContact'
366
+ _set(context, serviceobj, 'pycsw:Contacts',
367
+ json.dumps(contacts,default=lambda o: o.__dict__))
368
+
369
+ _set(context, serviceobj, 'pycsw:AccessConstraints', md.identification.accessconstraints)
370
+ _set(context, serviceobj, 'pycsw:OtherConstraints', md.identification.fees)
371
+ _set(context, serviceobj, 'pycsw:Source', record)
372
+ _set(context, serviceobj, 'pycsw:Format', md.identification.type)
373
+ for c in md.contents:
374
+ if md.contents[c].parent is None:
375
+ bbox = md.contents[c].boundingBoxWGS84
376
+ tmp = '%s,%s,%s,%s' % (bbox[0], bbox[1], bbox[2], bbox[3])
377
+ _set(context, serviceobj, 'pycsw:BoundingBox', util.bbox2wktpolygon(tmp))
378
+ break
379
+ _set(context, serviceobj, 'pycsw:CRS', 'urn:ogc:def:crs:EPSG:6.11:4326')
380
+ _set(context, serviceobj, 'pycsw:DistanceUOM', 'degrees')
381
+ _set(context, serviceobj, 'pycsw:ServiceType', 'OGC:WMS')
382
+ _set(context, serviceobj, 'pycsw:ServiceTypeVersion', md.identification.version)
383
+ _set(context, serviceobj, 'pycsw:Operation', ','.join([d.name for d in md.operations]))
384
+ _set(context, serviceobj, 'pycsw:OperatesOn', ','.join(list(md.contents)))
385
+ _set(context, serviceobj, 'pycsw:CouplingType', 'tight')
386
+
387
+ links = [{
388
+ 'name': identifier,
389
+ 'description': 'OGC-WMS Web Map Service',
390
+ 'protocol': 'OGC:WMS',
391
+ 'url': md.url
392
+ }]
393
+
394
+ _set(context, serviceobj, 'pycsw:Links', json.dumps(links))
395
+ _set(context, serviceobj, 'pycsw:XML', caps2iso(serviceobj, md, context))
396
+ _set(context, serviceobj, 'pycsw:MetadataType', 'application/xml')
397
+
398
+ recobjs.append(serviceobj)
399
+
400
+ # generate record foreach layer
401
+
402
+ LOGGER.info('Harvesting %d WMS layers', len(md.contents))
403
+
404
+ for layer in md.contents:
405
+ recobj = repos.dataset()
406
+ identifier2 = '%s-%s' % (identifier, md.contents[layer].name)
407
+ _set(context, recobj, 'pycsw:Identifier', identifier2)
408
+ _set(context, recobj, 'pycsw:Typename', 'csw:Record')
409
+ _set(context, recobj, 'pycsw:Schema', 'http://www.opengis.net/wms')
410
+ _set(context, recobj, 'pycsw:MdSource', record)
411
+ _set(context, recobj, 'pycsw:InsertDate', util.get_today_and_now())
412
+ _set(context, recobj, 'pycsw:Type', 'dataset')
413
+ _set(context, recobj, 'pycsw:ParentIdentifier', identifier)
414
+ _set(context, recobj, 'pycsw:Title', md.contents[layer].title)
415
+ _set(context, recobj, 'pycsw:Abstract', md.contents[layer].abstract)
416
+ _set(context, recobj, 'pycsw:Keywords', ','.join(md.contents[layer].keywords))
417
+
418
+ _set(
419
+ context,
420
+ recobj,
421
+ 'pycsw:AnyText',
422
+ util.get_anytext([
423
+ md.contents[layer].title,
424
+ md.contents[layer].abstract,
425
+ ','.join(md.contents[layer].keywords)
426
+ ])
427
+ )
428
+
429
+ bbox = md.contents[layer].boundingBoxWGS84
430
+ if bbox is not None:
431
+ tmp = '%s,%s,%s,%s' % (bbox[0], bbox[1], bbox[2], bbox[3])
432
+ _set(context, recobj, 'pycsw:BoundingBox', util.bbox2wktpolygon(tmp))
433
+ _set(context, recobj, 'pycsw:CRS', 'urn:ogc:def:crs:EPSG:6.11:4326')
434
+ _set(context, recobj, 'pycsw:DistanceUOM', 'degrees')
435
+ else:
436
+ bbox = md.contents[layer].boundingBox
437
+ if bbox:
438
+ tmp = '%s,%s,%s,%s' % (bbox[0], bbox[1], bbox[2], bbox[3])
439
+ _set(context, recobj, 'pycsw:BoundingBox', util.bbox2wktpolygon(tmp))
440
+ _set(context, recobj, 'pycsw:CRS', 'urn:ogc:def:crs:EPSG:6.11:%s' % \
441
+ bbox[-1].split(':')[1])
442
+
443
+ _set(context, recobj, 'pycsw:Contacts',
444
+ json.dumps(contacts,default=lambda o: o.__dict__))
445
+
446
+ times = md.contents[layer].timepositions
447
+ if times is not None: # get temporal extent
448
+ time_begin = time_end = None
449
+ if len(times) == 1 and len(times[0].split('/')) > 1:
450
+ time_envelope = times[0].split('/')
451
+ time_begin = time_envelope[0]
452
+ time_end = time_envelope[1]
453
+ elif len(times) > 1: # get first/last
454
+ time_begin = times[0]
455
+ time_end = times[-1]
456
+ if all([time_begin is not None, time_end is not None]):
457
+ _set(context, recobj, 'pycsw:TempExtent_begin', time_begin)
458
+ _set(context, recobj, 'pycsw:TempExtent_end', time_end)
459
+
460
+ params = {
461
+ 'service': 'WMS',
462
+ 'version': '1.1.1',
463
+ 'request': 'GetMap',
464
+ 'layers': md.contents[layer].name,
465
+ 'format': 'image/png',
466
+ 'height': '200',
467
+ 'width': '200',
468
+ 'srs': 'EPSG:4326',
469
+ 'bbox': '%s,%s,%s,%s' % (bbox[0], bbox[1], bbox[2], bbox[3]),
470
+ 'styles': ''
471
+ }
472
+
473
+ links = [{
474
+ 'name': md.contents[layer].name,
475
+ 'description': 'OGC-Web Map Service',
476
+ 'protocol': 'OGC:WMS',
477
+ 'url': md.url
478
+ }, {
479
+ 'name': md.contents[layer].name,
480
+ 'description': 'Web image thumbnail (URL)',
481
+ 'protocol': 'WWW:LINK-1.0-http--image-thumbnail',
482
+ 'url': build_get_url(md.url, params)
483
+ }]
484
+
485
+ _set(context, recobj, 'pycsw:Links', json.dumps(links))
486
+ _set(context, recobj, 'pycsw:XML', caps2iso(recobj, md, context))
487
+ _set(context, recobj, 'pycsw:MetadataType', 'application/xml')
488
+
489
+ recobjs.append(recobj)
490
+
491
+ return recobjs
492
+
493
+ def _parse_wmts(context, repos, record, identifier):
494
+
495
+ from owslib.wmts import WebMapTileService
496
+
497
+ recobjs = []
498
+ serviceobj = repos.dataset()
499
+
500
+ md = WebMapTileService(record)
501
+ # generate record of service instance
502
+ _set(context, serviceobj, 'pycsw:Identifier', identifier)
503
+ _set(context, serviceobj, 'pycsw:Typename', 'csw:Record')
504
+ _set(context, serviceobj, 'pycsw:Schema', 'http://www.opengis.net/wmts/1.0')
505
+ _set(context, serviceobj, 'pycsw:MdSource', record)
506
+ _set(context, serviceobj, 'pycsw:InsertDate', util.get_today_and_now())
507
+ _set(
508
+ context,
509
+ serviceobj,
510
+ 'pycsw:AnyText',
511
+ util.get_anytext(md.getServiceXML())
512
+ )
513
+ _set(context, serviceobj, 'pycsw:Type', 'service')
514
+ _set(context, serviceobj, 'pycsw:Title', md.identification.title)
515
+ _set(context, serviceobj, 'pycsw:Abstract', md.identification.abstract)
516
+ _set(context, serviceobj, 'pycsw:Keywords', ','.join(md.identification.keywords))
517
+ _set(context, serviceobj, 'pycsw:Creator', md.provider.contact.name)
518
+ _set(context, serviceobj, 'pycsw:Publisher', md.provider.name)
519
+ _set(context, serviceobj, 'pycsw:Contributor', md.provider.contact.name)
520
+ _set(context, serviceobj, 'pycsw:OrganizationName', md.provider.contact.name)
521
+
522
+ contacts = [vars(md.provider.contact)]
523
+ contacts[0]['role'] = 'pointOfContact'
524
+ _set(context, serviceobj, 'pycsw:Contacts',
525
+ json.dumps(contacts,default=lambda o: o.__dict__))
526
+
527
+ _set(context, serviceobj, 'pycsw:AccessConstraints', md.identification.accessconstraints)
528
+ _set(context, serviceobj, 'pycsw:OtherConstraints', md.identification.fees)
529
+ _set(context, serviceobj, 'pycsw:Source', record)
530
+ _set(context, serviceobj, 'pycsw:Format', md.identification.type)
531
+
532
+ for c in md.contents:
533
+ if md.contents[c].parent is None:
534
+ bbox = md.contents[c].boundingBoxWGS84
535
+ tmp = '%s,%s,%s,%s' % (bbox[0], bbox[1], bbox[2], bbox[3])
536
+ _set(context, serviceobj, 'pycsw:BoundingBox', util.bbox2wktpolygon(tmp))
537
+ break
538
+ _set(context, serviceobj, 'pycsw:CRS', 'urn:ogc:def:crs:EPSG:6.11:4326')
539
+ _set(context, serviceobj, 'pycsw:DistanceUOM', 'degrees')
540
+ _set(context, serviceobj, 'pycsw:ServiceType', 'OGC:WMTS')
541
+ _set(context, serviceobj, 'pycsw:ServiceTypeVersion', md.identification.version)
542
+ _set(context, serviceobj, 'pycsw:Operation', ','.join([d.name for d in md.operations]))
543
+ _set(context, serviceobj, 'pycsw:OperatesOn', ','.join(list(md.contents)))
544
+ _set(context, serviceobj, 'pycsw:CouplingType', 'tight')
545
+
546
+ links = [{
547
+ 'name': identifier,
548
+ 'description': 'OGC-WMTS Web Map Service',
549
+ 'protocol': 'OGC:WMTS',
550
+ 'url': md.url
551
+ }]
552
+
553
+ _set(context, serviceobj, 'pycsw:Links', json.dumps(links))
554
+ _set(context, serviceobj, 'pycsw:XML', caps2iso(serviceobj, md, context))
555
+ _set(context, serviceobj, 'pycsw:MetadataType', 'application/xml')
556
+
557
+ recobjs.append(serviceobj)
558
+
559
+ # generate record for each layer
560
+
561
+ LOGGER.debug('Harvesting %d WMTS layers', len(md.contents))
562
+
563
+ for layer in md.contents:
564
+ recobj = repos.dataset()
565
+ keywords = ''
566
+ identifier2 = '%s-%s' % (identifier, md.contents[layer].name)
567
+ _set(context, recobj, 'pycsw:Identifier', identifier2)
568
+ _set(context, recobj, 'pycsw:Typename', 'csw:Record')
569
+ _set(context, recobj, 'pycsw:Schema', 'http://www.opengis.net/wmts/1.0')
570
+ _set(context, recobj, 'pycsw:MdSource', record)
571
+ _set(context, recobj, 'pycsw:InsertDate', util.get_today_and_now())
572
+ _set(context, recobj, 'pycsw:Type', 'dataset')
573
+ _set(context, recobj, 'pycsw:ParentIdentifier', identifier)
574
+ if md.contents[layer].title:
575
+ _set(context, recobj, 'pycsw:Title', md.contents[layer].title)
576
+ else:
577
+ _set(context, recobj, 'pycsw:Title', "")
578
+ if md.contents[layer].abstract:
579
+ _set(context, recobj, 'pycsw:Abstract', md.contents[layer].abstract)
580
+ else:
581
+ _set(context, recobj, 'pycsw:Abstract', "")
582
+ if hasattr(md.contents[layer], 'keywords'): # safeguard against older OWSLib installs
583
+ keywords = ','.join(md.contents[layer].keywords)
584
+ _set(context, recobj, 'pycsw:Keywords', keywords)
585
+
586
+ _set(
587
+ context,
588
+ recobj,
589
+ 'pycsw:AnyText',
590
+ util.get_anytext([
591
+ md.contents[layer].title,
592
+ md.contents[layer].abstract,
593
+ ','.join(keywords)
594
+ ])
595
+ )
596
+
597
+ bbox = md.contents[layer].boundingBoxWGS84
598
+
599
+ if bbox is not None:
600
+ tmp = '%s,%s,%s,%s' % (bbox[0], bbox[1], bbox[2], bbox[3])
601
+ _set(context, recobj, 'pycsw:BoundingBox', util.bbox2wktpolygon(tmp))
602
+ _set(context, recobj, 'pycsw:CRS', 'urn:ogc:def:crs:EPSG:6.11:4326')
603
+ _set(context, recobj, 'pycsw:DistanceUOM', 'degrees')
604
+ else:
605
+ bbox = md.contents[layer].boundingBox
606
+ if bbox:
607
+ tmp = '%s,%s,%s,%s' % (bbox[0], bbox[1], bbox[2], bbox[3])
608
+ _set(context, recobj, 'pycsw:BoundingBox', util.bbox2wktpolygon(tmp))
609
+ _set(context, recobj, 'pycsw:CRS', 'urn:ogc:def:crs:EPSG:6.11:%s' % \
610
+ bbox[-1].split(':')[1])
611
+
612
+ _set(context, recobj, 'pycsw:Contacts',
613
+ json.dumps(contacts,default=lambda o: o.__dict__))
614
+
615
+ params = {
616
+ 'service': 'WMTS',
617
+ 'version': '1.0.0',
618
+ 'request': 'GetTile',
619
+ 'layer': md.contents[layer].name,
620
+ }
621
+
622
+ links = [{
623
+ 'name': md.contents[layer].name,
624
+ 'description': 'OGC-Web Map Tile Service',
625
+ 'protocol': 'OGC:WMTS',
626
+ 'url': md.url
627
+ }, {
628
+ 'name': md.contents[layer].name,
629
+ 'description': 'Web image thumbnail (URL)',
630
+ 'protocol': 'WWW:LINK-1.0-http--image-thumbnai',
631
+ 'url': build_get_url(md.url, params)
632
+ }]
633
+
634
+ _set(context, recobj, 'pycsw:Links', json.dumps(links))
635
+ _set(context, recobj, 'pycsw:XML', caps2iso(recobj, md, context))
636
+ _set(context, recobj, 'pycsw:MetadataType', 'application/xml')
637
+
638
+ recobjs.append(recobj)
639
+
640
+ return recobjs
641
+
642
+
643
+ def _parse_wfs(context, repos, record, identifier, version):
644
+
645
+ import requests
646
+ from owslib.wfs import WebFeatureService
647
+
648
+ bboxs = []
649
+ recobjs = []
650
+ serviceobj = repos.dataset()
651
+
652
+ try:
653
+ md = WebFeatureService(record, version)
654
+ except requests.exceptions.HTTPError as err:
655
+ raise
656
+ except Exception as err:
657
+ if version == '1.1.0':
658
+ md = WebFeatureService(record, '1.0.0')
659
+
660
+ # generate record of service instance
661
+ _set(context, serviceobj, 'pycsw:Identifier', identifier)
662
+ _set(context, serviceobj, 'pycsw:Typename', 'csw:Record')
663
+ _set(context, serviceobj, 'pycsw:Schema', 'http://www.opengis.net/wfs')
664
+ _set(context, serviceobj, 'pycsw:MdSource', record)
665
+ _set(context, serviceobj, 'pycsw:InsertDate', util.get_today_and_now())
666
+ _set(
667
+ context,
668
+ serviceobj,
669
+ 'pycsw:AnyText',
670
+ util.get_anytext(etree.tostring(md._capabilities))
671
+ )
672
+ _set(context, serviceobj, 'pycsw:Type', 'service')
673
+ _set(context, serviceobj, 'pycsw:Title', md.identification.title)
674
+ _set(context, serviceobj, 'pycsw:Abstract', md.identification.abstract)
675
+ _set(context, serviceobj, 'pycsw:Keywords', ','.join(md.identification.keywords))
676
+ _set(context, serviceobj, 'pycsw:Creator', md.provider.contact.name)
677
+ _set(context, serviceobj, 'pycsw:Publisher', md.provider.name)
678
+ _set(context, serviceobj, 'pycsw:Contributor', md.provider.contact.name)
679
+ _set(context, serviceobj, 'pycsw:OrganizationName', md.provider.contact.name)
680
+
681
+ contacts = [vars(md.provider.contact)]
682
+ contacts[0]['role'] = 'pointOfContact'
683
+ _set(context, serviceobj, 'pycsw:Contacts',
684
+ json.dumps(contacts,default=lambda o: o.__dict__))
685
+
686
+ _set(context, serviceobj, 'pycsw:AccessConstraints', md.identification.accessconstraints)
687
+ _set(context, serviceobj, 'pycsw:OtherConstraints', md.identification.fees)
688
+ _set(context, serviceobj, 'pycsw:Source', record)
689
+ _set(context, serviceobj, 'pycsw:Format', md.identification.type)
690
+ _set(context, serviceobj, 'pycsw:CRS', 'urn:ogc:def:crs:EPSG:6.11:4326')
691
+ _set(context, serviceobj, 'pycsw:DistanceUOM', 'degrees')
692
+ _set(context, serviceobj, 'pycsw:ServiceType', 'OGC:WFS')
693
+ _set(context, serviceobj, 'pycsw:ServiceTypeVersion', md.identification.version)
694
+ _set(context, serviceobj, 'pycsw:Operation', ','.join([d.name for d in md.operations]))
695
+ _set(context, serviceobj, 'pycsw:OperatesOn', ','.join(list(md.contents)))
696
+ _set(context, serviceobj, 'pycsw:CouplingType', 'tight')
697
+
698
+ links = [{
699
+ 'name': identifier,
700
+ 'description': 'OGC-WFS Web Feature Service',
701
+ 'protocol': 'OGC:WFS',
702
+ 'url': md.url
703
+ }]
704
+
705
+ _set(context, serviceobj, 'pycsw:Links', json.dumps(links))
706
+
707
+ # generate record foreach featuretype
708
+
709
+ LOGGER.info('Harvesting %d WFS featuretypes', len(md.contents))
710
+
711
+ for featuretype in md.contents:
712
+ recobj = repos.dataset()
713
+ identifier2 = '%s-%s' % (identifier, md.contents[featuretype].id)
714
+ _set(context, recobj, 'pycsw:Identifier', identifier2)
715
+ _set(context, recobj, 'pycsw:Typename', 'csw:Record')
716
+ _set(context, recobj, 'pycsw:Schema', 'http://www.opengis.net/wfs')
717
+ _set(context, recobj, 'pycsw:MdSource', record)
718
+ _set(context, recobj, 'pycsw:InsertDate', util.get_today_and_now())
719
+ _set(context, recobj, 'pycsw:Type', 'dataset')
720
+ _set(context, recobj, 'pycsw:ParentIdentifier', identifier)
721
+ _set(context, recobj, 'pycsw:Title', md.contents[featuretype].title)
722
+ _set(context, recobj, 'pycsw:Abstract', md.contents[featuretype].abstract)
723
+ _set(context, recobj, 'pycsw:Keywords', ','.join(md.contents[featuretype].keywords))
724
+
725
+ _set(
726
+ context,
727
+ recobj,
728
+ 'pycsw:AnyText',
729
+ util.get_anytext([
730
+ md.contents[featuretype].title,
731
+ md.contents[featuretype].abstract,
732
+ ','.join(md.contents[featuretype].keywords)
733
+ ])
734
+ )
735
+
736
+ bbox = md.contents[featuretype].boundingBoxWGS84
737
+ if bbox is not None:
738
+ tmp = '%s,%s,%s,%s' % (bbox[0], bbox[1], bbox[2], bbox[3])
739
+ wkt_polygon = util.bbox2wktpolygon(tmp)
740
+ _set(context, recobj, 'pycsw:BoundingBox', wkt_polygon)
741
+ _set(context, recobj, 'pycsw:CRS', 'urn:ogc:def:crs:EPSG:6.11:4326')
742
+ _set(context, recobj, 'pycsw:DistanceUOM', 'degrees')
743
+ bboxs.append(wkt_polygon)
744
+
745
+ params = {
746
+ 'service': 'WFS',
747
+ 'version': '1.1.0',
748
+ 'request': 'GetFeature',
749
+ 'typename': md.contents[featuretype].id,
750
+ }
751
+
752
+ links = [{
753
+ 'name': md.contents[featuretype].id,
754
+ 'description': 'OGC-Web Feature Service',
755
+ 'protocol': 'OGC:WFS',
756
+ 'url': md.url
757
+ }, {
758
+ 'name': md.contents[featuretype].id,
759
+ 'description': 'File for download',
760
+ 'protocol': 'WWW:DOWNLOAD-1.0-http--download',
761
+ 'url': build_get_url(md.url, params)
762
+ }]
763
+
764
+ _set(context, recobj, 'pycsw:Links', json.dumps(links))
765
+ _set(context, recobj, 'pycsw:XML', caps2iso(recobj, md, context))
766
+ _set(context, recobj, 'pycsw:MetadataType', 'application/xml')
767
+ _set(context, recobj, 'pycsw:Contacts',
768
+ json.dumps(contacts,default=lambda o: o.__dict__))
769
+
770
+ recobjs.append(recobj)
771
+
772
+ # Derive a bbox based on aggregated featuretype bbox values
773
+
774
+ bbox_agg = bbox_from_polygons(bboxs)
775
+
776
+ if bbox_agg is not None:
777
+ _set(context, serviceobj, 'pycsw:BoundingBox', bbox_agg)
778
+
779
+ _set(context, serviceobj, 'pycsw:XML', caps2iso(serviceobj, md, context))
780
+
781
+ recobjs.insert(0, serviceobj)
782
+
783
+ return recobjs
784
+
785
+ def _parse_wcs(context, repos, record, identifier):
786
+
787
+ from owslib.wcs import WebCoverageService
788
+
789
+ bboxs = []
790
+ recobjs = []
791
+ serviceobj = repos.dataset()
792
+
793
+ md = WebCoverageService(record, '1.0.0')
794
+
795
+ # generate record of service instance
796
+ _set(context, serviceobj, 'pycsw:Identifier', identifier)
797
+ _set(context, serviceobj, 'pycsw:Typename', 'csw:Record')
798
+ _set(context, serviceobj, 'pycsw:Schema', 'http://www.opengis.net/wcs')
799
+ _set(context, serviceobj, 'pycsw:MdSource', record)
800
+ _set(context, serviceobj, 'pycsw:InsertDate', util.get_today_and_now())
801
+ _set(
802
+ context,
803
+ serviceobj,
804
+ 'pycsw:AnyText',
805
+ util.get_anytext(etree.tostring(md._capabilities))
806
+ )
807
+ _set(context, serviceobj, 'pycsw:Type', 'service')
808
+ _set(context, serviceobj, 'pycsw:Title', md.identification.title)
809
+ _set(context, serviceobj, 'pycsw:Abstract', md.identification.abstract)
810
+ _set(context, serviceobj, 'pycsw:Keywords', ','.join(md.identification.keywords))
811
+ _set(context, serviceobj, 'pycsw:Creator', md.provider.contact.name)
812
+ _set(context, serviceobj, 'pycsw:Publisher', md.provider.name)
813
+ _set(context, serviceobj, 'pycsw:Contributor', md.provider.contact.name)
814
+ _set(context, serviceobj, 'pycsw:OrganizationName', md.provider.contact.name)
815
+
816
+ contacts = [vars(md.provider.contact)]
817
+ contacts[0]['role'] = 'pointOfContact'
818
+ _set(context, serviceobj, 'pycsw:Contacts',
819
+ json.dumps(contacts,default=lambda o: o.__dict__))
820
+
821
+ _set(context, serviceobj, 'pycsw:AccessConstraints', md.identification.accessConstraints)
822
+ _set(context, serviceobj, 'pycsw:OtherConstraints', md.identification.fees)
823
+ _set(context, serviceobj, 'pycsw:Source', record)
824
+ _set(context, serviceobj, 'pycsw:Format', md.identification.type)
825
+ _set(context, serviceobj, 'pycsw:CRS', 'urn:ogc:def:crs:EPSG:6.11:4326')
826
+ _set(context, serviceobj, 'pycsw:DistanceUOM', 'degrees')
827
+ _set(context, serviceobj, 'pycsw:ServiceType', 'OGC:WCS')
828
+ _set(context, serviceobj, 'pycsw:ServiceTypeVersion', md.identification.version)
829
+ _set(context, serviceobj, 'pycsw:Operation', ','.join([d.name for d in md.operations]))
830
+ _set(context, serviceobj, 'pycsw:OperatesOn', ','.join(list(md.contents)))
831
+ _set(context, serviceobj, 'pycsw:CouplingType', 'tight')
832
+
833
+ links = [{
834
+ 'name': identifier,
835
+ 'description': 'OGC-WCS Web Coverage Service',
836
+ 'protocol': 'OGC:WCS',
837
+ 'url': md.url
838
+ }]
839
+
840
+ _set(context, serviceobj, 'pycsw:Links', json.dumps(links))
841
+
842
+ # generate record foreach coverage
843
+
844
+ LOGGER.info('Harvesting %d WCS coverages ', len(md.contents))
845
+
846
+ for coverage in md.contents:
847
+ recobj = repos.dataset()
848
+ identifier2 = '%s-%s' % (identifier, md.contents[coverage].id)
849
+ _set(context, recobj, 'pycsw:Identifier', identifier2)
850
+ _set(context, recobj, 'pycsw:Typename', 'csw:Record')
851
+ _set(context, recobj, 'pycsw:Schema', 'http://www.opengis.net/wcs')
852
+ _set(context, recobj, 'pycsw:MdSource', record)
853
+ _set(context, recobj, 'pycsw:InsertDate', util.get_today_and_now())
854
+ _set(context, recobj, 'pycsw:Type', 'dataset')
855
+ _set(context, recobj, 'pycsw:ParentIdentifier', identifier)
856
+ _set(context, recobj, 'pycsw:Title', md.contents[coverage].title)
857
+ _set(context, recobj, 'pycsw:Abstract', md.contents[coverage].abstract)
858
+ _set(context, recobj, 'pycsw:Keywords', ','.join(md.contents[coverage].keywords))
859
+
860
+ _set(
861
+ context,
862
+ recobj,
863
+ 'pycsw:AnyText',
864
+ util.get_anytext([
865
+ md.contents[coverage].title,
866
+ md.contents[coverage].abstract,
867
+ ','.join(md.contents[coverage].keywords)
868
+ ])
869
+ )
870
+
871
+ bbox = md.contents[coverage].boundingBoxWGS84
872
+ if bbox is not None:
873
+ tmp = '%s,%s,%s,%s' % (bbox[0], bbox[1], bbox[2], bbox[3])
874
+ wkt_polygon = util.bbox2wktpolygon(tmp)
875
+ _set(context, recobj, 'pycsw:BoundingBox', wkt_polygon)
876
+ _set(context, recobj, 'pycsw:CRS', 'urn:ogc:def:crs:EPSG:6.11:4326')
877
+ _set(context, recobj, 'pycsw:DistanceUOM', 'degrees')
878
+ bboxs.append(wkt_polygon)
879
+
880
+ links = [{
881
+ 'name': md.contents[coverage].id,
882
+ 'description': 'OGC-Web Coverage Service',
883
+ 'protocol': 'OGC:WCS',
884
+ 'url': md.url
885
+ }]
886
+
887
+ _set(context, recobj, 'pycsw:Links', json.dumps(links))
888
+ _set(context, recobj, 'pycsw:Contacts',
889
+ json.dumps(contacts,default=lambda o: o.__dict__))
890
+ _set(context, recobj, 'pycsw:XML', caps2iso(recobj, md, context))
891
+
892
+ recobjs.append(recobj)
893
+
894
+ # Derive a bbox based on aggregated coverage bbox values
895
+
896
+ bbox_agg = bbox_from_polygons(bboxs)
897
+
898
+ if bbox_agg is not None:
899
+ _set(context, serviceobj, 'pycsw:BoundingBox', bbox_agg)
900
+
901
+ _set(context, serviceobj, 'pycsw:XML', caps2iso(serviceobj, md, context))
902
+ recobjs.insert(0, serviceobj)
903
+
904
+ return recobjs
905
+
906
+ def _parse_wps(context, repos, record, identifier):
907
+
908
+ from owslib.wps import WebProcessingService
909
+
910
+ recobjs = []
911
+ serviceobj = repos.dataset()
912
+
913
+ md = WebProcessingService(record)
914
+
915
+ # generate record of service instance
916
+ _set(context, serviceobj, 'pycsw:Identifier', identifier)
917
+ _set(context, serviceobj, 'pycsw:Typename', 'csw:Record')
918
+ _set(context, serviceobj, 'pycsw:Schema', 'http://www.opengis.net/wps/1.0.0')
919
+ _set(context, serviceobj, 'pycsw:MdSource', record)
920
+ _set(context, serviceobj, 'pycsw:InsertDate', util.get_today_and_now())
921
+ _set(
922
+ context,
923
+ serviceobj,
924
+ 'pycsw:AnyText',
925
+ util.get_anytext(md._capabilities)
926
+ )
927
+ _set(context, serviceobj, 'pycsw:Type', 'service')
928
+ _set(context, serviceobj, 'pycsw:Title', md.identification.title)
929
+ _set(context, serviceobj, 'pycsw:Abstract', md.identification.abstract)
930
+ _set(context, serviceobj, 'pycsw:Keywords', ','.join(md.identification.keywords))
931
+ _set(context, serviceobj, 'pycsw:Creator', md.provider.contact.name)
932
+ _set(context, serviceobj, 'pycsw:Publisher', md.provider.name)
933
+ _set(context, serviceobj, 'pycsw:Contributor', md.provider.contact.name)
934
+ _set(context, serviceobj, 'pycsw:OrganizationName', md.provider.contact.name)
935
+
936
+ contacts = [vars(md.provider.contact)]
937
+ contacts[0]['role'] = 'pointOfContact'
938
+ _set(context, serviceobj, 'pycsw:Contacts',
939
+ json.dumps(contacts,default=lambda o: o.__dict__))
940
+
941
+ _set(context, serviceobj, 'pycsw:AccessConstraints', md.identification.accessconstraints)
942
+ _set(context, serviceobj, 'pycsw:OtherConstraints', md.identification.fees)
943
+ _set(context, serviceobj, 'pycsw:Source', record)
944
+ _set(context, serviceobj, 'pycsw:Format', md.identification.type)
945
+
946
+ _set(context, serviceobj, 'pycsw:ServiceType', 'OGC:WPS')
947
+ _set(context, serviceobj, 'pycsw:ServiceTypeVersion', md.identification.version)
948
+ _set(context, serviceobj, 'pycsw:Operation', ','.join([d.name for d in md.operations]))
949
+ _set(context, serviceobj, 'pycsw:OperatesOn', ','.join([o.identifier for o in md.processes]))
950
+ _set(context, serviceobj, 'pycsw:CouplingType', 'loose')
951
+
952
+ links = [{
953
+ 'name': identifier,
954
+ 'description': 'OGC-WPS Web Processing Service',
955
+ 'protocol': 'OGC:WPS',
956
+ 'url': md.url
957
+ }, {
958
+ 'name': identifier,
959
+ 'description': 'OGC-WPS Capabilities service (ver 1.0.0)',
960
+ 'protocol': 'OGC:WPS-1.1.0-http-get-capabilities',
961
+ 'url': build_get_url(md.url, {'service': 'WPS', 'version': '1.0.0', 'request': 'GetCapabilities'})
962
+ }]
963
+
964
+ _set(context, serviceobj, 'pycsw:Links', json.dumps(links))
965
+ _set(context, serviceobj, 'pycsw:XML', caps2iso(serviceobj, md, context))
966
+ _set(context, serviceobj, 'pycsw:MetadataType', 'application/xml')
967
+
968
+ recobjs.append(serviceobj)
969
+
970
+ # generate record foreach process
971
+
972
+ LOGGER.info('Harvesting %d WPS processes', len(md.processes))
973
+
974
+ for process in md.processes:
975
+ recobj = repos.dataset()
976
+ identifier2 = '%s-%s' % (identifier, process.identifier)
977
+ _set(context, recobj, 'pycsw:Identifier', identifier2)
978
+ _set(context, recobj, 'pycsw:Typename', 'csw:Record')
979
+ _set(context, recobj, 'pycsw:Schema', 'http://www.opengis.net/wps/1.0.0')
980
+ _set(context, recobj, 'pycsw:MdSource', record)
981
+ _set(context, recobj, 'pycsw:InsertDate', util.get_today_and_now())
982
+ _set(context, recobj, 'pycsw:Type', 'software')
983
+ _set(context, recobj, 'pycsw:ParentIdentifier', identifier)
984
+ _set(context, recobj, 'pycsw:Title', process.title)
985
+ _set(context, recobj, 'pycsw:Abstract', process.abstract)
986
+
987
+ _set(
988
+ context,
989
+ recobj,
990
+ 'pycsw:AnyText',
991
+ util.get_anytext([process.title, process.abstract])
992
+ )
993
+
994
+ params = {
995
+ 'service': 'WPS',
996
+ 'version': '1.0.0',
997
+ 'request': 'DescribeProcess',
998
+ 'identifier': process.identifier
999
+ }
1000
+
1001
+ links.append({
1002
+ 'name': identifier,
1003
+ 'description': 'OGC-WPS DescribeProcess service (ver 1.0.0)',
1004
+ 'protocol': 'OGC:WPS-1.0.0-http-describe-process',
1005
+ 'url': build_get_url(md.url, {'service': 'WPS', 'version': '1.0.0', 'request': 'DescribeProcess', 'identifier': process.identifier})
1006
+ })
1007
+
1008
+ _set(context, recobj, 'pycsw:Links', json.dumps(links))
1009
+ _set(context, recobj, 'pycsw:Contacts',
1010
+ json.dumps(contacts,default=lambda o: o.__dict__))
1011
+ _set(context, recobj, 'pycsw:XML', caps2iso(recobj, md, context))
1012
+ _set(context, recobj, 'pycsw:MetadataType', 'application/xml')
1013
+
1014
+ recobjs.append(recobj)
1015
+
1016
+ return recobjs
1017
+
1018
+
1019
+ def _parse_sos(context, repos, record, identifier, version):
1020
+
1021
+ from owslib.sos import SensorObservationService
1022
+
1023
+ bboxs = []
1024
+ recobjs = []
1025
+ serviceobj = repos.dataset()
1026
+
1027
+ if version == '1.0.0':
1028
+ schema = 'http://www.opengis.net/sos/1.0'
1029
+ else:
1030
+ schema = 'http://www.opengis.net/sos/2.0'
1031
+
1032
+ md = SensorObservationService(record, version=version)
1033
+
1034
+ # generate record of service instance
1035
+ _set(context, serviceobj, 'pycsw:Identifier', identifier)
1036
+ _set(context, serviceobj, 'pycsw:Typename', 'csw:Record')
1037
+ _set(context, serviceobj, 'pycsw:Schema', schema)
1038
+ _set(context, serviceobj, 'pycsw:MdSource', record)
1039
+ _set(context, serviceobj, 'pycsw:InsertDate', util.get_today_and_now())
1040
+ _set(
1041
+ context,
1042
+ serviceobj,
1043
+ 'pycsw:AnyText',
1044
+ util.get_anytext(etree.tostring(md._capabilities))
1045
+ )
1046
+ _set(context, serviceobj, 'pycsw:Type', 'service')
1047
+ _set(context, serviceobj, 'pycsw:Title', md.identification.title)
1048
+ _set(context, serviceobj, 'pycsw:Abstract', md.identification.abstract)
1049
+ _set(context, serviceobj, 'pycsw:Keywords', ','.join(md.identification.keywords))
1050
+ _set(context, serviceobj, 'pycsw:Creator', md.provider.contact.name)
1051
+ _set(context, serviceobj, 'pycsw:Publisher', md.provider.name)
1052
+ _set(context, serviceobj, 'pycsw:Contributor', md.provider.contact.name)
1053
+ _set(context, serviceobj, 'pycsw:OrganizationName', md.provider.contact.name)
1054
+
1055
+ contacts = [vars(md.provider.contact)]
1056
+ contacts[0]['role'] = 'pointOfContact'
1057
+ _set(context, serviceobj, 'pycsw:Contacts',
1058
+ json.dumps(contacts,default=lambda o: o.__dict__))
1059
+
1060
+ _set(context, serviceobj, 'pycsw:AccessConstraints', md.identification.accessconstraints)
1061
+ _set(context, serviceobj, 'pycsw:OtherConstraints', md.identification.fees)
1062
+ _set(context, serviceobj, 'pycsw:Source', record)
1063
+ _set(context, serviceobj, 'pycsw:Format', md.identification.type)
1064
+ _set(context, serviceobj, 'pycsw:CRS', 'urn:ogc:def:crs:EPSG:6.11:4326')
1065
+ _set(context, serviceobj, 'pycsw:DistanceUOM', 'degrees')
1066
+ _set(context, serviceobj, 'pycsw:ServiceType', 'OGC:SOS')
1067
+ _set(context, serviceobj, 'pycsw:ServiceTypeVersion', md.identification.version)
1068
+ _set(context, serviceobj, 'pycsw:Operation', ','.join([d.name for d in md.operations]))
1069
+ _set(context, serviceobj, 'pycsw:OperatesOn', ','.join(list(md.contents)))
1070
+ _set(context, serviceobj, 'pycsw:CouplingType', 'tight')
1071
+
1072
+ links = [{
1073
+ 'name': identifier,
1074
+ 'description': 'OGC-SOS Sensor Observation Service',
1075
+ 'protocol': 'OGC:SOS',
1076
+ 'url': md.url
1077
+ }]
1078
+
1079
+ _set(context, serviceobj, 'pycsw:Links', json.dumps(links))
1080
+
1081
+ # generate record foreach offering
1082
+
1083
+ LOGGER.info('Harvesting %d SOS ObservationOffering\'s ', len(md.contents))
1084
+
1085
+ for offering in md.contents:
1086
+ recobj = repos.dataset()
1087
+ identifier2 = '%s-%s' % (identifier, md.contents[offering].id)
1088
+ _set(context, recobj, 'pycsw:Identifier', identifier2)
1089
+ _set(context, recobj, 'pycsw:Typename', 'csw:Record')
1090
+ _set(context, recobj, 'pycsw:Schema', schema)
1091
+ _set(context, recobj, 'pycsw:MdSource', record)
1092
+ _set(context, recobj, 'pycsw:InsertDate', util.get_today_and_now())
1093
+ _set(context, recobj, 'pycsw:Type', 'dataset')
1094
+ _set(context, recobj, 'pycsw:ParentIdentifier', identifier)
1095
+ _set(context, recobj, 'pycsw:Title', md.contents[offering].description)
1096
+ _set(context, recobj, 'pycsw:Abstract', md.contents[offering].description)
1097
+ begin = md.contents[offering].begin_position
1098
+ end = md.contents[offering].end_position
1099
+ _set(context, recobj, 'pycsw:TempExtent_begin',
1100
+ util.datetime2iso8601(begin) if begin is not None else None)
1101
+ _set(context, recobj, 'pycsw:TempExtent_end',
1102
+ util.datetime2iso8601(end) if end is not None else None)
1103
+
1104
+ #For observed_properties that have mmi url or urn, we simply want the observation name.
1105
+ observed_properties = []
1106
+ for obs in md.contents[offering].observed_properties:
1107
+ #Observation is stored as urn representation: urn:ogc:def:phenomenon:mmisw.org:cf:sea_water_salinity
1108
+ if obs.lower().startswith(('urn:', 'x-urn')):
1109
+ observed_properties.append(obs.rsplit(':', 1)[-1])
1110
+ #Observation is stored as uri representation: http://mmisw.org/ont/cf/parameter/sea_floor_depth_below_sea_surface
1111
+ elif obs.lower().startswith(('http://', 'https://')):
1112
+ observed_properties.append(obs.rsplit('/', 1)[-1])
1113
+ else:
1114
+ observed_properties.append(obs)
1115
+ #Build anytext from description and the observed_properties.
1116
+ anytext = []
1117
+ anytext.append(md.contents[offering].description)
1118
+ anytext.extend(observed_properties)
1119
+ _set(context, recobj, 'pycsw:AnyText', util.get_anytext(anytext))
1120
+ _set(context, recobj, 'pycsw:Keywords', ','.join(observed_properties))
1121
+
1122
+ bbox = md.contents[offering].bbox
1123
+ if bbox is not None:
1124
+ tmp = '%s,%s,%s,%s' % (bbox[0], bbox[1], bbox[2], bbox[3])
1125
+ wkt_polygon = util.bbox2wktpolygon(tmp)
1126
+ _set(context, recobj, 'pycsw:BoundingBox', wkt_polygon)
1127
+ _set(context, recobj, 'pycsw:CRS', md.contents[offering].bbox_srs.id)
1128
+ _set(context, recobj, 'pycsw:DistanceUOM', 'degrees')
1129
+ bboxs.append(wkt_polygon)
1130
+ _set(context, recobj, 'pycsw:Contacts',
1131
+ json.dumps(contacts,default=lambda o: o.__dict__))
1132
+ _set(context, recobj, 'pycsw:XML', caps2iso(recobj, md, context))
1133
+ _set(context, recobj, 'pycsw:MetadataType', 'application/xml')
1134
+ recobjs.append(recobj)
1135
+
1136
+ # Derive a bbox based on aggregated featuretype bbox values
1137
+
1138
+ bbox_agg = bbox_from_polygons(bboxs)
1139
+
1140
+ if bbox_agg is not None:
1141
+ _set(context, serviceobj, 'pycsw:BoundingBox', bbox_agg)
1142
+
1143
+ _set(context, serviceobj, 'pycsw:XML', caps2iso(serviceobj, md, context))
1144
+ recobjs.insert(0, serviceobj)
1145
+
1146
+ return recobjs
1147
+
1148
+
1149
+ def _parse_fgdc(context, repos, exml):
1150
+
1151
+ from owslib.fgdc import Metadata
1152
+
1153
+ recobj = repos.dataset()
1154
+ links = []
1155
+
1156
+ md = Metadata(exml)
1157
+
1158
+ if md.idinfo.datasetid is not None: # we need an identifier
1159
+ _set(context, recobj, 'pycsw:Identifier', md.idinfo.datasetid)
1160
+ else: # generate one ourselves
1161
+ _set(context, recobj, 'pycsw:Identifier', uuid.uuid1().urn)
1162
+
1163
+ _set(context, recobj, 'pycsw:Typename', 'fgdc:metadata')
1164
+ _set(context, recobj, 'pycsw:Schema', context.namespaces['fgdc'])
1165
+ _set(context, recobj, 'pycsw:MdSource', 'local')
1166
+ _set(context, recobj, 'pycsw:InsertDate', util.get_today_and_now())
1167
+ _set(context, recobj, 'pycsw:XML', md.xml)
1168
+ _set(context, recobj, 'pycsw:MetadataType', 'application/xml')
1169
+ _set(context, recobj, 'pycsw:AnyText', util.get_anytext(exml))
1170
+ _set(context, recobj, 'pycsw:Language', 'en-US')
1171
+
1172
+ if hasattr(md.idinfo, 'descript'):
1173
+ _set(context, recobj, 'pycsw:Abstract', md.idinfo.descript.abstract)
1174
+
1175
+ if hasattr(md.idinfo, 'keywords'):
1176
+ if md.idinfo.keywords.theme:
1177
+ _set(context, recobj, 'pycsw:Keywords', \
1178
+ ','.join(md.idinfo.keywords.theme[0]['themekey']))
1179
+
1180
+ if hasattr(md.idinfo.timeperd, 'timeinfo'):
1181
+ if hasattr(md.idinfo.timeperd.timeinfo, 'rngdates'):
1182
+ _set(context, recobj, 'pycsw:TempExtent_begin',
1183
+ md.idinfo.timeperd.timeinfo.rngdates.begdate)
1184
+ _set(context, recobj, 'pycsw:TempExtent_end',
1185
+ md.idinfo.timeperd.timeinfo.rngdates.enddate)
1186
+
1187
+ if hasattr(md.idinfo, 'origin'):
1188
+ _set(context, recobj, 'pycsw:Creator', md.idinfo.origin)
1189
+ _set(context, recobj, 'pycsw:Publisher', md.idinfo.origin)
1190
+ _set(context, recobj, 'pycsw:Contributor', md.idinfo.origin)
1191
+
1192
+ contacts = []
1193
+ if hasattr(md.idinfo, 'ptcontac'):
1194
+ _set(context, recobj, 'pycsw:OrganizationName', md.idinfo.ptcontac.cntorg)
1195
+ contacts.append(fgdccontact2iso(md.idinfo.ptcontac, 'pointOfContact'))
1196
+
1197
+ if hasattr(md.metainfo, 'metc'):
1198
+ contacts.append(fgdccontact2iso(md.metainfo.metc, 'pointOfContact'))
1199
+
1200
+ if hasattr(md.distinfo, 'distrib'):
1201
+ contacts.append(fgdccontact2iso(md.distinfo.distrib, 'distributor'))
1202
+
1203
+ if len(contacts) > 0:
1204
+ _set(context, recobj, 'pycsw:Contacts', json.dumps(contacts))
1205
+
1206
+ _set(context, recobj, 'pycsw:AccessConstraints', md.idinfo.accconst)
1207
+ _set(context, recobj, 'pycsw:OtherConstraints', md.idinfo.useconst)
1208
+ _set(context, recobj, 'pycsw:Date', md.metainfo.metd)
1209
+
1210
+ if hasattr(md.idinfo, 'spdom') and hasattr(md.idinfo.spdom, 'bbox'):
1211
+ bbox = md.idinfo.spdom.bbox
1212
+ else:
1213
+ bbox = None
1214
+
1215
+ if hasattr(md.idinfo, 'citation'):
1216
+ if hasattr(md.idinfo.citation, 'citeinfo'):
1217
+ _set(context, recobj, 'pycsw:Type', md.idinfo.citation.citeinfo['geoform'])
1218
+ _set(context, recobj, 'pycsw:Title', md.idinfo.citation.citeinfo['title'])
1219
+ _set(context, recobj,
1220
+ 'pycsw:PublicationDate', md.idinfo.citation.citeinfo['pubdate'])
1221
+ _set(context, recobj, 'pycsw:Format', md.idinfo.citation.citeinfo['geoform'])
1222
+ if md.idinfo.citation.citeinfo['onlink']:
1223
+ for link in md.idinfo.citation.citeinfo['onlink']:
1224
+ links.append({
1225
+ 'name': None,
1226
+ 'description': None,
1227
+ 'protocol': None,
1228
+ 'url': link
1229
+ })
1230
+
1231
+ if hasattr(md, 'distinfo') and hasattr(md.distinfo, 'stdorder'):
1232
+ for link in md.distinfo.stdorder['digform']:
1233
+ tmp = ',%s,,%s' % (link['name'], link['url'])
1234
+ links.append({
1235
+ 'name': None,
1236
+ 'description': link['name'],
1237
+ 'protocol': None,
1238
+ 'url': link['url']
1239
+ })
1240
+
1241
+ if len(links) > 0:
1242
+ _set(context, recobj, 'pycsw:Links', json.dumps(links))
1243
+
1244
+ if bbox is not None:
1245
+ try:
1246
+ tmp = '%s,%s,%s,%s' % (bbox.minx, bbox.miny, bbox.maxx, bbox.maxy)
1247
+ _set(context, recobj, 'pycsw:BoundingBox', util.bbox2wktpolygon(tmp))
1248
+ except Exception:
1249
+ LOGGER.debug('Coordinates are corrupt')
1250
+ _set(context, recobj, 'pycsw:BoundingBox', None)
1251
+ else:
1252
+ _set(context, recobj, 'pycsw:BoundingBox', None)
1253
+
1254
+ return recobj
1255
+
1256
+ def _parse_gm03(context, repos, exml):
1257
+
1258
+ def get_value_by_language(pt_group, language, pt_type='text'):
1259
+ for ptg in pt_group:
1260
+ if ptg.language == language:
1261
+ if pt_type == 'url':
1262
+ val = ptg.plain_url
1263
+ else: # 'text'
1264
+ val = ptg.plain_text
1265
+ return val
1266
+
1267
+ from owslib.gm03 import GM03
1268
+
1269
+ recobj = repos.dataset()
1270
+ links = []
1271
+
1272
+ md = GM03(exml)
1273
+
1274
+ if hasattr(md.data, 'core'):
1275
+ data = md.data.core
1276
+ elif hasattr(md.data, 'comprehensive'):
1277
+ data = md.data.comprehensive
1278
+
1279
+ language = data.metadata.language
1280
+
1281
+ _set(context, recobj, 'pycsw:Identifier', data.metadata.file_identifier)
1282
+ _set(context, recobj, 'pycsw:Typename', 'gm03:TRANSFER')
1283
+ _set(context, recobj, 'pycsw:Schema', context.namespaces['gm03'])
1284
+ _set(context, recobj, 'pycsw:MdSource', 'local')
1285
+ _set(context, recobj, 'pycsw:InsertDate', util.get_today_and_now())
1286
+ _set(context, recobj, 'pycsw:XML', md.xml)
1287
+ _set(context, recobj, 'pycsw:MetadataType', 'application/xml')
1288
+ _set(context, recobj, 'pycsw:AnyText', util.get_anytext(exml))
1289
+ _set(context, recobj, 'pycsw:Language', data.metadata.language)
1290
+ _set(context, recobj, 'pycsw:Type', data.metadata.hierarchy_level[0])
1291
+ _set(context, recobj, 'pycsw:Date', data.metadata.date_stamp)
1292
+
1293
+ for dt in data.date:
1294
+ if dt.date_type == 'modified':
1295
+ _set(context, recobj, 'pycsw:Modified', dt.date)
1296
+ elif dt.date_type == 'creation':
1297
+ _set(context, recobj, 'pycsw:CreationDate', dt.date)
1298
+ elif dt.date_type == 'publication':
1299
+ _set(context, recobj, 'pycsw:PublicationDate', dt.date)
1300
+ elif dt.date_type == 'revision':
1301
+ _set(context, recobj, 'pycsw:RevisionDate', dt.date)
1302
+
1303
+ if hasattr(data, 'metadata_point_of_contact'):
1304
+ _set(context, recobj, 'pycsw:ResponsiblePartyRole', data.metadata_point_of_contact.role)
1305
+
1306
+ _set(context, recobj, 'pycsw:Source', data.metadata.dataset_uri)
1307
+ _set(context, recobj, 'pycsw:CRS','urn:ogc:def:crs:EPSG:6.11:4326')
1308
+
1309
+ if hasattr(data, 'citation'):
1310
+ _set(context, recobj, 'pycsw:Title', get_value_by_language(data.citation.title.pt_group, language))
1311
+
1312
+ if hasattr(data, 'data_identification'):
1313
+ _set(context, recobj, 'pycsw:Abstract', get_value_by_language(data.data_identification.abstract.pt_group, language))
1314
+ if hasattr(data.data_identification, 'topic_category'):
1315
+ _set(context, recobj, 'pycsw:TopicCategory', data.data_identification.topic_category)
1316
+ _set(context, recobj, 'pycsw:ResourceLanguage', data.data_identification.language)
1317
+
1318
+ if hasattr(data, 'format'):
1319
+ _set(context, recobj, 'pycsw:Format', data.format.name)
1320
+
1321
+ # bbox
1322
+ if hasattr(data, 'geographic_bounding_box'):
1323
+ try:
1324
+ tmp = '%s,%s,%s,%s' % (data.geographic_bounding_box.west_bound_longitude,
1325
+ data.geographic_bounding_box.south_bound_latitude,
1326
+ data.geographic_bounding_box.east_bound_longitude,
1327
+ data.geographic_bounding_box.north_bound_latitude)
1328
+ _set(context, recobj, 'pycsw:BoundingBox', util.bbox2wktpolygon(tmp))
1329
+ except Exception:
1330
+ LOGGER.debug('Coordinates are corrupt')
1331
+ _set(context, recobj, 'pycsw:BoundingBox', None)
1332
+ else:
1333
+ _set(context, recobj, 'pycsw:BoundingBox', None)
1334
+
1335
+ # temporal extent
1336
+ if hasattr(data, 'temporal_extent'):
1337
+ if data.temporal_extent.extent['begin'] is not None and data.temporal_extent.extent['end'] is not None:
1338
+ _set(context, recobj, 'pycsw:TempExtent_begin', data.temporal_extent.extent['begin'])
1339
+ _set(context, recobj, 'pycsw:TempExtent_end', data.temporal_extent.extent['end'])
1340
+
1341
+ # online linkages
1342
+ name = description = protocol = ''
1343
+
1344
+ if hasattr(data, 'online_resource'):
1345
+ if hasattr(data.online_resource, 'name'):
1346
+ name = get_value_by_language(data.online_resource.name.pt_group, language)
1347
+ if hasattr(data.online_resource, 'description'):
1348
+ description = get_value_by_language(data.online_resource.description.pt_group, language)
1349
+ linkage = get_value_by_language(data.online_resource.linkage.pt_group, language, 'url')
1350
+
1351
+ links.append({
1352
+ 'name': name,
1353
+ 'description': description,
1354
+ 'protocol': protocol,
1355
+ 'url': linkage
1356
+ })
1357
+
1358
+ if len(links) > 0:
1359
+ _set(context, recobj, 'pycsw:Links', json.dumps(links))
1360
+
1361
+ keywords = []
1362
+ for kw in data.keywords:
1363
+ keywords.append(get_value_by_language(kw.keyword.pt_group, language))
1364
+ _set(context, recobj, 'pycsw:Keywords', ','.join(keywords))
1365
+
1366
+ # contacts
1367
+ return recobj
1368
+
1369
+ def _parse_iso(context, repos, exml):
1370
+ """ Parses ISO 19139, ISO 19115p3 """
1371
+
1372
+ from owslib.iso import MD_ImageDescription, MD_Metadata, SV_ServiceIdentification
1373
+ from owslib.iso_che import CHE_MD_Metadata
1374
+
1375
+ recobj = repos.dataset()
1376
+ bbox = None
1377
+ links = []
1378
+ mdmeta_ns = 'gmd'
1379
+
1380
+ if exml.tag == '{http://www.geocat.ch/2008/che}CHE_MD_Metadata':
1381
+ md = CHE_MD_Metadata(exml)
1382
+ elif exml.tag == '{http://standards.iso.org/iso/19115/-3/mdb/2.0}MD_Metadata':
1383
+ from owslib.iso3 import MD_Metadata
1384
+ md = MD_Metadata(exml)
1385
+ mdmeta_ns = 'mdb'
1386
+ else:
1387
+ md = MD_Metadata(exml)
1388
+
1389
+ md_identification = md.identification[0]
1390
+
1391
+ _set(context, recobj, 'pycsw:Identifier', md.identifier)
1392
+ _set(context, recobj, 'pycsw:Typename', f'{mdmeta_ns}:MD_Metadata')
1393
+ _set(context, recobj, 'pycsw:Schema', context.namespaces['gmd'])
1394
+ _set(context, recobj, 'pycsw:MdSource', 'local')
1395
+ _set(context, recobj, 'pycsw:InsertDate', util.get_today_and_now())
1396
+ _set(context, recobj, 'pycsw:XML', md.xml)
1397
+ _set(context, recobj, 'pycsw:MetadataType', 'application/xml')
1398
+ _set(context, recobj, 'pycsw:AnyText', util.get_anytext(exml))
1399
+ _set(context, recobj, 'pycsw:Language', md.language or md.languagecode)
1400
+ _set(context, recobj, 'pycsw:Type', md.hierarchy)
1401
+ _set(context, recobj, 'pycsw:ParentIdentifier', md.parentidentifier)
1402
+ _set(context, recobj, 'pycsw:Date', md.datestamp)
1403
+ _set(context, recobj, 'pycsw:Modified', md.datestamp)
1404
+ _set(context, recobj, 'pycsw:Source', md.dataseturi)
1405
+
1406
+ if md.referencesystem is not None and md.referencesystem.code is not None:
1407
+ try:
1408
+ code_ = 'urn:ogc:def:crs:EPSG::%d' % int(md.referencesystem.code)
1409
+ except ValueError:
1410
+ code_ = md.referencesystem.code
1411
+ _set(context, recobj, 'pycsw:CRS', code_)
1412
+
1413
+ if md_identification:
1414
+ _set(context, recobj, 'pycsw:Title', md_identification.title)
1415
+ _set(context, recobj, 'pycsw:Edition', md_identification.edition)
1416
+ _set(context, recobj, 'pycsw:AlternateTitle', md_identification.alternatetitle)
1417
+ _set(context, recobj, 'pycsw:Abstract', md_identification.abstract)
1418
+ _set(context, recobj, 'pycsw:Relation', md_identification.aggregationinfo)
1419
+
1420
+ if hasattr(md_identification, 'temporalextent_start'):
1421
+ _set(context, recobj, 'pycsw:TempExtent_begin', md_identification.temporalextent_start)
1422
+ if hasattr(md_identification, 'temporalextent_end'):
1423
+ _set(context, recobj, 'pycsw:TempExtent_end', md_identification.temporalextent_end)
1424
+
1425
+ if len(md_identification.topiccategory) > 0:
1426
+ _set(context, recobj, 'pycsw:TopicCategory', md_identification.topiccategory[0])
1427
+
1428
+ if len(md_identification.resourcelanguage) > 0:
1429
+ _set(context, recobj, 'pycsw:ResourceLanguage', md_identification.resourcelanguage[0])
1430
+ elif len(md_identification.resourcelanguagecode) > 0:
1431
+ _set(context, recobj, 'pycsw:ResourceLanguage', md_identification.resourcelanguagecode[0])
1432
+
1433
+ # Geographic bounding box
1434
+ if hasattr(md_identification, 'bbox'):
1435
+ bbox = md_identification.bbox
1436
+ else:
1437
+ bbox = None
1438
+
1439
+ # Vertical extent of a bounding box
1440
+ if hasattr(md_identification, 'extent'):
1441
+ if hasattr(md_identification.extent, 'vertExtMin') and \
1442
+ md_identification.extent.vertExtMin is not None:
1443
+ _set(context, recobj, 'pycsw:VertExtentMin', md_identification.extent.vertExtMin)
1444
+ if hasattr(md_identification.extent, 'vertExtMax') and \
1445
+ md_identification.extent.vertExtMax is not None:
1446
+ _set(context, recobj, 'pycsw:VertExtentMax', md_identification.extent.vertExtMax)
1447
+
1448
+ if (hasattr(md_identification, 'keywords') and
1449
+ len(md_identification.keywords) > 0):
1450
+ all_keywords = [item for sublist in md_identification.keywords for item in sublist.keywords if item is not None]
1451
+ _set(context, recobj, 'pycsw:Keywords', ','.join([
1452
+ k.name for k in all_keywords if hasattr(k,'name') and k.name not in [None,'']]))
1453
+ _set(context, recobj, 'pycsw:KeywordType', md_identification.keywords[0].type)
1454
+ _set(context, recobj, 'pycsw:Themes',
1455
+ json.dumps([t for t in md_identification.keywords if t.thesaurus is not None],
1456
+ default=lambda o: o.__dict__))
1457
+
1458
+ # Creator
1459
+ if (hasattr(md_identification, 'creator') and
1460
+ len(md_identification.creator) > 0):
1461
+ all_orgs = set([item.organization for item in md_identification.creator if hasattr(item, 'organization') and item.organization is not None])
1462
+ _set(context, recobj, 'pycsw:Creator', ';'.join(all_orgs))
1463
+ # Publisher
1464
+ if (hasattr(md_identification, 'publisher') and
1465
+ len(md_identification.publisher) > 0):
1466
+ all_orgs = set([item.organization for item in md_identification.publisher if hasattr(item, 'organization') and item.organization is not None])
1467
+ _set(context, recobj, 'pycsw:Publisher', ';'.join(all_orgs))
1468
+ # Contributor
1469
+ if (hasattr(md_identification, 'contributor') and
1470
+ len(md_identification.contributor) > 0):
1471
+ all_orgs = set([item.organization for item in md_identification.contributor if hasattr(item, 'organization') and item.organization is not None])
1472
+ _set(context, recobj, 'pycsw:Contributor', ';'.join(all_orgs))
1473
+
1474
+ if (hasattr(md_identification, 'contact') and
1475
+ len(md_identification.contact) > 0):
1476
+ all_orgs = set([item.organization for item in md_identification.contact if hasattr(item, 'organization') and item.organization is not None])
1477
+ _set(context, recobj, 'pycsw:OrganizationName', ';'.join(all_orgs))
1478
+ _set(context, recobj, 'pycsw:Contacts', json.dumps(md_identification.contact, default=lambda o: o.__dict__))
1479
+
1480
+ if len(md_identification.securityconstraints) > 0:
1481
+ _set(context, recobj, 'pycsw:SecurityConstraints',
1482
+ md_identification.securityconstraints[0])
1483
+ if len(md_identification.accessconstraints) > 0:
1484
+ _set(context, recobj, 'pycsw:AccessConstraints',
1485
+ md_identification.accessconstraints[0])
1486
+ if len(md_identification.otherconstraints) > 0:
1487
+ _set(context, recobj, 'pycsw:OtherConstraints', md_identification.otherconstraints[0])
1488
+
1489
+ if hasattr(md_identification, 'date'):
1490
+ for datenode in md_identification.date:
1491
+ if datenode.type == 'revision':
1492
+ _set(context, recobj, 'pycsw:RevisionDate', datenode.date)
1493
+ elif datenode.type == 'creation':
1494
+ _set(context, recobj, 'pycsw:CreationDate', datenode.date)
1495
+ elif datenode.type == 'publication':
1496
+ _set(context, recobj, 'pycsw:PublicationDate', datenode.date)
1497
+
1498
+ if hasattr(md_identification, 'extent') and hasattr(md_identification.extent, 'description_code'):
1499
+ _set(context, recobj, 'pycsw:GeographicDescriptionCode', md_identification.extent.description_code)
1500
+
1501
+ if len(md_identification.denominators) > 0:
1502
+ _set(context, recobj, 'pycsw:Denominator', md_identification.denominators[0])
1503
+ if len(md_identification.distance) > 0:
1504
+ _set(context, recobj, 'pycsw:DistanceValue', md_identification.distance[0])
1505
+ if len(md_identification.uom) > 0:
1506
+ _set(context, recobj, 'pycsw:DistanceUOM', md_identification.uom[0])
1507
+
1508
+ if len(md_identification.classification) > 0:
1509
+ _set(context, recobj, 'pycsw:Classification', md_identification.classification[0])
1510
+ if len(md_identification.uselimitation) > 0:
1511
+ _set(context, recobj, 'pycsw:ConditionApplyingToAccessAndUse',
1512
+ md_identification.uselimitation[0])
1513
+
1514
+ service_types = []
1515
+ from owslib.iso import SV_ServiceIdentification
1516
+ for smd in md.identification:
1517
+ if isinstance(smd, SV_ServiceIdentification):
1518
+ service_types.append(smd.type)
1519
+ _set(context, recobj, 'pycsw:ServiceTypeVersion', smd.version)
1520
+ _set(context, recobj, 'pycsw:CouplingType', smd.couplingtype)
1521
+
1522
+ _set(context, recobj, 'pycsw:ServiceType', ','.join(service_types))
1523
+
1524
+ if hasattr(md_identification, 'dataquality'):
1525
+ _set(context, recobj, 'pycsw:Degree', md.dataquality.conformancedegree)
1526
+ _set(context, recobj, 'pycsw:Lineage', md.dataquality.lineage)
1527
+ _set(context, recobj, 'pycsw:SpecificationTitle', md.dataquality.specificationtitle)
1528
+ if hasattr(md.dataquality, 'specificationdate'):
1529
+ _set(context, recobj, 'pycsw:specificationDate',
1530
+ md.dataquality.specificationdate[0].date)
1531
+ _set(context, recobj, 'pycsw:SpecificationDateType',
1532
+ md.dataquality.specificationdate[0].datetype)
1533
+
1534
+ if hasattr(md, 'contact') and len(md.contact) > 0:
1535
+ _set(context, recobj, 'pycsw:ResponsiblePartyRole', md.contact[0].role)
1536
+
1537
+
1538
+ if hasattr(md, 'contentinfo') and len(md.contentinfo) > 0:
1539
+ for ci in md.contentinfo:
1540
+ if isinstance(ci, MD_ImageDescription):
1541
+ _set(context, recobj, 'pycsw:CloudCover', ci.cloud_cover)
1542
+
1543
+ keywords = _get(context, recobj, 'pycsw:Keywords')
1544
+ if ci.processing_level is not None:
1545
+ pl_keyword = 'eo:processingLevel:' + ci.processing_level
1546
+ if keywords is None:
1547
+ keywords = pl_keyword
1548
+ else:
1549
+ keywords += ',' + pl_keyword
1550
+
1551
+ _set(context, recobj, 'pycsw:Keywords', keywords)
1552
+
1553
+ bands = []
1554
+ for band in ci.bands:
1555
+ band_info = {
1556
+ 'id': band.id,
1557
+ 'units': band.units,
1558
+ 'min': band.min,
1559
+ 'max': band.max
1560
+ }
1561
+ bands.append(band_info)
1562
+
1563
+ if len(bands) > 0:
1564
+ _set(context, recobj, 'pycsw:Bands', json.dumps(bands))
1565
+
1566
+ if hasattr(md, 'acquisition') and md.acquisition is not None:
1567
+ platform = md.acquisition.platforms[0]
1568
+ _set(context, recobj, 'pycsw:Platform', platform.identifier)
1569
+
1570
+ if platform.instruments:
1571
+ instrument = platform.instruments[0]
1572
+ _set(context, recobj, 'pycsw:Instrument', instrument.identifier)
1573
+ _set(context, recobj, 'pycsw:SensorType', instrument.type)
1574
+
1575
+ all_formats=[]
1576
+ if hasattr(md.distribution, 'format') and md.distribution.format is not None:
1577
+ all_formats.append(md.distribution.format)
1578
+
1579
+ LOGGER.info('Scanning for links')
1580
+ if hasattr(md, 'distribution'):
1581
+ dist_links = []
1582
+ if hasattr(md.distribution, 'online'):
1583
+ LOGGER.debug(f'Scanning for {mdmeta_ns}:transferOptions element(s)')
1584
+ dist_links.extend(md.distribution.online)
1585
+ if hasattr(md.distribution, 'distributor'):
1586
+ LOGGER.debug(f'Scanning for {mdmeta_ns}:distributorTransferOptions element(s)')
1587
+ for dist_member in md.distribution.distributor:
1588
+ dist_links.extend(dist_member.online)
1589
+ for link in dist_links:
1590
+ if link.url is not None and link.protocol is None: # take a best guess
1591
+ link.protocol = sniff_link(link.url)
1592
+ if (link.protocol is not None and link.protocol not in all_formats):
1593
+ all_formats.append(link.protocol)
1594
+ links.append({
1595
+ 'name': link.name,
1596
+ 'description': link.description,
1597
+ 'protocol': link.protocol,
1598
+ 'url': link.url,
1599
+ 'function': link.function
1600
+ })
1601
+
1602
+ _set(context, recobj, 'pycsw:Format', ','.join(all_formats))
1603
+
1604
+ try:
1605
+ LOGGER.debug('Scanning for srv:SV_ServiceIdentification links')
1606
+ for sident in md.identification:
1607
+ if hasattr(sident, 'operations'):
1608
+ for sops in sident.operations:
1609
+ for scpt in sops['connectpoint']:
1610
+ LOGGER.debug('adding srv link %s', scpt.url)
1611
+ linkobj = {
1612
+ 'name': scpt.name,
1613
+ 'description': scpt.description,
1614
+ 'protocol': scpt.protocol,
1615
+ 'url': scpt.url
1616
+ }
1617
+ links.append(linkobj)
1618
+ except Exception as err: # srv: identification does not exist
1619
+ LOGGER.exception('no srv:SV_ServiceIdentification links found')
1620
+
1621
+ if hasattr(md_identification, 'graphicoverview'):
1622
+ for thumb in md_identification.graphicoverview:
1623
+ links.append({
1624
+ 'name': 'preview',
1625
+ 'description': 'Web image thumbnail (URL)',
1626
+ 'protocol': 'WWW:LINK-1.0-http--image-thumbnail',
1627
+ 'url': thumb
1628
+ })
1629
+
1630
+ if len(links) > 0:
1631
+ _set(context, recobj, 'pycsw:Links', json.dumps(links))
1632
+
1633
+ if bbox is not None:
1634
+ try:
1635
+ tmp = '%s,%s,%s,%s' % (bbox.minx, bbox.miny, bbox.maxx, bbox.maxy)
1636
+ _set(context, recobj, 'pycsw:BoundingBox', util.bbox2wktpolygon(tmp))
1637
+ except Exception:
1638
+ LOGGER.debug('Coordinates are corrupt')
1639
+ _set(context, recobj, 'pycsw:BoundingBox', None)
1640
+ else:
1641
+ _set(context, recobj, 'pycsw:BoundingBox', None)
1642
+
1643
+ return recobj
1644
+
1645
+ def _parse_dc(context, repos, exml):
1646
+
1647
+ from owslib.csw import CswRecord
1648
+
1649
+ recobj = repos.dataset()
1650
+ links = []
1651
+
1652
+ md = CswRecord(exml)
1653
+
1654
+ if md.bbox is None:
1655
+ bbox = None
1656
+ else:
1657
+ bbox = md.bbox
1658
+
1659
+ _set(context, recobj, 'pycsw:Identifier', md.identifier)
1660
+ _set(context, recobj, 'pycsw:Typename', 'csw:Record')
1661
+ _set(context, recobj, 'pycsw:Schema', context.namespaces['csw'])
1662
+ _set(context, recobj, 'pycsw:MdSource', 'local')
1663
+ _set(context, recobj, 'pycsw:InsertDate', util.get_today_and_now())
1664
+ _set(context, recobj, 'pycsw:XML', md.xml)
1665
+ _set(context, recobj, 'pycsw:MetadataType', 'application/xml')
1666
+ _set(context, recobj, 'pycsw:AnyText', util.get_anytext(exml))
1667
+ _set(context, recobj, 'pycsw:Language', md.language)
1668
+ _set(context, recobj, 'pycsw:Type', md.type)
1669
+ _set(context, recobj, 'pycsw:Title', md.title)
1670
+ _set(context, recobj, 'pycsw:AlternateTitle', md.alternative)
1671
+ _set(context, recobj, 'pycsw:Abstract', md.abstract)
1672
+
1673
+ if len(md.subjects) > 0 and None not in md.subjects:
1674
+ _set(context, recobj, 'pycsw:Keywords', ','.join(md.subjects))
1675
+
1676
+ _set(context, recobj, 'pycsw:ParentIdentifier', md.ispartof)
1677
+ _set(context, recobj, 'pycsw:Relation', md.relation)
1678
+ _set(context, recobj, 'pycsw:TempExtent_begin', md.temporal)
1679
+ _set(context, recobj, 'pycsw:TempExtent_end', md.temporal)
1680
+ _set(context, recobj, 'pycsw:ResourceLanguage', md.language)
1681
+ _set(context, recobj, 'pycsw:Creator', md.creator)
1682
+ _set(context, recobj, 'pycsw:Publisher', md.publisher)
1683
+ _set(context, recobj, 'pycsw:Contributor', md.contributor)
1684
+ _set(context, recobj, 'pycsw:OrganizationName', md.rightsholder)
1685
+ _set(context, recobj, 'pycsw:AccessConstraints', md.accessrights)
1686
+ _set(context, recobj, 'pycsw:OtherConstraints', md.license)
1687
+ _set(context, recobj, 'pycsw:Date', md.date)
1688
+ _set(context, recobj, 'pycsw:CreationDate', md.created)
1689
+ _set(context, recobj, 'pycsw:PublicationDate', md.issued)
1690
+ _set(context, recobj, 'pycsw:Modified', md.modified)
1691
+ _set(context, recobj, 'pycsw:Format', md.format)
1692
+ _set(context, recobj, 'pycsw:Source', md.source)
1693
+
1694
+ for ref in md.references:
1695
+ links.append({
1696
+ 'name': None,
1697
+ 'description': None,
1698
+ 'protocol': ref['scheme'],
1699
+ 'url': ref['url'],
1700
+ })
1701
+ for uri in md.uris:
1702
+ links.append({
1703
+ 'name': uri['name'],
1704
+ 'description': uri['description'],
1705
+ 'protocol': uri['protocol'],
1706
+ 'url': uri['url'],
1707
+ })
1708
+
1709
+ if len(links) > 0:
1710
+ _set(context, recobj, 'pycsw:Links', json.dumps(links))
1711
+
1712
+ if bbox is not None:
1713
+ try:
1714
+ tmp = '%s,%s,%s,%s' % (bbox.minx, bbox.miny, bbox.maxx, bbox.maxy)
1715
+ _set(context, recobj, 'pycsw:BoundingBox', util.bbox2wktpolygon(tmp))
1716
+ except Exception:
1717
+ LOGGER.debug('Coordinates are corrupt')
1718
+ _set(context, recobj, 'pycsw:BoundingBox', None)
1719
+ else:
1720
+ _set(context, recobj, 'pycsw:BoundingBox', None)
1721
+
1722
+ return recobj
1723
+
1724
+
1725
+ def _parse_json_record(context, repos, record):
1726
+ """Parse JSON record"""
1727
+
1728
+ recobj = None
1729
+
1730
+ if 'conformsTo' in record:
1731
+ LOGGER.debug('Parsing OGC API - Records record model')
1732
+ recobj = _parse_oarec_record(context, repos, record)
1733
+ elif 'stac_version' in record:
1734
+ LOGGER.debug('Parsing STAC resource')
1735
+ recobj = _parse_stac_resource(context, repos, record)
1736
+
1737
+ if recobj is None:
1738
+ raise RuntimeError('Unsupported JSON metadata format')
1739
+
1740
+ atom_xml = atom.write_record(recobj, 'full', context)
1741
+
1742
+ _set(context, recobj, 'pycsw:XML', etree.tostring(atom_xml))
1743
+
1744
+ return recobj
1745
+
1746
+
1747
+ def _parse_oarec_record(context, repos, record):
1748
+ """Parse OARec record"""
1749
+
1750
+ conformance = 'http://www.opengis.net/spec/ogcapi-records-1/1.0/req/record-core'
1751
+
1752
+ recobj = repos.dataset()
1753
+ keywords = []
1754
+ links = []
1755
+
1756
+ _set(context, recobj, 'pycsw:Identifier', record['id'])
1757
+ _set(context, recobj, 'pycsw:Typename', 'csw:Record')
1758
+ _set(context, recobj, 'pycsw:Schema', conformance)
1759
+ _set(context, recobj, 'pycsw:MdSource', 'local')
1760
+ _set(context, recobj, 'pycsw:InsertDate', util.get_today_and_now())
1761
+ _set(context, recobj, 'pycsw:XML', '') # FIXME: transform into XML? or not, to validate
1762
+ _set(context, recobj, 'pycsw:Metadata', json.dumps(record))
1763
+ _set(context, recobj, 'pycsw:MetadataType', 'application/json')
1764
+
1765
+ _set(context, recobj, 'pycsw:AnyText', ' '.join([str(t) for t in util.get_anytext_from_obj(record)]))
1766
+
1767
+ _set(context, recobj, 'pycsw:Language', record['properties'].get('language', {}).get('code'))
1768
+ _set(context, recobj, 'pycsw:Type', record['properties']['type'])
1769
+ _set(context, recobj, 'pycsw:Title', record['properties']['title'])
1770
+ _set(context, recobj, 'pycsw:Abstract', record['properties'].get('description'))
1771
+
1772
+ if 'keywords' in record['properties']:
1773
+ keywords = record['properties']['keywords']
1774
+ _set(context, recobj, 'pycsw:Keywords', ','.join(keywords))
1775
+
1776
+ if 'themes' in record['properties']:
1777
+ _set(context, recobj, 'pycsw:Themes', json.dumps(record['properties']['themes']))
1778
+
1779
+ if 'links' in record:
1780
+ for link in record['links']:
1781
+ new_link = {
1782
+ 'url': link.get('href')
1783
+ }
1784
+
1785
+ if link.get('title') is not None:
1786
+ new_link['name'] = link.get('title')
1787
+ new_link['description'] = link.get('title')
1788
+ if link.get('description') is not None:
1789
+ new_link['description'] = link.get('description')
1790
+ if link.get('type') is not None:
1791
+ new_link['protocol'] = link.get('type')
1792
+ if link.get('rel') is not None:
1793
+ new_link['function'] = link.get('rel')
1794
+
1795
+ links.append(new_link)
1796
+
1797
+ if links:
1798
+ _set(context, recobj, 'pycsw:Links', json.dumps(links))
1799
+
1800
+ if record.get('geometry') is not None:
1801
+ _set(context, recobj, 'pycsw:BoundingBox', util.bbox2wktpolygon(util.geojson_geometry2bbox(record['geometry'])))
1802
+
1803
+ if 'temporal' in record['properties'].get('extent', []):
1804
+ _set(context, recobj, 'pycsw:TempExtent_begin', record['properties']['extent']['temporal']['interval'][0])
1805
+ _set(context, recobj, 'pycsw:TempExtent_end', record['properties']['extent']['temporal']['interval'][1])
1806
+
1807
+ return recobj
1808
+
1809
+
1810
+ def _parse_stac_resource(context, repos, record):
1811
+ """Parse STAC resource"""
1812
+
1813
+ recobj = repos.dataset()
1814
+ keywords = []
1815
+ links = []
1816
+ bbox_wkt = None
1817
+
1818
+ stac_type = record.get('type', 'Feature')
1819
+ if stac_type == 'Feature':
1820
+ LOGGER.debug('Parsing STAC Item')
1821
+ conformance = 'https://github.com/radiantearth/stac-spec/tree/master/item-spec/item-spec.md'
1822
+ typename = 'stac:Item'
1823
+ stype = 'item'
1824
+ title = record['properties'].get('title')
1825
+ abstract = record['properties'].get('description')
1826
+ if record.get('geometry') is not None:
1827
+ bbox_wkt = util.bbox2wktpolygon(util.geojson_geometry2bbox(record['geometry']))
1828
+ elif stac_type == 'Collection':
1829
+ LOGGER.debug('Parsing STAC Collection')
1830
+ conformance = 'https://github.com/radiantearth/stac-spec/tree/master/collection-spec/collection-spec.md'
1831
+ typename = 'stac:Collection'
1832
+ stype = 'collection'
1833
+ title = record.get('title')
1834
+ abstract = record.get('description')
1835
+ if 'extent' in record and 'spatial' in record['extent']:
1836
+ bbox_csv = ','.join(str(t) for t in record['extent']['spatial']['bbox'][0])
1837
+ bbox_wkt = util.bbox2wktpolygon(bbox_csv)
1838
+ if 'extent' in record and 'temporal' in record['extent'] and 'interval' in record['extent']['temporal']:
1839
+ _set(context, recobj, 'pycsw:TempExtent_begin', record['extent']['temporal']['interval'][0][0])
1840
+ _set(context, recobj, 'pycsw:TempExtent_end', record['extent']['temporal']['interval'][0][1])
1841
+ elif stac_type == 'Catalog':
1842
+ LOGGER.debug('Parsing STAC Catalog')
1843
+ conformance = 'https://github.com/radiantearth/stac-spec/tree/master/catalog-spec/catalog-spec.md'
1844
+ typename = 'stac:Catalog'
1845
+ stype = 'catalog'
1846
+ title = record.get('title')
1847
+ abstract = record.get('description')
1848
+
1849
+ _set(context, recobj, 'pycsw:Identifier', record['id'])
1850
+ _set(context, recobj, 'pycsw:Typename', typename)
1851
+ _set(context, recobj, 'pycsw:Schema', conformance)
1852
+ _set(context, recobj, 'pycsw:MdSource', 'local')
1853
+ _set(context, recobj, 'pycsw:InsertDate', util.get_today_and_now())
1854
+ _set(context, recobj, 'pycsw:XML', '') # FIXME: transform into XML? or not, to validate
1855
+ _set(context, recobj, 'pycsw:Metadata', json.dumps(record))
1856
+ _set(context, recobj, 'pycsw:MetadataType', 'application/json')
1857
+ _set(context, recobj, 'pycsw:AnyText', ' '.join([str(t) for t in util.get_anytext_from_obj(record)]))
1858
+ _set(context, recobj, 'pycsw:Type', stype)
1859
+ _set(context, recobj, 'pycsw:Title', title)
1860
+ _set(context, recobj, 'pycsw:Abstract', abstract)
1861
+ _set(context, recobj, 'pycsw:BoundingBox', bbox_wkt)
1862
+
1863
+ if 'links' in record:
1864
+ for link in record['links']:
1865
+ new_link = {
1866
+ 'url': link.get('href')
1867
+ }
1868
+
1869
+ if link.get('title') is not None:
1870
+ new_link['name'] = link.get('title')
1871
+ new_link['description'] = link.get('title')
1872
+ if link.get('description') is not None:
1873
+ new_link['description'] = link.get('description')
1874
+ if link.get('type') is not None:
1875
+ new_link['protocol'] = link.get('type')
1876
+ if link.get('rel') is not None:
1877
+ new_link['function'] = link.get('rel')
1878
+
1879
+ links.append(new_link)
1880
+
1881
+ if 'assets' in record:
1882
+ for key, link in record['assets'].items():
1883
+ new_link = {
1884
+ 'url': link.get('href')
1885
+ }
1886
+
1887
+ if link.get('title') is not None:
1888
+ new_link['name'] = link.get('title')
1889
+ new_link['description'] = link.get('title')
1890
+ if link.get('type') is not None:
1891
+ new_link['protocol'] = link.get('type')
1892
+ if link.get('rel') is not None:
1893
+ new_link['function'] = link.get('rel')
1894
+ else:
1895
+ new_link['function'] = 'enclosure'
1896
+
1897
+ links.append(new_link)
1898
+
1899
+ if links:
1900
+ _set(context, recobj, 'pycsw:Links', json.dumps(links))
1901
+
1902
+ if stac_type == 'Feature':
1903
+ if 'keywords' in record['properties']:
1904
+ keywords = record['properties']['keywords']
1905
+ _set(context, recobj, 'pycsw:Keywords', ','.join(keywords))
1906
+
1907
+ if 'start_datetime' in record['properties']:
1908
+ _set(context, recobj, 'pycsw:TempExtent_begin', record['properties']['start_datetime'])
1909
+
1910
+ if 'end_datetime' in record['properties']:
1911
+ _set(context, recobj, 'pycsw:TempExtent_end', record['properties']['end_datetime'])
1912
+
1913
+ if 'datetime' in record['properties']:
1914
+ _set(context, recobj, 'pycsw:Date', record['properties']['datetime'])
1915
+ if 'start_datetime' not in record['properties'] and 'end_datetime' not in record['properties']:
1916
+ _set(context, recobj, 'pycsw:TempExtent_begin', record['properties']['datetime'])
1917
+ _set(context, recobj, 'pycsw:TempExtent_end', record['properties']['datetime'])
1918
+
1919
+ if 'eo:cloud_cover' in record['properties']:
1920
+ _set(context, recobj, 'pycsw:CloudCover', record['properties']['eo:cloud_cover'])
1921
+
1922
+ if 'collection' in record:
1923
+ _set(context, recobj, 'pycsw:ParentIdentifier', record['collection'])
1924
+
1925
+ if stac_type in ['Collection', 'Catalog']:
1926
+ if 'keywords' in record:
1927
+ keywords = record['keywords']
1928
+ _set(context, recobj, 'pycsw:Keywords', ','.join(keywords))
1929
+
1930
+ if 'start_datetime' in record:
1931
+ _set(context, recobj, 'pycsw:TempExtent_begin', record['start_datetime'])
1932
+
1933
+ if 'end_datetime' in record:
1934
+ _set(context, recobj, 'pycsw:TempExtent_end', record['end_datetime'])
1935
+
1936
+ if 'datetime' in record:
1937
+ _set(context, recobj, 'pycsw:Date', record['datetime'])
1938
+ if 'start_datetime' not in record and 'end_datetime' not in record:
1939
+ _set(context, recobj, 'pycsw:TempExtent_begin', record['datetime'])
1940
+ _set(context, recobj, 'pycsw:TempExtent_end', record['datetime'])
1941
+
1942
+ return recobj
1943
+
1944
+ def fgdccontact2iso(cnt, role='pointOfContact'):
1945
+ """Creates a iso format contact (owslib style) from fgdc format"""
1946
+
1947
+ return {'name': cnt.cntper,
1948
+ 'organization': cnt.cntorg,
1949
+ 'position': cnt.cntpos,
1950
+ 'phone': cnt.voice,
1951
+ 'address': cnt.address,
1952
+ 'city': cnt.city,
1953
+ 'region': cnt.state,
1954
+ 'postcode': cnt.postal,
1955
+ 'country': cnt.country,
1956
+ 'email': cnt.email,
1957
+ 'role': role
1958
+ }
1959
+
1960
+ def caps2iso(record, caps, context):
1961
+ """Creates ISO metadata from Capabilities XML"""
1962
+
1963
+ from pycsw.plugins.profiles.apiso.apiso import APISO
1964
+
1965
+ apiso_obj = APISO(context.model, context.namespaces, context)
1966
+ apiso_obj.ogc_schemas_base = 'http://schemas.opengis.net'
1967
+ apiso_obj.url = context.url
1968
+ queryables = dict(apiso_obj.repository['queryables']['SupportedISOQueryables'].items())
1969
+ iso_xml = apiso_obj.write_record(record, 'full', 'http://www.isotc211.org/2005/gmd', queryables, caps)
1970
+ return etree.tostring(iso_xml)
1971
+
1972
+
1973
+ def bbox_from_polygons(bboxs):
1974
+ """Derive an aggregated bbox from n polygons
1975
+
1976
+ Parameters
1977
+ ----------
1978
+ bboxs: list
1979
+ A sequence of strings containing Well-Known Text representations of
1980
+ polygons
1981
+
1982
+ Returns
1983
+ -------
1984
+ str
1985
+ Well-Known Text representation of the aggregated bounding box for
1986
+ all the input polygons
1987
+ """
1988
+
1989
+ try:
1990
+ multi_pol = MultiPolygon(
1991
+ [loads(bbox) for bbox in bboxs]
1992
+ )
1993
+ bstr = ",".join([f"{b:.2f}" for b in multi_pol.bounds])
1994
+ return util.bbox2wktpolygon(bstr)
1995
+ except Exception as err:
1996
+ raise RuntimeError('Cannot aggregate polygons: %s' % str(err)) from err