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