LCOV - code coverage report
Current view: top level - core/vectortile - qgsvectortilemvtencoder.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 198 0.0 %
Date: 2021-03-26 12:19:53 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :   qgsvectortilemvtencoder.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 "qgsvectortilemvtencoder.h"
      17                 :            : 
      18                 :            : #include "qgsfeedback.h"
      19                 :            : #include "qgslinestring.h"
      20                 :            : #include "qgslogger.h"
      21                 :            : #include "qgsmultilinestring.h"
      22                 :            : #include "qgsmultipoint.h"
      23                 :            : #include "qgsmultipolygon.h"
      24                 :            : #include "qgspolygon.h"
      25                 :            : #include "qgsvectorlayer.h"
      26                 :            : #include "qgsvectortilemvtutils.h"
      27                 :            : 
      28                 :            : 
      29                 :            : //! Helper class for writing of geometry commands
      30                 :            : struct MVTGeometryWriter
      31                 :            : {
      32                 :            :   vector_tile::Tile_Feature *feature = nullptr;
      33                 :            :   int resolution;
      34                 :            :   double tileXMin, tileYMax, tileDX, tileDY;
      35                 :            :   QPoint cursor;
      36                 :            : 
      37                 :          0 :   MVTGeometryWriter( vector_tile::Tile_Feature *f, int res, const QgsRectangle &tileExtent )
      38                 :          0 :     : feature( f )
      39                 :          0 :     , resolution( res )
      40                 :          0 :     , tileXMin( tileExtent.xMinimum() )
      41                 :          0 :     , tileYMax( tileExtent.yMaximum() )
      42                 :          0 :     , tileDX( tileExtent.width() )
      43                 :          0 :     , tileDY( tileExtent.height() )
      44                 :            :   {
      45                 :          0 :   }
      46                 :            : 
      47                 :          0 :   void addMoveTo( int count )
      48                 :            :   {
      49                 :          0 :     feature->add_geometry( 1 | ( count << 3 ) );
      50                 :          0 :   }
      51                 :          0 :   void addLineTo( int count )
      52                 :            :   {
      53                 :          0 :     feature->add_geometry( 2 | ( count << 3 ) );
      54                 :          0 :   }
      55                 :          0 :   void addClosePath()
      56                 :            :   {
      57                 :          0 :     feature->add_geometry( 7 | ( 1 << 3 ) );
      58                 :          0 :   }
      59                 :            : 
      60                 :          0 :   void addPoint( const QgsPoint &pt )
      61                 :            :   {
      62                 :          0 :     addPoint( mapToTileCoordinates( pt.x(), pt.y() ) );
      63                 :          0 :   }
      64                 :            : 
      65                 :          0 :   void addPoint( const QPoint &pt )
      66                 :            :   {
      67                 :          0 :     qint32 vx = pt.x() - cursor.x();
      68                 :          0 :     qint32 vy = pt.y() - cursor.y();
      69                 :            : 
      70                 :            :     // (quint32)(-(qint32)((quint32)vx >> 31)) is a C/C++ compliant way
      71                 :            :     // of doing vx >> 31, which is undefined behavior since vx is signed
      72                 :          0 :     feature->add_geometry( ( ( quint32 )vx << 1 ) ^ ( ( quint32 )( -( qint32 )( ( quint32 )vx >> 31 ) ) ) );
      73                 :          0 :     feature->add_geometry( ( ( quint32 )vy << 1 ) ^ ( ( quint32 )( -( qint32 )( ( quint32 )vy >> 31 ) ) ) );
      74                 :          0 : 
      75                 :          0 :     cursor = pt;
      76                 :          0 :   }
      77                 :            : 
      78                 :          0 :   QPoint mapToTileCoordinates( double x, double y )
      79                 :            :   {
      80                 :          0 :     return QPoint( static_cast<int>( round( ( x - tileXMin ) * resolution / tileDX ) ),
      81                 :          0 :                    static_cast<int>( round( ( tileYMax - y ) * resolution / tileDY ) ) );
      82                 :            :   }
      83                 :            : };
      84                 :            : 
      85                 :            : 
      86                 :          0 : static void encodeLineString( const QgsLineString *lineString, bool isRing, bool reversed, MVTGeometryWriter &geomWriter )
      87                 :            : {
      88                 :          0 :   int count = lineString->numPoints();
      89                 :          0 :   const double *xData = lineString->xData();
      90                 :          0 :   const double *yData = lineString->yData();
      91                 :            : 
      92                 :          0 :   if ( isRing )
      93                 :          0 :     count--;  // the last point in linear ring is repeated - but not in MVT
      94                 :            : 
      95                 :            :   // de-duplicate points
      96                 :          0 :   QVector<QPoint> tilePoints;
      97                 :          0 :   QPoint last( -9999, -9999 );
      98                 :          0 :   tilePoints.reserve( count );
      99                 :          0 :   for ( int i = 0; i < count; ++i )
     100                 :            :   {
     101                 :          0 :     QPoint pt = geomWriter.mapToTileCoordinates( xData[i], yData[i] );
     102                 :          0 :     if ( pt == last )
     103                 :          0 :       continue;
     104                 :            : 
     105                 :          0 :     tilePoints << pt;
     106                 :          0 :     last = pt;
     107                 :          0 :   }
     108                 :          0 :   count = tilePoints.count();
     109                 :            : 
     110                 :          0 :   geomWriter.addMoveTo( 1 );
     111                 :          0 :   geomWriter.addPoint( tilePoints[0] );
     112                 :          0 :   geomWriter.addLineTo( count - 1 );
     113                 :          0 :   if ( reversed )
     114                 :            :   {
     115                 :          0 :     for ( int i = count - 1; i >= 1; --i )
     116                 :          0 :       geomWriter.addPoint( tilePoints[i] );
     117                 :          0 :   }
     118                 :            :   else
     119                 :            :   {
     120                 :          0 :     for ( int i = 1; i < count; ++i )
     121                 :          0 :       geomWriter.addPoint( tilePoints[i] );
     122                 :            :   }
     123                 :          0 : }
     124                 :            : 
     125                 :          0 : static void encodePolygon( const QgsPolygon *polygon, MVTGeometryWriter &geomWriter )
     126                 :            : {
     127                 :          0 :   const QgsLineString *exteriorRing = qgsgeometry_cast<const QgsLineString *>( polygon->exteriorRing() );
     128                 :          0 :   encodeLineString( exteriorRing, true, !QgsVectorTileMVTUtils::isExteriorRing( exteriorRing ), geomWriter );
     129                 :          0 :   geomWriter.addClosePath();
     130                 :            : 
     131                 :          0 :   for ( int i = 0; i < polygon->numInteriorRings(); ++i )
     132                 :            :   {
     133                 :          0 :     const QgsLineString *interiorRing = qgsgeometry_cast<const QgsLineString *>( polygon->interiorRing( i ) );
     134                 :          0 :     encodeLineString( interiorRing, true, QgsVectorTileMVTUtils::isExteriorRing( interiorRing ), geomWriter );
     135                 :          0 :     geomWriter.addClosePath();
     136                 :          0 :   }
     137                 :          0 : }
     138                 :            : 
     139                 :            : 
     140                 :            : //
     141                 :            : 
     142                 :            : 
     143                 :          0 : QgsVectorTileMVTEncoder::QgsVectorTileMVTEncoder( QgsTileXYZ tileID )
     144                 :          0 :   : mTileID( tileID )
     145                 :            : {
     146                 :          0 :   QgsTileMatrix tm = QgsTileMatrix::fromWebMercator( mTileID.zoomLevel() );
     147                 :          0 :   mTileExtent = tm.tileExtent( mTileID );
     148                 :          0 : }
     149                 :            : 
     150                 :          0 : void QgsVectorTileMVTEncoder::addLayer( QgsVectorLayer *layer, QgsFeedback *feedback, QString filterExpression, QString layerName )
     151                 :            : {
     152                 :          0 :   if ( feedback && feedback->isCanceled() )
     153                 :          0 :     return;
     154                 :            : 
     155                 :          0 :   QgsCoordinateTransform ct( layer->crs(), QgsCoordinateReferenceSystem( "EPSG:3857" ), mTransformContext );
     156                 :            : 
     157                 :          0 :   QgsRectangle layerTileExtent = mTileExtent;
     158                 :            :   try
     159                 :            :   {
     160                 :          0 :     layerTileExtent = ct.transformBoundingBox( layerTileExtent, QgsCoordinateTransform::ReverseTransform );
     161                 :          0 :     if ( !layerTileExtent.intersects( layer->extent() ) )
     162                 :            :     {
     163                 :          0 :       return;  // tile is completely outside of the layer'e extent
     164                 :            :     }
     165                 :          0 :   }
     166                 :            :   catch ( const QgsCsException & )
     167                 :            :   {
     168                 :          0 :     QgsDebugMsg( "Failed to reproject tile extent to the layer" );
     169                 :            :     return;
     170                 :          0 :   }
     171                 :            : 
     172                 :          0 :   if ( layerName.isEmpty() )
     173                 :          0 :     layerName = layer->name();
     174                 :            : 
     175                 :            :   // add buffer to both filter extent in layer CRS (for feature request) and tile extent in target CRS (for clipping)
     176                 :          0 :   double bufferRatio = static_cast<double>( mBuffer ) / mResolution;
     177                 :          0 :   QgsRectangle tileExtent = mTileExtent;
     178                 :          0 :   tileExtent.grow( bufferRatio * mTileExtent.width() );
     179                 :          0 :   layerTileExtent.grow( bufferRatio * std::max( layerTileExtent.width(), layerTileExtent.height() ) );
     180                 :            : 
     181                 :          0 :   QgsFeatureRequest request;
     182                 :          0 :   request.setFilterRect( layerTileExtent );
     183                 :          0 :   if ( !filterExpression.isEmpty() )
     184                 :          0 :     request.setFilterExpression( filterExpression );
     185                 :          0 :   QgsFeatureIterator fit = layer->getFeatures( request );
     186                 :            : 
     187                 :          0 :   QgsFeature f;
     188                 :          0 :   if ( !fit.nextFeature( f ) )
     189                 :            :   {
     190                 :          0 :     return;  // nothing to write - do not add the layer at all
     191                 :            :   }
     192                 :            : 
     193                 :          0 :   vector_tile::Tile_Layer *tileLayer = tile.add_layers();
     194                 :          0 :   tileLayer->set_name( layerName.toUtf8() );
     195                 :          0 :   tileLayer->set_version( 2 );  // 2 means MVT spec version 2.1
     196                 :          0 :   tileLayer->set_extent( static_cast<::google::protobuf::uint32>( mResolution ) );
     197                 :            : 
     198                 :          0 :   const QgsFields fields = layer->fields();
     199                 :          0 :   for ( int i = 0; i < fields.count(); ++i )
     200                 :            :   {
     201                 :          0 :     tileLayer->add_keys( fields[i].name().toUtf8() );
     202                 :          0 :   }
     203                 :            : 
     204                 :          0 :   do
     205                 :            :   {
     206                 :          0 :     if ( feedback && feedback->isCanceled() )
     207                 :          0 :       break;
     208                 :            : 
     209                 :          0 :     QgsGeometry g = f.geometry();
     210                 :            : 
     211                 :            :     // reproject
     212                 :            :     try
     213                 :            :     {
     214                 :          0 :       g.transform( ct );
     215                 :          0 :     }
     216                 :            :     catch ( const QgsCsException & )
     217                 :            :     {
     218                 :          0 :       QgsDebugMsg( "Failed to reproject geometry " + QString::number( f.id() ) );
     219                 :            :       continue;
     220                 :          0 :     }
     221                 :            : 
     222                 :            :     // clip
     223                 :          0 :     g = g.clipped( tileExtent );
     224                 :            : 
     225                 :          0 :     f.setGeometry( g );
     226                 :            : 
     227                 :          0 :     addFeature( tileLayer, f );
     228                 :          0 :   }
     229                 :          0 :   while ( fit.nextFeature( f ) );
     230                 :            : 
     231                 :          0 :   mKnownValues.clear();
     232                 :          0 : }
     233                 :            : 
     234                 :          0 : void QgsVectorTileMVTEncoder::addFeature( vector_tile::Tile_Layer *tileLayer, const QgsFeature &f )
     235                 :            : {
     236                 :          0 :   QgsGeometry g = f.geometry();
     237                 :          0 :   QgsWkbTypes::GeometryType geomType = g.type();
     238                 :          0 :   double onePixel = mTileExtent.width() / mResolution;
     239                 :            : 
     240                 :          0 :   if ( geomType == QgsWkbTypes::LineGeometry )
     241                 :            :   {
     242                 :          0 :     if ( g.length() < onePixel )
     243                 :          0 :       return; // too short
     244                 :          0 :   }
     245                 :          0 :   else if ( geomType == QgsWkbTypes::PolygonGeometry )
     246                 :            :   {
     247                 :          0 :     if ( g.area() < onePixel * onePixel )
     248                 :          0 :       return; // too small
     249                 :          0 :   }
     250                 :            : 
     251                 :          0 :   vector_tile::Tile_Feature *feature = tileLayer->add_features();
     252                 :            : 
     253                 :          0 :   feature->set_id( static_cast<quint64>( f.id() ) );
     254                 :            : 
     255                 :            :   //
     256                 :            :   // encode attributes
     257                 :            :   //
     258                 :            : 
     259                 :          0 :   const QgsAttributes attrs = f.attributes();
     260                 :          0 :   for ( int i = 0; i < attrs.count(); ++i )
     261                 :            :   {
     262                 :          0 :     const QVariant v = attrs.at( i );
     263                 :          0 :     if ( !v.isValid() || v.isNull() )
     264                 :          0 :       continue;
     265                 :            : 
     266                 :            :     int valueIndex;
     267                 :          0 :     if ( mKnownValues.contains( v ) )
     268                 :            :     {
     269                 :          0 :       valueIndex = mKnownValues[v];
     270                 :          0 :     }
     271                 :            :     else
     272                 :            :     {
     273                 :          0 :       vector_tile::Tile_Value *value = tileLayer->add_values();
     274                 :          0 :       valueIndex = tileLayer->values_size() - 1;
     275                 :          0 :       mKnownValues[v] = valueIndex;
     276                 :            : 
     277                 :          0 :       if ( v.type() == QVariant::Double )
     278                 :          0 :         value->set_double_value( v.toDouble() );
     279                 :          0 :       else if ( v.type() == QVariant::Int )
     280                 :          0 :         value->set_int_value( v.toInt() );
     281                 :          0 :       else if ( v.type() == QVariant::Bool )
     282                 :          0 :         value->set_bool_value( v.toBool() );
     283                 :            :       else
     284                 :          0 :         value->set_string_value( v.toString().toUtf8().toStdString() );
     285                 :            :     }
     286                 :            : 
     287                 :          0 :     feature->add_tags( static_cast<quint32>( i ) );
     288                 :          0 :     feature->add_tags( static_cast<quint32>( valueIndex ) );
     289                 :          0 :   }
     290                 :            : 
     291                 :            :   //
     292                 :            :   // encode geometry
     293                 :            :   //
     294                 :            : 
     295                 :          0 :   vector_tile::Tile_GeomType mvtGeomType = vector_tile::Tile_GeomType_UNKNOWN;
     296                 :          0 :   if ( geomType == QgsWkbTypes::PointGeometry )
     297                 :          0 :     mvtGeomType = vector_tile::Tile_GeomType_POINT;
     298                 :          0 :   else if ( geomType == QgsWkbTypes::LineGeometry )
     299                 :          0 :     mvtGeomType = vector_tile::Tile_GeomType_LINESTRING;
     300                 :          0 :   else if ( geomType == QgsWkbTypes::PolygonGeometry )
     301                 :          0 :     mvtGeomType = vector_tile::Tile_GeomType_POLYGON;
     302                 :          0 :   feature->set_type( mvtGeomType );
     303                 :            : 
     304                 :          0 :   if ( QgsWkbTypes::isCurvedType( g.wkbType() ) )
     305                 :            :   {
     306                 :          0 :     g = QgsGeometry( g.get()->segmentize() );
     307                 :          0 :   }
     308                 :            : 
     309                 :          0 :   MVTGeometryWriter geomWriter( feature, mResolution, mTileExtent );
     310                 :            : 
     311                 :          0 :   const QgsAbstractGeometry *geom = g.constGet();
     312                 :          0 :   switch ( QgsWkbTypes::flatType( g.wkbType() ) )
     313                 :            :   {
     314                 :            :     case QgsWkbTypes::Point:
     315                 :            :     {
     316                 :          0 :       const QgsPoint *pt = static_cast<const QgsPoint *>( geom );
     317                 :          0 :       geomWriter.addMoveTo( 1 );
     318                 :          0 :       geomWriter.addPoint( *pt );
     319                 :            :     }
     320                 :          0 :     break;
     321                 :            : 
     322                 :            :     case QgsWkbTypes::LineString:
     323                 :            :     {
     324                 :          0 :       encodeLineString( qgsgeometry_cast<const QgsLineString *>( geom ), true, false, geomWriter );
     325                 :            :     }
     326                 :          0 :     break;
     327                 :            : 
     328                 :            :     case QgsWkbTypes::Polygon:
     329                 :            :     {
     330                 :          0 :       encodePolygon( static_cast<const QgsPolygon *>( geom ), geomWriter );
     331                 :            :     }
     332                 :          0 :     break;
     333                 :            : 
     334                 :            :     case QgsWkbTypes::MultiPoint:
     335                 :            :     {
     336                 :          0 :       const QgsMultiPoint *mpt = static_cast<const QgsMultiPoint *>( geom );
     337                 :          0 :       geomWriter.addMoveTo( mpt->numGeometries() );
     338                 :          0 :       for ( int i = 0; i < mpt->numGeometries(); ++i )
     339                 :          0 :         geomWriter.addPoint( *mpt->pointN( i ) );
     340                 :            :     }
     341                 :          0 :     break;
     342                 :            : 
     343                 :            :     case QgsWkbTypes::MultiLineString:
     344                 :            :     {
     345                 :          0 :       const QgsMultiLineString *mls = qgsgeometry_cast<const QgsMultiLineString *>( geom );
     346                 :          0 :       for ( int i = 0; i < mls->numGeometries(); ++i )
     347                 :            :       {
     348                 :          0 :         encodeLineString( mls->lineStringN( i ), true, false, geomWriter );
     349                 :          0 :       }
     350                 :            :     }
     351                 :          0 :     break;
     352                 :            : 
     353                 :            :     case QgsWkbTypes::MultiPolygon:
     354                 :            :     {
     355                 :          0 :       const QgsMultiPolygon *mp = qgsgeometry_cast<const QgsMultiPolygon *>( geom );
     356                 :          0 :       for ( int i = 0; i < mp->numGeometries(); ++i )
     357                 :            :       {
     358                 :          0 :         encodePolygon( mp->polygonN( i ), geomWriter );
     359                 :          0 :       }
     360                 :            :     }
     361                 :          0 :     break;
     362                 :            : 
     363                 :            :     default:
     364                 :          0 :       break;
     365                 :            :   }
     366                 :          0 : }
     367                 :            : 
     368                 :            : 
     369                 :          0 : QByteArray QgsVectorTileMVTEncoder::encode() const
     370                 :            : {
     371                 :          0 :   return QByteArray::fromStdString( tile.SerializeAsString() );
     372                 :          0 : }

Generated by: LCOV version 1.14