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