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