QGIS API Documentation 3.43.0-Master (0cdc48caa8d)
qgsvectortileutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvectortileutils.cpp
3 --------------------------------------
4 Date : March 2020
5 Copyright : (C) 2020 by Martin Dobias
6 Email : wonder dot sk at gmail dot 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 "qgsvectortileutils.h"
17
18#include <math.h>
19
20#include <QPolygon>
21#include <QJsonDocument>
22#include <QJsonArray>
23
26#include "qgsfields.h"
27#include "qgslogger.h"
28#include "qgsmaptopixel.h"
29#include "qgsrectangle.h"
30#include "qgsvectorlayer.h"
31
32#include "qgsvectortileloader.h"
34#include "qgsvectortilelayer.h"
39#include "qgsjsonutils.h"
41
42
43void QgsVectorTileUtils::updateUriSources( QString &uri, bool forceUpdate )
44{
45 QgsVectorTileProviderConnection::Data data = QgsVectorTileProviderConnection::decodedUri( uri );
46 if ( forceUpdate || ( data.url.isEmpty() && !data.styleUrl.isEmpty() ) )
47 {
48 const QMap<QString, QString> sources = QgsVectorTileUtils::parseStyleSourceUrl( data.styleUrl, data.httpHeaders, data.authCfg );
49 QMap<QString, QString>::const_iterator it = sources.constBegin();
50 int i = 0;
51 for ( ; it != sources.constEnd(); ++it )
52 {
53 i += 1;
54 QString urlKey = QStringLiteral( "url" );
55 QString nameKey = QStringLiteral( "urlName" );
56 if ( i > 1 )
57 {
58 urlKey.append( QString( "_%1" ).arg( i ) );
59 nameKey.append( QString( "_%1" ).arg( i ) );
60 }
61 uri.append( QString( "&%1=%2" ).arg( nameKey, it.key() ) );
62 uri.append( QString( "&%1=%2" ).arg( urlKey, it.value() ) );
63 }
64 }
65}
66
67QMap<QString, QString> QgsVectorTileUtils::parseStyleSourceUrl( const QString &styleUrl, const QgsHttpHeaders &headers, const QString &authCfg )
68{
69 QNetworkRequest nr;
70 nr.setUrl( QUrl( styleUrl ) );
71 headers.updateNetworkRequest( nr );
72
74 req.setAuthCfg( authCfg );
75 QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr, false );
77 {
78 QgsDebugError( QStringLiteral( "Request failed: " ) + styleUrl );
79 return QMap<QString, QString>();
80 }
81 QgsNetworkReplyContent reply = req.reply();
82
83 QJsonParseError err;
84 const QJsonDocument doc = QJsonDocument::fromJson( reply.content(), &err );
85 if ( doc.isNull() )
86 {
87 QgsDebugError( QStringLiteral( "Could not load style: %1" ).arg( err.errorString() ) );
88 }
89 else if ( !doc.isObject() )
90 {
91 QgsDebugError( QStringLiteral( "Could not read style, JSON object is expected" ) );
92 }
93 else
94 {
95 QMap<QString, QString> sources;
96 QJsonObject sourcesData = doc.object().value( QStringLiteral( "sources" ) ).toObject();
97 if ( sourcesData.count() == 0 )
98 {
99 QgsDebugError( QStringLiteral( "Could not read sources in the style" ) );
100 }
101 else
102 {
103 QJsonObject::const_iterator it = sourcesData.constBegin();
104 for ( ; it != sourcesData.constEnd(); ++it )
105 {
106 const QString sourceName = it.key();
107 const QJsonObject sourceData = it.value().toObject();
108 if ( sourceData.value( QStringLiteral( "type" ) ).toString() != QLatin1String( "vector" ) )
109 {
110 // raster layers are handled separately
111 // ideally we should handle the sources here also, the same way than for vector
112 continue;
113 }
114 QVariantList tiles;
115 if ( sourceData.contains( QStringLiteral( "tiles" ) ) )
116 {
117 tiles = sourceData["tiles"].toArray().toVariantList();
118 }
119 else if ( sourceData.contains( QStringLiteral( "url" ) ) )
120 {
121 tiles = parseStyleSourceContentUrl( sourceData.value( QStringLiteral( "url" ) ).toString(), headers, authCfg );
122 }
123 else
124 {
125 QgsDebugError( QStringLiteral( "Could not read source %1" ).arg( sourceName ) );
126 }
127 if ( tiles.count() == 0 )
128 {
129 QgsDebugError( QStringLiteral( "Could not read source %1, not tiles found" ).arg( sourceName ) );
130 }
131 else
132 {
133 // take a random one from the list
134 // we might want to save the alternatives for a fallback later
135 QString tilesString = tiles[rand() % tiles.count()].toString();
136 QUrl tilesUrl( tilesString );
137 if ( tilesUrl.isRelative() )
138 {
139 QUrl temporaryStyleUrl( styleUrl );
140 tilesString = QStringLiteral( "%1://%2%3" ).arg( temporaryStyleUrl.scheme(), temporaryStyleUrl.host(), tilesString );
141 }
142 sources.insert( sourceName, tilesString );
143 }
144 }
145 return sources;
146 }
147 }
148 return QMap<QString, QString>();
149}
150
151QVariantList QgsVectorTileUtils::parseStyleSourceContentUrl( const QString &sourceUrl, const QgsHttpHeaders &headers, const QString &authCfg )
152{
153 QNetworkRequest nr;
154 nr.setUrl( QUrl( sourceUrl ) );
155 headers.updateNetworkRequest( nr );
156
158 req.setAuthCfg( authCfg );
159 QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr, false );
160 if ( errCode != QgsBlockingNetworkRequest::NoError )
161 {
162 QgsDebugError( QStringLiteral( "Request failed: " ) + sourceUrl );
163 return QVariantList();
164 }
165 QgsNetworkReplyContent reply = req.reply();
166
167 QJsonParseError err;
168 const QJsonDocument doc = QJsonDocument::fromJson( reply.content(), &err );
169 if ( doc.isNull() )
170 {
171 QgsDebugError( QStringLiteral( "Could not load style: %1" ).arg( err.errorString() ) );
172 }
173 else if ( !doc.isObject() )
174 {
175 QgsDebugError( QStringLiteral( "Could not read style, JSON object is expected" ) );
176 }
177 else
178 {
179 return doc.object().value( QStringLiteral( "tiles" ) ).toArray().toVariantList();
180 }
181 return QVariantList();
182}
183
184
185
187{
188 QgsRectangle r = tm.tileExtent( id );
189 QgsPointXY p00a = mtp.transform( ct.transform( r.xMinimum(), r.yMinimum() ) );
190 QgsPointXY p11a = mtp.transform( ct.transform( r.xMaximum(), r.yMaximum() ) );
191 QgsPointXY p01a = mtp.transform( ct.transform( r.xMinimum(), r.yMaximum() ) );
192 QgsPointXY p10a = mtp.transform( ct.transform( r.xMaximum(), r.yMinimum() ) );
193 QPolygon path;
194 path << p00a.toQPointF().toPoint();
195 path << p01a.toQPointF().toPoint();
196 path << p11a.toQPointF().toPoint();
197 path << p10a.toQPointF().toPoint();
198 return path;
199}
200
202{
203 QgsFields fields;
204 QStringList fieldsSorted = qgis::setToList( flds );
205 std::sort( fieldsSorted.begin(), fieldsSorted.end() );
206 for ( const QString &fieldName : std::as_const( fieldsSorted ) )
207 {
208 fields.append( QgsField( fieldName, QMetaType::Type::QString ) );
209 }
210 return fields;
211}
212
213double QgsVectorTileUtils::scaleToZoom( double mapScale, double z0Scale )
214{
215 double s0 = z0Scale;
216 double tileZoom2 = log( s0 / mapScale ) / log( 2 );
217 tileZoom2 -= 1; // TODO: it seems that map scale is double (is that because of high-dpi screen?)
218 return tileZoom2;
219}
220
221int QgsVectorTileUtils::scaleToZoomLevel( double mapScale, int sourceMinZoom, int sourceMaxZoom, double z0Scale )
222{
223 int tileZoom = static_cast<int>( round( scaleToZoom( mapScale, z0Scale ) ) );
224
225 if ( tileZoom < sourceMinZoom )
226 tileZoom = sourceMinZoom;
227 if ( tileZoom > sourceMaxZoom )
228 tileZoom = sourceMaxZoom;
229
230 return tileZoom;
231}
232
234{
235 QgsVectorTileMVTDecoder decoder( mvt->tileMatrixSet() );
236 const QgsVectorTileRawData rawTile = mvt->getRawTile( tileID );
237 decoder.decode( rawTile );
238 QSet<QString> fieldNames = qgis::listToSet( decoder.layerFieldNames( layerName ) );
239 fieldNames << QStringLiteral( "_geom_type" );
240 QMap<QString, QgsFields> perLayerFields;
241 QgsFields fields = QgsVectorTileUtils::makeQgisFields( fieldNames );
242 perLayerFields[layerName] = fields;
243 QgsVectorTileFeatures data = decoder.layerFeatures( perLayerFields, QgsCoordinateTransform() );
244 QgsFeatureList featuresList = data[layerName].toList();
245
246 // turn all geometries to geom. collections (otherwise they won't be accepted by memory provider)
247 for ( int i = 0; i < featuresList.count(); ++i )
248 {
249 QgsGeometry g = featuresList[i].geometry();
251 const QgsAbstractGeometry *gg = g.constGet();
252 if ( const QgsGeometryCollection *ggc = qgsgeometry_cast<const QgsGeometryCollection *>( gg ) )
253 {
254 for ( int k = 0; k < ggc->numGeometries(); ++k )
255 gc->addGeometry( ggc->geometryN( k )->clone() );
256 }
257 else
258 gc->addGeometry( gg->clone() );
259 featuresList[i].setGeometry( QgsGeometry( gc ) );
260 }
261
262 QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "GeometryCollection" ), layerName, QStringLiteral( "memory" ) );
263 vl->dataProvider()->addAttributes( fields.toList() );
264 vl->updateFields();
265 bool res = vl->dataProvider()->addFeatures( featuresList );
266 Q_UNUSED( res )
267 Q_ASSERT( res );
268 Q_ASSERT( featuresList.count() == vl->featureCount() );
269 vl->updateExtents();
270 QgsDebugMsgLevel( QStringLiteral( "Layer %1 features %2" ).arg( layerName ).arg( vl->featureCount() ), 2 );
271 return vl;
272}
273
274
275QString QgsVectorTileUtils::formatXYZUrlTemplate( const QString &url, QgsTileXYZ tile, const QgsTileMatrix &tileMatrix )
276{
277 QString turl( url );
278
279 turl.replace( QLatin1String( "{x}" ), QString::number( tile.column() ), Qt::CaseInsensitive );
280 if ( turl.contains( QLatin1String( "{-y}" ) ) )
281 {
282 turl.replace( QLatin1String( "{-y}" ), QString::number( tileMatrix.matrixHeight() - tile.row() - 1 ), Qt::CaseInsensitive );
283 }
284 else
285 {
286 turl.replace( QLatin1String( "{y}" ), QString::number( tile.row() ), Qt::CaseInsensitive );
287 }
288 turl.replace( QLatin1String( "{z}" ), QString::number( tile.zoomLevel() ), Qt::CaseInsensitive );
289 return turl;
290}
291
293{
294 return url.contains( QStringLiteral( "{x}" ) ) &&
295 ( url.contains( QStringLiteral( "{y}" ) ) || url.contains( QStringLiteral( "{-y}" ) ) ) &&
296 url.contains( QStringLiteral( "{z}" ) );
297}
298
301{
302 QPointF center;
304 {
305 QPointF p1( req1.column() + 0.5, req1.row() + 0.5 );
306 QPointF p2( req2.column() + 0.5, req2.row() + 0.5 );
307 // using chessboard distance (loading order more natural than euclidean/manhattan distance)
308 double d1 = std::max( std::fabs( center.x() - p1.x() ), std::fabs( center.y() - p1.y() ) );
309 double d2 = std::max( std::fabs( center.x() - p2.x() ), std::fabs( center.y() - p2.y() ) );
310 return d1 < d2;
311 }
312};
313
314void QgsVectorTileUtils::sortTilesByDistanceFromCenter( QVector<QgsTileXYZ> &tiles, QPointF center )
315{
317 cmp.center = center;
318 std::sort( tiles.begin(), tiles.end(), cmp );
319}
320
321void QgsVectorTileUtils::loadSprites( const QVariantMap &styleDefinition, QgsMapBoxGlStyleConversionContext &context, const QString &styleUrl )
322{
323 if ( styleDefinition.contains( QStringLiteral( "sprite" ) ) && ( context.spriteDefinitions().empty() || context.spriteImage().isNull() ) )
324 {
325 // retrieve sprite definition
326 QString spriteUriBase;
327 if ( styleDefinition.value( QStringLiteral( "sprite" ) ).toString().startsWith( QLatin1String( "http" ) ) )
328 {
329 spriteUriBase = styleDefinition.value( QStringLiteral( "sprite" ) ).toString();
330 }
331 else
332 {
333 spriteUriBase = styleUrl + '/' + styleDefinition.value( QStringLiteral( "sprite" ) ).toString();
334 }
335
336 for ( int resolution = 2; resolution > 0; resolution-- )
337 {
338 QUrl spriteUrl = QUrl( spriteUriBase );
339 spriteUrl.setPath( spriteUrl.path() + QStringLiteral( "%1.json" ).arg( resolution > 1 ? QStringLiteral( "@%1x" ).arg( resolution ) : QString() ) );
340 QNetworkRequest request = QNetworkRequest( spriteUrl );
341 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLayer" ) )
342 QgsBlockingNetworkRequest networkRequest;
343 switch ( networkRequest.get( request ) )
344 {
346 {
347 const QgsNetworkReplyContent content = networkRequest.reply();
348 const QVariantMap spriteDefinition = QgsJsonUtils::parseJson( content.content() ).toMap();
349
350 // retrieve sprite images
351 QUrl spriteUrl = QUrl( spriteUriBase );
352 spriteUrl.setPath( spriteUrl.path() + QStringLiteral( "%1.png" ).arg( resolution > 1 ? QStringLiteral( "@%1x" ).arg( resolution ) : QString() ) );
353 QNetworkRequest request = QNetworkRequest( spriteUrl );
354 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLayer" ) )
355 QgsBlockingNetworkRequest networkRequest;
356 switch ( networkRequest.get( request ) )
357 {
359 {
360 const QgsNetworkReplyContent imageContent = networkRequest.reply();
361 const QImage spriteImage( QImage::fromData( imageContent.content() ) );
362 context.setSprites( spriteImage, spriteDefinition );
363 break;
364 }
365
369 break;
370 }
371
372 break;
373 }
374
378 break;
379 }
380
381 if ( !context.spriteDefinitions().isEmpty() )
382 break;
383 }
384 }
385}
386
Abstract base class for all geometries.
virtual QgsAbstractGeometry * clone() const =0
Clones the geometry by performing a deep copy.
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
void setAuthCfg(const QString &authCfg)
Sets the authentication config id which should be used during the request.
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr, RequestFlags requestFlags=QgsBlockingNetworkRequest::RequestFlags())
Performs a "get" operation on the specified request.
@ NetworkError
A network error occurred.
@ ServerExceptionError
An exception was raised by the server.
@ NoError
No error was encountered.
@ TimeoutError
Timeout was reached before a reply was received.
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get(), post(), head() or put() request has been mad...
Handles coordinate transforms between two coordinate systems.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform the point from the source CRS to the destination CRS.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:53
Container of fields for a vector layer.
Definition qgsfields.h:46
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:70
QList< QgsField > toList() const
Utility function to return a list of QgsField instances.
virtual bool addGeometry(QgsAbstractGeometry *g)
Adds a geometry and takes ownership. Returns true in case of success.
A geometry is the spatial representation of a feature.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Implements simple HTTP header management.
bool updateNetworkRequest(QNetworkRequest &request) const
Updates a request by adding all the HTTP headers.
static QVariant parseJson(const std::string &jsonString)
Converts JSON jsonString to a QVariant, in case of parsing error an invalid QVariant is returned and ...
Context for a MapBox GL style conversion operation.
void setSprites(const QImage &image, const QVariantMap &definitions)
Sets the sprite image and definitions JSON to use during conversion.
QImage spriteImage() const
Returns the sprite image to use during conversion, or an invalid image if this is not set.
QVariantMap spriteDefinitions() const
Returns the sprite definitions to use during conversion.
Perform transforms between map coordinates and device coordinates.
QgsPointXY transform(const QgsPointXY &p) const
Transforms a point p from map (world) coordinates to device coordinates.
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
QByteArray content() const
Returns the reply content.
Represents a 2D point.
Definition qgspointxy.h:60
QPointF toQPointF() const
Converts a point to a QPointF.
Definition qgspointxy.h:165
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
Defines a matrix of tiles for a single zoom level: it is defined by its size (width *.
Definition qgstiles.h:142
QgsRectangle tileExtent(QgsTileXYZ id) const
Returns extent of the given tile in this matrix.
Definition qgstiles.cpp:81
int matrixHeight() const
Returns number of rows of the tile matrix.
Definition qgstiles.h:194
Stores coordinates of a tile in a tile matrix set.
Definition qgstiles.h:40
int zoomLevel() const
Returns tile's zoom level (Z)
Definition qgstiles.h:53
int column() const
Returns tile's column index (X)
Definition qgstiles.h:49
int row() const
Returns tile's row index (Y)
Definition qgstiles.h:51
bool addFeatures(QgsFeatureList &flist, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) override
Adds a list of features to the sink.
virtual bool addAttributes(const QList< QgsField > &attributes)
Adds new attributes to the provider.
Represents a vector layer which manages a vector based dataset.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
void updateFields()
Will regenerate the fields property of this layer by obtaining all fields from the dataProvider,...
virtual void updateExtents(bool force=false)
Update the extents for the layer.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
Implements a map layer that is dedicated to rendering of vector tiles.
QgsVectorTileRawData getRawTile(QgsTileXYZ tileID)
Fetches raw tile data for the give tile coordinates.
QgsVectorTileMatrixSet & tileMatrixSet()
Returns the vector tile matrix set.
Responsible for decoding raw tile data written with Mapbox Vector Tiles encoding.
QStringList layerFieldNames(const QString &layerName) const
Returns a list of all field names in a tile. It can only be called after a successful decode()
bool decode(const QgsVectorTileRawData &rawTileData)
Tries to decode raw tile data, returns true on success.
QgsVectorTileFeatures layerFeatures(const QMap< QString, QgsFields > &perLayerFields, const QgsCoordinateTransform &ct, const QSet< QString > *layerSubset=nullptr) const
Returns decoded features grouped by sub-layers.
Keeps track of raw tile data from one or more sources that need to be decoded.
static QgsVectorLayer * makeVectorLayerForTile(QgsVectorTileLayer *mvt, QgsTileXYZ tileID, const QString &layerName)
Returns a temporary vector layer for given sub-layer of tile in vector tile layer.
static bool checkXYZUrlTemplate(const QString &url)
Checks whether the URL template string is correct (contains {x}, {y} / {-y}, {z} placeholders)
static QString formatXYZUrlTemplate(const QString &url, QgsTileXYZ tile, const QgsTileMatrix &tileMatrix)
Returns formatted tile URL string replacing {x}, {y}, {z} placeholders (or {-y} instead of {y} for TM...
static void sortTilesByDistanceFromCenter(QVector< QgsTileXYZ > &tiles, QPointF center)
Orders tile requests according to the distance from view center (given in tile matrix coords)
static QPolygon tilePolygon(QgsTileXYZ id, const QgsCoordinateTransform &ct, const QgsTileMatrix &tm, const QgsMapToPixel &mtp)
Returns polygon (made by four corners of the tile) in screen coordinates.
static double scaleToZoom(double mapScale, double z0Scale=559082264.0287178)
Finds zoom level given map scale denominator.
static int scaleToZoomLevel(double mapScale, int sourceMinZoom, int sourceMaxZoom, double z0Scale=559082264.0287178)
Finds the best fitting zoom level given a map scale denominator and allowed zoom level range.
static void updateUriSources(QString &uri SIP_INOUT, bool forceUpdate=false)
Parses the style URL to update the source URLs in the uri.
static QgsFields makeQgisFields(const QSet< QString > &flds)
Returns QgsFields instance based on the set of field names.
static void loadSprites(const QVariantMap &styleDefinition, QgsMapBoxGlStyleConversionContext &context, const QString &styleUrl=QString())
Downloads the sprite image and sets it to the conversion context.
QList< QgsFeature > QgsFeatureList
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41
#define QgsDebugError(str)
Definition qgslogger.h:40
#define QgsSetRequestInitiatorClass(request, _class)
QMap< QString, QVector< QgsFeature > > QgsVectorTileFeatures
Features of a vector tile, grouped by sub-layer names (key of the map)
a helper class for ordering tile requests according to the distance from view center
bool operator()(QgsTileXYZ req1, QgsTileXYZ req2)
QPointF center
Center in tile matrix (!) coordinates.