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