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