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