QGIS API Documentation 3.43.0-Master (e01d6d7c4c0)
qgsalgorithmgltftovector.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmgltftovector.cpp
3 ---------------------
4 begin : August 2023
5 copyright : (C) 2023 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19#include "qgsgltfutils.h"
20#include "qgsmatrix4x4.h"
21#include "qgsvector3d.h"
22#include "qgsmultipolygon.h"
23#include "qgspolygon.h"
24#include "qgslinestring.h"
25#include "qgsmultilinestring.h"
26
27#include <QMatrix4x4>
28
29#define TINYGLTF_NO_STB_IMAGE // we use QImage-based reading of images
30#define TINYGLTF_NO_STB_IMAGE_WRITE // we don't need writing of images
31
32#include "tiny_gltf.h"
33
35
36QString QgsGltfToVectorFeaturesAlgorithm::name() const
37{
38 return QStringLiteral( "gltftovector" );
39}
40
41QString QgsGltfToVectorFeaturesAlgorithm::displayName() const
42{
43 return QObject::tr( "Convert GLTF to vector features" );
44}
45
46QStringList QgsGltfToVectorFeaturesAlgorithm::tags() const
47{
48 return QObject::tr( "3d,tiles,cesium" ).split( ',' );
49}
50
51QString QgsGltfToVectorFeaturesAlgorithm::group() const
52{
53 return QObject::tr( "3D Tiles" );
54}
55
56QString QgsGltfToVectorFeaturesAlgorithm::groupId() const
57{
58 return QStringLiteral( "3dtiles" );
59}
60
61QString QgsGltfToVectorFeaturesAlgorithm::shortHelpString() const
62{
63 return QObject::tr( "This algorithm converts GLTF content to standard vector layer formats." );
64}
65
66QString QgsGltfToVectorFeaturesAlgorithm::shortDescription() const
67{
68 return QObject::tr( "Converts GLTF content to standard vector layer formats." );
69}
70
71QgsGltfToVectorFeaturesAlgorithm *QgsGltfToVectorFeaturesAlgorithm::createInstance() const
72{
73 return new QgsGltfToVectorFeaturesAlgorithm();
74}
75
76void QgsGltfToVectorFeaturesAlgorithm::initAlgorithm( const QVariantMap & )
77{
78 addParameter( new QgsProcessingParameterFile( QStringLiteral( "INPUT" ), QObject::tr( "Input GLTF" ), Qgis::ProcessingFileParameterBehavior::File, QStringLiteral( "gltf" ), QVariant(), false, QStringLiteral( "GLTF (*.gltf *.GLTF);;GLB (*.glb *.GLB)" ) ) );
79
80 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT_POLYGONS" ), QObject::tr( "Output polygons" ), Qgis::ProcessingSourceType::VectorPolygon, QVariant(), true, true ) );
81 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT_LINES" ), QObject::tr( "Output lines" ), Qgis::ProcessingSourceType::VectorLine, QVariant(), true, true ) );
82}
83
84std::unique_ptr<QgsAbstractGeometry> extractTriangles(
85 const tinygltf::Model &model,
86 const tinygltf::Primitive &primitive,
87 const QgsCoordinateTransform &ecefTransform,
88 const QgsVector3D &tileTranslationEcef,
89 const QMatrix4x4 *gltfLocalTransform,
90 QgsProcessingFeedback *feedback
91)
92{
93 auto posIt = primitive.attributes.find( "POSITION" );
94 if ( posIt == primitive.attributes.end() )
95 {
96 feedback->reportError( QObject::tr( "Could not find POSITION attribute for primitive" ) );
97 return nullptr;
98 }
99 int positionAccessorIndex = posIt->second;
100
101 QVector<double> x;
102 QVector<double> y;
103 QVector<double> z;
104 QgsGltfUtils::accessorToMapCoordinates(
105 model, positionAccessorIndex, QgsMatrix4x4(),
106 &ecefTransform,
107 tileTranslationEcef,
108 gltfLocalTransform,
110 x, y, z
111 );
112
113 auto mp = std::make_unique<QgsMultiPolygon>();
114
115 if ( primitive.indices == -1 )
116 {
117 Q_ASSERT( x.size() % 3 == 0 );
118
119 mp->reserve( x.size() );
120 for ( int i = 0; i < x.size(); i += 3 )
121 {
122 mp->addGeometry( new QgsPolygon( new QgsLineString( QVector<QgsPoint> { QgsPoint( x[i], y[i], z[i] ), QgsPoint( x[i + 1], y[i + 1], z[i + 1] ), QgsPoint( x[i + 2], y[i + 2], z[i + 2] ), QgsPoint( x[i], y[i], z[i] ) } ) ) );
123 }
124 }
125 else
126 {
127 const tinygltf::Accessor &primitiveAccessor = model.accessors[primitive.indices];
128 const tinygltf::BufferView &bvPrimitive = model.bufferViews[primitiveAccessor.bufferView];
129 const tinygltf::Buffer &bPrimitive = model.buffers[bvPrimitive.buffer];
130
131 Q_ASSERT( ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE ) && primitiveAccessor.type == TINYGLTF_TYPE_SCALAR );
132
133 const char *primitivePtr = reinterpret_cast<const char *>( bPrimitive.data.data() ) + bvPrimitive.byteOffset + primitiveAccessor.byteOffset;
134
135 mp->reserve( primitiveAccessor.count / 3 );
136 for ( std::size_t i = 0; i < primitiveAccessor.count / 3; i++ )
137 {
138 unsigned int index1 = 0;
139 unsigned int index2 = 0;
140 unsigned int index3 = 0;
141
142 if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT )
143 {
144 const unsigned short *usPtrPrimitive = reinterpret_cast<const unsigned short *>( primitivePtr );
145 if ( bvPrimitive.byteStride )
146 primitivePtr += bvPrimitive.byteStride;
147 else
148 primitivePtr += 3 * sizeof( unsigned short );
149
150 index1 = usPtrPrimitive[0];
151 index2 = usPtrPrimitive[1];
152 index3 = usPtrPrimitive[2];
153 }
154 else if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
155 {
156 const unsigned char *usPtrPrimitive = reinterpret_cast<const unsigned char *>( primitivePtr );
157 if ( bvPrimitive.byteStride )
158 primitivePtr += bvPrimitive.byteStride;
159 else
160 primitivePtr += 3 * sizeof( unsigned char );
161
162 index1 = usPtrPrimitive[0];
163 index2 = usPtrPrimitive[1];
164 index3 = usPtrPrimitive[2];
165 }
166 else
167 {
168 const unsigned int *uintPtrPrimitive = reinterpret_cast<const unsigned int *>( primitivePtr );
169 if ( bvPrimitive.byteStride )
170 primitivePtr += bvPrimitive.byteStride;
171 else
172 primitivePtr += 3 * sizeof( unsigned int );
173
174 index1 = uintPtrPrimitive[0];
175 index2 = uintPtrPrimitive[1];
176 index3 = uintPtrPrimitive[2];
177 }
178
179 mp->addGeometry( new QgsPolygon( new QgsLineString( QVector<QgsPoint> { QgsPoint( x[index1], y[index1], z[index1] ), QgsPoint( x[index2], y[index2], z[index2] ), QgsPoint( x[index3], y[index3], z[index3] ), QgsPoint( x[index1], y[index1], z[index1] ) } ) ) );
180 }
181 }
182 return mp;
183}
184
185std::unique_ptr<QgsAbstractGeometry> extractLines(
186 const tinygltf::Model &model,
187 const tinygltf::Primitive &primitive,
188 const QgsCoordinateTransform &ecefTransform,
189 const QgsVector3D &tileTranslationEcef,
190 const QMatrix4x4 *gltfLocalTransform,
191 QgsProcessingFeedback *feedback
192)
193{
194 auto posIt = primitive.attributes.find( "POSITION" );
195 if ( posIt == primitive.attributes.end() )
196 {
197 feedback->reportError( QObject::tr( "Could not find POSITION attribute for primitive" ) );
198 return nullptr;
199 }
200 int positionAccessorIndex = posIt->second;
201
202 QVector<double> x;
203 QVector<double> y;
204 QVector<double> z;
205 QgsGltfUtils::accessorToMapCoordinates(
206 model, positionAccessorIndex, QgsMatrix4x4(),
207 &ecefTransform,
208 tileTranslationEcef,
209 gltfLocalTransform,
211 x, y, z
212 );
213
214 auto ml = std::make_unique<QgsMultiLineString>();
215
216 if ( primitive.indices == -1 )
217 {
218 Q_ASSERT( x.size() % 2 == 0 );
219
220 ml->reserve( x.size() );
221 for ( int i = 0; i < x.size(); i += 2 )
222 {
223 ml->addGeometry( new QgsLineString( QVector<QgsPoint> { QgsPoint( x[i], y[i], z[i] ), QgsPoint( x[i + 1], y[i + 1], z[i + 1] ) } ) );
224 }
225 }
226 else
227 {
228 const tinygltf::Accessor &primitiveAccessor = model.accessors[primitive.indices];
229 const tinygltf::BufferView &bvPrimitive = model.bufferViews[primitiveAccessor.bufferView];
230 const tinygltf::Buffer &bPrimitive = model.buffers[bvPrimitive.buffer];
231
232 Q_ASSERT( ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE ) && primitiveAccessor.type == TINYGLTF_TYPE_SCALAR );
233
234 const char *primitivePtr = reinterpret_cast<const char *>( bPrimitive.data.data() ) + bvPrimitive.byteOffset + primitiveAccessor.byteOffset;
235
236 ml->reserve( primitiveAccessor.count / 2 );
237 for ( std::size_t i = 0; i < primitiveAccessor.count / 2; i++ )
238 {
239 unsigned int index1 = 0;
240 unsigned int index2 = 0;
241
242 if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT )
243 {
244 const unsigned short *usPtrPrimitive = reinterpret_cast<const unsigned short *>( primitivePtr );
245 if ( bvPrimitive.byteStride )
246 primitivePtr += bvPrimitive.byteStride;
247 else
248 primitivePtr += 2 * sizeof( unsigned short );
249
250 index1 = usPtrPrimitive[0];
251 index2 = usPtrPrimitive[1];
252 }
253 else if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
254 {
255 const unsigned char *usPtrPrimitive = reinterpret_cast<const unsigned char *>( primitivePtr );
256 if ( bvPrimitive.byteStride )
257 primitivePtr += bvPrimitive.byteStride;
258 else
259 primitivePtr += 2 * sizeof( unsigned char );
260
261 index1 = usPtrPrimitive[0];
262 index2 = usPtrPrimitive[1];
263 }
264 else
265 {
266 const unsigned int *uintPtrPrimitive = reinterpret_cast<const unsigned int *>( primitivePtr );
267 if ( bvPrimitive.byteStride )
268 primitivePtr += bvPrimitive.byteStride;
269 else
270 primitivePtr += 2 * sizeof( unsigned int );
271
272 index1 = uintPtrPrimitive[0];
273 index2 = uintPtrPrimitive[1];
274 }
275
276 ml->addGeometry( new QgsLineString( QVector<QgsPoint> { QgsPoint( x[index1], y[index1], z[index1] ), QgsPoint( x[index2], y[index2], z[index2] ) } ) );
277 }
278 }
279 return ml;
280}
281
282QVariantMap QgsGltfToVectorFeaturesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
283{
284 const QString path = parameterAsFile( parameters, QStringLiteral( "INPUT" ), context );
285
286 const QgsCoordinateReferenceSystem destCrs( QStringLiteral( "EPSG:4326" ) );
287 QgsFields fields;
288
289 QString polygonDest;
290 std::unique_ptr<QgsFeatureSink> polygonSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT_POLYGONS" ), context, polygonDest, fields, Qgis::WkbType::MultiPolygonZ, destCrs ) );
291 if ( !polygonSink && parameters.value( QStringLiteral( "OUTPUT_POLYGONS" ) ).isValid() )
292 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT_POLYGONS" ) ) );
293 QString lineDest;
294 std::unique_ptr<QgsFeatureSink> lineSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT_LINES" ), context, lineDest, fields, Qgis::WkbType::MultiLineStringZ, destCrs ) );
295 if ( !lineSink && parameters.value( QStringLiteral( "OUTPUT_LINES" ) ).isValid() )
296 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT_LINES" ) ) );
297
298 QFile f( path );
299 QByteArray fileContent;
300 if ( f.open( QIODevice::ReadOnly ) )
301 {
302 fileContent = f.readAll();
303 }
304 else
305 {
306 throw QgsProcessingException( QObject::tr( "Could not load source file %1." ).arg( path ) );
307 }
308
309 const QgsCoordinateTransform ecefTransform( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) ), destCrs, context.transformContext() );
310
311 tinygltf::Model model;
312 QString errors;
313 QString warnings;
314 if ( !QgsGltfUtils::loadGltfModel( fileContent, model, &errors, &warnings ) )
315 {
316 throw QgsProcessingException( QObject::tr( "Error loading GLTF model: %1" ).arg( errors ) );
317 }
318 if ( !warnings.isEmpty() )
319 {
320 feedback->pushWarning( warnings );
321 }
322 feedback->pushDebugInfo( QObject::tr( "Found %1 scenes" ).arg( model.scenes.size() ) );
323
324 bool sceneOk = false;
325 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
326 if ( !sceneOk )
327 {
328 throw QgsProcessingException( QObject::tr( "No scenes found in model" ) );
329 }
330
331 const tinygltf::Scene &scene = model.scenes[sceneIndex];
332 feedback->pushDebugInfo( QObject::tr( "Found %1 nodes in default scene [%2]" ).arg( scene.nodes.size() ).arg( sceneIndex ) );
333
334 QSet<int> warnedPrimitiveTypes;
335
336 const QgsVector3D tileTranslationEcef = QgsGltfUtils::extractTileTranslation( model );
337 std::function<void( int nodeIndex, const QMatrix4x4 &transform )> traverseNode;
338 traverseNode = [&model, feedback, &polygonSink, &lineSink, &warnedPrimitiveTypes, &ecefTransform, &tileTranslationEcef, &traverseNode, &parameters]( int nodeIndex, const QMatrix4x4 &parentTransform ) {
339 const tinygltf::Node &gltfNode = model.nodes[nodeIndex];
340 std::unique_ptr<QMatrix4x4> gltfLocalTransform = QgsGltfUtils::parseNodeTransform( gltfNode );
341 if ( !parentTransform.isIdentity() )
342 {
343 if ( gltfLocalTransform )
344 *gltfLocalTransform = parentTransform * *gltfLocalTransform;
345 else
346 {
347 gltfLocalTransform.reset( new QMatrix4x4( parentTransform ) );
348 }
349 }
350
351 if ( gltfNode.mesh >= 0 )
352 {
353 const tinygltf::Mesh &mesh = model.meshes[gltfNode.mesh];
354 feedback->pushDebugInfo( QObject::tr( "Found %1 primitives in node [%2]" ).arg( mesh.primitives.size() ).arg( nodeIndex ) );
355
356 for ( const tinygltf::Primitive &primitive : mesh.primitives )
357 {
358 switch ( primitive.mode )
359 {
360 case TINYGLTF_MODE_TRIANGLES:
361 {
362 if ( polygonSink )
363 {
364 std::unique_ptr<QgsAbstractGeometry> geometry = extractTriangles( model, primitive, ecefTransform, tileTranslationEcef, gltfLocalTransform.get(), feedback );
365 if ( geometry )
366 {
367 QgsFeature f;
368 f.setGeometry( std::move( geometry ) );
369 if ( !polygonSink->addFeature( f, QgsFeatureSink::FastInsert ) )
370 throw QgsProcessingException( writeFeatureError( polygonSink.get(), parameters, QStringLiteral( "OUTPUT_POLYGONS" ) ) );
371 }
372 }
373 break;
374 }
375
376 case TINYGLTF_MODE_LINE:
377 {
378 if ( lineSink )
379 {
380 std::unique_ptr<QgsAbstractGeometry> geometry = extractLines( model, primitive, ecefTransform, tileTranslationEcef, gltfLocalTransform.get(), feedback );
381 if ( geometry )
382 {
383 QgsFeature f;
384 f.setGeometry( std::move( geometry ) );
385 if ( !lineSink->addFeature( f, QgsFeatureSink::FastInsert ) )
386 throw QgsProcessingException( writeFeatureError( lineSink.get(), parameters, QStringLiteral( "OUTPUT_LINES" ) ) );
387 }
388 }
389 break;
390 }
391
392 case TINYGLTF_MODE_POINTS:
393 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_POINTS ) )
394 {
395 feedback->reportError( QObject::tr( "Point objects are not supported" ) );
396 warnedPrimitiveTypes.insert( TINYGLTF_MODE_POINTS );
397 }
398 break;
399
400 case TINYGLTF_MODE_LINE_LOOP:
401 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_LINE_LOOP ) )
402 {
403 feedback->reportError( QObject::tr( "Line loops in are not supported" ) );
404 warnedPrimitiveTypes.insert( TINYGLTF_MODE_LINE_LOOP );
405 }
406 break;
407
408 case TINYGLTF_MODE_LINE_STRIP:
409 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_LINE_STRIP ) )
410 {
411 feedback->reportError( QObject::tr( "Line strips in are not supported" ) );
412 warnedPrimitiveTypes.insert( TINYGLTF_MODE_LINE_STRIP );
413 }
414 break;
415
416 case TINYGLTF_MODE_TRIANGLE_STRIP:
417 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_TRIANGLE_STRIP ) )
418 {
419 feedback->reportError( QObject::tr( "Triangular strips are not supported" ) );
420 warnedPrimitiveTypes.insert( TINYGLTF_MODE_TRIANGLE_STRIP );
421 }
422 break;
423
424 case TINYGLTF_MODE_TRIANGLE_FAN:
425 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_TRIANGLE_FAN ) )
426 {
427 feedback->reportError( QObject::tr( "Triangular fans are not supported" ) );
428 warnedPrimitiveTypes.insert( TINYGLTF_MODE_TRIANGLE_FAN );
429 }
430 break;
431
432 default:
433 if ( !warnedPrimitiveTypes.contains( primitive.mode ) )
434 {
435 feedback->reportError( QObject::tr( "Primitive type %1 are not supported" ).arg( primitive.mode ) );
436 warnedPrimitiveTypes.insert( primitive.mode );
437 }
438 break;
439 }
440 }
441 }
442
443 for ( int childNode : gltfNode.children )
444 {
445 traverseNode( childNode, gltfLocalTransform ? *gltfLocalTransform : QMatrix4x4() );
446 }
447 };
448
449 if ( !scene.nodes.empty() )
450 {
451 for ( const int nodeIndex : scene.nodes )
452 {
453 traverseNode( nodeIndex, QMatrix4x4() );
454 }
455 }
456
457 QVariantMap outputs;
458 if ( polygonSink )
459 {
460 polygonSink->finalize();
461 outputs.insert( QStringLiteral( "OUTPUT_POLYGONS" ), polygonDest );
462 }
463 if ( lineSink )
464 {
465 lineSink->finalize();
466 outputs.insert( QStringLiteral( "OUTPUT_LINES" ), lineDest );
467 }
468 return outputs;
469}
470
471
@ VectorPolygon
Vector polygon layers.
@ VectorLine
Vector line layers.
@ File
Parameter is a single file.
@ Y
Y-axis.
@ MultiLineStringZ
MultiLineStringZ.
@ MultiPolygonZ
MultiPolygonZ.
Represents a coordinate reference system (CRS).
Handles coordinate transforms between two coordinate systems.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Container of fields for a vector layer.
Definition qgsfields.h:46
Line string geometry type, with support for z-dimension and m-values.
A simple 4x4 matrix implementation useful for transformation in 3D space.
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
Polygon geometry type.
Definition qgspolygon.h:33
Contains information about the context in which a processing algorithm is executed.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
virtual void pushWarning(const QString &warning)
Pushes a warning informational message from the algorithm.
virtual void pushDebugInfo(const QString &info)
Pushes an informational message containing debugging helpers from the algorithm.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
A feature sink output for processing algorithms.
An input file or folder parameter for processing algorithms.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:30