Branch data Line data Source code
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 : : 22 : : #include "qgscoordinatetransform.h" 23 : : #include "qgsgeometrycollection.h" 24 : : #include "qgsfields.h" 25 : : #include "qgslogger.h" 26 : : #include "qgsmaptopixel.h" 27 : : #include "qgsrectangle.h" 28 : : #include "qgsvectorlayer.h" 29 : : 30 : : #include "qgsvectortilemvtdecoder.h" 31 : : #include "qgsvectortilelayer.h" 32 : : #include "qgsvectortilerenderer.h" 33 : : 34 : : 35 : : 36 : 0 : QPolygon QgsVectorTileUtils::tilePolygon( QgsTileXYZ id, const QgsCoordinateTransform &ct, const QgsTileMatrix &tm, const QgsMapToPixel &mtp ) 37 : : { 38 : 0 : QgsRectangle r = tm.tileExtent( id ); 39 : 0 : QgsPointXY p00a = mtp.transform( ct.transform( r.xMinimum(), r.yMinimum() ) ); 40 : 0 : QgsPointXY p11a = mtp.transform( ct.transform( r.xMaximum(), r.yMaximum() ) ); 41 : 0 : QgsPointXY p01a = mtp.transform( ct.transform( r.xMinimum(), r.yMaximum() ) ); 42 : 0 : QgsPointXY p10a = mtp.transform( ct.transform( r.xMaximum(), r.yMinimum() ) ); 43 : 0 : QPolygon path; 44 : 0 : path << p00a.toQPointF().toPoint(); 45 : 0 : path << p01a.toQPointF().toPoint(); 46 : 0 : path << p11a.toQPointF().toPoint(); 47 : 0 : path << p10a.toQPointF().toPoint(); 48 : 0 : return path; 49 : 0 : } 50 : : 51 : 0 : QgsFields QgsVectorTileUtils::makeQgisFields( QSet<QString> flds ) 52 : : { 53 : 0 : QgsFields fields; 54 : 0 : QStringList fieldsSorted = qgis::setToList( flds ); 55 : 0 : std::sort( fieldsSorted.begin(), fieldsSorted.end() ); 56 : 0 : for ( const QString &fieldName : std::as_const( fieldsSorted ) ) 57 : : { 58 : 0 : fields.append( QgsField( fieldName, QVariant::String ) ); 59 : : } 60 : 0 : return fields; 61 : 0 : } 62 : : 63 : 0 : double QgsVectorTileUtils::scaleToZoom( double mapScale ) 64 : : { 65 : 0 : double s0 = 559082264.0287178; // scale denominator at zoom level 0 of GoogleCRS84Quad 66 : 0 : double tileZoom2 = log( s0 / mapScale ) / log( 2 ); 67 : 0 : tileZoom2 -= 1; // TODO: it seems that map scale is double (is that because of high-dpi screen?) 68 : 0 : return tileZoom2; 69 : : } 70 : : 71 : 0 : int QgsVectorTileUtils::scaleToZoomLevel( double mapScale, int sourceMinZoom, int sourceMaxZoom ) 72 : : { 73 : 0 : int tileZoom = static_cast<int>( floor( scaleToZoom( mapScale ) ) ); 74 : : 75 : 0 : if ( tileZoom < sourceMinZoom ) 76 : 0 : tileZoom = sourceMinZoom; 77 : 0 : if ( tileZoom > sourceMaxZoom ) 78 : 0 : tileZoom = sourceMaxZoom; 79 : : 80 : 0 : return tileZoom; 81 : : } 82 : : 83 : 0 : QgsVectorLayer *QgsVectorTileUtils::makeVectorLayerForTile( QgsVectorTileLayer *mvt, QgsTileXYZ tileID, const QString &layerName ) 84 : : { 85 : 0 : QgsVectorTileMVTDecoder decoder; 86 : 0 : decoder.decode( tileID, mvt->getRawTile( tileID ) ); 87 : 0 : QSet<QString> fieldNames = qgis::listToSet( decoder.layerFieldNames( layerName ) ); 88 : 0 : fieldNames << QStringLiteral( "_geom_type" ); 89 : 0 : QMap<QString, QgsFields> perLayerFields; 90 : 0 : QgsFields fields = QgsVectorTileUtils::makeQgisFields( fieldNames ); 91 : 0 : perLayerFields[layerName] = fields; 92 : 0 : QgsVectorTileFeatures data = decoder.layerFeatures( perLayerFields, QgsCoordinateTransform() ); 93 : 0 : QgsFeatureList featuresList = data[layerName].toList(); 94 : : 95 : : // turn all geometries to geom. collections (otherwise they won't be accepted by memory provider) 96 : 0 : for ( int i = 0; i < featuresList.count(); ++i ) 97 : : { 98 : 0 : QgsGeometry g = featuresList[i].geometry(); 99 : 0 : QgsGeometryCollection *gc = new QgsGeometryCollection; 100 : 0 : const QgsAbstractGeometry *gg = g.constGet(); 101 : 0 : if ( const QgsGeometryCollection *ggc = qgsgeometry_cast<const QgsGeometryCollection *>( gg ) ) 102 : : { 103 : 0 : for ( int k = 0; k < ggc->numGeometries(); ++k ) 104 : 0 : gc->addGeometry( ggc->geometryN( k )->clone() ); 105 : 0 : } 106 : : else 107 : 0 : gc->addGeometry( gg->clone() ); 108 : 0 : featuresList[i].setGeometry( QgsGeometry( gc ) ); 109 : 0 : } 110 : : 111 : 0 : QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "GeometryCollection" ), layerName, QStringLiteral( "memory" ) ); 112 : 0 : vl->dataProvider()->addAttributes( fields.toList() ); 113 : 0 : vl->updateFields(); 114 : 0 : bool res = vl->dataProvider()->addFeatures( featuresList ); 115 : : Q_UNUSED( res ); 116 : : Q_ASSERT( res ); 117 : : Q_ASSERT( featuresList.count() == vl->featureCount() ); 118 : 0 : vl->updateExtents(); 119 : 0 : QgsDebugMsgLevel( QStringLiteral( "Layer %1 features %2" ).arg( layerName ).arg( vl->featureCount() ), 2 ); 120 : 0 : return vl; 121 : 0 : } 122 : : 123 : : 124 : 0 : QString QgsVectorTileUtils::formatXYZUrlTemplate( const QString &url, QgsTileXYZ tile, const QgsTileMatrix &tileMatrix ) 125 : : { 126 : 0 : QString turl( url ); 127 : : 128 : 0 : turl.replace( QLatin1String( "{x}" ), QString::number( tile.column() ), Qt::CaseInsensitive ); 129 : 0 : if ( turl.contains( QLatin1String( "{-y}" ) ) ) 130 : : { 131 : 0 : turl.replace( QLatin1String( "{-y}" ), QString::number( tileMatrix.matrixHeight() - tile.row() - 1 ), Qt::CaseInsensitive ); 132 : 0 : } 133 : : else 134 : : { 135 : 0 : turl.replace( QLatin1String( "{y}" ), QString::number( tile.row() ), Qt::CaseInsensitive ); 136 : : } 137 : 0 : turl.replace( QLatin1String( "{z}" ), QString::number( tile.zoomLevel() ), Qt::CaseInsensitive ); 138 : 0 : return turl; 139 : 0 : } 140 : : 141 : 0 : bool QgsVectorTileUtils::checkXYZUrlTemplate( const QString &url ) 142 : : { 143 : 0 : return url.contains( QStringLiteral( "{x}" ) ) && 144 : 0 : ( url.contains( QStringLiteral( "{y}" ) ) || url.contains( QStringLiteral( "{-y}" ) ) ) && 145 : 0 : url.contains( QStringLiteral( "{z}" ) ); 146 : 0 : } 147 : : 148 : : //! a helper class for ordering tile requests according to the distance from view center 149 : 0 : struct LessThanTileRequest 150 : : { 151 : : QPointF center; //!< Center in tile matrix (!) coordinates 152 : 0 : bool operator()( const QgsTileXYZ &req1, const QgsTileXYZ &req2 ) 153 : : { 154 : 0 : QPointF p1( req1.column() + 0.5, req1.row() + 0.5 ); 155 : 0 : QPointF p2( req2.column() + 0.5, req2.row() + 0.5 ); 156 : : // using chessboard distance (loading order more natural than euclidean/manhattan distance) 157 : 0 : double d1 = std::max( std::fabs( center.x() - p1.x() ), std::fabs( center.y() - p1.y() ) ); 158 : 0 : double d2 = std::max( std::fabs( center.x() - p2.x() ), std::fabs( center.y() - p2.y() ) ); 159 : 0 : return d1 < d2; 160 : : } 161 : : }; 162 : : 163 : 0 : QVector<QgsTileXYZ> QgsVectorTileUtils::tilesInRange( const QgsTileRange &range, int zoomLevel ) 164 : : { 165 : 0 : QVector<QgsTileXYZ> tiles; 166 : 0 : for ( int tileRow = range.startRow(); tileRow <= range.endRow(); ++tileRow ) 167 : : { 168 : 0 : for ( int tileColumn = range.startColumn(); tileColumn <= range.endColumn(); ++tileColumn ) 169 : : { 170 : 0 : tiles.append( QgsTileXYZ( tileColumn, tileRow, zoomLevel ) ); 171 : 0 : } 172 : 0 : } 173 : 0 : return tiles; 174 : 0 : } 175 : : 176 : 0 : void QgsVectorTileUtils::sortTilesByDistanceFromCenter( QVector<QgsTileXYZ> &tiles, const QPointF ¢er ) 177 : : { 178 : 0 : LessThanTileRequest cmp; 179 : 0 : cmp.center = center; 180 : 0 : std::sort( tiles.begin(), tiles.end(), cmp ); 181 : 0 : }