QGIS API Documentation 4.1.0-Master (cbdd823d36a)
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, 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 return sfcgal::make_shared_geom( result );
1200}
1201
1202bool QgsSfcgalEngine::primitiveIsEqual( const sfcgal::primitive *primA, const sfcgal::primitive *primB, double tolerance, QString *errorMsg )
1203{
1204 sfcgal::errorHandler()->clearText( errorMsg );
1205 CHECK_NOT_NULL( primA, false );
1206 CHECK_NOT_NULL( primB, false );
1207
1208 bool result = sfcgal_primitive_is_almost_equals( primA, primB, tolerance );
1209 CHECK_SUCCESS( errorMsg, false );
1210
1211 return result;
1212}
1213
1214sfcgal::shared_prim QgsSfcgalEngine::primitiveClone( const sfcgal::primitive *prim, QString *errorMsg )
1215{
1216 sfcgal::errorHandler()->clearText( errorMsg );
1217 CHECK_NOT_NULL( prim, nullptr );
1218
1219 sfcgal::primitive *result = sfcgal_primitive_clone( prim );
1220
1221 CHECK_SUCCESS( errorMsg, nullptr );
1222 CHECK_NOT_NULL( result, nullptr );
1223
1224 return sfcgal::make_shared_prim( result );
1225}
1226
1227double QgsSfcgalEngine::primitiveArea( const sfcgal::primitive *prim, bool withDiscretization, QString *errorMsg )
1228{
1229 sfcgal::errorHandler()->clearText( errorMsg );
1230 CHECK_NOT_NULL( prim, std::numeric_limits<double>::quiet_NaN() );
1231
1232 double area = sfcgal_primitive_area( prim, withDiscretization );
1233
1234 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1235 return area;
1236}
1237
1238double QgsSfcgalEngine::primitiveVolume( const sfcgal::primitive *prim, bool withDiscretization, QString *errorMsg )
1239{
1240 sfcgal::errorHandler()->clearText( errorMsg );
1241 CHECK_NOT_NULL( prim, std::numeric_limits<double>::quiet_NaN() );
1242
1243 const double volume = sfcgal_primitive_volume( prim, withDiscretization );
1244 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1245
1246 return volume;
1247}
1248
1249void sfcgal::to_json( json &j, const sfcgal::PrimitiveParameterDesc &p )
1250{
1251 j["name"] = p.name;
1252 j["type"] = p.type;
1253
1254 if ( std::holds_alternative<int>( p.value ) )
1255 {
1256 j["value"] = std::get<int>( p.value );
1257 }
1258 else if ( std::holds_alternative<double>( p.value ) )
1259 {
1260 j["value"] = std::get<double>( p.value );
1261 }
1262 else if ( std::holds_alternative<QgsPoint>( p.value ) )
1263 {
1264 QgsPoint point = std::get<QgsPoint>( p.value );
1265 double z = std::numeric_limits<double>::quiet_NaN();
1266 double m = std::numeric_limits<double>::quiet_NaN();
1267 if ( point.is3D() )
1268 z = point.z();
1269 if ( point.isMeasure() )
1270 m = point.m();
1271 j["value"] = std::vector<double> { point.x(), point.y(), z, m };
1272 }
1273 else if ( std::holds_alternative<QgsVector3D>( p.value ) )
1274 {
1275 QgsVector3D vect = std::get<QgsVector3D>( p.value );
1276 j["value"] = std::vector<double> { vect.x(), vect.y(), vect.z() };
1277 }
1278 else
1279 throw json::type_error::create( 306, u"Unknown type '%1'."_s.arg( p.type.c_str() ).toStdString(), nullptr );
1280}
1281
1282void sfcgal::from_json( const json &j, sfcgal::PrimitiveParameterDesc &p )
1283{
1284 j.at( "name" ).get_to( p.name );
1285 j.at( "type" ).get_to( p.type );
1286 if ( j.contains( "value" ) )
1287 {
1288 json value = j.at( "value" );
1289 if ( p.type == "int" )
1290 {
1291 p.value = value.get<int>();
1292 }
1293 else if ( p.type == "double" )
1294 {
1295 p.value = value.get<double>();
1296 }
1297 else if ( p.type == "point3" )
1298 {
1299 std::vector<double> vect;
1300 vect = value.get<std::vector<double>>();
1301 QgsPoint point(
1302 vect[0],
1303 vect[1], //
1304 ( vect.size() > 2 ? vect[2] : std::numeric_limits<double>::quiet_NaN() ), //
1305 ( vect.size() > 3 ? vect[3] : std::numeric_limits<double>::quiet_NaN() )
1306 );
1307 p.value = point;
1308 }
1309 else if ( p.type == "vector3" )
1310 {
1311 std::vector<double> vect;
1312 vect = value.get<std::vector<double>>();
1313 QgsPoint point(
1314 vect[0],
1315 vect[1], //
1316 ( vect.size() > 2 ? vect[2] : std::numeric_limits<double>::quiet_NaN() ), //
1317 ( vect.size() > 3 ? vect[3] : std::numeric_limits<double>::quiet_NaN() )
1318 );
1319 p.value = point;
1320 }
1321 else
1322 throw json::type_error::create( 306, u"Unknown type '%1'."_s.arg( p.type.c_str() ).toStdString(), nullptr );
1323 }
1324}
1325
1326QVector<sfcgal::PrimitiveParameterDesc> QgsSfcgalEngine::primitiveParameters( const sfcgal::primitive *prim, QString *errorMsg )
1327{
1328 sfcgal::errorHandler()->clearText( errorMsg );
1329 CHECK_NOT_NULL( prim, QVector<sfcgal::PrimitiveParameterDesc>() );
1330
1331 char *jsonChars = nullptr;
1332 size_t len = 0;
1333 sfcgal_primitive_parameters( prim, &jsonChars, &len );
1334 CHECK_SUCCESS( errorMsg, QVector<sfcgal::PrimitiveParameterDesc>() );
1335
1336 std::string jsonString( jsonChars, len );
1337 sfcgal_free_buffer( jsonChars );
1338
1339 QVector<sfcgal::PrimitiveParameterDesc> result;
1340 try
1341 {
1342 const auto jParams = json::parse( jsonString );
1343 for ( const auto &jParam : jParams )
1344 {
1345 result.append( jParam.get<sfcgal::PrimitiveParameterDesc>() );
1346 }
1347 }
1348 catch ( json::exception &e )
1349 {
1350 sfcgal::errorHandler()->addText( u"Caught json exception for json: %1. Error: %2"_s.arg( jsonString.c_str() ).arg( e.what() ) );
1351 }
1352
1353 return result;
1354}
1355
1356QVariant QgsSfcgalEngine::primitiveParameter( const sfcgal::primitive *prim, const QString &name, QString *errorMsg )
1357{
1358 sfcgal::errorHandler()->clearText( errorMsg );
1359 CHECK_NOT_NULL( prim, QVariant() );
1360
1361 char *jsonChars = nullptr;
1362 size_t len = 0;
1363 sfcgal_primitive_parameter( prim, name.toStdString().c_str(), &jsonChars, &len );
1364 CHECK_SUCCESS( errorMsg, QVariant() );
1365
1366 std::string jsonString( jsonChars, len );
1367 sfcgal_free_buffer( jsonChars );
1368
1369 QVariant result;
1370 try
1371 {
1372 const auto jParam = json::parse( jsonString );
1373 sfcgal::PrimitiveParameterDesc param = jParam.get<sfcgal::PrimitiveParameterDesc>();
1374 result = QVariant::fromStdVariant( param.value );
1375 }
1376 catch ( json::exception &e )
1377 {
1378 sfcgal::errorHandler()->addText( u"Caught json exception for json: %1. Error: %2"_s.arg( jsonString.c_str() ).arg( e.what() ) );
1379 }
1380
1381 return result;
1382}
1383
1384void QgsSfcgalEngine::primitiveSetParameter( sfcgal::primitive *prim, const QString &name, const QVariant &value, QString *errorMsg )
1385{
1386 sfcgal::errorHandler()->clearText( errorMsg );
1387 CHECK_NOT_NULL( prim, void() );
1388
1389 try
1390 {
1391 json jParam;
1392 sfcgal::PrimitiveParameterDesc paramDesc;
1393 paramDesc.name = name.toStdString();
1394 paramDesc.type = value.typeName();
1395 if ( paramDesc.type == "int" )
1396 paramDesc.value = value.toInt();
1397 else if ( paramDesc.type == "double" )
1398 paramDesc.value = value.toDouble();
1399 else if ( value.canConvert<QgsPoint>() )
1400 paramDesc.value = value.value<QgsPoint>();
1401 else if ( value.canConvert<QgsVector3D>() )
1402 paramDesc.value = value.value<QgsVector3D>();
1403
1404 sfcgal::to_json( jParam, paramDesc );
1405 std::string jsonStr = jParam.dump();
1406 sfcgal_primitive_set_parameter( prim, name.toStdString().c_str(), jsonStr.c_str() );
1407 CHECK_SUCCESS( errorMsg, void() );
1408 }
1409 catch ( ... )
1410 {
1411 sfcgal::errorHandler()->addText( u"Caught json exception"_s );
1412 }
1413}
1414
1415sfcgal::shared_prim QgsSfcgalEngine::primitiveTranslate( const sfcgal::primitive *prim, const QgsVector3D &translation, QString *errorMsg )
1416{
1417 sfcgal::primitive *result = sfcgal_primitive_translate( prim, translation.x(), translation.y(), translation.z() );
1418 CHECK_SUCCESS( errorMsg, nullptr );
1419
1420 return sfcgal::make_shared_prim( result );
1421}
1422
1423sfcgal::shared_geom QgsSfcgalEngine::primitiveRotate( const sfcgal::primitive *prim, double angle, const QgsVector3D &axisVector, const QgsPoint &center, QString *errorMsg )
1424{
1425 const QgsPoint rotationCenter = center.isEmpty() ? QgsPoint( 0, 0, 0 ) : center;
1426
1427 sfcgal::primitive *result = sfcgal_primitive_rotate( prim, angle, axisVector.x(), axisVector.y(), axisVector.z(), rotationCenter.x(), rotationCenter.y(), rotationCenter.z() );
1428 CHECK_SUCCESS( errorMsg, nullptr );
1429
1430 return sfcgal::make_shared_prim( result );
1431}
1432
1433sfcgal::shared_geom QgsSfcgalEngine::primitiveScale( const sfcgal::primitive *prim, const QgsVector3D &scaleFactor, const QgsPoint &center, QString *errorMsg )
1434{
1435 const QgsPoint scaleCenter = center.isEmpty() ? QgsPoint( 0, 0, 0 ) : center;
1436
1437 sfcgal::primitive *result = sfcgal_primitive_scale( prim, scaleFactor.x(), scaleFactor.y(), scaleFactor.z(), scaleCenter.x(), scaleCenter.y(), scaleCenter.z() );
1438 CHECK_SUCCESS( errorMsg, nullptr );
1439
1440 return sfcgal::make_shared_prim( result );
1441}
1442
1443#endif
1444
1445
1446#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.
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:777
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