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