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