QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 ( name.size() > 3 && name.midRef( 0, 3 ).compare( QLatin1String( "ST_" ), Qt::CaseInsensitive ) == 0 )
2684  nameCompare = name.mid( 3 );
2685  QStringList spatialOps;
2686  spatialOps << QStringLiteral( "BBOX" ) << QStringLiteral( "Intersects" ) << QStringLiteral( "Contains" ) << QStringLiteral( "Crosses" ) << QStringLiteral( "Equals" )
2687  << QStringLiteral( "Disjoint" ) << QStringLiteral( "Overlaps" ) << QStringLiteral( "Touches" ) << QStringLiteral( "Within" );
2688  const auto constSpatialOps = spatialOps;
2689  for ( QString op : constSpatialOps )
2690  {
2691  if ( nameCompare.compare( op, Qt::CaseInsensitive ) == 0 )
2692  return op;
2693  }
2694  return QString();
2695 }
2696 
2697 static QString mapTernarySpatialToOgc( const QString &name )
2698 {
2699  QString nameCompare( name );
2700  if ( name.size() > 3 && name.midRef( 0, 3 ).compare( QLatin1String( "ST_" ), Qt::CaseInsensitive ) == 0 )
2701  nameCompare = name.mid( 3 );
2702  if ( nameCompare.compare( QLatin1String( "DWithin" ), Qt::CaseInsensitive ) == 0 )
2703  return QStringLiteral( "DWithin" );
2704  if ( nameCompare.compare( QLatin1String( "Beyond" ), Qt::CaseInsensitive ) == 0 )
2705  return QStringLiteral( "Beyond" );
2706  return QString();
2707 }
2708 
2709 QString QgsOgcUtilsSQLStatementToFilter::getGeometryColumnSRSName( const QgsSQLStatement::Node *node )
2710 {
2711  if ( node->nodeType() != QgsSQLStatement::ntColumnRef )
2712  return QString();
2713 
2714  const QgsSQLStatement::NodeColumnRef *col = static_cast<const QgsSQLStatement::NodeColumnRef *>( node );
2715  if ( !col->tableName().isEmpty() )
2716  {
2717  const auto constMLayerProperties = mLayerProperties;
2718  for ( QgsOgcUtils::LayerProperties prop : constMLayerProperties )
2719  {
2720  if ( prop.mName.compare( mMapTableAliasToNames[col->tableName()], Qt::CaseInsensitive ) == 0 &&
2721  prop.mGeometryAttribute.compare( col->name(), Qt::CaseInsensitive ) == 0 )
2722  {
2723  return prop.mSRSName;
2724  }
2725  }
2726  }
2727  if ( !mLayerProperties.empty() &&
2728  mLayerProperties.at( 0 ).mGeometryAttribute.compare( col->name(), Qt::CaseInsensitive ) == 0 )
2729  {
2730  return mLayerProperties.at( 0 ).mSRSName;
2731  }
2732  return QString();
2733 }
2734 
2735 bool QgsOgcUtilsSQLStatementToFilter::processSRSName( const QgsSQLStatement::NodeFunction *mainNode,
2736  QList<QgsSQLStatement::Node *> args,
2737  bool lastArgIsSRSName,
2738  QString &srsName,
2739  bool &axisInversion )
2740 {
2741  srsName = mCurrentSRSName;
2742  axisInversion = mInvertAxisOrientation;
2743 
2744  if ( lastArgIsSRSName )
2745  {
2746  QgsSQLStatement::Node *lastArg = args[ args.size() - 1 ];
2747  if ( lastArg->nodeType() != QgsSQLStatement::ntLiteral )
2748  {
2749  mErrorMessage = QObject::tr( "%1: Last argument must be string or integer literal" ).arg( mainNode->name() );
2750  return false;
2751  }
2752  const QgsSQLStatement::NodeLiteral *lit = static_cast<const QgsSQLStatement::NodeLiteral *>( lastArg );
2753  if ( lit->value().type() == QVariant::Int )
2754  {
2755  if ( mFilterVersion == QgsOgcUtils::FILTER_OGC_1_0 )
2756  {
2757  srsName = "EPSG:" + QString::number( lit->value().toInt() );
2758  }
2759  else
2760  {
2761  srsName = "urn:ogc:def:crs:EPSG::" + QString::number( lit->value().toInt() );
2762  }
2763  }
2764  else
2765  {
2766  srsName = lit->value().toString();
2767  if ( srsName.startsWith( QLatin1String( "EPSG:" ), Qt::CaseInsensitive ) )
2768  return true;
2769  }
2770  }
2771 
2773  if ( !srsName.isEmpty() )
2775  if ( crs.isValid() )
2776  {
2777  if ( mHonourAxisOrientation && crs.hasAxisInverted() )
2778  {
2779  axisInversion = !axisInversion;
2780  }
2781  }
2782 
2783  return true;
2784 }
2785 
2787 {
2788  // ST_GeometryFromText
2789  if ( node->name().compare( QLatin1String( "ST_GeometryFromText" ), Qt::CaseInsensitive ) == 0 )
2790  {
2791  QList<QgsSQLStatement::Node *> args = node->args()->list();
2792  if ( args.size() != 1 && args.size() != 2 )
2793  {
2794  mErrorMessage = QObject::tr( "Function %1 should have 1 or 2 arguments" ).arg( node->name() );
2795  return QDomElement();
2796  }
2797 
2798  QgsSQLStatement::Node *firstFnArg = args[0];
2799  if ( firstFnArg->nodeType() != QgsSQLStatement::ntLiteral )
2800  {
2801  mErrorMessage = QObject::tr( "%1: First argument must be string literal" ).arg( node->name() );
2802  return QDomElement();
2803  }
2804 
2805  QString srsName;
2806  bool axisInversion;
2807  if ( ! processSRSName( node, args, args.size() == 2, srsName, axisInversion ) )
2808  {
2809  return QDomElement();
2810  }
2811 
2812  QString wkt = static_cast<const QgsSQLStatement::NodeLiteral *>( firstFnArg )->value().toString();
2813  QgsGeometry geom = QgsGeometry::fromWkt( wkt );
2814  QDomElement geomElem = QgsOgcUtils::geometryToGML( geom, mDoc, mGMLVersion, srsName, axisInversion,
2815  QStringLiteral( "qgis_id_geom_%1" ).arg( mGeomId ) );
2816  mGeomId ++;
2817  if ( geomElem.isNull() )
2818  {
2819  mErrorMessage = QObject::tr( "%1: invalid WKT" ).arg( node->name() );
2820  return QDomElement();
2821  }
2822  mGMLUsed = true;
2823  return geomElem;
2824  }
2825 
2826  // ST_MakeEnvelope
2827  if ( node->name().compare( QLatin1String( "ST_MakeEnvelope" ), Qt::CaseInsensitive ) == 0 )
2828  {
2829  QList<QgsSQLStatement::Node *> args = node->args()->list();
2830  if ( args.size() != 4 && args.size() != 5 )
2831  {
2832  mErrorMessage = QObject::tr( "Function %1 should have 4 or 5 arguments" ).arg( node->name() );
2833  return QDomElement();
2834  }
2835 
2836  QgsRectangle rect;
2837 
2838  for ( int i = 0; i < 4; i++ )
2839  {
2840  QgsSQLStatement::Node *arg = args[i];
2841  if ( arg->nodeType() != QgsSQLStatement::ntLiteral )
2842  {
2843  mErrorMessage = QObject::tr( "%1: Argument %2 must be numeric literal" ).arg( node->name() ).arg( i + 1 );
2844  return QDomElement();
2845  }
2846  const QgsSQLStatement::NodeLiteral *lit = static_cast<const QgsSQLStatement::NodeLiteral *>( arg );
2847  double val = 0.0;
2848  if ( lit->value().type() == QVariant::Int )
2849  val = lit->value().toInt();
2850  else if ( lit->value().type() == QVariant::LongLong )
2851  val = lit->value().toLongLong();
2852  else if ( lit->value().type() == QVariant::Double )
2853  val = lit->value().toDouble();
2854  else
2855  {
2856  mErrorMessage = QObject::tr( "%1 Argument %2 must be numeric literal" ).arg( node->name() ).arg( i + 1 );
2857  return QDomElement();
2858  }
2859  if ( i == 0 )
2860  rect.setXMinimum( val );
2861  else if ( i == 1 )
2862  rect.setYMinimum( val );
2863  else if ( i == 2 )
2864  rect.setXMaximum( val );
2865  else
2866  rect.setYMaximum( val );
2867  }
2868 
2869  QString srsName;
2870  bool axisInversion;
2871  if ( ! processSRSName( node, args, args.size() == 5, srsName, axisInversion ) )
2872  {
2873  return QDomElement();
2874  }
2875 
2876  mGMLUsed = true;
2877 
2878  return ( mGMLVersion == QgsOgcUtils::GML_2_1_2 ) ?
2879  QgsOgcUtils::rectangleToGMLBox( &rect, mDoc, srsName, axisInversion, 15 ) :
2880  QgsOgcUtils::rectangleToGMLEnvelope( &rect, mDoc, srsName, axisInversion, 15 );
2881  }
2882 
2883  // ST_GeomFromGML
2884  if ( node->name().compare( QLatin1String( "ST_GeomFromGML" ), Qt::CaseInsensitive ) == 0 )
2885  {
2886  QList<QgsSQLStatement::Node *> args = node->args()->list();
2887  if ( args.size() != 1 )
2888  {
2889  mErrorMessage = QObject::tr( "Function %1 should have 1 argument" ).arg( node->name() );
2890  return QDomElement();
2891  }
2892 
2893  QgsSQLStatement::Node *firstFnArg = args[0];
2894  if ( firstFnArg->nodeType() != QgsSQLStatement::ntLiteral )
2895  {
2896  mErrorMessage = QObject::tr( "%1: Argument must be string literal" ).arg( node->name() );
2897  return QDomElement();
2898  }
2899 
2900  QDomDocument geomDoc;
2901  QString gml = static_cast<const QgsSQLStatement::NodeLiteral *>( firstFnArg )->value().toString();
2902  if ( !geomDoc.setContent( gml, true ) )
2903  {
2904  mErrorMessage = QObject::tr( "ST_GeomFromGML: unable to parse XML" );
2905  return QDomElement();
2906  }
2907 
2908  QDomNode geomNode = mDoc.importNode( geomDoc.documentElement(), true );
2909  mGMLUsed = true;
2910  return geomNode.toElement();
2911  }
2912 
2913  // Binary geometry operators
2914  QString ogcName( mapBinarySpatialToOgc( node->name() ) );
2915  if ( !ogcName.isEmpty() )
2916  {
2917  QList<QgsSQLStatement::Node *> args = node->args()->list();
2918  if ( args.size() != 2 )
2919  {
2920  mErrorMessage = QObject::tr( "Function %1 should have 2 arguments" ).arg( node->name() );
2921  return QDomElement();
2922  }
2923 
2924  for ( int i = 0; i < 2; i ++ )
2925  {
2926  if ( args[i]->nodeType() == QgsSQLStatement::ntFunction &&
2927  ( static_cast<const QgsSQLStatement::NodeFunction *>( args[i] )->name().compare( QLatin1String( "ST_GeometryFromText" ), Qt::CaseInsensitive ) == 0 ||
2928  static_cast<const QgsSQLStatement::NodeFunction *>( args[i] )->name().compare( QLatin1String( "ST_MakeEnvelope" ), Qt::CaseInsensitive ) == 0 ) )
2929  {
2930  mCurrentSRSName = getGeometryColumnSRSName( args[1 - i] );
2931  break;
2932  }
2933  }
2934 
2935  //if( ogcName == "Intersects" && mFilterVersion == QgsOgcUtils::FILTER_OGC_1_0 )
2936  // ogcName = "Intersect";
2937  QDomElement funcElem = mDoc.createElement( mFilterPrefix + ":" + ogcName );
2938  const auto constArgs = args;
2939  for ( QgsSQLStatement::Node *n : constArgs )
2940  {
2941  QDomElement childElem = toOgcFilter( n );
2942  if ( !mErrorMessage.isEmpty() )
2943  {
2944  mCurrentSRSName.clear();
2945  return QDomElement();
2946  }
2947 
2948  funcElem.appendChild( childElem );
2949  }
2950 
2951  mCurrentSRSName.clear();
2952  return funcElem;
2953  }
2954 
2955  ogcName = mapTernarySpatialToOgc( node->name() );
2956  if ( !ogcName.isEmpty() )
2957  {
2958  QList<QgsSQLStatement::Node *> args = node->args()->list();
2959  if ( args.size() != 3 )
2960  {
2961  mErrorMessage = QObject::tr( "Function %1 should have 3 arguments" ).arg( node->name() );
2962  return QDomElement();
2963  }
2964 
2965  for ( int i = 0; i < 2; i ++ )
2966  {
2967  if ( args[i]->nodeType() == QgsSQLStatement::ntFunction &&
2968  ( static_cast<const QgsSQLStatement::NodeFunction *>( args[i] )->name().compare( QLatin1String( "ST_GeometryFromText" ), Qt::CaseInsensitive ) == 0 ||
2969  static_cast<const QgsSQLStatement::NodeFunction *>( args[i] )->name().compare( QLatin1String( "ST_MakeEnvelope" ), Qt::CaseInsensitive ) == 0 ) )
2970  {
2971  mCurrentSRSName = getGeometryColumnSRSName( args[1 - i] );
2972  break;
2973  }
2974  }
2975 
2976  QDomElement funcElem = mDoc.createElement( mFilterPrefix + ":" + node->name().mid( 3 ) );
2977  for ( int i = 0; i < 2; i++ )
2978  {
2979  QDomElement childElem = toOgcFilter( args[i] );
2980  if ( !mErrorMessage.isEmpty() )
2981  {
2982  mCurrentSRSName.clear();
2983  return QDomElement();
2984  }
2985 
2986  funcElem.appendChild( childElem );
2987  }
2988  mCurrentSRSName.clear();
2989 
2990  QgsSQLStatement::Node *distanceNode = args[2];
2991  if ( distanceNode->nodeType() != QgsSQLStatement::ntLiteral )
2992  {
2993  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() );
2994  return QDomElement();
2995  }
2996  const QgsSQLStatement::NodeLiteral *lit = static_cast<const QgsSQLStatement::NodeLiteral *>( distanceNode );
2997  if ( lit->value().isNull() )
2998  {
2999  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() );
3000  return QDomElement();
3001  }
3002  QString distance;
3003  QString unit( QStringLiteral( "m" ) );
3004  switch ( lit->value().type() )
3005  {
3006  case QVariant::Int:
3007  distance = QString::number( lit->value().toInt() );
3008  break;
3009  case QVariant::LongLong:
3010  distance = QString::number( lit->value().toLongLong() );
3011  break;
3012  case QVariant::Double:
3013  distance = qgsDoubleToString( lit->value().toDouble() );
3014  break;
3015  case QVariant::String:
3016  {
3017  distance = lit->value().toString();
3018  for ( int i = 0; i < distance.size(); i++ )
3019  {
3020  if ( !( ( distance[i] >= '0' && distance[i] <= '9' ) || distance[i] == '-' || distance[i] == '.' || distance[i] == 'e' || distance[i] == 'E' ) )
3021  {
3022  unit = distance.mid( i ).trimmed();
3023  distance = distance.mid( 0, i );
3024  break;
3025  }
3026  }
3027  break;
3028  }
3029 
3030  default:
3031  mErrorMessage = QObject::tr( "Literal type not supported: %1" ).arg( lit->value().type() );
3032  return QDomElement();
3033  }
3034 
3035  QDomElement distanceElem = mDoc.createElement( mFilterPrefix + ":Distance" );
3036  if ( mFilterVersion == QgsOgcUtils::FILTER_FES_2_0 )
3037  distanceElem.setAttribute( QStringLiteral( "uom" ), unit );
3038  else
3039  distanceElem.setAttribute( QStringLiteral( "unit" ), unit );
3040  distanceElem.appendChild( mDoc.createTextNode( distance ) );
3041  funcElem.appendChild( distanceElem );
3042  return funcElem;
3043  }
3044 
3045  // Other function
3046  QDomElement funcElem = mDoc.createElement( mFilterPrefix + ":Function" );
3047  funcElem.setAttribute( QStringLiteral( "name" ), node->name() );
3048  const auto constList = node->args()->list();
3049  for ( QgsSQLStatement::Node *n : constList )
3050  {
3051  QDomElement childElem = toOgcFilter( n );
3052  if ( !mErrorMessage.isEmpty() )
3053  return QDomElement();
3054 
3055  funcElem.appendChild( childElem );
3056  }
3057  return funcElem;
3058 }
3059 
3061  const QString &leftTable )
3062 {
3063  QgsSQLStatement::Node *onExpr = node->onExpr();
3064  if ( onExpr )
3065  {
3066  return toOgcFilter( onExpr );
3067  }
3068 
3069  QList<QDomElement> listElem;
3070  const auto constUsingColumns = node->usingColumns();
3071  for ( const QString &columnName : constUsingColumns )
3072  {
3073  QDomElement eqElem = mDoc.createElement( mFilterPrefix + ":PropertyIsEqualTo" );
3074  QDomElement propElem1 = mDoc.createElement( mFilterPrefix + ":" + mPropertyName );
3075  propElem1.appendChild( mDoc.createTextNode( leftTable + "/" + columnName ) );
3076  eqElem.appendChild( propElem1 );
3077  QDomElement propElem2 = mDoc.createElement( mFilterPrefix + ":" + mPropertyName );
3078  propElem2.appendChild( mDoc.createTextNode( node->tableDef()->name() + "/" + columnName ) );
3079  eqElem.appendChild( propElem2 );
3080  listElem.append( eqElem );
3081  }
3082 
3083  if ( listElem.size() == 1 )
3084  {
3085  return listElem[0];
3086  }
3087  else if ( listElem.size() > 1 )
3088  {
3089  QDomElement andElem = mDoc.createElement( mFilterPrefix + ":And" );
3090  const auto constListElem = listElem;
3091  for ( const QDomElement &elem : constListElem )
3092  {
3093  andElem.appendChild( elem );
3094  }
3095  return andElem;
3096  }
3097 
3098  return QDomElement();
3099 }
3100 
3101 void QgsOgcUtilsSQLStatementToFilter::visit( const QgsSQLStatement::NodeTableDef *node )
3102 {
3103  if ( node->alias().isEmpty() )
3104  {
3105  mMapTableAliasToNames[ node->name()] = node->name();
3106  }
3107  else
3108  {
3109  mMapTableAliasToNames[ node->alias()] = node->name();
3110  }
3111 }
3112 
3114 {
3115  QList<QDomElement> listElem;
3116 
3117  if ( mFilterVersion != QgsOgcUtils::FILTER_FES_2_0 &&
3118  ( node->tables().size() != 1 || !node->joins().empty() ) )
3119  {
3120  mErrorMessage = QObject::tr( "Joins are only supported with WFS 2.0" );
3121  return QDomElement();
3122  }
3123 
3124  // Register all table name aliases
3125  const auto constTables = node->tables();
3126  for ( QgsSQLStatement::NodeTableDef *table : constTables )
3127  {
3128  visit( table );
3129  }
3130  const auto constJoins = node->joins();
3131  for ( QgsSQLStatement::NodeJoin *join : constJoins )
3132  {
3133  visit( join->tableDef() );
3134  }
3135 
3136  // Process JOIN conditions
3137  QList< QgsSQLStatement::NodeTableDef *> nodeTables = node->tables();
3138  QString leftTable = nodeTables.at( nodeTables.length() - 1 )->name();
3139  for ( QgsSQLStatement::NodeJoin *join : constJoins )
3140  {
3141  QDomElement joinElem = toOgcFilter( join, leftTable );
3142  if ( !mErrorMessage.isEmpty() )
3143  return QDomElement();
3144  listElem.append( joinElem );
3145  leftTable = join->tableDef()->name();
3146  }
3147 
3148  // Process WHERE conditions
3149  if ( node->where() )
3150  {
3151  QDomElement whereElem = toOgcFilter( node->where() );
3152  if ( !mErrorMessage.isEmpty() )
3153  return QDomElement();
3154  listElem.append( whereElem );
3155  }
3156 
3157  // Concatenate all conditions
3158  if ( listElem.size() == 1 )
3159  {
3160  return listElem[0];
3161  }
3162  else if ( listElem.size() > 1 )
3163  {
3164  QDomElement andElem = mDoc.createElement( mFilterPrefix + ":And" );
3165  const auto constListElem = listElem;
3166  for ( const QDomElement &elem : constListElem )
3167  {
3168  andElem.appendChild( elem );
3169  }
3170  return andElem;
3171  }
3172 
3173  return QDomElement();
3174 }
3175 
3177  : mLayer( layer )
3178 {
3179  mPropertyName = QStringLiteral( "PropertyName" );
3180  mPrefix = QStringLiteral( "ogc" );
3181 
3182  if ( version == QgsOgcUtils::FILTER_FES_2_0 )
3183  {
3184  mPropertyName = QStringLiteral( "ValueReference" );
3185  mPrefix = QStringLiteral( "fes" );
3186  }
3187 }
3188 
3190 {
3191  if ( element.isNull() )
3192  return nullptr;
3193 
3194  // check for binary operators
3195  if ( isBinaryOperator( element.tagName() ) )
3196  {
3197  return nodeBinaryOperatorFromOgcFilter( element );
3198  }
3199 
3200  // check for spatial operators
3201  if ( isSpatialOperator( element.tagName() ) )
3202  {
3203  return nodeSpatialOperatorFromOgcFilter( element );
3204  }
3205 
3206  // check for other OGC operators, convert them to expressions
3207  if ( element.tagName() == QLatin1String( "Not" ) )
3208  {
3209  return nodeNotFromOgcFilter( element );
3210  }
3211  else if ( element.tagName() == QLatin1String( "PropertyIsNull" ) )
3212  {
3213  return nodePropertyIsNullFromOgcFilter( element );
3214  }
3215  else if ( element.tagName() == QLatin1String( "Literal" ) )
3216  {
3217  return nodeLiteralFromOgcFilter( element );
3218  }
3219  else if ( element.tagName() == QLatin1String( "Function" ) )
3220  {
3221  return nodeFunctionFromOgcFilter( element );
3222  }
3223  else if ( element.tagName() == mPropertyName )
3224  {
3225  return nodeColumnRefFromOgcFilter( element );
3226  }
3227  else if ( element.tagName() == QLatin1String( "PropertyIsBetween" ) )
3228  {
3229  return nodeIsBetweenFromOgcFilter( element );
3230  }
3231 
3232  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() );
3233  return nullptr;
3234 }
3235 
3237 {
3238  if ( element.isNull() )
3239  return nullptr;
3240 
3241  int op = binaryOperatorFromTagName( element.tagName() );
3242  if ( op < 0 )
3243  {
3244  mErrorMessage = QObject::tr( "'%1' binary operator not supported." ).arg( element.tagName() );
3245  return nullptr;
3246  }
3247 
3248  if ( op == QgsExpressionNodeBinaryOperator::boLike && element.hasAttribute( QStringLiteral( "matchCase" ) ) && element.attribute( QStringLiteral( "matchCase" ) ) == QLatin1String( "false" ) )
3249  {
3251  }
3252 
3253  QDomElement operandElem = element.firstChildElement();
3254  std::unique_ptr<QgsExpressionNode> expr( nodeFromOgcFilter( operandElem ) );
3255 
3256  if ( !expr )
3257  {
3258  mErrorMessage = QObject::tr( "invalid left operand for '%1' binary operator" ).arg( element.tagName() );
3259  return nullptr;
3260  }
3261 
3262  std::unique_ptr<QgsExpressionNode> leftOp( expr->clone() );
3263  for ( operandElem = operandElem.nextSiblingElement(); !operandElem.isNull(); operandElem = operandElem.nextSiblingElement() )
3264  {
3265  std::unique_ptr<QgsExpressionNode> opRight( nodeFromOgcFilter( operandElem ) );
3266  if ( !opRight )
3267  {
3268  mErrorMessage = QObject::tr( "invalid right operand for '%1' binary operator" ).arg( element.tagName() );
3269  return nullptr;
3270  }
3271 
3273  {
3274  QString wildCard;
3275  if ( element.hasAttribute( QStringLiteral( "wildCard" ) ) )
3276  {
3277  wildCard = element.attribute( QStringLiteral( "wildCard" ) );
3278  }
3279  QString singleChar;
3280  if ( element.hasAttribute( QStringLiteral( "singleChar" ) ) )
3281  {
3282  singleChar = element.attribute( QStringLiteral( "singleChar" ) );
3283  }
3284  QString escape = QStringLiteral( "\\" );
3285  if ( element.hasAttribute( QStringLiteral( "escape" ) ) )
3286  {
3287  escape = element.attribute( QStringLiteral( "escape" ) );
3288  }
3289  if ( element.hasAttribute( QStringLiteral( "escapeChar" ) ) )
3290  {
3291  escape = element.attribute( QStringLiteral( "escapeChar" ) );
3292  }
3293  // replace
3294  QString oprValue = static_cast<const QgsExpressionNodeLiteral *>( opRight.get() )->value().toString();
3295  if ( !wildCard.isEmpty() && wildCard != QLatin1String( "%" ) )
3296  {
3297  oprValue.replace( '%', QLatin1String( "\\%" ) );
3298  if ( oprValue.startsWith( wildCard ) )
3299  {
3300  oprValue.replace( 0, 1, QStringLiteral( "%" ) );
3301  }
3302  QRegExp rx( "[^" + QRegExp::escape( escape ) + "](" + QRegExp::escape( wildCard ) + ")" );
3303  int pos = 0;
3304  while ( ( pos = rx.indexIn( oprValue, pos ) ) != -1 )
3305  {
3306  oprValue.replace( pos + 1, 1, QStringLiteral( "%" ) );
3307  pos += 1;
3308  }
3309  oprValue.replace( escape + wildCard, wildCard );
3310  }
3311  if ( !singleChar.isEmpty() && singleChar != QLatin1String( "_" ) )
3312  {
3313  oprValue.replace( '_', QLatin1String( "\\_" ) );
3314  if ( oprValue.startsWith( singleChar ) )
3315  {
3316  oprValue.replace( 0, 1, QStringLiteral( "_" ) );
3317  }
3318  QRegExp rx( "[^" + QRegExp::escape( escape ) + "](" + QRegExp::escape( singleChar ) + ")" );
3319  int pos = 0;
3320  while ( ( pos = rx.indexIn( oprValue, pos ) ) != -1 )
3321  {
3322  oprValue.replace( pos + 1, 1, QStringLiteral( "_" ) );
3323  pos += 1;
3324  }
3325  oprValue.replace( escape + singleChar, singleChar );
3326  }
3327  if ( !escape.isEmpty() && escape != QLatin1String( "\\" ) )
3328  {
3329  oprValue.replace( escape + escape, escape );
3330  }
3331  opRight.reset( new QgsExpressionNodeLiteral( oprValue ) );
3332  }
3333 
3334  expr.reset( new QgsExpressionNodeBinaryOperator( static_cast< QgsExpressionNodeBinaryOperator::BinaryOperator >( op ), expr.release(), opRight.release() ) );
3335  }
3336 
3337  if ( expr == leftOp )
3338  {
3339  mErrorMessage = QObject::tr( "only one operand for '%1' binary operator" ).arg( element.tagName() );
3340  return nullptr;
3341  }
3342 
3343  return dynamic_cast< QgsExpressionNodeBinaryOperator * >( expr.release() );
3344 }
3345 
3346 
3348 {
3349  // we are exploiting the fact that our function names are the same as the XML tag names
3350  const int opIdx = QgsExpression::functionIndex( element.tagName().toLower() );
3351 
3352  std::unique_ptr<QgsExpressionNode::NodeList> gml2Args( new QgsExpressionNode::NodeList() );
3353  QDomElement childElem = element.firstChildElement();
3354  QString gml2Str;
3355  while ( !childElem.isNull() && gml2Str.isEmpty() )
3356  {
3357  if ( childElem.tagName() != mPropertyName )
3358  {
3359  QTextStream gml2Stream( &gml2Str );
3360  childElem.save( gml2Stream, 0 );
3361  }
3362  childElem = childElem.nextSiblingElement();
3363  }
3364  if ( !gml2Str.isEmpty() )
3365  {
3366  gml2Args->append( new QgsExpressionNodeLiteral( QVariant( gml2Str.remove( '\n' ) ) ) );
3367  }
3368  else
3369  {
3370  mErrorMessage = QObject::tr( "No OGC Geometry found" );
3371  return nullptr;
3372  }
3373 
3374  std::unique_ptr<QgsExpressionNode::NodeList> opArgs( new QgsExpressionNode::NodeList() );
3375  opArgs->append( new QgsExpressionNodeFunction( QgsExpression::functionIndex( QStringLiteral( "$geometry" ) ), new QgsExpressionNode::NodeList() ) );
3376  opArgs->append( new QgsExpressionNodeFunction( QgsExpression::functionIndex( QStringLiteral( "geomFromGML" ) ), gml2Args.release() ) );
3377 
3378  return new QgsExpressionNodeFunction( opIdx, opArgs.release() );
3379 }
3380 
3382 {
3383  if ( element.isNull() || element.tagName() != mPropertyName )
3384  {
3385  mErrorMessage = QObject::tr( "%1:PropertyName expected, got %2" ).arg( mPrefix, element.tagName() );
3386  return nullptr;
3387  }
3388 
3389  return new QgsExpressionNodeColumnRef( element.firstChild().nodeValue() );
3390 }
3391 
3393 {
3394  if ( element.isNull() || element.tagName() != QLatin1String( "Literal" ) )
3395  {
3396  mErrorMessage = QObject::tr( "%1:Literal expected, got %2" ).arg( mPrefix, element.tagName() );
3397  return nullptr;
3398  }
3399 
3400  std::unique_ptr<QgsExpressionNode> root;
3401 
3402  // the literal content can have more children (e.g. CDATA section, text, ...)
3403  QDomNode childNode = element.firstChild();
3404  while ( !childNode.isNull() )
3405  {
3406  std::unique_ptr<QgsExpressionNode> operand;
3407 
3408  if ( childNode.nodeType() == QDomNode::ElementNode )
3409  {
3410  // found a element node (e.g. PropertyName), convert it
3411  const QDomElement operandElem = childNode.toElement();
3412  operand.reset( nodeFromOgcFilter( operandElem ) );
3413  if ( !operand )
3414  {
3415  mErrorMessage = QObject::tr( "'%1' is an invalid or not supported content for %2:Literal" ).arg( operandElem.tagName(), mPrefix );
3416  return nullptr;
3417  }
3418  }
3419  else
3420  {
3421  // probably a text/CDATA node
3422  QVariant value = childNode.nodeValue();
3423 
3424  bool converted = false;
3425 
3426  // try to convert the node content to corresponding field type if possible
3427  if ( mLayer )
3428  {
3429  QDomElement propertyNameElement = element.previousSiblingElement( mPropertyName );
3430  if ( propertyNameElement.isNull() || propertyNameElement.tagName() != mPropertyName )
3431  {
3432  propertyNameElement = element.nextSiblingElement( mPropertyName );
3433  }
3434  if ( !propertyNameElement.isNull() || propertyNameElement.tagName() == mPropertyName )
3435  {
3436  const int fieldIndex = mLayer->fields().indexOf( propertyNameElement.firstChild().nodeValue() );
3437  if ( fieldIndex != -1 )
3438  {
3439  QgsField field = mLayer->fields().field( propertyNameElement.firstChild().nodeValue() );
3440  field.convertCompatible( value );
3441  converted = true;
3442  }
3443  }
3444  }
3445  if ( !converted )
3446  {
3447  // try to convert the node content to number if possible,
3448  // otherwise let's use it as string
3449  bool ok;
3450  const double d = value.toDouble( &ok );
3451  if ( ok )
3452  value = d;
3453  }
3454 
3455  operand.reset( new QgsExpressionNodeLiteral( value ) );
3456  if ( !operand )
3457  continue;
3458  }
3459 
3460  // use the concat operator to merge the ogc:Literal children
3461  if ( !root )
3462  {
3463  root = std::move( operand );
3464  }
3465  else
3466  {
3467  root.reset( new QgsExpressionNodeBinaryOperator( QgsExpressionNodeBinaryOperator::boConcat, root.release(), operand.release() ) );
3468  }
3469 
3470  childNode = childNode.nextSibling();
3471  }
3472 
3473  if ( root )
3474  return root.release();
3475 
3476  return nullptr;
3477 }
3478 
3480 {
3481  if ( element.tagName() != QLatin1String( "Not" ) )
3482  return nullptr;
3483 
3484  const QDomElement operandElem = element.firstChildElement();
3485  std::unique_ptr<QgsExpressionNode> operand( nodeFromOgcFilter( operandElem ) );
3486  if ( !operand )
3487  {
3488  mErrorMessage = QObject::tr( "invalid operand for '%1' unary operator" ).arg( element.tagName() );
3489  return nullptr;
3490  }
3491 
3493 }
3494 
3496 {
3497  // convert ogc:PropertyIsNull to IS operator with NULL right operand
3498  if ( element.tagName() != QLatin1String( "PropertyIsNull" ) )
3499  {
3500  return nullptr;
3501  }
3502 
3503  const QDomElement operandElem = element.firstChildElement();
3504  std::unique_ptr<QgsExpressionNode> opLeft( nodeFromOgcFilter( operandElem ) );
3505  if ( !opLeft )
3506  return nullptr;
3507 
3508  std::unique_ptr<QgsExpressionNode> opRight( new QgsExpressionNodeLiteral( QVariant() ) );
3509  return new QgsExpressionNodeBinaryOperator( QgsExpressionNodeBinaryOperator::boIs, opLeft.release(), opRight.release() );
3510 }
3511 
3513 {
3514  if ( element.isNull() || element.tagName() != QLatin1String( "Function" ) )
3515  {
3516  mErrorMessage = QObject::tr( "%1:Function expected, got %2" ).arg( mPrefix, element.tagName() );
3517  return nullptr;
3518  }
3519 
3520  for ( int i = 0; i < QgsExpression::Functions().size(); i++ )
3521  {
3522  const QgsExpressionFunction *funcDef = QgsExpression::Functions()[i];
3523 
3524  if ( element.attribute( QStringLiteral( "name" ) ) != funcDef->name() )
3525  continue;
3526 
3527  std::unique_ptr<QgsExpressionNode::NodeList> args( new QgsExpressionNode::NodeList() );
3528 
3529  QDomElement operandElem = element.firstChildElement();
3530  while ( !operandElem.isNull() )
3531  {
3532  std::unique_ptr<QgsExpressionNode> op( nodeFromOgcFilter( operandElem ) );
3533  if ( !op )
3534  {
3535  return nullptr;
3536  }
3537  args->append( op.release() );
3538 
3539  operandElem = operandElem.nextSiblingElement();
3540  }
3541 
3542  return new QgsExpressionNodeFunction( i, args.release() );
3543  }
3544 
3545  return nullptr;
3546 }
3547 
3549 {
3550  // <ogc:PropertyIsBetween> encode a Range check
3551  std::unique_ptr<QgsExpressionNode> operand;
3552  std::unique_ptr<QgsExpressionNode> lowerBound;
3553  std::unique_ptr<QgsExpressionNode> upperBound;
3554 
3555  QDomElement operandElem = element.firstChildElement();
3556  while ( !operandElem.isNull() )
3557  {
3558  if ( operandElem.tagName() == QLatin1String( "LowerBoundary" ) )
3559  {
3560  QDomElement lowerBoundElem = operandElem.firstChildElement();
3561  lowerBound.reset( nodeFromOgcFilter( lowerBoundElem ) );
3562  }
3563  else if ( operandElem.tagName() == QLatin1String( "UpperBoundary" ) )
3564  {
3565  QDomElement upperBoundElem = operandElem.firstChildElement();
3566  upperBound.reset( nodeFromOgcFilter( upperBoundElem ) );
3567  }
3568  else
3569  {
3570  // <ogc:expression>
3571  operand.reset( nodeFromOgcFilter( operandElem ) );
3572  }
3573 
3574  if ( operand && lowerBound && upperBound )
3575  break;
3576 
3577  operandElem = operandElem.nextSiblingElement();
3578  }
3579 
3580  if ( !operand || !lowerBound || !upperBound )
3581  {
3582  mErrorMessage = QObject::tr( "missing some required sub-elements in %1:PropertyIsBetween" ).arg( mPrefix );
3583  return nullptr;
3584  }
3585 
3586  std::unique_ptr<QgsExpressionNode> leOperator( new QgsExpressionNodeBinaryOperator( QgsExpressionNodeBinaryOperator::boLE, operand->clone(), upperBound.release() ) );
3587  std::unique_ptr<QgsExpressionNode> geOperator( new QgsExpressionNodeBinaryOperator( QgsExpressionNodeBinaryOperator::boGE, operand.release(), lowerBound.release() ) );
3588  return new QgsExpressionNodeBinaryOperator( QgsExpressionNodeBinaryOperator::boAnd, geOperator.release(), leOperator.release() );
3589 }
3590 
3592 {
3593  return mErrorMessage;
3594 }
virtual void swapXy()=0
Swaps the x and y coordinates from the geometry.
A const WKB pointer.
Definition: qgswkbptr.h:130
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
Gets 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:91
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:44
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:172
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:162
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:167
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:177
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:140
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:135
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:130
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:145
void normalize()
Normalize the rectangle so it has non-negative width/height.
Definition: qgsrectangle.h:182
'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:829
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:276
QMap< QString, QString > QgsStringMap
Definition: qgis.h:759
const QgsField & field
Definition: qgsfield.h:472
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