1 /*************************************************************************** 2 qgswmsprovider.h - QGIS Data provider for 3 OGC Web Map Service layers 4 ------------------- 5 begin : 17 Mar, 2005 6 copyright : (C) 2005 by Brendan Morley 7 email : morb at ozemail dot com dot au 8 ***************************************************************************/ 9 10 /*************************************************************************** 11 * * 12 * This program is free software; you can redistribute it and/or modify * 13 * it under the terms of the GNU General Public License as published by * 14 * the Free Software Foundation; either version 2 of the License, or * 15 * (at your option) any later version. * 16 * * 17 ***************************************************************************/ 18 19 20 #ifndef QGSWMSPROVIDER_H 21 #define QGSWMSPROVIDER_H 22 23 #include "qgsrasterdataprovider.h" 24 #include "qgscoordinatereferencesystem.h" 25 #include "qgsnetworkreplyparser.h" 26 #include "qgswmscapabilities.h" 27 #include "qgsprovidermetadata.h" 28 29 #include <QString> 30 #include <QStringList> 31 #include <QDomElement> 32 #include <QHash> 33 #include <QMap> 34 #include <QVector> 35 #include <QUrl> 36 37 class QgsCoordinateTransform; 38 class QgsNetworkAccessManager; 39 class QgsWmsCapabilities; 40 41 class QNetworkAccessManager; 42 class QNetworkReply; 43 class QNetworkRequest; 44 45 /** 46 * \class Handles asynchronous download of WMS legend 47 * 48 * \todo turn into a generic async image downloader ? 49 * 50 */ 51 class QgsWmsLegendDownloadHandler : public QgsImageFetcher 52 { 53 Q_OBJECT 54 public: 55 56 QgsWmsLegendDownloadHandler( QgsNetworkAccessManager &networkAccessManager, const QgsWmsSettings &settings, const QUrl &url ); 57 ~QgsWmsLegendDownloadHandler() override; 58 59 // Make sure to connect to "finish" before starting 60 void start() override; 61 62 private: 63 64 // Make sure to connect to "finish" before starting 65 void startUrl( const QUrl &url ); 66 67 // Delete reply (later), emit error and finish with empty image 68 void sendError( const QString &msg ); 69 // Delete reply (later), emit finish 70 void sendSuccess( const QImage &img ); 71 72 QgsNetworkAccessManager &mNetworkAccessManager; 73 const QgsWmsSettings &mSettings; 74 QNetworkReply *mReply = nullptr; 75 QSet<QUrl> mVisitedUrls; 76 QUrl mInitialUrl; 77 78 private slots: 79 80 void errored( QNetworkReply::NetworkError code ); 81 void finished(); 82 void progressed( qint64, qint64 ); 83 }; 84 85 class QgsCachedImageFetcher: public QgsImageFetcher 86 { 87 Q_OBJECT 88 public: 89 explicit QgsCachedImageFetcher( const QImage &img ); 90 91 void start() override; 92 private: 93 const QImage _img; // copy is intentional 94 private slots: send()95 void send() 96 { 97 QgsDebugMsg( QStringLiteral( "XXX Sending %1x%2 image" ).arg( _img.width() ).arg( _img.height() ) ); 98 emit finish( _img ); 99 } 100 }; 101 102 103 /** 104 * 105 * \brief Data provider for OGC WMS layers. 106 * 107 * This provider implements the 108 * interface defined in the QgsDataProvider class to provide access to spatial 109 * data residing in a OGC Web Map Service. 110 * 111 */ 112 class QgsWmsProvider final: public QgsRasterDataProvider 113 { 114 Q_OBJECT 115 116 public: 117 118 static QString WMS_KEY; 119 static QString WMS_DESCRIPTION; 120 121 /** 122 * Constructor for the provider. 123 * 124 * \param uri HTTP URL of the Web Server. If needed a proxy will be used 125 * otherwise we contact the host directly. 126 * \param options generic data provider options 127 * \param capabilities Optionally existing parsed capabilities for the given URI 128 * 129 */ 130 QgsWmsProvider( const QString &uri, const QgsDataProvider::ProviderOptions &providerOptions, const QgsWmsCapabilities *capabilities = nullptr ); 131 132 133 ~QgsWmsProvider() override; 134 135 QgsWmsProvider *clone() const override; 136 137 QgsCoordinateReferenceSystem crs() const override; 138 139 QgsRasterDataProvider::ProviderCapabilities providerCapabilities() const override; 140 141 /** 142 * Reorder the list of WMS layer names to be rendered by this server 143 * (in order from bottom to top) 144 * \note layers must have been previously added. 145 */ 146 void setLayerOrder( QStringList const &layers ) override; 147 148 void setSubLayerVisibility( const QString &name, bool vis ) override; 149 150 /** 151 * Set the name of the connection for use in authentication where required 152 */ 153 void setConnectionName( QString const &connName ); 154 155 bool readBlock( int bandNo, QgsRectangle const &viewExtent, int width, int height, void *data, QgsRasterBlockFeedback *feedback = nullptr ) override; 156 //void readBlock( int bandNo, QgsRectangle const & viewExtent, int width, int height, QgsCoordinateReferenceSystem srcCRS, QgsCoordinateReferenceSystem destCRS, void *data ); 157 158 QgsRectangle extent() const override; 159 160 bool isValid() const override; 161 162 #if 0 163 164 /** 165 * Returns true if layer has tile set profiles 166 */ 167 virtual bool hasTiles() const; 168 #endif 169 170 virtual QString getMapUrl() const; 171 virtual QString getFeatureInfoUrl() const; 172 virtual QString getTileUrl() const; 173 virtual QString getLegendGraphicUrl() const; 174 175 //! Gets WMS version string 176 QString wmsVersion(); 177 178 /** 179 * Sub-layers handled by this provider, in order from bottom to top 180 * 181 * Sub-layers are used to abstract the way the WMS server can combine 182 * layers in some way at the server, before it serves them to this 183 * WMS client. 184 */ 185 QStringList subLayers() const override; 186 187 /** 188 * Sub-layer styles for each sub-layer handled by this provider, 189 * in order from bottom to top 190 * 191 * Sub-layer styles are used to abstract the way the WMS server can symbolise 192 * layers in some way at the server, before it serves them to this 193 * WMS client. 194 */ 195 QStringList subLayerStyles() const override; 196 supportsLegendGraphic()197 bool supportsLegendGraphic() const override { return true; } 198 199 QImage getLegendGraphic( double scale = 0.0, bool forceRefresh = false, const QgsRectangle *visibleExtent = nullptr ) override; 200 QgsImageFetcher *getLegendGraphicFetcher( const QgsMapSettings *mapSettings ) override; 201 202 // TODO: Get the WMS connection 203 204 // TODO: Get the table name associated with this provider instance 205 206 int capabilities() const override; 207 Qgis::DataType dataType( int bandNo ) const override; 208 Qgis::DataType sourceDataType( int bandNo ) const override; 209 int bandCount() const override; 210 QString htmlMetadata() override; 211 QgsRasterIdentifyResult identify( const QgsPointXY &point, QgsRaster::IdentifyFormat format, const QgsRectangle &boundingBox = QgsRectangle(), int width = 0, int height = 0, int dpi = 96 ) override; 212 QString lastErrorTitle() override; 213 QString lastError() override; 214 QString lastErrorFormat() override; 215 QString name() const override; 216 static QString providerKey(); 217 QString description() const override; 218 bool renderInPreview( const QgsDataProvider::PreviewContext &context ) override; 219 QList< double > nativeResolutions() const override; 220 QgsLayerMetadata layerMetadata() const override; 221 222 static QVector<QgsWmsSupportedFormat> supportedFormats(); 223 224 static void showMessageBox( const QString &title, const QString &text ); 225 226 /** 227 * \brief parse the full WMS ServiceExceptionReport XML document 228 * 229 * \note errorTitle and errorText are updated to suit the results of this function. Format of error is plain text. 230 */ 231 static bool parseServiceExceptionReportDom( QByteArray const &xml, QString &errorTitle, QString &errorText ); 232 233 /** 234 * \brief Prepare the URI so that we can later simply append param=value 235 * \param uri uri to prepare 236 * \returns prepared uri 237 */ 238 static QString prepareUri( QString uri ); 239 stepWidth()240 int stepWidth() const override { return mSettings.mStepWidth; } stepHeight()241 int stepHeight() const override { return mSettings.mStepHeight; } 242 243 //! Helper struct for tile requests 244 struct TileRequest 245 { TileRequestTileRequest246 TileRequest( const QUrl &u, const QRectF &r, int i ) 247 : url( u ) 248 , rect( r ) 249 , index( i ) 250 {} 251 QUrl url; 252 QRectF rect; 253 int index; 254 }; 255 typedef QList<TileRequest> TileRequests; 256 257 //! Tile identifier within a tile source 258 typedef struct TilePosition 259 { TilePositionTilePosition260 TilePosition( int r, int c ): row( r ), col( c ) {} 261 bool operator==( TilePosition other ) const { return row == other.row && col == other.col; } 262 int row; 263 int col; 264 } TilePosition; 265 typedef QList<TilePosition> TilePositions; 266 267 static bool isUrlForWMTS( const QString &url ); 268 269 private slots: 270 void identifyReplyFinished(); 271 void getLegendGraphicReplyFinished( const QImage & ); 272 void getLegendGraphicReplyErrored( const QString &message ); 273 void getLegendGraphicReplyProgress( qint64, qint64 ); 274 275 private: 276 277 //! In case of XYZ tile layer, setup capabilities from its URI 278 void setupXyzCapabilities( const QString &uri, const QgsRectangle &sourceExtent = QgsRectangle(), int sourceMinZoom = -1, int sourceMaxZoom = -1, double sourceTilePixelRatio = 0. ); 279 //! In case of MBTiles layer, setup capabilities from its metadata 280 bool setupMBTilesCapabilities( const QString &uri ); 281 282 QImage *draw( QgsRectangle const &viewExtent, int pixelWidth, int pixelHeight, QgsRasterBlockFeedback *feedback ); 283 284 /** 285 * Try to get best extent for the layer in given CRS. Returns true on success, false otherwise (layer not found, invalid CRS, transform failed) 286 */ 287 bool extentForNonTiledLayer( const QString &layerName, const QString &crs, QgsRectangle &extent ) const; 288 289 // case insensitive attribute value lookup 290 static QString nodeAttribute( const QDomElement &e, const QString &name, const QString &defValue = QString() ); 291 292 /** 293 * Add the list of WMS layer names to be rendered by this server 294 */ 295 bool addLayers(); 296 297 /** 298 * Set the image projection (in WMS CRS format) used in the transfer from the WMS server 299 * 300 * \note an empty crs value will result in the previous CRS being retained. 301 */ 302 bool setImageCrs( QString const &crs ); 303 304 /** 305 * \brief Retrieve and parse the (cached) Capabilities document from the server 306 * 307 * \param forceRefresh if true, ignores any previous response cached in memory 308 * and always contact the server for a new copy. 309 * \returns false if the capabilities document could not be retrieved or parsed - 310 * see lastError() for more info 311 * 312 * When this returns, "layers" will make sense. 313 * 314 * TODO: Make network-timeout tolerant 315 */ 316 bool retrieveServerCapabilities( bool forceRefresh = false ); 317 318 //! parse the WMS ServiceException XML element 319 static void parseServiceException( QDomElement const &e, QString &errorTitle, QString &errorText ); 320 321 void parseOperationMetadata( QDomElement const &e ); 322 323 /** 324 * \brief Calculates the combined extent of the layers selected by layersDrawn 325 * 326 * \returns false if the capabilities document could not be retrieved or parsed - 327 * see lastError() for more info 328 */ 329 bool calculateExtent() const; 330 331 /* \brief Bounding box in WMS format 332 * 333 * \note it does not perform any escape 334 */ 335 QString toParamValue( const QgsRectangle &rect, bool changeXY ); 336 337 /* \brief add SRS or CRS parameter */ 338 void setSRSQueryItem( QUrlQuery &url ); 339 340 bool ignoreExtents() const override; 341 342 private: 343 344 QUrl createRequestUrlWMS( const QgsRectangle &viewExtent, int pixelWidth, int pixelHeight ); 345 void createTileRequestsWMSC( const QgsWmtsTileMatrix *tm, const QgsWmsProvider::TilePositions &tiles, QgsWmsProvider::TileRequests &requests ); 346 void createTileRequestsWMTS( const QgsWmtsTileMatrix *tm, const QgsWmsProvider::TilePositions &tiles, QgsWmsProvider::TileRequests &requests ); 347 void createTileRequestsXYZ( const QgsWmtsTileMatrix *tm, const QgsWmsProvider::TilePositions &tiles, QgsWmsProvider::TileRequests &requests ); 348 349 /** 350 * Add WMS-T parameters to the \a query, if provider has temporal properties 351 * 352 * \since QGIS 3.14 353 */ 354 void addWmstParameters( QUrlQuery &query ); 355 356 //! Helper structure to store a cached tile image with its rectangle 357 typedef struct TileImage 358 { TileImageTileImage359 TileImage( const QRectF &r, const QImage &i, bool smooth ): rect( r ), img( i ), smooth( smooth ) {} 360 QRectF rect; //!< Destination rectangle for a tile (in screen coordinates) 361 QImage img; //!< Cached tile to be drawn 362 bool smooth; //!< Whether to use antialiasing/smooth transforms when rendering tile 363 } TileImage; 364 //! Gets tiles from a different resolution to cover the missing areas 365 void fetchOtherResTiles( QgsTileMode tileMode, const QgsRectangle &viewExtent, int imageWidth, QList<QRectF> &missing, double tres, int resOffset, QList<TileImage> &otherResTiles ); 366 367 /** 368 * Returns the full url to request legend graphic 369 * The visibleExtent isi only used if provider supports contextual 370 * legends according to the QgsWmsSettings 371 * \since QGIS 2.8 372 */ 373 QUrl getLegendGraphicFullURL( double scale, const QgsRectangle &visibleExtent ); 374 375 //QStringList identifyAs( const QgsPointXY &point, QString format ); 376 377 QString layerMetadata( QgsWmsLayerProperty &layer ); 378 379 //! remove query item and replace it with a new value 380 void setQueryItem( QUrlQuery &url, const QString &key, const QString &value ); 381 382 //! add image FORMAT parameter to url 383 void setFormatQueryItem( QUrlQuery &url ); 384 385 //! Name of the stored connection 386 QString mConnectionName; 387 388 /** 389 * Flag indicating if the layer data source is a valid WMS layer 390 */ 391 bool mValid; 392 393 /** 394 * Spatial reference id of the layer 395 */ 396 QString mSrid; 397 398 /** 399 * Rectangle that contains the extent (bounding box) of the layer 400 */ 401 mutable QgsRectangle mLayerExtent; 402 403 /** 404 * GetLegendGraphic of the WMS (raw) 405 */ 406 QByteArray mHttpGetLegendGraphicResponse; 407 408 /** 409 * GetLegendGraphic WMS Pixmap result 410 */ 411 QImage mGetLegendGraphicImage; 412 413 /** 414 * GetLegendGraphic scale for the WMS Pixmap result 415 */ 416 double mGetLegendGraphicScale = 0.0; 417 418 QgsRectangle mGetLegendGraphicExtent; 419 420 std::unique_ptr<QgsImageFetcher> mLegendGraphicFetcher; 421 422 //! TRUE if an error was encountered while fetching a legend graphic 423 bool mLegendGraphicFetchErrored = false; 424 425 /** 426 * Visibility status of the given active sublayer 427 */ 428 QMap<QString, bool> mActiveSubLayerVisibility; 429 430 /** 431 * WMS CRS type of the image CRS used from the WMS server 432 */ 433 QString mImageCrs; 434 435 /** 436 * The reply to the capabilities request 437 */ 438 QNetworkReply *mIdentifyReply = nullptr; 439 440 /** 441 * The result of the identify reply 442 */ 443 //QString mIdentifyResult; 444 QList< QgsNetworkReplyParser::RawHeaderMap > mIdentifyResultHeaders; 445 QList<QByteArray> mIdentifyResultBodies; 446 447 // TODO: better 448 QString mIdentifyResultXsd; 449 450 /** 451 * The error caption associated with the last WMS error. 452 */ 453 QString mErrorCaption; 454 455 /** 456 * The error message associated with the last WMS error. 457 */ 458 QString mError; 459 460 /** 461 * The mime type of the message 462 */ 463 QString mErrorFormat; 464 465 //! See if calculateExtents() needs to be called before extent() returns useful data 466 mutable bool mExtentDirty = true; 467 468 QString mServiceMetadataURL; 469 470 //! tile request number, cache hits and misses 471 int mTileReqNo = 0; 472 473 //! chosen tile layer 474 QgsWmtsTileLayer *mTileLayer = nullptr; 475 //! chosen matrix set 476 QgsWmtsTileMatrixSet *mTileMatrixSet = nullptr; 477 478 //! supported formats for GetFeatureInfo in order of preference 479 QStringList mSupportedGetFeatureFormats; 480 481 QgsCoordinateReferenceSystem mCrs; 482 QgsLayerMetadata mLayerMetadata; 483 484 //! Parsed response of server's capabilities - initially (or on error) may be invalid 485 QgsWmsCapabilities mCaps; 486 487 //! User's settings (URI, authorization, layer, style, ...) 488 QgsWmsSettings mSettings; 489 490 //! Temporal range member 491 QgsDateTimeRange mRange; 492 493 QList< double > mNativeResolutions; 494 495 friend class TestQgsWmsProvider; 496 497 }; 498 499 500 //! Handler for downloading of non-tiled WMS requests, the data are written to the given image 501 class QgsWmsImageDownloadHandler : public QObject 502 { 503 Q_OBJECT 504 public: 505 QgsWmsImageDownloadHandler( const QString &providerUri, const QUrl &url, const QgsWmsAuthorization &auth, QImage *image, QgsRasterBlockFeedback *feedback ); 506 ~QgsWmsImageDownloadHandler() override; 507 508 void downloadBlocking(); 509 510 protected slots: 511 void cacheReplyFinished(); 512 void cacheReplyProgress( qint64 bytesReceived, qint64 bytesTotal ); 513 void canceled(); 514 515 protected: finish()516 void finish() { QMetaObject::invokeMethod( mEventLoop, "quit", Qt::QueuedConnection ); } 517 518 QString mProviderUri; 519 520 QNetworkReply *mCacheReply = nullptr; 521 QImage *mCachedImage = nullptr; 522 523 QEventLoop *mEventLoop = nullptr; 524 525 QgsRasterBlockFeedback *mFeedback = nullptr; 526 }; 527 528 529 //! Handler for tiled WMS-C/WMTS requests, the data are written to the given image 530 class QgsWmsTiledImageDownloadHandler : public QObject 531 { 532 Q_OBJECT 533 public: 534 535 QgsWmsTiledImageDownloadHandler( const QString &providerUri, const QgsWmsAuthorization &auth, int reqNo, const QgsWmsProvider::TileRequests &requests, QImage *image, const QgsRectangle &viewExtent, bool smoothPixmapTransform, QgsRasterBlockFeedback *feedback ); 536 ~QgsWmsTiledImageDownloadHandler() override; 537 538 void downloadBlocking(); 539 540 protected slots: 541 void tileReplyFinished(); 542 void canceled(); 543 544 protected: 545 546 /** 547 * \brief Relaunch tile request cloning previous request parameters and managing max repeat 548 * 549 * \param oldRequest request to clone to generate new tile request 550 * 551 * request is not launched if max retry is reached. Message is logged. 552 */ 553 void repeatTileRequest( QNetworkRequest const &oldRequest ); 554 finish()555 void finish() { QMetaObject::invokeMethod( mEventLoop, "quit", Qt::QueuedConnection ); } 556 557 QString mProviderUri; 558 559 QgsWmsAuthorization mAuth; 560 561 QImage *mImage = nullptr; 562 QgsRectangle mViewExtent; 563 564 QEventLoop *mEventLoop = nullptr; 565 566 int mTileReqNo; 567 bool mSmoothPixmapTransform; 568 569 //! Running tile requests 570 QList<QNetworkReply *> mReplies; 571 572 QgsRasterBlockFeedback *mFeedback = nullptr; 573 }; 574 575 576 //! Class keeping simple statistics for WMS provider - per unique URI 577 class QgsWmsStatistics 578 { 579 public: 580 struct Stat 581 { 582 Stat() = default; 583 int errors = 0; 584 int cacheHits = 0; 585 int cacheMisses = 0; 586 }; 587 588 //! Gets reference to layer's statistics - insert to map if does not exist yet statForUri(const QString & uri)589 static Stat &statForUri( const QString &uri ) { return sData[uri]; } 590 591 protected: 592 static QMap<QString, Stat> sData; 593 }; 594 595 Q_DECLARE_TYPEINFO( QgsWmsProvider::TilePosition, Q_PRIMITIVE_TYPE ); 596 597 class QgsWmsProviderMetadata final: public QgsProviderMetadata 598 { 599 public: 600 QgsWmsProviderMetadata(); 601 QgsWmsProvider *createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags = QgsDataProvider::ReadFlags() ) override; 602 QList<QgsDataItemProvider *> dataItemProviders() const override; 603 QVariantMap decodeUri( const QString &uri ) override; 604 QString encodeUri( const QVariantMap &parts ) override; 605 }; 606 607 #endif 608 609 // ENDS 610