1 /***************************************************************************
2 qgsafsprovider.cpp
3 ------------------
4 begin : May 27, 2015
5 copyright : (C) 2015 Sandro Mani
6 email : smani@sourcepole.ch
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18 #include "qgsafsprovider.h"
19 #include "qgsarcgisrestutils.h"
20 #include "qgsafsfeatureiterator.h"
21 #include "qgsdatasourceuri.h"
22 #include "qgsafsdataitems.h"
23 #include "qgslogger.h"
24 #include "qgsdataitemprovider.h"
25 #include "qgsapplication.h"
26 #include "qgsruntimeprofiler.h"
27
28 const QString QgsAfsProvider::AFS_PROVIDER_KEY = QStringLiteral( "arcgisfeatureserver" );
29 const QString QgsAfsProvider::AFS_PROVIDER_DESCRIPTION = QStringLiteral( "ArcGIS Feature Service data provider" );
30
31
QgsAfsProvider(const QString & uri,const ProviderOptions & options,QgsDataProvider::ReadFlags flags)32 QgsAfsProvider::QgsAfsProvider( const QString &uri, const ProviderOptions &options, QgsDataProvider::ReadFlags flags )
33 : QgsVectorDataProvider( uri, options, flags )
34 {
35 mSharedData.reset( new QgsAfsSharedData() );
36 mSharedData->mGeometryType = QgsWkbTypes::Unknown;
37 mSharedData->mDataSource = QgsDataSourceUri( uri );
38
39 const QString authcfg = mSharedData->mDataSource.authConfigId();
40
41 // Set CRS
42 mSharedData->mSourceCRS.createFromString( mSharedData->mDataSource.param( QStringLiteral( "crs" ) ) );
43
44 // Get layer info
45 QString errorTitle, errorMessage;
46
47 const QString referer = mSharedData->mDataSource.param( QStringLiteral( "referer" ) );
48 if ( !referer.isEmpty() )
49 mRequestHeaders[ QStringLiteral( "Referer" )] = referer;
50
51 std::unique_ptr< QgsScopedRuntimeProfile > profile;
52 if ( QgsApplication::profiler()->groupIsActive( QStringLiteral( "projectload" ) ) )
53 profile = qgis::make_unique< QgsScopedRuntimeProfile >( tr( "Retrieve service definition" ), QStringLiteral( "projectload" ) );
54
55 const QVariantMap layerData = QgsArcGisRestUtils::getLayerInfo( mSharedData->mDataSource.param( QStringLiteral( "url" ) ),
56 authcfg, errorTitle, errorMessage, mRequestHeaders );
57 if ( layerData.isEmpty() )
58 {
59 pushError( errorTitle + ": " + errorMessage );
60 appendError( QgsErrorMessage( tr( "getLayerInfo failed" ), QStringLiteral( "AFSProvider" ) ) );
61 return;
62 }
63 mLayerName = layerData[QStringLiteral( "name" )].toString();
64 mLayerDescription = layerData[QStringLiteral( "description" )].toString();
65
66 // Set extent
67 QStringList coords = mSharedData->mDataSource.param( QStringLiteral( "bbox" ) ).split( ',' );
68 bool limitBbox = false;
69 if ( coords.size() == 4 )
70 {
71 bool xminOk = false, yminOk = false, xmaxOk = false, ymaxOk = false;
72 mSharedData->mExtent.setXMinimum( coords[0].toDouble( &xminOk ) );
73 mSharedData->mExtent.setYMinimum( coords[1].toDouble( &yminOk ) );
74 mSharedData->mExtent.setXMaximum( coords[2].toDouble( &xmaxOk ) );
75 mSharedData->mExtent.setYMaximum( coords[3].toDouble( &ymaxOk ) );
76 if ( !xminOk || !yminOk || !xmaxOk || !ymaxOk )
77 mSharedData->mExtent = QgsRectangle();
78 else
79 {
80 // user has set a bounding box limit on the layer - so we only EVER fetch features from this extent
81 limitBbox = true;
82 }
83 }
84
85 const QVariantMap layerExtentMap = layerData[QStringLiteral( "extent" )].toMap();
86 bool xminOk = false, yminOk = false, xmaxOk = false, ymaxOk = false;
87 QgsRectangle originalExtent;
88 originalExtent.setXMinimum( layerExtentMap[QStringLiteral( "xmin" )].toDouble( &xminOk ) );
89 originalExtent.setYMinimum( layerExtentMap[QStringLiteral( "ymin" )].toDouble( &yminOk ) );
90 originalExtent.setXMaximum( layerExtentMap[QStringLiteral( "xmax" )].toDouble( &xmaxOk ) );
91 originalExtent.setYMaximum( layerExtentMap[QStringLiteral( "ymax" )].toDouble( &ymaxOk ) );
92 if ( mSharedData->mExtent.isEmpty() && ( !xminOk || !yminOk || !xmaxOk || !ymaxOk ) )
93 {
94 appendError( QgsErrorMessage( tr( "Could not retrieve layer extent" ), QStringLiteral( "AFSProvider" ) ) );
95 return;
96 }
97 QgsCoordinateReferenceSystem extentCrs = QgsArcGisRestUtils::parseSpatialReference( layerExtentMap[QStringLiteral( "spatialReference" )].toMap() );
98 if ( mSharedData->mExtent.isEmpty() && !extentCrs.isValid() )
99 {
100 appendError( QgsErrorMessage( tr( "Could not parse spatial reference" ), QStringLiteral( "AFSProvider" ) ) );
101 return;
102 }
103
104 if ( xminOk && yminOk && xmaxOk && ymaxOk )
105 {
106 QgsLayerMetadata::SpatialExtent spatialExtent;
107 spatialExtent.bounds = QgsBox3d( originalExtent );
108 spatialExtent.extentCrs = extentCrs;
109 QgsLayerMetadata::Extent metadataExtent;
110 metadataExtent.setSpatialExtents( QList< QgsLayerMetadata::SpatialExtent >() << spatialExtent );
111 mLayerMetadata.setExtent( metadataExtent );
112 }
113 if ( extentCrs.isValid() )
114 {
115 mLayerMetadata.setCrs( extentCrs );
116 }
117
118 if ( mSharedData->mExtent.isEmpty() )
119 {
120 mSharedData->mExtent = originalExtent;
121 QgsCoordinateTransform ct( extentCrs, mSharedData->mSourceCRS, options.transformContext );
122 try
123 {
124 mSharedData->mExtent = ct.transformBoundingBox( mSharedData->mExtent );
125 }
126 catch ( QgsCsException & )
127 {
128 QgsDebugMsg( QStringLiteral( "Exception raised while transforming layer extent" ) );
129 }
130 }
131
132 QString objectIdFieldName;
133
134 // Read fields
135 const QVariantList fields = layerData.value( QStringLiteral( "fields" ) ).toList();
136 for ( const QVariant &fieldData : fields )
137 {
138 const QVariantMap fieldDataMap = fieldData.toMap();
139 const QString fieldName = fieldDataMap[QStringLiteral( "name" )].toString();
140 const QString fieldAlias = fieldDataMap[QStringLiteral( "alias" )].toString();
141 const QString fieldTypeString = fieldDataMap[QStringLiteral( "type" )].toString();
142 QVariant::Type type = QgsArcGisRestUtils::mapEsriFieldType( fieldTypeString );
143 if ( fieldName == QLatin1String( "geometry" ) || fieldTypeString == QLatin1String( "esriFieldTypeGeometry" ) )
144 {
145 // skip geometry field
146 continue;
147 }
148 if ( fieldTypeString == QLatin1String( "esriFieldTypeOID" ) )
149 {
150 objectIdFieldName = fieldName;
151 }
152 if ( type == QVariant::Invalid )
153 {
154 QgsDebugMsg( QStringLiteral( "Skipping unsupported field %1 of type %2" ).arg( fieldName, fieldTypeString ) );
155 continue;
156 }
157 QgsField field( fieldName, type, fieldDataMap[QStringLiteral( "type" )].toString(), fieldDataMap[QStringLiteral( "length" )].toInt() );
158 if ( !fieldAlias.isEmpty() && fieldAlias != fieldName )
159 field.setAlias( fieldAlias );
160
161 if ( fieldDataMap.contains( QStringLiteral( "domain" ) ) && fieldDataMap.value( QStringLiteral( "domain" ) ).toMap().value( QStringLiteral( "type" ) ).toString() == QLatin1String( "codedValue" ) )
162 {
163 const QVariantList values = fieldDataMap.value( QStringLiteral( "domain" ) ).toMap().value( QStringLiteral( "codedValues" ) ).toList();
164 QVariantList valueConfig;
165 valueConfig.reserve( values.count() );
166 for ( const QVariant &v : values )
167 {
168 const QVariantMap value = v.toMap();
169 QVariantMap config;
170 config[ value.value( QStringLiteral( "name" ) ).toString() ] = value.value( QStringLiteral( "code" ) );
171 valueConfig.append( config );
172 }
173 QVariantMap editorConfig;
174 editorConfig.insert( QStringLiteral( "map" ), valueConfig );
175 field.setEditorWidgetSetup( QgsEditorWidgetSetup( QStringLiteral( "ValueMap" ), editorConfig ) );
176 }
177
178 mSharedData->mFields.append( field );
179 }
180 if ( objectIdFieldName.isEmpty() )
181 objectIdFieldName = QStringLiteral( "objectid" );
182
183 // Determine geometry type
184 bool hasM = layerData[QStringLiteral( "hasM" )].toBool();
185 bool hasZ = layerData[QStringLiteral( "hasZ" )].toBool();
186 mSharedData->mGeometryType = QgsArcGisRestUtils::mapEsriGeometryType( layerData[QStringLiteral( "geometryType" )].toString() );
187 if ( mSharedData->mGeometryType == QgsWkbTypes::Unknown )
188 {
189 if ( layerData.value( QStringLiteral( "serviceDataType" ) ).toString().startsWith( QLatin1String( "esriImageService" ) ) )
190 {
191 // it's possible to connect to ImageServers as a feature service, to view tile boundaries
192 mSharedData->mGeometryType = QgsWkbTypes::Polygon;
193 }
194 else
195 {
196 appendError( QgsErrorMessage( tr( "Failed to determine geometry type" ), QStringLiteral( "AFSProvider" ) ) );
197 return;
198 }
199 }
200 mSharedData->mGeometryType = QgsWkbTypes::zmType( mSharedData->mGeometryType, hasZ, hasM );
201
202 // read temporal properties
203 if ( layerData.contains( QStringLiteral( "timeInfo" ) ) )
204 {
205 const QVariantMap timeInfo = layerData.value( QStringLiteral( "timeInfo" ) ).toMap();
206
207 temporalCapabilities()->setHasTemporalCapabilities( true );
208 temporalCapabilities()->setStartField( timeInfo.value( QStringLiteral( "startTimeField" ) ).toString() );
209 temporalCapabilities()->setEndField( timeInfo.value( QStringLiteral( "endTimeField" ) ).toString() );
210 if ( !temporalCapabilities()->endField().isEmpty() )
211 temporalCapabilities()->setMode( QgsVectorDataProviderTemporalCapabilities::ProviderStoresFeatureDateTimeStartAndEndInSeparateFields );
212 else if ( !temporalCapabilities()->startField().isEmpty() )
213 temporalCapabilities()->setMode( QgsVectorDataProviderTemporalCapabilities::ProviderStoresFeatureDateTimeInstantInField );
214 else
215 temporalCapabilities()->setMode( QgsVectorDataProviderTemporalCapabilities::ProviderHasFixedTemporalRange );
216
217 const QVariantList extent = timeInfo.value( QStringLiteral( "timeExtent" ) ).toList();
218 if ( extent.size() == 2 )
219 {
220 temporalCapabilities()->setAvailableTemporalRange( QgsDateTimeRange( QgsArcGisRestUtils::parseDateTime( extent.at( 0 ) ),
221 QgsArcGisRestUtils::parseDateTime( extent.at( 1 ) ) ) );
222 }
223 }
224
225 if ( profile )
226 profile->switchTask( tr( "Retrieve object IDs" ) );
227
228 // Read OBJECTIDs of all features: these may not be a continuous sequence,
229 // and we need to store these to iterate through the features. This query
230 // also returns the name of the ObjectID field.
231 QVariantMap objectIdData = QgsArcGisRestUtils::getObjectIds( mSharedData->mDataSource.param( QStringLiteral( "url" ) ), authcfg,
232 errorTitle, errorMessage, mRequestHeaders, limitBbox ? mSharedData->mExtent : QgsRectangle() );
233 if ( objectIdData.isEmpty() )
234 {
235 appendError( QgsErrorMessage( tr( "getObjectIds failed: %1 - %2" ).arg( errorTitle, errorMessage ), QStringLiteral( "AFSProvider" ) ) );
236 return;
237 }
238 if ( !objectIdData[QStringLiteral( "objectIdFieldName" )].isValid() || !objectIdData[QStringLiteral( "objectIds" )].isValid() )
239 {
240 appendError( QgsErrorMessage( tr( "Failed to determine objectIdFieldName and/or objectIds" ), QStringLiteral( "AFSProvider" ) ) );
241 return;
242 }
243 mSharedData->mObjectIdFieldName = objectIdData[QStringLiteral( "objectIdFieldName" )].toString();
244 for ( int idx = 0, nIdx = mSharedData->mFields.count(); idx < nIdx; ++idx )
245 {
246 if ( mSharedData->mFields.at( idx ).name() == mSharedData->mObjectIdFieldName )
247 {
248 mObjectIdFieldIdx = idx;
249
250 // primary key is not null, unique
251 QgsFieldConstraints constraints = mSharedData->mFields.at( idx ).constraints();
252 constraints.setConstraint( QgsFieldConstraints::ConstraintNotNull, QgsFieldConstraints::ConstraintOriginProvider );
253 constraints.setConstraint( QgsFieldConstraints::ConstraintUnique, QgsFieldConstraints::ConstraintOriginProvider );
254 mSharedData->mFields[ idx ].setConstraints( constraints );
255
256 break;
257 }
258 }
259 const QVariantList objectIds = objectIdData.value( QStringLiteral( "objectIds" ) ).toList();
260 for ( const QVariant &objectId : objectIds )
261 {
262 mSharedData->mObjectIds.append( objectId.toInt() );
263 }
264
265 // layer metadata
266
267 mLayerMetadata.setIdentifier( mSharedData->mDataSource.param( QStringLiteral( "url" ) ) );
268 const QString parentIdentifier = layerData[QStringLiteral( "parentLayer" )].toMap().value( QStringLiteral( "id" ) ).toString();
269 if ( !parentIdentifier.isEmpty() )
270 {
271 const QString childUrl = mSharedData->mDataSource.param( QStringLiteral( "url" ) );
272 const QString parentUrl = childUrl.left( childUrl.lastIndexOf( '/' ) ) + '/' + parentIdentifier;
273 mLayerMetadata.setParentIdentifier( parentUrl );
274 }
275 mLayerMetadata.setType( QStringLiteral( "dataset" ) );
276 mLayerMetadata.setAbstract( mLayerDescription );
277 mLayerMetadata.setTitle( mLayerName );
278 QString copyright = layerData[QStringLiteral( "copyrightText" )].toString();
279 if ( !copyright.isEmpty() )
280 mLayerMetadata.setRights( QStringList() << copyright );
281 mLayerMetadata.addLink( QgsAbstractMetadataBase::Link( tr( "Source" ), QStringLiteral( "WWW:LINK" ), mSharedData->mDataSource.param( QStringLiteral( "url" ) ) ) );
282
283 // renderer
284 mRendererDataMap = layerData.value( QStringLiteral( "drawingInfo" ) ).toMap().value( QStringLiteral( "renderer" ) ).toMap();
285 mLabelingDataList = layerData.value( QStringLiteral( "drawingInfo" ) ).toMap().value( QStringLiteral( "labelingInfo" ) ).toList();
286
287 mValid = true;
288 }
289
featureSource() const290 QgsAbstractFeatureSource *QgsAfsProvider::featureSource() const
291 {
292 return new QgsAfsFeatureSource( mSharedData );
293 }
294
getFeatures(const QgsFeatureRequest & request) const295 QgsFeatureIterator QgsAfsProvider::getFeatures( const QgsFeatureRequest &request ) const
296 {
297 return new QgsAfsFeatureIterator( new QgsAfsFeatureSource( mSharedData ), true, request );
298 }
299
wkbType() const300 QgsWkbTypes::Type QgsAfsProvider::wkbType() const
301 {
302 return mSharedData->mGeometryType;
303 }
304
featureCount() const305 long QgsAfsProvider::featureCount() const
306 {
307 return mSharedData->mObjectIds.size();
308 }
309
fields() const310 QgsFields QgsAfsProvider::fields() const
311 {
312 return mSharedData->mFields;
313 }
314
layerMetadata() const315 QgsLayerMetadata QgsAfsProvider::layerMetadata() const
316 {
317 return mLayerMetadata;
318 }
319
capabilities() const320 QgsVectorDataProvider::Capabilities QgsAfsProvider::capabilities() const
321 {
322 QgsVectorDataProvider::Capabilities c = QgsVectorDataProvider::SelectAtId | QgsVectorDataProvider::ReadLayerMetadata;
323 if ( !mRendererDataMap.empty() )
324 {
325 c = c | QgsVectorDataProvider::CreateRenderer;
326 }
327 if ( !mLabelingDataList.empty() )
328 {
329 c = c | QgsVectorDataProvider::CreateLabeling;
330 }
331 return c;
332 }
333
setDataSourceUri(const QString & uri)334 void QgsAfsProvider::setDataSourceUri( const QString &uri )
335 {
336 mSharedData->mDataSource = QgsDataSourceUri( uri );
337 QgsDataProvider::setDataSourceUri( uri );
338 }
339
crs() const340 QgsCoordinateReferenceSystem QgsAfsProvider::crs() const
341 {
342 return mSharedData->crs();
343 }
344
extent() const345 QgsRectangle QgsAfsProvider::extent() const
346 {
347 return mSharedData->extent();
348 }
349
name() const350 QString QgsAfsProvider::name() const
351 {
352 return AFS_PROVIDER_KEY;
353 }
354
description() const355 QString QgsAfsProvider::description() const
356 {
357 return AFS_PROVIDER_DESCRIPTION;
358 }
359
dataComment() const360 QString QgsAfsProvider::dataComment() const
361 {
362 return mLayerDescription;
363 }
364
reloadProviderData()365 void QgsAfsProvider::reloadProviderData()
366 {
367 mSharedData->clearCache();
368 }
369
createRenderer(const QVariantMap &) const370 QgsFeatureRenderer *QgsAfsProvider::createRenderer( const QVariantMap & ) const
371 {
372 return QgsArcGisRestUtils::parseEsriRenderer( mRendererDataMap );
373 }
374
createLabeling(const QVariantMap &) const375 QgsAbstractVectorLayerLabeling *QgsAfsProvider::createLabeling( const QVariantMap & ) const
376 {
377 return QgsArcGisRestUtils::parseEsriLabeling( mLabelingDataList );
378 }
379
renderInPreview(const QgsDataProvider::PreviewContext &)380 bool QgsAfsProvider::renderInPreview( const QgsDataProvider::PreviewContext & )
381 {
382 // these servers can be sloooooooow, and unpredictable. The previous preview job may have been fast to render,
383 // but the next may take minutes or worse to download...
384 return false;
385 }
386
387
QgsAfsProviderMetadata()388 QgsAfsProviderMetadata::QgsAfsProviderMetadata():
389 QgsProviderMetadata( QgsAfsProvider::AFS_PROVIDER_KEY, QgsAfsProvider::AFS_PROVIDER_DESCRIPTION )
390 {
391 }
392
dataItemProviders() const393 QList<QgsDataItemProvider *> QgsAfsProviderMetadata::dataItemProviders() const
394 {
395 QList<QgsDataItemProvider *> providers;
396
397 providers
398 << new QgsAfsDataItemProvider;
399
400 return providers;
401 }
402
decodeUri(const QString & uri)403 QVariantMap QgsAfsProviderMetadata::decodeUri( const QString &uri )
404 {
405 QgsDataSourceUri dsUri = QgsDataSourceUri( uri );
406
407 QVariantMap components;
408 components.insert( QStringLiteral( "url" ), dsUri.param( QStringLiteral( "url" ) ) );
409 const QStringList bbox = dsUri.param( QStringLiteral( "bbox" ) ).split( ',' );
410 if ( bbox.size() == 4 )
411 {
412 QgsRectangle r;
413 bool xminOk = false;
414 bool yminOk = false;
415 bool xmaxOk = false;
416 bool ymaxOk = false;
417 r.setXMinimum( bbox[0].toDouble( &xminOk ) );
418 r.setYMinimum( bbox[1].toDouble( &yminOk ) );
419 r.setXMaximum( bbox[2].toDouble( &xmaxOk ) );
420 r.setYMaximum( bbox[3].toDouble( &ymaxOk ) );
421 if ( xminOk && yminOk && xmaxOk && ymaxOk )
422 components.insert( QStringLiteral( "bounds" ), r );
423 }
424 if ( !dsUri.param( QStringLiteral( "referer" ) ).isEmpty() )
425 {
426 components.insert( QStringLiteral( "referer" ), dsUri.param( QStringLiteral( "referer" ) ) );
427 }
428 if ( !dsUri.param( QStringLiteral( "crs" ) ).isEmpty() )
429 {
430 components.insert( QStringLiteral( "crs" ), dsUri.param( QStringLiteral( "crs" ) ) );
431 }
432 if ( !dsUri.authConfigId().isEmpty() )
433 {
434 components.insert( QStringLiteral( "authcfg" ), dsUri.authConfigId() );
435 }
436 return components;
437 }
438
encodeUri(const QVariantMap & parts)439 QString QgsAfsProviderMetadata::encodeUri( const QVariantMap &parts )
440 {
441 QgsDataSourceUri dsUri;
442 dsUri.setParam( QStringLiteral( "url" ), parts.value( QStringLiteral( "url" ) ).toString() );
443
444 if ( parts.contains( QStringLiteral( "bounds" ) ) && parts.value( QStringLiteral( "bounds" ) ).canConvert< QgsRectangle >() )
445 {
446 const QgsRectangle bBox = parts.value( QStringLiteral( "bounds" ) ).value< QgsRectangle >();
447 dsUri.setParam( QStringLiteral( "bbox" ), QStringLiteral( "%1,%2,%3,%4" ).arg( bBox.xMinimum() ).arg( bBox.yMinimum() ).arg( bBox.xMaximum() ).arg( bBox.yMaximum() ) );
448 }
449
450 if ( !parts.value( QStringLiteral( "crs" ) ).toString().isEmpty() )
451 {
452 dsUri.setParam( QStringLiteral( "crs" ), parts.value( QStringLiteral( "crs" ) ).toString() );
453 }
454 if ( !parts.value( QStringLiteral( "referer" ) ).toString().isEmpty() )
455 {
456 dsUri.setParam( QStringLiteral( "referer" ), parts.value( QStringLiteral( "referer" ) ).toString() );
457 }
458 if ( !parts.value( QStringLiteral( "authcfg" ) ).toString().isEmpty() )
459 {
460 dsUri.setAuthConfigId( parts.value( QStringLiteral( "authcfg" ) ).toString() );
461 }
462 return dsUri.uri( false );
463 }
464
createProvider(const QString & uri,const QgsDataProvider::ProviderOptions & options,QgsDataProvider::ReadFlags flags)465 QgsAfsProvider *QgsAfsProviderMetadata::createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags )
466 {
467 return new QgsAfsProvider( uri, options, flags );
468 }
469
470
providerMetadataFactory()471 QGISEXTERN QgsProviderMetadata *providerMetadataFactory()
472 {
473 return new QgsAfsProviderMetadata();
474 }
475