QGIS API Documentation 3.41.0-Master (cea29feecf2)
Loading...
Searching...
No Matches
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, QStringLiteral( "gltf" ), QVariant(), false, QStringLiteral( "GLTF (*.gltf *.GLTF);;GLB (*.glb *.GLB)" ) ) );
74
75 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT_POLYGONS" ), QObject::tr( "Output polygons" ), Qgis::ProcessingSourceType::VectorPolygon, QVariant(), true, true ) );
76 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT_LINES" ), QObject::tr( "Output lines" ), Qgis::ProcessingSourceType::VectorLine, QVariant(), true, true ) );
77}
78
79std::unique_ptr<QgsAbstractGeometry> extractTriangles(
80 const tinygltf::Model &model,
81 const tinygltf::Primitive &primitive,
82 const QgsCoordinateTransform &ecefTransform,
83 const QgsVector3D &tileTranslationEcef,
84 const QMatrix4x4 *gltfLocalTransform,
85 QgsProcessingFeedback *feedback
86)
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 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE ) && primitiveAccessor.type == TINYGLTF_TYPE_SCALAR );
127
128 const char *primitivePtr = reinterpret_cast<const char *>( bPrimitive.data.data() ) + bvPrimitive.byteOffset + primitiveAccessor.byteOffset;
129
130 mp->reserve( primitiveAccessor.count / 3 );
131 for ( std::size_t i = 0; i < primitiveAccessor.count / 3; i++ )
132 {
133 unsigned int index1 = 0;
134 unsigned int index2 = 0;
135 unsigned int index3 = 0;
136
137 if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT )
138 {
139 const unsigned short *usPtrPrimitive = reinterpret_cast<const unsigned short *>( primitivePtr );
140 if ( bvPrimitive.byteStride )
141 primitivePtr += bvPrimitive.byteStride;
142 else
143 primitivePtr += 3 * sizeof( unsigned short );
144
145 index1 = usPtrPrimitive[0];
146 index2 = usPtrPrimitive[1];
147 index3 = usPtrPrimitive[2];
148 }
149 else if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
150 {
151 const unsigned char *usPtrPrimitive = reinterpret_cast<const unsigned char *>( primitivePtr );
152 if ( bvPrimitive.byteStride )
153 primitivePtr += bvPrimitive.byteStride;
154 else
155 primitivePtr += 3 * sizeof( unsigned char );
156
157 index1 = usPtrPrimitive[0];
158 index2 = usPtrPrimitive[1];
159 index3 = usPtrPrimitive[2];
160 }
161 else
162 {
163 const unsigned int *uintPtrPrimitive = reinterpret_cast<const unsigned int *>( primitivePtr );
164 if ( bvPrimitive.byteStride )
165 primitivePtr += bvPrimitive.byteStride;
166 else
167 primitivePtr += 3 * sizeof( unsigned int );
168
169 index1 = uintPtrPrimitive[0];
170 index2 = uintPtrPrimitive[1];
171 index3 = uintPtrPrimitive[2];
172 }
173
174 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] ) } ) ) );
175 }
176 }
177 return mp;
178}
179
180std::unique_ptr<QgsAbstractGeometry> extractLines(
181 const tinygltf::Model &model,
182 const tinygltf::Primitive &primitive,
183 const QgsCoordinateTransform &ecefTransform,
184 const QgsVector3D &tileTranslationEcef,
185 const QMatrix4x4 *gltfLocalTransform,
186 QgsProcessingFeedback *feedback
187)
188{
189 auto posIt = primitive.attributes.find( "POSITION" );
190 if ( posIt == primitive.attributes.end() )
191 {
192 feedback->reportError( QObject::tr( "Could not find POSITION attribute for primitive" ) );
193 return nullptr;
194 }
195 int positionAccessorIndex = posIt->second;
196
197 QVector<double> x;
198 QVector<double> y;
199 QVector<double> z;
200 QgsGltfUtils::accessorToMapCoordinates(
201 model, positionAccessorIndex, QgsMatrix4x4(),
202 &ecefTransform,
203 tileTranslationEcef,
204 gltfLocalTransform,
206 x, y, z
207 );
208
209 std::unique_ptr<QgsMultiLineString> ml = std::make_unique<QgsMultiLineString>();
210
211 if ( primitive.indices == -1 )
212 {
213 Q_ASSERT( x.size() % 2 == 0 );
214
215 ml->reserve( x.size() );
216 for ( int i = 0; i < x.size(); i += 2 )
217 {
218 ml->addGeometry( new QgsLineString( QVector<QgsPoint> { QgsPoint( x[i], y[i], z[i] ), QgsPoint( x[i + 1], y[i + 1], z[i + 1] ) } ) );
219 }
220 }
221 else
222 {
223 const tinygltf::Accessor &primitiveAccessor = model.accessors[primitive.indices];
224 const tinygltf::BufferView &bvPrimitive = model.bufferViews[primitiveAccessor.bufferView];
225 const tinygltf::Buffer &bPrimitive = model.buffers[bvPrimitive.buffer];
226
227 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 );
228
229 const char *primitivePtr = reinterpret_cast<const char *>( bPrimitive.data.data() ) + bvPrimitive.byteOffset + primitiveAccessor.byteOffset;
230
231 ml->reserve( primitiveAccessor.count / 2 );
232 for ( std::size_t i = 0; i < primitiveAccessor.count / 2; i++ )
233 {
234 unsigned int index1 = 0;
235 unsigned int index2 = 0;
236
237 if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT )
238 {
239 const unsigned short *usPtrPrimitive = reinterpret_cast<const unsigned short *>( primitivePtr );
240 if ( bvPrimitive.byteStride )
241 primitivePtr += bvPrimitive.byteStride;
242 else
243 primitivePtr += 2 * sizeof( unsigned short );
244
245 index1 = usPtrPrimitive[0];
246 index2 = usPtrPrimitive[1];
247 }
248 else if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
249 {
250 const unsigned char *usPtrPrimitive = reinterpret_cast<const unsigned char *>( primitivePtr );
251 if ( bvPrimitive.byteStride )
252 primitivePtr += bvPrimitive.byteStride;
253 else
254 primitivePtr += 2 * sizeof( unsigned char );
255
256 index1 = usPtrPrimitive[0];
257 index2 = usPtrPrimitive[1];
258 }
259 else
260 {
261 const unsigned int *uintPtrPrimitive = reinterpret_cast<const unsigned int *>( primitivePtr );
262 if ( bvPrimitive.byteStride )
263 primitivePtr += bvPrimitive.byteStride;
264 else
265 primitivePtr += 2 * sizeof( unsigned int );
266
267 index1 = uintPtrPrimitive[0];
268 index2 = uintPtrPrimitive[1];
269 }
270
271 ml->addGeometry( new QgsLineString( QVector<QgsPoint> { QgsPoint( x[index1], y[index1], z[index1] ), QgsPoint( x[index2], y[index2], z[index2] ) } ) );
272 }
273 }
274 return ml;
275}
276
277QVariantMap QgsGltfToVectorFeaturesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
278{
279 const QString path = parameterAsFile( parameters, QStringLiteral( "INPUT" ), context );
280
281 const QgsCoordinateReferenceSystem destCrs( QStringLiteral( "EPSG:4326" ) );
282 QgsFields fields;
283
284 QString polygonDest;
285 std::unique_ptr<QgsFeatureSink> polygonSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT_POLYGONS" ), context, polygonDest, fields, Qgis::WkbType::MultiPolygonZ, destCrs ) );
286 if ( !polygonSink && parameters.value( QStringLiteral( "OUTPUT_POLYGONS" ) ).isValid() )
287 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT_POLYGONS" ) ) );
288 QString lineDest;
289 std::unique_ptr<QgsFeatureSink> lineSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT_LINES" ), context, lineDest, fields, Qgis::WkbType::MultiLineStringZ, destCrs ) );
290 if ( !lineSink && parameters.value( QStringLiteral( "OUTPUT_LINES" ) ).isValid() )
291 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT_LINES" ) ) );
292
293 QFile f( path );
294 QByteArray fileContent;
295 if ( f.open( QIODevice::ReadOnly ) )
296 {
297 fileContent = f.readAll();
298 }
299 else
300 {
301 throw QgsProcessingException( QObject::tr( "Could not load source file %1." ).arg( path ) );
302 }
303
304 const QgsCoordinateTransform ecefTransform( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) ), destCrs, context.transformContext() );
305
306 tinygltf::Model model;
307 QString errors;
308 QString warnings;
309 if ( !QgsGltfUtils::loadGltfModel( fileContent, model, &errors, &warnings ) )
310 {
311 throw QgsProcessingException( QObject::tr( "Error loading GLTF model: %1" ).arg( errors ) );
312 }
313 if ( !warnings.isEmpty() )
314 {
315 feedback->pushWarning( warnings );
316 }
317 feedback->pushDebugInfo( QObject::tr( "Found %1 scenes" ).arg( model.scenes.size() ) );
318
319 bool sceneOk = false;
320 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
321 if ( !sceneOk )
322 {
323 throw QgsProcessingException( QObject::tr( "No scenes found in model" ) );
324 }
325
326 const tinygltf::Scene &scene = model.scenes[sceneIndex];
327 feedback->pushDebugInfo( QObject::tr( "Found %1 nodes in default scene [%2]" ).arg( scene.nodes.size() ).arg( sceneIndex ) );
328
329 QSet<int> warnedPrimitiveTypes;
330
331 const QgsVector3D tileTranslationEcef = QgsGltfUtils::extractTileTranslation( model );
332 std::function<void( int nodeIndex, const QMatrix4x4 &transform )> traverseNode;
333 traverseNode = [&model, feedback, &polygonSink, &lineSink, &warnedPrimitiveTypes, &ecefTransform, &tileTranslationEcef, &traverseNode, &parameters]( int nodeIndex, const QMatrix4x4 &parentTransform ) {
334 const tinygltf::Node &gltfNode = model.nodes[nodeIndex];
335 std::unique_ptr<QMatrix4x4> gltfLocalTransform = QgsGltfUtils::parseNodeTransform( gltfNode );
336 if ( !parentTransform.isIdentity() )
337 {
338 if ( gltfLocalTransform )
339 *gltfLocalTransform = parentTransform * *gltfLocalTransform;
340 else
341 {
342 gltfLocalTransform.reset( new QMatrix4x4( parentTransform ) );
343 }
344 }
345
346 if ( gltfNode.mesh >= 0 )
347 {
348 const tinygltf::Mesh &mesh = model.meshes[gltfNode.mesh];
349 feedback->pushDebugInfo( QObject::tr( "Found %1 primitives in node [%2]" ).arg( mesh.primitives.size() ).arg( nodeIndex ) );
350
351 for ( const tinygltf::Primitive &primitive : mesh.primitives )
352 {
353 switch ( primitive.mode )
354 {
355 case TINYGLTF_MODE_TRIANGLES:
356 {
357 if ( polygonSink )
358 {
359 std::unique_ptr<QgsAbstractGeometry> geometry = extractTriangles( model, primitive, ecefTransform, tileTranslationEcef, gltfLocalTransform.get(), feedback );
360 if ( geometry )
361 {
362 QgsFeature f;
363 f.setGeometry( std::move( geometry ) );
364 if ( !polygonSink->addFeature( f, QgsFeatureSink::FastInsert ) )
365 throw QgsProcessingException( writeFeatureError( polygonSink.get(), parameters, QStringLiteral( "OUTPUT_POLYGONS" ) ) );
366 }
367 }
368 break;
369 }
370
371 case TINYGLTF_MODE_LINE:
372 {
373 if ( lineSink )
374 {
375 std::unique_ptr<QgsAbstractGeometry> geometry = extractLines( model, primitive, ecefTransform, tileTranslationEcef, gltfLocalTransform.get(), feedback );
376 if ( geometry )
377 {
378 QgsFeature f;
379 f.setGeometry( std::move( geometry ) );
380 if ( !lineSink->addFeature( f, QgsFeatureSink::FastInsert ) )
381 throw QgsProcessingException( writeFeatureError( lineSink.get(), parameters, QStringLiteral( "OUTPUT_LINES" ) ) );
382 }
383 }
384 break;
385 }
386
387 case TINYGLTF_MODE_POINTS:
388 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_POINTS ) )
389 {
390 feedback->reportError( QObject::tr( "Point objects are not supported" ) );
391 warnedPrimitiveTypes.insert( TINYGLTF_MODE_POINTS );
392 }
393 break;
394
395 case TINYGLTF_MODE_LINE_LOOP:
396 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_LINE_LOOP ) )
397 {
398 feedback->reportError( QObject::tr( "Line loops in are not supported" ) );
399 warnedPrimitiveTypes.insert( TINYGLTF_MODE_LINE_LOOP );
400 }
401 break;
402
403 case TINYGLTF_MODE_LINE_STRIP:
404 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_LINE_STRIP ) )
405 {
406 feedback->reportError( QObject::tr( "Line strips in are not supported" ) );
407 warnedPrimitiveTypes.insert( TINYGLTF_MODE_LINE_STRIP );
408 }
409 break;
410
411 case TINYGLTF_MODE_TRIANGLE_STRIP:
412 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_TRIANGLE_STRIP ) )
413 {
414 feedback->reportError( QObject::tr( "Triangular strips are not supported" ) );
415 warnedPrimitiveTypes.insert( TINYGLTF_MODE_TRIANGLE_STRIP );
416 }
417 break;
418
419 case TINYGLTF_MODE_TRIANGLE_FAN:
420 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_TRIANGLE_FAN ) )
421 {
422 feedback->reportError( QObject::tr( "Triangular fans are not supported" ) );
423 warnedPrimitiveTypes.insert( TINYGLTF_MODE_TRIANGLE_FAN );
424 }
425 break;
426
427 default:
428 if ( !warnedPrimitiveTypes.contains( primitive.mode ) )
429 {
430 feedback->reportError( QObject::tr( "Primitive type %1 are not supported" ).arg( primitive.mode ) );
431 warnedPrimitiveTypes.insert( primitive.mode );
432 }
433 break;
434 }
435 }
436 }
437
438 for ( int childNode : gltfNode.children )
439 {
440 traverseNode( childNode, gltfLocalTransform ? *gltfLocalTransform : QMatrix4x4() );
441 }
442 };
443
444 if ( !scene.nodes.empty() )
445 {
446 for ( const int nodeIndex : scene.nodes )
447 {
448 traverseNode( nodeIndex, QMatrix4x4() );
449 }
450 }
451
452 QVariantMap outputs;
453 if ( polygonSink )
454 {
455 polygonSink->finalize();
456 outputs.insert( QStringLiteral( "OUTPUT_POLYGONS" ), polygonDest );
457 }
458 if ( lineSink )
459 {
460 lineSink->finalize();
461 outputs.insert( QStringLiteral( "OUTPUT_LINES" ), lineDest );
462 }
463 return outputs;
464}
465
466
@ 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: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.
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
Definition qgsvector3d.h:31