LCOV - code coverage report
Current view: top level - core/vectortile - qgsvectortilewriter.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 161 0.0 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :   qgsvectortilewriter.cpp
       3                 :            :   --------------------------------------
       4                 :            :   Date                 : April 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 "qgsvectortilewriter.h"
      17                 :            : 
      18                 :            : #include "qgsdatasourceuri.h"
      19                 :            : #include "qgsfeedback.h"
      20                 :            : #include "qgsjsonutils.h"
      21                 :            : #include "qgslogger.h"
      22                 :            : #include "qgsmbtiles.h"
      23                 :            : #include "qgstiles.h"
      24                 :            : #include "qgsvectorlayer.h"
      25                 :            : #include "qgsvectortilemvtencoder.h"
      26                 :            : #include "qgsvectortileutils.h"
      27                 :            : 
      28                 :            : #include <nlohmann/json.hpp>
      29                 :            : 
      30                 :            : #include <QDir>
      31                 :            : #include <QFile>
      32                 :            : #include <QFileInfo>
      33                 :            : #include <QUrl>
      34                 :            : 
      35                 :            : 
      36                 :          0 : QgsVectorTileWriter::QgsVectorTileWriter()
      37                 :            : {
      38                 :          0 : }
      39                 :            : 
      40                 :            : 
      41                 :          0 : bool QgsVectorTileWriter::writeTiles( QgsFeedback *feedback )
      42                 :            : {
      43                 :          0 :   if ( mMinZoom < 0 )
      44                 :            :   {
      45                 :          0 :     mErrorMessage = tr( "Invalid min. zoom level" );
      46                 :          0 :     return false;
      47                 :            :   }
      48                 :          0 :   if ( mMaxZoom > 24 )
      49                 :            :   {
      50                 :          0 :     mErrorMessage = tr( "Invalid max. zoom level" );
      51                 :          0 :     return false;
      52                 :            :   }
      53                 :            : 
      54                 :          0 :   std::unique_ptr<QgsMbTiles> mbtiles;
      55                 :            : 
      56                 :          0 :   QgsDataSourceUri dsUri;
      57                 :          0 :   dsUri.setEncodedUri( mDestinationUri );
      58                 :            : 
      59                 :          0 :   QString sourceType = dsUri.param( QStringLiteral( "type" ) );
      60                 :          0 :   QString sourcePath = dsUri.param( QStringLiteral( "url" ) );
      61                 :          0 :   if ( sourceType == QLatin1String( "xyz" ) )
      62                 :            :   {
      63                 :            :     // remove the initial file:// scheme
      64                 :          0 :     sourcePath = QUrl( sourcePath ).toLocalFile();
      65                 :            : 
      66                 :          0 :     if ( !QgsVectorTileUtils::checkXYZUrlTemplate( sourcePath ) )
      67                 :            :     {
      68                 :          0 :       mErrorMessage = tr( "Invalid template for XYZ: " ) + sourcePath;
      69                 :          0 :       return false;
      70                 :            :     }
      71                 :          0 :   }
      72                 :          0 :   else if ( sourceType == QLatin1String( "mbtiles" ) )
      73                 :            :   {
      74                 :          0 :     mbtiles.reset( new QgsMbTiles( sourcePath ) );
      75                 :          0 :   }
      76                 :            :   else
      77                 :            :   {
      78                 :          0 :     mErrorMessage = tr( "Unsupported source type for writing: " ) + sourceType;
      79                 :          0 :     return false;
      80                 :            :   }
      81                 :            : 
      82                 :          0 :   QgsRectangle outputExtent = mExtent;
      83                 :          0 :   if ( outputExtent.isEmpty() )
      84                 :            :   {
      85                 :          0 :     outputExtent = fullExtent();
      86                 :          0 :     if ( outputExtent.isEmpty() )
      87                 :            :     {
      88                 :          0 :       mErrorMessage = tr( "Failed to calculate output extent" );
      89                 :          0 :       return false;
      90                 :            :     }
      91                 :          0 :   }
      92                 :            : 
      93                 :            :   // figure out how many tiles we will need to do
      94                 :          0 :   int tilesToCreate = 0;
      95                 :          0 :   for ( int zoomLevel = mMinZoom; zoomLevel <= mMaxZoom; ++zoomLevel )
      96                 :            :   {
      97                 :          0 :     QgsTileMatrix tileMatrix = QgsTileMatrix::fromWebMercator( zoomLevel );
      98                 :            : 
      99                 :          0 :     QgsTileRange tileRange = tileMatrix.tileRangeFromExtent( outputExtent );
     100                 :          0 :     tilesToCreate += ( tileRange.endRow() - tileRange.startRow() + 1 ) *
     101                 :          0 :                      ( tileRange.endColumn() - tileRange.startColumn() + 1 );
     102                 :          0 :   }
     103                 :            : 
     104                 :          0 :   if ( tilesToCreate == 0 )
     105                 :            :   {
     106                 :          0 :     mErrorMessage = tr( "No tiles to generate" );
     107                 :          0 :     return false;
     108                 :            :   }
     109                 :            : 
     110                 :          0 :   if ( mbtiles )
     111                 :            :   {
     112                 :          0 :     if ( !mbtiles->create() )
     113                 :            :     {
     114                 :          0 :       mErrorMessage = tr( "Failed to create MBTiles file: " ) + sourcePath;
     115                 :          0 :       return false;
     116                 :            :     }
     117                 :            : 
     118                 :            :     // required metadata
     119                 :          0 :     mbtiles->setMetadataValue( "format", "pbf" );
     120                 :          0 :     mbtiles->setMetadataValue( "json", mbtilesJsonSchema() );
     121                 :            : 
     122                 :            :     // metadata specified by the client
     123                 :          0 :     const QStringList metaKeys = mMetadata.keys();
     124                 :          0 :     for ( const QString &key : metaKeys )
     125                 :            :     {
     126                 :          0 :       mbtiles->setMetadataValue( key, mMetadata[key].toString() );
     127                 :            :     }
     128                 :            : 
     129                 :            :     // default metadata that we always write (if not written by the client)
     130                 :          0 :     if ( !mMetadata.contains( "name" ) )
     131                 :          0 :       mbtiles->setMetadataValue( "name",  "unnamed" );  // required by the spec
     132                 :          0 :     if ( !mMetadata.contains( "minzoom" ) )
     133                 :          0 :       mbtiles->setMetadataValue( "minzoom", QString::number( mMinZoom ) );
     134                 :          0 :     if ( !mMetadata.contains( "maxzoom" ) )
     135                 :          0 :       mbtiles->setMetadataValue( "maxzoom", QString::number( mMaxZoom ) );
     136                 :          0 :     if ( !mMetadata.contains( "bounds" ) )
     137                 :            :     {
     138                 :            :       try
     139                 :            :       {
     140                 :          0 :         QgsCoordinateTransform ct( QgsCoordinateReferenceSystem( "EPSG:3857" ), QgsCoordinateReferenceSystem( "EPSG:4326" ), mTransformContext );
     141                 :          0 :         QgsRectangle wgsExtent = ct.transform( outputExtent );
     142                 :          0 :         QString boundsStr = QString( "%1,%2,%3,%4" )
     143                 :          0 :                             .arg( wgsExtent.xMinimum() ).arg( wgsExtent.yMinimum() )
     144                 :          0 :                             .arg( wgsExtent.xMaximum() ).arg( wgsExtent.yMaximum() );
     145                 :          0 :         mbtiles->setMetadataValue( "bounds", boundsStr );
     146                 :          0 :       }
     147                 :            :       catch ( const QgsCsException & )
     148                 :            :       {
     149                 :            :         // bounds won't be written (not a problem - it is an optional value)
     150                 :          0 :       }
     151                 :          0 :     }
     152                 :          0 :   }
     153                 :            : 
     154                 :          0 :   int tilesCreated = 0;
     155                 :          0 :   for ( int zoomLevel = mMinZoom; zoomLevel <= mMaxZoom; ++zoomLevel )
     156                 :            :   {
     157                 :          0 :     QgsTileMatrix tileMatrix = QgsTileMatrix::fromWebMercator( zoomLevel );
     158                 :            : 
     159                 :          0 :     QgsTileRange tileRange = tileMatrix.tileRangeFromExtent( outputExtent );
     160                 :          0 :     for ( int row = tileRange.startRow(); row <= tileRange.endRow(); ++row )
     161                 :            :     {
     162                 :          0 :       for ( int col = tileRange.startColumn(); col <= tileRange.endColumn(); ++col )
     163                 :            :       {
     164                 :          0 :         QgsTileXYZ tileID( col, row, zoomLevel );
     165                 :          0 :         QgsVectorTileMVTEncoder encoder( tileID );
     166                 :          0 :         encoder.setTransformContext( mTransformContext );
     167                 :            : 
     168                 :          0 :         for ( const Layer &layer : std::as_const( mLayers ) )
     169                 :            :         {
     170                 :          0 :           if ( ( layer.minZoom() >= 0 && zoomLevel < layer.minZoom() ) ||
     171                 :          0 :                ( layer.maxZoom() >= 0 && zoomLevel > layer.maxZoom() ) )
     172                 :          0 :             continue;
     173                 :            : 
     174                 :          0 :           encoder.addLayer( layer.layer(), feedback, layer.filterExpression(), layer.layerName() );
     175                 :          0 :         }
     176                 :            : 
     177                 :          0 :         if ( feedback && feedback->isCanceled() )
     178                 :            :         {
     179                 :          0 :           mErrorMessage = tr( "Operation has been canceled" );
     180                 :          0 :           return false;
     181                 :            :         }
     182                 :            : 
     183                 :          0 :         QByteArray tileData = encoder.encode();
     184                 :            : 
     185                 :          0 :         ++tilesCreated;
     186                 :          0 :         if ( feedback )
     187                 :            :         {
     188                 :          0 :           feedback->setProgress( static_cast<double>( tilesCreated ) / tilesToCreate * 100 );
     189                 :          0 :         }
     190                 :            : 
     191                 :          0 :         if ( tileData.isEmpty() )
     192                 :            :         {
     193                 :            :           // skipping empty tile - no need to write it
     194                 :          0 :           continue;
     195                 :            :         }
     196                 :            : 
     197                 :          0 :         if ( sourceType == QLatin1String( "xyz" ) )
     198                 :            :         {
     199                 :          0 :           if ( !writeTileFileXYZ( sourcePath, tileID, tileMatrix, tileData ) )
     200                 :          0 :             return false;  // error message already set
     201                 :          0 :         }
     202                 :            :         else  // mbtiles
     203                 :            :         {
     204                 :          0 :           QByteArray gzipTileData;
     205                 :          0 :           QgsMbTiles::encodeGzip( tileData, gzipTileData );
     206                 :          0 :           int rowTMS = pow( 2, tileID.zoomLevel() ) - tileID.row() - 1;
     207                 :          0 :           mbtiles->setTileData( tileID.zoomLevel(), tileID.column(), rowTMS, gzipTileData );
     208                 :          0 :         }
     209                 :          0 :       }
     210                 :          0 :     }
     211                 :          0 :   }
     212                 :            : 
     213                 :          0 :   return true;
     214                 :          0 : }
     215                 :            : 
     216                 :          0 : QgsRectangle QgsVectorTileWriter::fullExtent() const
     217                 :            : {
     218                 :          0 :   QgsRectangle extent;
     219                 :          0 :   QgsCoordinateReferenceSystem destCrs( "EPSG:3857" );
     220                 :            : 
     221                 :          0 :   for ( const Layer &layer : mLayers )
     222                 :            :   {
     223                 :          0 :     QgsVectorLayer *vl = layer.layer();
     224                 :          0 :     QgsCoordinateTransform ct( vl->crs(), destCrs, mTransformContext );
     225                 :            :     try
     226                 :            :     {
     227                 :          0 :       QgsRectangle r = ct.transformBoundingBox( vl->extent() );
     228                 :          0 :       extent.combineExtentWith( r );
     229                 :          0 :     }
     230                 :            :     catch ( const QgsCsException & )
     231                 :            :     {
     232                 :          0 :       QgsDebugMsg( "Failed to reproject layer extent to destination CRS" );
     233                 :          0 :     }
     234                 :          0 :   }
     235                 :            :   return extent;
     236                 :          0 : }
     237                 :            : 
     238                 :          0 : bool QgsVectorTileWriter::writeTileFileXYZ( const QString &sourcePath, QgsTileXYZ tileID, const QgsTileMatrix &tileMatrix, const QByteArray &tileData )
     239                 :            : {
     240                 :          0 :   QString filePath = QgsVectorTileUtils::formatXYZUrlTemplate( sourcePath, tileID, tileMatrix );
     241                 :            : 
     242                 :            :   // make dirs if needed
     243                 :          0 :   QFileInfo fi( filePath );
     244                 :          0 :   QDir fileDir = fi.dir();
     245                 :          0 :   if ( !fileDir.exists() )
     246                 :            :   {
     247                 :          0 :     if ( !fileDir.mkpath( "." ) )
     248                 :            :     {
     249                 :          0 :       mErrorMessage = tr( "Cannot create directory " ) + fileDir.path();
     250                 :          0 :       return false;
     251                 :            :     }
     252                 :          0 :   }
     253                 :            : 
     254                 :          0 :   QFile f( filePath );
     255                 :          0 :   if ( !f.open( QIODevice::WriteOnly ) )
     256                 :            :   {
     257                 :          0 :     mErrorMessage = tr( "Cannot open file for writing " ) + filePath;
     258                 :          0 :     return false;
     259                 :            :   }
     260                 :            : 
     261                 :          0 :   f.write( tileData );
     262                 :          0 :   f.close();
     263                 :          0 :   return true;
     264                 :          0 : }
     265                 :            : 
     266                 :            : 
     267                 :          0 : QString QgsVectorTileWriter::mbtilesJsonSchema()
     268                 :            : {
     269                 :          0 :   QVariantList arrayLayers;
     270                 :          0 :   for ( const Layer &layer : std::as_const( mLayers ) )
     271                 :            :   {
     272                 :          0 :     QgsVectorLayer *vl = layer.layer();
     273                 :          0 :     const QgsFields fields = vl->fields();
     274                 :            : 
     275                 :          0 :     QVariantMap fieldsObj;
     276                 :          0 :     for ( const QgsField &field : fields )
     277                 :            :     {
     278                 :          0 :       QString fieldTypeStr;
     279                 :          0 :       if ( field.type() == QVariant::Bool )
     280                 :          0 :         fieldTypeStr = QStringLiteral( "Boolean" );
     281                 :          0 :       else if ( field.type() == QVariant::Int || field.type() == QVariant::Double )
     282                 :          0 :         fieldTypeStr = QStringLiteral( "Number" );
     283                 :            :       else
     284                 :          0 :         fieldTypeStr = QStringLiteral( "String" );
     285                 :            : 
     286                 :          0 :       fieldsObj[field.name()] = fieldTypeStr;
     287                 :          0 :     }
     288                 :            : 
     289                 :          0 :     QVariantMap layerObj;
     290                 :          0 :     layerObj["id"] = vl->name();
     291                 :          0 :     layerObj["fields"] = fieldsObj;
     292                 :          0 :     arrayLayers.append( layerObj );
     293                 :          0 :   }
     294                 :            : 
     295                 :          0 :   QVariantMap rootObj;
     296                 :          0 :   rootObj["vector_layers"] = arrayLayers;
     297                 :          0 :   return QString::fromStdString( QgsJsonUtils::jsonFromVariant( rootObj ).dump() );
     298                 :          0 : }

Generated by: LCOV version 1.14