QGIS API Documentation 4.1.0-Master (64029b4150b)
Loading...
Searching...
No Matches
qgssfcgalengine.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgssfcgalengine.cpp
3 ----------------
4 begin : May 2025
5 copyright : (C) 2025 by Oslandia
6 email : benoit dot de dot mezzo at oslandia 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
18#ifdef WITH_SFCGAL
19#include "qgssfcgalengine.h"
20
21#include <SFCGAL/capi/sfcgal_c.h>
22#include <nlohmann/json.hpp>
23
24#include "qgsgeometry.h"
25#include "qgsgeometryfactory.h"
26#include "qgssfcgalgeometry.h"
27
28#include <QString>
29
30using namespace Qt::StringLiterals;
31
32// ===================================
33// sfcgal namespace
34// ===================================
35
36thread_local sfcgal::ErrorHandler sSfcgalErrorHandler;
37
38sfcgal::ErrorHandler *sfcgal::errorHandler()
39{
40 return &sSfcgalErrorHandler;
41}
42
43void sfcgal::GeometryDeleter::operator()( sfcgal::geometry *geom ) const
44{
45 sfcgal_geometry_delete( geom );
46}
47
48sfcgal::shared_geom sfcgal::make_shared_geom( sfcgal::geometry *geom )
49{
50 return sfcgal::shared_geom( geom, sfcgal::GeometryDeleter() );
51}
52
53
54#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 3, 0 )
55void sfcgal::PrimitiveDeleter::operator()( sfcgal::primitive *prim ) const
56{
57 sfcgal_primitive_delete( prim );
58}
59
60sfcgal::shared_prim sfcgal::make_shared_prim( sfcgal::primitive *prim )
61{
62 return sfcgal::shared_prim( prim, sfcgal::PrimitiveDeleter() );
63}
64#endif
65
66bool sfcgal::ErrorHandler::hasSucceedOrStack( QString *errorMsg, const std::source_location &location )
67{
68 bool succeed = isTextEmpty();
69 if ( !succeed )
70 {
71 addText( "relaying error from: ", location );
72 if ( errorMsg )
73 {
74 errorMsg->append( errorMessages.first() );
75 }
76 }
77 return succeed;
78}
79
80int sfcgal::errorCallback( const char *fmt, ... )
81{
82 va_list ap;
83 char buffer[1024];
84
85 va_start( ap, fmt );
86 vsnprintf( buffer, sizeof buffer, fmt, ap );
87 va_end( ap );
88
89 sfcgal::errorHandler()->addText( u"SFCGAL error occurred: %1"_s.arg( buffer ) );
90
91 return static_cast<int>( strlen( buffer ) );
92}
93
94int sfcgal::warningCallback( const char *fmt, ... )
95{
96 va_list ap;
97 char buffer[1024];
98
99 va_start( ap, fmt );
100 vsnprintf( buffer, sizeof buffer, fmt, ap );
101 va_end( ap );
102
103 sfcgal::errorHandler()->addText( u"SFCGAL warning occurred: %1"_s.arg( buffer ) );
104
105 return static_cast<int>( strlen( buffer ) );
106}
107
108
109sfcgal::ErrorHandler::ErrorHandler()
110{
111 sfcgal_init(); // empty but called
112 sfcgal_set_error_handlers( sfcgal::warningCallback, sfcgal::errorCallback );
113}
114
115void sfcgal::ErrorHandler::clearText( QString *errorMsg )
116{
117 errorMessages.clear();
118 if ( errorMsg )
119 {
120 errorMsg->clear();
121 }
122}
123
124QString sfcgal::ErrorHandler::getMainText() const
125{
126 return errorMessages.isEmpty() ? QString() : QString( "Error occurred: " ) + errorMessages.last();
127}
128
129QString sfcgal::ErrorHandler::getFullText() const
130{
131 return errorMessages.isEmpty() ? QString() : getMainText() + "\n\t\t" + errorMessages.join( "\n\t\t" );
132}
133
134bool sfcgal::ErrorHandler::isTextEmpty() const
135{
136 return errorMessages.isEmpty();
137}
138
139void sfcgal::ErrorHandler::addText( const QString &msg, const std::source_location &location )
140{
141 QString txt = QString( "%2 (%3:%4) %1" )
142 .arg( msg ) //
143 .arg( QString::fromStdString( location.function_name() ) ) //
144 .arg( QString::fromStdString( location.file_name() ) ) //
145 .arg( location.line() );
146
147 errorMessages.push_front( txt );
148}
149
150// ===================================
151// QgsSfcgalEngine static functions
152// ===================================
153
162template<typename T> static T geom_to_primtype( T ( *func_2d )( const sfcgal_geometry_t * ), T ( *func_3d )( const sfcgal_geometry_t * ), const sfcgal::geometry *geom, QString *errorMsg )
163{
164 sfcgal::errorHandler()->clearText( errorMsg );
165 CHECK_NOT_NULL( geom, std::numeric_limits<T>::quiet_NaN() );
166
167 T result;
168 if ( func_3d && sfcgal_geometry_is_3d( geom ) )
169 result = func_3d( geom );
170 else
171 result = func_2d( geom );
172
173 CHECK_SUCCESS( errorMsg, std::numeric_limits<T>::quiet_NaN() );
174
175 return result;
176};
177
187template<typename T> static T geomgeom_to_primtype(
188 T ( *func_2d )( const sfcgal_geometry_t *, const sfcgal_geometry_t * ),
189 T ( *func_3d )( const sfcgal_geometry_t *, const sfcgal_geometry_t * ),
190 const sfcgal::geometry *geomA,
191 const sfcgal::geometry *geomB,
192 QString *errorMsg
193)
194{
195 sfcgal::errorHandler()->clearText( errorMsg );
196 CHECK_NOT_NULL( geomA, false );
197 CHECK_NOT_NULL( geomB, false );
198
199 T result;
200 if ( func_3d && ( sfcgal_geometry_is_3d( geomA ) || sfcgal_geometry_is_3d( geomB ) ) )
201 result = func_3d( geomA, geomB );
202 else
203 result = func_2d( geomA, geomB );
204
205 CHECK_SUCCESS( errorMsg, std::numeric_limits<T>::quiet_NaN() );
206
207 return result;
208};
209
218static sfcgal::shared_geom geom_to_geom( sfcgal::func_geom_to_geom func_2d, sfcgal::func_geom_to_geom func_3d, const sfcgal::geometry *geom, QString *errorMsg )
219{
220 sfcgal::errorHandler()->clearText( errorMsg );
221 CHECK_NOT_NULL( geom, nullptr );
222
223 sfcgal::geometry *result = nullptr;
224 if ( func_3d && sfcgal_geometry_is_3d( geom ) )
225 result = func_3d( geom );
226 else
227 result = func_2d( geom );
228
229 CHECK_SUCCESS( errorMsg, nullptr );
230 CHECK_NOT_NULL( result, nullptr );
231
232 return sfcgal::make_shared_geom( result );
233};
234
235
245static sfcgal::shared_geom geomgeom_to_geom( sfcgal::func_geomgeom_to_geom func_2d, sfcgal::func_geomgeom_to_geom func_3d, const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
246{
247 sfcgal::errorHandler()->clearText( errorMsg );
248 CHECK_NOT_NULL( geomA, nullptr );
249 CHECK_NOT_NULL( geomB, nullptr );
250
251 sfcgal::geometry *result = nullptr;
252 if ( func_3d && ( sfcgal_geometry_is_3d( geomA ) || sfcgal_geometry_is_3d( geomB ) ) )
253 result = func_3d( geomA, geomB );
254 else
255 result = func_2d( geomA, geomB );
256
257 CHECK_SUCCESS( errorMsg, nullptr );
258 CHECK_NOT_NULL( result, nullptr );
259
260 return sfcgal::make_shared_geom( result );
261};
262
263
264// ===================================
265// QgsSfcgalEngine class
266// ===================================
267
268
269std::unique_ptr<QgsSfcgalGeometry> QgsSfcgalEngine::toSfcgalGeometry( sfcgal::shared_geom &geom, QString *errorMsg )
270{
271 sfcgal::errorHandler()->clearText( errorMsg );
272 CHECK_NOT_NULL( geom.get(), nullptr );
273
274 return std::make_unique<QgsSfcgalGeometry>( geom );
275}
276
277std::unique_ptr<QgsAbstractGeometry> QgsSfcgalEngine::toAbstractGeometry( const sfcgal::geometry *geom, QString *errorMsg )
278{
279 std::unique_ptr<QgsAbstractGeometry> out( nullptr );
280 sfcgal::errorHandler()->clearText( errorMsg );
281 CHECK_NOT_NULL( geom, out );
282
283 QByteArray wkbArray = QgsSfcgalEngine::toWkb( geom, errorMsg );
284 CHECK_SUCCESS( errorMsg, out );
285
286 QgsConstWkbPtr wkbPtr( wkbArray );
287 out = QgsGeometryFactory::geomFromWkb( wkbPtr );
288 if ( !out )
289 {
290 Qgis::WkbType sfcgalType = QgsSfcgalEngine::wkbType( geom );
291 sfcgal::errorHandler()->addText(
292 u"WKB contains unmanaged geometry type (WKB:%1 / SFCGAL:%2"_s //
293 .arg( static_cast<int>( wkbPtr.readHeader() ) ) //
294 .arg( static_cast<int>( sfcgalType ) )
295 );
296 }
297
298 return out;
299}
300
301sfcgal::shared_geom QgsSfcgalEngine::fromAbstractGeometry( const QgsAbstractGeometry *geom, QString *errorMsg )
302{
303 sfcgal::errorHandler()->clearText( errorMsg );
304 CHECK_NOT_NULL( geom, sfcgal::shared_geom( nullptr ) );
305
306 QByteArray wkbBytes = geom->asWkb();
307
308 sfcgal::geometry *out = sfcgal_io_read_wkb( wkbBytes.data(), wkbBytes.length() );
309 CHECK_SUCCESS( errorMsg, nullptr );
310
311 return sfcgal::make_shared_geom( out );
312}
313
314sfcgal::shared_geom QgsSfcgalEngine::cloneGeometry( const sfcgal::geometry *geom, QString *errorMsg )
315{
316 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_clone, nullptr, geom, errorMsg );
317 CHECK_SUCCESS( errorMsg, nullptr );
318 return out;
319}
320
321QString QgsSfcgalEngine::geometryType( const sfcgal::geometry *geom, QString *errorMsg )
322{
323#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
324 ( void ) geom;
325 ( void ) errorMsg;
326 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "geometryType" ) );
327#else
328 sfcgal::errorHandler()->clearText( errorMsg );
329
330 char *typeChar;
331 size_t typeLen;
332 sfcgal_geometry_type( geom, &typeChar, &typeLen );
333 std::string typeStr( typeChar, typeLen );
334 sfcgal_free_buffer( typeChar );
335
336 return QString::fromStdString( typeStr );
337#endif
338}
339
340sfcgal::shared_geom QgsSfcgalEngine::fromWkb( const QgsConstWkbPtr &wkbPtr, QString *errorMsg )
341{
342 sfcgal::errorHandler()->clearText( errorMsg );
343
344 const unsigned char *wkbUnsignedPtr = wkbPtr;
345 sfcgal::geometry *out = sfcgal_io_read_wkb( reinterpret_cast<const char *>( wkbUnsignedPtr ), wkbPtr.remaining() );
346 CHECK_SUCCESS( errorMsg, nullptr );
347
348 return sfcgal::make_shared_geom( out );
349}
350
351sfcgal::shared_geom QgsSfcgalEngine::fromWkt( const QString &wkt, QString *errorMsg )
352{
353 sfcgal::errorHandler()->clearText( errorMsg );
354
355 sfcgal::geometry *out = sfcgal_io_read_wkt( wkt.toStdString().c_str(), wkt.length() );
356 CHECK_SUCCESS( errorMsg, nullptr );
357
358 return sfcgal::unique_geom( out );
359}
360
361QByteArray QgsSfcgalEngine::toWkb( const sfcgal::geometry *geom, QString *errorMsg )
362{
363 sfcgal::errorHandler()->clearText( errorMsg );
364 CHECK_NOT_NULL( geom, QByteArray() );
365
366 char *wkbHex;
367 size_t len = 0;
368 sfcgal_geometry_as_wkb( geom, &wkbHex, &len );
369 CHECK_SUCCESS( errorMsg, QByteArray() );
370 QByteArray wkbArray( wkbHex, static_cast<int>( len ) );
371
372#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
373 sfcgal_free_buffer( wkbHex );
374#else
375 free( wkbHex );
376#endif
377
378 return wkbArray;
379}
380
381QString QgsSfcgalEngine::toWkt( const sfcgal::geometry *geom, int numDecimals, QString *errorMsg )
382{
383 sfcgal::errorHandler()->clearText( errorMsg );
384 CHECK_NOT_NULL( geom, QString() );
385
386 char *wkt;
387 size_t len = 0;
388 sfcgal_geometry_as_text_decim( geom, numDecimals, &wkt, &len );
389 CHECK_SUCCESS( errorMsg, QString() );
390
391 std::string wktString( wkt, len );
392#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
393 sfcgal_free_buffer( wkt );
394#else
395 free( wkt );
396#endif
397 return QString::fromStdString( wktString );
398}
399
400Qgis::WkbType QgsSfcgalEngine::wkbType( const sfcgal::geometry *geom, QString *errorMsg )
401{
402 sfcgal::errorHandler()->clearText( errorMsg );
403 CHECK_NOT_NULL( geom, Qgis::WkbType::Unknown );
404
405 sfcgal_geometry_type_t type = sfcgal_geometry_type_id( geom );
406 CHECK_SUCCESS( errorMsg, Qgis::WkbType::Unknown );
407
408 int wkbType = type;
409 if ( sfcgal_geometry_is_3d( geom ) )
410 wkbType += 1000;
411
412 if ( sfcgal_geometry_is_measured( geom ) )
413 wkbType += 2000;
414
415 Qgis::WkbType qgisType = static_cast<Qgis::WkbType>( wkbType );
416 if ( qgisType >= Qgis::WkbType::Unknown && qgisType <= Qgis::WkbType::TriangleZM )
417 return qgisType;
418
419 sfcgal::errorHandler()->addText( u"WKB type '%1' is not known from QGIS"_s.arg( wkbType ) );
421}
422
423int QgsSfcgalEngine::dimension( const sfcgal::geometry *geom, QString *errorMsg )
424{
425#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
426 ( void ) geom;
427 ( void ) errorMsg;
428 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "dimension" ) );
429#else
430 int out = geom_to_primtype<int>( sfcgal_geometry_dimension, nullptr, geom, errorMsg );
431 CHECK_SUCCESS( errorMsg, std::numeric_limits<int>::quiet_NaN() );
432 return out;
433#endif
434}
435
436int QgsSfcgalEngine::partCount( const sfcgal::geometry *geom, QString *errorMsg )
437{
438 size_t out;
439 sfcgal::errorHandler()->clearText( errorMsg );
440 CHECK_NOT_NULL( geom, -1 );
441
442 sfcgal_geometry_type_t type = sfcgal_geometry_type_id( geom );
443 CHECK_SUCCESS( errorMsg, -1 );
444
445 switch ( type )
446 {
447 case SFCGAL_TYPE_MULTIPOINT:
448 case SFCGAL_TYPE_MULTILINESTRING:
449 case SFCGAL_TYPE_MULTIPOLYGON:
450 case SFCGAL_TYPE_MULTISOLID:
451 case SFCGAL_TYPE_GEOMETRYCOLLECTION:
452#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
453 out = sfcgal_geometry_num_geometries( geom );
454#else
455 out = sfcgal_geometry_collection_num_geometries( geom );
456#endif
457 break;
458 case SFCGAL_TYPE_POLYGON:
459 out = sfcgal_polygon_num_interior_rings( geom ) + 1;
460 break;
461 case SFCGAL_TYPE_SOLID:
462 out = sfcgal_solid_num_shells( geom );
463 break;
464 case SFCGAL_TYPE_POLYHEDRALSURFACE:
465#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
466 out = sfcgal_polyhedral_surface_num_patches( geom );
467#else
468 out = sfcgal_polyhedral_surface_num_polygons( geom );
469#endif
470 break;
471 case SFCGAL_TYPE_TRIANGULATEDSURFACE:
472#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
473 out = sfcgal_triangulated_surface_num_patches( geom );
474#else
475 out = sfcgal_triangulated_surface_num_triangles( geom );
476#endif
477 break;
478 case SFCGAL_TYPE_LINESTRING:
479 out = sfcgal_linestring_num_points( geom );
480 break;
481 case SFCGAL_TYPE_TRIANGLE:
482 out = 3;
483 break;
484 case SFCGAL_TYPE_POINT:
485 out = 1;
486 break;
487 default:
488 out = -1;
489 }
490
491 CHECK_SUCCESS( errorMsg, -1 );
492
493 return static_cast<int>( out );
494}
495
496bool QgsSfcgalEngine::addZValue( sfcgal::geometry *geom, double zValue, QString *errorMsg )
497{
498#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
499 ( void ) geom;
500 ( void ) zValue;
501 ( void ) errorMsg;
502 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "addZValue" ) );
503#else
504 sfcgal::errorHandler()->clearText( errorMsg );
505 CHECK_NOT_NULL( geom, false );
506
507 return sfcgal_geometry_force_z( geom, zValue );
508#endif
509}
510
511bool QgsSfcgalEngine::addMValue( sfcgal::geometry *geom, double mValue, QString *errorMsg )
512{
513#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
514 ( void ) geom;
515 ( void ) mValue;
516 ( void ) errorMsg;
517 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "addMValue" ) );
518#else
519 sfcgal::errorHandler()->clearText( errorMsg );
520 CHECK_NOT_NULL( geom, false );
521
522 return sfcgal_geometry_force_m( geom, mValue );
523#endif
524}
525
526bool QgsSfcgalEngine::dropZValue( sfcgal::geometry *geom, QString *errorMsg )
527{
528#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
529 ( void ) geom;
530 ( void ) errorMsg;
531 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "dropZValue" ) );
532#else
533 sfcgal::errorHandler()->clearText( errorMsg );
534 CHECK_NOT_NULL( geom, false );
535
536 return sfcgal_geometry_drop_z( geom );
537#endif
538}
539
540bool QgsSfcgalEngine::dropMValue( sfcgal::geometry *geom, QString *errorMsg )
541{
542#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
543 ( void ) geom;
544 ( void ) errorMsg;
545 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "dropMValue" ) );
546#else
547 sfcgal::errorHandler()->clearText( errorMsg );
548 CHECK_NOT_NULL( geom, false );
549
550 return sfcgal_geometry_drop_m( geom );
551#endif
552}
553
554void QgsSfcgalEngine::swapXy( sfcgal::geometry *geom, QString *errorMsg )
555{
556#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
557 ( void ) geom;
558 ( void ) errorMsg;
559 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "swapXy" ) );
560#else
561 sfcgal::errorHandler()->clearText( errorMsg );
562 CHECK_NOT_NULL( geom, void() );
563
564 sfcgal_geometry_swap_xy( geom );
565#endif
566}
567
568bool QgsSfcgalEngine::isEqual( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, double tolerance, QString *errorMsg )
569{
570#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
571 ( void ) geomA;
572 ( void ) geomB;
573 ( void ) tolerance;
574 ( void ) errorMsg;
575 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "isEqual" ) );
576#else
577 sfcgal::errorHandler()->clearText( errorMsg );
578 CHECK_NOT_NULL( geomA, false );
579 CHECK_NOT_NULL( geomB, false );
580
581 bool result = sfcgal_geometry_is_almost_equals( geomA, geomB, tolerance );
582 CHECK_SUCCESS( errorMsg, false );
583
584 return result;
585#endif
586}
587
588bool QgsSfcgalEngine::isEmpty( const sfcgal::geometry *geom, QString *errorMsg )
589{
590 int res = geom_to_primtype<int>( sfcgal_geometry_is_empty, nullptr, geom, errorMsg );
591 CHECK_SUCCESS( errorMsg, false );
592 return static_cast<bool>( res );
593}
594
595bool QgsSfcgalEngine::isValid( const sfcgal::geometry *geom, QString *errorMsg, QgsGeometry *errorLoc )
596{
597 sfcgal::errorHandler()->clearText( errorMsg );
598 CHECK_NOT_NULL( geom, false );
599
600 bool result = false;
601 char *reason;
602 sfcgal::geometry *location;
603 result = sfcgal_geometry_is_valid_detail( geom, &reason, &location );
604
605 CHECK_SUCCESS( errorMsg, false );
606
607 if ( reason && strlen( reason ) )
608 {
609 sfcgal::errorHandler()->addText( QString( reason ) );
610 free( reason );
611 }
612
613 if ( location && errorLoc )
614 {
615 std::unique_ptr<QgsAbstractGeometry> locationGeom = toAbstractGeometry( location, errorMsg );
616 CHECK_SUCCESS( errorMsg, false );
617 errorLoc->addPartV2( locationGeom.release() );
618 }
619
620 return result;
621}
622
623bool QgsSfcgalEngine::isSimple( const sfcgal::geometry *geom, QString *errorMsg )
624{
625#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
626 ( void ) geom;
627 ( void ) errorMsg;
628 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "isSimple" ) );
629#else
630 int res = geom_to_primtype<int>( sfcgal_geometry_is_simple, nullptr, geom, errorMsg );
631 CHECK_SUCCESS( errorMsg, false );
632 return static_cast<bool>( res );
633#endif
634}
635
636sfcgal::shared_geom QgsSfcgalEngine::geometryN( const sfcgal::geometry *geom, unsigned int index, QString *errorMsg )
637{
638 sfcgal::errorHandler()->clearText( errorMsg );
639 CHECK_NOT_NULL( geom, nullptr );
640
641 sfcgal_geometry_type_t type = sfcgal_geometry_type_id( geom );
642 CHECK_SUCCESS( errorMsg, nullptr );
643
644 const sfcgal::geometry *out = nullptr;
645
646 switch ( type )
647 {
648 case SFCGAL_TYPE_GEOMETRYCOLLECTION:
649 case SFCGAL_TYPE_MULTILINESTRING:
650 case SFCGAL_TYPE_MULTIPOINT:
651 case SFCGAL_TYPE_MULTIPOLYGON:
652 case SFCGAL_TYPE_MULTISOLID:
653 {
654#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
655 // Prior to version 2.1, index < nrGeoms is not checked
656 // by sfcgal_geometry_collection_geometry_n
657 const unsigned int nrGeoms = sfcgal_geometry_collection_num_geometries( geom );
658 if ( index < nrGeoms )
659 {
660 out = sfcgal_geometry_collection_geometry_n( geom, index );
661 }
662 else
663 {
664 sfcgal::errorHandler()->addText( u"Cannot access geometry at position %s. GeometryCollection has only %d geometries."_s.arg( index ).arg( nrGeoms ) );
665 }
666#else
667 out = sfcgal_geometry_collection_geometry_n( geom, index );
668#endif
669 break;
670 }
671 case SFCGAL_TYPE_LINESTRING:
672 case SFCGAL_TYPE_POINT:
673 case SFCGAL_TYPE_POLYGON:
674 case SFCGAL_TYPE_POLYHEDRALSURFACE:
675 case SFCGAL_TYPE_SOLID:
676 case SFCGAL_TYPE_TRIANGLE:
677 case SFCGAL_TYPE_TRIANGULATEDSURFACE:
678 if ( index == 0 )
679 {
680 out = geom;
681 }
682 break;
683 default:
684 out = nullptr;
685 }
686
687 CHECK_SUCCESS( errorMsg, nullptr );
688
689 sfcgal::shared_geom result = cloneGeometry( out, errorMsg );
690 CHECK_SUCCESS( errorMsg, nullptr );
691
692 return result;
693}
694
695sfcgal::shared_geom QgsSfcgalEngine::boundary( const sfcgal::geometry *geom, QString *errorMsg )
696{
697#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
698 ( void ) geom;
699 ( void ) errorMsg;
700 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "boundary" ) );
701#else
702 sfcgal::errorHandler()->clearText( errorMsg );
703 CHECK_NOT_NULL( geom, nullptr );
704
705 sfcgal::geometry *boundary = sfcgal_geometry_boundary( geom );
706 CHECK_SUCCESS( errorMsg, nullptr );
707
708 return sfcgal::make_shared_geom( boundary );
709#endif
710}
711
712QgsPoint QgsSfcgalEngine::centroid( const sfcgal::geometry *geom, QString *errorMsg )
713{
714#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
715 ( void ) geom;
716 ( void ) errorMsg;
717 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "centroid" ) );
718#else
719 sfcgal::errorHandler()->clearText( errorMsg );
720 CHECK_NOT_NULL( geom, QgsPoint() );
721
722 const sfcgal::geometry *result = nullptr;
723 if ( sfcgal_geometry_is_3d( geom ) )
724 result = sfcgal_geometry_centroid_3d( geom );
725 else
726 result = sfcgal_geometry_centroid( geom );
727
728 CHECK_SUCCESS( errorMsg, QgsPoint() );
729 CHECK_NOT_NULL( result, QgsPoint() );
730
731 QByteArray wkbArray = QgsSfcgalEngine::toWkb( result, errorMsg );
732 QgsConstWkbPtr wkbPtr( wkbArray );
733 QgsPoint out;
734 out.fromWkb( wkbPtr );
735
736 return out;
737#endif
738}
739
740sfcgal::shared_geom QgsSfcgalEngine::translate( const sfcgal::geometry *geom, const QgsVector3D &translation, QString *errorMsg )
741{
742#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
743 ( void ) geom;
744 ( void ) translation;
745 ( void ) errorMsg;
746 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "translate" ) );
747#else
748 sfcgal::errorHandler()->clearText( errorMsg );
749 CHECK_NOT_NULL( geom, nullptr );
750
751 sfcgal::geometry *result;
752 if ( sfcgal_geometry_is_3d( geom ) )
753 result = sfcgal_geometry_translate_3d( geom, translation.x(), translation.y(), translation.z() );
754 else
755 result = sfcgal_geometry_translate_2d( geom, translation.x(), translation.y() );
756 CHECK_SUCCESS( errorMsg, nullptr );
757
758 return sfcgal::make_shared_geom( result );
759#endif
760}
761
762sfcgal::shared_geom QgsSfcgalEngine::scale( const sfcgal::geometry *geom, const QgsVector3D &scaleFactor, const QgsPoint &center, QString *errorMsg )
763{
764 sfcgal::errorHandler()->clearText( errorMsg );
765 CHECK_NOT_NULL( geom, nullptr );
766
767 sfcgal::geometry *result;
768 if ( center.isEmpty() )
769 {
770 result = sfcgal_geometry_scale_3d( geom, scaleFactor.x(), scaleFactor.y(), scaleFactor.z() );
771 }
772 else
773 {
774 const double centerZ = center.is3D() ? center.z() : 0;
775 result = sfcgal_geometry_scale_3d_around_center( geom, scaleFactor.x(), scaleFactor.y(), scaleFactor.z(), center.x(), center.y(), centerZ );
776 }
777
778 CHECK_SUCCESS( errorMsg, nullptr );
779 return sfcgal::make_shared_geom( result );
780}
781
782sfcgal::shared_geom QgsSfcgalEngine::rotate2D( const sfcgal::geometry *geom, double angle, const QgsPoint &center, QString *errorMsg )
783{
784 sfcgal::errorHandler()->clearText( errorMsg );
785 CHECK_NOT_NULL( geom, nullptr );
786
787 sfcgal::geometry *result = sfcgal_geometry_rotate_2d( geom, angle, center.x(), center.y() );
788
789 CHECK_SUCCESS( errorMsg, nullptr );
790 return sfcgal::make_shared_geom( result );
791}
792
793sfcgal::shared_geom QgsSfcgalEngine::rotate3D( const sfcgal::geometry *geom, double angle, const QgsVector3D &axisVector, const QgsPoint &center, QString *errorMsg )
794{
795 sfcgal::errorHandler()->clearText( errorMsg );
796 CHECK_NOT_NULL( geom, nullptr );
797
798 sfcgal::geometry *result;
799 if ( center.isEmpty() )
800 {
801 result = sfcgal_geometry_rotate_3d( geom, angle, axisVector.x(), axisVector.y(), axisVector.z() );
802 }
803 else
804 {
805 result = sfcgal_geometry_rotate_3d_around_center( geom, angle, axisVector.x(), axisVector.y(), axisVector.z(), center.x(), center.y(), center.z() );
806 }
807
808 CHECK_SUCCESS( errorMsg, nullptr );
809 return sfcgal::make_shared_geom( result );
810}
811
812double QgsSfcgalEngine::distance( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
813{
814 double out = geomgeom_to_primtype<double>( sfcgal_geometry_distance, sfcgal_geometry_distance_3d, geomA, geomB, errorMsg );
815 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
816 return out;
817}
818
819bool QgsSfcgalEngine::distanceWithin( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, double maxdistance, QString *errorMsg )
820{
821 double dist = QgsSfcgalEngine::distance( geomA, geomB, errorMsg );
822 CHECK_SUCCESS( errorMsg, false );
823
824 return dist <= maxdistance;
825}
826
827double QgsSfcgalEngine::area( const sfcgal::geometry *geom, QString *errorMsg )
828{
829 double out = geom_to_primtype<double>( sfcgal_geometry_area, sfcgal_geometry_area_3d, geom, errorMsg );
830 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
831 return out;
832}
833
834double QgsSfcgalEngine::length( const sfcgal::geometry *geom, QString *errorMsg )
835{
836#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
837 ( void ) geom;
838 ( void ) errorMsg;
839 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "length" ) );
840#else
841 double out = geom_to_primtype<double>( sfcgal_geometry_length, sfcgal_geometry_length_3d, geom, errorMsg );
842 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
843 return out;
844#endif
845}
846
847bool QgsSfcgalEngine::intersects( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
848{
849 int res = geomgeom_to_primtype<int>( sfcgal_geometry_intersects, sfcgal_geometry_intersects_3d, geomA, geomB, errorMsg );
850 CHECK_SUCCESS( errorMsg, false );
851 return static_cast<bool>( res );
852}
853
854sfcgal::shared_geom QgsSfcgalEngine::intersection( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
855{
856 sfcgal::shared_geom out = geomgeom_to_geom( sfcgal_geometry_intersection, sfcgal_geometry_intersection_3d, geomA, geomB, errorMsg );
857 CHECK_SUCCESS( errorMsg, nullptr );
858 return out;
859}
860
861sfcgal::shared_geom QgsSfcgalEngine::difference( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
862{
863 sfcgal::shared_geom out = geomgeom_to_geom( sfcgal_geometry_difference, sfcgal_geometry_difference_3d, geomA, geomB, errorMsg );
864 CHECK_SUCCESS( errorMsg, nullptr );
865 return out;
866}
867
868sfcgal::shared_geom QgsSfcgalEngine::combine( const QVector<sfcgal::shared_geom> &geomList, QString *errorMsg )
869{
870 sfcgal::errorHandler()->clearText( errorMsg );
871 sfcgal::geometry *combined = nullptr;
872 for ( sfcgal::shared_geom other : geomList )
873 {
874 if ( !combined )
875 {
876 combined = other.get();
877 continue;
878 }
879
880 if ( sfcgal_geometry_is_3d( other.get() ) || sfcgal_geometry_is_3d( combined ) )
881 combined = sfcgal_geometry_union_3d( combined, other.get() );
882 else
883 combined = sfcgal_geometry_union( combined, other.get() );
884
885 if ( !combined )
886 sfcgal::errorHandler()->addText( "SFCGAL produced null result." );
887
888 CHECK_SUCCESS( errorMsg, nullptr );
889 }
890
891 return sfcgal::make_shared_geom( combined );
892}
893
894sfcgal::shared_geom QgsSfcgalEngine::triangulate( const sfcgal::geometry *geom, QString *errorMsg )
895{
896 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_triangulate_2dz, nullptr, geom, errorMsg );
897 CHECK_SUCCESS( errorMsg, nullptr );
898 return out;
899}
900
901bool QgsSfcgalEngine::covers( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
902{
903 int res = geomgeom_to_primtype<int>( sfcgal_geometry_covers, sfcgal_geometry_covers_3d, geomA, geomB, errorMsg );
904 CHECK_SUCCESS( errorMsg, false );
905 return static_cast<bool>( res );
906}
907
908sfcgal::shared_geom QgsSfcgalEngine::envelope( const sfcgal::geometry *geom, QString *errorMsg )
909{
910#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
911 ( void ) geom;
912 ( void ) errorMsg;
913 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "envelope" ) );
914#else
915 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_envelope, sfcgal_geometry_envelope_3d, geom, errorMsg );
916 CHECK_SUCCESS( errorMsg, nullptr );
917 return out;
918#endif
919}
920
921sfcgal::shared_geom QgsSfcgalEngine::convexHull( const sfcgal::geometry *geom, QString *errorMsg )
922{
923 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_convexhull, sfcgal_geometry_convexhull_3d, geom, errorMsg );
924 CHECK_SUCCESS( errorMsg, nullptr );
925 return out;
926}
927
928sfcgal::shared_geom QgsSfcgalEngine::offsetCurve( const sfcgal::geometry *geom, double distance, int, Qgis::JoinStyle, QString *errorMsg )
929{
930 sfcgal::errorHandler()->clearText( errorMsg );
931 CHECK_NOT_NULL( geom, nullptr );
932
933 sfcgal::geometry *result = nullptr;
934 result = sfcgal_geometry_offset_polygon( geom, distance );
935
936 CHECK_SUCCESS( errorMsg, nullptr );
937
938 return sfcgal::make_shared_geom( result );
939}
940
941sfcgal::shared_geom QgsSfcgalEngine::buffer2D( const sfcgal::geometry *geom, double radius, int segments, Qgis::JoinStyle joinStyle, QString *errorMsg )
942{
943 if ( joinStyle != Qgis::JoinStyle::Round )
944 qWarning() << ( u"Buffer not implemented for %1! Defaulting to round join."_s );
945
946 return offsetCurve( geom, radius, segments, joinStyle, errorMsg );
947}
948
949sfcgal::shared_geom QgsSfcgalEngine::buffer3D( const sfcgal::geometry *geom, double radius, int segments, Qgis::JoinStyle3D joinStyle3D, QString *errorMsg )
950{
951 sfcgal::errorHandler()->clearText( errorMsg );
952 CHECK_NOT_NULL( geom, nullptr );
953
954 sfcgal_buffer3d_type_t buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_FLAT;
955 switch ( joinStyle3D )
956 {
958 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_FLAT;
959 break;
961 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_ROUND;
962 break;
964 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_CYLSPHERE;
965 break;
966 }
967
968 sfcgal::geometry *result = sfcgal_geometry_buffer3d( geom, radius, segments, buffer_type );
969 CHECK_SUCCESS( errorMsg, nullptr );
970
971 return sfcgal::make_shared_geom( result );
972}
973
974sfcgal::shared_geom QgsSfcgalEngine::extrude( const sfcgal::geometry *geom, const QgsVector3D &extrusion, QString *errorMsg )
975{
976 sfcgal::errorHandler()->clearText( errorMsg );
977 CHECK_NOT_NULL( geom, nullptr );
978
979 sfcgal_geometry_t *solid = sfcgal_geometry_extrude( geom, extrusion.x(), extrusion.y(), extrusion.z() );
980
981 CHECK_SUCCESS( errorMsg, nullptr );
982
983 // sfcgal_geometry_extrude returns a SOLID
984 // This is not handled by QGIS
985 // convert it to a PolyhedralSurface
986 sfcgal::shared_geom polySurface = QgsSfcgalEngine::toPolyhedralSurface( solid, errorMsg );
987 sfcgal_geometry_delete( solid );
988
989 CHECK_SUCCESS( errorMsg, nullptr );
990
991 return polySurface;
992}
993
994sfcgal::shared_geom QgsSfcgalEngine::simplify( const sfcgal::geometry *geom, double tolerance, bool preserveTopology, QString *errorMsg )
995{
996#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
997 ( void ) geom;
998 ( void ) tolerance;
999 ( void ) preserveTopology;
1000 ( void ) errorMsg;
1001 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "boundary" ) );
1002#else
1003 sfcgal::errorHandler()->clearText( errorMsg );
1004 CHECK_NOT_NULL( geom, nullptr );
1005
1006 sfcgal::geometry *result = sfcgal_geometry_simplify( geom, tolerance, preserveTopology );
1007 CHECK_SUCCESS( errorMsg, nullptr );
1008
1009 return sfcgal::make_shared_geom( result );
1010#endif
1011}
1012
1013sfcgal::shared_geom QgsSfcgalEngine::approximateMedialAxis( const sfcgal::geometry *geom, bool extendToEdges, QString *errorMsg )
1014{
1015 sfcgal::errorHandler()->clearText( errorMsg );
1016 CHECK_NOT_NULL( geom, nullptr );
1017
1018#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 3, 0 )
1019 sfcgal::geometry *result = nullptr;
1020 if ( extendToEdges )
1021 {
1022 result = sfcgal_geometry_projected_medial_axis( geom );
1023 }
1024 else
1025 {
1026 result = sfcgal_geometry_approximate_medial_axis( geom );
1027 }
1028#else
1029 Q_UNUSED( extendToEdges )
1030 sfcgal::geometry *result = sfcgal_geometry_approximate_medial_axis( geom );
1031#endif
1032 CHECK_SUCCESS( errorMsg, nullptr );
1033
1034 return sfcgal::make_shared_geom( result );
1035}
1036
1037sfcgal::shared_geom QgsSfcgalEngine::toSolid( const sfcgal::geometry *geom, QString *errorMsg )
1038{
1039 sfcgal::errorHandler()->clearText( errorMsg );
1040 CHECK_NOT_NULL( geom, nullptr );
1041
1042 sfcgal::geometry *solid = sfcgal_geometry_make_solid( geom );
1043 CHECK_SUCCESS( errorMsg, nullptr );
1044
1045 return sfcgal::make_shared_geom( solid );
1046}
1047
1048sfcgal::shared_geom QgsSfcgalEngine::toPolyhedralSurface( const sfcgal::geometry *geom, QString *errorMsg )
1049{
1050 sfcgal::errorHandler()->clearText( errorMsg );
1051 CHECK_NOT_NULL( geom, nullptr );
1052
1053 if ( sfcgal_geometry_type_id( geom ) != SFCGAL_TYPE_SOLID )
1054 {
1055 sfcgal::errorHandler()->addText( u"toPolyhedralSurface() only applies to solids"_s );
1056 return nullptr;
1057 }
1058
1059 sfcgal_geometry_t *polySurface = sfcgal_polyhedral_surface_create();
1060 for ( unsigned int shellIdx = 0; shellIdx < sfcgal_solid_num_shells( geom ); ++shellIdx )
1061 {
1062 const sfcgal_geometry_t *shell = sfcgal_solid_shell_n( geom, shellIdx );
1063#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
1064 for ( unsigned int polyIdx = 0; polyIdx < sfcgal_polyhedral_surface_num_patches( shell ); ++polyIdx )
1065 {
1066 const sfcgal_geometry_t *patch = sfcgal_polyhedral_surface_patch_n( shell, polyIdx );
1067 sfcgal_polyhedral_surface_add_patch( polySurface, sfcgal_geometry_clone( patch ) );
1068 }
1069#else
1070 for ( unsigned int polyIdx = 0; polyIdx < sfcgal_polyhedral_surface_num_polygons( shell ); ++polyIdx )
1071 {
1072 const sfcgal_geometry_t *patch = sfcgal_polyhedral_surface_polygon_n( shell, polyIdx );
1073 sfcgal_polyhedral_surface_add_polygon( polySurface, sfcgal_geometry_clone( patch ) );
1074 }
1075#endif
1076 }
1077
1078 CHECK_SUCCESS( errorMsg, nullptr );
1079 return sfcgal::make_shared_geom( polySurface );
1080}
1081
1082#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 3, 0 )
1083sfcgal::shared_geom QgsSfcgalEngine::transform( const sfcgal::geometry *geom, const QgsMatrix4x4 &mat, QString *errorMsg )
1084{
1085 sfcgal::errorHandler()->clearText( errorMsg );
1086 CHECK_NOT_NULL( geom, nullptr );
1087
1088 sfcgal::geometry *result;
1089 result = sfcgal_geometry_transform( geom, mat.constData() );
1090
1091 CHECK_SUCCESS( errorMsg, nullptr );
1092 return sfcgal::make_shared_geom( result );
1093}
1094
1095sfcgal::shared_geom QgsSfcgalEngine::split3D( const sfcgal::geometry *geom, const QgsPoint &planePoint, const QgsVector3D &planeNormal, bool closeGeometries, QString *errorMsg )
1096{
1097 sfcgal::errorHandler()->clearText( errorMsg );
1098 CHECK_NOT_NULL( geom, nullptr );
1099
1100 sfcgal::geometry *result = sfcgal_geometry_split_3d( geom, planePoint.x(), planePoint.y(), planePoint.z(), planeNormal.x(), planeNormal.y(), planeNormal.z(), closeGeometries );
1101
1102 CHECK_SUCCESS( errorMsg, nullptr );
1103 return sfcgal::make_shared_geom( result );
1104}
1105
1106std::unique_ptr<QgsSfcgalGeometry> QgsSfcgalEngine::toSfcgalGeometry( sfcgal::shared_prim &prim, sfcgal::primitiveType type, QString *errorMsg )
1107{
1108 sfcgal::errorHandler()->clearText( errorMsg );
1109 CHECK_NOT_NULL( prim.get(), nullptr );
1110
1111 return std::make_unique<QgsSfcgalGeometry>( prim, type );
1112}
1113
1114sfcgal::shared_prim QgsSfcgalEngine::createBox( double sizeX, double sizeY, double sizeZ, QString *errorMsg )
1115{
1116 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_BOX );
1117 CHECK_SUCCESS( errorMsg, nullptr );
1118
1119 sfcgal_primitive_set_parameter_double( result, "x_extent", sizeX );
1120 sfcgal_primitive_set_parameter_double( result, "y_extent", sizeY );
1121 sfcgal_primitive_set_parameter_double( result, "z_extent", sizeZ );
1122 CHECK_SUCCESS( errorMsg, nullptr );
1123
1124 return sfcgal::make_shared_prim( result );
1125}
1126
1127sfcgal::shared_prim QgsSfcgalEngine::createCone( double bottomRadius, double height, double topRadius, unsigned int radial, QString *errorMsg )
1128{
1129 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_CONE );
1130 CHECK_SUCCESS( errorMsg, nullptr );
1131
1132 sfcgal_primitive_set_parameter_double( result, "bottom_radius", bottomRadius );
1133 sfcgal_primitive_set_parameter_double( result, "height", height );
1134 sfcgal_primitive_set_parameter_double( result, "top_radius", topRadius );
1135 sfcgal_primitive_set_parameter_int( result, "num_radial", radial );
1136 CHECK_SUCCESS( errorMsg, nullptr );
1137
1138 return sfcgal::make_shared_prim( result );
1139}
1140
1141sfcgal::shared_prim QgsSfcgalEngine::createCube( double size, QString *errorMsg )
1142{
1143 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_CUBE );
1144 CHECK_SUCCESS( errorMsg, nullptr );
1145
1146 sfcgal_primitive_set_parameter_double( result, "size", size );
1147 CHECK_SUCCESS( errorMsg, nullptr );
1148
1149 return sfcgal::make_shared_prim( result );
1150}
1151
1152sfcgal::shared_prim QgsSfcgalEngine::createCylinder( double radius, double height, unsigned int radial, QString *errorMsg )
1153{
1154 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_CYLINDER );
1155 CHECK_SUCCESS( errorMsg, nullptr );
1156
1157 sfcgal_primitive_set_parameter_double( result, "radius", radius );
1158 sfcgal_primitive_set_parameter_double( result, "height", height );
1159 sfcgal_primitive_set_parameter_int( result, "num_radial", radial );
1160 CHECK_SUCCESS( errorMsg, nullptr );
1161
1162 return sfcgal::make_shared_prim( result );
1163}
1164
1165sfcgal::shared_prim QgsSfcgalEngine::createSphere( double radius, unsigned int subdivisions, QString *errorMsg )
1166{
1167 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_SPHERE );
1168 CHECK_SUCCESS( errorMsg, nullptr );
1169
1170 sfcgal_primitive_set_parameter_double( result, "radius", radius );
1171 sfcgal_primitive_set_parameter_int( result, "num_subdivisions", subdivisions );
1172 CHECK_SUCCESS( errorMsg, nullptr );
1173
1174 return sfcgal::make_shared_prim( result );
1175}
1176
1177sfcgal::shared_prim QgsSfcgalEngine::createTorus( double mainRadius, double tubeRadius, unsigned int mainRadial, unsigned int tubeRadial, QString *errorMsg )
1178{
1179 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_TORUS );
1180 CHECK_SUCCESS( errorMsg, nullptr );
1181
1182 sfcgal_primitive_set_parameter_double( result, "main_radius", mainRadius );
1183 sfcgal_primitive_set_parameter_double( result, "tube_radius", tubeRadius );
1184 sfcgal_primitive_set_parameter_int( result, "main_num_radial", mainRadial );
1185 sfcgal_primitive_set_parameter_int( result, "tube_num_radial", tubeRadial );
1186 CHECK_SUCCESS( errorMsg, nullptr );
1187
1188 return sfcgal::make_shared_prim( result );
1189}
1190
1191sfcgal::shared_geom QgsSfcgalEngine::primitiveAsPolyhedral( const sfcgal::primitive *prim, const QgsMatrix4x4 &mat, QString *errorMsg )
1192{
1193 sfcgal::errorHandler()->clearText( errorMsg );
1194 CHECK_NOT_NULL( prim, nullptr );
1195
1196 sfcgal::geometry *result = sfcgal_primitive_as_polyhedral_surface( prim );
1197 CHECK_SUCCESS( errorMsg, nullptr );
1198
1199 if ( !mat.isIdentity() )
1200 {
1201 sfcgal::geometry *result2 = sfcgal_geometry_transform( result, mat.constData() );
1202 sfcgal_geometry_delete( result );
1203 result = result2;
1204 CHECK_SUCCESS( errorMsg, nullptr );
1205 }
1206
1207 return sfcgal::make_shared_geom( result );
1208}
1209
1210bool QgsSfcgalEngine::primitiveIsEqual( const sfcgal::primitive *primA, const sfcgal::primitive *primB, double tolerance, QString *errorMsg )
1211{
1212 sfcgal::errorHandler()->clearText( errorMsg );
1213 CHECK_NOT_NULL( primA, false );
1214 CHECK_NOT_NULL( primB, false );
1215
1216 bool result = sfcgal_primitive_is_almost_equals( primA, primB, tolerance );
1217 CHECK_SUCCESS( errorMsg, false );
1218
1219 return result;
1220}
1221
1222sfcgal::shared_prim QgsSfcgalEngine::primitiveClone( const sfcgal::primitive *prim, QString *errorMsg )
1223{
1224 sfcgal::errorHandler()->clearText( errorMsg );
1225 CHECK_NOT_NULL( prim, nullptr );
1226
1227 sfcgal::primitive *result = sfcgal_primitive_clone( prim );
1228
1229 CHECK_SUCCESS( errorMsg, nullptr );
1230 CHECK_NOT_NULL( result, nullptr );
1231
1232 return sfcgal::make_shared_prim( result );
1233}
1234
1235double QgsSfcgalEngine::primitiveArea( const sfcgal::primitive *prim, const QgsMatrix4x4 &primTransform, bool withDiscretization, QString *errorMsg )
1236{
1237 sfcgal::errorHandler()->clearText( errorMsg );
1238 CHECK_NOT_NULL( prim, std::numeric_limits<double>::quiet_NaN() );
1239
1240 // simple case - no scale
1241 if ( primTransform.isIdentity() )
1242 {
1243 const double area = sfcgal_primitive_area( prim, withDiscretization );
1244 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1245 return area;
1246 }
1247
1248 double baseArea = 0.0;
1249 double scale = 1.0;
1250
1251 const double scaleX = primTransform.mapVector( QgsVector3D( 1, 0, 0 ) ).x();
1252 const double scaleY = primTransform.mapVector( QgsVector3D( 0, 1, 0 ) ).y();
1253 const double scaleZ = primTransform.mapVector( QgsVector3D( 0, 0, 1 ) ).z();
1254
1255 if ( !qgsDoubleNear( scaleX, scaleY ) || !qgsDoubleNear( scaleY, scaleZ ) )
1256 {
1257 // scale is not uniform - exact computation is not possible
1258 // use a polyhedralsurface approximation
1259 QgsDebugMsgLevel( u"The primitive has a non-uniform scale. Falling back to polyhedral surface approximation for computation."_s, 2 );
1260 sfcgal::shared_geom phs = primitiveAsPolyhedral( prim, primTransform );
1261 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1262 baseArea = area( phs.get() );
1263 }
1264 else
1265 {
1266 // uniform scale - no approximation is needed
1267 scale = std::cbrt( std::abs( primTransform.determinant() ) );
1268 baseArea = sfcgal_primitive_area( prim, withDiscretization );
1269 }
1270
1271 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1272 return baseArea * scale * scale;
1273}
1274
1275double QgsSfcgalEngine::primitiveVolume( const sfcgal::primitive *prim, const QgsMatrix4x4 &primTransform, bool withDiscretization, QString *errorMsg )
1276{
1277 sfcgal::errorHandler()->clearText( errorMsg );
1278 CHECK_NOT_NULL( prim, std::numeric_limits<double>::quiet_NaN() );
1279
1280 const double baseVolume = sfcgal_primitive_volume( prim, withDiscretization );
1281 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1282
1283 return primTransform.isIdentity() ? baseVolume : baseVolume * std::abs( primTransform.determinant() );
1284}
1285
1286void sfcgal::to_json( json &j, const sfcgal::PrimitiveParameterDesc &p )
1287{
1288 j["name"] = p.name;
1289 j["type"] = p.type;
1290
1291 if ( std::holds_alternative<int>( p.value ) )
1292 {
1293 j["value"] = std::get<int>( p.value );
1294 }
1295 else if ( std::holds_alternative<double>( p.value ) )
1296 {
1297 j["value"] = std::get<double>( p.value );
1298 }
1299 else if ( std::holds_alternative<QgsPoint>( p.value ) )
1300 {
1301 QgsPoint point = std::get<QgsPoint>( p.value );
1302 double z = std::numeric_limits<double>::quiet_NaN();
1303 double m = std::numeric_limits<double>::quiet_NaN();
1304 if ( point.is3D() )
1305 z = point.z();
1306 if ( point.isMeasure() )
1307 m = point.m();
1308 j["value"] = std::vector<double> { point.x(), point.y(), z, m };
1309 }
1310 else if ( std::holds_alternative<QgsVector3D>( p.value ) )
1311 {
1312 QgsVector3D vect = std::get<QgsVector3D>( p.value );
1313 j["value"] = std::vector<double> { vect.x(), vect.y(), vect.z() };
1314 }
1315 else
1316 throw json::type_error::create( 306, u"Unknown type '%1'."_s.arg( p.type.c_str() ).toStdString(), nullptr );
1317}
1318
1319void sfcgal::from_json( const json &j, sfcgal::PrimitiveParameterDesc &p )
1320{
1321 j.at( "name" ).get_to( p.name );
1322 j.at( "type" ).get_to( p.type );
1323 if ( j.contains( "value" ) )
1324 {
1325 json value = j.at( "value" );
1326 if ( p.type == "int" )
1327 {
1328 p.value = value.get<int>();
1329 }
1330 else if ( p.type == "double" )
1331 {
1332 p.value = value.get<double>();
1333 }
1334 else if ( p.type == "point3" )
1335 {
1336 std::vector<double> vect;
1337 vect = value.get<std::vector<double>>();
1338 QgsPoint point(
1339 vect[0],
1340 vect[1], //
1341 ( vect.size() > 2 ? vect[2] : std::numeric_limits<double>::quiet_NaN() ), //
1342 ( vect.size() > 3 ? vect[3] : std::numeric_limits<double>::quiet_NaN() )
1343 );
1344 p.value = point;
1345 }
1346 else if ( p.type == "vector3" )
1347 {
1348 std::vector<double> vect;
1349 vect = value.get<std::vector<double>>();
1350 QgsPoint point(
1351 vect[0],
1352 vect[1], //
1353 ( vect.size() > 2 ? vect[2] : std::numeric_limits<double>::quiet_NaN() ), //
1354 ( vect.size() > 3 ? vect[3] : std::numeric_limits<double>::quiet_NaN() )
1355 );
1356 p.value = point;
1357 }
1358 else
1359 throw json::type_error::create( 306, u"Unknown type '%1'."_s.arg( p.type.c_str() ).toStdString(), nullptr );
1360 }
1361}
1362
1363QVector<sfcgal::PrimitiveParameterDesc> QgsSfcgalEngine::primitiveParameters( const sfcgal::primitive *prim, QString *errorMsg )
1364{
1365 sfcgal::errorHandler()->clearText( errorMsg );
1366 CHECK_NOT_NULL( prim, QVector<sfcgal::PrimitiveParameterDesc>() );
1367
1368 char *jsonChars = nullptr;
1369 size_t len = 0;
1370 sfcgal_primitive_parameters( prim, &jsonChars, &len );
1371 CHECK_SUCCESS( errorMsg, QVector<sfcgal::PrimitiveParameterDesc>() );
1372
1373 std::string jsonString( jsonChars, len );
1374 sfcgal_free_buffer( jsonChars );
1375
1376 QVector<sfcgal::PrimitiveParameterDesc> result;
1377 try
1378 {
1379 const auto jParams = json::parse( jsonString );
1380 for ( const auto &jParam : jParams )
1381 {
1382 result.append( jParam.get<sfcgal::PrimitiveParameterDesc>() );
1383 }
1384 }
1385 catch ( json::exception &e )
1386 {
1387 sfcgal::errorHandler()->addText( u"Caught json exception for json: %1. Error: %2"_s.arg( jsonString.c_str() ).arg( e.what() ) );
1388 }
1389
1390 return result;
1391}
1392
1393QVariant QgsSfcgalEngine::primitiveParameter( const sfcgal::primitive *prim, const QString &name, QString *errorMsg )
1394{
1395 sfcgal::errorHandler()->clearText( errorMsg );
1396 CHECK_NOT_NULL( prim, QVariant() );
1397
1398 char *jsonChars = nullptr;
1399 size_t len = 0;
1400 sfcgal_primitive_parameter( prim, name.toStdString().c_str(), &jsonChars, &len );
1401 CHECK_SUCCESS( errorMsg, QVariant() );
1402
1403 std::string jsonString( jsonChars, len );
1404 sfcgal_free_buffer( jsonChars );
1405
1406 QVariant result;
1407 try
1408 {
1409 const auto jParam = json::parse( jsonString );
1410 sfcgal::PrimitiveParameterDesc param = jParam.get<sfcgal::PrimitiveParameterDesc>();
1411 result = QVariant::fromStdVariant( param.value );
1412 }
1413 catch ( json::exception &e )
1414 {
1415 sfcgal::errorHandler()->addText( u"Caught json exception for json: %1. Error: %2"_s.arg( jsonString.c_str() ).arg( e.what() ) );
1416 }
1417
1418 return result;
1419}
1420
1421void QgsSfcgalEngine::primitiveSetParameter( sfcgal::primitive *prim, const QString &name, const QVariant &value, QString *errorMsg )
1422{
1423 sfcgal::errorHandler()->clearText( errorMsg );
1424 CHECK_NOT_NULL( prim, void() );
1425
1426 try
1427 {
1428 json jParam;
1429 sfcgal::PrimitiveParameterDesc paramDesc;
1430 paramDesc.name = name.toStdString();
1431 paramDesc.type = value.typeName();
1432 if ( paramDesc.type == "int" )
1433 paramDesc.value = value.toInt();
1434 else if ( paramDesc.type == "double" )
1435 paramDesc.value = value.toDouble();
1436 else if ( value.canConvert<QgsPoint>() )
1437 paramDesc.value = value.value<QgsPoint>();
1438 else if ( value.canConvert<QgsVector3D>() )
1439 paramDesc.value = value.value<QgsVector3D>();
1440
1441 sfcgal::to_json( jParam, paramDesc );
1442 std::string jsonStr = jParam.dump();
1443 sfcgal_primitive_set_parameter( prim, name.toStdString().c_str(), jsonStr.c_str() );
1444 CHECK_SUCCESS( errorMsg, void() );
1445 }
1446 catch ( ... )
1447 {
1448 sfcgal::errorHandler()->addText( u"Caught json exception"_s );
1449 }
1450}
1451
1452#endif
1453
1454
1455#endif // #ifdef WITH_SFCGAL
JoinStyle3D
Join styles for 3D buffers.
Definition qgis.h:2255
@ CylindersAndSpheres
Cylinders along the linestring segments with spheres at the vertices.
Definition qgis.h:2258
@ Flat
Flat ends and constant width along the linestring.
Definition qgis.h:2257
@ Round
Smooth, rounded buffer around the input geometry.
Definition qgis.h:2256
JoinStyle
Join styles for buffers.
Definition qgis.h:2242
@ Round
Use rounded joins.
Definition qgis.h:2243
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
@ Unknown
Unknown.
Definition qgis.h:295
@ TriangleZM
TriangleZM.
Definition qgis.h:359
Abstract base class for all geometries.
bool isMeasure() const
Returns true if the geometry contains m values.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
virtual QByteArray asWkb(WkbFlags flags=QgsAbstractGeometry::WkbFlags()) const =0
Returns a WKB representation of the geometry.
A const WKB pointer.
Definition qgswkbptr.h:211
int remaining() const
remaining
Definition qgswkbptr.h:292
static std::unique_ptr< QgsAbstractGeometry > geomFromWkb(QgsConstWkbPtr &wkb)
Construct geometry from a WKB string.
A geometry is the spatial representation of a feature.
Qgis::GeometryOperationResult addPartV2(const QVector< QgsPointXY > &points, Qgis::WkbType wkbType=Qgis::WkbType::Unknown)
Adds a new part to a the geometry.
A simple 4x4 matrix implementation useful for transformation in 3D space.
QgsVector3D mapVector(const QgsVector3D &vector) const
Maps vector by multiplying the top 3x3 portion of this matrix by vector.
bool isIdentity() const
Returns whether this matrix is an identity matrix.
double determinant() const
Returns the determinant of this matrix.
const double * constData() const
Returns pointer to the matrix data (stored in column-major order).
Custom exception class which is raised when an operation is not supported.
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
bool fromWkb(QgsConstWkbPtr &wkb) override
Sets the geometry from a WKB string.
Definition qgspoint.cpp:173
double z
Definition qgspoint.h:58
double x
Definition qgspoint.h:56
bool isEmpty() const override
Returns true if the geometry is empty.
Definition qgspoint.cpp:766
double m
Definition qgspoint.h:59
double y
Definition qgspoint.h:57
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:60
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:62
double x() const
Returns X coordinate.
Definition qgsvector3d.h:58
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7209
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63