QGIS API Documentation 3.99.0-Master (8e76e220402)
Loading...
Searching...
No Matches
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
22#include "qgsfields.h"
24#include "qgsjsonutils.h"
25#include "qgslogger.h"
27#include "qgsmaptopixel.h"
28#include "qgsrectangle.h"
30#include "qgsvectorlayer.h"
32#include "qgsvectortilelayer.h"
33#include "qgsvectortileloader.h"
36
37#include <QJsonArray>
38#include <QJsonDocument>
39#include <QPolygon>
40#include <QString>
41
42using namespace Qt::StringLiterals;
43
44void QgsVectorTileUtils::updateUriSources( QString &uri, bool forceUpdate )
45{
46 QgsVectorTileProviderConnection::Data data = QgsVectorTileProviderConnection::decodedUri( uri );
47 if ( forceUpdate || ( data.url.isEmpty() && !data.styleUrl.isEmpty() ) )
48 {
49 const QMap<QString, QString> sources = QgsVectorTileUtils::parseStyleSourceUrl( data.styleUrl, data.httpHeaders, data.authCfg );
50 QMap<QString, QString>::const_iterator it = sources.constBegin();
51 int i = 0;
52 for ( ; it != sources.constEnd(); ++it )
53 {
54 i += 1;
55 QString urlKey = u"url"_s;
56 QString nameKey = u"urlName"_s;
57 if ( i > 1 )
58 {
59 urlKey.append( QString( "_%1" ).arg( i ) );
60 nameKey.append( QString( "_%1" ).arg( i ) );
61 }
62 uri.append( QString( "&%1=%2" ).arg( nameKey, it.key() ) );
63 uri.append( QString( "&%1=%2" ).arg( urlKey, it.value() ) );
64 }
65 }
66}
67
68QMap<QString, QString> QgsVectorTileUtils::parseStyleSourceUrl( const QString &styleUrl, const QgsHttpHeaders &headers, const QString &authCfg )
69{
70 QNetworkRequest nr;
71 nr.setUrl( QUrl( styleUrl ) );
72 headers.updateNetworkRequest( nr );
73
75 req.setAuthCfg( authCfg );
76 QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr, false );
78 {
79 QgsDebugError( u"Request failed: "_s + styleUrl );
80 return QMap<QString, QString>();
81 }
82 QgsNetworkReplyContent reply = req.reply();
83
84 QJsonParseError err;
85 const QJsonDocument doc = QJsonDocument::fromJson( reply.content(), &err );
86 if ( doc.isNull() )
87 {
88 QgsDebugError( u"Could not load style: %1"_s.arg( err.errorString() ) );
89 }
90 else if ( !doc.isObject() )
91 {
92 QgsDebugError( u"Could not read style, JSON object is expected"_s );
93 }
94 else
95 {
96 QMap<QString, QString> sources;
97 QJsonObject sourcesData = doc.object().value( u"sources"_s ).toObject();
98 if ( sourcesData.count() == 0 )
99 {
100 QgsDebugError( u"Could not read sources in the style"_s );
101 }
102 else
103 {
104 QJsonObject::const_iterator it = sourcesData.constBegin();
105 for ( ; it != sourcesData.constEnd(); ++it )
106 {
107 const QString sourceName = it.key();
108 const QJsonObject sourceData = it.value().toObject();
109 if ( sourceData.value( u"type"_s ).toString() != "vector"_L1 )
110 {
111 // raster layers are handled separately
112 // ideally we should handle the sources here also, the same way than for vector
113 continue;
114 }
115 QVariantList tiles;
116 QString tilesFrom;
117 if ( sourceData.contains( u"tiles"_s ) )
118 {
119 tiles = sourceData["tiles"].toArray().toVariantList();
120 tilesFrom = styleUrl;
121 }
122 else if ( sourceData.contains( u"url"_s ) )
123 {
124 tiles = parseStyleSourceContentUrl( sourceData.value( u"url"_s ).toString(), headers, authCfg );
125 tilesFrom = sourceData.value( u"url"_s ).toString();
126 }
127 else
128 {
129 QgsDebugError( u"Could not read source %1"_s.arg( sourceName ) );
130 }
131
132 if ( !tiles.isEmpty() )
133 {
134 // take a random one from the list
135 // we might want to save the alternatives for a fallback later
136 QString tile = tiles[rand() % tiles.count()].toString();
137 QUrl tileUrl( tile );
138 if ( tileUrl.isRelative() )
139 {
140 QUrl tilesFromUrl( tilesFrom );
141 if ( tile.startsWith( "/" ) )
142 {
143 tile = u"%1://%2%3"_s.arg( tilesFromUrl.scheme(), tilesFromUrl.host(), tile );
144 }
145 else
146 {
147 const QString fileName = tilesFromUrl.fileName();
148 if ( !fileName.isEmpty() && fileName.indexOf( "." ) >= 0 )
149 {
150 tilesFrom = tilesFrom.mid( 0, tilesFrom.length() - fileName.length() );
151 }
152 tile = u"%1/%2"_s.arg( tilesFrom, tile );
153 }
154 }
155 sources.insert( sourceName, tile );
156 }
157 else
158 {
159 QgsDebugError( u"Could not read source %1, not tiles found"_s.arg( sourceName ) );
160 }
161 }
162 return sources;
163 }
164 }
165 return QMap<QString, QString>();
166}
167
168QVariantList QgsVectorTileUtils::parseStyleSourceContentUrl( const QString &sourceUrl, const QgsHttpHeaders &headers, const QString &authCfg )
169{
170 QNetworkRequest nr;
171 nr.setUrl( QUrl( sourceUrl ) );
172 headers.updateNetworkRequest( nr );
173
174 QgsBlockingNetworkRequest req;
175 req.setAuthCfg( authCfg );
176 QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr, false );
177 if ( errCode != QgsBlockingNetworkRequest::NoError )
178 {
179 QgsDebugError( u"Request failed: "_s + sourceUrl );
180 return QVariantList();
181 }
182 QgsNetworkReplyContent reply = req.reply();
183
184 QJsonParseError err;
185 const QJsonDocument doc = QJsonDocument::fromJson( reply.content(), &err );
186 if ( doc.isNull() )
187 {
188 QgsDebugError( u"Could not load style: %1"_s.arg( err.errorString() ) );
189 }
190 else if ( !doc.isObject() )
191 {
192 QgsDebugError( u"Could not read style, JSON object is expected"_s );
193 }
194 else
195 {
196 return doc.object().value( u"tiles"_s ).toArray().toVariantList();
197 }
198 return QVariantList();
199}
200
201
202
204{
205 QgsRectangle r = tm.tileExtent( id );
206 QgsPointXY p00a = mtp.transform( ct.transform( r.xMinimum(), r.yMinimum() ) );
207 QgsPointXY p11a = mtp.transform( ct.transform( r.xMaximum(), r.yMaximum() ) );
208 QgsPointXY p01a = mtp.transform( ct.transform( r.xMinimum(), r.yMaximum() ) );
209 QgsPointXY p10a = mtp.transform( ct.transform( r.xMaximum(), r.yMinimum() ) );
210 QPolygon path;
211 path << p00a.toQPointF().toPoint();
212 path << p01a.toQPointF().toPoint();
213 path << p11a.toQPointF().toPoint();
214 path << p10a.toQPointF().toPoint();
215 return path;
216}
217
219{
220 QgsFields fields;
221 QStringList fieldsSorted = qgis::setToList( flds );
222 std::sort( fieldsSorted.begin(), fieldsSorted.end() );
223 for ( const QString &fieldName : std::as_const( fieldsSorted ) )
224 {
225 fields.append( QgsField( fieldName, QMetaType::Type::QString ) );
226 }
227 return fields;
228}
229
230double QgsVectorTileUtils::scaleToZoom( double mapScale, double z0Scale )
231{
232 double s0 = z0Scale;
233 double tileZoom2 = log( s0 / mapScale ) / log( 2 );
234 tileZoom2 -= 1; // TODO: it seems that map scale is double (is that because of high-dpi screen?)
235 return tileZoom2;
236}
237
238int QgsVectorTileUtils::scaleToZoomLevel( double mapScale, int sourceMinZoom, int sourceMaxZoom, double z0Scale )
239{
240 int tileZoom = static_cast<int>( round( scaleToZoom( mapScale, z0Scale ) ) );
241
242 if ( tileZoom < sourceMinZoom )
243 tileZoom = sourceMinZoom;
244 if ( tileZoom > sourceMaxZoom )
245 tileZoom = sourceMaxZoom;
246
247 return tileZoom;
248}
249
251{
252 QgsVectorTileMVTDecoder decoder( mvt->tileMatrixSet() );
253 const QgsVectorTileRawData rawTile = mvt->getRawTile( tileID );
254 decoder.decode( rawTile );
255 QSet<QString> fieldNames = qgis::listToSet( decoder.layerFieldNames( layerName ) );
256 fieldNames << u"_geom_type"_s;
257 QMap<QString, QgsFields> perLayerFields;
258 QgsFields fields = QgsVectorTileUtils::makeQgisFields( fieldNames );
259 perLayerFields[layerName] = fields;
260 QgsVectorTileFeatures data = decoder.layerFeatures( perLayerFields, QgsCoordinateTransform() );
261 QgsFeatureList featuresList = data[layerName].toList();
262
263 // turn all geometries to geom. collections (otherwise they won't be accepted by memory provider)
264 for ( int i = 0; i < featuresList.count(); ++i )
265 {
266 QgsGeometry g = featuresList[i].geometry();
268 const QgsAbstractGeometry *gg = g.constGet();
270 {
271 for ( int k = 0; k < ggc->numGeometries(); ++k )
272 gc->addGeometry( ggc->geometryN( k )->clone() );
273 }
274 else
275 gc->addGeometry( gg->clone() );
276 featuresList[i].setGeometry( QgsGeometry( gc ) );
277 }
278
279 QgsVectorLayer *vl = new QgsVectorLayer( u"GeometryCollection"_s, layerName, u"memory"_s );
280 vl->dataProvider()->addAttributes( fields.toList() );
281 vl->updateFields();
282 bool res = vl->dataProvider()->addFeatures( featuresList );
283 Q_UNUSED( res )
284 Q_ASSERT( res );
285 Q_ASSERT( featuresList.count() == vl->featureCount() );
286 vl->updateExtents();
287 QgsDebugMsgLevel( u"Layer %1 features %2"_s.arg( layerName ).arg( vl->featureCount() ), 2 );
288 return vl;
289}
290
291
292QString QgsVectorTileUtils::formatXYZUrlTemplate( const QString &url, QgsTileXYZ tile, const QgsTileMatrix &tileMatrix )
293{
294 QString turl( url );
295
296 turl.replace( "{x}"_L1, QString::number( tile.column() ), Qt::CaseInsensitive );
297 if ( turl.contains( "{-y}"_L1 ) )
298 {
299 turl.replace( "{-y}"_L1, QString::number( tileMatrix.matrixHeight() - tile.row() - 1 ), Qt::CaseInsensitive );
300 }
301 else
302 {
303 turl.replace( "{y}"_L1, QString::number( tile.row() ), Qt::CaseInsensitive );
304 }
305 turl.replace( "{z}"_L1, QString::number( tile.zoomLevel() ), Qt::CaseInsensitive );
306 return turl;
307}
308
310{
311 return url.contains( u"{x}"_s ) &&
312 ( url.contains( u"{y}"_s ) || url.contains( u"{-y}"_s ) ) &&
313 url.contains( u"{z}"_s );
314}
315
318{
319 QPointF center;
321 {
322 QPointF p1( req1.column() + 0.5, req1.row() + 0.5 );
323 QPointF p2( req2.column() + 0.5, req2.row() + 0.5 );
324 // using chessboard distance (loading order more natural than euclidean/manhattan distance)
325 double d1 = std::max( std::fabs( center.x() - p1.x() ), std::fabs( center.y() - p1.y() ) );
326 double d2 = std::max( std::fabs( center.x() - p2.x() ), std::fabs( center.y() - p2.y() ) );
327 return d1 < d2;
328 }
329};
330
331void QgsVectorTileUtils::sortTilesByDistanceFromCenter( QVector<QgsTileXYZ> &tiles, QPointF center )
332{
334 cmp.center = center;
335 std::sort( tiles.begin(), tiles.end(), cmp );
336}
337
338void QgsVectorTileUtils::loadSprites( const QVariantMap &styleDefinition, QgsMapBoxGlStyleConversionContext &context, const QString &styleUrl )
339{
340 if ( styleDefinition.contains( u"sprite"_s ) && ( context.spriteCategories().isEmpty() ) )
341 {
342 auto prepareSpriteUrl = []( const QString & sprite, const QString & styleUrl )
343 {
344 if ( sprite.startsWith( "http"_L1 ) )
345 {
346 return sprite;
347 }
348 else if ( sprite.startsWith( '/'_L1 ) )
349 {
350 const QUrl url( styleUrl );
351 return u"%1://%2%3"_s.arg( url.scheme(), url.host(), sprite );
352 }
353
354 return u"%1/%2"_s.arg( styleUrl, sprite );
355 };
356
357 // retrieve sprite definition
358 QMap<QString, QString> sprites;
359 const QVariant spriteVariant = styleDefinition.value( u"sprite"_s );
360 if ( spriteVariant.userType() == QMetaType::Type::QVariantList )
361 {
362 const QVariantList spriteList = spriteVariant.toList();
363 for ( const QVariant &spriteItem : spriteList )
364 {
365 QVariantMap spriteMap = spriteItem.toMap();
366 if ( spriteMap.contains( u"id"_s ) && spriteMap.contains( "url" ) )
367 {
368 sprites[spriteMap.value( u"id"_s ).toString()] = prepareSpriteUrl( spriteMap.value( u"url"_s ).toString(), styleUrl );
369 }
370 }
371 }
372 else
373 {
374 sprites[""] = prepareSpriteUrl( spriteVariant.toString(), styleUrl );
375 }
376
377 if ( sprites.isEmpty() )
378 {
379 return;
380 }
381
382 QMap<QString, QString>::const_iterator spritesIterator = sprites.constBegin();
383 while ( spritesIterator != sprites.end() )
384 {
385 for ( int resolution = 2; resolution > 0; resolution-- )
386 {
387 QUrl spriteUrl = QUrl( spritesIterator.value() );
388 spriteUrl.setPath( spriteUrl.path() + u"%1.json"_s.arg( resolution > 1 ? u"@%1x"_s.arg( resolution ) : QString() ) );
389 QNetworkRequest request = QNetworkRequest( spriteUrl );
390 QgsSetRequestInitiatorClass( request, u"QgsVectorTileLayer"_s )
391 QgsBlockingNetworkRequest networkRequest;
392 switch ( networkRequest.get( request ) )
393 {
395 {
396 const QgsNetworkReplyContent content = networkRequest.reply();
397 const QVariantMap spriteDefinition = QgsJsonUtils::parseJson( content.content() ).toMap();
398
399 // retrieve sprite images
400 QUrl spriteUrl = QUrl( spritesIterator.value() );
401 spriteUrl.setPath( spriteUrl.path() + u"%1.png"_s.arg( resolution > 1 ? u"@%1x"_s.arg( resolution ) : QString() ) );
402 QNetworkRequest request = QNetworkRequest( spriteUrl );
403 QgsSetRequestInitiatorClass( request, u"QgsVectorTileLayer"_s )
404 QgsBlockingNetworkRequest networkRequest;
405 switch ( networkRequest.get( request ) )
406 {
408 {
409 const QgsNetworkReplyContent imageContent = networkRequest.reply();
410 const QImage spriteImage( QImage::fromData( imageContent.content() ) );
411 context.setSprites( spriteImage, spriteDefinition, spritesIterator.key() );
412 break;
413 }
414
418 break;
419 }
420
421 break;
422 }
423
427 break;
428 }
429
430 if ( !context.spriteDefinitions().isEmpty() )
431 break;
432 }
433
434 ++spritesIterator;
435 }
436 }
437}
438
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:56
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:76
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, const QString &category=QString())
Sets the sprite image and definitions JSON for a given category to use during conversion.
QVariantMap spriteDefinitions(const QString &category=QString()) const
Returns the sprite definitions for a given category to use during conversion.
QStringList spriteCategories() const
Returns the list of sprite categories to use during conversion, or an empty list of none is set.
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:62
QPointF toQPointF() const
Converts a point to a QPointF.
Definition qgspointxy.h:167
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:162
QgsRectangle tileExtent(QgsTileXYZ id) const
Returns extent of the given tile in this matrix.
Definition qgstiles.cpp:85
int matrixHeight() const
Returns number of rows of the tile matrix.
Definition qgstiles.h:214
Stores coordinates of a tile in a tile matrix set.
Definition qgstiles.h:43
int zoomLevel() const
Returns tile's zoom level (Z).
Definition qgstiles.h:56
int column() const
Returns tile's column index (X).
Definition qgstiles.h:52
int row() const
Returns tile's row index (Y).
Definition qgstiles.h:54
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}...
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.
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QList< QgsFeature > QgsFeatureList
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
#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.