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 : virtual void SetSpatialFilter(OGRGeometry *) override;
69 :
70 0 : virtual void SetSpatialFilter(int iGeomField, OGRGeometry *poGeom) override
71 : {
72 0 : OGRLayer::SetSpatialFilter(iGeomField, poGeom);
73 0 : }
74 :
75 : virtual OGRErr SetAttributeFilter(const char *) override;
76 : };
77 :
78 : /************************************************************************/
79 : /* OGRCSWDataSource */
80 : /************************************************************************/
81 :
82 : class OGRCSWDataSource final : public GDALDataset
83 : {
84 : CPLString osBaseURL;
85 : CPLString osVersion;
86 : CPLString osElementSetName;
87 : CPLString osOutputSchema;
88 : int nMaxRecords;
89 :
90 : OGRCSWLayer *poLayer;
91 : bool bFullExtentRecordsAsNonSpatial;
92 :
93 : CPLHTTPResult *SendGetCapabilities();
94 :
95 : public:
96 : OGRCSWDataSource();
97 : virtual ~OGRCSWDataSource();
98 :
99 : int Open(const char *pszFilename, char **papszOpenOptions);
100 :
101 1 : virtual int GetLayerCount() override
102 : {
103 1 : return poLayer != nullptr;
104 : }
105 :
106 : virtual OGRLayer *GetLayer(int) override;
107 :
108 : static CPLHTTPResult *HTTPFetch(const char *pszURL, const char *pszPost);
109 :
110 39 : const CPLString &GetBaseURL()
111 : {
112 39 : return osBaseURL;
113 : }
114 :
115 39 : const CPLString &GetVersion()
116 : {
117 39 : return osVersion;
118 : }
119 :
120 39 : const CPLString &GetElementSetName()
121 : {
122 39 : return osElementSetName;
123 : }
124 :
125 60 : const CPLString &GetOutputSchema()
126 : {
127 60 : return osOutputSchema;
128 : }
129 :
130 10 : bool FullExtentRecordsAsNonSpatial()
131 : {
132 10 : return bFullExtentRecordsAsNonSpatial;
133 : }
134 :
135 31 : int GetMaxRecords()
136 : {
137 31 : return nMaxRecords;
138 : }
139 : };
140 :
141 : /************************************************************************/
142 : /* OGRCSWLayer() */
143 : /************************************************************************/
144 :
145 4 : OGRCSWLayer::OGRCSWLayer(OGRCSWDataSource *poDSIn)
146 4 : : poDS(poDSIn), poFeatureDefn(new OGRFeatureDefn("records")),
147 : poBaseDS(nullptr), poBaseLayer(nullptr), nPagingStartIndex(0),
148 8 : nFeatureRead(0), nFeaturesInCurrentPage(0)
149 : {
150 4 : SetDescription(poFeatureDefn->GetName());
151 4 : poFeatureDefn->Reference();
152 4 : poFeatureDefn->SetGeomType(wkbPolygon);
153 : OGRSpatialReference *poSRS =
154 4 : new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
155 4 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
156 4 : poFeatureDefn->GetGeomFieldDefn(0)->SetName("boundingbox");
157 4 : poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
158 : {
159 8 : OGRFieldDefn oField("identifier", OFTString);
160 4 : poFeatureDefn->AddFieldDefn(&oField);
161 : }
162 : {
163 8 : OGRFieldDefn oField("other_identifiers", OFTStringList);
164 4 : poFeatureDefn->AddFieldDefn(&oField);
165 : }
166 : {
167 8 : OGRFieldDefn oField("title", OFTString);
168 4 : poFeatureDefn->AddFieldDefn(&oField);
169 : }
170 : {
171 8 : OGRFieldDefn oField("type", OFTString);
172 4 : poFeatureDefn->AddFieldDefn(&oField);
173 : }
174 : {
175 8 : OGRFieldDefn oField("subject", OFTString);
176 4 : poFeatureDefn->AddFieldDefn(&oField);
177 : }
178 : {
179 8 : OGRFieldDefn oField("other_subjects", OFTStringList);
180 4 : poFeatureDefn->AddFieldDefn(&oField);
181 : }
182 : {
183 8 : OGRFieldDefn oField("references", OFTString);
184 4 : poFeatureDefn->AddFieldDefn(&oField);
185 : }
186 : {
187 8 : OGRFieldDefn oField("other_references", OFTStringList);
188 4 : poFeatureDefn->AddFieldDefn(&oField);
189 : }
190 : {
191 8 : OGRFieldDefn oField("modified", OFTString);
192 4 : poFeatureDefn->AddFieldDefn(&oField);
193 : }
194 : {
195 8 : OGRFieldDefn oField("abstract", OFTString);
196 4 : poFeatureDefn->AddFieldDefn(&oField);
197 : }
198 : {
199 8 : OGRFieldDefn oField("date", OFTString);
200 4 : poFeatureDefn->AddFieldDefn(&oField);
201 : }
202 : {
203 8 : OGRFieldDefn oField("language", OFTString);
204 4 : poFeatureDefn->AddFieldDefn(&oField);
205 : }
206 : {
207 8 : OGRFieldDefn oField("rights", OFTString);
208 4 : poFeatureDefn->AddFieldDefn(&oField);
209 : }
210 : {
211 8 : OGRFieldDefn oField("format", OFTString);
212 4 : poFeatureDefn->AddFieldDefn(&oField);
213 : }
214 : {
215 8 : OGRFieldDefn oField("other_formats", OFTStringList);
216 4 : poFeatureDefn->AddFieldDefn(&oField);
217 : }
218 : {
219 8 : OGRFieldDefn oField("creator", OFTString);
220 4 : poFeatureDefn->AddFieldDefn(&oField);
221 : }
222 : {
223 8 : OGRFieldDefn oField("source", OFTString);
224 4 : poFeatureDefn->AddFieldDefn(&oField);
225 : }
226 : {
227 8 : OGRFieldDefn oField("anytext", OFTString);
228 4 : poFeatureDefn->AddFieldDefn(&oField);
229 : }
230 4 : if (!poDS->GetOutputSchema().empty())
231 : {
232 6 : OGRFieldDefn oField("raw_xml", OFTString);
233 3 : poFeatureDefn->AddFieldDefn(&oField);
234 : }
235 :
236 4 : poSRS->Release();
237 :
238 4 : m_osTmpDir = VSIMemGenerateHiddenFilename("csw");
239 4 : }
240 :
241 : /************************************************************************/
242 : /* ~OGRCSWLayer() */
243 : /************************************************************************/
244 :
245 8 : OGRCSWLayer::~OGRCSWLayer()
246 : {
247 4 : poFeatureDefn->Release();
248 4 : GDALClose(poBaseDS);
249 4 : VSIRmdirRecursive(m_osTmpDir.c_str());
250 8 : }
251 :
252 : /************************************************************************/
253 : /* ResetReading() */
254 : /************************************************************************/
255 :
256 28 : void OGRCSWLayer::ResetReading()
257 : {
258 28 : nPagingStartIndex = 0;
259 28 : nFeatureRead = 0;
260 28 : nFeaturesInCurrentPage = 0;
261 28 : GDALClose(poBaseDS);
262 28 : poBaseDS = nullptr;
263 28 : poBaseLayer = nullptr;
264 28 : }
265 :
266 : /************************************************************************/
267 : /* GetNextFeature() */
268 : /************************************************************************/
269 :
270 38 : OGRFeature *OGRCSWLayer::GetNextFeature()
271 : {
272 : while (true)
273 : {
274 38 : if (nFeatureRead == nPagingStartIndex + nFeaturesInCurrentPage)
275 : {
276 31 : nPagingStartIndex = nFeatureRead;
277 :
278 31 : GDALClose(poBaseDS);
279 31 : poBaseLayer = nullptr;
280 :
281 31 : poBaseDS = FetchGetRecords();
282 31 : if (poBaseDS)
283 : {
284 16 : poBaseLayer = poBaseDS->GetLayer(0);
285 16 : poBaseLayer->ResetReading();
286 16 : nFeaturesInCurrentPage = (int)poBaseLayer->GetFeatureCount();
287 : }
288 : }
289 38 : if (!poBaseLayer)
290 15 : return nullptr;
291 :
292 23 : OGRFeature *poSrcFeature = poBaseLayer->GetNextFeature();
293 23 : if (poSrcFeature == nullptr)
294 0 : return nullptr;
295 23 : nFeatureRead++;
296 :
297 23 : OGRFeature *poNewFeature = new OGRFeature(poFeatureDefn);
298 :
299 440 : for (int i = 0; i < poFeatureDefn->GetFieldCount(); i++)
300 : {
301 : const char *pszFieldname =
302 417 : poFeatureDefn->GetFieldDefn(i)->GetNameRef();
303 417 : int iSrcField = poSrcFeature->GetFieldIndex(pszFieldname);
304 : /* http://www.paikkatietohakemisto.fi/geonetwork/srv/en/csw returns
305 : * URI ... */
306 417 : if (iSrcField < 0 && strcmp(pszFieldname, "references") == 0)
307 9 : iSrcField = poSrcFeature->GetFieldIndex("URI");
308 417 : if (iSrcField >= 0 && poSrcFeature->IsFieldSetAndNotNull(iSrcField))
309 : {
310 73 : OGRFieldType eType = poFeatureDefn->GetFieldDefn(i)->GetType();
311 : OGRFieldType eSrcType =
312 73 : poSrcFeature->GetFieldDefnRef(iSrcField)->GetType();
313 73 : if (eType == eSrcType)
314 : {
315 45 : poNewFeature->SetField(
316 45 : i, poSrcFeature->GetRawFieldRef(iSrcField));
317 : }
318 : else
319 : {
320 28 : if (eType == OFTString && eSrcType == OFTStringList &&
321 28 : strcmp(pszFieldname, "identifier") == 0)
322 : {
323 : char **papszValues =
324 7 : poSrcFeature->GetFieldAsStringList(iSrcField);
325 7 : poNewFeature->SetField("identifier", *papszValues);
326 7 : if (papszValues[1])
327 7 : poNewFeature->SetField("other_identifiers",
328 7 : papszValues + 1);
329 : }
330 21 : else if (eType == OFTString && eSrcType == OFTStringList &&
331 21 : strcmp(pszFieldname, "subject") == 0)
332 : {
333 : char **papszValues =
334 7 : poSrcFeature->GetFieldAsStringList(iSrcField);
335 7 : poNewFeature->SetField("subject", *papszValues);
336 7 : if (papszValues[1])
337 7 : poNewFeature->SetField("other_subjects",
338 7 : papszValues + 1);
339 : }
340 14 : else if (eType == OFTString && eSrcType == OFTStringList &&
341 14 : strcmp(pszFieldname, "references") == 0)
342 : {
343 : char **papszValues =
344 7 : poSrcFeature->GetFieldAsStringList(iSrcField);
345 7 : poNewFeature->SetField("references", *papszValues);
346 7 : if (papszValues[1])
347 7 : poNewFeature->SetField("other_references",
348 7 : papszValues + 1);
349 : }
350 7 : else if (eType == OFTString && eSrcType == OFTStringList &&
351 7 : strcmp(pszFieldname, "format") == 0)
352 : {
353 : char **papszValues =
354 7 : poSrcFeature->GetFieldAsStringList(iSrcField);
355 7 : poNewFeature->SetField("format", *papszValues);
356 7 : if (papszValues[1])
357 7 : poNewFeature->SetField("other_formats",
358 7 : papszValues + 1);
359 : }
360 : else
361 0 : poNewFeature->SetField(
362 : i, poSrcFeature->GetFieldAsString(iSrcField));
363 : }
364 : }
365 : }
366 :
367 23 : OGRGeometry *poGeom = poSrcFeature->StealGeometry();
368 23 : if (poGeom)
369 : {
370 10 : if (poDS->FullExtentRecordsAsNonSpatial())
371 : {
372 0 : OGREnvelope sEnvelope;
373 0 : poGeom->getEnvelope(&sEnvelope);
374 0 : if (sEnvelope.MinX == -180 && sEnvelope.MinY == -90 &&
375 0 : sEnvelope.MaxX == 180 && sEnvelope.MaxY == 90)
376 : {
377 0 : delete poGeom;
378 0 : poGeom = nullptr;
379 : }
380 : }
381 10 : if (poGeom)
382 : {
383 10 : poGeom->assignSpatialReference(
384 10 : poFeatureDefn->GetGeomFieldDefn(0)->GetSpatialRef());
385 10 : poNewFeature->SetGeometryDirectly(poGeom);
386 : }
387 : }
388 :
389 23 : poNewFeature->SetFID(nFeatureRead);
390 23 : delete poSrcFeature;
391 :
392 23 : if (osCSWWhere.empty() && m_poAttrQuery != nullptr &&
393 0 : !m_poAttrQuery->Evaluate(poNewFeature))
394 : {
395 0 : delete poNewFeature;
396 : }
397 : else
398 : {
399 23 : return poNewFeature;
400 : }
401 0 : }
402 : }
403 :
404 : /************************************************************************/
405 : /* GetFeatureCount() */
406 : /************************************************************************/
407 :
408 8 : GIntBig OGRCSWLayer::GetFeatureCount(int bForce)
409 : {
410 8 : GIntBig nFeatures = GetFeatureCountWithHits();
411 8 : if (nFeatures >= 0)
412 2 : return nFeatures;
413 6 : return OGRLayer::GetFeatureCount(bForce);
414 : }
415 :
416 : /************************************************************************/
417 : /* GetFeatureCountWithHits() */
418 : /************************************************************************/
419 :
420 8 : GIntBig OGRCSWLayer::GetFeatureCountWithHits()
421 : {
422 : CPLString osPost = CPLSPrintf(
423 : "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
424 : "<csw:GetRecords resultType=\"hits\" service=\"CSW\" version=\"%s\""
425 : " xmlns:csw=\"http://www.opengis.net/cat/csw/2.0.2\""
426 : " xmlns:gml=\"http://www.opengis.net/gml\""
427 : " xmlns:dc=\"http://purl.org/dc/elements/1.1/\""
428 : " xmlns:dct=\"http://purl.org/dc/terms/\""
429 : " xmlns:ogc=\"http://www.opengis.net/ogc\""
430 : " xmlns:ows=\"http://www.opengis.net/ows\""
431 : " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
432 : " xsi:schemaLocation=\"http://www.opengis.net/cat/csw/2.0.2 "
433 : "http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd\">"
434 : "<csw:Query typeNames=\"csw:Record\">"
435 : "<csw:ElementSetName>%s</csw:ElementSetName>"
436 : "%s"
437 : "</csw:Query>"
438 : "</csw:GetRecords>",
439 16 : poDS->GetVersion().c_str(), poDS->GetElementSetName().c_str(),
440 32 : osQuery.c_str());
441 :
442 : CPLHTTPResult *psResult =
443 8 : OGRCSWDataSource::HTTPFetch(poDS->GetBaseURL(), osPost);
444 8 : if (psResult == nullptr)
445 : {
446 3 : return -1;
447 : }
448 :
449 5 : CPLXMLNode *psXML = CPLParseXMLString((const char *)psResult->pabyData);
450 5 : if (psXML == nullptr)
451 : {
452 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
453 : psResult->pabyData);
454 1 : CPLHTTPDestroyResult(psResult);
455 1 : return -1;
456 : }
457 4 : CPLStripXMLNamespace(psXML, nullptr, TRUE);
458 4 : CPLHTTPDestroyResult(psResult);
459 4 : psResult = nullptr;
460 :
461 4 : GIntBig nFeatures = CPLAtoGIntBig(CPLGetXMLValue(
462 : psXML, "=GetRecordsResponse.SearchResults.numberOfRecordsMatched",
463 : "-1"));
464 :
465 4 : CPLDestroyXMLNode(psXML);
466 4 : return nFeatures;
467 : }
468 :
469 : /************************************************************************/
470 : /* FetchGetRecords() */
471 : /************************************************************************/
472 :
473 31 : GDALDataset *OGRCSWLayer::FetchGetRecords()
474 : {
475 31 : CPLHTTPResult *psResult = nullptr;
476 :
477 62 : CPLString osOutputSchema = poDS->GetOutputSchema();
478 31 : if (!osOutputSchema.empty())
479 5 : osOutputSchema = " outputSchema=\"" + osOutputSchema + "\"";
480 :
481 : CPLString osPost = CPLSPrintf(
482 : "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
483 : "<csw:GetRecords resultType=\"results\" service=\"CSW\" version=\"%s\""
484 : "%s"
485 : " startPosition=\"%d\""
486 : " maxRecords=\"%d\""
487 : " xmlns:csw=\"http://www.opengis.net/cat/csw/2.0.2\""
488 : " xmlns:gml=\"http://www.opengis.net/gml\""
489 : " xmlns:dc=\"http://purl.org/dc/elements/1.1/\""
490 : " xmlns:dct=\"http://purl.org/dc/terms/\""
491 : " xmlns:ogc=\"http://www.opengis.net/ogc\""
492 : " xmlns:ows=\"http://www.opengis.net/ows\""
493 : " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
494 : " xsi:schemaLocation=\"http://www.opengis.net/cat/csw/2.0.2 "
495 : "http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd\">"
496 : "<csw:Query typeNames=\"csw:Record\">"
497 : "<csw:ElementSetName>%s</csw:ElementSetName>"
498 : "%s"
499 : "</csw:Query>"
500 : "</csw:GetRecords>",
501 31 : poDS->GetVersion().c_str(), osOutputSchema.c_str(),
502 62 : nPagingStartIndex + 1, poDS->GetMaxRecords(),
503 93 : poDS->GetElementSetName().c_str(), osQuery.c_str());
504 :
505 31 : psResult = OGRCSWDataSource::HTTPFetch(poDS->GetBaseURL(), osPost);
506 31 : if (psResult == nullptr)
507 : {
508 5 : return nullptr;
509 : }
510 :
511 26 : VSIMkdir(m_osTmpDir.c_str(), 0);
512 :
513 26 : GByte *pabyData = psResult->pabyData;
514 26 : int nDataLen = psResult->nDataLen;
515 :
516 26 : if (strstr((const char *)pabyData, "<ServiceExceptionReport") != nullptr ||
517 25 : strstr((const char *)pabyData, "<ows:ExceptionReport") != nullptr)
518 : {
519 1 : CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
520 : pabyData);
521 1 : CPLHTTPDestroyResult(psResult);
522 1 : return nullptr;
523 : }
524 : // CPLDebug("CSW", "%s", (const char*)pabyData);
525 :
526 50 : CPLString osTmpFileName;
527 :
528 25 : osTmpFileName = m_osTmpDir + "/file.gfs";
529 25 : VSIUnlink(osTmpFileName);
530 :
531 25 : osTmpFileName = m_osTmpDir + "/file.gml";
532 :
533 : VSILFILE *fp =
534 25 : VSIFileFromMemBuffer(osTmpFileName, pabyData, nDataLen, TRUE);
535 25 : VSIFCloseL(fp);
536 25 : psResult->pabyData = nullptr;
537 :
538 25 : CPLHTTPDestroyResult(psResult);
539 :
540 25 : GDALDataset *l_poBaseDS = nullptr;
541 :
542 25 : if (!poDS->GetOutputSchema().empty())
543 : {
544 5 : GDALDriver *poDrv = (GDALDriver *)GDALGetDriverByName("Memory");
545 5 : if (poDrv == nullptr)
546 2 : return nullptr;
547 5 : CPLXMLNode *psRoot = CPLParseXMLFile(osTmpFileName);
548 5 : if (psRoot == nullptr)
549 : {
550 1 : if (strstr((const char *)pabyData, "<csw:GetRecordsResponse") ==
551 1 : nullptr &&
552 1 : strstr((const char *)pabyData, "<GetRecordsResponse") ==
553 : nullptr)
554 : {
555 1 : if (nDataLen > 1000)
556 0 : pabyData[1000] = 0;
557 1 : CPLError(CE_Failure, CPLE_AppDefined, "Error: cannot parse %s",
558 : pabyData);
559 : }
560 1 : return nullptr;
561 : }
562 : CPLXMLNode *psSearchResults =
563 4 : CPLGetXMLNode(psRoot, "=csw:GetRecordsResponse.csw:SearchResults");
564 4 : if (psSearchResults == nullptr)
565 : {
566 1 : CPLError(CE_Failure, CPLE_AppDefined,
567 : "Cannot find GetRecordsResponse.SearchResults");
568 1 : CPLDestroyXMLNode(psRoot);
569 1 : return nullptr;
570 : }
571 :
572 3 : l_poBaseDS = poDrv->Create("", 0, 0, 0, GDT_Unknown, nullptr);
573 3 : OGRLayer *poLyr = l_poBaseDS->CreateLayer("records");
574 6 : OGRFieldDefn oField("raw_xml", OFTString);
575 3 : poLyr->CreateField(&oField);
576 21 : for (CPLXMLNode *psIter = psSearchResults->psChild; psIter;
577 18 : psIter = psIter->psNext)
578 : {
579 18 : if (psIter->eType == CXT_Element)
580 : {
581 3 : OGRFeature *poFeature = new OGRFeature(poLyr->GetLayerDefn());
582 :
583 3 : CPLXMLNode *psNext = psIter->psNext;
584 3 : psIter->psNext = nullptr;
585 3 : char *pszXML = CPLSerializeXMLTree(psIter);
586 :
587 3 : const char *pszWest = nullptr;
588 3 : const char *pszEast = nullptr;
589 3 : const char *pszSouth = nullptr;
590 3 : const char *pszNorth = nullptr;
591 : CPLXMLNode *psBBox =
592 3 : CPLSearchXMLNode(psIter, "gmd:EX_GeographicBoundingBox");
593 3 : if (psBBox)
594 : {
595 : /* ISO 19115/19119: http://www.isotc211.org/2005/gmd */
596 1 : pszWest = CPLGetXMLValue(
597 : psBBox, "gmd:westBoundLongitude.gco:Decimal", nullptr);
598 1 : pszEast = CPLGetXMLValue(
599 : psBBox, "gmd:eastBoundLongitude.gco:Decimal", nullptr);
600 1 : pszSouth = CPLGetXMLValue(
601 : psBBox, "gmd:southBoundLatitude.gco:Decimal", nullptr);
602 1 : pszNorth = CPLGetXMLValue(
603 : psBBox, "gmd:northBoundLatitude.gco:Decimal", nullptr);
604 : }
605 2 : else if ((psBBox = CPLSearchXMLNode(psIter, "spdom")) !=
606 : nullptr)
607 : {
608 : /* FGDC: http://www.opengis.net/cat/csw/csdgm */
609 : pszWest =
610 1 : CPLGetXMLValue(psBBox, "bounding.westbc", nullptr);
611 : pszEast =
612 1 : CPLGetXMLValue(psBBox, "bounding.eastbc", nullptr);
613 : pszSouth =
614 1 : CPLGetXMLValue(psBBox, "bounding.southbc", nullptr);
615 : pszNorth =
616 1 : CPLGetXMLValue(psBBox, "bounding.northbc", nullptr);
617 : }
618 3 : if (pszWest && pszEast && pszSouth && pszNorth)
619 : {
620 2 : double dfMinX = CPLAtof(pszWest);
621 2 : double dfMaxX = CPLAtof(pszEast);
622 2 : double dfMinY = CPLAtof(pszSouth);
623 2 : double dfMaxY = CPLAtof(pszNorth);
624 2 : OGRLinearRing *poLR = new OGRLinearRing();
625 2 : poLR->addPoint(dfMinX, dfMinY);
626 2 : poLR->addPoint(dfMinX, dfMaxY);
627 2 : poLR->addPoint(dfMaxX, dfMaxY);
628 2 : poLR->addPoint(dfMaxX, dfMinY);
629 2 : poLR->addPoint(dfMinX, dfMinY);
630 2 : OGRPolygon *poPoly = new OGRPolygon();
631 2 : poPoly->addRingDirectly(poLR);
632 2 : poFeature->SetGeometryDirectly(poPoly);
633 : }
634 1 : else if ((psBBox = CPLSearchXMLNode(
635 1 : psIter, "ows:BoundingBox")) != nullptr)
636 : {
637 1 : CPLFree(psBBox->pszValue);
638 1 : psBBox->pszValue = CPLStrdup("gml:Envelope");
639 2 : CPLString osSRS = CPLGetXMLValue(psBBox, "crs", "");
640 1 : OGRGeometry *poGeom = GML2OGRGeometry_XMLNode(
641 : psBBox, FALSE, 0, 0, false, true, false);
642 1 : if (poGeom)
643 : {
644 1 : bool bLatLongOrder = true;
645 1 : if (!osSRS.empty())
646 1 : bLatLongOrder = GML_IsSRSLatLongOrder(osSRS);
647 2 : if (bLatLongOrder &&
648 1 : CPLTestBool(CPLGetConfigOption(
649 : "GML_INVERT_AXIS_ORDER_IF_LAT_LONG", "YES")))
650 1 : poGeom->swapXY();
651 1 : poFeature->SetGeometryDirectly(poGeom);
652 : }
653 : }
654 :
655 3 : psIter->psNext = psNext;
656 :
657 3 : poFeature->SetField(0, pszXML);
658 3 : CPL_IGNORE_RET_VAL(poLyr->CreateFeature(poFeature));
659 3 : CPLFree(pszXML);
660 3 : delete poFeature;
661 : }
662 : }
663 3 : CPLDestroyXMLNode(psRoot);
664 : }
665 : else
666 : {
667 20 : l_poBaseDS = (GDALDataset *)OGROpen(osTmpFileName, FALSE, nullptr);
668 20 : if (l_poBaseDS == nullptr)
669 : {
670 2 : if (strstr((const char *)pabyData, "<csw:GetRecordsResponse") ==
671 2 : nullptr &&
672 2 : strstr((const char *)pabyData, "<GetRecordsResponse") ==
673 : nullptr)
674 : {
675 2 : if (nDataLen > 1000)
676 0 : pabyData[1000] = 0;
677 2 : CPLError(CE_Failure, CPLE_AppDefined, "Error: cannot parse %s",
678 : pabyData);
679 : }
680 2 : return nullptr;
681 : }
682 : }
683 :
684 21 : OGRLayer *poLayer = l_poBaseDS->GetLayer(0);
685 21 : if (poLayer == nullptr)
686 : {
687 5 : GDALClose(l_poBaseDS);
688 5 : return nullptr;
689 : }
690 :
691 16 : return l_poBaseDS;
692 : }
693 :
694 : /************************************************************************/
695 : /* SetSpatialFilter() */
696 : /************************************************************************/
697 :
698 2 : void OGRCSWLayer::SetSpatialFilter(OGRGeometry *poGeom)
699 : {
700 2 : OGRLayer::SetSpatialFilter(poGeom);
701 2 : ResetReading();
702 2 : BuildQuery();
703 2 : }
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 45006 : static int OGRCSWDriverIdentify(GDALOpenInfo *poOpenInfo)
1076 :
1077 : {
1078 45006 : 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 1595 : void RegisterOGRCSW()
1107 :
1108 : {
1109 1595 : if (GDALGetDriverByName("CSW") != nullptr)
1110 302 : return;
1111 :
1112 1293 : GDALDriver *poDriver = new GDALDriver();
1113 :
1114 1293 : poDriver->SetDescription("CSW");
1115 1293 : poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
1116 1293 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1117 1293 : "OGC CSW (Catalog Service for the Web)");
1118 1293 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/csw.html");
1119 :
1120 1293 : poDriver->SetMetadataItem(GDAL_DMD_CONNECTION_PREFIX, "CSW:");
1121 1293 : poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
1122 :
1123 1293 : 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 1293 : "</OpenOptionList>");
1142 :
1143 1293 : poDriver->pfnIdentify = OGRCSWDriverIdentify;
1144 1293 : poDriver->pfnOpen = OGRCSWDriverOpen;
1145 :
1146 1293 : GetGDALDriverManager()->RegisterDriver(poDriver);
1147 : }
|