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 ¶m : 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