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