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