1 /***************************************************************************
2 qgsoapifprovider.cpp
3 ---------------------
4 begin : October 2019
5 copyright : (C) 2019 by Even Rouault
6 email : even.rouault at spatialys.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
16 #include "qgsexpressionnodeimpl.h"
17 #include "qgslogger.h"
18 #include "qgsmessagelog.h"
19 #include "qgsoapifprovider.h"
20 #include "qgsoapiflandingpagerequest.h"
21 #include "qgsoapifapirequest.h"
22 #include "qgsoapifcollection.h"
23 #include "qgsoapifitemsrequest.h"
24 #include "qgswfsconstants.h"
25 #include "qgswfsutils.h" // for isCompatibleType()
26
27 #include <algorithm>
28
29 const QString QgsOapifProvider::OAPIF_PROVIDER_KEY = QStringLiteral( "OAPIF" );
30 const QString QgsOapifProvider::OAPIF_PROVIDER_DESCRIPTION = QStringLiteral( "OGC API - Features data provider" );
31
32
QgsOapifProvider(const QString & uri,const ProviderOptions & options,QgsDataProvider::ReadFlags flags)33 QgsOapifProvider::QgsOapifProvider( const QString &uri, const ProviderOptions &options, QgsDataProvider::ReadFlags flags )
34 : QgsVectorDataProvider( uri, options, flags ),
35 mShared( new QgsOapifSharedData( uri ) )
36 {
37
38 connect( mShared.get(), &QgsOapifSharedData::raiseError, this, &QgsOapifProvider::pushErrorSlot );
39 connect( mShared.get(), &QgsOapifSharedData::extentUpdated, this, &QgsOapifProvider::fullExtentCalculated );
40
41 if ( uri.isEmpty() )
42 {
43 mValid = false;
44 return;
45 }
46
47 mShared->mSourceCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( QStringLiteral( "EPSG:4326" ) );
48
49 mSubsetString = mShared->mURI.filter();
50
51 if ( !init() )
52 {
53 mValid = false;
54 return;
55 }
56
57 QString errorMsg;
58 if ( !mShared->computeServerFilter( errorMsg ) )
59 {
60 QgsMessageLog::logMessage( errorMsg, tr( "OAPIF" ) );
61 mValid = false;
62 return;
63 }
64 }
65
~QgsOapifProvider()66 QgsOapifProvider::~QgsOapifProvider()
67 {
68 }
69
init()70 bool QgsOapifProvider::init()
71 {
72 const bool synchronous = true;
73 const bool forceRefresh = false;
74
75 const QString url = QgsDataSourceUri( mShared->mURI.uri() ).param( QgsWFSConstants::URI_PARAM_URL );
76 const int pos = url.indexOf( '?' );
77 if ( pos >= 0 )
78 {
79 mShared->mExtraQueryParameters = url.mid( pos + 1 );
80 }
81
82 QgsOapifLandingPageRequest landingPageRequest( mShared->mURI.uri() );
83 if ( !landingPageRequest.request( synchronous, forceRefresh ) )
84 return false;
85 if ( landingPageRequest.errorCode() != QgsBaseNetworkRequest::NoError )
86 return false;
87
88 QgsOapifApiRequest apiRequest( mShared->mURI.uri(), mShared->appendExtraQueryParameters( landingPageRequest.apiUrl() ) );
89 if ( !apiRequest.request( synchronous, forceRefresh ) )
90 return false;
91 if ( apiRequest.errorCode() != QgsBaseNetworkRequest::NoError )
92 return false;
93
94 mShared->mServerMaxFeatures = apiRequest.maxLimit();
95
96 if ( mShared->mURI.maxNumFeatures() > 0 && mShared->mServerMaxFeatures > 0 && !mShared->mURI.pagingEnabled() )
97 {
98 mShared->mMaxFeatures = std::min( mShared->mURI.maxNumFeatures(), mShared->mServerMaxFeatures );
99 }
100 else if ( mShared->mURI.maxNumFeatures() > 0 )
101 {
102 mShared->mMaxFeatures = mShared->mURI.maxNumFeatures();
103 }
104 else if ( mShared->mServerMaxFeatures > 0 && !mShared->mURI.pagingEnabled() )
105 {
106 mShared->mMaxFeatures = mShared->mServerMaxFeatures;
107 }
108
109 if ( mShared->mURI.pagingEnabled() && mShared->mURI.pageSize() > 0 )
110 {
111 if ( mShared->mServerMaxFeatures > 0 )
112 {
113 mShared->mPageSize = std::min( mShared->mURI.pageSize(), mShared->mServerMaxFeatures );
114 }
115 else
116 {
117 mShared->mPageSize = mShared->mURI.pageSize();
118 }
119 }
120 else if ( mShared->mURI.pagingEnabled() )
121 {
122 if ( apiRequest.defaultLimit() > 0 && apiRequest.maxLimit() > 0 )
123 {
124 // Use the default, but if it is below 1000, aim for 1000
125 // but clamp to the maximum limit
126 mShared->mPageSize = std::min( std::max( 1000, apiRequest.defaultLimit() ), apiRequest.maxLimit() );
127 }
128 else if ( apiRequest.defaultLimit() > 0 )
129 mShared->mPageSize = std::max( 1000, apiRequest.defaultLimit() );
130 else if ( apiRequest.maxLimit() > 0 )
131 mShared->mPageSize = apiRequest.maxLimit();
132 else
133 mShared->mPageSize = 100; // fallback to arbitrary page size
134 }
135
136 mShared->mCollectionUrl =
137 landingPageRequest.collectionsUrl() + QStringLiteral( "/" ) + mShared->mURI.typeName();
138 QgsOapifCollectionRequest collectionRequest( mShared->mURI.uri(), mShared->appendExtraQueryParameters( mShared->mCollectionUrl ) );
139 if ( !collectionRequest.request( synchronous, forceRefresh ) )
140 return false;
141 if ( collectionRequest.errorCode() != QgsBaseNetworkRequest::NoError )
142 return false;
143
144 mShared->mCapabilityExtent = collectionRequest.collection().mBbox;
145
146 mLayerMetadata = collectionRequest.collection().mLayerMetadata;
147 // Merge contact info from /api
148 mLayerMetadata.setContacts( apiRequest.metadata().contacts() );
149
150 mShared->mItemsUrl = mShared->mCollectionUrl + QStringLiteral( "/items" );
151
152 QgsOapifItemsRequest itemsRequest( mShared->mURI.uri(), mShared->appendExtraQueryParameters( mShared->mItemsUrl + QStringLiteral( "?limit=10" ) ) );
153 if ( mShared->mCapabilityExtent.isNull() )
154 {
155 itemsRequest.setComputeBbox();
156 }
157 if ( !itemsRequest.request( synchronous, forceRefresh ) )
158 return false;
159 if ( itemsRequest.errorCode() != QgsBaseNetworkRequest::NoError )
160 return false;
161
162 if ( itemsRequest.numberMatched() >= 0 )
163 {
164 mShared->mHasNumberMatched = true;
165 if ( mSubsetString.isEmpty() )
166 {
167 mShared->setFeatureCount( itemsRequest.numberMatched(), true );
168 }
169 }
170
171 if ( mShared->mCapabilityExtent.isNull() )
172 {
173 mShared->mCapabilityExtent = itemsRequest.bbox();
174 }
175
176 mShared->mFields = itemsRequest.fields();
177 mShared->mWKBType = itemsRequest.wkbType();
178
179 return true;
180 }
181
pushErrorSlot(const QString & errorMsg)182 void QgsOapifProvider::pushErrorSlot( const QString &errorMsg )
183 {
184 pushError( errorMsg );
185 }
186
featureSource() const187 QgsAbstractFeatureSource *QgsOapifProvider::featureSource() const
188 {
189 return new QgsBackgroundCachedFeatureSource( mShared );
190 }
191
getFeatures(const QgsFeatureRequest & request) const192 QgsFeatureIterator QgsOapifProvider::getFeatures( const QgsFeatureRequest &request ) const
193 {
194 return QgsFeatureIterator( new QgsBackgroundCachedFeatureIterator( new QgsBackgroundCachedFeatureSource( mShared ), true, mShared, request ) );
195 }
196
wkbType() const197 QgsWkbTypes::Type QgsOapifProvider::wkbType() const
198 {
199 return mShared->mWKBType;
200 }
201
featureCount() const202 long long QgsOapifProvider::featureCount() const
203 {
204 if ( mUpdateFeatureCountAtNextFeatureCountRequest )
205 {
206 mUpdateFeatureCountAtNextFeatureCountRequest = false;
207
208 QgsFeature f;
209 QgsFeatureRequest request;
210 request.setNoAttributes();
211 auto iter = getFeatures( request );
212 long long count = 0;
213 bool countExact = true;
214 while ( iter.nextFeature( f ) )
215 {
216 if ( count == 1000 ) // to avoid too long processing time
217 {
218 countExact = false;
219 break;
220 }
221 count ++;
222 }
223
224 mShared->setFeatureCount( count, countExact );
225 }
226 return mShared->getFeatureCount();
227 }
228
fields() const229 QgsFields QgsOapifProvider::fields() const
230 {
231 return mShared->mFields;
232 }
233
crs() const234 QgsCoordinateReferenceSystem QgsOapifProvider::crs() const
235 {
236 return mShared->mSourceCrs;
237 }
238
extent() const239 QgsRectangle QgsOapifProvider::extent() const
240 {
241 return mShared->consolidatedExtent();
242 }
243
reloadProviderData()244 void QgsOapifProvider::reloadProviderData()
245 {
246 mUpdateFeatureCountAtNextFeatureCountRequest = true;
247 mShared->invalidateCache();
248 }
249
isValid() const250 bool QgsOapifProvider::isValid() const
251 {
252 return mValid;
253 }
254
capabilities() const255 QgsVectorDataProvider::Capabilities QgsOapifProvider::capabilities() const
256 {
257 return QgsVectorDataProvider::SelectAtId | QgsVectorDataProvider::ReadLayerMetadata | QgsVectorDataProvider::Capability::ReloadData;
258 }
259
empty() const260 bool QgsOapifProvider::empty() const
261 {
262 if ( subsetString().isEmpty() && mShared->isFeatureCountExact() )
263 {
264 return mShared->getFeatureCount( false ) == 0;
265 }
266
267 QgsFeature f;
268 QgsFeatureRequest request;
269 request.setNoAttributes();
270 request.setFlags( QgsFeatureRequest::NoGeometry );
271
272 // Whoops, the provider returns an empty iterator when we are using
273 // a setLimit call in combination with a subsetString.
274 // Remove this method (and default to the QgsVectorDataProvider one)
275 // once this is fixed
276 #if 0
277 request.setLimit( 1 );
278 #endif
279 return !getFeatures( request ).nextFeature( f );
280
281 };
282
setSubsetString(const QString & filter,bool updateFeatureCount)283 bool QgsOapifProvider::setSubsetString( const QString &filter, bool updateFeatureCount )
284 {
285 QgsDebugMsgLevel( QStringLiteral( "filter = '%1'" ).arg( filter ), 4 );
286
287 if ( filter == mSubsetString )
288 return true;
289
290 if ( !filter.isEmpty() )
291 {
292 const QgsExpression filterExpression( filter );
293 if ( !filterExpression.isValid() )
294 {
295 QgsMessageLog::logMessage( filterExpression.parserErrorString(), tr( "OAPIF" ) );
296 return false;
297 }
298 }
299
300 // Invalid and cancel current download before altering fields, etc...
301 // (crashes might happen if not done at the beginning)
302 mShared->invalidateCache();
303
304 mSubsetString = filter;
305 clearMinMaxCache();
306
307 // update URI
308 mShared->mURI.setFilter( filter );
309 setDataSourceUri( mShared->mURI.uri() );
310 QString errorMsg;
311 if ( !mShared->computeServerFilter( errorMsg ) )
312 QgsMessageLog::logMessage( errorMsg, tr( "OAPIF" ) );
313
314
315 if ( updateFeatureCount )
316 {
317 reloadData();
318 }
319 else
320 {
321 mShared->invalidateCache();
322 emit dataChanged();
323 }
324
325 return true;
326 }
327
filterTranslatedState() const328 QgsOapifProvider::FilterTranslationState QgsOapifProvider::filterTranslatedState() const
329 {
330 return mShared->mFilterTranslationState;
331 }
332
clientSideFilterExpression() const333 const QString &QgsOapifProvider::clientSideFilterExpression() const
334 {
335 return mShared->mClientSideFilterExpression;
336 }
337
name() const338 QString QgsOapifProvider::name() const
339 {
340 return OAPIF_PROVIDER_KEY;
341 }
342
providerKey()343 QString QgsOapifProvider::providerKey()
344 {
345 return OAPIF_PROVIDER_KEY;
346 }
347
description() const348 QString QgsOapifProvider::description() const
349 {
350 return OAPIF_PROVIDER_DESCRIPTION;
351 }
352
353 // ---------------------------------
354
QgsOapifSharedData(const QString & uri)355 QgsOapifSharedData::QgsOapifSharedData( const QString &uri )
356 : QgsBackgroundCachedSharedData( "oapif", tr( "OAPIF" ) )
357 , mURI( uri )
358 {
359 mHideProgressDialog = mURI.hideDownloadProgressDialog();
360 }
361
~QgsOapifSharedData()362 QgsOapifSharedData::~QgsOapifSharedData()
363 {
364 QgsDebugMsgLevel( QStringLiteral( "~QgsOapifSharedData()" ), 4 );
365
366 cleanup();
367 }
368
appendExtraQueryParameters(const QString & url) const369 QString QgsOapifSharedData::appendExtraQueryParameters( const QString &url ) const
370 {
371 if ( mExtraQueryParameters.isEmpty() || url.indexOf( mExtraQueryParameters ) > 0 )
372 return url;
373 const int nPos = url.indexOf( '?' );
374 if ( nPos < 0 )
375 return url + '?' + mExtraQueryParameters;
376 return url + '&' + mExtraQueryParameters;
377 }
378
isRestrictedToRequestBBOX() const379 bool QgsOapifSharedData::isRestrictedToRequestBBOX() const
380 {
381 return mURI.isRestrictedToRequestBBOX();
382 }
383
384
newFeatureDownloaderImpl(QgsFeatureDownloader * downloader,bool requestMadeFromMainThread)385 std::unique_ptr<QgsFeatureDownloaderImpl> QgsOapifSharedData::newFeatureDownloaderImpl( QgsFeatureDownloader *downloader, bool requestMadeFromMainThread )
386 {
387 return std::unique_ptr<QgsFeatureDownloaderImpl>( new QgsOapifFeatureDownloaderImpl( this, downloader, requestMadeFromMainThread ) );
388 }
389
390
invalidateCacheBaseUnderLock()391 void QgsOapifSharedData::invalidateCacheBaseUnderLock()
392 {
393 }
394
getDateTimeValue(const QVariant & v)395 static QDateTime getDateTimeValue( const QVariant &v )
396 {
397 if ( v.type() == QVariant::String )
398 return QDateTime::fromString( v.toString(), Qt::ISODateWithMs );
399 else if ( v.type() == QVariant::DateTime )
400 return v.toDateTime();
401 return QDateTime();
402 }
403
isDateTime(const QVariant & v)404 static bool isDateTime( const QVariant &v )
405 {
406 return getDateTimeValue( v ).isValid();
407 }
408
getDateTimeValueAsString(const QVariant & v)409 static QString getDateTimeValueAsString( const QVariant &v )
410 {
411 if ( v.type() == QVariant::String )
412 return v.toString();
413 else if ( v.type() == QVariant::DateTime )
414 return v.toDateTime().toString( Qt::ISODateWithMs );
415 return QString();
416 }
417
isDateTimeField(const QgsFields & fields,const QString & fieldName)418 static bool isDateTimeField( const QgsFields &fields, const QString &fieldName )
419 {
420 const int idx = fields.indexOf( fieldName );
421 if ( idx >= 0 )
422 {
423 const auto type = fields.at( idx ).type();
424 return type == QVariant::DateTime || type == QVariant::Date;
425 }
426 return false;
427 }
428
collectTopLevelAndNodes(const QgsExpressionNode * node,std::vector<const QgsExpressionNode * > & topAndNodes)429 static void collectTopLevelAndNodes( const QgsExpressionNode *node,
430 std::vector<const QgsExpressionNode *> &topAndNodes )
431 {
432 if ( node->nodeType() == QgsExpressionNode::ntBinaryOperator )
433 {
434 const auto binNode = static_cast<const QgsExpressionNodeBinaryOperator *>( node );
435 const auto op = binNode->op();
436 if ( op == QgsExpressionNodeBinaryOperator::boAnd )
437 {
438 collectTopLevelAndNodes( binNode->opLeft(), topAndNodes );
439 collectTopLevelAndNodes( binNode->opRight(), topAndNodes );
440 return;
441 }
442 }
443 topAndNodes.push_back( node );
444 }
445
translateNodeToServer(const QgsExpressionNode * rootNode,QgsOapifProvider::FilterTranslationState & translationState,QString & untranslatedPart)446 QString QgsOapifSharedData::translateNodeToServer(
447 const QgsExpressionNode *rootNode,
448 QgsOapifProvider::FilterTranslationState &translationState,
449 QString &untranslatedPart )
450 {
451 std::vector<const QgsExpressionNode *> topAndNodes;
452 collectTopLevelAndNodes( rootNode, topAndNodes );
453 QDateTime minDate;
454 QDateTime maxDate;
455 QString minDateStr;
456 QString maxDateStr;
457 bool hasTranslatedParts = false;
458 for ( size_t i = 0; i < topAndNodes.size(); /* do not increment here */ )
459 {
460 bool removeMe = false;
461 const auto node = topAndNodes[i];
462 if ( node->nodeType() == QgsExpressionNode::ntBinaryOperator )
463 {
464 const auto binNode = static_cast<const QgsExpressionNodeBinaryOperator *>( node );
465 const auto op = binNode->op();
466 if ( binNode->opLeft()->nodeType() == QgsExpressionNode::ntColumnRef &&
467 binNode->opRight()->nodeType() == QgsExpressionNode::ntLiteral )
468 {
469 const auto left = static_cast<const QgsExpressionNodeColumnRef *>( binNode->opLeft() );
470 const auto right = static_cast<const QgsExpressionNodeLiteral *>( binNode->opRight() );
471 if ( isDateTimeField( mFields, left->name() ) &&
472 isDateTime( right->value() ) )
473 {
474 if ( op == QgsExpressionNodeBinaryOperator::boGE ||
475 op == QgsExpressionNodeBinaryOperator::boGT ||
476 op == QgsExpressionNodeBinaryOperator::boEQ )
477 {
478 removeMe = true;
479 if ( !minDate.isValid() || getDateTimeValue( right->value() ) > minDate )
480 {
481 minDate = getDateTimeValue( right->value() );
482 minDateStr = getDateTimeValueAsString( right->value() );
483 }
484 }
485 if ( op == QgsExpressionNodeBinaryOperator::boLE ||
486 op == QgsExpressionNodeBinaryOperator::boLT ||
487 op == QgsExpressionNodeBinaryOperator::boEQ )
488 {
489 removeMe = true;
490 if ( !maxDate.isValid() || getDateTimeValue( right->value() ) < maxDate )
491 {
492 maxDate = getDateTimeValue( right->value() );
493 maxDateStr = getDateTimeValueAsString( right->value() );
494 }
495 }
496 }
497 }
498 }
499 if ( removeMe )
500 {
501 hasTranslatedParts = true;
502 topAndNodes.erase( topAndNodes.begin() + i );
503 }
504 else
505 ++i;
506 }
507
508 QString ret;
509 if ( minDate.isValid() && maxDate.isValid() )
510 {
511 if ( minDate == maxDate )
512 {
513 ret = QStringLiteral( "datetime=" ) + minDateStr;
514 }
515 else
516 {
517 ret = QStringLiteral( "datetime=" ) + minDateStr + QStringLiteral( "%2F" ) + maxDateStr;
518 }
519 }
520 else if ( minDate.isValid() )
521 {
522 // TODO: use ellipsis '..' instead of dummy upper bound once more servers are compliant
523 ret = QStringLiteral( "datetime=" ) + minDateStr + QStringLiteral( "%2F9999-12-31T00:00:00Z" );
524 }
525 else if ( maxDate.isValid() )
526 {
527 // TODO: use ellipsis '..' instead of dummy upper bound once more servers are compliant
528 ret = QStringLiteral( "datetime=0000-01-01T00:00:00Z%2F" ) + maxDateStr;
529 }
530
531 if ( !hasTranslatedParts )
532 {
533 untranslatedPart = rootNode->dump();
534 translationState = QgsOapifProvider::FilterTranslationState::FULLY_CLIENT;
535 }
536 else if ( topAndNodes.empty() )
537 {
538 untranslatedPart.clear();
539 translationState = QgsOapifProvider::FilterTranslationState::FULLY_SERVER;
540 }
541 else
542 {
543 translationState = QgsOapifProvider::FilterTranslationState::PARTIAL;
544
545 // Collect part(s) of the filter to be evaluated on client-side
546 if ( topAndNodes.size() == 1 )
547 {
548 untranslatedPart = topAndNodes[0]->dump();
549 }
550 else
551 {
552 for ( size_t i = 0; i < topAndNodes.size(); ++i )
553 {
554 if ( i == 0 )
555 untranslatedPart = QStringLiteral( "(" );
556 else
557 untranslatedPart += QLatin1String( " AND (" );
558 untranslatedPart += topAndNodes[i]->dump();
559 untranslatedPart += QLatin1Char( ')' );
560 }
561 }
562 }
563
564 return ret;
565 }
566
computeServerFilter(QString & errorMsg)567 bool QgsOapifSharedData::computeServerFilter( QString &errorMsg )
568 {
569 errorMsg.clear();
570 mClientSideFilterExpression = mURI.filter();
571 mServerFilter.clear();
572 if ( mClientSideFilterExpression.isEmpty() )
573 {
574 mFilterTranslationState = QgsOapifProvider::FilterTranslationState::FULLY_SERVER;
575 return true;
576 }
577
578 const QgsExpression expr( mClientSideFilterExpression );
579 const auto rootNode = expr.rootNode();
580 if ( !rootNode )
581 return false;
582 mServerFilter = translateNodeToServer( rootNode, mFilterTranslationState, mClientSideFilterExpression );
583 if ( mFilterTranslationState == QgsOapifProvider::FilterTranslationState::PARTIAL )
584 {
585 QgsDebugMsg( QStringLiteral( "Part of the filter will be evaluated on client-side: %1" ).arg( mClientSideFilterExpression ) );
586 }
587 else if ( mFilterTranslationState == QgsOapifProvider::FilterTranslationState::FULLY_CLIENT )
588 {
589 QgsDebugMsg( "Whole filter will be evaluated on client-side" );
590 }
591
592 return true;
593 }
594
pushError(const QString & errorMsg) const595 void QgsOapifSharedData::pushError( const QString &errorMsg ) const
596 {
597 QgsMessageLog::logMessage( errorMsg, tr( "OAPIF" ) );
598 emit raiseError( errorMsg );
599 }
600
601 // ---------------------------------
602
QgsOapifFeatureDownloaderImpl(QgsOapifSharedData * shared,QgsFeatureDownloader * downloader,bool requestMadeFromMainThread)603 QgsOapifFeatureDownloaderImpl::QgsOapifFeatureDownloaderImpl( QgsOapifSharedData *shared, QgsFeatureDownloader *downloader, bool requestMadeFromMainThread ):
604 QgsFeatureDownloaderImpl( shared, downloader ),
605 mShared( shared )
606 {
607 QGS_FEATURE_DOWNLOADER_IMPL_CONNECT_SIGNALS( requestMadeFromMainThread );
608 }
609
~QgsOapifFeatureDownloaderImpl()610 QgsOapifFeatureDownloaderImpl::~QgsOapifFeatureDownloaderImpl()
611 {
612 }
613
createProgressDialog()614 void QgsOapifFeatureDownloaderImpl::createProgressDialog()
615 {
616 QgsFeatureDownloaderImpl::createProgressDialog( mNumberMatched );
617 CONNECT_PROGRESS_DIALOG( QgsOapifFeatureDownloaderImpl );
618 }
619
run(bool serializeFeatures,long long maxFeatures)620 void QgsOapifFeatureDownloaderImpl::run( bool serializeFeatures, long long maxFeatures )
621 {
622 QEventLoop loop;
623 connect( this, &QgsOapifFeatureDownloaderImpl::doStop, &loop, &QEventLoop::quit );
624
625 const bool useProgressDialog = ( !mShared->mHideProgressDialog && maxFeatures != 1 );
626
627 long long maxTotalFeatures = 0;
628 if ( maxFeatures > 0 && mShared->mMaxFeatures > 0 )
629 {
630 maxTotalFeatures = std::min( maxFeatures, mShared->mMaxFeatures );
631 }
632 else if ( maxFeatures > 0 )
633 {
634 maxTotalFeatures = maxFeatures;
635 }
636 else
637 {
638 maxTotalFeatures = mShared->mMaxFeatures;
639 }
640
641 long long totalDownloadedFeatureCount = 0;
642 bool interrupted = false;
643 bool success = true;
644 QString errorMessage;
645 QString url;
646
647 long long maxFeaturesThisRequest = maxTotalFeatures;
648 if ( mShared->mPageSize > 0 )
649 {
650 if ( maxFeaturesThisRequest > 0 )
651 {
652 maxFeaturesThisRequest = std::min( maxFeaturesThisRequest, mShared->mPageSize );
653 }
654 else
655 {
656 maxFeaturesThisRequest = mShared->mPageSize;
657 }
658 }
659 url = mShared->mItemsUrl;
660 bool hasQueryParam = false;
661 if ( maxFeaturesThisRequest > 0 )
662 {
663 url += QStringLiteral( "?limit=%1" ).arg( maxFeaturesThisRequest );
664 hasQueryParam = true;
665 }
666
667 if ( !mShared->mServerFilter.isEmpty() )
668 {
669 url += ( hasQueryParam ? QStringLiteral( "&" ) : QStringLiteral( "?" ) );
670 url += mShared->mServerFilter;
671 hasQueryParam = true;
672 }
673
674 const QgsRectangle &rect = mShared->currentRect();
675 if ( !rect.isNull() )
676 {
677 // Clamp to avoid server errors.
678 const double minx = std::max( -180.0, rect.xMinimum() );
679 const double miny = std::max( -90.0, rect.yMinimum() );
680 const double maxx = std::min( 180.0, rect.xMaximum() );
681 const double maxy = std::min( 90.0, rect.yMaximum() );
682 if ( minx > 180.0 || miny > 90.0 || maxx < -180.0 || maxy < -90.0 )
683 {
684 // completely out of range. Servers could error out
685 url.clear();
686 }
687 else if ( minx > -180.0 || miny > -90.0 || maxx < 180.0 || maxy < 90.0 )
688 {
689 url += ( hasQueryParam ? QStringLiteral( "&" ) : QStringLiteral( "?" ) );
690 url += QStringLiteral( "bbox=%1,%2,%3,%4" )
691 .arg( qgsDoubleToString( minx ),
692 qgsDoubleToString( miny ),
693 qgsDoubleToString( maxx ),
694 qgsDoubleToString( maxy ) );
695 }
696 }
697
698 while ( !url.isEmpty() )
699 {
700 url = mShared->appendExtraQueryParameters( url );
701
702 if ( maxTotalFeatures > 0 && totalDownloadedFeatureCount >= maxTotalFeatures )
703 {
704 break;
705 }
706
707 QgsOapifItemsRequest itemsRequest( mShared->mURI.uri(), url );
708 connect( &itemsRequest, &QgsOapifItemsRequest::gotResponse, &loop, &QEventLoop::quit );
709 itemsRequest.request( false /* synchronous*/, true /* forceRefresh */ );
710 loop.exec( QEventLoop::ExcludeUserInputEvents );
711 if ( mStop )
712 {
713 interrupted = true;
714 success = false;
715 break;
716 }
717 if ( itemsRequest.errorCode() != QgsBaseNetworkRequest::NoError )
718 {
719 errorMessage = itemsRequest.errorMessage();
720 success = false;
721 break;
722 }
723 if ( itemsRequest.features().empty() )
724 {
725 break;
726 }
727 url = itemsRequest.nextUrl();
728
729 // Consider if we should display a progress dialog
730 // We can only do that if we know how many features will be downloaded
731 if ( mNumberMatched < 0 && !mTimer && useProgressDialog && itemsRequest.numberMatched() > 0 )
732 {
733 mNumberMatched = itemsRequest.numberMatched();
734 CREATE_PROGRESS_DIALOG( QgsOapifFeatureDownloaderImpl );
735 }
736
737 totalDownloadedFeatureCount += itemsRequest.features().size();
738 if ( !mStop )
739 {
740 emit updateProgress( totalDownloadedFeatureCount );
741 }
742
743 QVector<QgsFeatureUniqueIdPair> featureList;
744 size_t i = 0;
745 const QgsFields srcFields = itemsRequest.fields();
746 const QgsFields dstFields = mShared->fields();
747 for ( const auto &pair : itemsRequest.features() )
748 {
749 // In the case the features of the current page have not the same schema
750 // as the layer, convert them
751 const QgsFeature &f = pair.first;
752 QgsFeature dstFeat( dstFields, f.id() );
753 dstFeat.setGeometry( f.geometry() );
754 const auto srcAttrs = f.attributes();
755 for ( int j = 0; j < dstFields.size(); j++ )
756 {
757 const int srcIdx = srcFields.indexOf( dstFields[j].name() );
758 if ( srcIdx >= 0 )
759 {
760 const QVariant &v = srcAttrs.value( srcIdx );
761 const auto dstFieldType = dstFields.at( j ).type();
762 if ( v.isNull() )
763 dstFeat.setAttribute( j, QVariant( dstFieldType ) );
764 else if ( QgsWFSUtils::isCompatibleType( v.type(), dstFieldType ) )
765 dstFeat.setAttribute( j, v );
766 else
767 dstFeat.setAttribute( j, QgsVectorDataProvider::convertValue( dstFieldType, v.toString() ) );
768 }
769 }
770
771 QString uniqueId( pair.second );
772 if ( uniqueId.isEmpty() )
773 {
774 uniqueId = QgsBackgroundCachedSharedData::getMD5( f );
775 }
776
777 featureList.push_back( QgsFeatureUniqueIdPair( dstFeat, uniqueId ) );
778
779 if ( ( i > 0 && ( i % 1000 ) == 0 ) || i + 1 == itemsRequest.features().size() )
780 {
781 // We call it directly to avoid asynchronous signal notification, and
782 // as serializeFeatures() can modify the featureList to remove features
783 // that have already been cached, so as to avoid to notify them several
784 // times to subscribers
785 if ( serializeFeatures )
786 mShared->serializeFeatures( featureList );
787
788 if ( !featureList.isEmpty() )
789 {
790 emitFeatureReceived( featureList );
791 emitFeatureReceived( featureList.size() );
792 }
793
794 featureList.clear();
795 }
796 i++;
797 }
798
799 if ( mShared->mPageSize <= 0 )
800 {
801 break;
802 }
803 }
804
805 endOfRun( serializeFeatures, success, totalDownloadedFeatureCount, false /* truncatedResponse */, interrupted, errorMessage );
806 }
807
808 // ---------------------------------
809
createProvider(const QString & uri,const QgsDataProvider::ProviderOptions & options,QgsDataProvider::ReadFlags flags)810 QgsOapifProvider *QgsOapifProviderMetadata::createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags )
811 {
812 return new QgsOapifProvider( uri, options, flags );
813 }
814
QgsOapifProviderMetadata()815 QgsOapifProviderMetadata::QgsOapifProviderMetadata():
816 QgsProviderMetadata( QgsOapifProvider::OAPIF_PROVIDER_KEY, QgsOapifProvider::OAPIF_PROVIDER_DESCRIPTION ) {}
817