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