1 /***************************************************************************
2     qgsogcutils.h
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 #ifndef QGSOGCUTILS_H
16 #define QGSOGCUTILS_H
17 
18 class QColor;
19 class QDomNode;
20 class QDomElement;
21 class QDomDocument;
22 class QString;
23 
24 #include "qgis_core.h"
25 #include "qgis_sip.h"
26 #include <list>
27 #include <QVector>
28 
29 class QgsExpression;
30 class QgsGeometry;
31 class QgsPointXY;
32 class QgsRectangle;
33 class QgsVectorLayer;
34 
35 #include "qgsgeometry.h"
36 #include "qgsexpression.h"
37 #include "qgsexpressionnode.h"
38 #include "qgsexpressionnodeimpl.h"
39 #include "qgssqlstatement.h"
40 #include "qgscoordinatetransformcontext.h"
41 
42 /**
43  * \ingroup core
44  * \brief The QgsOgcUtils class provides various utility functions for conversion between
45  *   OGC (Open Geospatial Consortium) standards and QGIS internal representations.
46  *
47  * Currently supported standards:
48  *
49  * - GML2 - Geography Markup Language (import, export)
50  */
51 class CORE_EXPORT QgsOgcUtils
52 {
53   public:
54 
55     /**
56      * The Context struct stores the current layer and coordinate transform context.
57      * \since QGIS 3.14
58      */
59     struct Context
60     {
61 
62       /**
63        * Constructs a Context from \a layer and \a transformContext
64        */
65       Context( const QgsMapLayer *layer = nullptr, const QgsCoordinateTransformContext &transformContext = QgsCoordinateTransformContext() )
layerContext66         : layer( layer )
67         , transformContext( transformContext )
68       {
69       }
70       const QgsMapLayer *layer = nullptr;
71       QgsCoordinateTransformContext transformContext;
72     };
73 
74     /**
75      *GML version
76      */
77     enum GMLVersion
78     {
79       GML_2_1_2,
80       GML_3_1_0,
81       GML_3_2_1,
82     };
83 
84     /**
85      * Static method that creates geometry from GML
86      * \param xmlString xml representation of the geometry. GML elements are expected to be
87      *  in default namespace (\verbatim {<Point>...</Point> \endverbatim) or in
88      *  "gml" namespace (\verbatim <gml:Point>...</gml:Point> \endverbatim)
89      * \param context QgsOgcUtils context
90      */
91     static QgsGeometry geometryFromGML( const QString &xmlString, const QgsOgcUtils::Context &context = QgsOgcUtils::Context() );
92 
93     /**
94      * Static method that creates geometry from GML
95       */
96     static QgsGeometry geometryFromGML( const QDomNode &geometryNode, const QgsOgcUtils::Context &context = QgsOgcUtils::Context() );
97 
98     //! Read rectangle from GML2 Box
99     static QgsRectangle rectangleFromGMLBox( const QDomNode &boxNode );
100 
101     //! Read rectangle from GML3 Envelope
102     static QgsRectangle rectangleFromGMLEnvelope( const QDomNode &envelopeNode );
103 
104     /**
105      * Exports the geometry to GML
106       * \returns QDomElement
107       * \since QGIS 2.16
108      */
109     static QDomElement geometryToGML( const QgsGeometry &geometry, QDomDocument &doc,
110                                       QgsOgcUtils::GMLVersion gmlVersion,
111                                       const QString &srsName,
112                                       bool invertAxisOrientation,
113                                       const QString &gmlIdBase,
114                                       int precision = 17 );
115 
116     /**
117      * Exports the geometry to GML2 or GML3
118      * \returns QDomElement
119      */
120     static QDomElement geometryToGML( const QgsGeometry &geometry, QDomDocument &doc, const QString &format, int precision = 17 );
121 
122     /**
123      * Exports the geometry to GML2
124      * \returns QDomElement
125      */
126     static QDomElement geometryToGML( const QgsGeometry &geometry, QDomDocument &doc, int precision = 17 );
127 
128     /**
129      * Exports the rectangle to GML2 Box
130      * \returns QDomElement
131      */
132     static QDomElement rectangleToGMLBox( QgsRectangle *box, QDomDocument &doc, int precision = 17 );
133 
134     /**
135      * Exports the rectangle to GML2 Box
136      * \returns QDomElement
137      * \since QGIS 2.16
138      */
139     static QDomElement rectangleToGMLBox( QgsRectangle *box, QDomDocument &doc,
140                                           const QString &srsName,
141                                           bool invertAxisOrientation,
142                                           int precision = 17 );
143 
144     /**
145      * Exports the rectangle to GML3 Envelope
146      * \returns QDomElement
147      */
148     static QDomElement rectangleToGMLEnvelope( QgsRectangle *env, QDomDocument &doc, int precision = 17 );
149 
150     /**
151      * Exports the rectangle to GML3 Envelope
152      * \returns QDomElement
153      * \since QGIS 2.16
154      */
155     static QDomElement rectangleToGMLEnvelope( QgsRectangle *env, QDomDocument &doc,
156         const QString &srsName,
157         bool invertAxisOrientation,
158         int precision = 17 );
159 
160 
161     //! Parse XML with OGC fill into QColor
162     static QColor colorFromOgcFill( const QDomElement &fillElement );
163 
164     //! Parse XML with OGC filter into QGIS expression
165     static QgsExpression *expressionFromOgcFilter( const QDomElement &element, QgsVectorLayer *layer = nullptr ) SIP_FACTORY;
166 
167     /**
168      * Creates OGC filter XML element. Supports minimum standard filter
169      * according to the OGC filter specs (=,!=,<,>,<=,>=,AND,OR,NOT)
170      * \returns valid \verbatim <Filter> \endverbatim QDomElement on success,
171      * otherwise null QDomElement
172      */
173     static QDomElement expressionToOgcFilter( const QgsExpression &exp, QDomDocument &doc, QString *errorMessage = nullptr );
174 
175     /**
176      * OGC filter version
177      */
178     enum FilterVersion
179     {
180       FILTER_OGC_1_0,
181       FILTER_OGC_1_1,
182       FILTER_FES_2_0
183     };
184 
185     /**
186      * Returns an expression from a WFS filter embedded in a document.
187      * \param element The WFS Filter
188      * \param version The WFS version
189      * \param layer Layer to use to retrieve field values from literal filters
190      * \since QGIS 3.4
191      */
192     static QgsExpression *expressionFromOgcFilter( const QDomElement &element, FilterVersion version, QgsVectorLayer *layer = nullptr ) SIP_FACTORY;
193 
194     /**
195      * Creates OGC filter XML element. Supports minimum standard filter
196      * according to the OGC filter specs (=,!=,<,>,<=,>=,AND,OR,NOT)
197      * \returns valid \verbatim <Filter> \endverbatim QDomElement on success,
198      * otherwise null QDomElement
199      * \note not available in Python bindings
200      * \since QGIS 2.16
201      */
202     static QDomElement expressionToOgcFilter( const QgsExpression &exp,
203         QDomDocument &doc,
204         QgsOgcUtils::GMLVersion gmlVersion,
205         FilterVersion filterVersion,
206         const QString &geometryName,
207         const QString &srsName,
208         bool honourAxisOrientation,
209         bool invertAxisOrientation,
210         QString *errorMessage = nullptr ) SIP_SKIP;
211 
212     /**
213      * Creates an OGC expression XML element.
214      * \returns valid OGC expression QDomElement on success,
215      * otherwise null QDomElement
216      */
217     static QDomElement expressionToOgcExpression( const QgsExpression &exp, QDomDocument &doc, QString *errorMessage = nullptr );
218 
219     /**
220      * Creates an OGC expression XML element.
221      * \returns valid OGC expression QDomElement on success,
222      * otherwise null QDomElement
223      */
224     static QDomElement expressionToOgcExpression( const QgsExpression &exp,
225         QDomDocument &doc,
226         QgsOgcUtils::GMLVersion gmlVersion,
227         FilterVersion filterVersion,
228         const QString &geometryName,
229         const QString &srsName,
230         bool honourAxisOrientation,
231         bool invertAxisOrientation,
232         QString *errorMessage = nullptr );
233 
234 #ifndef SIP_RUN
235 
236     /**
237      * \ingroup core
238      * \brief Layer properties. Used by SQLStatementToOgcFilter().
239      * \note not available in Python bindings
240      * \since QGIS 2.16
241      */
242     class LayerProperties
243     {
244       public:
245         //! Constructor
246         LayerProperties() = default;
247 
248         //! Layer name
249         QString mName;
250         //! Geometry attribute name
251         QString mGeometryAttribute;
252         //! SRS name
253         QString mSRSName;
254         //! Namespace prefix
255         QString mNamespacePrefix;
256         //! Namespace URI
257         QString mNamespaceURI;
258     };
259 #endif
260 
261     /**
262      * Creates OGC filter XML element from the WHERE and JOIN clauses of a SQL
263      * statement. Supports minimum standard filter
264      * according to the OGC filter specs (=,!=,<,>,<=,>=,AND,OR,NOT,LIKE,BETWEEN,IN)
265      * Supports layer joins.
266      * Supports ST_GeometryFromText(wkt[, srid/srsname]),
267      *          ST_MakeEnvelope(xmin,ymin,xmax,ymax[, srid/srsname])
268      *          ST_GeomFromGML(serialized_gml_string)
269      *          BBOX()
270      *          ST_Intersects(), ST_Contains(), ST_Crosses(), ST_Equals(),
271      *          ST_Disjoint(), ST_Overlaps(), ST_Touches(), ST_Within()
272      *          ST_DWithin(), ST_Beyond()
273      *          custom functions
274      * \returns valid \verbatim <Filter> \endverbatim QDomElement on success,
275      * otherwise null QDomElement
276      * \note not available in Python bindings
277      * \since QGIS 2.16
278      */
279     static QDomElement SQLStatementToOgcFilter( const QgsSQLStatement &statement,
280         QDomDocument &doc,
281         QgsOgcUtils::GMLVersion gmlVersion,
282         FilterVersion filterVersion,
283         const QList<LayerProperties> &layerProperties,
284         bool honourAxisOrientation,
285         bool invertAxisOrientation,
286         const QMap< QString, QString> &mapUnprefixedTypenameToPrefixedTypename,
287         QString *errorMessage = nullptr ) SIP_SKIP;
288 
289   private:
290 
291     //! Static method that creates geometry from GML Point
292     static QgsGeometry geometryFromGMLPoint( const QDomElement &geometryElement );
293     //! Static method that creates geometry from GML LineString
294     static QgsGeometry geometryFromGMLLineString( const QDomElement &geometryElement );
295     //! Static method that creates geometry from GML Polygon
296     static QgsGeometry geometryFromGMLPolygon( const QDomElement &geometryElement );
297     //! Static method that creates geometry from GML MultiPoint
298     static QgsGeometry geometryFromGMLMultiPoint( const QDomElement &geometryElement );
299     //! Static method that creates geometry from GML MultiLineString
300     static QgsGeometry geometryFromGMLMultiLineString( const QDomElement &geometryElement );
301     //! Static method that creates geometry from GML MultiPolygon
302     static QgsGeometry geometryFromGMLMultiPolygon( const QDomElement &geometryElement );
303 
304     /**
305      * Reads the \verbatim <gml:coordinates> \endverbatim element and extracts the coordinates as points
306      * \param coords list where the found coordinates are appended
307      * \param elem the \verbatim <gml:coordinates> \endverbatim element
308      * \returns boolean FALSE on success
309     */
310     static bool readGMLCoordinates( QgsPolylineXY &coords, const QDomElement &elem );
311 
312     /**
313      * Reads the \verbatim <gml:pos> \endverbatim or \verbatim <gml:posList> \endverbatim
314      * and extracts the coordinates as points
315      * \param coords list where the found coordinates are appended
316      * \param elem the \verbatim <gml:pos> \endverbatim or
317      *              \verbatim <gml:posList> \endverbatim element
318      * \returns boolean FALSE on success
319      */
320     static bool readGMLPositions( QgsPolylineXY &coords, const QDomElement &elem );
321 
322 
323     /**
324      * Create a GML coordinates element from a point list.
325      * \param points list of data points
326      * \param doc the GML document
327      * \returns QDomElement
328     */
329     static QDomElement createGMLCoordinates( const QgsPolylineXY &points, QDomDocument &doc );
330 
331     /**
332      * Create a GML pos or posList element from a point list.
333      * \param points list of data points
334      * \param doc the GML document
335      * \returns QDomElement
336     */
337     static QDomElement createGMLPositions( const QgsPolylineXY &points, QDomDocument &doc );
338 
339     //! handle a generic sub-expression
340     static QgsExpressionNode *nodeFromOgcFilter( QDomElement &element, QString &errorMessage, QgsVectorLayer *layer = nullptr );
341     //! handle a generic binary operator
342     static QgsExpressionNodeBinaryOperator *nodeBinaryOperatorFromOgcFilter( QDomElement &element, QString &errorMessage, QgsVectorLayer *layer = nullptr );
343     //! handles various spatial operation tags (\verbatim <Intersects> \endverbatim, \verbatim <Touches> \endverbatim etc.)
344     static QgsExpressionNodeFunction *nodeSpatialOperatorFromOgcFilter( QDomElement &element, QString &errorMessage );
345     //! handle \verbatim <Not> \endverbatim tag
346     static QgsExpressionNodeUnaryOperator *nodeNotFromOgcFilter( QDomElement &element, QString &errorMessage );
347     //! handles \verbatim <Function> \endverbatim tag
348     static QgsExpressionNodeFunction *nodeFunctionFromOgcFilter( QDomElement &element, QString &errorMessage );
349     //! handles \verbatim <Literal> \endverbatim tag
350     static QgsExpressionNode *nodeLiteralFromOgcFilter( QDomElement &element, QString &errorMessage, QgsVectorLayer *layer = nullptr );
351     //! handles \verbatim <PropertyName> \endverbatim tag
352     static QgsExpressionNodeColumnRef *nodeColumnRefFromOgcFilter( QDomElement &element, QString &errorMessage );
353     //! handles \verbatim <PropertyIsBetween> \endverbatim tag
354     static QgsExpressionNode *nodeIsBetweenFromOgcFilter( QDomElement &element, QString &errorMessage );
355     //! handles \verbatim <PropertyIsNull> \endverbatim tag
356     static QgsExpressionNodeBinaryOperator *nodePropertyIsNullFromOgcFilter( QDomElement &element, QString &errorMessage );
357 };
358 
359 #ifndef SIP_RUN
360 
361 /**
362  * \ingroup core
363  * \brief Internal use by QgsOgcUtils
364  * \note not available in Python bindings
365  */
366 class QgsOgcUtilsExprToFilter
367 {
368   public:
369     //! Constructor
370     QgsOgcUtilsExprToFilter( QDomDocument &doc,
371                              QgsOgcUtils::GMLVersion gmlVersion,
372                              QgsOgcUtils::FilterVersion filterVersion,
373                              const QString &geometryName,
374                              const QString &srsName,
375                              bool honourAxisOrientation,
376                              bool invertAxisOrientation );
377 
378     //! Convert an expression to a OGC filter
379     QDomElement expressionNodeToOgcFilter( const QgsExpressionNode *node, QgsExpression *expression, const QgsExpressionContext *context );
380 
381     //! Returns whether the gml: namespace is used
GMLNamespaceUsed()382     bool GMLNamespaceUsed() const { return mGMLUsed; }
383 
384     //! Returns the error message.
errorMessage()385     QString errorMessage() const { return mErrorMessage; }
386 
387   private:
388     QDomDocument &mDoc;
389     bool mGMLUsed;
390     QgsOgcUtils::GMLVersion mGMLVersion;
391     QgsOgcUtils::FilterVersion mFilterVersion;
392     const QString &mGeometryName;
393     const QString &mSrsName;
394     bool mInvertAxisOrientation;
395     QString mErrorMessage;
396     QString mFilterPrefix;
397     QString mPropertyName;
398     int mGeomId;
399 
400     QDomElement expressionUnaryOperatorToOgcFilter( const QgsExpressionNodeUnaryOperator *node, QgsExpression *expression, const QgsExpressionContext *context );
401     QDomElement expressionBinaryOperatorToOgcFilter( const QgsExpressionNodeBinaryOperator *node, QgsExpression *expression, const QgsExpressionContext *context );
402     QDomElement expressionLiteralToOgcFilter( const QgsExpressionNodeLiteral *node, QgsExpression *expression, const QgsExpressionContext *context );
403     QDomElement expressionColumnRefToOgcFilter( const QgsExpressionNodeColumnRef *node, QgsExpression *expression, const QgsExpressionContext *context );
404     QDomElement expressionInOperatorToOgcFilter( const QgsExpressionNodeInOperator *node, QgsExpression *expression, const QgsExpressionContext *context );
405     QDomElement expressionFunctionToOgcFilter( const QgsExpressionNodeFunction *node, QgsExpression *expression, const QgsExpressionContext *context );
406 };
407 
408 /**
409  * \ingroup core
410  * \brief Internal use by QgsOgcUtils
411  * \note not available in Python bindings
412  * \since QGIS 3.4
413  */
414 class QgsOgcUtilsExpressionFromFilter
415 {
416   public:
417 
418     /**
419      * Constructor for QgsOgcUtilsExpressionFromFilter.
420      * \param version WFS Version
421      * \param layer Layer to use to retrieve field values from literal filters
422      */
423     QgsOgcUtilsExpressionFromFilter( QgsOgcUtils::FilterVersion version = QgsOgcUtils::FILTER_OGC_1_0,
424                                      const QgsVectorLayer *layer = nullptr );
425 
426     /**
427      * Returns an expression node from a WFS filter embedded in a document
428      * element. NULLPTR is returned when an error happened.
429      * \param element The WFS filter
430      */
431     QgsExpressionNode *nodeFromOgcFilter( const QDomElement &element );
432 
433     /**
434      * Returns the underlying error message, or an empty string in case of no
435      * error.
436      */
437     QString errorMessage() const;
438 
439     /**
440      * Returns an expression node from a WFS filter embedded in a document with
441      * binary operators.
442      *
443      */
444     QgsExpressionNodeBinaryOperator *nodeBinaryOperatorFromOgcFilter( const QDomElement &element );
445 
446     /**
447      * Returns an expression node from a WFS filter embedded in a document with
448      * spatial operators.
449      */
450     QgsExpressionNodeFunction *nodeSpatialOperatorFromOgcFilter( const QDomElement &element );
451 
452     /**
453      * Returns an expression node from a WFS filter embedded in a document with
454      * column references.
455      */
456     QgsExpressionNodeColumnRef *nodeColumnRefFromOgcFilter( const QDomElement &element );
457 
458     /**
459      * Returns an expression node from a WFS filter embedded in a document with
460      * literal tag.
461      */
462     QgsExpressionNode *nodeLiteralFromOgcFilter( const QDomElement &element );
463 
464     /**
465      * Returns an expression node from a WFS filter embedded in a document with
466      * Not operator.
467      */
468     QgsExpressionNodeUnaryOperator *nodeNotFromOgcFilter( const QDomElement &element );
469 
470     /**
471      * Returns an expression node from a WFS filter embedded in a document with
472      * IsNull operator.
473      */
474     QgsExpressionNodeBinaryOperator *nodePropertyIsNullFromOgcFilter( const QDomElement &element );
475 
476     /**
477      * Returns an expression node from a WFS filter embedded in a document with
478      * functions.
479      */
480     QgsExpressionNodeFunction *nodeFunctionFromOgcFilter( const QDomElement &element );
481 
482     /**
483      * Returns an expression node from a WFS filter embedded in a document with
484      * boudnaries operator.
485      */
486     QgsExpressionNode *nodeIsBetweenFromOgcFilter( const QDomElement &element );
487 
488   private:
489     const QgsVectorLayer *mLayer = nullptr;
490     QString mErrorMessage;
491     QString mPropertyName;
492     QString mPrefix;
493 };
494 
495 /**
496  * \ingroup core
497  * \brief Internal use by QgsOgcUtils
498  * \note not available in Python bindings
499  */
500 class QgsOgcUtilsSQLStatementToFilter
501 {
502   public:
503     //! Constructor
504     QgsOgcUtilsSQLStatementToFilter( QDomDocument &doc,
505                                      QgsOgcUtils::GMLVersion gmlVersion,
506                                      QgsOgcUtils::FilterVersion filterVersion,
507                                      const QList<QgsOgcUtils::LayerProperties> &layerProperties,
508                                      bool honourAxisOrientation,
509                                      bool invertAxisOrientation,
510                                      const QMap< QString, QString> &mapUnprefixedTypenameToPrefixedTypename );
511 
512     //! Convert a SQL statement to a OGC filter
513     QDomElement toOgcFilter( const QgsSQLStatement::Node *node );
514 
515     //! Returns whether the gml: namespace is used
GMLNamespaceUsed()516     bool GMLNamespaceUsed() const { return mGMLUsed; }
517 
518     //! Returns the error message.
errorMessage()519     QString errorMessage() const { return mErrorMessage; }
520 
521   private:
522     QDomDocument &mDoc;
523     bool mGMLUsed;
524     QgsOgcUtils::GMLVersion mGMLVersion;
525     QgsOgcUtils::FilterVersion mFilterVersion;
526     const QList<QgsOgcUtils::LayerProperties> &mLayerProperties;
527     bool mHonourAxisOrientation;
528     bool mInvertAxisOrientation;
529     QString mErrorMessage;
530     QString mFilterPrefix;
531     QString mPropertyName;
532     int mGeomId;
533     QString mCurrentSRSName;
534     QMap<QString, QString> mMapTableAliasToNames;
535     const QMap< QString, QString> &mMapUnprefixedTypenameToPrefixedTypename;
536 
537     QDomElement toOgcFilter( const QgsSQLStatement::NodeUnaryOperator *node );
538     QDomElement toOgcFilter( const QgsSQLStatement::NodeBinaryOperator *node );
539     QDomElement toOgcFilter( const QgsSQLStatement::NodeLiteral *node );
540     QDomElement toOgcFilter( const QgsSQLStatement::NodeColumnRef *node );
541     QDomElement toOgcFilter( const QgsSQLStatement::NodeInOperator *node );
542     QDomElement toOgcFilter( const QgsSQLStatement::NodeBetweenOperator *node );
543     QDomElement toOgcFilter( const QgsSQLStatement::NodeFunction *node );
544     QDomElement toOgcFilter( const QgsSQLStatement::NodeJoin *node, const QString &leftTable );
545     QDomElement toOgcFilter( const QgsSQLStatement::NodeSelect *node );
546 
547     void visit( const QgsSQLStatement::NodeTableDef *node );
548     QString getGeometryColumnSRSName( const QgsSQLStatement::Node *node );
549     bool processSRSName( const QgsSQLStatement::NodeFunction *mainNode,
550                          QList<QgsSQLStatement::Node *> args,
551                          bool lastArgIsSRSName,
552                          QString &srsName,
553                          bool &axisInversion );
554 };
555 #endif // #ifndef SIP_RUN
556 
557 #endif // QGSOGCUTILS_H
558