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