QGIS API Documentation 4.3.0-Master (d3b565c628d)
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
20#include <memory>
21
22#include "qgsgltfutils.h"
23#include "qgslinestring.h"
24#include "qgsmatrix4x4.h"
25#include "qgsmultilinestring.h"
26#include "qgsmultipolygon.h"
27#include "qgspolygon.h"
28#include "qgsvector3d.h"
29
30#include <QFileInfo>
31#include <QMatrix4x4>
32#include <QString>
33
34using namespace Qt::StringLiterals;
35
36#define TINYGLTF_NO_STB_IMAGE // we use QImage-based reading of images
37#define TINYGLTF_NO_STB_IMAGE_WRITE // we don't need writing of images
38
39#include "tiny_gltf.h"
40
42
43QString QgsGltfToVectorFeaturesAlgorithm::name() const
44{
45 return u"gltftovector"_s;
46}
47
48QString QgsGltfToVectorFeaturesAlgorithm::displayName() const
49{
50 return QObject::tr( "Convert GLTF to vector features" );
51}
52
53QStringList QgsGltfToVectorFeaturesAlgorithm::tags() const
54{
55 return QObject::tr( "3d,tiles,cesium" ).split( ',' );
56}
57
58QString QgsGltfToVectorFeaturesAlgorithm::group() const
59{
60 return QObject::tr( "3D Tiles" );
61}
62
63QString QgsGltfToVectorFeaturesAlgorithm::groupId() const
64{
65 return u"3dtiles"_s;
66}
67
68QString QgsGltfToVectorFeaturesAlgorithm::shortHelpString() const
69{
70 return QObject::tr( "This algorithm converts GLTF content to standard vector layer formats." );
71}
72
73QString QgsGltfToVectorFeaturesAlgorithm::shortDescription() const
74{
75 return QObject::tr( "Converts GLTF content to standard vector layer formats." );
76}
77
78QgsGltfToVectorFeaturesAlgorithm *QgsGltfToVectorFeaturesAlgorithm::createInstance() const
79{
80 return new QgsGltfToVectorFeaturesAlgorithm();
81}
82
83void QgsGltfToVectorFeaturesAlgorithm::initAlgorithm( const QVariantMap & )
84{
85 addParameter(
86 new QgsProcessingParameterFile( u"INPUT"_s, QObject::tr( "Input GLTF" ), Qgis::ProcessingFileParameterBehavior::File, u"gltf"_s, QVariant(), false, u"GLTF (*.gltf *.GLTF);;GLB (*.glb *.GLB)"_s )
87 );
88
89 addParameter( new QgsProcessingParameterFeatureSink( u"OUTPUT_POLYGONS"_s, QObject::tr( "Output polygons" ), Qgis::ProcessingSourceType::VectorPolygon, QVariant(), true, true ) );
90 addParameter( new QgsProcessingParameterFeatureSink( u"OUTPUT_LINES"_s, QObject::tr( "Output lines" ), Qgis::ProcessingSourceType::VectorLine, QVariant(), true, true ) );
91}
92
93std::unique_ptr<QgsAbstractGeometry> extractTriangles(
94 const tinygltf::Model &model,
95 const tinygltf::Primitive &primitive,
96 const QgsCoordinateTransform &ecefTransform,
97 const QgsVector3D &tileTranslationEcef,
98 const QMatrix4x4 *gltfLocalTransform,
99 QgsProcessingFeedback *feedback
100)
101{
102 auto posIt = primitive.attributes.find( "POSITION" );
103 if ( posIt == primitive.attributes.end() )
104 {
105 feedback->reportError( QObject::tr( "Could not find POSITION attribute for primitive" ) );
106 return nullptr;
107 }
108 int positionAccessorIndex = posIt->second;
109
110 QVector<double> x;
111 QVector<double> y;
112 QVector<double> z;
113 QgsGltfUtils::accessorToMapCoordinates( model, positionAccessorIndex, QgsMatrix4x4(), &ecefTransform, tileTranslationEcef, gltfLocalTransform, Qgis::Axis::Y, x, y, z );
114
115 auto mp = std::make_unique<QgsMultiPolygon>();
116
117 if ( primitive.indices == -1 )
118 {
119 Q_ASSERT( x.size() % 3 == 0 );
120
121 mp->reserve( x.size() );
122 for ( int i = 0; i < x.size(); i += 3 )
123 {
124 mp->addGeometry( new QgsPolygon(
125 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] ) } )
126 ) );
127 }
128 }
129 else
130 {
131 const tinygltf::Accessor &primitiveAccessor = model.accessors[primitive.indices];
132 const tinygltf::BufferView &bvPrimitive = model.bufferViews[primitiveAccessor.bufferView];
133 const tinygltf::Buffer &bPrimitive = model.buffers[bvPrimitive.buffer];
134
135 Q_ASSERT(
136 ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT
137 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT
138 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
139 && primitiveAccessor.type == TINYGLTF_TYPE_SCALAR
140 );
141
142 const char *primitivePtr = reinterpret_cast<const char *>( bPrimitive.data.data() ) + bvPrimitive.byteOffset + primitiveAccessor.byteOffset;
143
144 mp->reserve( primitiveAccessor.count / 3 );
145 for ( std::size_t i = 0; i < primitiveAccessor.count / 3; i++ )
146 {
147 unsigned int index1 = 0;
148 unsigned int index2 = 0;
149 unsigned int index3 = 0;
150
151 if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT )
152 {
153 const unsigned short *usPtrPrimitive = reinterpret_cast<const unsigned short *>( primitivePtr );
154 if ( bvPrimitive.byteStride )
155 primitivePtr += bvPrimitive.byteStride;
156 else
157 primitivePtr += 3 * sizeof( unsigned short );
158
159 index1 = usPtrPrimitive[0];
160 index2 = usPtrPrimitive[1];
161 index3 = usPtrPrimitive[2];
162 }
163 else if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
164 {
165 const unsigned char *usPtrPrimitive = reinterpret_cast<const unsigned char *>( primitivePtr );
166 if ( bvPrimitive.byteStride )
167 primitivePtr += bvPrimitive.byteStride;
168 else
169 primitivePtr += 3 * sizeof( unsigned char );
170
171 index1 = usPtrPrimitive[0];
172 index2 = usPtrPrimitive[1];
173 index3 = usPtrPrimitive[2];
174 }
175 else
176 {
177 const unsigned int *uintPtrPrimitive = reinterpret_cast<const unsigned int *>( primitivePtr );
178 if ( bvPrimitive.byteStride )
179 primitivePtr += bvPrimitive.byteStride;
180 else
181 primitivePtr += 3 * sizeof( unsigned int );
182
183 index1 = uintPtrPrimitive[0];
184 index2 = uintPtrPrimitive[1];
185 index3 = uintPtrPrimitive[2];
186 }
187
188 mp->addGeometry( new QgsPolygon( new QgsLineString(
189 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] ) }
190 ) ) );
191 }
192 }
193 return mp;
194}
195
196std::unique_ptr<QgsAbstractGeometry> extractLines(
197 const tinygltf::Model &model,
198 const tinygltf::Primitive &primitive,
199 const QgsCoordinateTransform &ecefTransform,
200 const QgsVector3D &tileTranslationEcef,
201 const QMatrix4x4 *gltfLocalTransform,
202 QgsProcessingFeedback *feedback
203)
204{
205 auto posIt = primitive.attributes.find( "POSITION" );
206 if ( posIt == primitive.attributes.end() )
207 {
208 feedback->reportError( QObject::tr( "Could not find POSITION attribute for primitive" ) );
209 return nullptr;
210 }
211 int positionAccessorIndex = posIt->second;
212
213 QVector<double> x;
214 QVector<double> y;
215 QVector<double> z;
216 QgsGltfUtils::accessorToMapCoordinates( model, positionAccessorIndex, QgsMatrix4x4(), &ecefTransform, tileTranslationEcef, gltfLocalTransform, Qgis::Axis::Y, x, y, z );
217
218 auto ml = std::make_unique<QgsMultiLineString>();
219
220 if ( primitive.indices == -1 )
221 {
222 Q_ASSERT( x.size() % 2 == 0 );
223
224 ml->reserve( x.size() );
225 for ( int i = 0; i < x.size(); i += 2 )
226 {
227 ml->addGeometry( new QgsLineString( QVector<QgsPoint> { QgsPoint( x[i], y[i], z[i] ), QgsPoint( x[i + 1], y[i + 1], z[i + 1] ) } ) );
228 }
229 }
230 else
231 {
232 const tinygltf::Accessor &primitiveAccessor = model.accessors[primitive.indices];
233 const tinygltf::BufferView &bvPrimitive = model.bufferViews[primitiveAccessor.bufferView];
234 const tinygltf::Buffer &bPrimitive = model.buffers[bvPrimitive.buffer];
235
236 Q_ASSERT(
237 ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT
238 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT
239 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
240 && primitiveAccessor.type == TINYGLTF_TYPE_SCALAR
241 );
242
243 const char *primitivePtr = reinterpret_cast<const char *>( bPrimitive.data.data() ) + bvPrimitive.byteOffset + primitiveAccessor.byteOffset;
244
245 ml->reserve( primitiveAccessor.count / 2 );
246 for ( std::size_t i = 0; i < primitiveAccessor.count / 2; i++ )
247 {
248 unsigned int index1 = 0;
249 unsigned int index2 = 0;
250
251 if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT )
252 {
253 const unsigned short *usPtrPrimitive = reinterpret_cast<const unsigned short *>( primitivePtr );
254 if ( bvPrimitive.byteStride )
255 primitivePtr += bvPrimitive.byteStride;
256 else
257 primitivePtr += 2 * sizeof( unsigned short );
258
259 index1 = usPtrPrimitive[0];
260 index2 = usPtrPrimitive[1];
261 }
262 else if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
263 {
264 const unsigned char *usPtrPrimitive = reinterpret_cast<const unsigned char *>( primitivePtr );
265 if ( bvPrimitive.byteStride )
266 primitivePtr += bvPrimitive.byteStride;
267 else
268 primitivePtr += 2 * sizeof( unsigned char );
269
270 index1 = usPtrPrimitive[0];
271 index2 = usPtrPrimitive[1];
272 }
273 else
274 {
275 const unsigned int *uintPtrPrimitive = reinterpret_cast<const unsigned int *>( primitivePtr );
276 if ( bvPrimitive.byteStride )
277 primitivePtr += bvPrimitive.byteStride;
278 else
279 primitivePtr += 2 * sizeof( unsigned int );
280
281 index1 = uintPtrPrimitive[0];
282 index2 = uintPtrPrimitive[1];
283 }
284
285 ml->addGeometry( new QgsLineString( QVector<QgsPoint> { QgsPoint( x[index1], y[index1], z[index1] ), QgsPoint( x[index2], y[index2], z[index2] ) } ) );
286 }
287 }
288 return ml;
289}
290
291QVariantMap QgsGltfToVectorFeaturesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
292{
293 QGS_MARK_ALGORITHM_SOURCE
294
295 const QString path = parameterAsFile( parameters, u"INPUT"_s, context );
296
297 const QgsCoordinateReferenceSystem destCrs( u"EPSG:4326"_s );
298 QgsFields fields;
299
300 QString polygonDest;
301 std::unique_ptr<QgsFeatureSink> polygonSink( parameterAsSink( parameters, u"OUTPUT_POLYGONS"_s, context, polygonDest, fields, Qgis::WkbType::MultiPolygonZ, destCrs ) );
302 if ( !polygonSink && parameters.value( u"OUTPUT_POLYGONS"_s ).isValid() )
303 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT_POLYGONS"_s ) );
304 QString lineDest;
305 std::unique_ptr<QgsFeatureSink> lineSink( parameterAsSink( parameters, u"OUTPUT_LINES"_s, context, lineDest, fields, Qgis::WkbType::MultiLineStringZ, destCrs ) );
306 if ( !lineSink && parameters.value( u"OUTPUT_LINES"_s ).isValid() )
307 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT_LINES"_s ) );
308
309 QFile f( path );
310 QByteArray fileContent;
311 if ( f.open( QIODevice::ReadOnly ) )
312 {
313 fileContent = f.readAll();
314 }
315 else
316 {
317 throw QgsProcessingException( QObject::tr( "Could not load source file %1." ).arg( path ) );
318 }
319
320 const QgsCoordinateTransform ecefTransform( QgsCoordinateReferenceSystem( u"EPSG:4978"_s ), destCrs, context.transformContext() );
321
322 tinygltf::Model model;
323 QString errors;
324 QString warnings;
325 if ( !QgsGltfUtils::loadGltfModel( fileContent, model, &errors, &warnings, QFileInfo( path ).absolutePath() ) )
326 {
327 throw QgsProcessingException( QObject::tr( "Error loading GLTF model: %1" ).arg( errors ) );
328 }
329 if ( !warnings.isEmpty() )
330 {
331 feedback->pushWarning( warnings );
332 }
333 feedback->pushDebugInfo( QObject::tr( "Found %1 scenes" ).arg( model.scenes.size() ) );
334
335 bool sceneOk = false;
336 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
337 if ( !sceneOk )
338 {
339 throw QgsProcessingException( QObject::tr( "No scenes found in model" ) );
340 }
341
342 const tinygltf::Scene &scene = model.scenes[sceneIndex];
343 feedback->pushDebugInfo( QObject::tr( "Found %1 nodes in default scene [%2]" ).arg( scene.nodes.size() ).arg( sceneIndex ) );
344
345 QSet<int> warnedPrimitiveTypes;
346
347 const QgsVector3D tileTranslationEcef = QgsGltfUtils::extractTileTranslation( model );
348 std::function<void( int nodeIndex, const QMatrix4x4 &transform )> traverseNode;
349 traverseNode =
350 [&model, feedback, &polygonSink, &lineSink, &warnedPrimitiveTypes, &ecefTransform, &tileTranslationEcef, &traverseNode, &parameters, polygonDest, lineDest]( int nodeIndex, const QMatrix4x4 &parentTransform ) {
351 const tinygltf::Node &gltfNode = model.nodes[nodeIndex];
352 std::unique_ptr<QMatrix4x4> gltfLocalTransform = QgsGltfUtils::parseNodeTransform( gltfNode );
353 if ( !parentTransform.isIdentity() )
354 {
355 if ( gltfLocalTransform )
356 *gltfLocalTransform = parentTransform * *gltfLocalTransform;
357 else
358 {
359 gltfLocalTransform = std::make_unique<QMatrix4x4>( parentTransform );
360 }
361 }
362
363 if ( gltfNode.mesh >= 0 )
364 {
365 const tinygltf::Mesh &mesh = model.meshes[gltfNode.mesh];
366 feedback->pushDebugInfo( QObject::tr( "Found %1 primitives in node [%2]" ).arg( mesh.primitives.size() ).arg( nodeIndex ) );
367
368 for ( const tinygltf::Primitive &primitive : mesh.primitives )
369 {
370 switch ( primitive.mode )
371 {
372 case TINYGLTF_MODE_TRIANGLES:
373 {
374 if ( polygonSink )
375 {
376 std::unique_ptr<QgsAbstractGeometry> geometry = extractTriangles( model, primitive, ecefTransform, tileTranslationEcef, gltfLocalTransform.get(), feedback );
377 if ( geometry )
378 {
379 QgsFeature f;
380 f.setGeometry( std::move( geometry ) );
381 if ( !polygonSink->addFeature( f, QgsFeatureSink::FastInsert ) )
382 throw QgsProcessingException( writeFeatureError( polygonSink.get(), parameters, u"OUTPUT_POLYGONS"_s ) );
383 else
384 feedback->featureAddedToSink( u"OUTPUT_POLYGONS"_s );
385 }
386 }
387 break;
388 }
389
390 case TINYGLTF_MODE_LINE:
391 {
392 if ( lineSink )
393 {
394 std::unique_ptr<QgsAbstractGeometry> geometry = extractLines( model, primitive, ecefTransform, tileTranslationEcef, gltfLocalTransform.get(), feedback );
395 if ( geometry )
396 {
397 QgsFeature f;
398 f.setGeometry( std::move( geometry ) );
399 if ( !lineSink->addFeature( f, QgsFeatureSink::FastInsert ) )
400 throw QgsProcessingException( writeFeatureError( lineSink.get(), parameters, u"OUTPUT_LINES"_s ) );
401 else
402 feedback->featureAddedToSink( u"OUTPUT_LINES"_s );
403 }
404 }
405 break;
406 }
407
408 case TINYGLTF_MODE_POINTS:
409 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_POINTS ) )
410 {
411 feedback->reportError( QObject::tr( "Point objects are not supported" ) );
412 warnedPrimitiveTypes.insert( TINYGLTF_MODE_POINTS );
413 }
414 break;
415
416 case TINYGLTF_MODE_LINE_LOOP:
417 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_LINE_LOOP ) )
418 {
419 feedback->reportError( QObject::tr( "Line loops in are not supported" ) );
420 warnedPrimitiveTypes.insert( TINYGLTF_MODE_LINE_LOOP );
421 }
422 break;
423
424 case TINYGLTF_MODE_LINE_STRIP:
425 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_LINE_STRIP ) )
426 {
427 feedback->reportError( QObject::tr( "Line strips in are not supported" ) );
428 warnedPrimitiveTypes.insert( TINYGLTF_MODE_LINE_STRIP );
429 }
430 break;
431
432 case TINYGLTF_MODE_TRIANGLE_STRIP:
433 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_TRIANGLE_STRIP ) )
434 {
435 feedback->reportError( QObject::tr( "Triangular strips are not supported" ) );
436 warnedPrimitiveTypes.insert( TINYGLTF_MODE_TRIANGLE_STRIP );
437 }
438 break;
439
440 case TINYGLTF_MODE_TRIANGLE_FAN:
441 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_TRIANGLE_FAN ) )
442 {
443 feedback->reportError( QObject::tr( "Triangular fans are not supported" ) );
444 warnedPrimitiveTypes.insert( TINYGLTF_MODE_TRIANGLE_FAN );
445 }
446 break;
447
448 default:
449 if ( !warnedPrimitiveTypes.contains( primitive.mode ) )
450 {
451 feedback->reportError( QObject::tr( "Primitive type %1 are not supported" ).arg( primitive.mode ) );
452 warnedPrimitiveTypes.insert( primitive.mode );
453 }
454 break;
455 }
456 }
457 }
458
459 for ( int childNode : gltfNode.children )
460 {
461 traverseNode( childNode, gltfLocalTransform ? *gltfLocalTransform : QMatrix4x4() );
462 }
463 };
464
465 if ( !scene.nodes.empty() )
466 {
467 for ( const int nodeIndex : scene.nodes )
468 {
469 traverseNode( nodeIndex, QMatrix4x4() );
470 }
471 }
472
473 QVariantMap outputs;
474 if ( polygonSink )
475 {
476 polygonSink->finalize();
477 feedback->featureSinkFinalized( u"OUTPUT_POLYGONS"_s );
478 outputs.insert( u"OUTPUT_POLYGONS"_s, polygonDest );
479 }
480 if ( lineSink )
481 {
482 lineSink->finalize();
483 feedback->featureSinkFinalized( u"OUTPUT_LINES"_s );
484 outputs.insert( u"OUTPUT_LINES"_s, lineDest );
485 }
486 return outputs;
487}
488
489
@ VectorPolygon
Vector polygon layers.
Definition qgis.h:3752
@ VectorLine
Vector line layers.
Definition qgis.h:3751
@ File
Parameter is a single file.
Definition qgis.h:4008
@ Y
Y-axis.
Definition qgis.h:2640
@ MultiLineStringZ
MultiLineStringZ.
Definition qgis.h:318
@ MultiPolygonZ
MultiPolygonZ.
Definition qgis.h:319
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:60
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Container of fields for a vector layer.
Definition qgsfields.h:45
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:53
Polygon geometry type.
Definition qgspolygon.h:37
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:33