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 : }
|