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