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