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