QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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  // check if it is a single string value => no DomElement
1603  if ( element.firstChild().nodeType() == QDomNode::TextNode )
1604  {
1605  QgsExpression *expr = new QgsExpression( element.firstChild().nodeValue() );
1606  expr->d->mParserErrorString = QString();
1607  return expr;
1608  }
1609 
1610  // now parse OGC operators. OGC operator does not have a only text value
1611  // but only sub element operators
1612  QgsExpression *expr = new QgsExpression();
1613 
1614  QDomElement childElem = element.firstChildElement();
1615  while ( !childElem.isNull() )
1616  {
1617  QString errorMsg;
1618  QgsExpression::Node *node = nodeFromOgcFilter( childElem, errorMsg );
1619  if ( !node )
1620  {
1621  // invalid expression, parser error
1622  expr->d->mParserErrorString = errorMsg;
1623  return expr;
1624  }
1625 
1626  // use the concat binary operator to append to the root node
1627  if ( !expr->d->mRootNode )
1628  {
1629  expr->d->mRootNode = node;
1630  }
1631  else
1632  {
1633  expr->d->mRootNode = new QgsExpression::NodeBinaryOperator( QgsExpression::boConcat, expr->d->mRootNode, node );
1634  }
1635 
1636  childElem = childElem.nextSiblingElement();
1637  }
1638 
1639  // update expression string
1640  expr->d->mExp = expr->dump();
1641 
1642  return expr;
1643 }
1644 
1645 
1647 {
1648  static QMap<QString, int> binOps;
1649  if ( binOps.isEmpty() )
1650  {
1651  // logical
1652  binOps.insert( "Or", QgsExpression::boOr );
1653  binOps.insert( "And", QgsExpression::boAnd );
1654  // comparison
1655  binOps.insert( "PropertyIsEqualTo", QgsExpression::boEQ );
1656  binOps.insert( "PropertyIsNotEqualTo", QgsExpression::boNE );
1657  binOps.insert( "PropertyIsLessThanOrEqualTo", QgsExpression::boLE );
1658  binOps.insert( "PropertyIsGreaterThanOrEqualTo", QgsExpression::boGE );
1659  binOps.insert( "PropertyIsLessThan", QgsExpression::boLT );
1660  binOps.insert( "PropertyIsGreaterThan", QgsExpression::boGT );
1661  binOps.insert( "PropertyIsLike", QgsExpression::boLike );
1662  // arithmetics
1663  binOps.insert( "Add", QgsExpression::boPlus );
1664  binOps.insert( "Sub", QgsExpression::boMinus );
1665  binOps.insert( "Mul", QgsExpression::boMul );
1666  binOps.insert( "Div", QgsExpression::boDiv );
1667  }
1668  return binOps;
1669 }
1670 
1671 static int binaryOperatorFromTagName( const QString& tagName )
1672 {
1673 
1674  return binaryOperatorsTagNamesMap().value( tagName, -1 );
1675 }
1676 
1678 {
1679  if ( op == QgsExpression::boILike )
1680  {
1681  return "PropertyIsLike";
1682  }
1683  return binaryOperatorsTagNamesMap().key( op, QString() );
1684 }
1685 
1686 static bool isBinaryOperator( const QString& tagName )
1687 {
1688  return binaryOperatorFromTagName( tagName ) >= 0;
1689 }
1690 
1691 
1692 static bool isSpatialOperator( const QString& tagName )
1693 {
1694  static QStringList spatialOps;
1695  if ( spatialOps.isEmpty() )
1696  {
1697  spatialOps << "BBOX" << "Intersects" << "Contains" << "Crosses" << "Equals"
1698  << "Disjoint" << "Overlaps" << "Touches" << "Within";
1699  }
1700 
1701  return spatialOps.contains( tagName );
1702 }
1703 
1704 
1705 
1706 QgsExpression::Node* QgsOgcUtils::nodeFromOgcFilter( QDomElement &element, QString &errorMessage )
1707 {
1708  if ( element.isNull() )
1709  return nullptr;
1710 
1711  // check for binary operators
1712  if ( isBinaryOperator( element.tagName() ) )
1713  {
1714  return nodeBinaryOperatorFromOgcFilter( element, errorMessage );
1715  }
1716 
1717  // check for spatial operators
1718  if ( isSpatialOperator( element.tagName() ) )
1719  {
1720  return nodeSpatialOperatorFromOgcFilter( element, errorMessage );
1721  }
1722 
1723  // check for other OGC operators, convert them to expressions
1724 
1725  if ( element.tagName() == "Not" )
1726  {
1727  return nodeNotFromOgcFilter( element, errorMessage );
1728  }
1729  else if ( element.tagName() == "PropertyIsNull" )
1730  {
1731  return nodePropertyIsNullFromOgcFilter( element, errorMessage );
1732  }
1733  else if ( element.tagName() == "Literal" )
1734  {
1735  return nodeLiteralFromOgcFilter( element, errorMessage );
1736  }
1737  else if ( element.tagName() == "Function" )
1738  {
1739  return nodeFunctionFromOgcFilter( element, errorMessage );
1740  }
1741  else if ( element.tagName() == "PropertyName" )
1742  {
1743  return nodeColumnRefFromOgcFilter( element, errorMessage );
1744  }
1745  else if ( element.tagName() == "PropertyIsBetween" )
1746  {
1747  return nodeIsBetweenFromOgcFilter( element, errorMessage );
1748  }
1749 
1750  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() );
1751  return nullptr;
1752 }
1753 
1754 
1755 
1756 QgsExpression::NodeBinaryOperator* QgsOgcUtils::nodeBinaryOperatorFromOgcFilter( QDomElement &element, QString &errorMessage )
1757 {
1758  if ( element.isNull() )
1759  return nullptr;
1760 
1761  int op = binaryOperatorFromTagName( element.tagName() );
1762  if ( op < 0 )
1763  {
1764  if ( errorMessage.isEmpty() )
1765  errorMessage = QObject::tr( "'%1' binary operator not supported." ).arg( element.tagName() );
1766  return nullptr;
1767  }
1768 
1769  if ( op == QgsExpression::boLike && element.hasAttribute( "matchCase" ) && element.attribute( "matchCase" ) == "false" )
1770  {
1772  }
1773 
1774  QDomElement operandElem = element.firstChildElement();
1775  QgsExpression::Node *expr = nodeFromOgcFilter( operandElem, errorMessage ), *leftOp = expr;
1776  if ( !expr )
1777  {
1778  if ( errorMessage.isEmpty() )
1779  errorMessage = QObject::tr( "invalid left operand for '%1' binary operator" ).arg( element.tagName() );
1780  return nullptr;
1781  }
1782 
1783  for ( operandElem = operandElem.nextSiblingElement(); !operandElem.isNull(); operandElem = operandElem.nextSiblingElement() )
1784  {
1785  QgsExpression::Node* opRight = nodeFromOgcFilter( operandElem, errorMessage );
1786  if ( !opRight )
1787  {
1788  if ( errorMessage.isEmpty() )
1789  errorMessage = QObject::tr( "invalid right operand for '%1' binary operator" ).arg( element.tagName() );
1790  delete expr;
1791  return nullptr;
1792  }
1793 
1794  if ( op == QgsExpression::boLike || op == QgsExpression::boILike )
1795  {
1796  QString wildCard;
1797  if ( element.hasAttribute( "wildCard" ) )
1798  {
1799  wildCard = element.attribute( "wildCard" );
1800  }
1801  QString singleChar;
1802  if ( element.hasAttribute( "singleChar" ) )
1803  {
1804  singleChar = element.attribute( "singleChar" );
1805  }
1806  QString escape = "\\";
1807  if ( element.hasAttribute( "escape" ) )
1808  {
1809  escape = element.attribute( "escape" );
1810  }
1811  // replace
1812  QString oprValue = static_cast<const QgsExpression::NodeLiteral*>( opRight )->value().toString();
1813  if ( !wildCard.isEmpty() && wildCard != "%" )
1814  {
1815  oprValue.replace( '%', "\\%" );
1816  if ( oprValue.startsWith( wildCard ) )
1817  {
1818  oprValue.replace( 0, 1, "%" );
1819  }
1820  QRegExp rx( "[^" + QRegExp::escape( escape ) + "](" + QRegExp::escape( wildCard ) + ")" );
1821  int pos = 0;
1822  while (( pos = rx.indexIn( oprValue, pos ) ) != -1 )
1823  {
1824  oprValue.replace( pos + 1, 1, "%" );
1825  pos += 1;
1826  }
1827  oprValue.replace( escape + wildCard, wildCard );
1828  }
1829  if ( !singleChar.isEmpty() && singleChar != "_" )
1830  {
1831  oprValue.replace( '_', "\\_" );
1832  if ( oprValue.startsWith( singleChar ) )
1833  {
1834  oprValue.replace( 0, 1, "_" );
1835  }
1836  QRegExp rx( "[^" + QRegExp::escape( escape ) + "](" + QRegExp::escape( singleChar ) + ")" );
1837  int pos = 0;
1838  while (( pos = rx.indexIn( oprValue, pos ) ) != -1 )
1839  {
1840  oprValue.replace( pos + 1, 1, "_" );
1841  pos += 1;
1842  }
1843  oprValue.replace( escape + singleChar, singleChar );
1844  }
1845  if ( !escape.isEmpty() && escape != "\\" )
1846  {
1847  oprValue.replace( escape + escape, escape );
1848  }
1849  opRight = new QgsExpression::NodeLiteral( oprValue );
1850  }
1851 
1852  expr = new QgsExpression::NodeBinaryOperator( static_cast< QgsExpression::BinaryOperator >( op ), expr, opRight );
1853  }
1854 
1855  if ( expr == leftOp )
1856  {
1857  if ( errorMessage.isEmpty() )
1858  errorMessage = QObject::tr( "only one operand for '%1' binary operator" ).arg( element.tagName() );
1859  delete expr;
1860  return nullptr;
1861  }
1862 
1864  if ( !ret )
1865  delete expr;
1866 
1867  return ret;
1868 }
1869 
1870 
1871 QgsExpression::NodeFunction* QgsOgcUtils::nodeSpatialOperatorFromOgcFilter( QDomElement& element, QString &errorMessage )
1872 {
1873  // we are exploiting the fact that our function names are the same as the XML tag names
1874  int opIdx = QgsExpression::functionIndex( element.tagName().toLower() );
1875 
1877  QDomElement childElem = element.firstChildElement();
1878  QString gml2Str;
1879  while ( !childElem.isNull() && gml2Str.isEmpty() )
1880  {
1881  if ( childElem.tagName() != "PropertyName" )
1882  {
1883  QTextStream gml2Stream( &gml2Str );
1884  childElem.save( gml2Stream, 0 );
1885  }
1886  childElem = childElem.nextSiblingElement();
1887  }
1888  if ( !gml2Str.isEmpty() )
1889  {
1890  gml2Args->append( new QgsExpression::NodeLiteral( QVariant( gml2Str.remove( '\n' ) ) ) );
1891  }
1892  else
1893  {
1894  errorMessage = QObject::tr( "No OGC Geometry found" );
1895  delete gml2Args;
1896  return nullptr;
1897  }
1898 
1901  opArgs->append( new QgsExpression::NodeFunction( QgsExpression::functionIndex( "geomFromGML" ), gml2Args ) );
1902 
1903  return new QgsExpression::NodeFunction( opIdx, opArgs );
1904 }
1905 
1906 
1907 QgsExpression::NodeUnaryOperator* QgsOgcUtils::nodeNotFromOgcFilter( QDomElement &element, QString &errorMessage )
1908 {
1909  if ( element.tagName() != "Not" )
1910  return nullptr;
1911 
1912  QDomElement operandElem = element.firstChildElement();
1913  QgsExpression::Node* operand = nodeFromOgcFilter( operandElem, errorMessage );
1914  if ( !operand )
1915  {
1916  if ( errorMessage.isEmpty() )
1917  errorMessage = QObject::tr( "invalid operand for '%1' unary operator" ).arg( element.tagName() );
1918  return nullptr;
1919  }
1920 
1922 }
1923 
1924 
1925 QgsExpression::NodeFunction* QgsOgcUtils::nodeFunctionFromOgcFilter( QDomElement &element, QString &errorMessage )
1926 {
1927  if ( element.isNull() || element.tagName() != "Function" )
1928  {
1929  errorMessage = QObject::tr( "ogc:Function expected, got %1" ).arg( element.tagName() );
1930  return nullptr;
1931  }
1932 
1933  for ( int i = 0; i < QgsExpression::Functions().size(); i++ )
1934  {
1936 
1937  if ( element.attribute( "name" ) != funcDef->name() )
1938  continue;
1939 
1941 
1942  QDomElement operandElem = element.firstChildElement();
1943  while ( !operandElem.isNull() )
1944  {
1945  QgsExpression::Node* op = nodeFromOgcFilter( operandElem, errorMessage );
1946  if ( !op )
1947  {
1948  delete args;
1949  return nullptr;
1950  }
1951  args->append( op );
1952 
1953  operandElem = operandElem.nextSiblingElement();
1954  }
1955 
1956  return new QgsExpression::NodeFunction( i, args );
1957  }
1958 
1959  return nullptr;
1960 }
1961 
1962 
1963 
1964 QgsExpression::Node* QgsOgcUtils::nodeLiteralFromOgcFilter( QDomElement &element, QString &errorMessage )
1965 {
1966  if ( element.isNull() || element.tagName() != "Literal" )
1967  {
1968  errorMessage = QObject::tr( "ogc:Literal expected, got %1" ).arg( element.tagName() );
1969  return nullptr;
1970  }
1971 
1972  QgsExpression::Node *root = nullptr;
1973 
1974  // the literal content can have more children (e.g. CDATA section, text, ...)
1975  QDomNode childNode = element.firstChild();
1976  while ( !childNode.isNull() )
1977  {
1978  QgsExpression::Node* operand = nullptr;
1979 
1980  if ( childNode.nodeType() == QDomNode::ElementNode )
1981  {
1982  // found a element node (e.g. PropertyName), convert it
1983  QDomElement operandElem = childNode.toElement();
1984  operand = nodeFromOgcFilter( operandElem, errorMessage );
1985  if ( !operand )
1986  {
1987  if ( root )
1988  delete root;
1989 
1990  errorMessage = QObject::tr( "'%1' is an invalid or not supported content for ogc:Literal" ).arg( operandElem.tagName() );
1991  return nullptr;
1992  }
1993  }
1994  else
1995  {
1996  // probably a text/CDATA node
1997  QVariant value = childNode.nodeValue();
1998 
1999  // try to convert the node content to number if possible,
2000  // otherwise let's use it as string
2001  bool ok;
2002  double d = value.toDouble( &ok );
2003  if ( ok )
2004  value = d;
2005 
2006  operand = new QgsExpression::NodeLiteral( value );
2007  if ( !operand )
2008  continue;
2009  }
2010 
2011  // use the concat operator to merge the ogc:Literal children
2012  if ( !root )
2013  {
2014  root = operand;
2015  }
2016  else
2017  {
2018  root = new QgsExpression::NodeBinaryOperator( QgsExpression::boConcat, root, operand );
2019  }
2020 
2021  childNode = childNode.nextSibling();
2022  }
2023 
2024  if ( root )
2025  return root;
2026 
2027  return nullptr;
2028 }
2029 
2030 
2031 QgsExpression::NodeColumnRef* QgsOgcUtils::nodeColumnRefFromOgcFilter( QDomElement &element, QString &errorMessage )
2032 {
2033  if ( element.isNull() || element.tagName() != "PropertyName" )
2034  {
2035  errorMessage = QObject::tr( "ogc:PropertyName expected, got %1" ).arg( element.tagName() );
2036  return nullptr;
2037  }
2038 
2039  return new QgsExpression::NodeColumnRef( element.firstChild().nodeValue() );
2040 }
2041 
2042 
2043 QgsExpression::Node* QgsOgcUtils::nodeIsBetweenFromOgcFilter( QDomElement& element, QString& errorMessage )
2044 {
2045  // <ogc:PropertyIsBetween> encode a Range check
2046  QgsExpression::Node *operand = nullptr, *lowerBound = nullptr;
2047  QgsExpression::Node *operand2 = nullptr, *upperBound = nullptr;
2048 
2049  QDomElement operandElem = element.firstChildElement();
2050  while ( !operandElem.isNull() )
2051  {
2052  if ( operandElem.tagName() == "LowerBoundary" )
2053  {
2054  QDomElement lowerBoundElem = operandElem.firstChildElement();
2055  lowerBound = nodeFromOgcFilter( lowerBoundElem, errorMessage );
2056  }
2057  else if ( operandElem.tagName() == "UpperBoundary" )
2058  {
2059  QDomElement upperBoundElem = operandElem.firstChildElement();
2060  upperBound = nodeFromOgcFilter( upperBoundElem, errorMessage );
2061  }
2062  else
2063  {
2064  // <ogc:expression>
2065  // both operand and operand2 contain the same expression,
2066  // they are respectively compared to lower bound and upper bound
2067  operand = nodeFromOgcFilter( operandElem, errorMessage );
2068  operand2 = nodeFromOgcFilter( operandElem, errorMessage );
2069  }
2070 
2071  if ( operand && lowerBound && operand2 && upperBound )
2072  break;
2073 
2074  operandElem = operandElem.nextSiblingElement();
2075  }
2076 
2077  if ( !operand || !lowerBound || !operand2 || !upperBound )
2078  {
2079  if ( operand )
2080  delete operand;
2081 
2082  if ( lowerBound )
2083  delete lowerBound;
2084 
2085  if ( upperBound )
2086  delete upperBound;
2087 
2088  errorMessage = QObject::tr( "missing some required sub-elements in ogc:PropertyIsBetween" );
2089  return nullptr;
2090  }
2091 
2092  QgsExpression::Node *geOperator = new QgsExpression::NodeBinaryOperator( QgsExpression::boGE, operand, lowerBound );
2093  QgsExpression::Node *leOperator = new QgsExpression::NodeBinaryOperator( QgsExpression::boLE, operand2, upperBound );
2094  return new QgsExpression::NodeBinaryOperator( QgsExpression::boAnd, geOperator, leOperator );
2095 }
2096 
2097 
2098 QgsExpression::NodeBinaryOperator* QgsOgcUtils::nodePropertyIsNullFromOgcFilter( QDomElement& element, QString& errorMessage )
2099 {
2100  // convert ogc:PropertyIsNull to IS operator with NULL right operand
2101  if ( element.tagName() != "PropertyIsNull" )
2102  {
2103  return nullptr;
2104  }
2105 
2106  QDomElement operandElem = element.firstChildElement();
2107  QgsExpression::Node* opLeft = nodeFromOgcFilter( operandElem, errorMessage );
2108  if ( !opLeft )
2109  return nullptr;
2110 
2112  return new QgsExpression::NodeBinaryOperator( QgsExpression::boIs, opLeft, opRight );
2113 }
2114 
2115 
2117 
2118 
2120 {
2121  return expressionToOgcFilter( exp, doc, GML_2_1_2, FILTER_OGC_1_0,
2122  "geometry", QString(), false, false, errorMessage );
2123 }
2124 
2126 {
2127  return expressionToOgcExpression( exp, doc, GML_2_1_2, FILTER_OGC_1_0,
2128  "geometry", QString(), false, false, errorMessage );
2129 }
2130 
2132  QDomDocument& doc,
2133  GMLVersion gmlVersion,
2134  FilterVersion filterVersion,
2135  const QString& geometryName,
2136  const QString& srsName,
2137  bool honourAxisOrientation,
2138  bool invertAxisOrientation,
2139  QString* errorMessage )
2140 {
2141  if ( !exp.rootNode() )
2142  return QDomElement();
2143 
2144  QgsOgcUtilsExprToFilter utils( doc, gmlVersion, filterVersion, geometryName, srsName, honourAxisOrientation, invertAxisOrientation );
2145  QDomElement exprRootElem = utils.expressionNodeToOgcFilter( exp.rootNode() );
2146  if ( errorMessage )
2147  *errorMessage = utils.errorMessage();
2148  if ( exprRootElem.isNull() )
2149  return QDomElement();
2150 
2151  QDomElement filterElem =
2152  ( filterVersion == FILTER_FES_2_0 ) ?
2153  doc.createElementNS( FES_NAMESPACE, "fes:Filter" ) :
2154  doc.createElementNS( OGC_NAMESPACE, "ogc:Filter" );
2155  if ( utils.GMLNamespaceUsed() )
2156  {
2157  QDomAttr attr = doc.createAttribute( "xmlns:gml" );
2158  if ( gmlVersion == GML_3_2_1 )
2159  attr.setValue( GML32_NAMESPACE );
2160  else
2161  attr.setValue( GML_NAMESPACE );
2162  filterElem.setAttributeNode( attr );
2163  }
2164  filterElem.appendChild( exprRootElem );
2165  return filterElem;
2166 }
2167 
2169  QDomDocument& doc,
2170  GMLVersion gmlVersion,
2171  FilterVersion filterVersion,
2172  const QString& geometryName,
2173  const QString& srsName,
2174  bool honourAxisOrientation,
2175  bool invertAxisOrientation,
2176  QString* errorMessage )
2177 {
2178  const QgsExpression::Node* node = exp.rootNode();
2179  if ( !node )
2180  return QDomElement();
2181 
2182  switch ( node->nodeType() )
2183  {
2187  {
2188  QgsOgcUtilsExprToFilter utils( doc, gmlVersion, filterVersion, geometryName, srsName, honourAxisOrientation, invertAxisOrientation );
2189  QDomElement exprRootElem = utils.expressionNodeToOgcFilter( node );
2190 
2191  if ( errorMessage )
2192  *errorMessage = utils.errorMessage();
2193 
2194  if ( !exprRootElem.isNull() )
2195  {
2196  return exprRootElem;
2197  }
2198  break;
2199  }
2200  default:
2201  *errorMessage = QObject::tr( "Node type not supported in expression translation: %1" ).arg( node->nodeType() );
2202  }
2203  // got an error
2204  return QDomElement();
2205 }
2206 
2208  QDomDocument& doc,
2209  GMLVersion gmlVersion,
2210  FilterVersion filterVersion,
2211  const QList<LayerProperties>& layerProperties,
2212  bool honourAxisOrientation,
2213  bool invertAxisOrientation,
2214  const QMap< QString, QString>& mapUnprefixedTypenameToPrefixedTypename,
2215  QString* errorMessage )
2216 {
2217  if ( !statement.rootNode() )
2218  return QDomElement();
2219 
2220  QgsOgcUtilsSQLStatementToFilter utils( doc, gmlVersion, filterVersion,
2221  layerProperties, honourAxisOrientation, invertAxisOrientation,
2222  mapUnprefixedTypenameToPrefixedTypename );
2223  QDomElement exprRootElem = utils.toOgcFilter( statement.rootNode() );
2224  if ( errorMessage )
2225  *errorMessage = utils.errorMessage();
2226  if ( exprRootElem.isNull() )
2227  return QDomElement();
2228 
2229  QDomElement filterElem =
2230  ( filterVersion == FILTER_FES_2_0 ) ?
2231  doc.createElementNS( FES_NAMESPACE, "fes:Filter" ) :
2232  doc.createElementNS( OGC_NAMESPACE, "ogc:Filter" );
2233  if ( utils.GMLNamespaceUsed() )
2234  {
2235  QDomAttr attr = doc.createAttribute( "xmlns:gml" );
2236  if ( gmlVersion == GML_3_2_1 )
2237  attr.setValue( GML32_NAMESPACE );
2238  else
2239  attr.setValue( GML_NAMESPACE );
2240  filterElem.setAttributeNode( attr );
2241  }
2242  filterElem.appendChild( exprRootElem );
2243  return filterElem;
2244 }
2245 
2246 //
2247 
2248 
2250 {
2251  switch ( node->nodeType() )
2252  {
2254  return expressionUnaryOperatorToOgcFilter( static_cast<const QgsExpression::NodeUnaryOperator*>( node ) );
2256  return expressionBinaryOperatorToOgcFilter( static_cast<const QgsExpression::NodeBinaryOperator*>( node ) );
2258  return expressionInOperatorToOgcFilter( static_cast<const QgsExpression::NodeInOperator*>( node ) );
2260  return expressionFunctionToOgcFilter( static_cast<const QgsExpression::NodeFunction*>( node ) );
2262  return expressionLiteralToOgcFilter( static_cast<const QgsExpression::NodeLiteral*>( node ) );
2264  return expressionColumnRefToOgcFilter( static_cast<const QgsExpression::NodeColumnRef*>( node ) );
2265 
2266  default:
2267  mErrorMessage = QObject::tr( "Node type not supported: %1" ).arg( node->nodeType() );
2268  return QDomElement();
2269  }
2270 }
2271 
2272 
2273 QDomElement QgsOgcUtilsExprToFilter::expressionUnaryOperatorToOgcFilter( const QgsExpression::NodeUnaryOperator* node )
2274 {
2275 
2276  QDomElement operandElem = expressionNodeToOgcFilter( node->operand() );
2277  if ( !mErrorMessage.isEmpty() )
2278  return QDomElement();
2279 
2280  QDomElement uoElem;
2281  switch ( node->op() )
2282  {
2284  uoElem = mDoc.createElement( mFilterPrefix + ":Literal" );
2285  if ( node->operand()->nodeType() == QgsExpression::ntLiteral )
2286  {
2287  // operand expression already created a Literal node:
2288  // take the literal value, prepend - and remove old literal node
2289  uoElem.appendChild( mDoc.createTextNode( "-" + operandElem.text() ) );
2290  mDoc.removeChild( operandElem );
2291  }
2292  else
2293  {
2294  mErrorMessage = QObject::tr( "This use of unary operator not implemented yet" );
2295  return QDomElement();
2296  }
2297  break;
2298  case QgsExpression::uoNot:
2299  uoElem = mDoc.createElement( mFilterPrefix + ":Not" );
2300  uoElem.appendChild( operandElem );
2301  break;
2302 
2303  default:
2304  mErrorMessage = QObject::tr( "Unary operator %1 not implemented yet" ).arg( QgsExpression::UnaryOperatorText[node->op()] );
2305  return QDomElement();
2306  }
2307 
2308  return uoElem;
2309 }
2310 
2311 
2312 QDomElement QgsOgcUtilsExprToFilter::expressionBinaryOperatorToOgcFilter( const QgsExpression::NodeBinaryOperator* node )
2313 {
2314  QDomElement leftElem = expressionNodeToOgcFilter( node->opLeft() );
2315  if ( !mErrorMessage.isEmpty() )
2316  return QDomElement();
2317 
2318  QgsExpression::BinaryOperator op = node->op();
2319 
2320  // before right operator is parsed: to allow NULL handling
2321  if ( op == QgsExpression::boIs || op == QgsExpression::boIsNot )
2322  {
2323  if ( node->opRight()->nodeType() == QgsExpression::ntLiteral )
2324  {
2325  const QgsExpression::NodeLiteral* rightLit = static_cast<const QgsExpression::NodeLiteral*>( node->opRight() );
2326  if ( rightLit->value().isNull() )
2327  {
2328 
2329  QDomElement elem = mDoc.createElement( mFilterPrefix + ":PropertyIsNull" );
2330  elem.appendChild( leftElem );
2331 
2332  if ( op == QgsExpression::boIsNot )
2333  {
2334  QDomElement notElem = mDoc.createElement( mFilterPrefix + ":Not" );
2335  notElem.appendChild( elem );
2336  return notElem;
2337  }
2338 
2339  return elem;
2340  }
2341 
2342  // continue with equal / not equal operator once the null case is handled
2344  }
2345 
2346  }
2347 
2348  QDomElement rightElem = expressionNodeToOgcFilter( node->opRight() );
2349  if ( !mErrorMessage.isEmpty() )
2350  return QDomElement();
2351 
2352 
2353  QString opText = binaryOperatorToTagName( op );
2354  if ( opText.isEmpty() )
2355  {
2356  // not implemented binary operators
2357  // TODO: regex, % (mod), ^ (pow) are not supported yet
2358  mErrorMessage = QObject::tr( "Binary operator %1 not implemented yet" ).arg( QgsExpression::BinaryOperatorText[op] );
2359  return QDomElement();
2360  }
2361 
2362  QDomElement boElem = mDoc.createElement( mFilterPrefix + ":" + opText );
2363 
2364  if ( op == QgsExpression::boLike || op == QgsExpression::boILike )
2365  {
2366  if ( op == QgsExpression::boILike )
2367  boElem.setAttribute( "matchCase", "false" );
2368 
2369  // setup wildCards to <ogc:PropertyIsLike>
2370  boElem.setAttribute( "wildCard", "%" );
2371  boElem.setAttribute( "singleChar", "_" );
2372  if ( mFilterVersion == QgsOgcUtils::FILTER_OGC_1_0 )
2373  boElem.setAttribute( "escape", "\\" );
2374  else
2375  boElem.setAttribute( "escapeChar", "\\" );
2376  }
2377 
2378  boElem.appendChild( leftElem );
2379  boElem.appendChild( rightElem );
2380  return boElem;
2381 }
2382 
2383 
2384 QDomElement QgsOgcUtilsExprToFilter::expressionLiteralToOgcFilter( const QgsExpression::NodeLiteral* node )
2385 {
2386  QString value;
2387  switch ( node->value().type() )
2388  {
2389  case QVariant::Int:
2390  value = QString::number( node->value().toInt() );
2391  break;
2392  case QVariant::Double:
2393  value = qgsDoubleToString( node->value().toDouble() );
2394  break;
2395  case QVariant::String:
2396  value = node->value().toString();
2397  break;
2398 
2399  default:
2400  mErrorMessage = QObject::tr( "Literal type not supported: %1" ).arg( node->value().type() );
2401  return QDomElement();
2402  }
2403 
2404  QDomElement litElem = mDoc.createElement( mFilterPrefix + ":Literal" );
2405  litElem.appendChild( mDoc.createTextNode( value ) );
2406  return litElem;
2407 }
2408 
2409 
2410 QDomElement QgsOgcUtilsExprToFilter::expressionColumnRefToOgcFilter( const QgsExpression::NodeColumnRef* node )
2411 {
2412  QDomElement propElem = mDoc.createElement( mFilterPrefix + ":" + mPropertyName );
2413  propElem.appendChild( mDoc.createTextNode( node->name() ) );
2414  return propElem;
2415 }
2416 
2417 
2418 
2419 QDomElement QgsOgcUtilsExprToFilter::expressionInOperatorToOgcFilter( const QgsExpression::NodeInOperator* node )
2420 {
2421  if ( node->list()->list().size() == 1 )
2422  return expressionNodeToOgcFilter( node->list()->list()[0] );
2423 
2424  QDomElement orElem = mDoc.createElement( mFilterPrefix + ":Or" );
2425  QDomElement leftNode = expressionNodeToOgcFilter( node->node() );
2426 
2427  Q_FOREACH ( QgsExpression::Node* n, node->list()->list() )
2428  {
2429  QDomElement listNode = expressionNodeToOgcFilter( n );
2430  if ( !mErrorMessage.isEmpty() )
2431  return QDomElement();
2432 
2433  QDomElement eqElem = mDoc.createElement( mFilterPrefix + ":PropertyIsEqualTo" );
2434  eqElem.appendChild( leftNode.cloneNode() );
2435  eqElem.appendChild( listNode );
2436 
2437  orElem.appendChild( eqElem );
2438  }
2439 
2440  if ( node->isNotIn() )
2441  {
2442  QDomElement notElem = mDoc.createElement( mFilterPrefix + ":Not" );
2443  notElem.appendChild( orElem );
2444  return notElem;
2445  }
2446 
2447  return orElem;
2448 }
2449 
2451 {
2452  static QMap<QString, QString> binSpatialOps;
2453  if ( binSpatialOps.isEmpty() )
2454  {
2455  binSpatialOps.insert( "disjoint", "Disjoint" );
2456  binSpatialOps.insert( "intersects", "Intersects" );
2457  binSpatialOps.insert( "touches", "Touches" );
2458  binSpatialOps.insert( "crosses", "Crosses" );
2459  binSpatialOps.insert( "contains", "Contains" );
2460  binSpatialOps.insert( "overlaps", "Overlaps" );
2461  binSpatialOps.insert( "within", "Within" );
2462  }
2463  return binSpatialOps;
2464 }
2465 
2466 static bool isBinarySpatialOperator( const QString& fnName )
2467 {
2468  return binarySpatialOpsMap().contains( fnName );
2469 }
2470 
2472 {
2473  return binarySpatialOpsMap().value( fnName );
2474 }
2475 
2476 static bool isGeometryColumn( const QgsExpression::Node* node )
2477 {
2478  if ( node->nodeType() != QgsExpression::ntFunction )
2479  return false;
2480 
2481  const QgsExpression::NodeFunction* fn = static_cast<const QgsExpression::NodeFunction*>( node );
2483  return fd->name() == "$geometry";
2484 }
2485 
2487 {
2488  // Right now we support only geomFromWKT(' ..... ')
2489  // Ideally we should support any constant sub-expression (not dependent on feature's geometry or attributes)
2490 
2491  if ( node->nodeType() == QgsExpression::ntFunction )
2492  {
2493  const QgsExpression::NodeFunction* fnNode = static_cast<const QgsExpression::NodeFunction*>( node );
2495  if ( fnDef->name() == "geom_from_wkt" )
2496  {
2497  const QList<QgsExpression::Node*>& args = fnNode->args()->list();
2498  if ( args[0]->nodeType() == QgsExpression::ntLiteral )
2499  {
2500  QString wkt = static_cast<const QgsExpression::NodeLiteral*>( args[0] )->value().toString();
2501  return QgsGeometry::fromWkt( wkt );
2502  }
2503  }
2504  }
2505  return nullptr;
2506 }
2507 
2508 
2509 QDomElement QgsOgcUtilsExprToFilter::expressionFunctionToOgcFilter( const QgsExpression::NodeFunction* node )
2510 {
2512 
2513  if ( fd->name() == "intersects_bbox" )
2514  {
2515  QList<QgsExpression::Node*> argNodes = node->args()->list();
2516  Q_ASSERT( argNodes.count() == 2 ); // binary spatial ops must have two args
2517 
2518  QgsGeometry* geom = geometryFromConstExpr( argNodes[1] );
2519  if ( geom && isGeometryColumn( argNodes[0] ) )
2520  {
2521  QgsRectangle rect = geom->boundingBox();
2522  delete geom;
2523 
2524  mGMLUsed = true;
2525 
2526  QDomElement elemBox = ( mGMLVersion == QgsOgcUtils::GML_2_1_2 ) ?
2527  QgsOgcUtils::rectangleToGMLBox( &rect, mDoc, mSrsName, mInvertAxisOrientation ) :
2528  QgsOgcUtils::rectangleToGMLEnvelope( &rect, mDoc, mSrsName, mInvertAxisOrientation );
2529 
2530  QDomElement geomProperty = mDoc.createElement( mFilterPrefix + ":" + mPropertyName );
2531  geomProperty.appendChild( mDoc.createTextNode( mGeometryName ) );
2532 
2533  QDomElement funcElem = mDoc.createElement( mFilterPrefix + ":BBOX" );
2534  funcElem.appendChild( geomProperty );
2535  funcElem.appendChild( elemBox );
2536  return funcElem;
2537  }
2538  else
2539  {
2540  delete geom;
2541 
2542  mErrorMessage = QObject::tr( "<BBOX> is currently supported only in form: bbox($geometry, geomFromWKT('...'))" );
2543  return QDomElement();
2544  }
2545  }
2546 
2547  if ( isBinarySpatialOperator( fd->name() ) )
2548  {
2549  QList<QgsExpression::Node*> argNodes = node->args()->list();
2550  Q_ASSERT( argNodes.count() == 2 ); // binary spatial ops must have two args
2551 
2552  QgsExpression::Node* otherNode = nullptr;
2553  if ( isGeometryColumn( argNodes[0] ) )
2554  otherNode = argNodes[1];
2555  else if ( isGeometryColumn( argNodes[1] ) )
2556  otherNode = argNodes[0];
2557  else
2558  {
2559  mErrorMessage = QObject::tr( "Unable to translate spatial operator: at least one must refer to geometry." );
2560  return QDomElement();
2561  }
2562 
2563  QDomElement otherGeomElem;
2564 
2565  // the other node must be a geometry constructor
2566  if ( otherNode->nodeType() != QgsExpression::ntFunction )
2567  {
2568  mErrorMessage = QObject::tr( "spatial operator: the other operator must be a geometry constructor function" );
2569  return QDomElement();
2570  }
2571 
2572  const QgsExpression::NodeFunction* otherFn = static_cast<const QgsExpression::NodeFunction*>( otherNode );
2573  QgsExpression::Function* otherFnDef = QgsExpression::Functions()[otherFn->fnIndex()];
2574  if ( otherFnDef->name() == "geom_from_wkt" )
2575  {
2576  QgsExpression::Node* firstFnArg = otherFn->args()->list()[0];
2577  if ( firstFnArg->nodeType() != QgsExpression::ntLiteral )
2578  {
2579  mErrorMessage = QObject::tr( "geom_from_wkt: argument must be string literal" );
2580  return QDomElement();
2581  }
2582  QString wkt = static_cast<const QgsExpression::NodeLiteral*>( firstFnArg )->value().toString();
2583  QgsGeometry* geom = QgsGeometry::fromWkt( wkt );
2584  otherGeomElem = QgsOgcUtils::geometryToGML( geom, mDoc, mGMLVersion, mSrsName, mInvertAxisOrientation,
2585  QString( "qgis_id_geom_%1" ).arg( mGeomId ) );
2586  mGeomId ++;
2587  delete geom;
2588  }
2589  else if ( otherFnDef->name() == "geom_from_gml" )
2590  {
2591  QgsExpression::Node* firstFnArg = otherFn->args()->list()[0];
2592  if ( firstFnArg->nodeType() != QgsExpression::ntLiteral )
2593  {
2594  mErrorMessage = QObject::tr( "geom_from_gml: argument must be string literal" );
2595  return QDomElement();
2596  }
2597 
2598  QDomDocument geomDoc;
2599  QString gml = static_cast<const QgsExpression::NodeLiteral*>( firstFnArg )->value().toString();
2600  if ( !geomDoc.setContent( gml, true ) )
2601  {
2602  mErrorMessage = QObject::tr( "geom_from_gml: unable to parse XML" );
2603  return QDomElement();
2604  }
2605 
2606  QDomNode geomNode = mDoc.importNode( geomDoc.documentElement(), true );
2607  otherGeomElem = geomNode.toElement();
2608  }
2609  else
2610  {
2611  mErrorMessage = QObject::tr( "spatial operator: unknown geometry constructor function" );
2612  return QDomElement();
2613  }
2614 
2615  mGMLUsed = true;
2616 
2617  QDomElement funcElem = mDoc.createElement( mFilterPrefix + ":" + tagNameForSpatialOperator( fd->name() ) );
2618  QDomElement geomProperty = mDoc.createElement( mFilterPrefix + ":" + mPropertyName );
2619  geomProperty.appendChild( mDoc.createTextNode( mGeometryName ) );
2620  funcElem.appendChild( geomProperty );
2621  funcElem.appendChild( otherGeomElem );
2622  return funcElem;
2623  }
2624 
2625  if ( fd->params() == 0 )
2626  {
2627  mErrorMessage = QObject::tr( "Special columns/constants are not supported." );
2628  return QDomElement();
2629  }
2630 
2631  // this is somehow wrong - we are just hoping that the other side supports the same functions as we do...
2632  QDomElement funcElem = mDoc.createElement( mFilterPrefix + ":Function" );
2633  funcElem.setAttribute( "name", fd->name() );
2634  Q_FOREACH ( QgsExpression::Node* n, node->args()->list() )
2635  {
2636  QDomElement childElem = expressionNodeToOgcFilter( n );
2637  if ( !mErrorMessage.isEmpty() )
2638  return QDomElement();
2639 
2640  funcElem.appendChild( childElem );
2641  }
2642 
2643  return funcElem;
2644 }
2645 
2646 //
2647 
2649  QgsOgcUtils::GMLVersion gmlVersion,
2650  QgsOgcUtils::FilterVersion filterVersion,
2651  const QList<QgsOgcUtils::LayerProperties>& layerProperties,
2652  bool honourAxisOrientation,
2653  bool invertAxisOrientation,
2654  const QMap< QString, QString>& mapUnprefixedTypenameToPrefixedTypename )
2655  : mDoc( doc )
2656  , mGMLUsed( false )
2657  , mGMLVersion( gmlVersion )
2658  , mFilterVersion( filterVersion )
2659  , mLayerProperties( layerProperties )
2660  , mHonourAxisOrientation( honourAxisOrientation )
2661  , mInvertAxisOrientation( invertAxisOrientation )
2662  , mFilterPrefix(( filterVersion == QgsOgcUtils::FILTER_FES_2_0 ) ? "fes" : "ogc" )
2663  , mPropertyName(( filterVersion == QgsOgcUtils::FILTER_FES_2_0 ) ? "ValueReference" : "PropertyName" )
2664  , mGeomId( 1 )
2665  , mMapUnprefixedTypenameToPrefixedTypename( mapUnprefixedTypenameToPrefixedTypename )
2666 {
2667 }
2668 
2670 {
2671  switch ( node->nodeType() )
2672  {
2674  return toOgcFilter( static_cast<const QgsSQLStatement::NodeUnaryOperator*>( node ) );
2676  return toOgcFilter( static_cast<const QgsSQLStatement::NodeBinaryOperator*>( node ) );
2678  return toOgcFilter( static_cast<const QgsSQLStatement::NodeInOperator*>( node ) );
2680  return toOgcFilter( static_cast<const QgsSQLStatement::NodeBetweenOperator*>( node ) );
2682  return toOgcFilter( static_cast<const QgsSQLStatement::NodeFunction*>( node ) );
2684  return toOgcFilter( static_cast<const QgsSQLStatement::NodeLiteral*>( node ) );
2686  return toOgcFilter( static_cast<const QgsSQLStatement::NodeColumnRef*>( node ) );
2688  return toOgcFilter( static_cast<const QgsSQLStatement::NodeSelect*>( node ) );
2689 
2690  default:
2691  mErrorMessage = QObject::tr( "Node type not supported: %1" ).arg( node->nodeType() );
2692  return QDomElement();
2693  }
2694 }
2695 
2696 
2698 {
2699 
2700  QDomElement operandElem = toOgcFilter( node->operand() );
2701  if ( !mErrorMessage.isEmpty() )
2702  return QDomElement();
2703 
2704  QDomElement uoElem;
2705  switch ( node->op() )
2706  {
2708  uoElem = mDoc.createElement( mFilterPrefix + ":Literal" );
2709  if ( node->operand()->nodeType() == QgsSQLStatement::ntLiteral )
2710  {
2711  // operand expression already created a Literal node:
2712  // take the literal value, prepend - and remove old literal node
2713  uoElem.appendChild( mDoc.createTextNode( "-" + operandElem.text() ) );
2714  mDoc.removeChild( operandElem );
2715  }
2716  else
2717  {
2718  mErrorMessage = QObject::tr( "This use of unary operator not implemented yet" );
2719  return QDomElement();
2720  }
2721  break;
2723  uoElem = mDoc.createElement( mFilterPrefix + ":Not" );
2724  uoElem.appendChild( operandElem );
2725  break;
2726 
2727  default:
2728  mErrorMessage = QObject::tr( "Unary operator %1 not implemented yet" ).arg( QgsSQLStatement::UnaryOperatorText[node->op()] );
2729  return QDomElement();
2730  }
2731 
2732  return uoElem;
2733 }
2734 
2735 
2737 {
2738  QDomElement leftElem = toOgcFilter( node->opLeft() );
2739  if ( !mErrorMessage.isEmpty() )
2740  return QDomElement();
2741 
2742  QgsSQLStatement::BinaryOperator op = node->op();
2743 
2744  // before right operator is parsed: to allow NULL handling
2745  if ( op == QgsSQLStatement::boIs || op == QgsSQLStatement::boIsNot )
2746  {
2747  if ( node->opRight()->nodeType() == QgsSQLStatement::ntLiteral )
2748  {
2749  const QgsSQLStatement::NodeLiteral* rightLit = static_cast<const QgsSQLStatement::NodeLiteral*>( node->opRight() );
2750  if ( rightLit->value().isNull() )
2751  {
2752 
2753  QDomElement elem = mDoc.createElement( mFilterPrefix + ":PropertyIsNull" );
2754  elem.appendChild( leftElem );
2755 
2756  if ( op == QgsSQLStatement::boIsNot )
2757  {
2758  QDomElement notElem = mDoc.createElement( mFilterPrefix + ":Not" );
2759  notElem.appendChild( elem );
2760  return notElem;
2761  }
2762 
2763  return elem;
2764  }
2765 
2766  // continue with equal / not equal operator once the null case is handled
2768  }
2769 
2770  }
2771 
2772  QDomElement rightElem = toOgcFilter( node->opRight() );
2773  if ( !mErrorMessage.isEmpty() )
2774  return QDomElement();
2775 
2776 
2777  QString opText;
2778  if ( op == QgsSQLStatement::boOr )
2779  opText = "Or";
2780  else if ( op == QgsSQLStatement::boAnd )
2781  opText = "And";
2782  else if ( op == QgsSQLStatement::boEQ )
2783  opText = "PropertyIsEqualTo";
2784  else if ( op == QgsSQLStatement::boNE )
2785  opText = "PropertyIsNotEqualTo";
2786  else if ( op == QgsSQLStatement::boLE )
2787  opText = "PropertyIsLessThanOrEqualTo";
2788  else if ( op == QgsSQLStatement::boGE )
2789  opText = "PropertyIsGreaterThanOrEqualTo";
2790  else if ( op == QgsSQLStatement::boLT )
2791  opText = "PropertyIsLessThan";
2792  else if ( op == QgsSQLStatement::boGT )
2793  opText = "PropertyIsGreaterThan";
2794  else if ( op == QgsSQLStatement::boLike )
2795  opText = "PropertyIsLike";
2796  else if ( op == QgsSQLStatement::boILike )
2797  opText = "PropertyIsLike";
2798 
2799  if ( opText.isEmpty() )
2800  {
2801  // not implemented binary operators
2802  mErrorMessage = QObject::tr( "Binary operator %1 not implemented yet" ).arg( QgsSQLStatement::BinaryOperatorText[op] );
2803  return QDomElement();
2804  }
2805 
2806  QDomElement boElem = mDoc.createElement( mFilterPrefix + ":" + opText );
2807 
2809  {
2810  if ( op == QgsSQLStatement::boILike )
2811  boElem.setAttribute( "matchCase", "false" );
2812 
2813  // setup wildCards to <ogc:PropertyIsLike>
2814  boElem.setAttribute( "wildCard", "%" );
2815  boElem.setAttribute( "singleChar", "_" );
2816  if ( mFilterVersion == QgsOgcUtils::FILTER_OGC_1_0 )
2817  boElem.setAttribute( "escape", "\\" );
2818  else
2819  boElem.setAttribute( "escapeChar", "\\" );
2820  }
2821 
2822  boElem.appendChild( leftElem );
2823  boElem.appendChild( rightElem );
2824  return boElem;
2825 }
2826 
2827 
2829 {
2830  QString value;
2831  switch ( node->value().type() )
2832  {
2833  case QVariant::Int:
2834  value = QString::number( node->value().toInt() );
2835  break;
2836  case QVariant::LongLong:
2837  value = QString::number( node->value().toLongLong() );
2838  break;
2839  case QVariant::Double:
2840  value = qgsDoubleToString( node->value().toDouble() );
2841  break;
2842  case QVariant::String:
2843  value = node->value().toString();
2844  break;
2845 
2846  default:
2847  mErrorMessage = QObject::tr( "Literal type not supported: %1" ).arg( node->value().type() );
2848  return QDomElement();
2849  }
2850 
2851  QDomElement litElem = mDoc.createElement( mFilterPrefix + ":Literal" );
2852  litElem.appendChild( mDoc.createTextNode( value ) );
2853  return litElem;
2854 }
2855 
2856 
2858 {
2859  QDomElement propElem = mDoc.createElement( mFilterPrefix + ":" + mPropertyName );
2860  if ( node->tableName().isEmpty() || mLayerProperties.size() == 1 )
2861  propElem.appendChild( mDoc.createTextNode( node->name() ) );
2862  else
2863  {
2864  QString tableName( mMapTableAliasToNames[node->tableName()] );
2865  if ( mMapUnprefixedTypenameToPrefixedTypename.contains( tableName ) )
2866  tableName = mMapUnprefixedTypenameToPrefixedTypename[tableName];
2867  propElem.appendChild( mDoc.createTextNode( tableName + "/" + node->name() ) );
2868  }
2869  return propElem;
2870 }
2871 
2873 {
2874  if ( node->list()->list().size() == 1 )
2875  return toOgcFilter( node->list()->list()[0] );
2876 
2877  QDomElement orElem = mDoc.createElement( mFilterPrefix + ":Or" );
2878  QDomElement leftNode = toOgcFilter( node->node() );
2879 
2880  Q_FOREACH ( QgsSQLStatement::Node* n, node->list()->list() )
2881  {
2882  QDomElement listNode = toOgcFilter( n );
2883  if ( !mErrorMessage.isEmpty() )
2884  return QDomElement();
2885 
2886  QDomElement eqElem = mDoc.createElement( mFilterPrefix + ":PropertyIsEqualTo" );
2887  eqElem.appendChild( leftNode.cloneNode() );
2888  eqElem.appendChild( listNode );
2889 
2890  orElem.appendChild( eqElem );
2891  }
2892 
2893  if ( node->isNotIn() )
2894  {
2895  QDomElement notElem = mDoc.createElement( mFilterPrefix + ":Not" );
2896  notElem.appendChild( orElem );
2897  return notElem;
2898  }
2899 
2900  return orElem;
2901 }
2902 
2904 {
2905  QDomElement elem = mDoc.createElement( mFilterPrefix + ":PropertyIsBetween" );
2906  elem.appendChild( toOgcFilter( node->node() ) );
2907  QDomElement lowerBoundary = mDoc.createElement( mFilterPrefix + ":LowerBoundary" );
2908  lowerBoundary.appendChild( toOgcFilter( node->minVal() ) );
2909  elem.appendChild( lowerBoundary );
2910  QDomElement upperBoundary = mDoc.createElement( mFilterPrefix + ":UpperBoundary" );
2911  upperBoundary.appendChild( toOgcFilter( node->maxVal() ) );
2912  elem.appendChild( upperBoundary );
2913 
2914  if ( node->isNotBetween() )
2915  {
2916  QDomElement notElem = mDoc.createElement( mFilterPrefix + ":Not" );
2917  notElem.appendChild( elem );
2918  return notElem;
2919  }
2920 
2921  return elem;
2922 }
2923 
2925 {
2926  QString nameCompare( name );
2927  if ( name.size() > 3 && name.mid( 0, 3 ).compare( "ST_", Qt::CaseInsensitive ) == 0 )
2928  nameCompare = name.mid( 3 );
2929  QStringList spatialOps;
2930  spatialOps << "BBOX" << "Intersects" << "Contains" << "Crosses" << "Equals"
2931  << "Disjoint" << "Overlaps" << "Touches" << "Within";
2932  Q_FOREACH ( QString op, spatialOps )
2933  {
2934  if ( nameCompare.compare( op, Qt::CaseInsensitive ) == 0 )
2935  return op;
2936  }
2937  return QString();
2938 }
2939 
2941 {
2942  QString nameCompare( name );
2943  if ( name.size() > 3 && name.mid( 0, 3 ).compare( "ST_", Qt::CaseInsensitive ) == 0 )
2944  nameCompare = name.mid( 3 );
2945  if ( nameCompare.compare( "DWithin", Qt::CaseInsensitive ) == 0 )
2946  return "DWithin";
2947  if ( nameCompare.compare( "Beyond", Qt::CaseInsensitive ) == 0 )
2948  return "Beyond";
2949  return QString();
2950 }
2951 
2952 QString QgsOgcUtilsSQLStatementToFilter::getGeometryColumnSRSName( const QgsSQLStatement::Node* node )
2953 {
2954  if ( node->nodeType() != QgsSQLStatement::ntColumnRef )
2955  return QString();
2956 
2957  const QgsSQLStatement::NodeColumnRef* col = static_cast<const QgsSQLStatement::NodeColumnRef*>( node );
2958  if ( !col->tableName().isEmpty() )
2959  {
2960  Q_FOREACH ( QgsOgcUtils::LayerProperties prop, mLayerProperties )
2961  {
2962  if ( prop.mName.compare( mMapTableAliasToNames[col->tableName()], Qt::CaseInsensitive ) == 0 &&
2963  prop.mGeometryAttribute.compare( col->name(), Qt::CaseInsensitive ) == 0 )
2964  {
2965  return prop.mSRSName;
2966  }
2967  }
2968  }
2969  if ( mLayerProperties.size() != 0 &&
2970  mLayerProperties.at( 0 ).mGeometryAttribute.compare( col->name(), Qt::CaseInsensitive ) == 0 )
2971  {
2972  return mLayerProperties.at( 0 ).mSRSName;
2973  }
2974  return QString();
2975 }
2976 
2977 bool QgsOgcUtilsSQLStatementToFilter::processSRSName( const QgsSQLStatement::NodeFunction* mainNode,
2979  bool lastArgIsSRSName,
2980  QString& srsName,
2981  bool& axisInversion )
2982 {
2983  srsName = mCurrentSRSName;
2984  axisInversion = mInvertAxisOrientation;
2985 
2986  if ( lastArgIsSRSName )
2987  {
2988  QgsSQLStatement::Node* lastArg = args[ args.size() - 1 ];
2989  if ( lastArg->nodeType() != QgsSQLStatement::ntLiteral )
2990  {
2991  mErrorMessage = QObject::tr( "%1: Last argument must be string or integer literal" ).arg( mainNode->name() );
2992  return false;
2993  }
2994  const QgsSQLStatement::NodeLiteral* lit = static_cast<const QgsSQLStatement::NodeLiteral*>( lastArg );
2995  if ( lit->value().type() == QVariant::Int )
2996  {
2997  if ( mFilterVersion == QgsOgcUtils::FILTER_OGC_1_0 )
2998  {
2999  srsName = "EPSG:" + QString::number( lit->value().toInt() );
3000  }
3001  else
3002  {
3003  srsName = "urn:ogc:def:crs:EPSG::" + QString::number( lit->value().toInt() );
3004  }
3005  }
3006  else
3007  {
3008  srsName = lit->value().toString();
3009  if ( srsName.startsWith( "EPSG:", Qt::CaseInsensitive ) )
3010  return true;
3011  }
3012  }
3013 
3015  if ( !srsName.isEmpty() )
3016  crs = QgsCRSCache::instance()->crsByOgcWmsCrs( srsName );
3017  if ( crs.isValid() )
3018  {
3019  if ( mHonourAxisOrientation && crs.axisInverted() )
3020  {
3021  axisInversion = !axisInversion;
3022  }
3023  }
3024 
3025  return true;
3026 }
3027 
3029 {
3030  // ST_GeometryFromText
3031  if ( node->name().compare( "ST_GeometryFromText", Qt::CaseInsensitive ) == 0 )
3032  {
3033  QList<QgsSQLStatement::Node*> args = node->args()->list();
3034  if ( args.size() != 1 && args.size() != 2 )
3035  {
3036  mErrorMessage = QObject::tr( "Function %1 should have 1 or 2 arguments" ).arg( node->name() );
3037  return QDomElement();
3038  }
3039 
3040  QgsSQLStatement::Node* firstFnArg = args[0];
3041  if ( firstFnArg->nodeType() != QgsSQLStatement::ntLiteral )
3042  {
3043  mErrorMessage = QObject::tr( "%1: First argument must be string literal" ).arg( node->name() );
3044  return QDomElement();
3045  }
3046 
3047  QString srsName;
3048  bool axisInversion;
3049  if ( ! processSRSName( node, args, args.size() == 2, srsName, axisInversion ) )
3050  {
3051  return QDomElement();
3052  }
3053 
3054  QString wkt = static_cast<const QgsSQLStatement::NodeLiteral*>( firstFnArg )->value().toString();
3055  QgsGeometry* geom = QgsGeometry::fromWkt( wkt );
3056  QDomElement geomElem = QgsOgcUtils::geometryToGML( geom, mDoc, mGMLVersion, srsName, axisInversion,
3057  QString( "qgis_id_geom_%1" ).arg( mGeomId ) );
3058  mGeomId ++;
3059  delete geom;
3060  if ( geomElem.isNull() )
3061  {
3062  mErrorMessage = QObject::tr( "%1: invalid WKT" ).arg( node->name() );
3063  return QDomElement();
3064  }
3065  mGMLUsed = true;
3066  return geomElem;
3067  }
3068 
3069  // ST_MakeEnvelope
3070  if ( node->name().compare( "ST_MakeEnvelope", Qt::CaseInsensitive ) == 0 )
3071  {
3072  QList<QgsSQLStatement::Node*> args = node->args()->list();
3073  if ( args.size() != 4 && args.size() != 5 )
3074  {
3075  mErrorMessage = QObject::tr( "Function %1 should have 4 or 5 arguments" ).arg( node->name() );
3076  return QDomElement();
3077  }
3078 
3079  QgsRectangle rect;
3080 
3081  for ( int i = 0; i < 4;i++ )
3082  {
3083  QgsSQLStatement::Node* arg = args[i];
3084  if ( arg->nodeType() != QgsSQLStatement::ntLiteral )
3085  {
3086  mErrorMessage = QObject::tr( "%1: Argument %2 must be numeric literal" ).arg( node->name() ).arg( i + 1 );
3087  return QDomElement();
3088  }
3089  const QgsSQLStatement::NodeLiteral* lit = static_cast<const QgsSQLStatement::NodeLiteral*>( arg );
3090  double val = 0.0;
3091  if ( lit->value().type() == QVariant::Int )
3092  val = lit->value().toInt();
3093  else if ( lit->value().type() == QVariant::LongLong )
3094  val = lit->value().toLongLong();
3095  else if ( lit->value().type() == QVariant::Double )
3096  val = lit->value().toDouble();
3097  else
3098  {
3099  mErrorMessage = QObject::tr( "%1 Argument %2 must be numeric literal" ).arg( node->name() ).arg( i + 1 );
3100  return QDomElement();
3101  }
3102  if ( i == 0 )
3103  rect.setXMinimum( val );
3104  else if ( i == 1 )
3105  rect.setYMinimum( val );
3106  else if ( i == 2 )
3107  rect.setXMaximum( val );
3108  else
3109  rect.setYMaximum( val );
3110  }
3111 
3112  QString srsName;
3113  bool axisInversion;
3114  if ( ! processSRSName( node, args, args.size() == 5, srsName, axisInversion ) )
3115  {
3116  return QDomElement();
3117  }
3118 
3119  mGMLUsed = true;
3120 
3121  return ( mGMLVersion == QgsOgcUtils::GML_2_1_2 ) ?
3122  QgsOgcUtils::rectangleToGMLBox( &rect, mDoc, srsName, axisInversion, 15 ) :
3123  QgsOgcUtils::rectangleToGMLEnvelope( &rect, mDoc, srsName, axisInversion, 15 );
3124  }
3125 
3126  // ST_GeomFromGML
3127  if ( node->name().compare( "ST_GeomFromGML", Qt::CaseInsensitive ) == 0 )
3128  {
3129  QList<QgsSQLStatement::Node*> args = node->args()->list();
3130  if ( args.size() != 1 )
3131  {
3132  mErrorMessage = QObject::tr( "Function %1 should have 1 argument" ).arg( node->name() );
3133  return QDomElement();
3134  }
3135 
3136  QgsSQLStatement::Node* firstFnArg = args[0];
3137  if ( firstFnArg->nodeType() != QgsSQLStatement::ntLiteral )
3138  {
3139  mErrorMessage = QObject::tr( "%1: Argument must be string literal" ).arg( node->name() );
3140  return QDomElement();
3141  }
3142 
3143  QDomDocument geomDoc;
3144  QString gml = static_cast<const QgsSQLStatement::NodeLiteral*>( firstFnArg )->value().toString();
3145  if ( !geomDoc.setContent( gml, true ) )
3146  {
3147  mErrorMessage = QObject::tr( "ST_GeomFromGML: unable to parse XML" );
3148  return QDomElement();
3149  }
3150 
3151  QDomNode geomNode = mDoc.importNode( geomDoc.documentElement(), true );
3152  mGMLUsed = true;
3153  return geomNode.toElement();
3154  }
3155 
3156  // Binary geometry operators
3157  QString ogcName( mapBinarySpatialToOgc( node->name() ) );
3158  if ( !ogcName.isEmpty() )
3159  {
3160  QList<QgsSQLStatement::Node*> args = node->args()->list();
3161  if ( args.size() != 2 )
3162  {
3163  mErrorMessage = QObject::tr( "Function %1 should have 2 arguments" ).arg( node->name() );
3164  return QDomElement();
3165  }
3166 
3167  for ( int i = 0; i < 2; i ++ )
3168  {
3169  if ( args[i]->nodeType() == QgsSQLStatement::ntFunction &&
3170  ( static_cast<const QgsSQLStatement::NodeFunction*>( args[i] )->name().compare( "ST_GeometryFromText", Qt::CaseInsensitive ) == 0 ||
3171  static_cast<const QgsSQLStatement::NodeFunction*>( args[i] )->name().compare( "ST_MakeEnvelope", Qt::CaseInsensitive ) == 0 ) )
3172  {
3173  mCurrentSRSName = getGeometryColumnSRSName( args[1-i] );
3174  break;
3175  }
3176  }
3177 
3178  //if( ogcName == "Intersects" && mFilterVersion == QgsOgcUtils::FILTER_OGC_1_0 )
3179  // ogcName = "Intersect";
3180  QDomElement funcElem = mDoc.createElement( mFilterPrefix + ":" + ogcName );
3181  Q_FOREACH ( QgsSQLStatement::Node* n, args )
3182  {
3183  QDomElement childElem = toOgcFilter( n );
3184  if ( !mErrorMessage.isEmpty() )
3185  {
3186  mCurrentSRSName.clear();
3187  return QDomElement();
3188  }
3189 
3190  funcElem.appendChild( childElem );
3191  }
3192 
3193  mCurrentSRSName.clear();
3194  return funcElem;
3195  }
3196 
3197  ogcName = mapTernarySpatialToOgc( node->name() );
3198  if ( !ogcName.isEmpty() )
3199  {
3200  QList<QgsSQLStatement::Node*> args = node->args()->list();
3201  if ( args.size() != 3 )
3202  {
3203  mErrorMessage = QObject::tr( "Function %1 should have 3 arguments" ).arg( node->name() );
3204  return QDomElement();
3205  }
3206 
3207  for ( int i = 0; i < 2; i ++ )
3208  {
3209  if ( args[i]->nodeType() == QgsSQLStatement::ntFunction &&
3210  ( static_cast<const QgsSQLStatement::NodeFunction*>( args[i] )->name().compare( "ST_GeometryFromText", Qt::CaseInsensitive ) == 0 ||
3211  static_cast<const QgsSQLStatement::NodeFunction*>( args[i] )->name().compare( "ST_MakeEnvelope", Qt::CaseInsensitive ) == 0 ) )
3212  {
3213  mCurrentSRSName = getGeometryColumnSRSName( args[1-i] );
3214  break;
3215  }
3216  }
3217 
3218  QDomElement funcElem = mDoc.createElement( mFilterPrefix + ":" + node->name().mid( 3 ) );
3219  for ( int i = 0; i < 2; i++ )
3220  {
3221  QDomElement childElem = toOgcFilter( args[i] );
3222  if ( !mErrorMessage.isEmpty() )
3223  {
3224  mCurrentSRSName.clear();
3225  return QDomElement();
3226  }
3227 
3228  funcElem.appendChild( childElem );
3229  }
3230  mCurrentSRSName.clear();
3231 
3232  QgsSQLStatement::Node* distanceNode = args[2];
3233  if ( distanceNode->nodeType() != QgsSQLStatement::ntLiteral )
3234  {
3235  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() );
3236  return QDomElement();
3237  }
3238  const QgsSQLStatement::NodeLiteral* lit = static_cast<const QgsSQLStatement::NodeLiteral*>( distanceNode );
3239  if ( lit->value().isNull() )
3240  {
3241  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() );
3242  return QDomElement();
3243  }
3244  QString distance;
3245  QString unit( "m" );
3246  switch ( lit->value().type() )
3247  {
3248  case QVariant::Int:
3249  distance = QString::number( lit->value().toInt() );
3250  break;
3251  case QVariant::LongLong:
3252  distance = QString::number( lit->value().toLongLong() );
3253  break;
3254  case QVariant::Double:
3255  distance = qgsDoubleToString( lit->value().toDouble() );
3256  break;
3257  case QVariant::String:
3258  {
3259  distance = lit->value().toString();
3260  for ( int i = 0; i < distance.size(); i++ )
3261  {
3262  if ( !(( distance[i] >= '0' && distance[i] <= '9' ) || distance[i] == '-' || distance[i] == '.' || distance[i] == 'e' || distance[i] == 'E' ) )
3263  {
3264  unit = distance.mid( i ).trimmed();
3265  distance = distance.mid( 0, i );
3266  break;
3267  }
3268  }
3269  break;
3270  }
3271 
3272  default:
3273  mErrorMessage = QObject::tr( "Literal type not supported: %1" ).arg( lit->value().type() );
3274  return QDomElement();
3275  }
3276 
3277  QDomElement distanceElem = mDoc.createElement( mFilterPrefix + ":Distance" );
3278  if ( mFilterVersion == QgsOgcUtils::FILTER_FES_2_0 )
3279  distanceElem.setAttribute( "uom", unit );
3280  else
3281  distanceElem.setAttribute( "unit", unit );
3282  distanceElem.appendChild( mDoc.createTextNode( distance ) );
3283  funcElem.appendChild( distanceElem );
3284  return funcElem;
3285  }
3286 
3287  // Other function
3288  QDomElement funcElem = mDoc.createElement( mFilterPrefix + ":Function" );
3289  funcElem.setAttribute( "name", node->name() );
3290  Q_FOREACH ( QgsSQLStatement::Node* n, node->args()->list() )
3291  {
3292  QDomElement childElem = toOgcFilter( n );
3293  if ( !mErrorMessage.isEmpty() )
3294  return QDomElement();
3295 
3296  funcElem.appendChild( childElem );
3297  }
3298  return funcElem;
3299 }
3300 
3302  const QString& leftTable )
3303 {
3304  QgsSQLStatement::Node* onExpr = node->onExpr();
3305  if ( onExpr )
3306  {
3307  return toOgcFilter( onExpr );
3308  }
3309 
3310  QList<QDomElement> listElem;
3311  Q_FOREACH ( QString columnName, node->usingColumns() )
3312  {
3313  QDomElement eqElem = mDoc.createElement( mFilterPrefix + ":PropertyIsEqualTo" );
3314  QDomElement propElem1 = mDoc.createElement( mFilterPrefix + ":" + mPropertyName );
3315  propElem1.appendChild( mDoc.createTextNode( leftTable + "/" + columnName ) );
3316  eqElem.appendChild( propElem1 );
3317  QDomElement propElem2 = mDoc.createElement( mFilterPrefix + ":" + mPropertyName );
3318  propElem2.appendChild( mDoc.createTextNode( node->tableDef()->name() + "/" + columnName ) );
3319  eqElem.appendChild( propElem2 );
3320  listElem.append( eqElem );
3321  }
3322 
3323  if ( listElem.size() == 1 )
3324  {
3325  return listElem[0];
3326  }
3327  else if ( listElem.size() > 1 )
3328  {
3329  QDomElement andElem = mDoc.createElement( mFilterPrefix + ":And" );
3330  Q_FOREACH ( QDomElement elem, listElem )
3331  {
3332  andElem.appendChild( elem );
3333  }
3334  return andElem;
3335  }
3336 
3337  return QDomElement();
3338 }
3339 
3340 void QgsOgcUtilsSQLStatementToFilter::visit( const QgsSQLStatement::NodeTableDef* node )
3341 {
3342  if ( node->alias().isEmpty() )
3343  {
3344  mMapTableAliasToNames[ node->name()] = node->name();
3345  }
3346  else
3347  {
3348  mMapTableAliasToNames[ node->alias()] = node->name();
3349  }
3350 }
3351 
3353 {
3354  QList<QDomElement> listElem;
3355 
3356  if ( mFilterVersion != QgsOgcUtils::FILTER_FES_2_0 &&
3357  ( node->tables().size() != 1 || node->joins().size() != 0 ) )
3358  {
3359  mErrorMessage = QObject::tr( "Joins are only supported with WFS 2.0" );
3360  return QDomElement();
3361  }
3362 
3363  // Register all table name aliases
3364  Q_FOREACH ( QgsSQLStatement::NodeTableDef* table, node->tables() )
3365  {
3366  visit( table );
3367  }
3368  Q_FOREACH ( QgsSQLStatement::NodeJoin* join, node->joins() )
3369  {
3370  visit( join->tableDef() );
3371  }
3372 
3373  // Process JOIN conditions
3374  QString leftTable = node->tables().last()->name();
3375  Q_FOREACH ( QgsSQLStatement::NodeJoin* join, node->joins() )
3376  {
3377  QDomElement joinElem = toOgcFilter( join, leftTable );
3378  if ( !mErrorMessage.isEmpty() )
3379  return QDomElement();
3380  listElem.append( joinElem );
3381  leftTable = join->tableDef()->name();
3382  }
3383 
3384  // Process WHERE conditions
3385  if ( node->where() )
3386  {
3387  QDomElement whereElem = toOgcFilter( node->where() );
3388  if ( !mErrorMessage.isEmpty() )
3389  return QDomElement();
3390  listElem.append( whereElem );
3391  }
3392 
3393  // Concatenate all conditions
3394  if ( listElem.size() == 1 )
3395  {
3396  return listElem[0];
3397  }
3398  else if ( listElem.size() > 1 )
3399  {
3400  QDomElement andElem = mDoc.createElement( mFilterPrefix + ":And" );
3401  Q_FOREACH ( QDomElement elem, listElem )
3402  {
3403  andElem.appendChild( elem );
3404  }
3405  return andElem;
3406  }
3407 
3408  return QDomElement();
3409 }
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
virtual NodeType nodeType() const =0
Abstract virtual that returns the type of this node.
A rectangle specified with double values.
Definition: qgsrectangle.h:35
Internal use by QgsOgcUtils.
Definition: qgsogcutils.h:295
QGis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
QgsCoordinateReferenceSystem crsByOgcWmsCrs(const QString &ogcCrs) const
Returns the CRS from a given OGC WMS-format Coordinate Reference System string.
QDomNodeList elementsByTagNameNS(const QString &nsURI, const QString &localName) const
void setValue(const QString &v)
bool contains(const Key &key) const
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)
Node * onExpr() const
On expression.
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[]
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
QString dump() const
Return an expression string, constructed from the internal abstract syntax tree.
QString escape(const QString &str)
const QString & errorMessage() const
Return the error message.
Definition: qgsogcutils.h:360
Node * operand() const
Operand.
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
static bool isGeometryColumn(const QgsExpression::Node *node)
bool GMLNamespaceUsed() const
Return whether the gml: namespace is used.
Definition: qgsogcutils.h:357
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:76
WkbType
Used for symbology operations.
Definition: qgis.h:61
static QDomElement rectangleToGMLBox(QgsRectangle *box, QDomDocument &doc, int precision=17)
Exports the rectangle to GML2 Box.
QDomElement documentElement() const
QDomElement toOgcFilter(const QgsSQLStatement::Node *node)
Convert a SQL statement to a OGC filter.
NodeType nodeType() const
NodeTableDef * tableDef() const
Table definition.
Node * node() const
Variable at the left of BETWEEN.
QString & remove(int position, int n)
QDomElement createElementNS(const QString &nsURI, const QString &qName)
Binary logical/arithmetical operator (AND, OR, =, +, ...)
void setNamedColor(const QString &name)
double toDouble(bool *ok) const
Node * opLeft() const
Left operand.
int wkbSize() const
Returns the size of the WKB in asWkb().
QString tr(const char *sourceText, const char *disambiguation, int n)
Layer properties.
Definition: qgsogcutils.h:190
QString name() const
Return function name.
QgsOgcUtilsSQLStatementToFilter(QDomDocument &doc, QgsOgcUtils::GMLVersion gmlVersion, QgsOgcUtils::FilterVersion filterVersion, const QList< QgsOgcUtils::LayerProperties > &layerProperties, bool honourAxisOrientation, bool invertAxisOrientation, const QMap< QString, QString > &mapUnprefixedTypenameToPrefixedTypename)
Constructor.
static 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)
QList< QString > usingColumns() const
Columns referenced by USING.
void clear()
static QgsRectangle rectangleFromGMLEnvelope(const QDomNode &envelopeNode)
Read rectangle from GML3 Envelope.
const Node * rootNode() const
Returns root node of the expression. Root node is null is parsing has failed.
QDomElement toElement() const
bool GMLNamespaceUsed() const
Return whether the gml: namespace is used.
Definition: qgsogcutils.h:311
static const QList< Function * > & Functions()
static int binaryOperatorFromTagName(const QString &tagName)
QString mName
Layer name.
Definition: qgsogcutils.h:197
int indexIn(const QString &str, int offset, CaretMode caretMode) const
bool isEmpty() const
bool isNotIn() const
Whether this is a NOT IN operator.
static bool isBinaryOperator(const QString &tagName)
void clear()
static const char * UnaryOperatorText[]
Class for parsing SQL statements.
QString number(int n, int base)
int count(const T &value) const
void append(const T &value)
static const QString GML_NAMESPACE
Definition: qgsogcutils.cpp:36
Node * opRight() const
Right operand.
QgsExpressionPrivate * d
#define FALLTHROUGH
Definition: qgis.h:539
QString text() const
QString name() const
Table name.
int toInt(bool *ok) const
bool isNull() const
QString alias() const
Table alias.
static int functionIndex(const QString &name)
return index of the function in Functions array
bool hasAttribute(const QString &name) const
static QString mapTernarySpatialToOgc(const QString &name)
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
Literal value (integer, integer64, double, string)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
Node * where() const
Return the where clause.
QString name() const
The name of the column.
Internal use by QgsOgcUtils.
Definition: qgsogcutils.h:341
Node * node() const
Variable at the left of IN.
QString name() const
The name of the function.
Unary logicial/arithmetical operator ( NOT, - )
const unsigned char * asWkb() const
Returns the buffer containing this geometry in WKB format.
const Node * rootNode() const
Returns root node of the statement. Root node is null is parsing has failed.
BinaryOperator
list of binary operators
QList< Node * > list()
Return list.
static bool isBinarySpatialOperator(const QString &fnName)
QString mSRSName
SRS name.
Definition: qgsogcutils.h:201
static QString tagNameForSpatialOperator(const QString &fnName)
A class to represent a point.
Definition: qgspoint.h:117
bool hasChildNodes() const
bool isNotBetween() const
Whether this is a NOT BETWEEN operator.
static const QMap< QString, int > & binaryOperatorsTagNamesMap()
FilterVersion
OGC filter version.
Definition: qgsogcutils.h:140
QDomText createTextNode(const QString &value)
iterator end()
QString toLower() const
QDomNode removeChild(const QDomNode &oldChild)
BinaryOperator op() const
Operator.
Reference to a column.
NodeList * args() const
Return arguments.
&#39;X BETWEEN y and z&#39; operator
double yMinimum() const
Get the y minimum value (bottom side of rectangle)
Definition: qgsrectangle.h:202
double xMaximum() const
Get the x maximum value (right side of rectangle)
Definition: qgsrectangle.h:187
bool isNull() const
static const QString OGC_NAMESPACE
Definition: qgsogcutils.cpp:38
const QString & errorMessage() const
Return the error message.
Definition: qgsogcutils.h:314
QVariant value() const
The value of the literal.
const Key key(const T &value) const
Node * minVal() const
Minimum bound.
static bool isSpatialOperator(const QString &tagName)
QString & replace(int position, int n, QChar after)
QString name() const
The name of the column.
const_iterator constBegin() const
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.
QDomNode firstChild() const
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.
static QDomElement expressionToOgcExpression(const QgsExpression &exp, QDomDocument &doc, QString *errorMessage=nullptr)
Creates an OGC expression XML element.
QDomAttr setAttributeNode(const QDomAttr &newAttr)
QList< NodeTableDef * > tables() const
Return the list of tables.
QVariant value() const
The value of the literal.
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.
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.
double xMinimum() const
Get the x minimum value (left side of rectangle)
Definition: qgsrectangle.h:192
static QgsGeometry * geometryFromConstExpr(const QgsExpression::Node *node)
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:199
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)
QString tableName() const
The name of the table.
double yMaximum() const
Get the y maximum value (top side of rectangle)
Definition: qgsrectangle.h:197
double toDouble(bool *ok) const
iterator insert(const Key &key, const T &value)
int params() const
The number of parameters this function takes.
typedef const_iterator
void append(Node *node)
Takes ownership of the provided node.
bool isEmpty() const
QString tagName() const
QgsWKBTypes::Type readHeader() const
Definition: qgswkbptr.cpp:38
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
UnaryOperator op() const
Operator.
Type type() const
Node * maxVal() const
Maximum bound.
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
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()
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.
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:167
NodeList * list() const
Values list.
QList< Node * > list()
QDomNode at(int index) const
bool setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
QList< NodeJoin * > joins() const
Return the list of joins.
bool axisInverted() const
Returns whether axis is inverted (eg.
const T value(const Key &key) const
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
static const char * BinaryOperatorText[]