QGIS API Documentation 4.1.0-Master (d6fb7a379fb)
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 const QString path = parameterAsFile( parameters, u"INPUT"_s, context );
294
295 const QgsCoordinateReferenceSystem destCrs( u"EPSG:4326"_s );
296 QgsFields fields;
297
298 QString polygonDest;
299 std::unique_ptr<QgsFeatureSink> polygonSink( parameterAsSink( parameters, u"OUTPUT_POLYGONS"_s, context, polygonDest, fields, Qgis::WkbType::MultiPolygonZ, destCrs ) );
300 if ( !polygonSink && parameters.value( u"OUTPUT_POLYGONS"_s ).isValid() )
301 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT_POLYGONS"_s ) );
302 QString lineDest;
303 std::unique_ptr<QgsFeatureSink> lineSink( parameterAsSink( parameters, u"OUTPUT_LINES"_s, context, lineDest, fields, Qgis::WkbType::MultiLineStringZ, destCrs ) );
304 if ( !lineSink && parameters.value( u"OUTPUT_LINES"_s ).isValid() )
305 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT_LINES"_s ) );
306
307 QFile f( path );
308 QByteArray fileContent;
309 if ( f.open( QIODevice::ReadOnly ) )
310 {
311 fileContent = f.readAll();
312 }
313 else
314 {
315 throw QgsProcessingException( QObject::tr( "Could not load source file %1." ).arg( path ) );
316 }
317
318 const QgsCoordinateTransform ecefTransform( QgsCoordinateReferenceSystem( u"EPSG:4978"_s ), destCrs, context.transformContext() );
319
320 tinygltf::Model model;
321 QString errors;
322 QString warnings;
323 if ( !QgsGltfUtils::loadGltfModel( fileContent, model, &errors, &warnings, QFileInfo( path ).absolutePath() ) )
324 {
325 throw QgsProcessingException( QObject::tr( "Error loading GLTF model: %1" ).arg( errors ) );
326 }
327 if ( !warnings.isEmpty() )
328 {
329 feedback->pushWarning( warnings );
330 }
331 feedback->pushDebugInfo( QObject::tr( "Found %1 scenes" ).arg( model.scenes.size() ) );
332
333 bool sceneOk = false;
334 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
335 if ( !sceneOk )
336 {
337 throw QgsProcessingException( QObject::tr( "No scenes found in model" ) );
338 }
339
340 const tinygltf::Scene &scene = model.scenes[sceneIndex];
341 feedback->pushDebugInfo( QObject::tr( "Found %1 nodes in default scene [%2]" ).arg( scene.nodes.size() ).arg( sceneIndex ) );
342
343 QSet<int> warnedPrimitiveTypes;
344
345 const QgsVector3D tileTranslationEcef = QgsGltfUtils::extractTileTranslation( model );
346 std::function<void( int nodeIndex, const QMatrix4x4 &transform )> traverseNode;
347 traverseNode = [&model, feedback, &polygonSink, &lineSink, &warnedPrimitiveTypes, &ecefTransform, &tileTranslationEcef, &traverseNode, &parameters]( int nodeIndex, const QMatrix4x4 &parentTransform ) {
348 const tinygltf::Node &gltfNode = model.nodes[nodeIndex];
349 std::unique_ptr<QMatrix4x4> gltfLocalTransform = QgsGltfUtils::parseNodeTransform( gltfNode );
350 if ( !parentTransform.isIdentity() )
351 {
352 if ( gltfLocalTransform )
353 *gltfLocalTransform = parentTransform * *gltfLocalTransform;
354 else
355 {
356 gltfLocalTransform = std::make_unique<QMatrix4x4>( parentTransform );
357 }
358 }
359
360 if ( gltfNode.mesh >= 0 )
361 {
362 const tinygltf::Mesh &mesh = model.meshes[gltfNode.mesh];
363 feedback->pushDebugInfo( QObject::tr( "Found %1 primitives in node [%2]" ).arg( mesh.primitives.size() ).arg( nodeIndex ) );
364
365 for ( const tinygltf::Primitive &primitive : mesh.primitives )
366 {
367 switch ( primitive.mode )
368 {
369 case TINYGLTF_MODE_TRIANGLES:
370 {
371 if ( polygonSink )
372 {
373 std::unique_ptr<QgsAbstractGeometry> geometry = extractTriangles( model, primitive, ecefTransform, tileTranslationEcef, gltfLocalTransform.get(), feedback );
374 if ( geometry )
375 {
376 QgsFeature f;
377 f.setGeometry( std::move( geometry ) );
378 if ( !polygonSink->addFeature( f, QgsFeatureSink::FastInsert ) )
379 throw QgsProcessingException( writeFeatureError( polygonSink.get(), parameters, u"OUTPUT_POLYGONS"_s ) );
380 }
381 }
382 break;
383 }
384
385 case TINYGLTF_MODE_LINE:
386 {
387 if ( lineSink )
388 {
389 std::unique_ptr<QgsAbstractGeometry> geometry = extractLines( model, primitive, ecefTransform, tileTranslationEcef, gltfLocalTransform.get(), feedback );
390 if ( geometry )
391 {
392 QgsFeature f;
393 f.setGeometry( std::move( geometry ) );
394 if ( !lineSink->addFeature( f, QgsFeatureSink::FastInsert ) )
395 throw QgsProcessingException( writeFeatureError( lineSink.get(), parameters, u"OUTPUT_LINES"_s ) );
396 }
397 }
398 break;
399 }
400
401 case TINYGLTF_MODE_POINTS:
402 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_POINTS ) )
403 {
404 feedback->reportError( QObject::tr( "Point objects are not supported" ) );
405 warnedPrimitiveTypes.insert( TINYGLTF_MODE_POINTS );
406 }
407 break;
408
409 case TINYGLTF_MODE_LINE_LOOP:
410 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_LINE_LOOP ) )
411 {
412 feedback->reportError( QObject::tr( "Line loops in are not supported" ) );
413 warnedPrimitiveTypes.insert( TINYGLTF_MODE_LINE_LOOP );
414 }
415 break;
416
417 case TINYGLTF_MODE_LINE_STRIP:
418 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_LINE_STRIP ) )
419 {
420 feedback->reportError( QObject::tr( "Line strips in are not supported" ) );
421 warnedPrimitiveTypes.insert( TINYGLTF_MODE_LINE_STRIP );
422 }
423 break;
424
425 case TINYGLTF_MODE_TRIANGLE_STRIP:
426 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_TRIANGLE_STRIP ) )
427 {
428 feedback->reportError( QObject::tr( "Triangular strips are not supported" ) );
429 warnedPrimitiveTypes.insert( TINYGLTF_MODE_TRIANGLE_STRIP );
430 }
431 break;
432
433 case TINYGLTF_MODE_TRIANGLE_FAN:
434 if ( !warnedPrimitiveTypes.contains( TINYGLTF_MODE_TRIANGLE_FAN ) )
435 {
436 feedback->reportError( QObject::tr( "Triangular fans are not supported" ) );
437 warnedPrimitiveTypes.insert( TINYGLTF_MODE_TRIANGLE_FAN );
438 }
439 break;
440
441 default:
442 if ( !warnedPrimitiveTypes.contains( primitive.mode ) )
443 {
444 feedback->reportError( QObject::tr( "Primitive type %1 are not supported" ).arg( primitive.mode ) );
445 warnedPrimitiveTypes.insert( primitive.mode );
446 }
447 break;
448 }
449 }
450 }
451
452 for ( int childNode : gltfNode.children )
453 {
454 traverseNode( childNode, gltfLocalTransform ? *gltfLocalTransform : QMatrix4x4() );
455 }
456 };
457
458 if ( !scene.nodes.empty() )
459 {
460 for ( const int nodeIndex : scene.nodes )
461 {
462 traverseNode( nodeIndex, QMatrix4x4() );
463 }
464 }
465
466 QVariantMap outputs;
467 if ( polygonSink )
468 {
469 polygonSink->finalize();
470 outputs.insert( u"OUTPUT_POLYGONS"_s, polygonDest );
471 }
472 if ( lineSink )
473 {
474 lineSink->finalize();
475 outputs.insert( u"OUTPUT_LINES"_s, lineDest );
476 }
477 return outputs;
478}
479
480
@ VectorPolygon
Vector polygon layers.
Definition qgis.h:3717
@ VectorLine
Vector line layers.
Definition qgis.h:3716
@ File
Parameter is a single file.
Definition qgis.h:3973
@ Y
Y-axis.
Definition qgis.h:2609
@ 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