1 /***************************************************************************
2                               qgswfssutils.cpp
3                               -------------------------
4   begin                : December 20 , 2016
5   copyright            : (C) 2007 by Marco Hugentobler  ( parts from qgswmshandler)
6                          (C) 2012 by René-Luc D'Hont    ( parts from qgswmshandler)
7                          (C) 2014 by Alessandro Pasotti ( parts from qgswmshandler)
8                          (C) 2017 by David Marteau
9   email                : marco dot hugentobler at karto dot baug dot ethz dot ch
10                          a dot pasotti at itopen dot it
11                          david dot marteau at 3liz dot com
12  ***************************************************************************/
13 
14 /***************************************************************************
15  *                                                                         *
16  *   This program is free software; you can redistribute it and/or modify  *
17  *   it under the terms of the GNU General Public License as published by  *
18  *   the Free Software Foundation; either version 2 of the License, or     *
19  *   (at your option) any later version.                                  *
20  *                                                                         *
21  ***************************************************************************/
22 
23 #include "qgswfsutils.h"
24 #include "qgsogcutils.h"
25 #include "qgsserverprojectutils.h"
26 #include "qgswfsparameters.h"
27 #include "qgsvectorlayer.h"
28 #include "qgsproject.h"
29 
30 namespace QgsWfs
31 {
implementationVersion()32   QString implementationVersion()
33   {
34     return QStringLiteral( "1.1.0" );
35   }
36 
serviceUrl(const QgsServerRequest & request,const QgsProject * project)37   QString serviceUrl( const QgsServerRequest &request, const QgsProject *project )
38   {
39     QUrl href;
40     if ( project )
41     {
42       href.setUrl( QgsServerProjectUtils::wfsServiceUrl( *project ) );
43     }
44 
45     // Build default url
46     if ( href.isEmpty() )
47     {
48 
49       static QSet<QString> sFilter
50       {
51         QStringLiteral( "REQUEST" ),
52         QStringLiteral( "VERSION" ),
53         QStringLiteral( "SERVICE" ),
54       };
55 
56       href = request.originalUrl();
57       QUrlQuery q( href );
58 
59       const auto constQueryItems = q.queryItems();
60       for ( const auto &param : constQueryItems )
61       {
62         if ( sFilter.contains( param.first.toUpper() ) )
63           q.removeAllQueryItems( param.first );
64       }
65 
66       href.setQuery( q );
67     }
68 
69     return  href.toString();
70   }
71 
layerTypeName(const QgsMapLayer * layer)72   QString layerTypeName( const QgsMapLayer *layer )
73   {
74     QString name = layer->name();
75     if ( !layer->shortName().isEmpty() )
76       name = layer->shortName();
77     name = name.replace( ' ', '_' ).replace( ':', '-' );
78     return name;
79   }
80 
layerByTypeName(const QgsProject * project,const QString & typeName)81   QgsVectorLayer *layerByTypeName( const QgsProject *project, const QString &typeName )
82   {
83     QStringList layerIds = QgsServerProjectUtils::wfsLayerIds( *project );
84     for ( const QString &layerId : qgis::as_const( layerIds ) )
85     {
86       QgsMapLayer *layer = project->mapLayer( layerId );
87       if ( !layer )
88       {
89         continue;
90       }
91       if ( layer->type() != QgsMapLayerType::VectorLayer )
92       {
93         continue;
94       }
95 
96       if ( layerTypeName( layer ) == typeName )
97       {
98         return qobject_cast<QgsVectorLayer *>( layer );
99       }
100     }
101     return nullptr;
102   }
103 
parseFilterElement(const QString & typeName,QDomElement & filterElem,QgsProject * project)104   QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem, QgsProject *project )
105   {
106     // Get the server feature ids in filter element
107     QStringList collectedServerFids;
108     return parseFilterElement( typeName, filterElem, collectedServerFids, project );
109   }
110 
parseFilterElement(const QString & typeName,QDomElement & filterElem,QStringList & serverFids,const QgsProject * project,const QgsMapLayer * layer)111   QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem, QStringList &serverFids, const QgsProject *project, const QgsMapLayer *layer )
112   {
113     QgsFeatureRequest request;
114 
115     QDomNodeList fidNodes = filterElem.elementsByTagName( QStringLiteral( "FeatureId" ) );
116     QDomNodeList goidNodes = filterElem.elementsByTagName( QStringLiteral( "GmlObjectId" ) );
117     if ( !fidNodes.isEmpty() )
118     {
119       // Get the server feature ids in filter element
120       QStringList collectedServerFids;
121       QDomElement fidElem;
122       for ( int f = 0; f < fidNodes.size(); f++ )
123       {
124         fidElem = fidNodes.at( f ).toElement();
125         if ( !fidElem.hasAttribute( QStringLiteral( "fid" ) ) )
126         {
127           throw QgsRequestNotWellFormedException( "FeatureId element without fid attribute" );
128         }
129 
130         QString serverFid = fidElem.attribute( QStringLiteral( "fid" ) );
131         if ( serverFid.contains( QLatin1String( "." ) ) )
132         {
133           if ( serverFid.section( QStringLiteral( "." ), 0, 0 ) != typeName )
134             continue;
135           serverFid = serverFid.section( QStringLiteral( "." ), 1, 1 );
136         }
137         collectedServerFids << serverFid;
138       }
139       // No server feature ids found
140       if ( collectedServerFids.isEmpty() )
141       {
142         throw QgsRequestNotWellFormedException( QStringLiteral( "No FeatureId element correctly parse against typeName '%1'" ).arg( typeName ) );
143       }
144       // update server feature ids
145       serverFids.append( collectedServerFids );
146       request.setFlags( QgsFeatureRequest::NoFlags );
147       return request;
148     }
149     else if ( !goidNodes.isEmpty() )
150     {
151       // Get the server feature ids in filter element
152       QStringList collectedServerFids;
153       QDomElement goidElem;
154       for ( int f = 0; f < goidNodes.size(); f++ )
155       {
156         goidElem = goidNodes.at( f ).toElement();
157         if ( !goidElem.hasAttribute( QStringLiteral( "id" ) ) && !goidElem.hasAttribute( QStringLiteral( "gml:id" ) ) )
158         {
159           throw QgsRequestNotWellFormedException( "GmlObjectId element without gml:id attribute" );
160         }
161 
162         QString serverFid = goidElem.attribute( QStringLiteral( "id" ) );
163         if ( serverFid.isEmpty() )
164           serverFid = goidElem.attribute( QStringLiteral( "gml:id" ) );
165         if ( serverFid.contains( QLatin1String( "." ) ) )
166         {
167           if ( serverFid.section( QStringLiteral( "." ), 0, 0 ) != typeName )
168             continue;
169           serverFid = serverFid.section( QStringLiteral( "." ), 1, 1 );
170         }
171         collectedServerFids << serverFid;
172       }
173       // No server feature ids found
174       if ( collectedServerFids.isEmpty() )
175       {
176         throw QgsRequestNotWellFormedException( QStringLiteral( "No GmlObjectId element correctly parse against typeName '%1'" ).arg( typeName ) );
177       }
178       // update server feature ids
179       serverFids.append( collectedServerFids );
180       request.setFlags( QgsFeatureRequest::NoFlags );
181       return request;
182     }
183     else if ( filterElem.firstChildElement().tagName() == QLatin1String( "BBOX" ) )
184     {
185       QDomElement bboxElem = filterElem.firstChildElement();
186       QDomElement childElem = bboxElem.firstChildElement();
187 
188       while ( !childElem.isNull() )
189       {
190         if ( childElem.tagName() == QLatin1String( "Box" ) )
191         {
192           request.setFilterRect( QgsOgcUtils::rectangleFromGMLBox( childElem ) );
193         }
194         else if ( childElem.tagName() != QLatin1String( "PropertyName" ) )
195         {
196           QgsOgcUtils::Context ctx { layer, project ? project->transformContext() : QgsCoordinateTransformContext() };
197           QgsGeometry geom = QgsOgcUtils::geometryFromGML( childElem, ctx );
198           request.setFilterRect( geom.boundingBox() );
199         }
200         childElem = childElem.nextSiblingElement();
201       }
202 
203       request.setFlags( QgsFeatureRequest::ExactIntersect | QgsFeatureRequest::NoFlags );
204       return request;
205     }
206     // Apply BBOX through filterRect even inside an And to use spatial index
207     else if ( filterElem.firstChildElement().tagName() == QLatin1String( "And" ) &&
208               !filterElem.firstChildElement().firstChildElement( QLatin1String( "BBOX" ) ).isNull() )
209     {
210       int nbChildElem = filterElem.firstChildElement().childNodes().size();
211 
212       // Create a filter element to parse And child not BBOX
213       QDomElement childFilterElement = filterElem.ownerDocument().createElement( QLatin1String( "Filter" ) );
214       if ( nbChildElem > 2 )
215       {
216         QDomElement childAndElement = filterElem.ownerDocument().createElement( QLatin1String( "And" ) );
217         childFilterElement.appendChild( childAndElement );
218       }
219 
220       // Create a filter element to parse  BBOX
221       QDomElement bboxFilterElement = filterElem.ownerDocument().createElement( QLatin1String( "Filter" ) );
222 
223       QDomElement childElem = filterElem.firstChildElement().firstChildElement();
224       while ( !childElem.isNull() )
225       {
226         // Update request based on BBOX
227         if ( childElem.tagName() == QLatin1String( "BBOX" ) )
228         {
229           // Clone BBOX
230           bboxFilterElement.appendChild( childElem.cloneNode( true ) );
231         }
232         else
233         {
234           // Clone And child
235           if ( nbChildElem > 2 )
236           {
237             childFilterElement.firstChildElement().appendChild( childElem.cloneNode( true ) );
238           }
239           else
240           {
241             childFilterElement.appendChild( childElem.cloneNode( true ) );
242           }
243         }
244         childElem = childElem.nextSiblingElement();
245       }
246 
247       // Parse the filter element with the cloned BBOX
248       QStringList collectedServerFids;
249       QgsFeatureRequest bboxRequest = parseFilterElement( typeName, bboxFilterElement, collectedServerFids, project );
250 
251       // Update request based on BBOX
252       if ( request.filterRect().isEmpty() )
253       {
254         request.setFilterRect( bboxRequest.filterRect() );
255       }
256       else
257       {
258         request.setFilterRect( request.filterRect().intersect( bboxRequest.filterRect() ) );
259       }
260 
261       // Parse the filter element with the cloned And child
262       QgsFeatureRequest childRequest = parseFilterElement( typeName, childFilterElement, collectedServerFids, project );
263 
264       // Update server feature ids
265       if ( !collectedServerFids.isEmpty() )
266       {
267         serverFids.append( collectedServerFids );
268       }
269 
270       // Update expression
271       request.setFilterExpression( childRequest.filterExpression()->expression() );
272 
273       request.setFlags( QgsFeatureRequest::ExactIntersect | QgsFeatureRequest::NoFlags );
274       return request;
275     }
276     else
277     {
278       QgsVectorLayer *layer = nullptr;
279       if ( project != nullptr )
280       {
281         layer = layerByTypeName( project, typeName );
282       }
283       std::shared_ptr<QgsExpression> filter( QgsOgcUtils::expressionFromOgcFilter( filterElem, layer ) );
284       if ( filter )
285       {
286         if ( filter->hasParserError() || !filter->parserErrorString().isEmpty() )
287         {
288           throw QgsRequestNotWellFormedException( filter->parserErrorString() );
289         }
290 
291         if ( filter->needsGeometry() )
292         {
293           request.setFlags( QgsFeatureRequest::NoFlags );
294         }
295         request.setFilterExpression( filter->expression() );
296         return request;
297       }
298     }
299     return request;
300   }
301 
302 } // namespace QgsWfs
303 
304 
305