QGIS API Documentation 3.40.0-Bratislava (b56115d8743)
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
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 sources.insert( sourceName, tiles[rand() % tiles.count()].toString() );
136 }
137 }
138 return sources;
139 }
140 }
141 return QMap<QString, QString>();
142}
143
144QVariantList QgsVectorTileUtils::parseStyleSourceContentUrl( const QString &sourceUrl, const QgsHttpHeaders &headers, const QString &authCfg )
145{
146 QNetworkRequest nr;
147 nr.setUrl( QUrl( sourceUrl ) );
148 headers.updateNetworkRequest( nr );
149
151 req.setAuthCfg( authCfg );
152 QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr, false );
153 if ( errCode != QgsBlockingNetworkRequest::NoError )
154 {
155 QgsDebugError( QStringLiteral( "Request failed: " ) + sourceUrl );
156 return QVariantList();
157 }
158 QgsNetworkReplyContent reply = req.reply();
159
160 QJsonParseError err;
161 const QJsonDocument doc = QJsonDocument::fromJson( reply.content(), &err );
162 if ( doc.isNull() )
163 {
164 QgsDebugError( QStringLiteral( "Could not load style: %1" ).arg( err.errorString() ) );
165 }
166 else if ( !doc.isObject() )
167 {
168 QgsDebugError( QStringLiteral( "Could not read style, JSON object is expected" ) );
169 }
170 else
171 {
172 return doc.object().value( QStringLiteral( "tiles" ) ).toArray().toVariantList();
173 }
174 return QVariantList();
175}
176
177
178
180{
181 QgsRectangle r = tm.tileExtent( id );
182 QgsPointXY p00a = mtp.transform( ct.transform( r.xMinimum(), r.yMinimum() ) );
183 QgsPointXY p11a = mtp.transform( ct.transform( r.xMaximum(), r.yMaximum() ) );
184 QgsPointXY p01a = mtp.transform( ct.transform( r.xMinimum(), r.yMaximum() ) );
185 QgsPointXY p10a = mtp.transform( ct.transform( r.xMaximum(), r.yMinimum() ) );
186 QPolygon path;
187 path << p00a.toQPointF().toPoint();
188 path << p01a.toQPointF().toPoint();
189 path << p11a.toQPointF().toPoint();
190 path << p10a.toQPointF().toPoint();
191 return path;
192}
193
195{
196 QgsFields fields;
197 QStringList fieldsSorted = qgis::setToList( flds );
198 std::sort( fieldsSorted.begin(), fieldsSorted.end() );
199 for ( const QString &fieldName : std::as_const( fieldsSorted ) )
200 {
201 fields.append( QgsField( fieldName, QMetaType::Type::QString ) );
202 }
203 return fields;
204}
205
206double QgsVectorTileUtils::scaleToZoom( double mapScale, double z0Scale )
207{
208 double s0 = z0Scale;
209 double tileZoom2 = log( s0 / mapScale ) / log( 2 );
210 tileZoom2 -= 1; // TODO: it seems that map scale is double (is that because of high-dpi screen?)
211 return tileZoom2;
212}
213
214int QgsVectorTileUtils::scaleToZoomLevel( double mapScale, int sourceMinZoom, int sourceMaxZoom, double z0Scale )
215{
216 int tileZoom = static_cast<int>( round( scaleToZoom( mapScale, z0Scale ) ) );
217
218 if ( tileZoom < sourceMinZoom )
219 tileZoom = sourceMinZoom;
220 if ( tileZoom > sourceMaxZoom )
221 tileZoom = sourceMaxZoom;
222
223 return tileZoom;
224}
225
227{
228 QgsVectorTileMVTDecoder decoder( mvt->tileMatrixSet() );
229 const QgsVectorTileRawData rawTile = mvt->getRawTile( tileID );
230 decoder.decode( rawTile );
231 QSet<QString> fieldNames = qgis::listToSet( decoder.layerFieldNames( layerName ) );
232 fieldNames << QStringLiteral( "_geom_type" );
233 QMap<QString, QgsFields> perLayerFields;
234 QgsFields fields = QgsVectorTileUtils::makeQgisFields( fieldNames );
235 perLayerFields[layerName] = fields;
236 QgsVectorTileFeatures data = decoder.layerFeatures( perLayerFields, QgsCoordinateTransform() );
237 QgsFeatureList featuresList = data[layerName].toList();
238
239 // turn all geometries to geom. collections (otherwise they won't be accepted by memory provider)
240 for ( int i = 0; i < featuresList.count(); ++i )
241 {
242 QgsGeometry g = featuresList[i].geometry();
244 const QgsAbstractGeometry *gg = g.constGet();
245 if ( const QgsGeometryCollection *ggc = qgsgeometry_cast<const QgsGeometryCollection *>( gg ) )
246 {
247 for ( int k = 0; k < ggc->numGeometries(); ++k )
248 gc->addGeometry( ggc->geometryN( k )->clone() );
249 }
250 else
251 gc->addGeometry( gg->clone() );
252 featuresList[i].setGeometry( QgsGeometry( gc ) );
253 }
254
255 QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "GeometryCollection" ), layerName, QStringLiteral( "memory" ) );
256 vl->dataProvider()->addAttributes( fields.toList() );
257 vl->updateFields();
258 bool res = vl->dataProvider()->addFeatures( featuresList );
259 Q_UNUSED( res )
260 Q_ASSERT( res );
261 Q_ASSERT( featuresList.count() == vl->featureCount() );
262 vl->updateExtents();
263 QgsDebugMsgLevel( QStringLiteral( "Layer %1 features %2" ).arg( layerName ).arg( vl->featureCount() ), 2 );
264 return vl;
265}
266
267
268QString QgsVectorTileUtils::formatXYZUrlTemplate( const QString &url, QgsTileXYZ tile, const QgsTileMatrix &tileMatrix )
269{
270 QString turl( url );
271
272 turl.replace( QLatin1String( "{x}" ), QString::number( tile.column() ), Qt::CaseInsensitive );
273 if ( turl.contains( QLatin1String( "{-y}" ) ) )
274 {
275 turl.replace( QLatin1String( "{-y}" ), QString::number( tileMatrix.matrixHeight() - tile.row() - 1 ), Qt::CaseInsensitive );
276 }
277 else
278 {
279 turl.replace( QLatin1String( "{y}" ), QString::number( tile.row() ), Qt::CaseInsensitive );
280 }
281 turl.replace( QLatin1String( "{z}" ), QString::number( tile.zoomLevel() ), Qt::CaseInsensitive );
282 return turl;
283}
284
286{
287 return url.contains( QStringLiteral( "{x}" ) ) &&
288 ( url.contains( QStringLiteral( "{y}" ) ) || url.contains( QStringLiteral( "{-y}" ) ) ) &&
289 url.contains( QStringLiteral( "{z}" ) );
290}
291
294{
295 QPointF center;
297 {
298 QPointF p1( req1.column() + 0.5, req1.row() + 0.5 );
299 QPointF p2( req2.column() + 0.5, req2.row() + 0.5 );
300 // using chessboard distance (loading order more natural than euclidean/manhattan distance)
301 double d1 = std::max( std::fabs( center.x() - p1.x() ), std::fabs( center.y() - p1.y() ) );
302 double d2 = std::max( std::fabs( center.x() - p2.x() ), std::fabs( center.y() - p2.y() ) );
303 return d1 < d2;
304 }
305};
306
307void QgsVectorTileUtils::sortTilesByDistanceFromCenter( QVector<QgsTileXYZ> &tiles, QPointF center )
308{
310 cmp.center = center;
311 std::sort( tiles.begin(), tiles.end(), cmp );
312}
313
314void QgsVectorTileUtils::loadSprites( const QVariantMap &styleDefinition, QgsMapBoxGlStyleConversionContext &context, const QString &styleUrl )
315{
316 if ( styleDefinition.contains( QStringLiteral( "sprite" ) ) && ( context.spriteDefinitions().empty() || context.spriteImage().isNull() ) )
317 {
318 // retrieve sprite definition
319 QString spriteUriBase;
320 if ( styleDefinition.value( QStringLiteral( "sprite" ) ).toString().startsWith( QLatin1String( "http" ) ) )
321 {
322 spriteUriBase = styleDefinition.value( QStringLiteral( "sprite" ) ).toString();
323 }
324 else
325 {
326 spriteUriBase = styleUrl + '/' + styleDefinition.value( QStringLiteral( "sprite" ) ).toString();
327 }
328
329 for ( int resolution = 2; resolution > 0; resolution-- )
330 {
331 QUrl spriteUrl = QUrl( spriteUriBase );
332 spriteUrl.setPath( spriteUrl.path() + QStringLiteral( "%1.json" ).arg( resolution > 1 ? QStringLiteral( "@%1x" ).arg( resolution ) : QString() ) );
333 QNetworkRequest request = QNetworkRequest( spriteUrl );
334 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLayer" ) )
335 QgsBlockingNetworkRequest networkRequest;
336 switch ( networkRequest.get( request ) )
337 {
339 {
340 const QgsNetworkReplyContent content = networkRequest.reply();
341 const QVariantMap spriteDefinition = QgsJsonUtils::parseJson( content.content() ).toMap();
342
343 // retrieve sprite images
344 QUrl spriteUrl = QUrl( spriteUriBase );
345 spriteUrl.setPath( spriteUrl.path() + QStringLiteral( "%1.png" ).arg( resolution > 1 ? QStringLiteral( "@%1x" ).arg( resolution ) : QString() ) );
346 QNetworkRequest request = QNetworkRequest( spriteUrl );
347 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsVectorTileLayer" ) )
348 QgsBlockingNetworkRequest networkRequest;
349 switch ( networkRequest.get( request ) )
350 {
352 {
353 const QgsNetworkReplyContent imageContent = networkRequest.reply();
354 const QImage spriteImage( QImage::fromData( imageContent.content() ) );
355 context.setSprites( spriteImage, spriteDefinition );
356 break;
357 }
358
362 break;
363 }
364
365 break;
366 }
367
371 break;
372 }
373
374 if ( !context.spriteDefinitions().isEmpty() )
375 break;
376 }
377 }
378}
379
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...
Class for doing transforms between two map 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:69
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.
This class 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.
A class to represent 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() const
Returns the x minimum value (left side of rectangle).
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double xMaximum() const
Returns the x maximum value (right side of rectangle).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Defines a matrix of tiles for a single zoom level: it is defined by its size (width *.
Definition qgstiles.h:136
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:188
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 data sets.
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.
This class is 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:39
#define QgsDebugError(str)
Definition qgslogger.h:38
#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.