QGIS API Documentation 3.99.0-Master (a8882ad4560)
Loading...
Searching...
No Matches
qgsogcutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsogcutils.cpp
3 ---------------------
4 begin : March 2013
5 copyright : (C) 2013 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15#include "qgsogcutils.h"
16
17#include <memory>
18#include <ogr_api.h>
19
21#include "qgsexpression.h"
22#include "qgsexpression_p.h"
26#include "qgsgeometry.h"
27#include "qgslogger.h"
28#include "qgsmultipolygon.h"
29#include "qgsogrutils.h"
30#include "qgspolygon.h"
31#include "qgsrectangle.h"
32#include "qgsstringutils.h"
33#include "qgsvectorlayer.h"
34#include "qgswkbptr.h"
35
36#include <QColor>
37#include <QObject>
38#include <QRegularExpression>
39#include <QStringList>
40#include <QTextStream>
41
42#ifndef Q_OS_WIN
43#include <netinet/in.h>
44#else
45#include <winsock.h>
46#endif
47
48
49#define GML_NAMESPACE u"http://www.opengis.net/gml"_s
50#define GML32_NAMESPACE u"http://www.opengis.net/gml/3.2"_s
51#define OGC_NAMESPACE u"http://www.opengis.net/ogc"_s
52#define FES_NAMESPACE u"http://www.opengis.net/fes/2.0"_s
53#define SE_NAMESPACE u"http://www.opengis.net/se"_s
54
56 QgsOgcUtils::GMLVersion gmlVersion,
57 QgsOgcUtils::FilterVersion filterVersion,
58 const QString &namespacePrefix,
59 const QString &namespaceURI,
60 const QString &geometryName,
61 const QString &srsName,
62 bool honourAxisOrientation,
63 bool invertAxisOrientation,
64 const QMap<QString, QString> &fieldNameToXPathMap,
65 const QMap<QString, QString> &namespacePrefixToUriMap )
66 : mDoc( doc )
67 , mGMLVersion( gmlVersion )
68 , mFilterVersion( filterVersion )
69 , mNamespacePrefix( namespacePrefix )
70 , mNamespaceURI( namespaceURI )
71 , mGeometryName( geometryName )
72 , mSrsName( srsName )
73 , mInvertAxisOrientation( invertAxisOrientation )
74 , mFieldNameToXPathMap( fieldNameToXPathMap )
75 , mNamespacePrefixToUriMap( namespacePrefixToUriMap )
76 , mFilterPrefix( ( filterVersion == QgsOgcUtils::FILTER_FES_2_0 ) ? "fes" : "ogc" )
77 , mPropertyName( ( filterVersion == QgsOgcUtils::FILTER_FES_2_0 ) ? "ValueReference" : "PropertyName" )
78{
80 if ( !mSrsName.isEmpty() )
82 if ( crs.isValid() )
83 {
84 if ( honourAxisOrientation && crs.hasAxisInverted() )
85 {
86 mInvertAxisOrientation = !mInvertAxisOrientation;
87 }
88 }
89}
90
91QgsGeometry QgsOgcUtils::geometryFromGML( const QDomNode &geometryNode, const Context &context )
92{
93 QDomElement geometryTypeElement = geometryNode.toElement();
94 QString geomType = geometryTypeElement.tagName();
95 QgsGeometry geometry;
96
97 if ( !( geomType == "Point"_L1 || geomType == "LineString"_L1 || geomType == "Polygon"_L1 ||
98 geomType == "MultiPoint"_L1 || geomType == "MultiLineString"_L1 || geomType == "MultiPolygon"_L1 ||
99 geomType == "Box"_L1 || geomType == "Envelope"_L1 || geomType == "MultiCurve"_L1 ) )
100 {
101 const QDomNode geometryChild = geometryNode.firstChild();
102 if ( geometryChild.isNull() )
103 {
104 return geometry;
105 }
106 geometryTypeElement = geometryChild.toElement();
107 geomType = geometryTypeElement.tagName();
108 }
109
110 if ( !( geomType == "Point"_L1 || geomType == "LineString"_L1 || geomType == "Polygon"_L1 ||
111 geomType == "MultiPoint"_L1 || geomType == "MultiLineString"_L1 || geomType == "MultiPolygon"_L1 ||
112 geomType == "Box"_L1 || geomType == "Envelope"_L1 || geomType == "MultiCurve"_L1 ) )
113 return QgsGeometry();
114
115 if ( geomType == "Point"_L1 )
116 {
117 geometry = geometryFromGMLPoint( geometryTypeElement );
118 }
119 else if ( geomType == "LineString"_L1 )
120 {
121 geometry = geometryFromGMLLineString( geometryTypeElement );
122 }
123 else if ( geomType == "Polygon"_L1 )
124 {
125 geometry = geometryFromGMLPolygon( geometryTypeElement );
126 }
127 else if ( geomType == "MultiPoint"_L1 )
128 {
129 geometry = geometryFromGMLMultiPoint( geometryTypeElement );
130 }
131 else if ( geomType == "MultiLineString"_L1 )
132 {
133 geometry = geometryFromGMLMultiLineString( geometryTypeElement );
134 }
135 else if ( geomType == "MultiCurve"_L1 )
136 {
137 geometry = geometryFromGMLMultiCurve( geometryTypeElement );
138 }
139 else if ( geomType == "MultiPolygon"_L1 )
140 {
141 geometry = geometryFromGMLMultiPolygon( geometryTypeElement );
142 }
143 else if ( geomType == "Box"_L1 )
144 {
145 geometry = QgsGeometry::fromRect( rectangleFromGMLBox( geometryTypeElement ) );
146 }
147 else if ( geomType == "Envelope"_L1 )
148 {
149 geometry = QgsGeometry::fromRect( rectangleFromGMLEnvelope( geometryTypeElement ) );
150 }
151 else //unknown type
152 {
153 QgsDebugMsgLevel( u"Unknown geometry type %1"_s.arg( geomType ), 2 );
154 return geometry;
155 }
156
157 // Handle srsName
158 // Check if the XY coordinates of geometry need to be swapped by checking the srs from the GML
160 if ( geometryTypeElement.hasAttribute( u"srsName"_s ) )
161 {
162 QString srsName { geometryTypeElement.attribute( u"srsName"_s ) };
163
164 // The logic here follows WFS GeoServer conventions from https://docs.geoserver.org/latest/en/user/services/wfs/axis_order.html
165 const bool ignoreAxisOrientation { srsName.startsWith( "http://www.opengis.net/gml/srs/"_L1 ) || srsName.startsWith( "EPSG:"_L1 ) };
166
167 // GDAL does not recognise http://www.opengis.net/gml/srs/epsg.xml#4326 but it does
168 // http://www.opengis.net/def/crs/EPSG/0/4326 so, let's try that
169 if ( srsName.startsWith( "http://www.opengis.net/gml/srs/"_L1 ) )
170 {
171 const auto parts { srsName.split( QRegularExpression( QStringLiteral( R"raw(/|#|\.)raw" ) ) ) };
172 if ( parts.length() == 10 )
173 {
174 srsName = u"http://www.opengis.net/def/crs/%1/0/%2"_s.arg( parts[ 7 ].toUpper(), parts[ 9 ] );
175 }
176 }
177 geomSrs.createFromUserInput( srsName );
178 if ( geomSrs.isValid() && geomSrs.hasAxisInverted() && !ignoreAxisOrientation )
179 {
180 geometry.get()->swapXy();
181 }
182 }
183
184 // Apply a coordinate transformation if context has information about the layer and the transformation context
185 if ( geomSrs.isValid() && context.layer && geomSrs != context.layer->crs() )
186 {
187 const QgsCoordinateTransform transformer { geomSrs, context.layer->crs(), context.transformContext };
188 try
189 {
190 const Qgis::GeometryOperationResult result = geometry.transform( transformer );
192 {
193 QgsDebugMsgLevel( u"Error transforming geometry: %1"_s.arg( qgsEnumValueToKey( result ) ), 2 );
194 }
195 }
196 catch ( QgsCsException & )
197 {
198 QgsDebugMsgLevel( u"CS error transforming geometry"_s, 2 );
199 }
200 }
201
202 return geometry;
203}
204
205QgsGeometry QgsOgcUtils::geometryFromGML( const QString &xmlString, const Context &context )
206{
207 // wrap the string into a root tag to have "gml" namespace (and also as a default namespace)
208 const QString xml = u"<tmp xmlns=\"%1\" xmlns:gml=\"%1\">%2</tmp>"_s.arg( GML_NAMESPACE, xmlString );
209 QDomDocument doc;
210 if ( !doc.setContent( xml, true ) )
211 return QgsGeometry();
212
213 return geometryFromGML( doc.documentElement().firstChildElement(), context );
214}
215
216
217QgsGeometry QgsOgcUtils::geometryFromGMLPoint( const QDomElement &geometryElement )
218{
219 QgsPolyline pointCoordinate;
220
221 const QDomNodeList coordList = geometryElement.elementsByTagNameNS( GML_NAMESPACE, u"coordinates"_s );
222 if ( !coordList.isEmpty() )
223 {
224 const QDomElement coordElement = coordList.at( 0 ).toElement();
225 if ( readGMLCoordinates( pointCoordinate, coordElement ) != 0 )
226 {
227 return QgsGeometry();
228 }
229 }
230 else
231 {
232 const QDomNodeList posList = geometryElement.elementsByTagNameNS( GML_NAMESPACE, u"pos"_s );
233 if ( posList.size() < 1 )
234 {
235 return QgsGeometry();
236 }
237 const QDomElement posElement = posList.at( 0 ).toElement();
238 if ( readGMLPositions( pointCoordinate, posElement ) != 0 )
239 {
240 return QgsGeometry();
241 }
242 }
243
244 if ( pointCoordinate.empty() )
245 {
246 return QgsGeometry();
247 }
248
249 const bool hasZ { !std::isnan( pointCoordinate.first().z() ) };
250 QgsPolyline::const_iterator point_it = pointCoordinate.constBegin();
251 const char e = static_cast<char>( htonl( 1 ) != 1 );
252 const double x = point_it->x();
253 const double y = point_it->y();
254 const int size = 1 + static_cast<int>( sizeof( int ) ) + ( hasZ ? 3 : 2 ) * static_cast<int>( sizeof( double ) );
255
257 unsigned char *wkb = new unsigned char[size];
258
259 int wkbPosition = 0; //current offset from wkb beginning (in bytes)
260 memcpy( &( wkb )[wkbPosition], &e, 1 );
261 wkbPosition += 1;
262 memcpy( &( wkb )[wkbPosition], &type, sizeof( int ) );
263 wkbPosition += sizeof( int );
264 memcpy( &( wkb )[wkbPosition], &x, sizeof( double ) );
265 wkbPosition += sizeof( double );
266 memcpy( &( wkb )[wkbPosition], &y, sizeof( double ) );
267
268 if ( hasZ )
269 {
270 wkbPosition += sizeof( double );
271 double z = point_it->z();
272 memcpy( &( wkb )[wkbPosition], &z, sizeof( double ) );
273 }
274
275 QgsGeometry g;
276 g.fromWkb( wkb, size );
277 return g;
278}
279
280QgsGeometry QgsOgcUtils::geometryFromGMLLineString( const QDomElement &geometryElement )
281{
282 QgsPolyline lineCoordinates;
283
284 const QDomNodeList coordList = geometryElement.elementsByTagNameNS( GML_NAMESPACE, u"coordinates"_s );
285 if ( !coordList.isEmpty() )
286 {
287 const QDomElement coordElement = coordList.at( 0 ).toElement();
288 if ( readGMLCoordinates( lineCoordinates, coordElement ) != 0 )
289 {
290 return QgsGeometry();
291 }
292 }
293 else
294 {
295 const QDomNodeList posList = geometryElement.elementsByTagNameNS( GML_NAMESPACE, u"posList"_s );
296 if ( posList.size() < 1 )
297 {
298 return QgsGeometry();
299 }
300 const QDomElement posElement = posList.at( 0 ).toElement();
301 if ( readGMLPositions( lineCoordinates, posElement ) != 0 )
302 {
303 return QgsGeometry();
304 }
305 }
306
307 const bool hasZ { !std::isnan( lineCoordinates.first().z() ) };
308
309 char e = static_cast<char>( htonl( 1 ) != 1 );
310 const int size = 1 + 2 * static_cast<int>( sizeof( int ) + lineCoordinates.size() ) * ( hasZ ? 3 : 2 ) * static_cast<int>( sizeof( double ) );
311
313 unsigned char *wkb = new unsigned char[size];
314
315 int wkbPosition = 0; //current offset from wkb beginning (in bytes)
316 double x, y;
317 int nPoints = lineCoordinates.size();
318
319 //fill the contents into *wkb
320 memcpy( &( wkb )[wkbPosition], &e, 1 );
321 wkbPosition += 1;
322 memcpy( &( wkb )[wkbPosition], &type, sizeof( int ) );
323 wkbPosition += sizeof( int );
324 memcpy( &( wkb )[wkbPosition], &nPoints, sizeof( int ) );
325 wkbPosition += sizeof( int );
326
327 QgsPolyline::const_iterator iter;
328 for ( iter = lineCoordinates.constBegin(); iter != lineCoordinates.constEnd(); ++iter )
329 {
330 x = iter->x();
331 y = iter->y();
332 memcpy( &( wkb )[wkbPosition], &x, sizeof( double ) );
333 wkbPosition += sizeof( double );
334 memcpy( &( wkb )[wkbPosition], &y, sizeof( double ) );
335 wkbPosition += sizeof( double );
336
337 if ( hasZ )
338 {
339 double z = iter->z();
340 memcpy( &( wkb )[wkbPosition], &z, sizeof( double ) );
341 wkbPosition += sizeof( double );
342 }
343
344 }
345
346 QgsGeometry g;
347 g.fromWkb( wkb, size );
348 return g;
349}
350
351QgsGeometry QgsOgcUtils::geometryFromGMLPolygon( const QDomElement &geometryElement )
352{
353 //read all the coordinates (as QgsPoint) into memory. Each linear ring has an entry in the vector
354 QgsMultiPolyline ringCoordinates;
355
356 //read coordinates for outer boundary
357 QgsPolyline exteriorPointList;
358 const QDomNodeList outerBoundaryList = geometryElement.elementsByTagNameNS( GML_NAMESPACE, u"outerBoundaryIs"_s );
359 if ( !outerBoundaryList.isEmpty() ) //outer ring is necessary
360 {
361 QDomElement coordinatesElement = outerBoundaryList.at( 0 ).firstChild().firstChild().toElement();
362 if ( coordinatesElement.isNull() )
363 {
364 return QgsGeometry();
365 }
366 if ( readGMLCoordinates( exteriorPointList, coordinatesElement ) != 0 )
367 {
368 return QgsGeometry();
369 }
370 ringCoordinates.push_back( exteriorPointList );
371
372 //read coordinates for inner boundary
373 const QDomNodeList innerBoundaryList = geometryElement.elementsByTagNameNS( GML_NAMESPACE, u"innerBoundaryIs"_s );
374 for ( int i = 0; i < innerBoundaryList.size(); ++i )
375 {
376 QgsPolyline interiorPointList;
377 coordinatesElement = innerBoundaryList.at( i ).firstChild().firstChild().toElement();
378 if ( coordinatesElement.isNull() )
379 {
380 return QgsGeometry();
381 }
382 if ( readGMLCoordinates( interiorPointList, coordinatesElement ) != 0 )
383 {
384 return QgsGeometry();
385 }
386 ringCoordinates.push_back( interiorPointList );
387 }
388 }
389 else
390 {
391 //read coordinates for exterior
392 const QDomNodeList exteriorList = geometryElement.elementsByTagNameNS( GML_NAMESPACE, u"exterior"_s );
393 if ( exteriorList.size() < 1 ) //outer ring is necessary
394 {
395 return QgsGeometry();
396 }
397 const QDomElement posElement = exteriorList.at( 0 ).firstChild().firstChild().toElement();
398 if ( posElement.isNull() )
399 {
400 return QgsGeometry();
401 }
402 if ( readGMLPositions( exteriorPointList, posElement ) != 0 )
403 {
404 return QgsGeometry();
405 }
406 ringCoordinates.push_back( exteriorPointList );
407
408 //read coordinates for inner boundary
409 const QDomNodeList interiorList = geometryElement.elementsByTagNameNS( GML_NAMESPACE, u"interior"_s );
410 for ( int i = 0; i < interiorList.size(); ++i )
411 {
412 QgsPolyline interiorPointList;
413 const QDomElement posElement = interiorList.at( i ).firstChild().firstChild().toElement();
414 if ( posElement.isNull() )
415 {
416 return QgsGeometry();
417 }
418 // Note: readGMLPositions returns true on errors and false on success
419 if ( readGMLPositions( interiorPointList, posElement ) )
420 {
421 return QgsGeometry();
422 }
423 ringCoordinates.push_back( interiorPointList );
424 }
425 }
426
427 //calculate number of bytes to allocate
428 int nrings = ringCoordinates.size();
429 if ( nrings < 1 )
430 return QgsGeometry();
431
432 int npoints = 0;//total number of points
433 for ( QgsMultiPolyline::const_iterator it = ringCoordinates.constBegin(); it != ringCoordinates.constEnd(); ++it )
434 {
435 npoints += it->size();
436 }
437
438 const bool hasZ { !std::isnan( ringCoordinates.first().first().z() ) };
439
440 const int size = 1 + 2 * static_cast<int>( sizeof( int ) ) + nrings * static_cast<int>( sizeof( int ) ) + ( hasZ ? 3 : 2 ) * npoints * static_cast<int>( sizeof( double ) );
441
443 unsigned char *wkb = new unsigned char[size];
444
445 //char e = QgsApplication::endian();
446 char e = static_cast<char>( htonl( 1 ) != 1 );
447 int wkbPosition = 0; //current offset from wkb beginning (in bytes)
448 int nPointsInRing = 0;
449 double x, y, z;
450
451 //fill the contents into *wkb
452 memcpy( &( wkb )[wkbPosition], &e, 1 );
453 wkbPosition += 1;
454 memcpy( &( wkb )[wkbPosition], &type, sizeof( int ) );
455 wkbPosition += sizeof( int );
456 memcpy( &( wkb )[wkbPosition], &nrings, sizeof( int ) );
457 wkbPosition += sizeof( int );
458 for ( QgsMultiPolyline::const_iterator it = ringCoordinates.constBegin(); it != ringCoordinates.constEnd(); ++it )
459 {
460 nPointsInRing = it->size();
461 memcpy( &( wkb )[wkbPosition], &nPointsInRing, sizeof( int ) );
462 wkbPosition += sizeof( int );
463 //iterate through the string list converting the strings to x-/y- doubles
464 QgsPolyline::const_iterator iter;
465 for ( iter = it->begin(); iter != it->end(); ++iter )
466 {
467 x = iter->x();
468 y = iter->y();
469 //qWarning("currentCoordinate: " + QString::number(x) + " // " + QString::number(y));
470 memcpy( &( wkb )[wkbPosition], &x, sizeof( double ) );
471 wkbPosition += sizeof( double );
472 memcpy( &( wkb )[wkbPosition], &y, sizeof( double ) );
473 wkbPosition += sizeof( double );
474
475 if ( hasZ )
476 {
477 z = iter->z();
478 memcpy( &( wkb )[wkbPosition], &z, sizeof( double ) );
479 wkbPosition += sizeof( double );
480 }
481 }
482 }
483
484 QgsGeometry g;
485 g.fromWkb( wkb, size );
486 return g;
487}
488
489QgsGeometry QgsOgcUtils::geometryFromGMLMultiPoint( const QDomElement &geometryElement )
490{
491 QgsPolyline pointList;
492 QgsPolyline currentPoint;
493 const QDomNodeList pointMemberList = geometryElement.elementsByTagNameNS( GML_NAMESPACE, u"pointMember"_s );
494 if ( pointMemberList.size() < 1 )
495 {
496 return QgsGeometry();
497 }
498 QDomNodeList pointNodeList;
499 // coordinates or pos element
500 QDomNodeList coordinatesList;
501 QDomNodeList posList;
502 for ( int i = 0; i < pointMemberList.size(); ++i )
503 {
504 //<Point> element
505 pointNodeList = pointMemberList.at( i ).toElement().elementsByTagNameNS( GML_NAMESPACE, u"Point"_s );
506 if ( pointNodeList.size() < 1 )
507 {
508 continue;
509 }
510 //<coordinates> element
511 coordinatesList = pointNodeList.at( 0 ).toElement().elementsByTagNameNS( GML_NAMESPACE, u"coordinates"_s );
512 if ( !coordinatesList.isEmpty() )
513 {
514 currentPoint.clear();
515 if ( readGMLCoordinates( currentPoint, coordinatesList.at( 0 ).toElement() ) != 0 )
516 {
517 continue;
518 }
519 if ( currentPoint.empty() )
520 {
521 continue;
522 }
523 pointList.push_back( ( *currentPoint.begin() ) );
524 continue;
525 }
526 else
527 {
528 //<pos> element
529 posList = pointNodeList.at( 0 ).toElement().elementsByTagNameNS( GML_NAMESPACE, u"pos"_s );
530 if ( posList.size() < 1 )
531 {
532 continue;
533 }
534 currentPoint.clear();
535 if ( readGMLPositions( currentPoint, posList.at( 0 ).toElement() ) != 0 )
536 {
537 continue;
538 }
539 if ( currentPoint.empty() )
540 {
541 continue;
542 }
543 pointList.push_back( ( *currentPoint.begin() ) );
544 }
545 }
546
547 int nPoints = pointList.size(); //number of points
548 if ( nPoints < 1 )
549 return QgsGeometry();
550
551 const bool hasZ { !std::isnan( pointList.first().z() ) };
552
553 //calculate the required wkb size
554 const int size = 1 + 2 * static_cast<int>( sizeof( int ) ) + static_cast<int>( pointList.size() ) * ( ( hasZ ? 3 : 2 ) * static_cast<int>( sizeof( double ) ) + 1 + static_cast<int>( sizeof( int ) ) );
555
557 unsigned char *wkb = new unsigned char[size];
558
559 //fill the wkb content
560 char e = static_cast<char>( htonl( 1 ) != 1 );
561 int wkbPosition = 0; //current offset from wkb beginning (in bytes)
562 double x, y, z;
563 memcpy( &( wkb )[wkbPosition], &e, 1 );
564 wkbPosition += 1;
565 memcpy( &( wkb )[wkbPosition], &type, sizeof( int ) );
566 wkbPosition += sizeof( int );
567 memcpy( &( wkb )[wkbPosition], &nPoints, sizeof( int ) );
568 wkbPosition += sizeof( int );
569 const Qgis::WkbType pointType { hasZ ? Qgis::WkbType::PointZ : Qgis::WkbType::Point };
570 for ( QgsPolyline::const_iterator it = pointList.constBegin(); it != pointList.constEnd(); ++it )
571 {
572 memcpy( &( wkb )[wkbPosition], &e, 1 );
573 wkbPosition += 1;
574 memcpy( &( wkb )[wkbPosition], &pointType, sizeof( int ) );
575 wkbPosition += sizeof( int );
576 x = it->x();
577 memcpy( &( wkb )[wkbPosition], &x, sizeof( double ) );
578 wkbPosition += sizeof( double );
579 y = it->y();
580 memcpy( &( wkb )[wkbPosition], &y, sizeof( double ) );
581 wkbPosition += sizeof( double );
582
583 if ( hasZ )
584 {
585 z = it->z();
586 memcpy( &( wkb )[wkbPosition], &z, sizeof( double ) );
587 wkbPosition += sizeof( double );
588 }
589 }
590
591 QgsGeometry g;
592 g.fromWkb( wkb, size );
593 return g;
594}
595
596QgsGeometry QgsOgcUtils::geometryFromGMLMultiLineString( const QDomElement &geometryElement )
597{
598 //geoserver has
599 //<gml:MultiLineString>
600 //<gml:lineStringMember>
601 //<gml:LineString>
602
603 //mapserver has directly
604 //<gml:MultiLineString
605 //<gml:LineString
606
607
608 QList< QgsPolyline > lineCoordinates; //first list: lines, second list: points of one line
609 QDomElement currentLineStringElement;
610 QDomNodeList currentCoordList;
611 QDomNodeList currentPosList;
612
613 const QDomNodeList lineStringMemberList = geometryElement.elementsByTagNameNS( GML_NAMESPACE, u"lineStringMember"_s );
614 if ( !lineStringMemberList.isEmpty() ) //geoserver
615 {
616 for ( int i = 0; i < lineStringMemberList.size(); ++i )
617 {
618 const QDomNodeList lineStringNodeList = lineStringMemberList.at( i ).toElement().elementsByTagNameNS( GML_NAMESPACE, u"LineString"_s );
619 if ( lineStringNodeList.size() < 1 )
620 {
621 return QgsGeometry();
622 }
623 currentLineStringElement = lineStringNodeList.at( 0 ).toElement();
624 currentCoordList = currentLineStringElement.elementsByTagNameNS( GML_NAMESPACE, u"coordinates"_s );
625 if ( !currentCoordList.isEmpty() )
626 {
627 QgsPolyline currentPointList;
628 if ( readGMLCoordinates( currentPointList, currentCoordList.at( 0 ).toElement() ) != 0 )
629 {
630 return QgsGeometry();
631 }
632 lineCoordinates.push_back( currentPointList );
633 }
634 else
635 {
636 currentPosList = currentLineStringElement.elementsByTagNameNS( GML_NAMESPACE, u"posList"_s );
637 if ( currentPosList.size() < 1 )
638 {
639 return QgsGeometry();
640 }
641 QgsPolyline currentPointList;
642 if ( readGMLPositions( currentPointList, currentPosList.at( 0 ).toElement() ) != 0 )
643 {
644 return QgsGeometry();
645 }
646 lineCoordinates.push_back( currentPointList );
647 }
648 }
649 }
650 else
651 {
652 const QDomNodeList lineStringList = geometryElement.elementsByTagNameNS( GML_NAMESPACE, u"LineString"_s );
653 if ( !lineStringList.isEmpty() ) //mapserver
654 {
655 for ( int i = 0; i < lineStringList.size(); ++i )
656 {
657 currentLineStringElement = lineStringList.at( i ).toElement();
658 currentCoordList = currentLineStringElement.elementsByTagNameNS( GML_NAMESPACE, u"coordinates"_s );
659 if ( !currentCoordList.isEmpty() )
660 {
661 QgsPolyline currentPointList;
662 if ( readGMLCoordinates( currentPointList, currentCoordList.at( 0 ).toElement() ) != 0 )
663 {
664 return QgsGeometry();
665 }
666 lineCoordinates.push_back( currentPointList );
667 return QgsGeometry();
668 }
669 else
670 {
671 currentPosList = currentLineStringElement.elementsByTagNameNS( GML_NAMESPACE, u"posList"_s );
672 if ( currentPosList.size() < 1 )
673 {
674 return QgsGeometry();
675 }
676 QgsPolyline currentPointList;
677 if ( readGMLPositions( currentPointList, currentPosList.at( 0 ).toElement() ) != 0 )
678 {
679 return QgsGeometry();
680 }
681 lineCoordinates.push_back( currentPointList );
682 }
683 }
684 }
685 else
686 {
687 return QgsGeometry();
688 }
689 }
690
691 int nLines = lineCoordinates.size();
692 if ( nLines < 1 )
693 return QgsGeometry();
694
695 const bool hasZ { !std::isnan( lineCoordinates.first().first().z() ) };
696 const int coordSize { hasZ ? 3 : 2 };
697
698 //calculate the required wkb size
699 int size = static_cast<int>( lineCoordinates.size() + 1 ) * ( 1 + 2 * sizeof( int ) );
700 for ( QList< QgsPolyline >::const_iterator it = lineCoordinates.constBegin(); it != lineCoordinates.constEnd(); ++it )
701 {
702 size += it->size() * coordSize * sizeof( double );
703 }
704
706 unsigned char *wkb = new unsigned char[size];
707
708 //fill the wkb content
709 char e = static_cast<char>( htonl( 1 ) != 1 );
710 int wkbPosition = 0; //current offset from wkb beginning (in bytes)
711 int nPoints; //number of points in a line
712 double x, y, z;
713 memcpy( &( wkb )[wkbPosition], &e, 1 );
714 wkbPosition += 1;
715 memcpy( &( wkb )[wkbPosition], &type, sizeof( int ) );
716 wkbPosition += sizeof( int );
717 memcpy( &( wkb )[wkbPosition], &nLines, sizeof( int ) );
718 wkbPosition += sizeof( int );
720 for ( QList< QgsPolyline >::const_iterator it = lineCoordinates.constBegin(); it != lineCoordinates.constEnd(); ++it )
721 {
722 memcpy( &( wkb )[wkbPosition], &e, 1 );
723 wkbPosition += 1;
724 memcpy( &( wkb )[wkbPosition], &lineType, sizeof( int ) );
725 wkbPosition += sizeof( int );
726 nPoints = it->size();
727 memcpy( &( wkb )[wkbPosition], &nPoints, sizeof( int ) );
728 wkbPosition += sizeof( int );
729 for ( QgsPolyline::const_iterator iter = it->begin(); iter != it->end(); ++iter )
730 {
731 x = iter->x();
732 y = iter->y();
733 // QgsDebugMsgLevel( u"x, y is %1,%2"_s.arg( x, 'f' ).arg( y, 'f' ), 2 );
734 memcpy( &( wkb )[wkbPosition], &x, sizeof( double ) );
735 wkbPosition += sizeof( double );
736 memcpy( &( wkb )[wkbPosition], &y, sizeof( double ) );
737 wkbPosition += sizeof( double );
738
739 if ( hasZ )
740 {
741 z = iter->z();
742 memcpy( &( wkb )[wkbPosition], &z, sizeof( double ) );
743 wkbPosition += sizeof( double );
744 }
745 }
746 }
747
748 QgsGeometry g;
749 g.fromWkb( wkb, size );
750 return g;
751}
752
753QgsGeometry QgsOgcUtils::geometryFromGMLMultiPolygon( const QDomElement &geometryElement )
754{
755 //first list: different polygons, second list: different rings, third list: different points
756 QVector<QgsMultiPolyline> multiPolygonPoints;
757 QDomElement currentPolygonMemberElement;
758 QDomNodeList polygonList;
759 QDomElement currentPolygonElement;
760 // rings in GML2
761 QDomNodeList outerBoundaryList;
762 QDomElement currentOuterBoundaryElement;
763 QDomElement currentInnerBoundaryElement;
764 // rings in GML3
765 QDomNodeList exteriorList;
766 QDomElement currentExteriorElement;
767 QDomElement currentInteriorElement;
768 // linear ring
769 QDomNodeList linearRingNodeList;
770 QDomElement currentLinearRingElement;
771 // Coordinates or position list
772 QDomNodeList currentCoordinateList;
773 QDomNodeList currentPosList;
774
775 const QDomNodeList polygonMemberList = geometryElement.elementsByTagNameNS( GML_NAMESPACE, u"polygonMember"_s );
776 QgsMultiPolyline currentPolygonList;
777 for ( int i = 0; i < polygonMemberList.size(); ++i )
778 {
779 currentPolygonList.resize( 0 ); // preserve capacity - don't use clear
780 currentPolygonMemberElement = polygonMemberList.at( i ).toElement();
781 polygonList = currentPolygonMemberElement.elementsByTagNameNS( GML_NAMESPACE, u"Polygon"_s );
782 if ( polygonList.size() < 1 )
783 {
784 continue;
785 }
786 currentPolygonElement = polygonList.at( 0 ).toElement();
787
788 //find exterior ring
789 outerBoundaryList = currentPolygonElement.elementsByTagNameNS( GML_NAMESPACE, u"outerBoundaryIs"_s );
790 if ( !outerBoundaryList.isEmpty() )
791 {
792 currentOuterBoundaryElement = outerBoundaryList.at( 0 ).toElement();
793 QgsPolyline ringCoordinates;
794
795 linearRingNodeList = currentOuterBoundaryElement.elementsByTagNameNS( GML_NAMESPACE, u"LinearRing"_s );
796 if ( linearRingNodeList.size() < 1 )
797 {
798 continue;
799 }
800 currentLinearRingElement = linearRingNodeList.at( 0 ).toElement();
801 currentCoordinateList = currentLinearRingElement.elementsByTagNameNS( GML_NAMESPACE, u"coordinates"_s );
802 if ( currentCoordinateList.size() < 1 )
803 {
804 continue;
805 }
806 if ( readGMLCoordinates( ringCoordinates, currentCoordinateList.at( 0 ).toElement() ) != 0 )
807 {
808 continue;
809 }
810 currentPolygonList.push_back( ringCoordinates );
811
812 //find interior rings
813 const QDomNodeList innerBoundaryList = currentPolygonElement.elementsByTagNameNS( GML_NAMESPACE, u"innerBoundaryIs"_s );
814 for ( int j = 0; j < innerBoundaryList.size(); ++j )
815 {
816 QgsPolyline ringCoordinates;
817 currentInnerBoundaryElement = innerBoundaryList.at( j ).toElement();
818 linearRingNodeList = currentInnerBoundaryElement.elementsByTagNameNS( GML_NAMESPACE, u"LinearRing"_s );
819 if ( linearRingNodeList.size() < 1 )
820 {
821 continue;
822 }
823 currentLinearRingElement = linearRingNodeList.at( 0 ).toElement();
824 currentCoordinateList = currentLinearRingElement.elementsByTagNameNS( GML_NAMESPACE, u"coordinates"_s );
825 if ( currentCoordinateList.size() < 1 )
826 {
827 continue;
828 }
829 if ( readGMLCoordinates( ringCoordinates, currentCoordinateList.at( 0 ).toElement() ) != 0 )
830 {
831 continue;
832 }
833 currentPolygonList.push_back( ringCoordinates );
834 }
835 }
836 else
837 {
838 //find exterior ring
839 exteriorList = currentPolygonElement.elementsByTagNameNS( GML_NAMESPACE, u"exterior"_s );
840 if ( exteriorList.size() < 1 )
841 {
842 continue;
843 }
844
845 currentExteriorElement = exteriorList.at( 0 ).toElement();
846 QgsPolyline ringPositions;
847
848 linearRingNodeList = currentExteriorElement.elementsByTagNameNS( GML_NAMESPACE, u"LinearRing"_s );
849 if ( linearRingNodeList.size() < 1 )
850 {
851 continue;
852 }
853 currentLinearRingElement = linearRingNodeList.at( 0 ).toElement();
854 currentPosList = currentLinearRingElement.elementsByTagNameNS( GML_NAMESPACE, u"posList"_s );
855 if ( currentPosList.size() < 1 )
856 {
857 continue;
858 }
859 if ( readGMLPositions( ringPositions, currentPosList.at( 0 ).toElement() ) != 0 )
860 {
861 continue;
862 }
863 currentPolygonList.push_back( ringPositions );
864
865 //find interior rings
866 const QDomNodeList interiorList = currentPolygonElement.elementsByTagNameNS( GML_NAMESPACE, u"interior"_s );
867 for ( int j = 0; j < interiorList.size(); ++j )
868 {
869 QgsPolyline ringPositions;
870 currentInteriorElement = interiorList.at( j ).toElement();
871 linearRingNodeList = currentInteriorElement.elementsByTagNameNS( GML_NAMESPACE, u"LinearRing"_s );
872 if ( linearRingNodeList.size() < 1 )
873 {
874 continue;
875 }
876 currentLinearRingElement = linearRingNodeList.at( 0 ).toElement();
877 currentPosList = currentLinearRingElement.elementsByTagNameNS( GML_NAMESPACE, u"posList"_s );
878 if ( currentPosList.size() < 1 )
879 {
880 continue;
881 }
882 if ( readGMLPositions( ringPositions, currentPosList.at( 0 ).toElement() ) != 0 )
883 {
884 continue;
885 }
886 currentPolygonList.push_back( ringPositions );
887 }
888 }
889 multiPolygonPoints.push_back( currentPolygonList );
890 }
891
892 int nPolygons = multiPolygonPoints.size();
893 if ( nPolygons < 1 )
894 return QgsGeometry();
895
896 const bool hasZ { !std::isnan( multiPolygonPoints.first().first().first().z() ) };
897
898 int size = 1 + 2 * sizeof( int );
899 //calculate the wkb size
900
901 for ( auto it = multiPolygonPoints.constBegin(); it != multiPolygonPoints.constEnd(); ++it )
902 {
903 size += 1 + 2 * sizeof( int );
904 for ( auto iter = it->begin(); iter != it->end(); ++iter )
905 {
906 size += static_cast<int>( sizeof( int ) ) + ( hasZ ? 3 : 2 ) * static_cast<int>( iter->size() * sizeof( double ) );
907 }
908 }
909
911 unsigned char *wkb = new unsigned char[size];
912
913 char e = static_cast<char>( htonl( 1 ) != 1 );
914 int wkbPosition = 0; //current offset from wkb beginning (in bytes)
915 double x, y;
916 int nRings;
917 int nPointsInRing;
918
919 //fill the contents into *wkb
920 memcpy( &( wkb )[wkbPosition], &e, 1 );
921 wkbPosition += 1;
922 memcpy( &( wkb )[wkbPosition], &type, sizeof( int ) );
923 wkbPosition += sizeof( int );
924 memcpy( &( wkb )[wkbPosition], &nPolygons, sizeof( int ) );
925 wkbPosition += sizeof( int );
926
928
929 for ( auto it = multiPolygonPoints.constBegin(); it != multiPolygonPoints.constEnd(); ++it )
930 {
931 memcpy( &( wkb )[wkbPosition], &e, 1 );
932 wkbPosition += 1;
933 memcpy( &( wkb )[wkbPosition], &type, sizeof( int ) );
934 wkbPosition += sizeof( int );
935 nRings = it->size();
936 memcpy( &( wkb )[wkbPosition], &nRings, sizeof( int ) );
937 wkbPosition += sizeof( int );
938 for ( auto iter = it->begin(); iter != it->end(); ++iter )
939 {
940 nPointsInRing = iter->size();
941 memcpy( &( wkb )[wkbPosition], &nPointsInRing, sizeof( int ) );
942 wkbPosition += sizeof( int );
943 for ( auto iterator = iter->begin(); iterator != iter->end(); ++iterator )
944 {
945 x = iterator->x();
946 y = iterator->y();
947 memcpy( &( wkb )[wkbPosition], &x, sizeof( double ) );
948 wkbPosition += sizeof( double );
949 memcpy( &( wkb )[wkbPosition], &y, sizeof( double ) );
950 wkbPosition += sizeof( double );
951 if ( hasZ )
952 {
953 double z = iterator->z();
954 memcpy( &( wkb )[wkbPosition], &z, sizeof( double ) );
955 wkbPosition += sizeof( double );
956 }
957 }
958 }
959 }
960
961 QgsGeometry g;
962 g.fromWkb( wkb, size );
963 return g;
964}
965
966QDomElement QgsOgcUtils::filterElement( QDomDocument &doc, GMLVersion gmlVersion, FilterVersion filterVersion, bool GMLUsed )
967{
968 QDomElement filterElem =
969 ( filterVersion == FILTER_FES_2_0 ) ?
970 doc.createElementNS( FES_NAMESPACE, u"fes:Filter"_s ) :
971 doc.createElementNS( OGC_NAMESPACE, u"ogc:Filter"_s );
972
973 if ( GMLUsed )
974 {
975 QDomAttr attr = doc.createAttribute( u"xmlns:gml"_s );
976 if ( gmlVersion == GML_3_2_1 )
977 attr.setValue( GML32_NAMESPACE );
978 else
979 attr.setValue( GML_NAMESPACE );
980 filterElem.setAttributeNode( attr );
981 }
982 return filterElem;
983}
984
985
986bool QgsOgcUtils::readGMLCoordinates( QgsPolyline &coords, const QDomElement &elem )
987{
988 QString coordSeparator = u","_s;
989 QString tupleSeparator = u" "_s;
990 //"decimal" has to be "."
991
992 coords.clear();
993
994 if ( elem.hasAttribute( u"cs"_s ) )
995 {
996 coordSeparator = elem.attribute( u"cs"_s );
997 }
998 if ( elem.hasAttribute( u"ts"_s ) )
999 {
1000 tupleSeparator = elem.attribute( u"ts"_s );
1001 }
1002
1003 const QStringList tupels = elem.text().split( tupleSeparator, Qt::SkipEmptyParts );
1004 QStringList tuple_coords;
1005 double x, y, z;
1006 bool conversionSuccess;
1007
1008 QStringList::const_iterator it;
1009 for ( it = tupels.constBegin(); it != tupels.constEnd(); ++it )
1010 {
1011 tuple_coords = ( *it ).split( coordSeparator, Qt::SkipEmptyParts );
1012 if ( tuple_coords.size() < 2 )
1013 {
1014 continue;
1015 }
1016 x = tuple_coords.at( 0 ).toDouble( &conversionSuccess );
1017 if ( !conversionSuccess )
1018 {
1019 return true;
1020 }
1021 y = tuple_coords.at( 1 ).toDouble( &conversionSuccess );
1022 if ( !conversionSuccess )
1023 {
1024 return true;
1025 }
1026 if ( tuple_coords.size() > 2 )
1027 {
1028 z = tuple_coords.at( 2 ).toDouble( &conversionSuccess );
1029 if ( !conversionSuccess )
1030 {
1031 return true;
1032 }
1033 }
1034 else
1035 {
1036 z = std::numeric_limits<double>::quiet_NaN();
1037 }
1038 coords.append( QgsPoint( x, y, z ) );
1039 }
1040 return false;
1041}
1042
1044{
1045 QgsRectangle rect;
1046
1047 const QDomElement boxElem = boxNode.toElement();
1048 if ( boxElem.tagName() != "Box"_L1 )
1049 return rect;
1050
1051 const QDomElement bElem = boxElem.firstChild().toElement();
1052 QString coordSeparator = u","_s;
1053 QString tupleSeparator = u" "_s;
1054 if ( bElem.hasAttribute( u"cs"_s ) )
1055 {
1056 coordSeparator = bElem.attribute( u"cs"_s );
1057 }
1058 if ( bElem.hasAttribute( u"ts"_s ) )
1059 {
1060 tupleSeparator = bElem.attribute( u"ts"_s );
1061 }
1062
1063 const QString bString = bElem.text();
1064 bool ok1, ok2, ok3, ok4;
1065 const double xmin = bString.section( tupleSeparator, 0, 0 ).section( coordSeparator, 0, 0 ).toDouble( &ok1 );
1066 const double ymin = bString.section( tupleSeparator, 0, 0 ).section( coordSeparator, 1, 1 ).toDouble( &ok2 );
1067 const double xmax = bString.section( tupleSeparator, 1, 1 ).section( coordSeparator, 0, 0 ).toDouble( &ok3 );
1068 const double ymax = bString.section( tupleSeparator, 1, 1 ).section( coordSeparator, 1, 1 ).toDouble( &ok4 );
1069
1070 if ( ok1 && ok2 && ok3 && ok4 )
1071 {
1072 rect = QgsRectangle( xmin, ymin, xmax, ymax );
1073 rect.normalize();
1074 }
1075
1076 return rect;
1077}
1078
1079bool QgsOgcUtils::readGMLPositions( QgsPolyline &coords, const QDomElement &elem )
1080{
1081 coords.clear();
1082
1083 const QStringList pos = elem.text().split( ' ', Qt::SkipEmptyParts );
1084 double x, y, z;
1085 bool conversionSuccess;
1086 const int posSize = pos.size();
1087
1088 int srsDimension = 2;
1089 if ( elem.hasAttribute( u"srsDimension"_s ) )
1090 {
1091 srsDimension = elem.attribute( u"srsDimension"_s ).toInt( &conversionSuccess );
1092 if ( !conversionSuccess )
1093 {
1094 srsDimension = 2;
1095 }
1096 }
1097 else if ( elem.hasAttribute( u"dimension"_s ) )
1098 {
1099 srsDimension = elem.attribute( u"dimension"_s ).toInt( &conversionSuccess );
1100 if ( !conversionSuccess )
1101 {
1102 srsDimension = 2;
1103 }
1104 }
1105
1106 for ( int i = 0; i < posSize / srsDimension; i++ )
1107 {
1108 x = pos.at( i * srsDimension ).toDouble( &conversionSuccess );
1109 if ( !conversionSuccess )
1110 {
1111 return true;
1112 }
1113 y = pos.at( i * srsDimension + 1 ).toDouble( &conversionSuccess );
1114 if ( !conversionSuccess )
1115 {
1116 return true;
1117 }
1118 if ( srsDimension > 2 )
1119 {
1120 z = pos.at( i * srsDimension + 2 ).toDouble( &conversionSuccess );
1121 if ( !conversionSuccess )
1122 {
1123 return true;
1124 }
1125 }
1126 else
1127 {
1128 z = std::numeric_limits<double>::quiet_NaN();
1129 }
1130 coords.append( QgsPoint( x, y, z ) );
1131 }
1132 return false;
1133}
1134
1135
1137{
1138 QgsRectangle rect;
1139
1140 const QDomElement envelopeElem = envelopeNode.toElement();
1141 if ( envelopeElem.tagName() != "Envelope"_L1 )
1142 return rect;
1143
1144 const QDomNodeList lowerCornerList = envelopeElem.elementsByTagNameNS( GML_NAMESPACE, u"lowerCorner"_s );
1145 if ( lowerCornerList.size() < 1 )
1146 return rect;
1147
1148 const QDomNodeList upperCornerList = envelopeElem.elementsByTagNameNS( GML_NAMESPACE, u"upperCorner"_s );
1149 if ( upperCornerList.size() < 1 )
1150 return rect;
1151
1152 bool conversionSuccess;
1153 int srsDimension = 2;
1154
1155 QDomElement elem = lowerCornerList.at( 0 ).toElement();
1156 if ( elem.hasAttribute( u"srsDimension"_s ) )
1157 {
1158 srsDimension = elem.attribute( u"srsDimension"_s ).toInt( &conversionSuccess );
1159 if ( !conversionSuccess )
1160 {
1161 srsDimension = 2;
1162 }
1163 }
1164 else if ( elem.hasAttribute( u"dimension"_s ) )
1165 {
1166 srsDimension = elem.attribute( u"dimension"_s ).toInt( &conversionSuccess );
1167 if ( !conversionSuccess )
1168 {
1169 srsDimension = 2;
1170 }
1171 }
1172 QString bString = elem.text();
1173
1174 const double xmin = bString.section( ' ', 0, 0 ).toDouble( &conversionSuccess );
1175 if ( !conversionSuccess )
1176 return rect;
1177 const double ymin = bString.section( ' ', 1, 1 ).toDouble( &conversionSuccess );
1178 if ( !conversionSuccess )
1179 return rect;
1180
1181 elem = upperCornerList.at( 0 ).toElement();
1182 if ( elem.hasAttribute( u"srsDimension"_s ) )
1183 {
1184 srsDimension = elem.attribute( u"srsDimension"_s ).toInt( &conversionSuccess );
1185 if ( !conversionSuccess )
1186 {
1187 srsDimension = 2;
1188 }
1189 }
1190 else if ( elem.hasAttribute( u"dimension"_s ) )
1191 {
1192 srsDimension = elem.attribute( u"dimension"_s ).toInt( &conversionSuccess );
1193 if ( !conversionSuccess )
1194 {
1195 srsDimension = 2;
1196 }
1197 }
1198
1199 Q_UNUSED( srsDimension )
1200
1201 bString = elem.text();
1202 const double xmax = bString.section( ' ', 0, 0 ).toDouble( &conversionSuccess );
1203 if ( !conversionSuccess )
1204 return rect;
1205 const double ymax = bString.section( ' ', 1, 1 ).toDouble( &conversionSuccess );
1206 if ( !conversionSuccess )
1207 return rect;
1208
1209 rect = QgsRectangle( xmin, ymin, xmax, ymax );
1210 rect.normalize();
1211
1212 return rect;
1213}
1214
1215QDomElement QgsOgcUtils::rectangleToGMLBox( const QgsRectangle *box, QDomDocument &doc, int precision )
1216{
1217 return rectangleToGMLBox( box, doc, QString(), false, precision );
1218}
1219
1220QDomElement QgsOgcUtils::rectangleToGMLBox( const QgsRectangle *box, QDomDocument &doc,
1221 const QString &srsName,
1222 bool invertAxisOrientation,
1223 int precision )
1224{
1225 if ( !box )
1226 {
1227 return QDomElement();
1228 }
1229
1230 QDomElement boxElem = doc.createElement( u"gml:Box"_s );
1231 if ( !srsName.isEmpty() )
1232 {
1233 boxElem.setAttribute( u"srsName"_s, srsName );
1234 }
1235 QDomElement coordElem = doc.createElement( u"gml:coordinates"_s );
1236 coordElem.setAttribute( u"cs"_s, u","_s );
1237 coordElem.setAttribute( u"ts"_s, u" "_s );
1238
1239 QString coordString;
1240 coordString += qgsDoubleToString( invertAxisOrientation ? box->yMinimum() : box->xMinimum(), precision );
1241 coordString += ',';
1242 coordString += qgsDoubleToString( invertAxisOrientation ? box->xMinimum() : box->yMinimum(), precision );
1243 coordString += ' ';
1244 coordString += qgsDoubleToString( invertAxisOrientation ? box->yMaximum() : box->xMaximum(), precision );
1245 coordString += ',';
1246 coordString += qgsDoubleToString( invertAxisOrientation ? box->xMaximum() : box->yMaximum(), precision );
1247
1248 const QDomText coordText = doc.createTextNode( coordString );
1249 coordElem.appendChild( coordText );
1250 boxElem.appendChild( coordElem );
1251
1252 return boxElem;
1253}
1254
1255QDomElement QgsOgcUtils::rectangleToGMLEnvelope( const QgsRectangle *env, QDomDocument &doc, int precision )
1256{
1257 return rectangleToGMLEnvelope( env, doc, QString(), false, precision );
1258}
1259
1260QDomElement QgsOgcUtils::rectangleToGMLEnvelope( const QgsRectangle *env, QDomDocument &doc,
1261 const QString &srsName,
1262 bool invertAxisOrientation,
1263 int precision )
1264{
1265 if ( !env )
1266 {
1267 return QDomElement();
1268 }
1269
1270 QDomElement envElem = doc.createElement( u"gml:Envelope"_s );
1271 if ( !srsName.isEmpty() )
1272 {
1273 envElem.setAttribute( u"srsName"_s, srsName );
1274 }
1275 QString posList;
1276
1277 QDomElement lowerCornerElem = doc.createElement( u"gml:lowerCorner"_s );
1278 posList = qgsDoubleToString( invertAxisOrientation ? env->yMinimum() : env->xMinimum(), precision );
1279 posList += ' ';
1280 posList += qgsDoubleToString( invertAxisOrientation ? env->xMinimum() : env->yMinimum(), precision );
1281 const QDomText lowerCornerText = doc.createTextNode( posList );
1282 lowerCornerElem.appendChild( lowerCornerText );
1283 envElem.appendChild( lowerCornerElem );
1284
1285 QDomElement upperCornerElem = doc.createElement( u"gml:upperCorner"_s );
1286 posList = qgsDoubleToString( invertAxisOrientation ? env->yMaximum() : env->xMaximum(), precision );
1287 posList += ' ';
1288 posList += qgsDoubleToString( invertAxisOrientation ? env->xMaximum() : env->yMaximum(), precision );
1289 const QDomText upperCornerText = doc.createTextNode( posList );
1290 upperCornerElem.appendChild( upperCornerText );
1291 envElem.appendChild( upperCornerElem );
1292
1293 return envElem;
1294}
1295
1296QDomElement QgsOgcUtils::geometryToGML( const QgsGeometry &geometry, QDomDocument &doc, const QString &format, int precision )
1297{
1298 return geometryToGML( geometry, doc, ( format == "GML2"_L1 ) ? GML_2_1_2 : GML_3_2_1, QString(), false, QString(), precision );
1299}
1300
1301QDomElement QgsOgcUtils::geometryToGML( const QgsGeometry &geometry,
1302 QDomDocument &doc,
1303 GMLVersion gmlVersion,
1304 const QString &srsName,
1305 bool invertAxisOrientation,
1306 const QString &gmlIdBase,
1307 int precision )
1308{
1309 if ( geometry.isNull() )
1310 return QDomElement();
1311
1312 // coordinate separator
1313 QString cs = u","_s;
1314 // tuple separator
1315 const QString ts = u" "_s;
1316 // coord element tagname
1317 QDomElement baseCoordElem;
1318
1319 bool hasZValue = false;
1320
1321 const QByteArray wkb( geometry.asWkb() );
1322 QgsConstWkbPtr wkbPtr( wkb );
1323 try
1324 {
1325 wkbPtr.readHeader();
1326 }
1327 catch ( const QgsWkbException &e )
1328 {
1329 Q_UNUSED( e )
1330 // WKB exception while reading header
1331 return QDomElement();
1332 }
1333
1334 if ( gmlVersion != GML_2_1_2 )
1335 {
1336 switch ( geometry.wkbType() )
1337 {
1344 baseCoordElem = doc.createElement( u"gml:pos"_s );
1345 break;
1346 default:
1347 baseCoordElem = doc.createElement( u"gml:posList"_s );
1348 break;
1349 }
1350 cs = ' ';
1351 }
1352 else
1353 {
1354 baseCoordElem = doc.createElement( u"gml:coordinates"_s );
1355 baseCoordElem.setAttribute( u"cs"_s, cs );
1356 baseCoordElem.setAttribute( u"ts"_s, ts );
1357 }
1358
1359 try
1360 {
1361 switch ( geometry.wkbType() )
1362 {
1365 hasZValue = true;
1366 //intentional fall-through
1367 [[fallthrough]];
1369 {
1370 QDomElement pointElem = doc.createElement( u"gml:Point"_s );
1371 if ( gmlVersion == GML_3_2_1 && !gmlIdBase.isEmpty() )
1372 pointElem.setAttribute( u"gml:id"_s, gmlIdBase );
1373 if ( !srsName.isEmpty() )
1374 pointElem.setAttribute( u"srsName"_s, srsName );
1375 QDomElement coordElem = baseCoordElem.cloneNode().toElement();
1376
1377 double x, y;
1378
1379 if ( invertAxisOrientation )
1380 wkbPtr >> y >> x;
1381 else
1382 wkbPtr >> x >> y;
1383
1384 QString coordString = qgsDoubleToString( x, precision ) + cs + qgsDoubleToString( y, precision );
1385
1386 // Add Z
1387 if ( hasZValue )
1388 {
1389 double z = 0;
1390 wkbPtr >> z;
1391 coordString += cs + qgsDoubleToString( z, precision );
1392 }
1393 const QDomText coordText = doc.createTextNode( coordString );
1394
1395 coordElem.appendChild( coordText );
1396 if ( gmlVersion != GML_2_1_2 )
1397 coordElem.setAttribute( u"srsDimension"_s, hasZValue ? u"3"_s : u"2"_s );
1398 pointElem.appendChild( coordElem );
1399 return pointElem;
1400 }
1403 hasZValue = true;
1404 //intentional fall-through
1405 [[fallthrough]];
1407 {
1408 QDomElement multiPointElem = doc.createElement( u"gml:MultiPoint"_s );
1409 if ( gmlVersion == GML_3_2_1 && !gmlIdBase.isEmpty() )
1410 multiPointElem.setAttribute( u"gml:id"_s, gmlIdBase );
1411 if ( !srsName.isEmpty() )
1412 multiPointElem.setAttribute( u"srsName"_s, srsName );
1413
1414 int nPoints;
1415 wkbPtr >> nPoints;
1416
1417 for ( int idx = 0; idx < nPoints; ++idx )
1418 {
1419 QDomElement pointMemberElem = doc.createElement( u"gml:pointMember"_s );
1420 QDomElement pointElem = doc.createElement( u"gml:Point"_s );
1421 if ( gmlVersion == GML_3_2_1 && !gmlIdBase.isEmpty() )
1422 pointElem.setAttribute( u"gml:id"_s, gmlIdBase + u".%1"_s.arg( idx + 1 ) );
1423 QDomElement coordElem = baseCoordElem.cloneNode().toElement();
1424
1425 wkbPtr.readHeader();
1426
1427 double x = 0;
1428 double y = 0;
1429 if ( invertAxisOrientation )
1430 wkbPtr >> y >> x;
1431 else
1432 wkbPtr >> x >> y;
1433
1434 QString coordString = qgsDoubleToString( x, precision ) + cs + qgsDoubleToString( y, precision );
1435 // Add Z
1436 if ( hasZValue )
1437 {
1438 double z = 0;
1439 wkbPtr >> z;
1440 coordString += cs + qgsDoubleToString( z, precision );
1441 }
1442
1443 const QDomText coordText = doc.createTextNode( coordString );
1444
1445 coordElem.appendChild( coordText );
1446 if ( gmlVersion != GML_2_1_2 )
1447 coordElem.setAttribute( u"srsDimension"_s, hasZValue ? u"3"_s : u"2"_s );
1448 pointElem.appendChild( coordElem );
1449
1450
1451 pointMemberElem.appendChild( pointElem );
1452 multiPointElem.appendChild( pointMemberElem );
1453 }
1454 return multiPointElem;
1455 }
1458 hasZValue = true;
1459 //intentional fall-through
1460 [[fallthrough]];
1462 {
1463 QDomElement lineStringElem = doc.createElement( u"gml:LineString"_s );
1464 if ( gmlVersion == GML_3_2_1 && !gmlIdBase.isEmpty() )
1465 lineStringElem.setAttribute( u"gml:id"_s, gmlIdBase );
1466 if ( !srsName.isEmpty() )
1467 lineStringElem.setAttribute( u"srsName"_s, srsName );
1468 // get number of points in the line
1469
1470 int nPoints;
1471 wkbPtr >> nPoints;
1472
1473 QDomElement coordElem = baseCoordElem.cloneNode().toElement();
1474 QString coordString;
1475 for ( int idx = 0; idx < nPoints; ++idx )
1476 {
1477 if ( idx != 0 )
1478 {
1479 coordString += ts;
1480 }
1481
1482 double x = 0;
1483 double y = 0;
1484 if ( invertAxisOrientation )
1485 wkbPtr >> y >> x;
1486 else
1487 wkbPtr >> x >> y;
1488 coordString += qgsDoubleToString( x, precision ) + cs + qgsDoubleToString( y, precision );
1489
1490 if ( hasZValue )
1491 {
1492 double z = 0;
1493 wkbPtr >> z;
1494 coordString += cs + qgsDoubleToString( z, precision );
1495 }
1496
1497 }
1498 const QDomText coordText = doc.createTextNode( coordString );
1499 coordElem.appendChild( coordText );
1500 if ( gmlVersion != GML_2_1_2 )
1501 coordElem.setAttribute( u"srsDimension"_s, hasZValue ? u"3"_s : u"2"_s );
1502 lineStringElem.appendChild( coordElem );
1503 return lineStringElem;
1504 }
1507 hasZValue = true;
1508 //intentional fall-through
1509 [[fallthrough]];
1511 {
1512 QDomElement multiLineStringElem = doc.createElement( u"gml:MultiLineString"_s );
1513 if ( gmlVersion == GML_3_2_1 && !gmlIdBase.isEmpty() )
1514 multiLineStringElem.setAttribute( u"gml:id"_s, gmlIdBase );
1515 if ( !srsName.isEmpty() )
1516 multiLineStringElem.setAttribute( u"srsName"_s, srsName );
1517
1518 int nLines;
1519 wkbPtr >> nLines;
1520
1521 for ( int jdx = 0; jdx < nLines; jdx++ )
1522 {
1523 QDomElement lineStringMemberElem = doc.createElement( u"gml:lineStringMember"_s );
1524 QDomElement lineStringElem = doc.createElement( u"gml:LineString"_s );
1525 if ( gmlVersion == GML_3_2_1 && !gmlIdBase.isEmpty() )
1526 lineStringElem.setAttribute( u"gml:id"_s, gmlIdBase + u".%1"_s.arg( jdx + 1 ) );
1527
1528 wkbPtr.readHeader();
1529
1530 int nPoints;
1531 wkbPtr >> nPoints;
1532
1533 QDomElement coordElem = baseCoordElem.cloneNode().toElement();
1534 QString coordString;
1535 for ( int idx = 0; idx < nPoints; idx++ )
1536 {
1537 if ( idx != 0 )
1538 {
1539 coordString += ts;
1540 }
1541
1542 double x = 0;
1543 double y = 0;
1544 if ( invertAxisOrientation )
1545 wkbPtr >> y >> x;
1546 else
1547 wkbPtr >> x >> y;
1548
1549 coordString += qgsDoubleToString( x, precision ) + cs + qgsDoubleToString( y, precision );
1550
1551 if ( hasZValue )
1552 {
1553 double z = 0;
1554 wkbPtr >> z;
1555 coordString += cs + qgsDoubleToString( z, precision );
1556 }
1557
1558 }
1559 const QDomText coordText = doc.createTextNode( coordString );
1560 coordElem.appendChild( coordText );
1561 if ( gmlVersion != GML_2_1_2 )
1562 coordElem.setAttribute( u"srsDimension"_s, hasZValue ? u"3"_s : u"2"_s );
1563 lineStringElem.appendChild( coordElem );
1564 lineStringMemberElem.appendChild( lineStringElem );
1565 multiLineStringElem.appendChild( lineStringMemberElem );
1566 }
1567 return multiLineStringElem;
1568 }
1571 hasZValue = true;
1572 //intentional fall-through
1573 [[fallthrough]];
1575 {
1576 QDomElement polygonElem = doc.createElement( u"gml:Polygon"_s );
1577 if ( gmlVersion == GML_3_2_1 && !gmlIdBase.isEmpty() )
1578 polygonElem.setAttribute( u"gml:id"_s, gmlIdBase );
1579 if ( !srsName.isEmpty() )
1580 polygonElem.setAttribute( u"srsName"_s, srsName );
1581
1582 // get number of rings in the polygon
1583 int numRings;
1584 wkbPtr >> numRings;
1585
1586 if ( numRings == 0 ) // sanity check for zero rings in polygon
1587 return QDomElement();
1588
1589 for ( int idx = 0; idx < numRings; idx++ )
1590 {
1591 QString boundaryName = ( gmlVersion == GML_2_1_2 ) ? "gml:outerBoundaryIs" : "gml:exterior";
1592 if ( idx != 0 )
1593 {
1594 boundaryName = ( gmlVersion == GML_2_1_2 ) ? "gml:innerBoundaryIs" : "gml:interior";
1595 }
1596 QDomElement boundaryElem = doc.createElement( boundaryName );
1597 QDomElement ringElem = doc.createElement( u"gml:LinearRing"_s );
1598 // get number of points in the ring
1599 int nPoints = 0;
1600 wkbPtr >> nPoints;
1601
1602 QDomElement coordElem = baseCoordElem.cloneNode().toElement();
1603 QString coordString;
1604 for ( int jdx = 0; jdx < nPoints; jdx++ )
1605 {
1606 if ( jdx != 0 )
1607 {
1608 coordString += ts;
1609 }
1610
1611 double x = 0;
1612 double y = 0;
1613 if ( invertAxisOrientation )
1614 wkbPtr >> y >> x;
1615 else
1616 wkbPtr >> x >> y;
1617
1618 coordString += qgsDoubleToString( x, precision ) + cs + qgsDoubleToString( y, precision );
1619
1620 if ( hasZValue )
1621 {
1622 double z = 0;
1623 wkbPtr >> z;
1624 coordString += cs + qgsDoubleToString( z, precision );
1625 }
1626 }
1627 const QDomText coordText = doc.createTextNode( coordString );
1628 coordElem.appendChild( coordText );
1629 if ( gmlVersion != GML_2_1_2 )
1630 coordElem.setAttribute( u"srsDimension"_s, hasZValue ? u"3"_s : u"2"_s );
1631 ringElem.appendChild( coordElem );
1632 boundaryElem.appendChild( ringElem );
1633 polygonElem.appendChild( boundaryElem );
1634 }
1635
1636 return polygonElem;
1637 }
1640 hasZValue = true;
1641 //intentional fall-through
1642 [[fallthrough]];
1644 {
1645 QDomElement multiPolygonElem = doc.createElement( u"gml:MultiPolygon"_s );
1646 if ( gmlVersion == GML_3_2_1 && !gmlIdBase.isEmpty() )
1647 multiPolygonElem.setAttribute( u"gml:id"_s, gmlIdBase );
1648 if ( !srsName.isEmpty() )
1649 multiPolygonElem.setAttribute( u"srsName"_s, srsName );
1650
1651 int numPolygons;
1652 wkbPtr >> numPolygons;
1653
1654 for ( int kdx = 0; kdx < numPolygons; kdx++ )
1655 {
1656 QDomElement polygonMemberElem = doc.createElement( u"gml:polygonMember"_s );
1657 QDomElement polygonElem = doc.createElement( u"gml:Polygon"_s );
1658 if ( gmlVersion == GML_3_2_1 && !gmlIdBase.isEmpty() )
1659 polygonElem.setAttribute( u"gml:id"_s, gmlIdBase + u".%1"_s.arg( kdx + 1 ) );
1660
1661 wkbPtr.readHeader();
1662
1663 int numRings;
1664 wkbPtr >> numRings;
1665
1666 for ( int idx = 0; idx < numRings; idx++ )
1667 {
1668 QString boundaryName = ( gmlVersion == GML_2_1_2 ) ? "gml:outerBoundaryIs" : "gml:exterior";
1669 if ( idx != 0 )
1670 {
1671 boundaryName = ( gmlVersion == GML_2_1_2 ) ? "gml:innerBoundaryIs" : "gml:interior";
1672 }
1673 QDomElement boundaryElem = doc.createElement( boundaryName );
1674 QDomElement ringElem = doc.createElement( u"gml:LinearRing"_s );
1675
1676 int nPoints;
1677 wkbPtr >> nPoints;
1678
1679 QDomElement coordElem = baseCoordElem.cloneNode().toElement();
1680 QString coordString;
1681 for ( int jdx = 0; jdx < nPoints; jdx++ )
1682 {
1683 if ( jdx != 0 )
1684 {
1685 coordString += ts;
1686 }
1687
1688 double x = 0;
1689 double y = 0;
1690 if ( invertAxisOrientation )
1691 wkbPtr >> y >> x;
1692 else
1693 wkbPtr >> x >> y;
1694
1695 coordString += qgsDoubleToString( x, precision ) + cs + qgsDoubleToString( y, precision );
1696
1697 if ( hasZValue )
1698 {
1699 double z = 0;
1700 wkbPtr >> z;
1701 coordString += cs + qgsDoubleToString( z, precision );
1702 }
1703
1704 }
1705 const QDomText coordText = doc.createTextNode( coordString );
1706 coordElem.appendChild( coordText );
1707 if ( gmlVersion != GML_2_1_2 )
1708 coordElem.setAttribute( u"srsDimension"_s, hasZValue ? u"3"_s : u"2"_s );
1709 ringElem.appendChild( coordElem );
1710 boundaryElem.appendChild( ringElem );
1711 polygonElem.appendChild( boundaryElem );
1712 polygonMemberElem.appendChild( polygonElem );
1713 multiPolygonElem.appendChild( polygonMemberElem );
1714 }
1715 }
1716 return multiPolygonElem;
1717 }
1718 default:
1719 return QDomElement();
1720 }
1721 }
1722 catch ( const QgsWkbException &e )
1723 {
1724 Q_UNUSED( e )
1725 return QDomElement();
1726 }
1727}
1728
1729QDomElement QgsOgcUtils::geometryToGML( const QgsGeometry &geometry, QDomDocument &doc, int precision )
1730{
1731 return geometryToGML( geometry, doc, u"GML2"_s, precision );
1732}
1733
1734QDomElement QgsOgcUtils::createGMLCoordinates( const QgsPolylineXY &points, QDomDocument &doc )
1735{
1736 QDomElement coordElem = doc.createElement( u"gml:coordinates"_s );
1737 coordElem.setAttribute( u"cs"_s, u","_s );
1738 coordElem.setAttribute( u"ts"_s, u" "_s );
1739
1740 QString coordString;
1741 QVector<QgsPointXY>::const_iterator pointIt = points.constBegin();
1742 for ( ; pointIt != points.constEnd(); ++pointIt )
1743 {
1744 if ( pointIt != points.constBegin() )
1745 {
1746 coordString += ' ';
1747 }
1748 coordString += qgsDoubleToString( pointIt->x() );
1749 coordString += ',';
1750 coordString += qgsDoubleToString( pointIt->y() );
1751 }
1752
1753 const QDomText coordText = doc.createTextNode( coordString );
1754 coordElem.appendChild( coordText );
1755 return coordElem;
1756}
1757
1758QDomElement QgsOgcUtils::createGMLPositions( const QgsPolylineXY &points, QDomDocument &doc )
1759{
1760 QDomElement posElem = doc.createElement( u"gml:pos"_s );
1761 if ( points.size() > 1 )
1762 posElem = doc.createElement( u"gml:posList"_s );
1763 posElem.setAttribute( u"srsDimension"_s, u"2"_s );
1764
1765 QString coordString;
1766 QVector<QgsPointXY>::const_iterator pointIt = points.constBegin();
1767 for ( ; pointIt != points.constEnd(); ++pointIt )
1768 {
1769 if ( pointIt != points.constBegin() )
1770 {
1771 coordString += ' ';
1772 }
1773 coordString += qgsDoubleToString( pointIt->x() );
1774 coordString += ' ';
1775 coordString += qgsDoubleToString( pointIt->y() );
1776 }
1777
1778 const QDomText coordText = doc.createTextNode( coordString );
1779 posElem.appendChild( coordText );
1780 return posElem;
1781}
1782
1783// -----------------------------------------
1784
1785QColor QgsOgcUtils::colorFromOgcFill( const QDomElement &fillElement )
1786{
1787 if ( fillElement.isNull() || !fillElement.hasChildNodes() )
1788 {
1789 return QColor();
1790 }
1791
1792 QString cssName;
1793 QString elemText;
1794 QColor color;
1795 QDomElement cssElem = fillElement.firstChildElement( u"CssParameter"_s );
1796 while ( !cssElem.isNull() )
1797 {
1798 cssName = cssElem.attribute( u"name"_s, u"not_found"_s );
1799 if ( cssName != "not_found"_L1 )
1800 {
1801 elemText = cssElem.text();
1802 if ( cssName == "fill"_L1 )
1803 {
1804 color.setNamedColor( elemText );
1805 }
1806 else if ( cssName == "fill-opacity"_L1 )
1807 {
1808 bool ok;
1809 const double opacity = elemText.toDouble( &ok );
1810 if ( ok )
1811 {
1812 color.setAlphaF( opacity );
1813 }
1814 }
1815 }
1816
1817 cssElem = cssElem.nextSiblingElement( u"CssParameter"_s );
1818 }
1819
1820 return color;
1821}
1822
1823
1825{
1826 return expressionFromOgcFilter( element, QgsOgcUtils::FILTER_OGC_1_0, layer );
1827}
1828
1829QgsExpression *QgsOgcUtils::expressionFromOgcFilter( const QDomElement &element, const FilterVersion version, QgsVectorLayer *layer )
1830{
1831 if ( element.isNull() || !element.hasChildNodes() )
1832 return nullptr;
1833
1834 QgsExpression *expr = new QgsExpression();
1835
1836 // check if it is a single string value not having DOM elements
1837 // that express OGC operators
1838 if ( element.firstChild().nodeType() == QDomNode::TextNode )
1839 {
1840 expr->setExpression( element.firstChild().nodeValue() );
1841 return expr;
1842 }
1843
1844 QgsOgcUtilsExpressionFromFilter utils( version, layer );
1845
1846 // then check OGC DOM elements that contain OGC tags specifying
1847 // OGC operators.
1848 QDomElement childElem = element.firstChildElement();
1849 while ( !childElem.isNull() )
1850 {
1851 QgsExpressionNode *node = utils.nodeFromOgcFilter( childElem );
1852
1853 if ( !node )
1854 {
1855 // invalid expression, parser error
1856 expr->d->mParserErrorString = utils.errorMessage();
1857 return expr;
1858 }
1859
1860 // use the concat binary operator to append to the root node
1861 if ( !expr->d->mRootNode )
1862 {
1863 expr->d->mRootNode.reset( node );
1864 }
1865 else
1866 {
1867 expr->d->mRootNode = std::make_unique<QgsExpressionNodeBinaryOperator>( QgsExpressionNodeBinaryOperator::boConcat, expr->d->mRootNode.release(), node );
1868 }
1869
1870 childElem = childElem.nextSiblingElement();
1871 }
1872
1873 // update expression string
1874 expr->d->mExp = expr->dump();
1875
1876 return expr;
1877}
1878
1879typedef QMap<QString, int> IntMap;
1880Q_GLOBAL_STATIC_WITH_ARGS( IntMap, BINARY_OPERATORS_TAG_NAMES_MAP, (
1881{
1882 // logical
1885 // comparison
1886 { "PropertyIsEqualTo"_L1, QgsExpressionNodeBinaryOperator::boEQ },
1887 { "PropertyIsNotEqualTo"_L1, QgsExpressionNodeBinaryOperator::boNE },
1888 { "PropertyIsLessThanOrEqualTo"_L1, QgsExpressionNodeBinaryOperator::boLE },
1889 { "PropertyIsGreaterThanOrEqualTo"_L1, QgsExpressionNodeBinaryOperator::boGE },
1890 { "PropertyIsLessThan"_L1, QgsExpressionNodeBinaryOperator::boLT },
1891 { "PropertyIsGreaterThan"_L1, QgsExpressionNodeBinaryOperator::boGT },
1892 { "PropertyIsLike"_L1, QgsExpressionNodeBinaryOperator::boLike },
1893 // arithmetic
1898} ) )
1899
1900static int binaryOperatorFromTagName( const QString &tagName )
1901{
1902
1903 return BINARY_OPERATORS_TAG_NAMES_MAP()->value( tagName, -1 );
1904}
1905
1906static QString binaryOperatorToTagName( QgsExpressionNodeBinaryOperator::BinaryOperator op )
1907{
1909 {
1910 return u"PropertyIsLike"_s;
1911 }
1912 return BINARY_OPERATORS_TAG_NAMES_MAP()->key( op, QString() );
1913}
1914
1915static bool isBinaryOperator( const QString &tagName )
1916{
1917 return binaryOperatorFromTagName( tagName ) >= 0;
1918}
1919
1920
1921static bool isSpatialOperator( const QString &tagName )
1922{
1923 static QStringList spatialOps;
1924 if ( spatialOps.isEmpty() )
1925 {
1926 spatialOps << u"BBOX"_s << u"Intersects"_s << u"Contains"_s << u"Crosses"_s << u"Equals"_s
1927 << u"Disjoint"_s << u"Overlaps"_s << u"Touches"_s << u"Within"_s;
1928 }
1929
1930 return spatialOps.contains( tagName );
1931}
1932
1933QgsExpressionNode *QgsOgcUtils::nodeFromOgcFilter( QDomElement &element, QString &errorMessage, QgsVectorLayer *layer )
1934{
1935 QgsOgcUtilsExpressionFromFilter utils( QgsOgcUtils::FILTER_OGC_1_0, layer );
1936 QgsExpressionNode *node = utils.nodeFromOgcFilter( element );
1937 errorMessage = utils.errorMessage();
1938 return node;
1939}
1940
1941QgsExpressionNodeBinaryOperator *QgsOgcUtils::nodeBinaryOperatorFromOgcFilter( QDomElement &element, QString &errorMessage, QgsVectorLayer *layer )
1942{
1943 QgsOgcUtilsExpressionFromFilter utils( QgsOgcUtils::FILTER_OGC_1_0, layer );
1944 QgsExpressionNodeBinaryOperator *node = utils.nodeBinaryOperatorFromOgcFilter( element );
1945 errorMessage = utils.errorMessage();
1946 return node;
1947}
1948
1949QgsExpressionNodeFunction *QgsOgcUtils::nodeSpatialOperatorFromOgcFilter( QDomElement &element, QString &errorMessage )
1950{
1951 QgsOgcUtilsExpressionFromFilter utils( QgsOgcUtils::FILTER_OGC_1_0 );
1952 QgsExpressionNodeFunction *node = utils.nodeSpatialOperatorFromOgcFilter( element );
1953 errorMessage = utils.errorMessage();
1954 return node;
1955}
1956
1957QgsExpressionNodeUnaryOperator *QgsOgcUtils::nodeNotFromOgcFilter( QDomElement &element, QString &errorMessage )
1958{
1959 QgsOgcUtilsExpressionFromFilter utils( QgsOgcUtils::FILTER_OGC_1_0 );
1960 QgsExpressionNodeUnaryOperator *node = utils.nodeNotFromOgcFilter( element );
1961 errorMessage = utils.errorMessage();
1962 return node;
1963}
1964
1965QgsExpressionNodeFunction *QgsOgcUtils::nodeFunctionFromOgcFilter( QDomElement &element, QString &errorMessage )
1966{
1967 QgsOgcUtilsExpressionFromFilter utils( QgsOgcUtils::FILTER_OGC_1_0 );
1968 QgsExpressionNodeFunction *node = utils.nodeFunctionFromOgcFilter( element );
1969 errorMessage = utils.errorMessage();
1970 return node;
1971}
1972
1973QgsExpressionNode *QgsOgcUtils::nodeLiteralFromOgcFilter( QDomElement &element, QString &errorMessage, QgsVectorLayer *layer )
1974{
1975 QgsOgcUtilsExpressionFromFilter utils( QgsOgcUtils::FILTER_OGC_1_0, layer );
1976 QgsExpressionNode *node = utils.nodeLiteralFromOgcFilter( element );
1977 errorMessage = utils.errorMessage();
1978 return node;
1979}
1980
1981QgsExpressionNodeColumnRef *QgsOgcUtils::nodeColumnRefFromOgcFilter( QDomElement &element, QString &errorMessage )
1982{
1983 QgsOgcUtilsExpressionFromFilter utils( QgsOgcUtils::FILTER_OGC_1_0 );
1984 QgsExpressionNodeColumnRef *node = utils.nodeColumnRefFromOgcFilter( element );
1985 errorMessage = utils.errorMessage();
1986 return node;
1987}
1988
1989QgsExpressionNode *QgsOgcUtils::nodeIsBetweenFromOgcFilter( QDomElement &element, QString &errorMessage )
1990{
1991 QgsOgcUtilsExpressionFromFilter utils( QgsOgcUtils::FILTER_OGC_1_0 );
1992 QgsExpressionNode *node = utils.nodeIsBetweenFromOgcFilter( element );
1993 errorMessage = utils.errorMessage();
1994 return node;
1995}
1996
1997QgsExpressionNodeBinaryOperator *QgsOgcUtils::nodePropertyIsNullFromOgcFilter( QDomElement &element, QString &errorMessage )
1998{
1999 QgsOgcUtilsExpressionFromFilter utils( QgsOgcUtils::FILTER_OGC_1_0 );
2000 QgsExpressionNodeBinaryOperator *node = utils.nodePropertyIsNullFromOgcFilter( element );
2001 errorMessage = utils.errorMessage();
2002 return node;
2003}
2004
2005
2007
2008
2009QDomElement QgsOgcUtils::expressionToOgcFilter( const QgsExpression &exp, QDomDocument &doc, QString *errorMessage )
2010{
2011 return expressionToOgcFilter( exp, doc, GML_2_1_2, FILTER_OGC_1_0, QString(), QString(),
2012 u"geometry"_s, QString(), false, false, errorMessage );
2013}
2014
2015QDomElement QgsOgcUtils::expressionToOgcExpression( const QgsExpression &exp, QDomDocument &doc, QString *errorMessage, bool requiresFilterElement )
2016{
2018 u"geometry"_s, QString(), false, false, errorMessage, requiresFilterElement );
2019}
2020
2021QDomElement QgsOgcUtils::elseFilterExpression( QDomDocument &doc )
2022{
2023 return doc.createElementNS( SE_NAMESPACE, u"se:ElseFilter"_s );
2024}
2025
2026
2028 QDomDocument &doc,
2029 GMLVersion gmlVersion,
2030 FilterVersion filterVersion,
2031 const QString &namespacePrefix,
2032 const QString &namespaceURI,
2033 const QString &geometryName,
2034 const QString &srsName,
2035 bool honourAxisOrientation,
2036 bool invertAxisOrientation,
2037 QString *errorMessage,
2038 const QMap<QString, QString> &fieldNameToXPathMap,
2039 const QMap<QString, QString> &namespacePrefixToUriMap )
2040{
2041 if ( !expression.rootNode() )
2042 return QDomElement();
2043
2044 QgsExpression exp = expression;
2045
2046 QgsExpressionContext context;
2048 QgsOgcUtilsExprToFilter utils( doc, gmlVersion, filterVersion, namespacePrefix, namespaceURI, geometryName, srsName, honourAxisOrientation, invertAxisOrientation, fieldNameToXPathMap, namespacePrefixToUriMap );
2049 const QDomElement exprRootElem = utils.expressionNodeToOgcFilter( exp.rootNode(), &exp, &context );
2050 if ( errorMessage )
2051 *errorMessage = utils.errorMessage();
2052 if ( exprRootElem.isNull() )
2053 return QDomElement();
2054
2055 QDomElement filterElem = filterElement( doc, gmlVersion, filterVersion, utils.GMLNamespaceUsed() );
2056
2057 if ( !namespacePrefix.isEmpty() && !namespaceURI.isEmpty() )
2058 {
2059 QDomAttr attr = doc.createAttribute( u"xmlns:"_s + namespacePrefix );
2060 attr.setValue( namespaceURI );
2061 filterElem.setAttributeNode( attr );
2062 }
2063
2064 filterElem.appendChild( exprRootElem );
2065 return filterElem;
2066}
2067
2069 QDomDocument &doc,
2070 GMLVersion gmlVersion,
2071 FilterVersion filterVersion,
2072 const QString &geometryName,
2073 const QString &srsName,
2074 bool honourAxisOrientation,
2075 bool invertAxisOrientation,
2076 QString *errorMessage,
2077 bool requiresFilterElement,
2078 const QMap<QString, QString> &fieldNameToXPathMap,
2079 const QMap<QString, QString> &namespacePrefixToUriMap )
2080{
2081 QgsExpressionContext context;
2083
2084 QgsExpression exp = expression;
2085
2086 const QgsExpressionNode *node = exp.rootNode();
2087 if ( !node )
2088 return QDomElement();
2089
2090 QgsOgcUtilsExprToFilter utils( doc, gmlVersion, filterVersion, QString(), QString(), geometryName, srsName, honourAxisOrientation, invertAxisOrientation, fieldNameToXPathMap, namespacePrefixToUriMap );
2091 const QDomElement exprRootElem = utils.expressionNodeToOgcFilter( node, &exp, &context );
2092
2093 if ( errorMessage )
2094 {
2095 *errorMessage = utils.errorMessage();
2096 }
2097
2098 if ( !exprRootElem.isNull() )
2099 {
2100 if ( requiresFilterElement )
2101 {
2102 QDomElement filterElem = filterElement( doc, gmlVersion, filterVersion, utils.GMLNamespaceUsed() );
2103
2104 filterElem.appendChild( exprRootElem );
2105 return filterElem;
2106 }
2107 return exprRootElem;
2108 }
2109
2110 return QDomElement();
2111}
2112
2114 QDomDocument &doc,
2115 GMLVersion gmlVersion,
2116 FilterVersion filterVersion,
2117 const QList<LayerProperties> &layerProperties,
2118 bool honourAxisOrientation,
2119 bool invertAxisOrientation,
2120 const QMap< QString, QString> &mapUnprefixedTypenameToPrefixedTypename,
2121 QString *errorMessage,
2122 const QMap<QString, QString> &fieldNameToXPathMap,
2123 const QMap<QString, QString> &namespacePrefixToUriMap )
2124{
2125 if ( !statement.rootNode() )
2126 return QDomElement();
2127
2128 QgsOgcUtilsSQLStatementToFilter utils( doc, gmlVersion, filterVersion,
2129 layerProperties, honourAxisOrientation, invertAxisOrientation,
2130 mapUnprefixedTypenameToPrefixedTypename, fieldNameToXPathMap, namespacePrefixToUriMap );
2131 const QDomElement exprRootElem = utils.toOgcFilter( statement.rootNode() );
2132 if ( errorMessage )
2133 *errorMessage = utils.errorMessage();
2134 if ( exprRootElem.isNull() )
2135 return QDomElement();
2136
2137 QDomElement filterElem = filterElement( doc, gmlVersion, filterVersion, utils.GMLNamespaceUsed() );
2138
2139 QSet<QString> setNamespaceURI;
2140 for ( const LayerProperties &props : layerProperties )
2141 {
2142 if ( !props.mNamespacePrefix.isEmpty() && !props.mNamespaceURI.isEmpty() &&
2143 !setNamespaceURI.contains( props.mNamespaceURI ) )
2144 {
2145 setNamespaceURI.insert( props.mNamespaceURI );
2146 QDomAttr attr = doc.createAttribute( u"xmlns:"_s + props.mNamespacePrefix );
2147 attr.setValue( props.mNamespaceURI );
2148 filterElem.setAttributeNode( attr );
2149 }
2150 }
2151 filterElem.appendChild( exprRootElem );
2152 return filterElem;
2153}
2154
2155//
2156
2157/* static */ Qgis::WkbType QgsOgcUtils::geomTypeFromPropertyType( const QString &gmlGeomType )
2158{
2159 if ( gmlGeomType == "Point"_L1 )
2160 return Qgis::WkbType::Point;
2161 if ( gmlGeomType == "LineString"_L1 || gmlGeomType == "Curve"_L1 )
2163 if ( gmlGeomType == "Polygon"_L1 || gmlGeomType == "Surface"_L1 )
2165 if ( gmlGeomType == "MultiPoint"_L1 )
2167 if ( gmlGeomType == "MultiLineString"_L1 || gmlGeomType == "MultiCurve"_L1 )
2169 if ( gmlGeomType == "MultiPolygon"_L1 || gmlGeomType == "MultiSurface"_L1 )
2172}
2173
2174//
2175
2176
2178{
2179 switch ( node->nodeType() )
2180 {
2182 return expressionUnaryOperatorToOgcFilter( static_cast<const QgsExpressionNodeUnaryOperator *>( node ), expression, context );
2184 return expressionBinaryOperatorToOgcFilter( static_cast<const QgsExpressionNodeBinaryOperator *>( node ), expression, context );
2186 return expressionInOperatorToOgcFilter( static_cast<const QgsExpressionNodeInOperator *>( node ), expression, context );
2188 return expressionFunctionToOgcFilter( static_cast<const QgsExpressionNodeFunction *>( node ), expression, context );
2190 return expressionLiteralToOgcFilter( static_cast<const QgsExpressionNodeLiteral *>( node ), expression, context );
2192 return expressionColumnRefToOgcFilter( static_cast<const QgsExpressionNodeColumnRef *>( node ), expression, context );
2193
2194 default:
2195 mErrorMessage = QObject::tr( "Node type not supported: %1" ).arg( node->nodeType() );
2196 return QDomElement();
2197 }
2198}
2199
2200QDomElement QgsOgcUtilsExprToFilter::expressionUnaryOperatorToOgcFilter( const QgsExpressionNodeUnaryOperator *node, QgsExpression *expression, const QgsExpressionContext *context )
2201{
2202 const QDomElement operandElem = expressionNodeToOgcFilter( node->operand(), expression, context );
2203 if ( !mErrorMessage.isEmpty() )
2204 return QDomElement();
2205
2206 QDomElement uoElem;
2207 switch ( node->op() )
2208 {
2210 uoElem = mDoc.createElement( mFilterPrefix + ":Literal" );
2211 if ( node->operand()->nodeType() == QgsExpressionNode::ntLiteral )
2212 {
2213 // operand expression already created a Literal node:
2214 // take the literal value, prepend - and remove old literal node
2215 uoElem.appendChild( mDoc.createTextNode( "-" + operandElem.text() ) );
2216 mDoc.removeChild( operandElem );
2217 }
2218 else
2219 {
2220 mErrorMessage = QObject::tr( "This use of unary operator not implemented yet" );
2221 return QDomElement();
2222 }
2223 break;
2225 uoElem = mDoc.createElement( mFilterPrefix + ":Not" );
2226 uoElem.appendChild( operandElem );
2227 break;
2228
2229 default:
2230 mErrorMessage = QObject::tr( "Unary operator '%1' not implemented yet" ).arg( node->text() );
2231 return QDomElement();
2232 }
2233
2234 return uoElem;
2235}
2236
2237
2238QDomElement QgsOgcUtilsExprToFilter::expressionBinaryOperatorToOgcFilter( const QgsExpressionNodeBinaryOperator *node, QgsExpression *expression, const QgsExpressionContext *context )
2239{
2240 const QDomElement leftElem = expressionNodeToOgcFilter( node->opLeft(), expression, context );
2241 if ( !mErrorMessage.isEmpty() )
2242 return QDomElement();
2243
2245
2246 // before right operator is parsed: to allow NULL handling
2248 {
2249 if ( node->opRight()->nodeType() == QgsExpressionNode::ntLiteral )
2250 {
2251 const QgsExpressionNodeLiteral *rightLit = static_cast<const QgsExpressionNodeLiteral *>( node->opRight() );
2252 if ( QgsVariantUtils::isNull( rightLit->value() ) )
2253 {
2254
2255 QDomElement elem = mDoc.createElement( mFilterPrefix + ":PropertyIsNull" );
2256 elem.appendChild( leftElem );
2257
2259 {
2260 QDomElement notElem = mDoc.createElement( mFilterPrefix + ":Not" );
2261 notElem.appendChild( elem );
2262 return notElem;
2263 }
2264
2265 return elem;
2266 }
2267
2268 // continue with equal / not equal operator once the null case is handled
2270 }
2271
2272 }
2273
2274 const QDomElement rightElem = expressionNodeToOgcFilter( node->opRight(), expression, context );
2275 if ( !mErrorMessage.isEmpty() )
2276 return QDomElement();
2277
2278
2279 const QString opText = binaryOperatorToTagName( op );
2280 if ( opText.isEmpty() )
2281 {
2282 // not implemented binary operators
2283 // TODO: regex, % (mod), ^ (pow) are not supported yet
2284 mErrorMessage = QObject::tr( "Binary operator %1 not implemented yet" ).arg( node->text() );
2285 return QDomElement();
2286 }
2287
2288 QDomElement boElem = mDoc.createElement( mFilterPrefix + ":" + opText );
2289
2291 {
2293 boElem.setAttribute( u"matchCase"_s, u"false"_s );
2294
2295 // setup wildCards to <ogc:PropertyIsLike>
2296 boElem.setAttribute( u"wildCard"_s, u"%"_s );
2297 boElem.setAttribute( u"singleChar"_s, u"_"_s );
2298 if ( mFilterVersion == QgsOgcUtils::FILTER_OGC_1_0 )
2299 boElem.setAttribute( u"escape"_s, u"\\"_s );
2300 else
2301 boElem.setAttribute( u"escapeChar"_s, u"\\"_s );
2302 }
2303
2304 boElem.appendChild( leftElem );
2305 boElem.appendChild( rightElem );
2306 return boElem;
2307}
2308
2309
2310QDomElement QgsOgcUtilsExprToFilter::expressionLiteralToOgcFilter( const QgsExpressionNodeLiteral *node, QgsExpression *expression, const QgsExpressionContext *context )
2311{
2312 Q_UNUSED( expression )
2313 Q_UNUSED( context )
2314 QString value;
2315 switch ( node->value().userType() )
2316 {
2317 case QMetaType::Type::Int:
2318 value = QString::number( node->value().toInt() );
2319 break;
2320 case QMetaType::Type::Double:
2321 value = qgsDoubleToString( node->value().toDouble() );
2322 break;
2323 case QMetaType::Type::QString:
2324 value = node->value().toString();
2325 break;
2326 case QMetaType::Type::QDate:
2327 value = node->value().toDate().toString( Qt::ISODate );
2328 break;
2329 case QMetaType::Type::QDateTime:
2330 value = node->value().toDateTime().toString( Qt::ISODate );
2331 break;
2332
2333 default:
2334 mErrorMessage = QObject::tr( "Literal type not supported: %1" ).arg( static_cast<QMetaType::Type>( node->value().userType() ) );
2335 return QDomElement();
2336 }
2337
2338 QDomElement litElem = mDoc.createElement( mFilterPrefix + ":Literal" );
2339 litElem.appendChild( mDoc.createTextNode( value ) );
2340 return litElem;
2341}
2342
2343
2344QDomElement QgsOgcUtilsExprToFilter::expressionColumnRefToOgcFilter( const QgsExpressionNodeColumnRef *node, QgsExpression *expression, const QgsExpressionContext *context )
2345{
2346 Q_UNUSED( expression )
2347 Q_UNUSED( context )
2348 QDomElement propElem = mDoc.createElement( mFilterPrefix + ":" + mPropertyName );
2349 if ( !mFieldNameToXPathMap.isEmpty() )
2350 {
2351 const auto iterFieldName = mFieldNameToXPathMap.constFind( node->name() );
2352 if ( iterFieldName != mFieldNameToXPathMap.constEnd() )
2353 {
2354 const QString xpath( *iterFieldName );
2355
2356 if ( !mNamespacePrefixToUriMap.isEmpty() )
2357 {
2358 const QStringList parts = xpath.split( '/' );
2359 QSet<QString> setNamespacePrefix;
2360 for ( const QString &part : std::as_const( parts ) )
2361 {
2362 const QStringList subparts = part.split( ':' );
2363 if ( subparts.size() == 2 && !setNamespacePrefix.contains( subparts[0] ) )
2364 {
2365 const auto iterNamespacePrefix = mNamespacePrefixToUriMap.constFind( subparts[0] );
2366 if ( iterNamespacePrefix != mNamespacePrefixToUriMap.constEnd() )
2367 {
2368 setNamespacePrefix.insert( subparts[0] );
2369 QDomAttr attr = mDoc.createAttribute( u"xmlns:"_s + subparts[0] );
2370 attr.setValue( *iterNamespacePrefix );
2371 propElem.setAttributeNode( attr );
2372 }
2373 }
2374 }
2375 }
2376
2377 propElem.appendChild( mDoc.createTextNode( xpath ) );
2378
2379 return propElem;
2380 }
2381 }
2382 QString columnRef( node->name() );
2383 if ( !mNamespacePrefix.isEmpty() && !mNamespaceURI.isEmpty() )
2384 columnRef = mNamespacePrefix + u":"_s + columnRef;
2385 propElem.appendChild( mDoc.createTextNode( columnRef ) );
2386 return propElem;
2387}
2388
2389
2390
2391QDomElement QgsOgcUtilsExprToFilter::expressionInOperatorToOgcFilter( const QgsExpressionNodeInOperator *node, QgsExpression *expression, const QgsExpressionContext *context )
2392{
2393 if ( node->list()->list().size() == 1 )
2394 {
2395 const QDomElement leftNode = expressionNodeToOgcFilter( node->node(), expression, context );
2396 const QDomElement firstListNode = expressionNodeToOgcFilter( node->list()->list().first(), expression, context );
2397 QDomElement eqElem = mDoc.createElement( mFilterPrefix + ":PropertyIsEqualTo" );
2398 eqElem.appendChild( leftNode );
2399 eqElem.appendChild( firstListNode );
2400 if ( node->isNotIn() )
2401 {
2402 QDomElement notElem = mDoc.createElement( mFilterPrefix + ":Not" );
2403 notElem.appendChild( eqElem );
2404 return notElem;
2405 }
2406 return eqElem;
2407 }
2408
2409 QDomElement orElem = mDoc.createElement( mFilterPrefix + ":Or" );
2410 const QDomElement leftNode = expressionNodeToOgcFilter( node->node(), expression, context );
2411
2412 const auto constList = node->list()->list();
2413 for ( QgsExpressionNode *n : constList )
2414 {
2415 const QDomElement listNode = expressionNodeToOgcFilter( n, expression, context );
2416 if ( !mErrorMessage.isEmpty() )
2417 return QDomElement();
2418
2419 QDomElement eqElem = mDoc.createElement( mFilterPrefix + ":PropertyIsEqualTo" );
2420 eqElem.appendChild( leftNode.cloneNode() );
2421 eqElem.appendChild( listNode );
2422
2423 orElem.appendChild( eqElem );
2424 }
2425
2426 if ( node->isNotIn() )
2427 {
2428 QDomElement notElem = mDoc.createElement( mFilterPrefix + ":Not" );
2429 notElem.appendChild( orElem );
2430 return notElem;
2431 }
2432
2433 return orElem;
2434}
2435
2436Q_GLOBAL_STATIC_WITH_ARGS( QgsStringMap, BINARY_SPATIAL_OPS_MAP, (
2437{
2438 { "disjoint"_L1, "Disjoint"_L1 },
2439 { "intersects"_L1, "Intersects"_L1},
2440 { "touches"_L1, "Touches"_L1 },
2441 { "crosses"_L1, "Crosses"_L1 },
2442 { "contains"_L1, "Contains"_L1 },
2443 { "overlaps"_L1, "Overlaps"_L1 },
2444 { "within"_L1, "Within"_L1 }
2445} ) )
2446
2447static bool isBinarySpatialOperator( const QString &fnName )
2448{
2449 return BINARY_SPATIAL_OPS_MAP()->contains( fnName );
2450}
2451
2452static QString tagNameForSpatialOperator( const QString &fnName )
2453{
2454 return BINARY_SPATIAL_OPS_MAP()->value( fnName );
2455}
2456
2457static bool isGeometryColumn( const QgsExpressionNode *node )
2458{
2459 if ( node->nodeType() != QgsExpressionNode::ntFunction )
2460 return false;
2461
2462 const QgsExpressionNodeFunction *fn = static_cast<const QgsExpressionNodeFunction *>( node );
2464 return fd->name() == "$geometry"_L1 || ( fd->name() == "var"_L1 && fn->referencedVariables().contains( "geometry"_L1 ) );
2465}
2466
2467static QgsGeometry geometryFromConstExpr( const QgsExpressionNode *node )
2468{
2469 // Right now we support only geomFromWKT(' ..... ')
2470 // Ideally we should support any constant sub-expression (not dependent on feature's geometry or attributes)
2471
2472 if ( node->nodeType() == QgsExpressionNode::ntFunction )
2473 {
2474 const QgsExpressionNodeFunction *fnNode = static_cast<const QgsExpressionNodeFunction *>( node );
2476 if ( fnDef->name() == "geom_from_wkt"_L1 )
2477 {
2478 const QList<QgsExpressionNode *> &args = fnNode->args()->list();
2479 if ( args[0]->nodeType() == QgsExpressionNode::ntLiteral )
2480 {
2481 const QString wkt = static_cast<const QgsExpressionNodeLiteral *>( args[0] )->value().toString();
2482 return QgsGeometry::fromWkt( wkt );
2483 }
2484 }
2485 }
2486 return QgsGeometry();
2487}
2488
2489
2490QDomElement QgsOgcUtilsExprToFilter::expressionFunctionToOgcFilter( const QgsExpressionNodeFunction *node, QgsExpression *expression, const QgsExpressionContext *context )
2491{
2492 QgsExpressionFunction *fd = QgsExpression::Functions()[node->fnIndex()];
2493
2494 if ( fd->name() == "intersects_bbox"_L1 )
2495 {
2496 QList<QgsExpressionNode *> argNodes = node->args()->list();
2497 Q_ASSERT( argNodes.count() == 2 ); // binary spatial ops must have two args
2498
2499 const QgsGeometry geom = geometryFromConstExpr( argNodes[1] );
2500 if ( !geom.isNull() && isGeometryColumn( argNodes[0] ) )
2501 {
2502 QgsRectangle rect = geom.boundingBox();
2503
2504 mGMLUsed = true;
2505
2506 const QDomElement elemBox = ( mGMLVersion == QgsOgcUtils::GML_2_1_2 ) ?
2507 QgsOgcUtils::rectangleToGMLBox( &rect, mDoc, mSrsName, mInvertAxisOrientation ) :
2508 QgsOgcUtils::rectangleToGMLEnvelope( &rect, mDoc, mSrsName, mInvertAxisOrientation );
2509
2510 QDomElement funcElem = mDoc.createElement( mFilterPrefix + ":BBOX" );
2511
2512 if ( !mGeometryName.isEmpty() )
2513 {
2514 // Geometry column is optional for a BBOX filter.
2515 QDomElement geomProperty = mDoc.createElement( mFilterPrefix + ":" + mPropertyName );
2516 QString columnRef( mGeometryName );
2517 if ( !mNamespacePrefix.isEmpty() && !mNamespaceURI.isEmpty() )
2518 columnRef = mNamespacePrefix + u":"_s + columnRef;
2519 geomProperty.appendChild( mDoc.createTextNode( columnRef ) );
2520
2521 funcElem.appendChild( geomProperty );
2522 }
2523 funcElem.appendChild( elemBox );
2524 return funcElem;
2525 }
2526 else
2527 {
2528 mErrorMessage = QObject::tr( "<BBOX> is currently supported only in form: bbox(@geometry, geomFromWKT('…'))" );
2529 return QDomElement();
2530 }
2531 }
2532
2533 if ( isBinarySpatialOperator( fd->name() ) )
2534 {
2535 QList<QgsExpressionNode *> argNodes = node->args()->list();
2536 Q_ASSERT( argNodes.count() == 2 ); // binary spatial ops must have two args
2537
2538 QgsExpressionNode *otherNode = nullptr;
2539 if ( isGeometryColumn( argNodes[0] ) )
2540 otherNode = argNodes[1];
2541 else if ( isGeometryColumn( argNodes[1] ) )
2542 otherNode = argNodes[0];
2543 else
2544 {
2545 mErrorMessage = QObject::tr( "Unable to translate spatial operator: at least one must refer to geometry." );
2546 return QDomElement();
2547 }
2548
2549 QDomElement otherGeomElem;
2550
2551 // the other node must be a geometry constructor
2552 if ( otherNode->nodeType() != QgsExpressionNode::ntFunction )
2553 {
2554 mErrorMessage = QObject::tr( "spatial operator: the other operator must be a geometry constructor function" );
2555 return QDomElement();
2556 }
2557
2558 const QgsExpressionNodeFunction *otherFn = static_cast<const QgsExpressionNodeFunction *>( otherNode );
2559 QgsExpressionFunction *otherFnDef = QgsExpression::Functions()[otherFn->fnIndex()];
2560 if ( otherFnDef->name() == "geom_from_wkt"_L1 )
2561 {
2562 QgsExpressionNode *firstFnArg = otherFn->args()->list()[0];
2563 if ( firstFnArg->nodeType() != QgsExpressionNode::ntLiteral )
2564 {
2565 mErrorMessage = QObject::tr( "geom_from_wkt: argument must be string literal" );
2566 return QDomElement();
2567 }
2568 const QString wkt = static_cast<const QgsExpressionNodeLiteral *>( firstFnArg )->value().toString();
2569 const QgsGeometry geom = QgsGeometry::fromWkt( wkt );
2570 otherGeomElem = QgsOgcUtils::geometryToGML( geom, mDoc, mGMLVersion, mSrsName, mInvertAxisOrientation,
2571 u"qgis_id_geom_%1"_s.arg( mGeomId ) );
2572 if ( otherGeomElem.isNull() )
2573 {
2574 mErrorMessage = QObject::tr( "geom_from_wkt: unable to generate GML from wkt geometry" );
2575 return QDomElement();
2576 }
2577 mGeomId ++;
2578 }
2579 else if ( otherFnDef->name() == "geom_from_gml"_L1 )
2580 {
2581 QgsExpressionNode *firstFnArg = otherFn->args()->list()[0];
2582 if ( firstFnArg->nodeType() != QgsExpressionNode::ntLiteral )
2583 {
2584 mErrorMessage = QObject::tr( "geom_from_gml: argument must be string literal" );
2585 return QDomElement();
2586 }
2587
2588 QDomDocument geomDoc;
2589 const QString gml = static_cast<const QgsExpressionNodeLiteral *>( firstFnArg )->value().toString();
2590 // wrap the string into a root tag to have "gml" namespace
2591 const QString xml = u"<tmp xmlns:gml=\"%1\">%2</tmp>"_s.arg( GML_NAMESPACE, gml );
2592 if ( !geomDoc.setContent( xml, true ) )
2593 {
2594 mErrorMessage = QObject::tr( "geom_from_gml: unable to parse XML" );
2595 return QDomElement();
2596 }
2597
2598 const QDomNode geomNode = mDoc.importNode( geomDoc.documentElement().firstChildElement(), true );
2599 otherGeomElem = geomNode.toElement();
2600 }
2601 else if ( otherNode->hasCachedStaticValue() && otherNode->cachedStaticValue().userType() == qMetaTypeId< QgsGeometry>() )
2602 {
2603 QgsGeometry geom = otherNode->cachedStaticValue().value<QgsGeometry>();
2604 otherGeomElem = QgsOgcUtils::geometryToGML( geom, mDoc, mGMLVersion, mSrsName, mInvertAxisOrientation,
2605 u"qgis_id_geom_%1"_s.arg( mGeomId ) );
2606 if ( otherGeomElem.isNull() )
2607 {
2608 mErrorMessage = QObject::tr( "geom from static value: unable to generate GML from static variable" );
2609 return QDomElement();
2610 }
2611 mGeomId ++;
2612 }
2613 else
2614 {
2615 mErrorMessage = QObject::tr( "spatial operator: unknown geometry constructor function" );
2616 return QDomElement();
2617 }
2618
2619 mGMLUsed = true;
2620
2621 QDomElement funcElem = mDoc.createElement( mFilterPrefix + ":" + tagNameForSpatialOperator( fd->name() ) );
2622 QDomElement geomProperty = mDoc.createElement( mFilterPrefix + ":" + mPropertyName );
2623 QString columnRef( mGeometryName );
2624 if ( !mNamespacePrefix.isEmpty() && !mNamespaceURI.isEmpty() )
2625 columnRef = mNamespacePrefix + u":"_s + columnRef;
2626 geomProperty.appendChild( mDoc.createTextNode( columnRef ) );
2627 funcElem.appendChild( geomProperty );
2628 funcElem.appendChild( otherGeomElem );
2629 return funcElem;
2630 }
2631
2632 if ( fd->isStatic( node, expression, context ) )
2633 {
2634 const QVariant result = fd->run( node->args(), context, expression, node );
2635 const QgsExpressionNodeLiteral literal( result );
2636 return expressionLiteralToOgcFilter( &literal, expression, context );
2637 }
2638
2639 if ( fd->params() == 0 )
2640 {
2641 mErrorMessage = QObject::tr( "Special columns/constants are not supported." );
2642 return QDomElement();
2643 }
2644
2645 // this is somehow wrong - we are just hoping that the other side supports the same functions as we do...
2646 QDomElement funcElem = mDoc.createElement( mFilterPrefix + ":Function" );
2647 funcElem.setAttribute( u"name"_s, fd->name() );
2648 const auto constList = node->args()->list();
2649 for ( QgsExpressionNode *n : constList )
2650 {
2651 const QDomElement childElem = expressionNodeToOgcFilter( n, expression, context );
2652 if ( !mErrorMessage.isEmpty() )
2653 return QDomElement();
2654
2655 funcElem.appendChild( childElem );
2656 }
2657
2658 return funcElem;
2659}
2660
2661//
2662
2664 QgsOgcUtils::GMLVersion gmlVersion,
2665 QgsOgcUtils::FilterVersion filterVersion,
2666 const QList<QgsOgcUtils::LayerProperties> &layerProperties,
2667 bool honourAxisOrientation,
2668 bool invertAxisOrientation,
2669 const QMap< QString, QString> &mapUnprefixedTypenameToPrefixedTypename,
2670 const QMap<QString, QString> &fieldNameToXPathMap,
2671 const QMap<QString, QString> &namespacePrefixToUriMap )
2672 : mDoc( doc )
2673 , mGMLVersion( gmlVersion )
2674 , mFilterVersion( filterVersion )
2675 , mLayerProperties( layerProperties )
2676 , mHonourAxisOrientation( honourAxisOrientation )
2677 , mInvertAxisOrientation( invertAxisOrientation )
2678 , mFilterPrefix( ( filterVersion == QgsOgcUtils::FILTER_FES_2_0 ) ? "fes" : "ogc" )
2679 , mPropertyName( ( filterVersion == QgsOgcUtils::FILTER_FES_2_0 ) ? "ValueReference" : "PropertyName" )
2680 , mMapUnprefixedTypenameToPrefixedTypename( mapUnprefixedTypenameToPrefixedTypename )
2681 , mFieldNameToXPathMap( fieldNameToXPathMap )
2682 , mNamespacePrefixToUriMap( namespacePrefixToUriMap )
2683{
2684}
2685
2687{
2688 switch ( node->nodeType() )
2689 {
2691 return toOgcFilter( static_cast<const QgsSQLStatement::NodeUnaryOperator *>( node ) );
2693 return toOgcFilter( static_cast<const QgsSQLStatement::NodeBinaryOperator *>( node ) );
2695 return toOgcFilter( static_cast<const QgsSQLStatement::NodeInOperator *>( node ) );
2697 return toOgcFilter( static_cast<const QgsSQLStatement::NodeBetweenOperator *>( node ) );
2699 return toOgcFilter( static_cast<const QgsSQLStatement::NodeFunction *>( node ) );
2701 return toOgcFilter( static_cast<const QgsSQLStatement::NodeLiteral *>( node ) );
2703 return toOgcFilter( static_cast<const QgsSQLStatement::NodeColumnRef *>( node ) );
2705 return toOgcFilter( static_cast<const QgsSQLStatement::NodeSelect *>( node ) );
2706
2707 default:
2708 mErrorMessage = QObject::tr( "Node type not supported: %1" ).arg( node->nodeType() );
2709 return QDomElement();
2710 }
2711}
2712
2713
2715{
2716
2717 const QDomElement operandElem = toOgcFilter( node->operand() );
2718 if ( !mErrorMessage.isEmpty() )
2719 return QDomElement();
2720
2721 QDomElement uoElem;
2722 switch ( node->op() )
2723 {
2725 uoElem = mDoc.createElement( mFilterPrefix + ":Literal" );
2726 if ( node->operand()->nodeType() == QgsSQLStatement::ntLiteral )
2727 {
2728 // operand expression already created a Literal node:
2729 // take the literal value, prepend - and remove old literal node
2730 uoElem.appendChild( mDoc.createTextNode( "-" + operandElem.text() ) );
2731 mDoc.removeChild( operandElem );
2732 }
2733 else
2734 {
2735 mErrorMessage = QObject::tr( "This use of unary operator not implemented yet" );
2736 return QDomElement();
2737 }
2738 break;
2740 uoElem = mDoc.createElement( mFilterPrefix + ":Not" );
2741 uoElem.appendChild( operandElem );
2742 break;
2743
2744 default:
2745 mErrorMessage = QObject::tr( "Unary operator %1 not implemented yet" ).arg( QgsSQLStatement::UNARY_OPERATOR_TEXT[node->op()] );
2746 return QDomElement();
2747 }
2748
2749 return uoElem;
2750}
2751
2752
2754{
2755 const QDomElement leftElem = toOgcFilter( node->opLeft() );
2756 if ( !mErrorMessage.isEmpty() )
2757 return QDomElement();
2758
2760
2761 // before right operator is parsed: to allow NULL handling
2763 {
2764 if ( node->opRight()->nodeType() == QgsSQLStatement::ntLiteral )
2765 {
2766 const QgsSQLStatement::NodeLiteral *rightLit = static_cast<const QgsSQLStatement::NodeLiteral *>( node->opRight() );
2767 if ( QgsVariantUtils::isNull( rightLit->value() ) )
2768 {
2769
2770 QDomElement elem = mDoc.createElement( mFilterPrefix + ":PropertyIsNull" );
2771 elem.appendChild( leftElem );
2772
2773 if ( op == QgsSQLStatement::boIsNot )
2774 {
2775 QDomElement notElem = mDoc.createElement( mFilterPrefix + ":Not" );
2776 notElem.appendChild( elem );
2777 return notElem;
2778 }
2779
2780 return elem;
2781 }
2782
2783 // continue with equal / not equal operator once the null case is handled
2785 }
2786
2787 }
2788
2789 const QDomElement rightElem = toOgcFilter( node->opRight() );
2790 if ( !mErrorMessage.isEmpty() )
2791 return QDomElement();
2792
2793
2794 QString opText;
2795 if ( op == QgsSQLStatement::boOr )
2796 opText = u"Or"_s;
2797 else if ( op == QgsSQLStatement::boAnd )
2798 opText = u"And"_s;
2799 else if ( op == QgsSQLStatement::boEQ )
2800 opText = u"PropertyIsEqualTo"_s;
2801 else if ( op == QgsSQLStatement::boNE )
2802 opText = u"PropertyIsNotEqualTo"_s;
2803 else if ( op == QgsSQLStatement::boLE )
2804 opText = u"PropertyIsLessThanOrEqualTo"_s;
2805 else if ( op == QgsSQLStatement::boGE )
2806 opText = u"PropertyIsGreaterThanOrEqualTo"_s;
2807 else if ( op == QgsSQLStatement::boLT )
2808 opText = u"PropertyIsLessThan"_s;
2809 else if ( op == QgsSQLStatement::boGT )
2810 opText = u"PropertyIsGreaterThan"_s;
2811 else if ( op == QgsSQLStatement::boLike )
2812 opText = u"PropertyIsLike"_s;
2813 else if ( op == QgsSQLStatement::boILike )
2814 opText = u"PropertyIsLike"_s;
2815
2816 if ( opText.isEmpty() )
2817 {
2818 // not implemented binary operators
2819 mErrorMessage = QObject::tr( "Binary operator %1 not implemented yet" ).arg( QgsSQLStatement::BINARY_OPERATOR_TEXT[op] );
2820 return QDomElement();
2821 }
2822
2823 QDomElement boElem = mDoc.createElement( mFilterPrefix + ":" + opText );
2824
2826 {
2827 if ( op == QgsSQLStatement::boILike )
2828 boElem.setAttribute( u"matchCase"_s, u"false"_s );
2829
2830 // setup wildCards to <ogc:PropertyIsLike>
2831 boElem.setAttribute( u"wildCard"_s, u"%"_s );
2832 boElem.setAttribute( u"singleChar"_s, u"_"_s );
2833 if ( mFilterVersion == QgsOgcUtils::FILTER_OGC_1_0 )
2834 boElem.setAttribute( u"escape"_s, u"\\"_s );
2835 else
2836 boElem.setAttribute( u"escapeChar"_s, u"\\"_s );
2837 }
2838
2839 boElem.appendChild( leftElem );
2840 boElem.appendChild( rightElem );
2841 return boElem;
2842}
2843
2844
2846{
2847 QString value;
2848 switch ( node->value().userType() )
2849 {
2850 case QMetaType::Type::Int:
2851 value = QString::number( node->value().toInt() );
2852 break;
2853 case QMetaType::Type::LongLong:
2854 value = QString::number( node->value().toLongLong() );
2855 break;
2856 case QMetaType::Type::Double:
2857 value = qgsDoubleToString( node->value().toDouble() );
2858 break;
2859 case QMetaType::Type::QString:
2860 value = node->value().toString();
2861 break;
2862
2863 default:
2864 mErrorMessage = QObject::tr( "Literal type not supported: %1" ).arg( static_cast<QMetaType::Type>( node->value().userType() ) );
2865 return QDomElement();
2866 }
2867
2868 QDomElement litElem = mDoc.createElement( mFilterPrefix + ":Literal" );
2869 litElem.appendChild( mDoc.createTextNode( value ) );
2870 return litElem;
2871}
2872
2873
2875{
2876 QDomElement propElem = mDoc.createElement( mFilterPrefix + ":" + mPropertyName );
2877 if ( node->tableName().isEmpty() || mLayerProperties.size() == 1 )
2878 {
2879 if ( !mFieldNameToXPathMap.isEmpty() )
2880 {
2881 const auto iterFieldName = mFieldNameToXPathMap.constFind( node->name() );
2882 if ( iterFieldName != mFieldNameToXPathMap.constEnd() )
2883 {
2884 const QString xpath( *iterFieldName );
2885
2886 if ( !mNamespacePrefixToUriMap.isEmpty() )
2887 {
2888 const QStringList parts = xpath.split( '/' );
2889 QSet<QString> setNamespacePrefix;
2890 for ( const QString &part : std::as_const( parts ) )
2891 {
2892 const QStringList subparts = part.split( ':' );
2893 if ( subparts.size() == 2 && !setNamespacePrefix.contains( subparts[0] ) )
2894 {
2895 const auto iterNamespacePrefix = mNamespacePrefixToUriMap.constFind( subparts[0] );
2896 if ( iterNamespacePrefix != mNamespacePrefixToUriMap.constEnd() )
2897 {
2898 setNamespacePrefix.insert( subparts[0] );
2899 QDomAttr attr = mDoc.createAttribute( u"xmlns:"_s + subparts[0] );
2900 attr.setValue( *iterNamespacePrefix );
2901 propElem.setAttributeNode( attr );
2902 }
2903 }
2904 }
2905 }
2906
2907 propElem.appendChild( mDoc.createTextNode( xpath ) );
2908
2909 return propElem;
2910 }
2911 }
2912 if ( mLayerProperties.size() == 1 && !mLayerProperties[0].mNamespacePrefix.isEmpty() && !mLayerProperties[0].mNamespaceURI.isEmpty() )
2913 propElem.appendChild( mDoc.createTextNode(
2914 mLayerProperties[0].mNamespacePrefix + u":"_s + node->name() ) );
2915 else
2916 propElem.appendChild( mDoc.createTextNode( node->name() ) );
2917 }
2918 else
2919 {
2920 QString tableName( mMapTableAliasToNames[node->tableName()] );
2921 if ( mMapUnprefixedTypenameToPrefixedTypename.contains( tableName ) )
2922 tableName = mMapUnprefixedTypenameToPrefixedTypename[tableName];
2923 propElem.appendChild( mDoc.createTextNode( tableName + "/" + node->name() ) );
2924 }
2925 return propElem;
2926}
2927
2929{
2930 if ( node->list()->list().size() == 1 )
2931 {
2932 const QDomElement leftNode = toOgcFilter( node->node() );
2933 const QDomElement firstListNode = toOgcFilter( node->list()->list().first() );
2934 QDomElement eqElem = mDoc.createElement( mFilterPrefix + ":PropertyIsEqualTo" );
2935 eqElem.appendChild( leftNode );
2936 eqElem.appendChild( firstListNode );
2937 if ( node->isNotIn() )
2938 {
2939 QDomElement notElem = mDoc.createElement( mFilterPrefix + ":Not" );
2940 notElem.appendChild( eqElem );
2941 return notElem;
2942 }
2943 return eqElem;
2944 }
2945
2946 QDomElement orElem = mDoc.createElement( mFilterPrefix + ":Or" );
2947 const QDomElement leftNode = toOgcFilter( node->node() );
2948
2949 const auto constList = node->list()->list();
2950 for ( QgsSQLStatement::Node *n : constList )
2951 {
2952 const QDomElement listNode = toOgcFilter( n );
2953 if ( !mErrorMessage.isEmpty() )
2954 return QDomElement();
2955
2956 QDomElement eqElem = mDoc.createElement( mFilterPrefix + ":PropertyIsEqualTo" );
2957 eqElem.appendChild( leftNode.cloneNode() );
2958 eqElem.appendChild( listNode );
2959
2960 orElem.appendChild( eqElem );
2961 }
2962
2963 if ( node->isNotIn() )
2964 {
2965 QDomElement notElem = mDoc.createElement( mFilterPrefix + ":Not" );
2966 notElem.appendChild( orElem );
2967 return notElem;
2968 }
2969
2970 return orElem;
2971}
2972
2974{
2975 QDomElement elem = mDoc.createElement( mFilterPrefix + ":PropertyIsBetween" );
2976 elem.appendChild( toOgcFilter( node->node() ) );
2977 QDomElement lowerBoundary = mDoc.createElement( mFilterPrefix + ":LowerBoundary" );
2978 lowerBoundary.appendChild( toOgcFilter( node->minVal() ) );
2979 elem.appendChild( lowerBoundary );
2980 QDomElement upperBoundary = mDoc.createElement( mFilterPrefix + ":UpperBoundary" );
2981 upperBoundary.appendChild( toOgcFilter( node->maxVal() ) );
2982 elem.appendChild( upperBoundary );
2983
2984 if ( node->isNotBetween() )
2985 {
2986 QDomElement notElem = mDoc.createElement( mFilterPrefix + ":Not" );
2987 notElem.appendChild( elem );
2988 return notElem;
2989 }
2990
2991 return elem;
2992}
2993
2994static QString mapBinarySpatialToOgc( const QString &name )
2995{
2996 QString nameCompare( name );
2997 if ( name.size() > 3 && QStringView {name} .mid( 0, 3 ).toString().compare( "ST_"_L1, Qt::CaseInsensitive ) == 0 )
2998 nameCompare = name.mid( 3 );
2999 QStringList spatialOps;
3000 spatialOps << u"BBOX"_s << u"Intersects"_s << u"Contains"_s << u"Crosses"_s << u"Equals"_s
3001 << u"Disjoint"_s << u"Overlaps"_s << u"Touches"_s << u"Within"_s;
3002 const auto constSpatialOps = spatialOps;
3003 for ( QString op : constSpatialOps )
3004 {
3005 if ( nameCompare.compare( op, Qt::CaseInsensitive ) == 0 )
3006 return op;
3007 }
3008 return QString();
3009}
3010
3011static QString mapTernarySpatialToOgc( const QString &name )
3012{
3013 QString nameCompare( name );
3014 if ( name.size() > 3 && QStringView {name} .mid( 0, 3 ).compare( "ST_"_L1, Qt::CaseInsensitive ) == 0 )
3015 nameCompare = name.mid( 3 );
3016 if ( nameCompare.compare( "DWithin"_L1, Qt::CaseInsensitive ) == 0 )
3017 return u"DWithin"_s;
3018 if ( nameCompare.compare( "Beyond"_L1, Qt::CaseInsensitive ) == 0 )
3019 return u"Beyond"_s;
3020 return QString();
3021}
3022
3023QString QgsOgcUtilsSQLStatementToFilter::getGeometryColumnSRSName( const QgsSQLStatement::Node *node )
3024{
3025 if ( node->nodeType() != QgsSQLStatement::ntColumnRef )
3026 return QString();
3027
3028 const QgsSQLStatement::NodeColumnRef *col = static_cast<const QgsSQLStatement::NodeColumnRef *>( node );
3029 if ( !col->tableName().isEmpty() )
3030 {
3031 const auto constMLayerProperties = mLayerProperties;
3032 for ( const QgsOgcUtils::LayerProperties &prop : constMLayerProperties )
3033 {
3034 if ( prop.mName.compare( mMapTableAliasToNames[col->tableName()], Qt::CaseInsensitive ) == 0 &&
3035 prop.mGeometryAttribute.compare( col->name(), Qt::CaseInsensitive ) == 0 )
3036 {
3037 return prop.mSRSName;
3038 }
3039 }
3040 }
3041 if ( !mLayerProperties.empty() &&
3042 mLayerProperties.at( 0 ).mGeometryAttribute.compare( col->name(), Qt::CaseInsensitive ) == 0 )
3043 {
3044 return mLayerProperties.at( 0 ).mSRSName;
3045 }
3046 return QString();
3047}
3048
3049bool QgsOgcUtilsSQLStatementToFilter::processSRSName( const QgsSQLStatement::NodeFunction *mainNode,
3050 QList<QgsSQLStatement::Node *> args,
3051 bool lastArgIsSRSName,
3052 QString &srsName,
3053 bool &axisInversion )
3054{
3055 srsName = mCurrentSRSName;
3056 axisInversion = mInvertAxisOrientation;
3057
3058 if ( lastArgIsSRSName )
3059 {
3060 QgsSQLStatement::Node *lastArg = args[ args.size() - 1 ];
3061 if ( lastArg->nodeType() != QgsSQLStatement::ntLiteral )
3062 {
3063 mErrorMessage = QObject::tr( "%1: Last argument must be string or integer literal" ).arg( mainNode->name() );
3064 return false;
3065 }
3066 const QgsSQLStatement::NodeLiteral *lit = static_cast<const QgsSQLStatement::NodeLiteral *>( lastArg );
3067 if ( lit->value().userType() == QMetaType::Type::Int )
3068 {
3069 if ( mFilterVersion == QgsOgcUtils::FILTER_OGC_1_0 )
3070 {
3071 srsName = "EPSG:" + QString::number( lit->value().toInt() );
3072 }
3073 else
3074 {
3075 srsName = "urn:ogc:def:crs:EPSG::" + QString::number( lit->value().toInt() );
3076 }
3077 }
3078 else
3079 {
3080 srsName = lit->value().toString();
3081 if ( srsName.startsWith( "EPSG:"_L1, Qt::CaseInsensitive ) )
3082 return true;
3083 }
3084 }
3085
3086 QgsCoordinateReferenceSystem crs;
3087 if ( !srsName.isEmpty() )
3089 if ( crs.isValid() )
3090 {
3091 if ( mHonourAxisOrientation && crs.hasAxisInverted() )
3092 {
3093 axisInversion = !axisInversion;
3094 }
3095 }
3096
3097 return true;
3098}
3099
3101{
3102 // ST_GeometryFromText
3103 if ( node->name().compare( "ST_GeometryFromText"_L1, Qt::CaseInsensitive ) == 0 )
3104 {
3105 QList<QgsSQLStatement::Node *> args = node->args()->list();
3106 if ( args.size() != 1 && args.size() != 2 )
3107 {
3108 mErrorMessage = QObject::tr( "Function %1 should have 1 or 2 arguments" ).arg( node->name() );
3109 return QDomElement();
3110 }
3111
3112 QgsSQLStatement::Node *firstFnArg = args[0];
3113 if ( firstFnArg->nodeType() != QgsSQLStatement::ntLiteral )
3114 {
3115 mErrorMessage = QObject::tr( "%1: First argument must be string literal" ).arg( node->name() );
3116 return QDomElement();
3117 }
3118
3119 QString srsName;
3120 bool axisInversion;
3121 if ( ! processSRSName( node, args, args.size() == 2, srsName, axisInversion ) )
3122 {
3123 return QDomElement();
3124 }
3125
3126 const QString wkt = static_cast<const QgsSQLStatement::NodeLiteral *>( firstFnArg )->value().toString();
3127 const QgsGeometry geom = QgsGeometry::fromWkt( wkt );
3128 const QDomElement geomElem = QgsOgcUtils::geometryToGML( geom, mDoc, mGMLVersion, srsName, axisInversion,
3129 u"qgis_id_geom_%1"_s.arg( mGeomId ) );
3130 mGeomId ++;
3131 if ( geomElem.isNull() )
3132 {
3133 mErrorMessage = QObject::tr( "%1: invalid WKT" ).arg( node->name() );
3134 return QDomElement();
3135 }
3136 mGMLUsed = true;
3137 return geomElem;
3138 }
3139
3140 // ST_MakeEnvelope
3141 if ( node->name().compare( "ST_MakeEnvelope"_L1, Qt::CaseInsensitive ) == 0 )
3142 {
3143 QList<QgsSQLStatement::Node *> args = node->args()->list();
3144 if ( args.size() != 4 && args.size() != 5 )
3145 {
3146 mErrorMessage = QObject::tr( "Function %1 should have 4 or 5 arguments" ).arg( node->name() );
3147 return QDomElement();
3148 }
3149
3150 QgsRectangle rect;
3151
3152 for ( int i = 0; i < 4; i++ )
3153 {
3154 QgsSQLStatement::Node *arg = args[i];
3155 if ( arg->nodeType() != QgsSQLStatement::ntLiteral )
3156 {
3157 mErrorMessage = QObject::tr( "%1: Argument %2 must be numeric literal" ).arg( node->name() ).arg( i + 1 );
3158 return QDomElement();
3159 }
3160 const QgsSQLStatement::NodeLiteral *lit = static_cast<const QgsSQLStatement::NodeLiteral *>( arg );
3161 double val = 0.0;
3162 if ( lit->value().userType() == QMetaType::Type::Int )
3163 val = lit->value().toInt();
3164 else if ( lit->value().userType() == QMetaType::Type::LongLong )
3165 val = lit->value().toLongLong();
3166 else if ( lit->value().userType() == QMetaType::Type::Double )
3167 val = lit->value().toDouble();
3168 else
3169 {
3170 mErrorMessage = QObject::tr( "%1 Argument %2 must be numeric literal" ).arg( node->name() ).arg( i + 1 );
3171 return QDomElement();
3172 }
3173 if ( i == 0 )
3174 rect.setXMinimum( val );
3175 else if ( i == 1 )
3176 rect.setYMinimum( val );
3177 else if ( i == 2 )
3178 rect.setXMaximum( val );
3179 else
3180 rect.setYMaximum( val );
3181 }
3182
3183 QString srsName;
3184 bool axisInversion;
3185 if ( ! processSRSName( node, args, args.size() == 5, srsName, axisInversion ) )
3186 {
3187 return QDomElement();
3188 }
3189
3190 mGMLUsed = true;
3191
3192 return ( mGMLVersion == QgsOgcUtils::GML_2_1_2 ) ?
3193 QgsOgcUtils::rectangleToGMLBox( &rect, mDoc, srsName, axisInversion, 15 ) :
3194 QgsOgcUtils::rectangleToGMLEnvelope( &rect, mDoc, srsName, axisInversion, 15 );
3195 }
3196
3197 // ST_GeomFromGML
3198 if ( node->name().compare( "ST_GeomFromGML"_L1, Qt::CaseInsensitive ) == 0 )
3199 {
3200 QList<QgsSQLStatement::Node *> args = node->args()->list();
3201 if ( args.size() != 1 )
3202 {
3203 mErrorMessage = QObject::tr( "Function %1 should have 1 argument" ).arg( node->name() );
3204 return QDomElement();
3205 }
3206
3207 QgsSQLStatement::Node *firstFnArg = args[0];
3208 if ( firstFnArg->nodeType() != QgsSQLStatement::ntLiteral )
3209 {
3210 mErrorMessage = QObject::tr( "%1: Argument must be string literal" ).arg( node->name() );
3211 return QDomElement();
3212 }
3213
3214 QDomDocument geomDoc;
3215 const QString gml = static_cast<const QgsSQLStatement::NodeLiteral *>( firstFnArg )->value().toString();
3216 // wrap the string into a root tag to have "gml" namespace
3217 const QString xml = u"<tmp xmlns:gml=\"%1\">%2</tmp>"_s.arg( GML_NAMESPACE, gml );
3218 if ( !geomDoc.setContent( xml, true ) )
3219 {
3220 mErrorMessage = QObject::tr( "ST_GeomFromGML: unable to parse XML" );
3221 return QDomElement();
3222 }
3223
3224 const QDomNode geomNode = mDoc.importNode( geomDoc.documentElement().firstChildElement(), true );
3225 mGMLUsed = true;
3226 return geomNode.toElement();
3227 }
3228
3229 // Binary geometry operators
3230 QString ogcName( mapBinarySpatialToOgc( node->name() ) );
3231 if ( !ogcName.isEmpty() )
3232 {
3233 QList<QgsSQLStatement::Node *> args = node->args()->list();
3234 if ( args.size() != 2 )
3235 {
3236 mErrorMessage = QObject::tr( "Function %1 should have 2 arguments" ).arg( node->name() );
3237 return QDomElement();
3238 }
3239
3240 for ( int i = 0; i < 2; i ++ )
3241 {
3242 if ( args[i]->nodeType() == QgsSQLStatement::ntFunction &&
3243 ( static_cast<const QgsSQLStatement::NodeFunction *>( args[i] )->name().compare( "ST_GeometryFromText"_L1, Qt::CaseInsensitive ) == 0 ||
3244 static_cast<const QgsSQLStatement::NodeFunction *>( args[i] )->name().compare( "ST_MakeEnvelope"_L1, Qt::CaseInsensitive ) == 0 ) )
3245 {
3246 mCurrentSRSName = getGeometryColumnSRSName( args[1 - i] );
3247 break;
3248 }
3249 }
3250
3251 //if( ogcName == "Intersects" && mFilterVersion == QgsOgcUtils::FILTER_OGC_1_0 )
3252 // ogcName = "Intersect";
3253 QDomElement funcElem = mDoc.createElement( mFilterPrefix + ":" + ogcName );
3254 const auto constArgs = args;
3255 for ( QgsSQLStatement::Node *n : constArgs )
3256 {
3257 const QDomElement childElem = toOgcFilter( n );
3258 if ( !mErrorMessage.isEmpty() )
3259 {
3260 mCurrentSRSName.clear();
3261 return QDomElement();
3262 }
3263
3264 funcElem.appendChild( childElem );
3265 }
3266
3267 mCurrentSRSName.clear();
3268 return funcElem;
3269 }
3270
3271 ogcName = mapTernarySpatialToOgc( node->name() );
3272 if ( !ogcName.isEmpty() )
3273 {
3274 QList<QgsSQLStatement::Node *> args = node->args()->list();
3275 if ( args.size() != 3 )
3276 {
3277 mErrorMessage = QObject::tr( "Function %1 should have 3 arguments" ).arg( node->name() );
3278 return QDomElement();
3279 }
3280
3281 for ( int i = 0; i < 2; i ++ )
3282 {
3283 if ( args[i]->nodeType() == QgsSQLStatement::ntFunction &&
3284 ( static_cast<const QgsSQLStatement::NodeFunction *>( args[i] )->name().compare( "ST_GeometryFromText"_L1, Qt::CaseInsensitive ) == 0 ||
3285 static_cast<const QgsSQLStatement::NodeFunction *>( args[i] )->name().compare( "ST_MakeEnvelope"_L1, Qt::CaseInsensitive ) == 0 ) )
3286 {
3287 mCurrentSRSName = getGeometryColumnSRSName( args[1 - i] );
3288 break;
3289 }
3290 }
3291
3292 QDomElement funcElem = mDoc.createElement( mFilterPrefix + ":" + node->name().mid( 3 ) );
3293 for ( int i = 0; i < 2; i++ )
3294 {
3295 const QDomElement childElem = toOgcFilter( args[i] );
3296 if ( !mErrorMessage.isEmpty() )
3297 {
3298 mCurrentSRSName.clear();
3299 return QDomElement();
3300 }
3301
3302 funcElem.appendChild( childElem );
3303 }
3304 mCurrentSRSName.clear();
3305
3306 QgsSQLStatement::Node *distanceNode = args[2];
3307 if ( distanceNode->nodeType() != QgsSQLStatement::ntLiteral )
3308 {
3309 mErrorMessage = QObject::tr( "Function %1 3rd argument should be a numeric value or a string made of a numeric value followed by a string" ).arg( node->name() );
3310 return QDomElement();
3311 }
3312 const QgsSQLStatement::NodeLiteral *lit = static_cast<const QgsSQLStatement::NodeLiteral *>( distanceNode );
3313 if ( QgsVariantUtils::isNull( lit->value() ) )
3314 {
3315 mErrorMessage = QObject::tr( "Function %1 3rd argument should be a numeric value or a string made of a numeric value followed by a string" ).arg( node->name() );
3316 return QDomElement();
3317 }
3318 QString distance;
3319 QString unit( u"m"_s );
3320 switch ( lit->value().userType() )
3321 {
3322 case QMetaType::Type::Int:
3323 distance = QString::number( lit->value().toInt() );
3324 break;
3325 case QMetaType::Type::LongLong:
3326 distance = QString::number( lit->value().toLongLong() );
3327 break;
3328 case QMetaType::Type::Double:
3329 distance = qgsDoubleToString( lit->value().toDouble() );
3330 break;
3331 case QMetaType::Type::QString:
3332 {
3333 distance = lit->value().toString();
3334 for ( int i = 0; i < distance.size(); i++ )
3335 {
3336 if ( !( ( distance[i] >= '0' && distance[i] <= '9' ) || distance[i] == '-' || distance[i] == '.' || distance[i] == 'e' || distance[i] == 'E' ) )
3337 {
3338 unit = distance.mid( i ).trimmed();
3339 distance = distance.mid( 0, i );
3340 break;
3341 }
3342 }
3343 break;
3344 }
3345
3346 default:
3347 mErrorMessage = QObject::tr( "Literal type not supported: %1" ).arg( static_cast<QMetaType::Type>( lit->value().userType() ) );
3348 return QDomElement();
3349 }
3350
3351 QDomElement distanceElem = mDoc.createElement( mFilterPrefix + ":Distance" );
3352 if ( mFilterVersion == QgsOgcUtils::FILTER_FES_2_0 )
3353 distanceElem.setAttribute( u"uom"_s, unit );
3354 else
3355 distanceElem.setAttribute( u"unit"_s, unit );
3356 distanceElem.appendChild( mDoc.createTextNode( distance ) );
3357 funcElem.appendChild( distanceElem );
3358 return funcElem;
3359 }
3360
3361 // Other function
3362 QDomElement funcElem = mDoc.createElement( mFilterPrefix + ":Function" );
3363 funcElem.setAttribute( u"name"_s, node->name() );
3364 const auto constList = node->args()->list();
3365 for ( QgsSQLStatement::Node *n : constList )
3366 {
3367 const QDomElement childElem = toOgcFilter( n );
3368 if ( !mErrorMessage.isEmpty() )
3369 return QDomElement();
3370
3371 funcElem.appendChild( childElem );
3372 }
3373 return funcElem;
3374}
3375
3377 const QString &leftTable )
3378{
3379 QgsSQLStatement::Node *onExpr = node->onExpr();
3380 if ( onExpr )
3381 {
3382 return toOgcFilter( onExpr );
3383 }
3384
3385 QList<QDomElement> listElem;
3386 const auto constUsingColumns = node->usingColumns();
3387 for ( const QString &columnName : constUsingColumns )
3388 {
3389 QDomElement eqElem = mDoc.createElement( mFilterPrefix + ":PropertyIsEqualTo" );
3390 QDomElement propElem1 = mDoc.createElement( mFilterPrefix + ":" + mPropertyName );
3391 propElem1.appendChild( mDoc.createTextNode( leftTable + "/" + columnName ) );
3392 eqElem.appendChild( propElem1 );
3393 QDomElement propElem2 = mDoc.createElement( mFilterPrefix + ":" + mPropertyName );
3394 propElem2.appendChild( mDoc.createTextNode( node->tableDef()->name() + "/" + columnName ) );
3395 eqElem.appendChild( propElem2 );
3396 listElem.append( eqElem );
3397 }
3398
3399 if ( listElem.size() == 1 )
3400 {
3401 return listElem[0];
3402 }
3403 else if ( listElem.size() > 1 )
3404 {
3405 QDomElement andElem = mDoc.createElement( mFilterPrefix + ":And" );
3406 const auto constListElem = listElem;
3407 for ( const QDomElement &elem : constListElem )
3408 {
3409 andElem.appendChild( elem );
3410 }
3411 return andElem;
3412 }
3413
3414 return QDomElement();
3415}
3416
3417void QgsOgcUtilsSQLStatementToFilter::visit( const QgsSQLStatement::NodeTableDef *node )
3418{
3419 if ( node->alias().isEmpty() )
3420 {
3421 mMapTableAliasToNames[ node->name()] = node->name();
3422 }
3423 else
3424 {
3425 mMapTableAliasToNames[ node->alias()] = node->name();
3426 }
3427}
3428
3430{
3431 QList<QDomElement> listElem;
3432
3433 if ( mFilterVersion != QgsOgcUtils::FILTER_FES_2_0 &&
3434 ( node->tables().size() != 1 || !node->joins().empty() ) )
3435 {
3436 mErrorMessage = QObject::tr( "Joins are only supported with WFS 2.0" );
3437 return QDomElement();
3438 }
3439
3440 // Register all table name aliases
3441 const auto constTables = node->tables();
3442 for ( QgsSQLStatement::NodeTableDef *table : constTables )
3443 {
3444 visit( table );
3445 }
3446 const auto constJoins = node->joins();
3447 for ( QgsSQLStatement::NodeJoin *join : constJoins )
3448 {
3449 visit( join->tableDef() );
3450 }
3451
3452 // Process JOIN conditions
3453 const QList< QgsSQLStatement::NodeTableDef *> nodeTables = node->tables();
3454 QString leftTable = nodeTables.at( nodeTables.length() - 1 )->name();
3455 for ( QgsSQLStatement::NodeJoin *join : constJoins )
3456 {
3457 const QDomElement joinElem = toOgcFilter( join, leftTable );
3458 if ( !mErrorMessage.isEmpty() )
3459 return QDomElement();
3460 listElem.append( joinElem );
3461 leftTable = join->tableDef()->name();
3462 }
3463
3464 // Process WHERE conditions
3465 if ( node->where() )
3466 {
3467 const QDomElement whereElem = toOgcFilter( node->where() );
3468 if ( !mErrorMessage.isEmpty() )
3469 return QDomElement();
3470 listElem.append( whereElem );
3471 }
3472
3473 // Concatenate all conditions
3474 if ( listElem.size() == 1 )
3475 {
3476 return listElem[0];
3477 }
3478 else if ( listElem.size() > 1 )
3479 {
3480 QDomElement andElem = mDoc.createElement( mFilterPrefix + ":And" );
3481 const auto constListElem = listElem;
3482 for ( const QDomElement &elem : constListElem )
3483 {
3484 andElem.appendChild( elem );
3485 }
3486 return andElem;
3487 }
3488
3489 return QDomElement();
3490}
3491
3493 : mLayer( layer )
3494{
3495 mPropertyName = u"PropertyName"_s;
3496 mPrefix = u"ogc"_s;
3497
3498 if ( version == QgsOgcUtils::FILTER_FES_2_0 )
3499 {
3500 mPropertyName = u"ValueReference"_s;
3501 mPrefix = u"fes"_s;
3502 }
3503}
3504
3506{
3507 if ( element.isNull() )
3508 return nullptr;
3509
3510 // check for binary operators
3511 if ( isBinaryOperator( element.tagName() ) )
3512 {
3513 return nodeBinaryOperatorFromOgcFilter( element );
3514 }
3515
3516 // check for spatial operators
3517 if ( isSpatialOperator( element.tagName() ) )
3518 {
3519 return nodeSpatialOperatorFromOgcFilter( element );
3520 }
3521
3522 // check for other OGC operators, convert them to expressions
3523 if ( element.tagName() == "Not"_L1 )
3524 {
3525 return nodeNotFromOgcFilter( element );
3526 }
3527 else if ( element.tagName() == "PropertyIsNull"_L1 )
3528 {
3529 return nodePropertyIsNullFromOgcFilter( element );
3530 }
3531 else if ( element.tagName() == "Literal"_L1 )
3532 {
3533 return nodeLiteralFromOgcFilter( element );
3534 }
3535 else if ( element.tagName() == "Function"_L1 )
3536 {
3537 return nodeFunctionFromOgcFilter( element );
3538 }
3539 else if ( element.tagName() == mPropertyName )
3540 {
3541 return nodeColumnRefFromOgcFilter( element );
3542 }
3543 else if ( element.tagName() == "PropertyIsBetween"_L1 )
3544 {
3545 return nodeIsBetweenFromOgcFilter( element );
3546 }
3547
3548 mErrorMessage += QObject::tr( "unable to convert '%1' element to a valid expression: it is not supported yet or it has invalid arguments" ).arg( element.tagName() );
3549 return nullptr;
3550}
3551
3553{
3554 if ( element.isNull() )
3555 return nullptr;
3556
3557 int op = binaryOperatorFromTagName( element.tagName() );
3558 if ( op < 0 )
3559 {
3560 mErrorMessage = QObject::tr( "'%1' binary operator not supported." ).arg( element.tagName() );
3561 return nullptr;
3562 }
3563
3564 if ( op == QgsExpressionNodeBinaryOperator::boLike && element.hasAttribute( u"matchCase"_s ) && element.attribute( u"matchCase"_s ) == "false"_L1 )
3565 {
3567 }
3568
3569 QDomElement operandElem = element.firstChildElement();
3570 std::unique_ptr<QgsExpressionNode> expr( nodeFromOgcFilter( operandElem ) );
3571
3572 if ( !expr )
3573 {
3574 mErrorMessage = QObject::tr( "invalid left operand for '%1' binary operator" ).arg( element.tagName() );
3575 return nullptr;
3576 }
3577
3578 const std::unique_ptr<QgsExpressionNode> leftOp( expr->clone() );
3579 for ( operandElem = operandElem.nextSiblingElement(); !operandElem.isNull(); operandElem = operandElem.nextSiblingElement() )
3580 {
3581 std::unique_ptr<QgsExpressionNode> opRight( nodeFromOgcFilter( operandElem ) );
3582 if ( !opRight )
3583 {
3584 mErrorMessage = QObject::tr( "invalid right operand for '%1' binary operator" ).arg( element.tagName() );
3585 return nullptr;
3586 }
3587
3589 {
3590 QString wildCard;
3591 if ( element.hasAttribute( u"wildCard"_s ) )
3592 {
3593 wildCard = element.attribute( u"wildCard"_s );
3594 }
3595 QString singleChar;
3596 if ( element.hasAttribute( u"singleChar"_s ) )
3597 {
3598 singleChar = element.attribute( u"singleChar"_s );
3599 }
3600 QString escape = u"\\"_s;
3601 if ( element.hasAttribute( u"escape"_s ) )
3602 {
3603 escape = element.attribute( u"escape"_s );
3604 }
3605 if ( element.hasAttribute( u"escapeChar"_s ) )
3606 {
3607 escape = element.attribute( u"escapeChar"_s );
3608 }
3609 // replace
3610 QString oprValue = static_cast<const QgsExpressionNodeLiteral *>( opRight.get() )->value().toString();
3611 if ( !wildCard.isEmpty() && wildCard != "%"_L1 )
3612 {
3613 oprValue.replace( '%', "\\%"_L1 );
3614 if ( oprValue.startsWith( wildCard ) )
3615 {
3616 oprValue.replace( 0, 1, u"%"_s );
3617 }
3618 const QRegularExpression rx( "[^" + QgsStringUtils::qRegExpEscape( escape ) + "](" + QgsStringUtils::qRegExpEscape( wildCard ) + ")" );
3619 QRegularExpressionMatch match = rx.match( oprValue );
3620 int pos;
3621 while ( match.hasMatch() )
3622 {
3623 pos = match.capturedStart();
3624 oprValue.replace( pos + 1, 1, u"%"_s );
3625 pos += 1;
3626 match = rx.match( oprValue, pos );
3627 }
3628 oprValue.replace( escape + wildCard, wildCard );
3629 }
3630 if ( !singleChar.isEmpty() && singleChar != "_"_L1 )
3631 {
3632 oprValue.replace( '_', "\\_"_L1 );
3633 if ( oprValue.startsWith( singleChar ) )
3634 {
3635 oprValue.replace( 0, 1, u"_"_s );
3636 }
3637 const QRegularExpression rx( "[^" + QgsStringUtils::qRegExpEscape( escape ) + "](" + QgsStringUtils::qRegExpEscape( singleChar ) + ")" );
3638 QRegularExpressionMatch match = rx.match( oprValue );
3639 int pos;
3640 while ( match.hasMatch() )
3641 {
3642 pos = match.capturedStart();
3643 oprValue.replace( pos + 1, 1, u"_"_s );
3644 pos += 1;
3645 match = rx.match( oprValue, pos );
3646 }
3647 oprValue.replace( escape + singleChar, singleChar );
3648 }
3649 if ( !escape.isEmpty() && escape != "\\"_L1 )
3650 {
3651 oprValue.replace( escape + escape, escape );
3652 }
3653 opRight = std::make_unique<QgsExpressionNodeLiteral>( oprValue );
3654 }
3655
3656 expr = std::make_unique<QgsExpressionNodeBinaryOperator>( static_cast< QgsExpressionNodeBinaryOperator::BinaryOperator >( op ), expr.release(), opRight.release() );
3657 }
3658
3659 if ( expr == leftOp )
3660 {
3661 mErrorMessage = QObject::tr( "only one operand for '%1' binary operator" ).arg( element.tagName() );
3662 return nullptr;
3663 }
3664
3665 return dynamic_cast< QgsExpressionNodeBinaryOperator * >( expr.release() );
3666}
3667
3668
3670{
3671 // we are exploiting the fact that our function names are the same as the XML tag names
3672 const int opIdx = QgsExpression::functionIndex( element.tagName().toLower() );
3673
3674 auto gml2Args = std::make_unique<QgsExpressionNode::NodeList>();
3675 QDomElement childElem = element.firstChildElement();
3676 QString gml2Str;
3677 while ( !childElem.isNull() && gml2Str.isEmpty() )
3678 {
3679 if ( childElem.tagName() != mPropertyName )
3680 {
3681 QTextStream gml2Stream( &gml2Str );
3682 childElem.save( gml2Stream, 0 );
3683 }
3684 childElem = childElem.nextSiblingElement();
3685 }
3686 if ( !gml2Str.isEmpty() )
3687 {
3688 gml2Args->append( new QgsExpressionNodeLiteral( QVariant( gml2Str.remove( '\n' ) ) ) );
3689 }
3690 else
3691 {
3692 mErrorMessage = QObject::tr( "No OGC Geometry found" );
3693 return nullptr;
3694 }
3695
3696 auto opArgs = std::make_unique<QgsExpressionNode::NodeList>();
3697 opArgs->append( new QgsExpressionNodeFunction( QgsExpression::functionIndex( u"$geometry"_s ), new QgsExpressionNode::NodeList() ) );
3698 opArgs->append( new QgsExpressionNodeFunction( QgsExpression::functionIndex( u"geomFromGML"_s ), gml2Args.release() ) );
3699
3700 return new QgsExpressionNodeFunction( opIdx, opArgs.release() );
3701}
3702
3704{
3705 if ( element.isNull() || element.tagName() != mPropertyName )
3706 {
3707 mErrorMessage = QObject::tr( "%1:PropertyName expected, got %2" ).arg( mPrefix, element.tagName() );
3708 return nullptr;
3709 }
3710
3711 return new QgsExpressionNodeColumnRef( element.firstChild().nodeValue() );
3712}
3713
3715{
3716 if ( element.isNull() || element.tagName() != "Literal"_L1 )
3717 {
3718 mErrorMessage = QObject::tr( "%1:Literal expected, got %2" ).arg( mPrefix, element.tagName() );
3719 return nullptr;
3720 }
3721
3722 std::unique_ptr<QgsExpressionNode> root;
3723 if ( !element.hasChildNodes() )
3724 {
3725 root = std::make_unique<QgsExpressionNodeLiteral>( QVariant( "" ) );
3726 return root.release();
3727 }
3728
3729 // the literal content can have more children (e.g. CDATA section, text, ...)
3730 QDomNode childNode = element.firstChild();
3731 while ( !childNode.isNull() )
3732 {
3733 std::unique_ptr<QgsExpressionNode> operand;
3734
3735 if ( childNode.nodeType() == QDomNode::ElementNode )
3736 {
3737 // found a element node (e.g. PropertyName), convert it
3738 const QDomElement operandElem = childNode.toElement();
3739 operand.reset( nodeFromOgcFilter( operandElem ) );
3740 if ( !operand )
3741 {
3742 mErrorMessage = QObject::tr( "'%1' is an invalid or not supported content for %2:Literal" ).arg( operandElem.tagName(), mPrefix );
3743 return nullptr;
3744 }
3745 }
3746 else
3747 {
3748 // probably a text/CDATA node
3749 QVariant value = childNode.nodeValue();
3750
3751 bool converted = false;
3752
3753 // try to convert the node content to corresponding field type if possible
3754 if ( mLayer )
3755 {
3756 QDomElement propertyNameElement = element.previousSiblingElement( mPropertyName );
3757 if ( propertyNameElement.isNull() || propertyNameElement.tagName() != mPropertyName )
3758 {
3759 propertyNameElement = element.nextSiblingElement( mPropertyName );
3760 }
3761 if ( !propertyNameElement.isNull() || propertyNameElement.tagName() == mPropertyName )
3762 {
3763 const int fieldIndex = mLayer->fields().indexOf( propertyNameElement.firstChild().nodeValue() );
3764 if ( fieldIndex != -1 )
3765 {
3766 const QgsField field = mLayer->fields().field( propertyNameElement.firstChild().nodeValue() );
3767 field.convertCompatible( value );
3768 converted = true;
3769 }
3770 }
3771 }
3772 if ( !converted )
3773 {
3774 // try to convert the node content to number if possible,
3775 // otherwise let's use it as string
3776 bool ok;
3777 const double d = value.toDouble( &ok );
3778 if ( ok )
3779 value = d;
3780 }
3781
3782 operand = std::make_unique<QgsExpressionNodeLiteral>( value );
3783 }
3784
3785 // use the concat operator to merge the ogc:Literal children
3786 if ( !root )
3787 {
3788 root = std::move( operand );
3789 }
3790 else
3791 {
3792 root = std::make_unique<QgsExpressionNodeBinaryOperator>( QgsExpressionNodeBinaryOperator::boConcat, root.release(), operand.release() );
3793 }
3794
3795 childNode = childNode.nextSibling();
3796 }
3797
3798 if ( root )
3799 return root.release();
3800
3801 return nullptr;
3802}
3803
3805{
3806 if ( element.tagName() != "Not"_L1 )
3807 return nullptr;
3808
3809 const QDomElement operandElem = element.firstChildElement();
3810 std::unique_ptr<QgsExpressionNode> operand( nodeFromOgcFilter( operandElem ) );
3811 if ( !operand )
3812 {
3813 mErrorMessage = QObject::tr( "invalid operand for '%1' unary operator" ).arg( element.tagName() );
3814 return nullptr;
3815 }
3816
3818}
3819
3821{
3822 // convert ogc:PropertyIsNull to IS operator with NULL right operand
3823 if ( element.tagName() != "PropertyIsNull"_L1 )
3824 {
3825 return nullptr;
3826 }
3827
3828 const QDomElement operandElem = element.firstChildElement();
3829 std::unique_ptr<QgsExpressionNode> opLeft( nodeFromOgcFilter( operandElem ) );
3830 if ( !opLeft )
3831 return nullptr;
3832
3833 std::unique_ptr<QgsExpressionNode> opRight( new QgsExpressionNodeLiteral( QVariant() ) );
3834 return new QgsExpressionNodeBinaryOperator( QgsExpressionNodeBinaryOperator::boIs, opLeft.release(), opRight.release() );
3835}
3836
3838{
3839 if ( element.isNull() || element.tagName() != "Function"_L1 )
3840 {
3841 mErrorMessage = QObject::tr( "%1:Function expected, got %2" ).arg( mPrefix, element.tagName() );
3842 return nullptr;
3843 }
3844
3845 for ( int i = 0; i < QgsExpression::Functions().size(); i++ )
3846 {
3847 const QgsExpressionFunction *funcDef = QgsExpression::Functions()[i];
3848
3849 if ( element.attribute( u"name"_s ) != funcDef->name() )
3850 continue;
3851
3852 auto args = std::make_unique<QgsExpressionNode::NodeList>();
3853
3854 QDomElement operandElem = element.firstChildElement();
3855 while ( !operandElem.isNull() )
3856 {
3857 std::unique_ptr<QgsExpressionNode> op( nodeFromOgcFilter( operandElem ) );
3858 if ( !op )
3859 {
3860 return nullptr;
3861 }
3862 args->append( op.release() );
3863
3864 operandElem = operandElem.nextSiblingElement();
3865 }
3866
3867 return new QgsExpressionNodeFunction( i, args.release() );
3868 }
3869
3870 return nullptr;
3871}
3872
3874{
3875 // <ogc:PropertyIsBetween> encode a Range check
3876 std::unique_ptr<QgsExpressionNode> operand;
3877 std::unique_ptr<QgsExpressionNode> lowerBound;
3878 std::unique_ptr<QgsExpressionNode> upperBound;
3879
3880 QDomElement operandElem = element.firstChildElement();
3881 while ( !operandElem.isNull() )
3882 {
3883 if ( operandElem.tagName() == "LowerBoundary"_L1 )
3884 {
3885 const QDomElement lowerBoundElem = operandElem.firstChildElement();
3886 lowerBound.reset( nodeFromOgcFilter( lowerBoundElem ) );
3887 }
3888 else if ( operandElem.tagName() == "UpperBoundary"_L1 )
3889 {
3890 const QDomElement upperBoundElem = operandElem.firstChildElement();
3891 upperBound.reset( nodeFromOgcFilter( upperBoundElem ) );
3892 }
3893 else
3894 {
3895 // <ogc:expression>
3896 operand.reset( nodeFromOgcFilter( operandElem ) );
3897 }
3898
3899 if ( operand && lowerBound && upperBound )
3900 break;
3901
3902 operandElem = operandElem.nextSiblingElement();
3903 }
3904
3905 if ( !operand || !lowerBound || !upperBound )
3906 {
3907 mErrorMessage = QObject::tr( "missing some required sub-elements in %1:PropertyIsBetween" ).arg( mPrefix );
3908 return nullptr;
3909 }
3910
3911 std::unique_ptr<QgsExpressionNode> leOperator( new QgsExpressionNodeBinaryOperator( QgsExpressionNodeBinaryOperator::boLE, operand->clone(), upperBound.release() ) );
3912 std::unique_ptr<QgsExpressionNode> geOperator( new QgsExpressionNodeBinaryOperator( QgsExpressionNodeBinaryOperator::boGE, operand.release(), lowerBound.release() ) );
3913 return new QgsExpressionNodeBinaryOperator( QgsExpressionNodeBinaryOperator::boAnd, geOperator.release(), leOperator.release() );
3914}
3915
3917{
3918 return mErrorMessage;
3919}
3920
3921QgsOgcCrsUtils::CRSFlavor QgsOgcCrsUtils::parseCrsName( const QString &crsName, QString &authority, QString &code )
3922{
3923 const thread_local QRegularExpression re_url( QRegularExpression::anchoredPattern( u"http://www\\.opengis\\.net/gml/srs/epsg\\.xml#(.+)"_s ), QRegularExpression::CaseInsensitiveOption );
3924 if ( const QRegularExpressionMatch match = re_url.match( crsName ); match.hasMatch() )
3925 {
3926 authority = u"EPSG"_s;
3927 code = match.captured( 1 );
3929 }
3930
3931 const thread_local QRegularExpression re_ogc_urn( QRegularExpression::anchoredPattern( u"urn:ogc:def:crs:([^:]+).+(?<=:)([^:]+)"_s ), QRegularExpression::CaseInsensitiveOption );
3932 if ( const QRegularExpressionMatch match = re_ogc_urn.match( crsName ); match.hasMatch() )
3933 {
3934 authority = match.captured( 1 );
3935 code = match.captured( 2 );
3936 return CRSFlavor::OGC_URN;
3937 }
3938
3939 const thread_local QRegularExpression re_x_ogc_urn( QRegularExpression::anchoredPattern( u"urn:x-ogc:def:crs:([^:]+).+(?<=:)([^:]+)"_s ), QRegularExpression::CaseInsensitiveOption );
3940 if ( const QRegularExpressionMatch match = re_x_ogc_urn.match( crsName ); match.hasMatch() )
3941 {
3942 authority = match.captured( 1 );
3943 code = match.captured( 2 );
3944 return CRSFlavor::X_OGC_URN;
3945 }
3946
3947 const thread_local QRegularExpression re_http_uri( QRegularExpression::anchoredPattern( u"http://www\\.opengis\\.net/def/crs/([^/]+).+/([^/]+)"_s ), QRegularExpression::CaseInsensitiveOption );
3948 if ( const QRegularExpressionMatch match = re_http_uri.match( crsName ); match.hasMatch() )
3949 {
3950 authority = match.captured( 1 );
3951 code = match.captured( 2 );
3953 }
3954
3955 const thread_local QRegularExpression re_auth_code( QRegularExpression::anchoredPattern( u"([^:]+):(.+)"_s ), QRegularExpression::CaseInsensitiveOption );
3956 if ( const QRegularExpressionMatch match = re_auth_code.match( crsName ); match.hasMatch() )
3957 {
3958 authority = match.captured( 1 );
3959 code = match.captured( 2 );
3960 return CRSFlavor::AUTH_CODE;
3961 }
3962
3963 return CRSFlavor::UNKNOWN;
3964}
3965
3966QgsGeometry QgsOgcUtils::geometryFromGMLUsingGdal( const QDomElement &geometryElement )
3967{
3968 QString gml;
3969 QTextStream gmlStream( &gml );
3970 geometryElement.save( gmlStream, 0 );
3971 gdal::ogr_geometry_unique_ptr ogrGeom { OGR_G_CreateFromGML( gml.toUtf8().constData() ) };
3972 return QgsOgrUtils::ogrGeometryToQgsGeometry( ogrGeom.get() );
3973}
3974
3975QgsGeometry QgsOgcUtils::geometryFromGMLMultiCurve( const QDomElement &geometryElement )
3976{
3977 return geometryFromGMLUsingGdal( geometryElement );
3978}
GeometryOperationResult
Success or failure of a geometry operation.
Definition qgis.h:2088
@ Success
Operation succeeded.
Definition qgis.h:2089
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:280
@ LineString25D
LineString25D.
Definition qgis.h:348
@ MultiPointZ
MultiPointZ.
Definition qgis.h:303
@ Point
Point.
Definition qgis.h:282
@ LineString
LineString.
Definition qgis.h:283
@ MultiPolygon25D
MultiPolygon25D.
Definition qgis.h:352
@ MultiPoint
MultiPoint.
Definition qgis.h:286
@ Polygon
Polygon.
Definition qgis.h:284
@ MultiLineString25D
MultiLineString25D.
Definition qgis.h:351
@ MultiPolygon
MultiPolygon.
Definition qgis.h:288
@ MultiLineString
MultiLineString.
Definition qgis.h:287
@ MultiPoint25D
MultiPoint25D.
Definition qgis.h:350
@ Unknown
Unknown.
Definition qgis.h:281
@ PointZ
PointZ.
Definition qgis.h:299
@ MultiLineStringZ
MultiLineStringZ.
Definition qgis.h:304
@ MultiPolygonZ
MultiPolygonZ.
Definition qgis.h:305
@ Point25D
Point25D.
Definition qgis.h:347
@ LineStringZ
LineStringZ.
Definition qgis.h:300
@ PolygonZ
PolygonZ.
Definition qgis.h:301
@ Polygon25D
Polygon25D.
Definition qgis.h:349
virtual void swapXy()=0
Swaps the x and y coordinates from the geometry.
A const WKB pointer.
Definition qgswkbptr.h:139
Qgis::WkbType readHeader() const
readHeader
Definition qgswkbptr.cpp:56
Represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
bool createFromUserInput(const QString &definition)
Set up this CRS from various text formats.
bool hasAxisInverted() const
Returns whether the axis order is inverted for the CRS compared to the order east/north (longitude/la...
Handles coordinate transforms between two coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
An abstract base class for defining QgsExpression functions.
int params() const
The number of parameters this function takes.
virtual bool isStatic(const QgsExpressionNodeFunction *node, QgsExpression *parent, const QgsExpressionContext *context) const
Will be called during prepare to determine if the function is static.
QString name() const
The name of the function.
virtual QVariant run(QgsExpressionNode::NodeList *args, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction *node)
Evaluates the function, first evaluating all required arguments before passing them to the function's...
A binary expression operator, which operates on two values.
QgsExpressionNode * opLeft() const
Returns the node to the left of the operator.
QgsExpressionNode * opRight() const
Returns the node to the right of the operator.
QgsExpressionNodeBinaryOperator::BinaryOperator op() const
Returns the binary operator.
QString text() const
Returns a the name of this operator without the operands.
An expression node which takes its value from a feature's field.
QString name() const
The name of the column.
An expression node for expression functions.
int fnIndex() const
Returns the index of the node's function.
QgsExpressionNode::NodeList * args() const
Returns a list of arguments specified for the function.
QSet< QString > referencedVariables() const override
Returns a set of all variables which are used in this expression.
An expression node for value IN or NOT IN clauses.
QgsExpressionNode * node() const
Returns the expression node.
QgsExpressionNode::NodeList * list() const
Returns the list of nodes to search for matching values within.
bool isNotIn() const
Returns true if this node is a "NOT IN" operator, or false if the node is a normal "IN" operator.
An expression node for literal values.
QVariant value() const
The value of the literal.
A unary node is either negative as in boolean (not) or as in numbers (minus).
QgsExpressionNodeUnaryOperator::UnaryOperator op() const
Returns the unary operator.
QString text() const
Returns a the name of this operator without the operands.
QgsExpressionNode * operand() const
Returns the node the operator will operate upon.
A list of expression nodes.
QList< QgsExpressionNode * > list()
Gets a list of all the nodes.
Abstract base class for all nodes that can appear in an expression.
bool hasCachedStaticValue() const
Returns true if the node can be replaced by a static cached value.
virtual QgsExpressionNode::NodeType nodeType() const =0
Gets the type of this node.
QVariant cachedStaticValue() const
Returns the node's static cached value.
Handles parsing and evaluation of expressions (formerly called "search strings").
static const QList< QgsExpressionFunction * > & Functions()
void setExpression(const QString &expression)
Set the expression string, will reset the whole internal structure.
static int functionIndex(const QString &name)
Returns index of the function in Functions array.
QString dump() const
Returns an expression string, constructed from the internal abstract syntax tree.
const QgsExpressionNode * rootNode() const
Returns the root node of the expression.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:54
bool convertCompatible(QVariant &v, QString *errorMessage=nullptr) const
Converts the provided variant to a compatible format.
Definition qgsfield.cpp:475
A geometry is the spatial representation of a feature.
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
static Q_INVOKABLE QgsGeometry fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
void fromWkb(unsigned char *wkb, int length)
Set the geometry, feeding in the buffer containing OGC Well-Known Binary and the buffer's length.
QByteArray asWkb(QgsAbstractGeometry::WkbFlags flags=QgsAbstractGeometry::WkbFlags()) const
Export the geometry to WKB.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Qgis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.).
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:87
CRSFlavor
CRS flavor.
@ HTTP_EPSG_DOT_XML
E.g. http://www.opengis.net/gml/srs/epsg.xml#4326 (called "OGC HTTP URL" in GeoServer WFS configurati...
@ OGC_HTTP_URI
E.g. http://www.opengis.net/def/crs/EPSG/0/4326.
@ X_OGC_URN
E.g. urn:x-ogc:def:crs:EPSG::4326.
@ UNKNOWN
Unknown/unhandled flavor.
@ OGC_URN
E.g. urn:ogc:def:crs:EPSG::4326.
@ AUTH_CODE
E.g EPSG:4326.
static CRSFlavor parseCrsName(const QString &crsName, QString &authority, QString &code)
Parse a CRS name in one of the flavors of OGC services, and decompose it as authority and code.
Internal use by QgsOgcUtils.
QgsOgcUtilsExprToFilter(QDomDocument &doc, QgsOgcUtils::GMLVersion gmlVersion, QgsOgcUtils::FilterVersion filterVersion, const QString &namespacePrefix, const QString &namespaceURI, const QString &geometryName, const QString &srsName, bool honourAxisOrientation, bool invertAxisOrientation, const QMap< QString, QString > &fieldNameToXPathMap, const QMap< QString, QString > &namespacePrefixToUriMap)
Constructor.
bool GMLNamespaceUsed() const
Returns whether the gml: namespace is used.
QDomElement expressionNodeToOgcFilter(const QgsExpressionNode *node, QgsExpression *expression, const QgsExpressionContext *context)
Convert an expression to a OGC filter.
QString errorMessage() const
Returns the error message.
Internal use by QgsOgcUtils.
QgsExpressionNodeFunction * nodeSpatialOperatorFromOgcFilter(const QDomElement &element)
Returns an expression node from a WFS filter embedded in a document with spatial operators.
QgsExpressionNodeUnaryOperator * nodeNotFromOgcFilter(const QDomElement &element)
Returns an expression node from a WFS filter embedded in a document with Not operator.
QgsExpressionNodeColumnRef * nodeColumnRefFromOgcFilter(const QDomElement &element)
Returns an expression node from a WFS filter embedded in a document with column references.
QgsExpressionNode * nodeIsBetweenFromOgcFilter(const QDomElement &element)
Returns an expression node from a WFS filter embedded in a document with boundaries operator.
QgsOgcUtilsExpressionFromFilter(QgsOgcUtils::FilterVersion version=QgsOgcUtils::FILTER_OGC_1_0, const QgsVectorLayer *layer=nullptr)
Constructor for QgsOgcUtilsExpressionFromFilter.
QgsExpressionNodeBinaryOperator * nodeBinaryOperatorFromOgcFilter(const QDomElement &element)
Returns an expression node from a WFS filter embedded in a document with binary operators.
QgsExpressionNodeFunction * nodeFunctionFromOgcFilter(const QDomElement &element)
Returns an expression node from a WFS filter embedded in a document with functions.
QgsExpressionNode * nodeFromOgcFilter(const QDomElement &element)
Returns an expression node from a WFS filter embedded in a document element.
QgsExpressionNodeBinaryOperator * nodePropertyIsNullFromOgcFilter(const QDomElement &element)
Returns an expression node from a WFS filter embedded in a document with IsNull operator.
QString errorMessage() const
Returns the underlying error message, or an empty string in case of no error.
QgsExpressionNode * nodeLiteralFromOgcFilter(const QDomElement &element)
Returns an expression node from a WFS filter embedded in a document with literal tag.
Internal use by QgsOgcUtils.
QgsOgcUtilsSQLStatementToFilter(QDomDocument &doc, QgsOgcUtils::GMLVersion gmlVersion, QgsOgcUtils::FilterVersion filterVersion, const QList< QgsOgcUtils::LayerProperties > &layerProperties, bool honourAxisOrientation, bool invertAxisOrientation, const QMap< QString, QString > &mapUnprefixedTypenameToPrefixedTypename, const QMap< QString, QString > &fieldNameToXPathMap, const QMap< QString, QString > &namespacePrefixToUriMap)
Constructor.
QDomElement toOgcFilter(const QgsSQLStatement::Node *node)
Convert a SQL statement to a OGC filter.
bool GMLNamespaceUsed() const
Returns whether the gml: namespace is used.
QString errorMessage() const
Returns the error message.
Provides various utility functions for conversion between OGC (Open Geospatial Consortium) standards ...
Definition qgsogcutils.h:56
static QDomElement elseFilterExpression(QDomDocument &doc)
Creates an ElseFilter from doc.
static QgsRectangle rectangleFromGMLBox(const QDomNode &boxNode)
Read rectangle from GML2 Box.
static QDomElement expressionToOgcExpression(const QgsExpression &exp, QDomDocument &doc, QString *errorMessage=nullptr, bool requiresFilterElement=false)
Creates an OGC expression XML element from the exp expression with default values for the geometry na...
static QColor colorFromOgcFill(const QDomElement &fillElement)
Parse XML with OGC fill into QColor.
static QDomElement expressionToOgcFilter(const QgsExpression &exp, QDomDocument &doc, QString *errorMessage=nullptr)
Creates OGC filter XML element.
GMLVersion
GML version.
Definition qgsogcutils.h:82
FilterVersion
OGC filter version.
static QDomElement SQLStatementToOgcFilter(const QgsSQLStatement &statement, QDomDocument &doc, QgsOgcUtils::GMLVersion gmlVersion, FilterVersion filterVersion, const QList< LayerProperties > &layerProperties, bool honourAxisOrientation, bool invertAxisOrientation, const QMap< QString, QString > &mapUnprefixedTypenameToPrefixedTypename, QString *errorMessage=nullptr, const QMap< QString, QString > &fieldNameToXPathMap=QMap< QString, QString >(), const QMap< QString, QString > &namespacePrefixToUriMap=QMap< QString, QString >())
Creates OGC filter XML element from the WHERE and JOIN clauses of a SQL statement.
static QDomElement rectangleToGMLEnvelope(const QgsRectangle *env, QDomDocument &doc, int precision=17)
Exports the rectangle to GML3 Envelope.
static QgsRectangle rectangleFromGMLEnvelope(const QDomNode &envelopeNode)
Read rectangle from GML3 Envelope.
static QDomElement geometryToGML(const QgsGeometry &geometry, QDomDocument &doc, QgsOgcUtils::GMLVersion gmlVersion, const QString &srsName, bool invertAxisOrientation, const QString &gmlIdBase, int precision=17)
Exports the geometry to GML.
static QgsGeometry geometryFromGML(const QString &xmlString, const QgsOgcUtils::Context &context=QgsOgcUtils::Context())
Static method that creates geometry from GML.
static Qgis::WkbType geomTypeFromPropertyType(const QString &gmlGeomType)
Returns the Qgis::WkbType corresponding to a GML geometry type.
static QgsExpression * expressionFromOgcFilter(const QDomElement &element, QgsVectorLayer *layer=nullptr)
Parse XML with OGC filter into QGIS expression.
static QDomElement rectangleToGMLBox(const QgsRectangle *box, QDomDocument &doc, int precision=17)
Exports the rectangle to GML2 Box.
static QgsGeometry ogrGeometryToQgsGeometry(OGRGeometryH geom)
Converts an OGR geometry representation to a QgsGeometry object.
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
void setYMinimum(double y)
Set the minimum y value.
void setXMinimum(double x)
Set the minimum x value.
void setYMaximum(double y)
Set the maximum y value.
void setXMaximum(double x)
Set the maximum x value.
double yMaximum
void normalize()
Normalize the rectangle so it has non-negative width/height.
An 'X BETWEEN y and z' operator.
QgsSQLStatement::Node * node() const
Variable at the left of BETWEEN.
QgsSQLStatement::Node * minVal() const
Minimum bound.
bool isNotBetween() const
Whether this is a NOT BETWEEN operator.
QgsSQLStatement::Node * maxVal() const
Maximum bound.
Binary logical/arithmetical operator (AND, OR, =, +, ...).
QgsSQLStatement::Node * opLeft() const
Left operand.
QgsSQLStatement::BinaryOperator op() const
Operator.
QgsSQLStatement::Node * opRight() const
Right operand.
QString name() const
The name of the column.
QString tableName() const
The name of the table. May be empty.
Function with a name and arguments node.
QgsSQLStatement::NodeList * args() const
Returns arguments.
QString name() const
Returns function name.
An 'x IN (y, z)' operator.
bool isNotIn() const
Whether this is a NOT IN operator.
QgsSQLStatement::Node * node() const
Variable at the left of IN.
QgsSQLStatement::NodeList * list() const
Values list.
QgsSQLStatement::NodeTableDef * tableDef() const
Table definition.
QgsSQLStatement::Node * onExpr() const
On expression. Will be nullptr if usingColumns() is not empty.
QList< QString > usingColumns() const
Columns referenced by USING.
QList< QgsSQLStatement::Node * > list()
Returns list.
Literal value (integer, integer64, double, string).
QVariant value() const
The value of the literal.
QList< QgsSQLStatement::NodeJoin * > joins() const
Returns the list of joins.
QgsSQLStatement::Node * where() const
Returns the where clause.
QList< QgsSQLStatement::NodeTableDef * > tables() const
Returns the list of tables.
QString name() const
Table name.
QString alias() const
Table alias.
Unary logical/arithmetical operator ( NOT, - ).
QgsSQLStatement::UnaryOperator op() const
Operator.
QgsSQLStatement::Node * operand() const
Operand.
Abstract node class for SQL statement nodes.
virtual QgsSQLStatement::NodeType nodeType() const =0
Abstract virtual that returns the type of this node.
Parses SQL statements.
BinaryOperator
list of binary operators
static const char * BINARY_OPERATOR_TEXT[]
const QgsSQLStatement::Node * rootNode() const
Returns the root node of the statement.
static const char * UNARY_OPERATOR_TEXT[]
static QString qRegExpEscape(const QString &string)
Returns an escaped string matching the behavior of QRegExp::escape.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based dataset.
Custom exception class for Wkb related exceptions.
Definition qgswkbptr.h:32
std::unique_ptr< std::remove_pointer< OGRGeometryH >::type, OGRGeometryDeleter > ogr_geometry_unique_ptr
Scoped OGR geometry.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:6805
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7079
QMap< QString, QString > QgsStringMap
Definition qgis.h:7401
QVector< QgsPointXY > QgsPolylineXY
Polyline as represented as a vector of two-dimensional points.
Definition qgsgeometry.h:61
QVector< QgsPolyline > QgsMultiPolyline
Multi polyline represented as a vector of polylines.
Definition qgsgeometry.h:83
QgsPointSequence QgsPolyline
Polyline as represented as a vector of points.
Definition qgsgeometry.h:70
#define GML_NAMESPACE
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define GML32_NAMESPACE
#define SE_NAMESPACE
#define FES_NAMESPACE
#define OGC_NAMESPACE
Q_GLOBAL_STATIC_WITH_ARGS(IntMap, BINARY_OPERATORS_TAG_NAMES_MAP,({ { "Or"_L1, QgsExpressionNodeBinaryOperator::boOr }, { "And"_L1, QgsExpressionNodeBinaryOperator::boAnd }, { "PropertyIsEqualTo"_L1, QgsExpressionNodeBinaryOperator::boEQ }, { "PropertyIsNotEqualTo"_L1, QgsExpressionNodeBinaryOperator::boNE }, { "PropertyIsLessThanOrEqualTo"_L1, QgsExpressionNodeBinaryOperator::boLE }, { "PropertyIsGreaterThanOrEqualTo"_L1, QgsExpressionNodeBinaryOperator::boGE }, { "PropertyIsLessThan"_L1, QgsExpressionNodeBinaryOperator::boLT }, { "PropertyIsGreaterThan"_L1, QgsExpressionNodeBinaryOperator::boGT }, { "PropertyIsLike"_L1, QgsExpressionNodeBinaryOperator::boLike }, { "Add"_L1, QgsExpressionNodeBinaryOperator::boPlus }, { "Sub"_L1, QgsExpressionNodeBinaryOperator::boMinus }, { "Mul"_L1, QgsExpressionNodeBinaryOperator::boMul }, { "Div"_L1, QgsExpressionNodeBinaryOperator::boDiv }, })) static int binaryOperatorFromTagName(const QString &tagName)
QMap< QString, int > IntMap
The Context struct stores the current layer and coordinate transform context.
Definition qgsogcutils.h:64
const QgsMapLayer * layer
Definition qgsogcutils.h:74
QgsCoordinateTransformContext transformContext
Definition qgsogcutils.h:75