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