QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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( "Converts GLTF content to standard vector layer formats." );
64}
65
66QgsGltfToVectorFeaturesAlgorithm *QgsGltfToVectorFeaturesAlgorithm::createInstance() const
67{
68 return new QgsGltfToVectorFeaturesAlgorithm();
69}
70
71void QgsGltfToVectorFeaturesAlgorithm::initAlgorithm( const QVariantMap & )
72{
73 addParameter( new QgsProcessingParameterFile( QStringLiteral( "INPUT" ), QObject::tr( "Input GLTF" ), Qgis::ProcessingFileParameterBehavior::File,
74 QStringLiteral( "gltf" ), QVariant(), false, QStringLiteral( "GLTF (*.gltf *.GLTF);;GLB (*.glb *.GLB)" ) ) );
75
76 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT_POLYGONS" ), QObject::tr( "Output polygons" ), Qgis::ProcessingSourceType::VectorPolygon, QVariant(), true, true ) );
77 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT_LINES" ), QObject::tr( "Output lines" ), Qgis::ProcessingSourceType::VectorLine, QVariant(), true, true ) );
78}
79
80std::unique_ptr< QgsAbstractGeometry > extractTriangles(
81 const tinygltf::Model &model,
82 const tinygltf::Primitive &primitive,
83 const QgsCoordinateTransform &ecefTransform,
84 const QgsVector3D &tileTranslationEcef,
85 const QMatrix4x4 *gltfLocalTransform,
86 QgsProcessingFeedback *feedback )
87{
88 auto posIt = primitive.attributes.find( "POSITION" );
89 if ( posIt == primitive.attributes.end() )
90 {
91 feedback->reportError( QObject::tr( "Could not find POSITION attribute for primitive" ) );
92 return nullptr;
93 }
94 int positionAccessorIndex = posIt->second;
95
96 QVector< double > x;
97 QVector< double > y;
98 QVector< double > z;
99 QgsGltfUtils::accessorToMapCoordinates(
100 model, positionAccessorIndex, QgsMatrix4x4(),
101 &ecefTransform,
102 tileTranslationEcef,
103 gltfLocalTransform,
105 x, y, z
106 );
107
108 std::unique_ptr< QgsMultiPolygon > mp = std::make_unique< QgsMultiPolygon >();
109
110 if ( primitive.indices == -1 )
111 {
112 Q_ASSERT( x.size() % 3 == 0 );
113
114 mp->reserve( x.size() );
115 for ( int i = 0; i < x.size(); i += 3 )
116 {
117 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] ) } ) ) );
118 }
119 }
120 else
121 {
122 const tinygltf::Accessor &primitiveAccessor = model.accessors[primitive.indices];
123 const tinygltf::BufferView &bvPrimitive = model.bufferViews[primitiveAccessor.bufferView];
124 const tinygltf::Buffer &bPrimitive = model.buffers[bvPrimitive.buffer];
125
126 Q_ASSERT( ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT
127 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT
128 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
129 && primitiveAccessor.type == TINYGLTF_TYPE_SCALAR );
130
131 const char *primitivePtr = reinterpret_cast< const char * >( bPrimitive.data.data() ) + bvPrimitive.byteOffset + primitiveAccessor.byteOffset;
132
133 mp->reserve( primitiveAccessor.count / 3 );
134 for ( std::size_t i = 0; i < primitiveAccessor.count / 3; i++ )
135 {
136 unsigned int index1 = 0;
137 unsigned int index2 = 0;
138 unsigned int index3 = 0;
139
140 if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT )
141 {
142 const unsigned short *usPtrPrimitive = reinterpret_cast< const unsigned short * >( primitivePtr );
143 if ( bvPrimitive.byteStride )
144 primitivePtr += bvPrimitive.byteStride;
145 else
146 primitivePtr += 3 * sizeof( unsigned short );
147
148 index1 = usPtrPrimitive[0];
149 index2 = usPtrPrimitive[1];
150 index3 = usPtrPrimitive[2];
151 }
152 else if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
153 {
154 const unsigned char *usPtrPrimitive = reinterpret_cast< const unsigned char * >( primitivePtr );
155 if ( bvPrimitive.byteStride )
156 primitivePtr += bvPrimitive.byteStride;
157 else
158 primitivePtr += 3 * sizeof( unsigned char );
159
160 index1 = usPtrPrimitive[0];
161 index2 = usPtrPrimitive[1];
162 index3 = usPtrPrimitive[2];
163 }
164 else
165 {
166 const unsigned int *uintPtrPrimitive = reinterpret_cast< const unsigned int * >( primitivePtr );
167 if ( bvPrimitive.byteStride )
168 primitivePtr += bvPrimitive.byteStride;
169 else
170 primitivePtr += 3 * sizeof( unsigned int );
171
172 index1 = uintPtrPrimitive[0];
173 index2 = uintPtrPrimitive[1];
174 index3 = uintPtrPrimitive[2];
175 }
176
177 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] ) } ) ) );
178 }
179 }
180 return mp;
181}
182
183std::unique_ptr< QgsAbstractGeometry > extractLines(
184 const tinygltf::Model &model,
185 const tinygltf::Primitive &primitive,
186 const QgsCoordinateTransform &ecefTransform,
187 const QgsVector3D &tileTranslationEcef,
188 const QMatrix4x4 *gltfLocalTransform,
189 QgsProcessingFeedback *feedback )
190{
191 auto posIt = primitive.attributes.find( "POSITION" );
192 if ( posIt == primitive.attributes.end() )
193 {
194 feedback->reportError( QObject::tr( "Could not find POSITION attribute for primitive" ) );
195 return nullptr;
196 }
197 int positionAccessorIndex = posIt->second;
198
199 QVector< double > x;
200 QVector< double > y;
201 QVector< double > z;
202 QgsGltfUtils::accessorToMapCoordinates(
203 model, positionAccessorIndex, QgsMatrix4x4(),
204 &ecefTransform,
205 tileTranslationEcef,
206 gltfLocalTransform,
208 x, y, z
209 );
210
211 std::unique_ptr< QgsMultiLineString > ml = std::make_unique< QgsMultiLineString >();
212
213 if ( primitive.indices == -1 )
214 {
215 Q_ASSERT( x.size() % 2 == 0 );
216
217 ml->reserve( x.size() );
218 for ( int i = 0; i < x.size(); i += 2 )
219 {
220 ml->addGeometry( new QgsLineString( QVector<QgsPoint> { QgsPoint( x[i], y[i], z[i] ), QgsPoint( x[i + 1], y[i + 1], z[i + 1] ) } ) );
221 }
222 }
223 else
224 {
225 const tinygltf::Accessor &primitiveAccessor = model.accessors[primitive.indices];
226 const tinygltf::BufferView &bvPrimitive = model.bufferViews[primitiveAccessor.bufferView];
227 const tinygltf::Buffer &bPrimitive = model.buffers[bvPrimitive.buffer];
228
229 Q_ASSERT( ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT
230 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT
231 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
232 && 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,
291 Qgis::WkbType::MultiPolygonZ, destCrs ) );
292 if ( !polygonSink && parameters.value( QStringLiteral( "OUTPUT_POLYGONS" ) ).isValid() )
293 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT_POLYGONS" ) ) );
294 QString lineDest;
295 std::unique_ptr< QgsFeatureSink > lineSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT_LINES" ), context, lineDest, fields,
297 if ( !lineSink && parameters.value( QStringLiteral( "OUTPUT_LINES" ) ).isValid() )
298 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT_LINES" ) ) );
299
300 QFile f( path );
301 QByteArray fileContent;
302 if ( f.open( QIODevice::ReadOnly ) )
303 {
304 fileContent = f.readAll();
305 }
306 else
307 {
308 throw QgsProcessingException( QObject::tr( "Could not load source file %1." ).arg( path ) );
309 }
310
311 const QgsCoordinateTransform ecefTransform( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) ), destCrs, context.transformContext() );
312
313 tinygltf::Model model;
314 QString errors;
315 QString warnings;
316 if ( !QgsGltfUtils::loadGltfModel( fileContent, model, &errors, &warnings ) )
317 {
318 throw QgsProcessingException( QObject::tr( "Error loading GLTF model: %1" ).arg( errors ) );
319 }
320 if ( !warnings.isEmpty() )
321 {
322 feedback->pushWarning( warnings );
323 }
324 feedback->pushDebugInfo( QObject::tr( "Found %1 scenes" ).arg( model.scenes.size() ) );
325
326 bool sceneOk = false;
327 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
328 if ( !sceneOk )
329 {
330 throw QgsProcessingException( QObject::tr( "No scenes found in model" ) );
331 }
332
333 const tinygltf::Scene &scene = model.scenes[sceneIndex];
334 feedback->pushDebugInfo( QObject::tr( "Found %1 nodes in default scene [%2]" ).arg( scene.nodes.size() ).arg( sceneIndex ) );
335
336 QSet< int > warnedPrimitiveTypes;
337
338 const QgsVector3D tileTranslationEcef = QgsGltfUtils::extractTileTranslation( model );
339 std::function< void( int nodeIndex, const QMatrix4x4 &transform ) > traverseNode;
340 traverseNode = [&model, feedback, &polygonSink, &lineSink, &warnedPrimitiveTypes, &ecefTransform, &tileTranslationEcef, &traverseNode]( int nodeIndex, const QMatrix4x4 & parentTransform )
341 {
342 const tinygltf::Node &gltfNode = model.nodes[nodeIndex];
343 std::unique_ptr< QMatrix4x4 > gltfLocalTransform = QgsGltfUtils::parseNodeTransform( gltfNode );
344 if ( !parentTransform.isIdentity() )
345 {
346 if ( gltfLocalTransform )
347 *gltfLocalTransform = parentTransform * *gltfLocalTransform;
348 else
349 {
350 gltfLocalTransform.reset( new QMatrix4x4( parentTransform ) );
351 }
352 }
353
354 if ( gltfNode.mesh >= 0 )
355 {
356 const tinygltf::Mesh &mesh = model.meshes[gltfNode.mesh];
357 feedback->pushDebugInfo( QObject::tr( "Found %1 primitives in node [%2]" ).arg( mesh.primitives.size() ).arg( nodeIndex ) );
358
359 for ( const tinygltf::Primitive &primitive : mesh.primitives )
360 {
361 switch ( primitive.mode )
362 {
363 case TINYGLTF_MODE_TRIANGLES:
364 {
365 if ( polygonSink )
366 {
367 std::unique_ptr< QgsAbstractGeometry > geometry = extractTriangles( model, primitive, ecefTransform, tileTranslationEcef, gltfLocalTransform.get(), feedback );
368 if ( geometry )
369 {
370 QgsFeature f;
371 f.setGeometry( std::move( geometry ) );
372 polygonSink->addFeature( f, QgsFeatureSink::FastInsert );
373 }
374 }
375 break;
376 }
377
378 case TINYGLTF_MODE_LINE:
379 {
380 if ( lineSink )
381 {
382 std::unique_ptr< QgsAbstractGeometry > geometry = extractLines( model, primitive, ecefTransform, tileTranslationEcef, gltfLocalTransform.get(), feedback );
383 if ( geometry )
384 {
385 QgsFeature f;
386 f.setGeometry( std::move( geometry ) );
387 polygonSink->addFeature( f, QgsFeatureSink::FastInsert );
388 }
389 }
390 break;
391 }
392
393 case TINYGLTF_MODE_POINTS:
394 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_POINTS ) )
395 {
396 feedback->reportError( QObject::tr( "Point objects are not supported" ) );
397 warnedPrimitiveTypes.insert( TINYGLTF_MODE_POINTS );
398 }
399 break;
400
401 case TINYGLTF_MODE_LINE_LOOP:
402 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_LINE_LOOP ) )
403 {
404 feedback->reportError( QObject::tr( "Line loops in are not supported" ) );
405 warnedPrimitiveTypes.insert( TINYGLTF_MODE_LINE_LOOP );
406 }
407 break;
408
409 case TINYGLTF_MODE_LINE_STRIP:
410 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_LINE_STRIP ) )
411 {
412 feedback->reportError( QObject::tr( "Line strips in are not supported" ) );
413 warnedPrimitiveTypes.insert( TINYGLTF_MODE_LINE_STRIP );
414 }
415 break;
416
417 case TINYGLTF_MODE_TRIANGLE_STRIP:
418 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_TRIANGLE_STRIP ) )
419 {
420 feedback->reportError( QObject::tr( "Triangular strips are not supported" ) );
421 warnedPrimitiveTypes.insert( TINYGLTF_MODE_TRIANGLE_STRIP );
422 }
423 break;
424
425 case TINYGLTF_MODE_TRIANGLE_FAN:
426 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_TRIANGLE_FAN ) )
427 {
428 feedback->reportError( QObject::tr( "Triangular fans are not supported" ) );
429 warnedPrimitiveTypes.insert( TINYGLTF_MODE_TRIANGLE_FAN );
430 }
431 break;
432
433 default:
434 if ( !warnedPrimitiveTypes.contains( primitive.mode ) )
435 {
436 feedback->reportError( QObject::tr( "Primitive type %1 are not supported" ).arg( primitive.mode ) );
437 warnedPrimitiveTypes.insert( primitive.mode );
438 }
439 break;
440 }
441 }
442 }
443
444 for ( int childNode : gltfNode.children )
445 {
446 traverseNode( childNode, gltfLocalTransform ? *gltfLocalTransform : QMatrix4x4() );
447 }
448
449 };
450
451 if ( !scene.nodes.empty() )
452 {
453 for ( const int nodeIndex : scene.nodes )
454 {
455 traverseNode( nodeIndex, QMatrix4x4() );
456 }
457 }
458
459 QVariantMap outputs;
460 if ( polygonSink )
461 outputs.insert( QStringLiteral( "OUTPUT_POLYGONS" ), polygonDest );
462 if ( lineSink )
463 outputs.insert( QStringLiteral( "OUTPUT_LINES" ), lineDest );
464 return outputs;
465}
466
467
@ VectorPolygon
Vector polygon layers.
@ VectorLine
Vector line layers.
@ File
Parameter is a single file.
@ Y
Y-axis.
@ MultiLineStringZ
MultiLineStringZ.
@ MultiPolygonZ
MultiPolygonZ.
This class represents a coordinate reference system (CRS).
Class for doing transforms between two map 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:56
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:167
Container of fields for a vector layer.
Definition: qgsfields.h:45
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:45
A simple 4x4 matrix implementation useful for transformation in 3D space.
Definition: qgsmatrix4x4.h:40
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.
Definition: qgsexception.h:83
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.
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
Definition: qgsvector3d.h:31