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