QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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(
85 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 )
86 );
87
88 addParameter( new QgsProcessingParameterFeatureSink( u"OUTPUT_POLYGONS"_s, QObject::tr( "Output polygons" ), Qgis::ProcessingSourceType::VectorPolygon, QVariant(), true, true ) );
89 addParameter( new QgsProcessingParameterFeatureSink( u"OUTPUT_LINES"_s, QObject::tr( "Output lines" ), Qgis::ProcessingSourceType::VectorLine, QVariant(), true, true ) );
90}
91
92std::unique_ptr<QgsAbstractGeometry> extractTriangles(
93 const tinygltf::Model &model,
94 const tinygltf::Primitive &primitive,
95 const QgsCoordinateTransform &ecefTransform,
96 const QgsVector3D &tileTranslationEcef,
97 const QMatrix4x4 *gltfLocalTransform,
98 QgsProcessingFeedback *feedback
99)
100{
101 auto posIt = primitive.attributes.find( "POSITION" );
102 if ( posIt == primitive.attributes.end() )
103 {
104 feedback->reportError( QObject::tr( "Could not find POSITION attribute for primitive" ) );
105 return nullptr;
106 }
107 int positionAccessorIndex = posIt->second;
108
109 QVector<double> x;
110 QVector<double> y;
111 QVector<double> z;
112 QgsGltfUtils::accessorToMapCoordinates( model, positionAccessorIndex, QgsMatrix4x4(), &ecefTransform, tileTranslationEcef, gltfLocalTransform, Qgis::Axis::Y, x, y, z );
113
114 auto mp = std::make_unique<QgsMultiPolygon>();
115
116 if ( primitive.indices == -1 )
117 {
118 Q_ASSERT( x.size() % 3 == 0 );
119
120 mp->reserve( x.size() );
121 for ( int i = 0; i < x.size(); i += 3 )
122 {
123 mp->addGeometry( new QgsPolygon(
124 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] ) } )
125 ) );
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(
135 ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT
136 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT
137 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
138 && primitiveAccessor.type == TINYGLTF_TYPE_SCALAR
139 );
140
141 const char *primitivePtr = reinterpret_cast<const char *>( bPrimitive.data.data() ) + bvPrimitive.byteOffset + primitiveAccessor.byteOffset;
142
143 mp->reserve( primitiveAccessor.count / 3 );
144 for ( std::size_t i = 0; i < primitiveAccessor.count / 3; i++ )
145 {
146 unsigned int index1 = 0;
147 unsigned int index2 = 0;
148 unsigned int index3 = 0;
149
150 if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT )
151 {
152 const unsigned short *usPtrPrimitive = reinterpret_cast<const unsigned short *>( primitivePtr );
153 if ( bvPrimitive.byteStride )
154 primitivePtr += bvPrimitive.byteStride;
155 else
156 primitivePtr += 3 * sizeof( unsigned short );
157
158 index1 = usPtrPrimitive[0];
159 index2 = usPtrPrimitive[1];
160 index3 = usPtrPrimitive[2];
161 }
162 else if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
163 {
164 const unsigned char *usPtrPrimitive = reinterpret_cast<const unsigned char *>( primitivePtr );
165 if ( bvPrimitive.byteStride )
166 primitivePtr += bvPrimitive.byteStride;
167 else
168 primitivePtr += 3 * sizeof( unsigned char );
169
170 index1 = usPtrPrimitive[0];
171 index2 = usPtrPrimitive[1];
172 index3 = usPtrPrimitive[2];
173 }
174 else
175 {
176 const unsigned int *uintPtrPrimitive = reinterpret_cast<const unsigned int *>( primitivePtr );
177 if ( bvPrimitive.byteStride )
178 primitivePtr += bvPrimitive.byteStride;
179 else
180 primitivePtr += 3 * sizeof( unsigned int );
181
182 index1 = uintPtrPrimitive[0];
183 index2 = uintPtrPrimitive[1];
184 index3 = uintPtrPrimitive[2];
185 }
186
187 mp->addGeometry( new QgsPolygon( new QgsLineString(
188 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] ) }
189 ) ) );
190 }
191 }
192 return mp;
193}
194
195std::unique_ptr<QgsAbstractGeometry> extractLines(
196 const tinygltf::Model &model,
197 const tinygltf::Primitive &primitive,
198 const QgsCoordinateTransform &ecefTransform,
199 const QgsVector3D &tileTranslationEcef,
200 const QMatrix4x4 *gltfLocalTransform,
201 QgsProcessingFeedback *feedback
202)
203{
204 auto posIt = primitive.attributes.find( "POSITION" );
205 if ( posIt == primitive.attributes.end() )
206 {
207 feedback->reportError( QObject::tr( "Could not find POSITION attribute for primitive" ) );
208 return nullptr;
209 }
210 int positionAccessorIndex = posIt->second;
211
212 QVector<double> x;
213 QVector<double> y;
214 QVector<double> z;
215 QgsGltfUtils::accessorToMapCoordinates( model, positionAccessorIndex, QgsMatrix4x4(), &ecefTransform, tileTranslationEcef, gltfLocalTransform, Qgis::Axis::Y, x, y, z );
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(
236 ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT
237 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT
238 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
239 && primitiveAccessor.type == TINYGLTF_TYPE_SCALAR
240 );
241
242 const char *primitivePtr = reinterpret_cast<const char *>( bPrimitive.data.data() ) + bvPrimitive.byteOffset + primitiveAccessor.byteOffset;
243
244 ml->reserve( primitiveAccessor.count / 2 );
245 for ( std::size_t i = 0; i < primitiveAccessor.count / 2; i++ )
246 {
247 unsigned int index1 = 0;
248 unsigned int index2 = 0;
249
250 if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT )
251 {
252 const unsigned short *usPtrPrimitive = reinterpret_cast<const unsigned short *>( primitivePtr );
253 if ( bvPrimitive.byteStride )
254 primitivePtr += bvPrimitive.byteStride;
255 else
256 primitivePtr += 2 * sizeof( unsigned short );
257
258 index1 = usPtrPrimitive[0];
259 index2 = usPtrPrimitive[1];
260 }
261 else if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
262 {
263 const unsigned char *usPtrPrimitive = reinterpret_cast<const unsigned char *>( primitivePtr );
264 if ( bvPrimitive.byteStride )
265 primitivePtr += bvPrimitive.byteStride;
266 else
267 primitivePtr += 2 * sizeof( unsigned char );
268
269 index1 = usPtrPrimitive[0];
270 index2 = usPtrPrimitive[1];
271 }
272 else
273 {
274 const unsigned int *uintPtrPrimitive = reinterpret_cast<const unsigned int *>( primitivePtr );
275 if ( bvPrimitive.byteStride )
276 primitivePtr += bvPrimitive.byteStride;
277 else
278 primitivePtr += 2 * sizeof( unsigned int );
279
280 index1 = uintPtrPrimitive[0];
281 index2 = uintPtrPrimitive[1];
282 }
283
284 ml->addGeometry( new QgsLineString( QVector<QgsPoint> { QgsPoint( x[index1], y[index1], z[index1] ), QgsPoint( x[index2], y[index2], z[index2] ) } ) );
285 }
286 }
287 return ml;
288}
289
290QVariantMap QgsGltfToVectorFeaturesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
291{
292 const QString path = parameterAsFile( parameters, u"INPUT"_s, context );
293
294 const QgsCoordinateReferenceSystem destCrs( u"EPSG:4326"_s );
295 QgsFields fields;
296
297 QString polygonDest;
298 std::unique_ptr<QgsFeatureSink> polygonSink( parameterAsSink( parameters, u"OUTPUT_POLYGONS"_s, context, polygonDest, fields, Qgis::WkbType::MultiPolygonZ, destCrs ) );
299 if ( !polygonSink && parameters.value( u"OUTPUT_POLYGONS"_s ).isValid() )
300 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT_POLYGONS"_s ) );
301 QString lineDest;
302 std::unique_ptr<QgsFeatureSink> lineSink( parameterAsSink( parameters, u"OUTPUT_LINES"_s, context, lineDest, fields, Qgis::WkbType::MultiLineStringZ, destCrs ) );
303 if ( !lineSink && parameters.value( u"OUTPUT_LINES"_s ).isValid() )
304 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT_LINES"_s ) );
305
306 QFile f( path );
307 QByteArray fileContent;
308 if ( f.open( QIODevice::ReadOnly ) )
309 {
310 fileContent = f.readAll();
311 }
312 else
313 {
314 throw QgsProcessingException( QObject::tr( "Could not load source file %1." ).arg( path ) );
315 }
316
317 const QgsCoordinateTransform ecefTransform( QgsCoordinateReferenceSystem( u"EPSG:4978"_s ), destCrs, context.transformContext() );
318
319 tinygltf::Model model;
320 QString errors;
321 QString warnings;
322 if ( !QgsGltfUtils::loadGltfModel( fileContent, model, &errors, &warnings ) )
323 {
324 throw QgsProcessingException( QObject::tr( "Error loading GLTF model: %1" ).arg( errors ) );
325 }
326 if ( !warnings.isEmpty() )
327 {
328 feedback->pushWarning( warnings );
329 }
330 feedback->pushDebugInfo( QObject::tr( "Found %1 scenes" ).arg( model.scenes.size() ) );
331
332 bool sceneOk = false;
333 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
334 if ( !sceneOk )
335 {
336 throw QgsProcessingException( QObject::tr( "No scenes found in model" ) );
337 }
338
339 const tinygltf::Scene &scene = model.scenes[sceneIndex];
340 feedback->pushDebugInfo( QObject::tr( "Found %1 nodes in default scene [%2]" ).arg( scene.nodes.size() ).arg( sceneIndex ) );
341
342 QSet<int> warnedPrimitiveTypes;
343
344 const QgsVector3D tileTranslationEcef = QgsGltfUtils::extractTileTranslation( model );
345 std::function<void( int nodeIndex, const QMatrix4x4 &transform )> traverseNode;
346 traverseNode = [&model, feedback, &polygonSink, &lineSink, &warnedPrimitiveTypes, &ecefTransform, &tileTranslationEcef, &traverseNode, &parameters]( int nodeIndex, const QMatrix4x4 &parentTransform ) {
347 const tinygltf::Node &gltfNode = model.nodes[nodeIndex];
348 std::unique_ptr<QMatrix4x4> gltfLocalTransform = QgsGltfUtils::parseNodeTransform( gltfNode );
349 if ( !parentTransform.isIdentity() )
350 {
351 if ( gltfLocalTransform )
352 *gltfLocalTransform = parentTransform * *gltfLocalTransform;
353 else
354 {
355 gltfLocalTransform = std::make_unique<QMatrix4x4>( parentTransform );
356 }
357 }
358
359 if ( gltfNode.mesh >= 0 )
360 {
361 const tinygltf::Mesh &mesh = model.meshes[gltfNode.mesh];
362 feedback->pushDebugInfo( QObject::tr( "Found %1 primitives in node [%2]" ).arg( mesh.primitives.size() ).arg( nodeIndex ) );
363
364 for ( const tinygltf::Primitive &primitive : mesh.primitives )
365 {
366 switch ( primitive.mode )
367 {
368 case TINYGLTF_MODE_TRIANGLES:
369 {
370 if ( polygonSink )
371 {
372 std::unique_ptr<QgsAbstractGeometry> geometry = extractTriangles( model, primitive, ecefTransform, tileTranslationEcef, gltfLocalTransform.get(), feedback );
373 if ( geometry )
374 {
375 QgsFeature f;
376 f.setGeometry( std::move( geometry ) );
377 if ( !polygonSink->addFeature( f, QgsFeatureSink::FastInsert ) )
378 throw QgsProcessingException( writeFeatureError( polygonSink.get(), parameters, u"OUTPUT_POLYGONS"_s ) );
379 }
380 }
381 break;
382 }
383
384 case TINYGLTF_MODE_LINE:
385 {
386 if ( lineSink )
387 {
388 std::unique_ptr<QgsAbstractGeometry> geometry = extractLines( model, primitive, ecefTransform, tileTranslationEcef, gltfLocalTransform.get(), feedback );
389 if ( geometry )
390 {
391 QgsFeature f;
392 f.setGeometry( std::move( geometry ) );
393 if ( !lineSink->addFeature( f, QgsFeatureSink::FastInsert ) )
394 throw QgsProcessingException( writeFeatureError( lineSink.get(), parameters, u"OUTPUT_LINES"_s ) );
395 }
396 }
397 break;
398 }
399
400 case TINYGLTF_MODE_POINTS:
401 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_POINTS ) )
402 {
403 feedback->reportError( QObject::tr( "Point objects are not supported" ) );
404 warnedPrimitiveTypes.insert( TINYGLTF_MODE_POINTS );
405 }
406 break;
407
408 case TINYGLTF_MODE_LINE_LOOP:
409 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_LINE_LOOP ) )
410 {
411 feedback->reportError( QObject::tr( "Line loops in are not supported" ) );
412 warnedPrimitiveTypes.insert( TINYGLTF_MODE_LINE_LOOP );
413 }
414 break;
415
416 case TINYGLTF_MODE_LINE_STRIP:
417 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_LINE_STRIP ) )
418 {
419 feedback->reportError( QObject::tr( "Line strips in are not supported" ) );
420 warnedPrimitiveTypes.insert( TINYGLTF_MODE_LINE_STRIP );
421 }
422 break;
423
424 case TINYGLTF_MODE_TRIANGLE_STRIP:
425 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_TRIANGLE_STRIP ) )
426 {
427 feedback->reportError( QObject::tr( "Triangular strips are not supported" ) );
428 warnedPrimitiveTypes.insert( TINYGLTF_MODE_TRIANGLE_STRIP );
429 }
430 break;
431
432 case TINYGLTF_MODE_TRIANGLE_FAN:
433 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_TRIANGLE_FAN ) )
434 {
435 feedback->reportError( QObject::tr( "Triangular fans are not supported" ) );
436 warnedPrimitiveTypes.insert( TINYGLTF_MODE_TRIANGLE_FAN );
437 }
438 break;
439
440 default:
441 if ( !warnedPrimitiveTypes.contains( primitive.mode ) )
442 {
443 feedback->reportError( QObject::tr( "Primitive type %1 are not supported" ).arg( primitive.mode ) );
444 warnedPrimitiveTypes.insert( primitive.mode );
445 }
446 break;
447 }
448 }
449 }
450
451 for ( int childNode : gltfNode.children )
452 {
453 traverseNode( childNode, gltfLocalTransform ? *gltfLocalTransform : QMatrix4x4() );
454 }
455 };
456
457 if ( !scene.nodes.empty() )
458 {
459 for ( const int nodeIndex : scene.nodes )
460 {
461 traverseNode( nodeIndex, QMatrix4x4() );
462 }
463 }
464
465 QVariantMap outputs;
466 if ( polygonSink )
467 {
468 polygonSink->finalize();
469 outputs.insert( u"OUTPUT_POLYGONS"_s, polygonDest );
470 }
471 if ( lineSink )
472 {
473 lineSink->finalize();
474 outputs.insert( u"OUTPUT_LINES"_s, lineDest );
475 }
476 return outputs;
477}
478
479
@ VectorPolygon
Vector polygon layers.
Definition qgis.h:3650
@ VectorLine
Vector line layers.
Definition qgis.h:3649
@ File
Parameter is a single file.
Definition qgis.h:3906
@ Y
Y-axis.
Definition qgis.h:2542
@ 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: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