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