Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: CSW Translator
4 : * Purpose: Implements OGRCSWDriver.
5 : * Author: Even Rouault, Even Rouault <even dot rouault at spatialys dot com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2015, Even Rouault <even dot rouault at spatialys dot com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "ogrsf_frmts.h"
14 : #include "cpl_conv.h"
15 : #include "cpl_http.h"
16 : #include "ogr_p.h"
17 : #include "ogr_swq.h"
18 : #include "ogrwfsfilter.h"
19 : #include "gmlutils.h"
20 :
21 : extern "C" void RegisterOGRCSW();
22 :
23 : /************************************************************************/
24 : /* OGRCSWLayer */
25 : /************************************************************************/
26 :
27 : class OGRCSWDataSource;
28 :
29 : class OGRCSWLayer final : public OGRLayer
30 : {
31 : OGRCSWDataSource *poDS;
32 : OGRFeatureDefn *poFeatureDefn;
33 :
34 : GDALDataset *poBaseDS;
35 : OGRLayer *poBaseLayer;
36 :
37 : int nPagingStartIndex;
38 : int nFeatureRead;
39 : int nFeaturesInCurrentPage;
40 :
41 : CPLString osQuery;
42 : CPLString osCSWWhere;
43 :
44 : std::string m_osTmpDir{};
45 :
46 : GDALDataset *FetchGetRecords();
47 : GIntBig GetFeatureCountWithHits();
48 : void BuildQuery();
49 :
50 : public:
51 : explicit OGRCSWLayer(OGRCSWDataSource *poDS);
52 : virtual ~OGRCSWLayer();
53 :
54 : virtual void ResetReading() override;
55 : virtual OGRFeature *GetNextFeature() override;
56 : virtual GIntBig GetFeatureCount(int bForce = FALSE) override;
57 :
58 3 : virtual OGRFeatureDefn *GetLayerDefn() override
59 : {
60 3 : return poFeatureDefn;
61 : }
62 :
63 1 : virtual int TestCapability(const char *) override
64 : {
65 1 : return FALSE;
66 : }
67 :
68 : OGRErr ISetSpatialFilter(int iGeomField,
69 : const OGRGeometry *poGeom) override;
70 :
71 : virtual OGRErr SetAttributeFilter(const char *) override;
72 : };
73 :
74 : /************************************************************************/
75 : /* OGRCSWDataSource */
76 : /************************************************************************/
77 :
78 : class OGRCSWDataSource final : public GDALDataset
79 : {
80 : CPLString osBaseURL;
81 : CPLString osVersion;
82 : CPLString osElementSetName;
83 : CPLString osOutputSchema;
84 : int nMaxRecords;
85 :
86 : OGRCSWLayer *poLayer;
87 : bool bFullExtentRecordsAsNonSpatial;
88 :
89 : CPLHTTPResult *SendGetCapabilities();
90 :
91 : public:
92 : OGRCSWDataSource();
93 : virtual ~OGRCSWDataSource();
94 :
95 : int Open(const char *pszFilename, char **papszOpenOptions);
96 :
97 1 : virtual int GetLayerCount() override
98 : {
99 1 : return poLayer != nullptr;
100 : }
101 :
102 : virtual OGRLayer *GetLayer(int) override;
103 :
104 : static CPLHTTPResult *HTTPFetch(const char *pszURL, const char *pszPost);
105 :
106 39 : const CPLString &GetBaseURL()
107 : {
108 39 : return osBaseURL;
109 : }
110 :
111 39 : const CPLString &GetVersion()
112 : {
113 39 : return osVersion;
114 : }
115 :
116 39 : const CPLString &GetElementSetName()
117 : {
118 39 : return osElementSetName;
119 : }
120 :
121 60 : const CPLString &GetOutputSchema()
122 : {
123 60 : return osOutputSchema;
124 : }
125 :
126 10 : bool FullExtentRecordsAsNonSpatial()
127 : {
128 10 : return bFullExtentRecordsAsNonSpatial;
129 : }
130 :
131 31 : int GetMaxRecords()
132 : {
133 31 : return nMaxRecords;
134 : }
135 : };
136 :
137 : /************************************************************************/
138 : /* OGRCSWLayer() */
139 : /************************************************************************/
140 :
141 4 : OGRCSWLayer::OGRCSWLayer(OGRCSWDataSource *poDSIn)
142 4 : : poDS(poDSIn), poFeatureDefn(new OGRFeatureDefn("records")),
143 : poBaseDS(nullptr), poBaseLayer(nullptr), nPagingStartIndex(0),
144 8 : nFeatureRead(0), nFeaturesInCurrentPage(0)
145 : {
146 4 : SetDescription(poFeatureDefn->GetName());
147 4 : poFeatureDefn->Reference();
148 4 : poFeatureDefn->SetGeomType(wkbPolygon);
149 : OGRSpatialReference *poSRS =
150 4 : new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
151 4 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
152 4 : poFeatureDefn->GetGeomFieldDefn(0)->SetName("boundingbox");
153 4 : poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
154 : {
155 8 : OGRFieldDefn oField("identifier", OFTString);
156 4 : poFeatureDefn->AddFieldDefn(&oField);
157 : }
158 : {
159 8 : OGRFieldDefn oField("other_identifiers", OFTStringList);
160 4 : poFeatureDefn->AddFieldDefn(&oField);
161 : }
162 : {
163 8 : OGRFieldDefn oField("title", OFTString);
164 4 : poFeatureDefn->AddFieldDefn(&oField);
165 : }
166 : {
167 8 : OGRFieldDefn oField("type", OFTString);
168 4 : poFeatureDefn->AddFieldDefn(&oField);
169 : }
170 : {
171 8 : OGRFieldDefn oField("subject", OFTString);
172 4 : poFeatureDefn->AddFieldDefn(&oField);
173 : }
174 : {
175 8 : OGRFieldDefn oField("other_subjects", OFTStringList);
176 4 : poFeatureDefn->AddFieldDefn(&oField);
177 : }
178 : {
179 8 : OGRFieldDefn oField("references", OFTString);
180 4 : poFeatureDefn->AddFieldDefn(&oField);
181 : }
182 : {
183 8 : OGRFieldDefn oField("other_references", OFTStringList);
184 4 : poFeatureDefn->AddFieldDefn(&oField);
185 : }
186 : {
187 8 : OGRFieldDefn oField("modified", OFTString);
188 4 : poFeatureDefn->AddFieldDefn(&oField);
189 : }
190 : {
191 8 : OGRFieldDefn oField("abstract", OFTString);
192 4 : poFeatureDefn->AddFieldDefn(&oField);
193 : }
194 : {
195 8 : OGRFieldDefn oField("date", OFTString);
196 4 : poFeatureDefn->AddFieldDefn(&oField);
197 : }
198 : {
199 8 : OGRFieldDefn oField("language", OFTString);
200 4 : poFeatureDefn->AddFieldDefn(&oField);
201 : }
202 : {
203 8 : OGRFieldDefn oField("rights", OFTString);
204 4 : poFeatureDefn->AddFieldDefn(&oField);
205 : }
206 : {
207 8 : OGRFieldDefn oField("format", OFTString);
208 4 : poFeatureDefn->AddFieldDefn(&oField);
209 : }
210 : {
211 8 : OGRFieldDefn oField("other_formats", OFTStringList);
212 4 : poFeatureDefn->AddFieldDefn(&oField);
213 : }
214 : {
215 8 : OGRFieldDefn oField("creator", OFTString);
216 4 : poFeatureDefn->AddFieldDefn(&oField);
217 : }
218 : {
219 8 : OGRFieldDefn oField("source", OFTString);
220 4 : poFeatureDefn->AddFieldDefn(&oField);
221 : }
222 : {
223 8 : OGRFieldDefn oField("anytext", OFTString);
224 4 : poFeatureDefn->AddFieldDefn(&oField);
225 : }
226 4 : if (!poDS->GetOutputSchema().empty())
227 : {
228 6 : OGRFieldDefn oField("raw_xml", OFTString);
229 3 : poFeatureDefn->AddFieldDefn(&oField);
230 : }
231 :
232 4 : poSRS->Release();
233 :
234 4 : m_osTmpDir = VSIMemGenerateHiddenFilename("csw");
235 4 : }
236 :
237 : /************************************************************************/
238 : /* ~OGRCSWLayer() */
239 : /************************************************************************/
240 :
241 8 : OGRCSWLayer::~OGRCSWLayer()
242 : {
243 4 : poFeatureDefn->Release();
244 4 : GDALClose(poBaseDS);
245 4 : VSIRmdirRecursive(m_osTmpDir.c_str());
246 8 : }
247 :
248 : /************************************************************************/
249 : /* ResetReading() */
250 : /************************************************************************/
251 :
252 28 : void OGRCSWLayer::ResetReading()
253 : {
254 28 : nPagingStartIndex = 0;
255 28 : nFeatureRead = 0;
256 28 : nFeaturesInCurrentPage = 0;
257 28 : GDALClose(poBaseDS);
258 28 : poBaseDS = nullptr;
259 28 : poBaseLayer = nullptr;
260 28 : }
261 :
262 : /************************************************************************/
263 : /* GetNextFeature() */
264 : /************************************************************************/
265 :
266 38 : OGRFeature *OGRCSWLayer::GetNextFeature()
267 : {
268 : while (true)
269 : {
270 38 : if (nFeatureRead == nPagingStartIndex + nFeaturesInCurrentPage)
271 : {
272 31 : nPagingStartIndex = nFeatureRead;
273 :
274 31 : GDALClose(poBaseDS);
275 31 : poBaseLayer = nullptr;
276 :
277 31 : poBaseDS = FetchGetRecords();
278 31 : if (poBaseDS)
279 : {
280 16 : poBaseLayer = poBaseDS->GetLayer(0);
281 16 : poBaseLayer->ResetReading();
282 16 : nFeaturesInCurrentPage = (int)poBaseLayer->GetFeatureCount();
283 : }
284 : }
285 38 : if (!poBaseLayer)
286 15 : return nullptr;
287 :
288 23 : OGRFeature *poSrcFeature = poBaseLayer->GetNextFeature();
289 23 : if (poSrcFeature == nullptr)
290 0 : return nullptr;
291 23 : nFeatureRead++;
292 :
293 23 : OGRFeature *poNewFeature = new OGRFeature(poFeatureDefn);
294 :
295 440 : for (int i = 0; i < poFeatureDefn->GetFieldCount(); i++)
296 : {
297 : const char *pszFieldname =
298 417 : poFeatureDefn->GetFieldDefn(i)->GetNameRef();
299 417 : int iSrcField = poSrcFeature->GetFieldIndex(pszFieldname);
300 : /* http://www.paikkatietohakemisto.fi/geonetwork/srv/en/csw returns
301 : * URI ... */
302 417 : if (iSrcField < 0 && strcmp(pszFieldname, "references") == 0)
303 9 : iSrcField = poSrcFeature->GetFieldIndex("URI");
304 417 : if (iSrcField >= 0 && poSrcFeature->IsFieldSetAndNotNull(iSrcField))
305 : {
306 73 : OGRFieldType eType = poFeatureDefn->GetFieldDefn(i)->GetType();
307 : OGRFieldType eSrcType =
308 73 : poSrcFeature->GetFieldDefnRef(iSrcField)->GetType();
309 73 : if (eType == eSrcType)
310 : {
311 45 : poNewFeature->SetField(
312 45 : i, poSrcFeature->GetRawFieldRef(iSrcField));
313 : }
314 : else
315 : {
316 28 : if (eType == OFTString && eSrcType == OFTStringList &&
317 28 : strcmp(pszFieldname, "identifier") == 0)
318 : {
319 : char **papszValues =
320 7 : poSrcFeature->GetFieldAsStringList(iSrcField);
321 7 : poNewFeature->SetField("identifier", *papszValues);
322 7 : if (papszValues[1])
323 7 : poNewFeature->SetField("other_identifiers",
324 7 : papszValues + 1);
325 : }
326 21 : else if (eType == OFTString && eSrcType == OFTStringList &&
327 21 : strcmp(pszFieldname, "subject") == 0)
328 : {
329 : char **papszValues =
330 7 : poSrcFeature->GetFieldAsStringList(iSrcField);
331 7 : poNewFeature->SetField("subject", *papszValues);
332 7 : if (papszValues[1])
333 7 : poNewFeature->SetField("other_subjects",
334 7 : papszValues + 1);
335 : }
336 14 : else if (eType == OFTString && eSrcType == OFTStringList &&
337 14 : strcmp(pszFieldname, "references") == 0)
338 : {
339 : char **papszValues =
340 7 : poSrcFeature->GetFieldAsStringList(iSrcField);
341 7 : poNewFeature->SetField("references", *papszValues);
342 7 : if (papszValues[1])
343 7 : poNewFeature->SetField("other_references",
344 7 : papszValues + 1);
345 : }
346 7 : else if (eType == OFTString && eSrcType == OFTStringList &&
347 7 : strcmp(pszFieldname, "format") == 0)
348 : {
349 : char **papszValues =
350 7 : poSrcFeature->GetFieldAsStringList(iSrcField);
351 7 : poNewFeature->SetField("format", *papszValues);
352 7 : if (papszValues[1])
353 7 : poNewFeature->SetField("other_formats",
354 7 : papszValues + 1);
355 : }
356 : else
357 0 : poNewFeature->SetField(
358 : i, poSrcFeature->GetFieldAsString(iSrcField));
359 : }
360 : }
361 : }
362 :
363 23 : OGRGeometry *poGeom = poSrcFeature->StealGeometry();
364 23 : if (poGeom)
365 : {
366 10 : if (poDS->FullExtentRecordsAsNonSpatial())
367 : {
368 0 : OGREnvelope sEnvelope;
369 0 : poGeom->getEnvelope(&sEnvelope);
370 0 : if (sEnvelope.MinX == -180 && sEnvelope.MinY == -90 &&
371 0 : sEnvelope.MaxX == 180 && sEnvelope.MaxY == 90)
372 : {
373 0 : delete poGeom;
374 0 : poGeom = nullptr;
375 : }
376 : }
377 10 : if (poGeom)
378 : {
379 10 : poGeom->assignSpatialReference(
380 10 : poFeatureDefn->GetGeomFieldDefn(0)->GetSpatialRef());
381 10 : poNewFeature->SetGeometryDirectly(poGeom);
382 : }
383 : }
384 :
385 23 : poNewFeature->SetFID(nFeatureRead);
386 23 : delete poSrcFeature;
387 :
388 23 : if (osCSWWhere.empty() && m_poAttrQuery != nullptr &&
389 0 : !m_poAttrQuery->Evaluate(poNewFeature))
390 : {
391 0 : delete poNewFeature;
392 : }
393 : else
394 : {
395 23 : return poNewFeature;
396 : }
397 0 : }
398 : }
399 :
400 : /************************************************************************/
401 : /* GetFeatureCount() */
402 : /************************************************************************/
403 :
404 8 : GIntBig OGRCSWLayer::GetFeatureCount(int bForce)
405 : {
406 8 : GIntBig nFeatures = GetFeatureCountWithHits();
407 8 : if (nFeatures >= 0)
408 2 : return nFeatures;
409 6 : return OGRLayer::GetFeatureCount(bForce);
410 : }
411 :
412 : /************************************************************************/
413 : /* GetFeatureCountWithHits() */
414 : /************************************************************************/
415 :
416 8 : GIntBig OGRCSWLayer::GetFeatureCountWithHits()
417 : {
418 : CPLString osPost = CPLSPrintf(
419 : "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
420 : "<csw:GetRecords resultType=\"hits\" service=\"CSW\" version=\"%s\""
421 : " xmlns:csw=\"http://www.opengis.net/cat/csw/2.0.2\""
422 : " xmlns:gml=\"http://www.opengis.net/gml\""
423 : " xmlns:dc=\"http://purl.org/dc/elements/1.1/\""
424 : " xmlns:dct=\"http://purl.org/dc/terms/\""
425 : " xmlns:ogc=\"http://www.opengis.net/ogc\""
426 : " xmlns:ows=\"http://www.opengis.net/ows\""
427 : " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
428 : " xsi:schemaLocation=\"http://www.opengis.net/cat/csw/2.0.2 "
429 : "http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd\">"
430 : "<csw:Query typeNames=\"csw:Record\">"
431 : "<csw:ElementSetName>%s</csw:ElementSetName>"
432 : "%s"
433 : "</csw:Query>"
434 : "</csw:GetRecords>",
435 16 : poDS->GetVersion().c_str(), poDS->GetElementSetName().c_str(),
436 32 : osQuery.c_str());
437 :
438 : CPLHTTPResult *psResult =
439 8 : OGRCSWDataSource::HTTPFetch(poDS->GetBaseURL(), osPost);
440 8 : if (psResult == nullptr)
441 : {
442 3 : return -1;
443 : }
444 :
445 5 : CPLXMLNode *psXML = CPLParseXMLString((const char *)psResult->pabyData);
446 5 : if (psXML == nullptr)
447 : {
448 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
449 : psResult->pabyData);
450 1 : CPLHTTPDestroyResult(psResult);
451 1 : return -1;
452 : }
453 4 : CPLStripXMLNamespace(psXML, nullptr, TRUE);
454 4 : CPLHTTPDestroyResult(psResult);
455 4 : psResult = nullptr;
456 :
457 4 : GIntBig nFeatures = CPLAtoGIntBig(CPLGetXMLValue(
458 : psXML, "=GetRecordsResponse.SearchResults.numberOfRecordsMatched",
459 : "-1"));
460 :
461 4 : CPLDestroyXMLNode(psXML);
462 4 : return nFeatures;
463 : }
464 :
465 : /************************************************************************/
466 : /* FetchGetRecords() */
467 : /************************************************************************/
468 :
469 31 : GDALDataset *OGRCSWLayer::FetchGetRecords()
470 : {
471 31 : CPLHTTPResult *psResult = nullptr;
472 :
473 62 : CPLString osOutputSchema = poDS->GetOutputSchema();
474 31 : if (!osOutputSchema.empty())
475 5 : osOutputSchema = " outputSchema=\"" + osOutputSchema + "\"";
476 :
477 : CPLString osPost = CPLSPrintf(
478 : "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
479 : "<csw:GetRecords resultType=\"results\" service=\"CSW\" version=\"%s\""
480 : "%s"
481 : " startPosition=\"%d\""
482 : " maxRecords=\"%d\""
483 : " xmlns:csw=\"http://www.opengis.net/cat/csw/2.0.2\""
484 : " xmlns:gml=\"http://www.opengis.net/gml\""
485 : " xmlns:dc=\"http://purl.org/dc/elements/1.1/\""
486 : " xmlns:dct=\"http://purl.org/dc/terms/\""
487 : " xmlns:ogc=\"http://www.opengis.net/ogc\""
488 : " xmlns:ows=\"http://www.opengis.net/ows\""
489 : " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
490 : " xsi:schemaLocation=\"http://www.opengis.net/cat/csw/2.0.2 "
491 : "http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd\">"
492 : "<csw:Query typeNames=\"csw:Record\">"
493 : "<csw:ElementSetName>%s</csw:ElementSetName>"
494 : "%s"
495 : "</csw:Query>"
496 : "</csw:GetRecords>",
497 31 : poDS->GetVersion().c_str(), osOutputSchema.c_str(),
498 62 : nPagingStartIndex + 1, poDS->GetMaxRecords(),
499 93 : poDS->GetElementSetName().c_str(), osQuery.c_str());
500 :
501 31 : psResult = OGRCSWDataSource::HTTPFetch(poDS->GetBaseURL(), osPost);
502 31 : if (psResult == nullptr)
503 : {
504 5 : return nullptr;
505 : }
506 :
507 26 : VSIMkdir(m_osTmpDir.c_str(), 0);
508 :
509 26 : GByte *pabyData = psResult->pabyData;
510 26 : int nDataLen = psResult->nDataLen;
511 :
512 26 : if (strstr((const char *)pabyData, "<ServiceExceptionReport") != nullptr ||
513 25 : strstr((const char *)pabyData, "<ows:ExceptionReport") != nullptr)
514 : {
515 1 : CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
516 : pabyData);
517 1 : CPLHTTPDestroyResult(psResult);
518 1 : return nullptr;
519 : }
520 : // CPLDebug("CSW", "%s", (const char*)pabyData);
521 :
522 50 : CPLString osTmpFileName;
523 :
524 25 : osTmpFileName = m_osTmpDir + "/file.gfs";
525 25 : VSIUnlink(osTmpFileName);
526 :
527 25 : osTmpFileName = m_osTmpDir + "/file.gml";
528 :
529 : VSILFILE *fp =
530 25 : VSIFileFromMemBuffer(osTmpFileName, pabyData, nDataLen, TRUE);
531 25 : VSIFCloseL(fp);
532 25 : psResult->pabyData = nullptr;
533 :
534 25 : CPLHTTPDestroyResult(psResult);
535 :
536 25 : GDALDataset *l_poBaseDS = nullptr;
537 :
538 25 : if (!poDS->GetOutputSchema().empty())
539 : {
540 5 : GDALDriver *poDrv = (GDALDriver *)GDALGetDriverByName("Memory");
541 5 : if (poDrv == nullptr)
542 2 : return nullptr;
543 5 : CPLXMLNode *psRoot = CPLParseXMLFile(osTmpFileName);
544 5 : if (psRoot == nullptr)
545 : {
546 1 : if (strstr((const char *)pabyData, "<csw:GetRecordsResponse") ==
547 1 : nullptr &&
548 1 : strstr((const char *)pabyData, "<GetRecordsResponse") ==
549 : nullptr)
550 : {
551 1 : if (nDataLen > 1000)
552 0 : pabyData[1000] = 0;
553 1 : CPLError(CE_Failure, CPLE_AppDefined, "Error: cannot parse %s",
554 : pabyData);
555 : }
556 1 : return nullptr;
557 : }
558 : CPLXMLNode *psSearchResults =
559 4 : CPLGetXMLNode(psRoot, "=csw:GetRecordsResponse.csw:SearchResults");
560 4 : if (psSearchResults == nullptr)
561 : {
562 1 : CPLError(CE_Failure, CPLE_AppDefined,
563 : "Cannot find GetRecordsResponse.SearchResults");
564 1 : CPLDestroyXMLNode(psRoot);
565 1 : return nullptr;
566 : }
567 :
568 3 : l_poBaseDS = poDrv->Create("", 0, 0, 0, GDT_Unknown, nullptr);
569 3 : OGRLayer *poLyr = l_poBaseDS->CreateLayer("records");
570 6 : OGRFieldDefn oField("raw_xml", OFTString);
571 3 : poLyr->CreateField(&oField);
572 21 : for (CPLXMLNode *psIter = psSearchResults->psChild; psIter;
573 18 : psIter = psIter->psNext)
574 : {
575 18 : if (psIter->eType == CXT_Element)
576 : {
577 3 : OGRFeature *poFeature = new OGRFeature(poLyr->GetLayerDefn());
578 :
579 3 : CPLXMLNode *psNext = psIter->psNext;
580 3 : psIter->psNext = nullptr;
581 3 : char *pszXML = CPLSerializeXMLTree(psIter);
582 :
583 3 : const char *pszWest = nullptr;
584 3 : const char *pszEast = nullptr;
585 3 : const char *pszSouth = nullptr;
586 3 : const char *pszNorth = nullptr;
587 : CPLXMLNode *psBBox =
588 3 : CPLSearchXMLNode(psIter, "gmd:EX_GeographicBoundingBox");
589 3 : if (psBBox)
590 : {
591 : /* ISO 19115/19119: http://www.isotc211.org/2005/gmd */
592 1 : pszWest = CPLGetXMLValue(
593 : psBBox, "gmd:westBoundLongitude.gco:Decimal", nullptr);
594 1 : pszEast = CPLGetXMLValue(
595 : psBBox, "gmd:eastBoundLongitude.gco:Decimal", nullptr);
596 1 : pszSouth = CPLGetXMLValue(
597 : psBBox, "gmd:southBoundLatitude.gco:Decimal", nullptr);
598 1 : pszNorth = CPLGetXMLValue(
599 : psBBox, "gmd:northBoundLatitude.gco:Decimal", nullptr);
600 : }
601 2 : else if ((psBBox = CPLSearchXMLNode(psIter, "spdom")) !=
602 : nullptr)
603 : {
604 : /* FGDC: http://www.opengis.net/cat/csw/csdgm */
605 : pszWest =
606 1 : CPLGetXMLValue(psBBox, "bounding.westbc", nullptr);
607 : pszEast =
608 1 : CPLGetXMLValue(psBBox, "bounding.eastbc", nullptr);
609 : pszSouth =
610 1 : CPLGetXMLValue(psBBox, "bounding.southbc", nullptr);
611 : pszNorth =
612 1 : CPLGetXMLValue(psBBox, "bounding.northbc", nullptr);
613 : }
614 3 : if (pszWest && pszEast && pszSouth && pszNorth)
615 : {
616 2 : double dfMinX = CPLAtof(pszWest);
617 2 : double dfMaxX = CPLAtof(pszEast);
618 2 : double dfMinY = CPLAtof(pszSouth);
619 2 : double dfMaxY = CPLAtof(pszNorth);
620 2 : OGRLinearRing *poLR = new OGRLinearRing();
621 2 : poLR->addPoint(dfMinX, dfMinY);
622 2 : poLR->addPoint(dfMinX, dfMaxY);
623 2 : poLR->addPoint(dfMaxX, dfMaxY);
624 2 : poLR->addPoint(dfMaxX, dfMinY);
625 2 : poLR->addPoint(dfMinX, dfMinY);
626 2 : OGRPolygon *poPoly = new OGRPolygon();
627 2 : poPoly->addRingDirectly(poLR);
628 2 : poFeature->SetGeometryDirectly(poPoly);
629 : }
630 1 : else if ((psBBox = CPLSearchXMLNode(
631 1 : psIter, "ows:BoundingBox")) != nullptr)
632 : {
633 1 : CPLFree(psBBox->pszValue);
634 1 : psBBox->pszValue = CPLStrdup("gml:Envelope");
635 2 : CPLString osSRS = CPLGetXMLValue(psBBox, "crs", "");
636 1 : OGRGeometry *poGeom = GML2OGRGeometry_XMLNode(
637 : psBBox, FALSE, 0, 0, false, true, false);
638 1 : if (poGeom)
639 : {
640 1 : bool bLatLongOrder = true;
641 1 : if (!osSRS.empty())
642 1 : bLatLongOrder = GML_IsSRSLatLongOrder(osSRS);
643 2 : if (bLatLongOrder &&
644 1 : CPLTestBool(CPLGetConfigOption(
645 : "GML_INVERT_AXIS_ORDER_IF_LAT_LONG", "YES")))
646 1 : poGeom->swapXY();
647 1 : poFeature->SetGeometryDirectly(poGeom);
648 : }
649 : }
650 :
651 3 : psIter->psNext = psNext;
652 :
653 3 : poFeature->SetField(0, pszXML);
654 3 : CPL_IGNORE_RET_VAL(poLyr->CreateFeature(poFeature));
655 3 : CPLFree(pszXML);
656 3 : delete poFeature;
657 : }
658 : }
659 3 : CPLDestroyXMLNode(psRoot);
660 : }
661 : else
662 : {
663 20 : l_poBaseDS = (GDALDataset *)OGROpen(osTmpFileName, FALSE, nullptr);
664 20 : if (l_poBaseDS == nullptr)
665 : {
666 2 : if (strstr((const char *)pabyData, "<csw:GetRecordsResponse") ==
667 2 : nullptr &&
668 2 : strstr((const char *)pabyData, "<GetRecordsResponse") ==
669 : nullptr)
670 : {
671 2 : if (nDataLen > 1000)
672 0 : pabyData[1000] = 0;
673 2 : CPLError(CE_Failure, CPLE_AppDefined, "Error: cannot parse %s",
674 : pabyData);
675 : }
676 2 : return nullptr;
677 : }
678 : }
679 :
680 21 : OGRLayer *poLayer = l_poBaseDS->GetLayer(0);
681 21 : if (poLayer == nullptr)
682 : {
683 5 : GDALClose(l_poBaseDS);
684 5 : return nullptr;
685 : }
686 :
687 16 : return l_poBaseDS;
688 : }
689 :
690 : /************************************************************************/
691 : /* ISetSpatialFilter() */
692 : /************************************************************************/
693 :
694 2 : OGRErr OGRCSWLayer::ISetSpatialFilter(int iGeomField, const OGRGeometry *poGeom)
695 : {
696 2 : const OGRErr eErr = OGRLayer::ISetSpatialFilter(iGeomField, poGeom);
697 2 : if (eErr == OGRERR_NONE)
698 : {
699 2 : ResetReading();
700 2 : BuildQuery();
701 : }
702 2 : return eErr;
703 : }
704 :
705 : /************************************************************************/
706 : /* OGRCSWAddRightPrefixes() */
707 : /************************************************************************/
708 :
709 39 : static void OGRCSWAddRightPrefixes(swq_expr_node *poNode)
710 : {
711 39 : if (poNode->eNodeType == SNT_COLUMN)
712 : {
713 9 : if (EQUAL(poNode->string_value, "identifier") ||
714 7 : EQUAL(poNode->string_value, "title") ||
715 7 : EQUAL(poNode->string_value, "type") ||
716 7 : EQUAL(poNode->string_value, "subject") ||
717 7 : EQUAL(poNode->string_value, "date") ||
718 7 : EQUAL(poNode->string_value, "language") ||
719 7 : EQUAL(poNode->string_value, "rights") ||
720 7 : EQUAL(poNode->string_value, "format") ||
721 7 : EQUAL(poNode->string_value, "creator") ||
722 7 : EQUAL(poNode->string_value, "source"))
723 : {
724 : char *pszNewVal =
725 2 : CPLStrdup(CPLSPrintf("dc:%s", poNode->string_value));
726 2 : CPLFree(poNode->string_value);
727 2 : poNode->string_value = pszNewVal;
728 : }
729 7 : else if (EQUAL(poNode->string_value, "references") ||
730 6 : EQUAL(poNode->string_value, "modified") ||
731 6 : EQUAL(poNode->string_value, "abstract"))
732 : {
733 : char *pszNewVal =
734 1 : CPLStrdup(CPLSPrintf("dct:%s", poNode->string_value));
735 1 : CPLFree(poNode->string_value);
736 1 : poNode->string_value = pszNewVal;
737 : }
738 6 : else if (EQUAL(poNode->string_value, "other_identifiers"))
739 : {
740 1 : CPLFree(poNode->string_value);
741 1 : poNode->string_value = CPLStrdup("dc:identifier");
742 : }
743 5 : else if (EQUAL(poNode->string_value, "other_subjects"))
744 : {
745 1 : CPLFree(poNode->string_value);
746 1 : poNode->string_value = CPLStrdup("dc:subject");
747 : }
748 4 : else if (EQUAL(poNode->string_value, "other_references"))
749 : {
750 1 : CPLFree(poNode->string_value);
751 1 : poNode->string_value = CPLStrdup("dct:references");
752 : }
753 3 : else if (EQUAL(poNode->string_value, "other_formats"))
754 : {
755 1 : CPLFree(poNode->string_value);
756 1 : poNode->string_value = CPLStrdup("dc:format");
757 : }
758 2 : else if (EQUAL(poNode->string_value, "AnyText"))
759 : {
760 1 : CPLFree(poNode->string_value);
761 1 : poNode->string_value = CPLStrdup("csw:AnyText");
762 : }
763 1 : else if (EQUAL(poNode->string_value, "boundingbox"))
764 : {
765 1 : CPLFree(poNode->string_value);
766 1 : poNode->string_value = CPLStrdup("ows:BoundingBox");
767 : }
768 : }
769 30 : else if (poNode->eNodeType == SNT_OPERATION)
770 : {
771 54 : for (int i = 0; i < poNode->nSubExprCount; i++)
772 37 : OGRCSWAddRightPrefixes(poNode->papoSubExpr[i]);
773 : }
774 39 : }
775 :
776 : /************************************************************************/
777 : /* SetAttributeFilter() */
778 : /************************************************************************/
779 :
780 3 : OGRErr OGRCSWLayer::SetAttributeFilter(const char *pszFilter)
781 : {
782 3 : if (pszFilter != nullptr && pszFilter[0] == 0)
783 0 : pszFilter = nullptr;
784 :
785 3 : CPLFree(m_pszAttrQueryString);
786 3 : m_pszAttrQueryString = (pszFilter) ? CPLStrdup(pszFilter) : nullptr;
787 :
788 3 : delete m_poAttrQuery;
789 3 : m_poAttrQuery = nullptr;
790 :
791 3 : if (pszFilter != nullptr)
792 : {
793 2 : m_poAttrQuery = new OGRFeatureQuery();
794 :
795 2 : OGRErr eErr = m_poAttrQuery->Compile(GetLayerDefn(), pszFilter, TRUE,
796 : WFSGetCustomFuncRegistrar());
797 2 : if (eErr != OGRERR_NONE)
798 : {
799 0 : delete m_poAttrQuery;
800 0 : m_poAttrQuery = nullptr;
801 0 : return eErr;
802 : }
803 : }
804 :
805 3 : if (m_poAttrQuery != nullptr)
806 : {
807 2 : swq_expr_node *poNode = (swq_expr_node *)m_poAttrQuery->GetSWQExpr();
808 2 : swq_expr_node *poNodeClone = poNode->Clone();
809 2 : poNodeClone->ReplaceBetweenByGEAndLERecurse();
810 2 : OGRCSWAddRightPrefixes(poNodeClone);
811 :
812 2 : int bNeedsNullCheck = FALSE;
813 2 : if (poNode->field_type != SWQ_BOOLEAN)
814 0 : osCSWWhere = "";
815 : else
816 4 : osCSWWhere = WFS_TurnSQLFilterToOGCFilter(
817 : poNodeClone, nullptr, nullptr, 110, FALSE, FALSE, FALSE,
818 2 : "ogc:", &bNeedsNullCheck);
819 2 : delete poNodeClone;
820 : }
821 : else
822 1 : osCSWWhere = "";
823 :
824 3 : if (m_poAttrQuery != nullptr && osCSWWhere.empty())
825 : {
826 0 : CPLDebug("CSW", "Using client-side only mode for filter \"%s\"",
827 : pszFilter);
828 0 : OGRErr eErr = OGRLayer::SetAttributeFilter(pszFilter);
829 0 : if (eErr != OGRERR_NONE)
830 0 : return eErr;
831 : }
832 :
833 3 : ResetReading();
834 3 : BuildQuery();
835 :
836 3 : return OGRERR_NONE;
837 : }
838 :
839 : /************************************************************************/
840 : /* BuildQuery() */
841 : /************************************************************************/
842 :
843 5 : void OGRCSWLayer::BuildQuery()
844 : {
845 5 : if (m_poFilterGeom != nullptr || !osCSWWhere.empty())
846 : {
847 4 : osQuery = "<csw:Constraint version=\"1.1.0\">";
848 4 : osQuery += "<ogc:Filter>";
849 4 : if (m_poFilterGeom != nullptr && !osCSWWhere.empty())
850 2 : osQuery += "<ogc:And>";
851 4 : if (m_poFilterGeom != nullptr)
852 : {
853 3 : osQuery += "<ogc:BBOX>";
854 3 : osQuery += "<ogc:PropertyName>ows:BoundingBox</ogc:PropertyName>";
855 3 : osQuery += "<gml:Envelope srsName=\"urn:ogc:def:crs:EPSG::4326\">";
856 3 : OGREnvelope sEnvelope;
857 3 : m_poFilterGeom->getEnvelope(&sEnvelope);
858 3 : if (CPLTestBool(CPLGetConfigOption(
859 : "GML_INVERT_AXIS_ORDER_IF_LAT_LONG", "YES")))
860 : {
861 : osQuery +=
862 : CPLSPrintf("<gml:lowerCorner>%.16g %.16g</gml:lowerCorner>",
863 3 : sEnvelope.MinY, sEnvelope.MinX);
864 : osQuery +=
865 : CPLSPrintf("<gml:upperCorner>%.16g %.16g</gml:upperCorner>",
866 3 : sEnvelope.MaxY, sEnvelope.MaxX);
867 : }
868 : else
869 : {
870 : osQuery +=
871 : CPLSPrintf("<gml:lowerCorner>%.16g %.16g</gml:lowerCorner>",
872 0 : sEnvelope.MinX, sEnvelope.MinY);
873 : osQuery +=
874 : CPLSPrintf("<gml:upperCorner>%.16g %.16g</gml:upperCorner>",
875 0 : sEnvelope.MaxX, sEnvelope.MaxY);
876 : }
877 3 : osQuery += "</gml:Envelope>";
878 3 : osQuery += "</ogc:BBOX>";
879 : }
880 4 : osQuery += osCSWWhere;
881 4 : if (m_poFilterGeom != nullptr && !osCSWWhere.empty())
882 2 : osQuery += "</ogc:And>";
883 4 : osQuery += "</ogc:Filter>";
884 4 : osQuery += "</csw:Constraint>";
885 : }
886 : else
887 1 : osQuery = "";
888 5 : }
889 :
890 : /************************************************************************/
891 : /* OGRCSWDataSource() */
892 : /************************************************************************/
893 :
894 11 : OGRCSWDataSource::OGRCSWDataSource()
895 11 : : nMaxRecords(500), poLayer(nullptr), bFullExtentRecordsAsNonSpatial(false)
896 : {
897 11 : }
898 :
899 : /************************************************************************/
900 : /* ~OGRCSWDataSource() */
901 : /************************************************************************/
902 :
903 22 : OGRCSWDataSource::~OGRCSWDataSource()
904 : {
905 11 : delete poLayer;
906 22 : }
907 :
908 : /************************************************************************/
909 : /* SendGetCapabilities() */
910 : /************************************************************************/
911 :
912 11 : CPLHTTPResult *OGRCSWDataSource::SendGetCapabilities()
913 : {
914 22 : CPLString osURL(osBaseURL);
915 :
916 11 : osURL = CPLURLAddKVP(osURL, "SERVICE", "CSW");
917 11 : osURL = CPLURLAddKVP(osURL, "REQUEST", "GetCapabilities");
918 :
919 11 : CPLDebug("CSW", "%s", osURL.c_str());
920 :
921 11 : CPLHTTPResult *psResult = HTTPFetch(osURL, nullptr);
922 11 : if (psResult == nullptr)
923 : {
924 3 : return nullptr;
925 : }
926 :
927 8 : if (strstr((const char *)psResult->pabyData, "<ServiceExceptionReport") !=
928 7 : nullptr ||
929 7 : strstr((const char *)psResult->pabyData, "<ows:ExceptionReport") !=
930 7 : nullptr ||
931 7 : strstr((const char *)psResult->pabyData, "<ExceptionReport") != nullptr)
932 : {
933 1 : CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
934 : psResult->pabyData);
935 1 : CPLHTTPDestroyResult(psResult);
936 1 : return nullptr;
937 : }
938 :
939 7 : return psResult;
940 : }
941 :
942 : /************************************************************************/
943 : /* Open() */
944 : /************************************************************************/
945 :
946 11 : int OGRCSWDataSource::Open(const char *pszFilename, char **papszOpenOptionsIn)
947 : {
948 11 : const char *pszBaseURL = CSLFetchNameValue(papszOpenOptionsIn, "URL");
949 11 : if (pszBaseURL == nullptr)
950 : {
951 11 : pszBaseURL = pszFilename;
952 11 : if (STARTS_WITH_CI(pszFilename, "CSW:"))
953 11 : pszBaseURL += 4;
954 11 : if (pszBaseURL[0] == '\0')
955 : {
956 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing URL open option");
957 0 : return FALSE;
958 : }
959 : }
960 11 : osBaseURL = pszBaseURL;
961 : osElementSetName =
962 11 : CSLFetchNameValueDef(papszOpenOptionsIn, "ELEMENTSETNAME", "full");
963 11 : bFullExtentRecordsAsNonSpatial = CPLFetchBool(
964 : papszOpenOptionsIn, "FULL_EXTENT_RECORDS_AS_NON_SPATIAL", false);
965 : osOutputSchema =
966 11 : CSLFetchNameValueDef(papszOpenOptionsIn, "OUTPUT_SCHEMA", "");
967 11 : if (EQUAL(osOutputSchema, "gmd"))
968 1 : osOutputSchema = "http://www.isotc211.org/2005/gmd";
969 10 : else if (EQUAL(osOutputSchema, "csw"))
970 1 : osOutputSchema = "http://www.opengis.net/cat/csw/2.0.2";
971 11 : nMaxRecords =
972 11 : atoi(CSLFetchNameValueDef(papszOpenOptionsIn, "MAX_RECORDS", "500"));
973 :
974 11 : if (!STARTS_WITH(osBaseURL, "http://") &&
975 22 : !STARTS_WITH(osBaseURL, "https://") &&
976 11 : !STARTS_WITH(osBaseURL, "/vsimem/"))
977 0 : return FALSE;
978 :
979 11 : CPLHTTPResult *psResult = SendGetCapabilities();
980 11 : if (psResult == nullptr)
981 4 : return FALSE;
982 :
983 7 : CPLXMLNode *psXML = CPLParseXMLString((const char *)psResult->pabyData);
984 7 : if (psXML == nullptr)
985 : {
986 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
987 : psResult->pabyData);
988 1 : CPLHTTPDestroyResult(psResult);
989 1 : return FALSE;
990 : }
991 6 : CPLStripXMLNamespace(psXML, nullptr, TRUE);
992 6 : CPLHTTPDestroyResult(psResult);
993 6 : psResult = nullptr;
994 :
995 : const char *pszVersion =
996 6 : CPLGetXMLValue(psXML, "=Capabilities.version", nullptr);
997 6 : if (pszVersion == nullptr)
998 : {
999 2 : CPLError(CE_Failure, CPLE_AppDefined,
1000 : "Cannot find Capabilities.version");
1001 2 : CPLDestroyXMLNode(psXML);
1002 2 : return FALSE;
1003 : }
1004 4 : if (!EQUAL(pszVersion, "2.0.2"))
1005 0 : CPLDebug(
1006 : "CSW",
1007 : "Presumably only work properly with 2.0.2. Reported version is %s",
1008 : pszVersion);
1009 4 : osVersion = pszVersion;
1010 4 : CPLDestroyXMLNode(psXML);
1011 :
1012 4 : poLayer = new OGRCSWLayer(this);
1013 :
1014 4 : return TRUE;
1015 : }
1016 :
1017 : /************************************************************************/
1018 : /* GetLayer() */
1019 : /************************************************************************/
1020 :
1021 6 : OGRLayer *OGRCSWDataSource::GetLayer(int iLayer)
1022 :
1023 : {
1024 6 : if (iLayer < 0 || iLayer >= ((poLayer != nullptr) ? 1 : 0))
1025 2 : return nullptr;
1026 : else
1027 4 : return poLayer;
1028 : }
1029 :
1030 : /************************************************************************/
1031 : /* HTTPFetch() */
1032 : /************************************************************************/
1033 :
1034 50 : CPLHTTPResult *OGRCSWDataSource::HTTPFetch(const char *pszURL,
1035 : const char *pszPost)
1036 : {
1037 50 : char **papszOptions = nullptr;
1038 50 : if (pszPost)
1039 : {
1040 39 : papszOptions = CSLAddNameValue(papszOptions, "POSTFIELDS", pszPost);
1041 : papszOptions =
1042 39 : CSLAddNameValue(papszOptions, "HEADERS",
1043 : "Content-Type: application/xml; charset=UTF-8");
1044 : }
1045 50 : CPLHTTPResult *psResult = CPLHTTPFetch(pszURL, papszOptions);
1046 50 : CSLDestroy(papszOptions);
1047 :
1048 50 : if (psResult == nullptr)
1049 : {
1050 0 : return nullptr;
1051 : }
1052 50 : if (psResult->nStatus != 0 || psResult->pszErrBuf != nullptr)
1053 : {
1054 8 : CPLError(CE_Failure, CPLE_AppDefined,
1055 : "Error returned by server : %s (%d)",
1056 8 : (psResult->pszErrBuf) ? psResult->pszErrBuf : "unknown",
1057 : psResult->nStatus);
1058 8 : CPLHTTPDestroyResult(psResult);
1059 8 : return nullptr;
1060 : }
1061 42 : if (psResult->pabyData == nullptr)
1062 : {
1063 3 : CPLError(CE_Failure, CPLE_AppDefined,
1064 : "Empty content returned by server");
1065 3 : CPLHTTPDestroyResult(psResult);
1066 3 : return nullptr;
1067 : }
1068 39 : return psResult;
1069 : }
1070 :
1071 : /************************************************************************/
1072 : /* Identify() */
1073 : /************************************************************************/
1074 :
1075 45925 : static int OGRCSWDriverIdentify(GDALOpenInfo *poOpenInfo)
1076 :
1077 : {
1078 45925 : return STARTS_WITH_CI(poOpenInfo->pszFilename, "CSW:");
1079 : }
1080 :
1081 : /************************************************************************/
1082 : /* Open() */
1083 : /************************************************************************/
1084 :
1085 11 : static GDALDataset *OGRCSWDriverOpen(GDALOpenInfo *poOpenInfo)
1086 :
1087 : {
1088 11 : if (!OGRCSWDriverIdentify(poOpenInfo) || poOpenInfo->eAccess == GA_Update)
1089 0 : return nullptr;
1090 :
1091 11 : OGRCSWDataSource *poDS = new OGRCSWDataSource();
1092 :
1093 11 : if (!poDS->Open(poOpenInfo->pszFilename, poOpenInfo->papszOpenOptions))
1094 : {
1095 7 : delete poDS;
1096 7 : poDS = nullptr;
1097 : }
1098 :
1099 11 : return poDS;
1100 : }
1101 :
1102 : /************************************************************************/
1103 : /* RegisterOGRCSW() */
1104 : /************************************************************************/
1105 :
1106 1686 : void RegisterOGRCSW()
1107 :
1108 : {
1109 1686 : if (GDALGetDriverByName("CSW") != nullptr)
1110 302 : return;
1111 :
1112 1384 : GDALDriver *poDriver = new GDALDriver();
1113 :
1114 1384 : poDriver->SetDescription("CSW");
1115 1384 : poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
1116 1384 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1117 1384 : "OGC CSW (Catalog Service for the Web)");
1118 1384 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/csw.html");
1119 :
1120 1384 : poDriver->SetMetadataItem(GDAL_DMD_CONNECTION_PREFIX, "CSW:");
1121 1384 : poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
1122 :
1123 1384 : poDriver->SetMetadataItem(
1124 : GDAL_DMD_OPENOPTIONLIST,
1125 : "<OpenOptionList>"
1126 : " <Option name='URL' type='string' description='URL to the CSW server "
1127 : "endpoint' required='true'/>"
1128 : " <Option name='ELEMENTSETNAME' type='string-select' "
1129 : "description='Level of details of properties' default='full'>"
1130 : " <Value>brief</Value>"
1131 : " <Value>summary</Value>"
1132 : " <Value>full</Value>"
1133 : " </Option>"
1134 : " <Option name='FULL_EXTENT_RECORDS_AS_NON_SPATIAL' type='boolean' "
1135 : "description='Whether records with (-180,-90,180,90) extent should be "
1136 : "considered non-spatial' default='false'/>"
1137 : " <Option name='OUTPUT_SCHEMA' type='string' description='Value of "
1138 : "outputSchema parameter'/>"
1139 : " <Option name='MAX_RECORDS' type='int' description='Maximum number "
1140 : "of records to retrieve in a single time' default='500'/>"
1141 1384 : "</OpenOptionList>");
1142 :
1143 1384 : poDriver->pfnIdentify = OGRCSWDriverIdentify;
1144 1384 : poDriver->pfnOpen = OGRCSWDriverOpen;
1145 :
1146 1384 : GetGDALDriverManager()->RegisterDriver(poDriver);
1147 : }
|