QGIS API Documentation 4.3.0-Master (153eea77ea7)
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, QString *reasonMsg, QgsGeometry *errorLoc )
596{
597 sfcgal::errorHandler()->clearText( errorMsg );
598 CHECK_NOT_NULL( geom, false );
599
600 bool result = false;
601 char *reason = nullptr;
602 sfcgal::geometry *location = nullptr;
603 result = sfcgal_geometry_is_valid_detail( geom, &reason, &location );
604
605 CHECK_SUCCESS( errorMsg, false );
606
607 if ( reason && strlen( reason ) )
608 {
609 if ( reasonMsg )
610 {
611 *reasonMsg = QString( reason );
612 }
613 free( reason );
614 }
615
616 if ( location && errorLoc )
617 {
618 std::unique_ptr<QgsAbstractGeometry> locationGeom = toAbstractGeometry( location, errorMsg );
619 CHECK_SUCCESS( errorMsg, false );
620 errorLoc->addPartV2( locationGeom.release() );
621 }
622
623 return result;
624}
625
626bool QgsSfcgalEngine::isSimple( const sfcgal::geometry *geom, QString *errorMsg )
627{
628#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
629 ( void ) geom;
630 ( void ) errorMsg;
631 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "isSimple" ) );
632#else
633 int res = geom_to_primtype<int>( sfcgal_geometry_is_simple, nullptr, geom, errorMsg );
634 CHECK_SUCCESS( errorMsg, false );
635 return static_cast<bool>( res );
636#endif
637}
638
639sfcgal::shared_geom QgsSfcgalEngine::geometryN( const sfcgal::geometry *geom, unsigned int index, QString *errorMsg )
640{
641 sfcgal::errorHandler()->clearText( errorMsg );
642 CHECK_NOT_NULL( geom, nullptr );
643
644 sfcgal_geometry_type_t type = sfcgal_geometry_type_id( geom );
645 CHECK_SUCCESS( errorMsg, nullptr );
646
647 const sfcgal::geometry *out = nullptr;
648
649 switch ( type )
650 {
651 case SFCGAL_TYPE_GEOMETRYCOLLECTION:
652 case SFCGAL_TYPE_MULTILINESTRING:
653 case SFCGAL_TYPE_MULTIPOINT:
654 case SFCGAL_TYPE_MULTIPOLYGON:
655 case SFCGAL_TYPE_MULTISOLID:
656 {
657#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
658 // Prior to version 2.1, index < nrGeoms is not checked
659 // by sfcgal_geometry_collection_geometry_n
660 const unsigned int nrGeoms = sfcgal_geometry_collection_num_geometries( geom );
661 if ( index < nrGeoms )
662 {
663 out = sfcgal_geometry_collection_geometry_n( geom, index );
664 }
665 else
666 {
667 sfcgal::errorHandler()->addText( u"Cannot access geometry at position %s. GeometryCollection has only %d geometries."_s.arg( index ).arg( nrGeoms ) );
668 }
669#else
670 out = sfcgal_geometry_collection_geometry_n( geom, index );
671#endif
672 break;
673 }
674 case SFCGAL_TYPE_LINESTRING:
675 case SFCGAL_TYPE_POINT:
676 case SFCGAL_TYPE_POLYGON:
677 case SFCGAL_TYPE_POLYHEDRALSURFACE:
678 case SFCGAL_TYPE_SOLID:
679 case SFCGAL_TYPE_TRIANGLE:
680 case SFCGAL_TYPE_TRIANGULATEDSURFACE:
681 if ( index == 0 )
682 {
683 out = geom;
684 }
685 break;
686 default:
687 out = nullptr;
688 }
689
690 CHECK_SUCCESS( errorMsg, nullptr );
691
692 sfcgal::shared_geom result = cloneGeometry( out, errorMsg );
693 CHECK_SUCCESS( errorMsg, nullptr );
694
695 return result;
696}
697
698sfcgal::shared_geom QgsSfcgalEngine::boundary( const sfcgal::geometry *geom, QString *errorMsg )
699{
700#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
701 ( void ) geom;
702 ( void ) errorMsg;
703 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "boundary" ) );
704#else
705 sfcgal::errorHandler()->clearText( errorMsg );
706 CHECK_NOT_NULL( geom, nullptr );
707
708 sfcgal::geometry *boundary = sfcgal_geometry_boundary( geom );
709 CHECK_SUCCESS( errorMsg, nullptr );
710
711 return sfcgal::make_shared_geom( boundary );
712#endif
713}
714
715QgsPoint QgsSfcgalEngine::centroid( const sfcgal::geometry *geom, QString *errorMsg )
716{
717#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
718 ( void ) geom;
719 ( void ) errorMsg;
720 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "centroid" ) );
721#else
722 sfcgal::errorHandler()->clearText( errorMsg );
723 CHECK_NOT_NULL( geom, QgsPoint() );
724
725 const sfcgal::geometry *result = nullptr;
726 if ( sfcgal_geometry_is_3d( geom ) )
727 result = sfcgal_geometry_centroid_3d( geom );
728 else
729 result = sfcgal_geometry_centroid( geom );
730
731 CHECK_SUCCESS( errorMsg, QgsPoint() );
732 CHECK_NOT_NULL( result, QgsPoint() );
733
734 QByteArray wkbArray = QgsSfcgalEngine::toWkb( result, errorMsg );
735 QgsConstWkbPtr wkbPtr( wkbArray );
736 QgsPoint out;
737 out.fromWkb( wkbPtr );
738
739 return out;
740#endif
741}
742
743sfcgal::shared_geom QgsSfcgalEngine::translate( const sfcgal::geometry *geom, const QgsVector3D &translation, QString *errorMsg )
744{
745#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
746 ( void ) geom;
747 ( void ) translation;
748 ( void ) errorMsg;
749 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "translate" ) );
750#else
751 sfcgal::errorHandler()->clearText( errorMsg );
752 CHECK_NOT_NULL( geom, nullptr );
753
754 sfcgal::geometry *result;
755 if ( sfcgal_geometry_is_3d( geom ) )
756 result = sfcgal_geometry_translate_3d( geom, translation.x(), translation.y(), translation.z() );
757 else
758 result = sfcgal_geometry_translate_2d( geom, translation.x(), translation.y() );
759 CHECK_SUCCESS( errorMsg, nullptr );
760
761 return sfcgal::make_shared_geom( result );
762#endif
763}
764
765sfcgal::shared_geom QgsSfcgalEngine::scale( const sfcgal::geometry *geom, const QgsVector3D &scaleFactor, const QgsPoint &center, QString *errorMsg )
766{
767 sfcgal::errorHandler()->clearText( errorMsg );
768 CHECK_NOT_NULL( geom, nullptr );
769
770 sfcgal::geometry *result;
771 if ( center.isEmpty() )
772 {
773 result = sfcgal_geometry_scale_3d( geom, scaleFactor.x(), scaleFactor.y(), scaleFactor.z() );
774 }
775 else
776 {
777 const double centerZ = center.is3D() ? center.z() : 0;
778 result = sfcgal_geometry_scale_3d_around_center( geom, scaleFactor.x(), scaleFactor.y(), scaleFactor.z(), center.x(), center.y(), centerZ );
779 }
780
781 CHECK_SUCCESS( errorMsg, nullptr );
782 return sfcgal::make_shared_geom( result );
783}
784
785sfcgal::shared_geom QgsSfcgalEngine::rotate2D( const sfcgal::geometry *geom, double angle, const QgsPoint &center, QString *errorMsg )
786{
787 sfcgal::errorHandler()->clearText( errorMsg );
788 CHECK_NOT_NULL( geom, nullptr );
789
790 sfcgal::geometry *result = sfcgal_geometry_rotate_2d( geom, angle, center.x(), center.y() );
791
792 CHECK_SUCCESS( errorMsg, nullptr );
793 return sfcgal::make_shared_geom( result );
794}
795
796sfcgal::shared_geom QgsSfcgalEngine::rotate3D( const sfcgal::geometry *geom, double angle, const QgsVector3D &axisVector, const QgsPoint &center, QString *errorMsg )
797{
798 sfcgal::errorHandler()->clearText( errorMsg );
799 CHECK_NOT_NULL( geom, nullptr );
800
801 sfcgal::geometry *result;
802 if ( center.isEmpty() )
803 {
804 result = sfcgal_geometry_rotate_3d( geom, angle, axisVector.x(), axisVector.y(), axisVector.z() );
805 }
806 else
807 {
808 result = sfcgal_geometry_rotate_3d_around_center( geom, angle, axisVector.x(), axisVector.y(), axisVector.z(), center.x(), center.y(), center.z() );
809 }
810
811 CHECK_SUCCESS( errorMsg, nullptr );
812 return sfcgal::make_shared_geom( result );
813}
814
815double QgsSfcgalEngine::distance( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
816{
817 double out = geomgeom_to_primtype<double>( sfcgal_geometry_distance, sfcgal_geometry_distance_3d, geomA, geomB, errorMsg );
818 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
819 return out;
820}
821
822bool QgsSfcgalEngine::distanceWithin( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, double maxdistance, QString *errorMsg )
823{
824 double dist = QgsSfcgalEngine::distance( geomA, geomB, errorMsg );
825 CHECK_SUCCESS( errorMsg, false );
826
827 return dist <= maxdistance;
828}
829
830double QgsSfcgalEngine::area( const sfcgal::geometry *geom, QString *errorMsg )
831{
832 double out = geom_to_primtype<double>( sfcgal_geometry_area, sfcgal_geometry_area_3d, geom, errorMsg );
833 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
834 return out;
835}
836
837double QgsSfcgalEngine::length( const sfcgal::geometry *geom, QString *errorMsg )
838{
839#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
840 ( void ) geom;
841 ( void ) errorMsg;
842 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "length" ) );
843#else
844 double out = geom_to_primtype<double>( sfcgal_geometry_length, sfcgal_geometry_length_3d, geom, errorMsg );
845 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
846 return out;
847#endif
848}
849
850bool QgsSfcgalEngine::intersects( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
851{
852 int res = geomgeom_to_primtype<int>( sfcgal_geometry_intersects, sfcgal_geometry_intersects_3d, geomA, geomB, errorMsg );
853 CHECK_SUCCESS( errorMsg, false );
854 return static_cast<bool>( res );
855}
856
857sfcgal::shared_geom QgsSfcgalEngine::intersection( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
858{
859 sfcgal::shared_geom out = geomgeom_to_geom( sfcgal_geometry_intersection, sfcgal_geometry_intersection_3d, geomA, geomB, errorMsg );
860 CHECK_SUCCESS( errorMsg, nullptr );
861 return out;
862}
863
864sfcgal::shared_geom QgsSfcgalEngine::difference( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
865{
866 sfcgal::shared_geom out = geomgeom_to_geom( sfcgal_geometry_difference, sfcgal_geometry_difference_3d, geomA, geomB, errorMsg );
867 CHECK_SUCCESS( errorMsg, nullptr );
868 return out;
869}
870
871sfcgal::shared_geom QgsSfcgalEngine::combine( const QVector<sfcgal::shared_geom> &geomList, QString *errorMsg )
872{
873 sfcgal::errorHandler()->clearText( errorMsg );
874 sfcgal::geometry *combined = nullptr;
875 for ( sfcgal::shared_geom other : geomList )
876 {
877 if ( !combined )
878 {
879 combined = other.get();
880 continue;
881 }
882
883 if ( sfcgal_geometry_is_3d( other.get() ) || sfcgal_geometry_is_3d( combined ) )
884 combined = sfcgal_geometry_union_3d( combined, other.get() );
885 else
886 combined = sfcgal_geometry_union( combined, other.get() );
887
888 if ( !combined )
889 sfcgal::errorHandler()->addText( "SFCGAL produced null result." );
890
891 CHECK_SUCCESS( errorMsg, nullptr );
892 }
893
894 return sfcgal::make_shared_geom( combined );
895}
896
897sfcgal::shared_geom QgsSfcgalEngine::triangulate( const sfcgal::geometry *geom, QString *errorMsg )
898{
899 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_triangulate_2dz, nullptr, geom, errorMsg );
900 CHECK_SUCCESS( errorMsg, nullptr );
901 return out;
902}
903
904bool QgsSfcgalEngine::covers( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
905{
906 int res = geomgeom_to_primtype<int>( sfcgal_geometry_covers, sfcgal_geometry_covers_3d, geomA, geomB, errorMsg );
907 CHECK_SUCCESS( errorMsg, false );
908 return static_cast<bool>( res );
909}
910
911sfcgal::shared_geom QgsSfcgalEngine::envelope( const sfcgal::geometry *geom, QString *errorMsg )
912{
913#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
914 ( void ) geom;
915 ( void ) errorMsg;
916 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "envelope" ) );
917#else
918 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_envelope, sfcgal_geometry_envelope_3d, geom, errorMsg );
919 CHECK_SUCCESS( errorMsg, nullptr );
920 return out;
921#endif
922}
923
924sfcgal::shared_geom QgsSfcgalEngine::convexHull( const sfcgal::geometry *geom, QString *errorMsg )
925{
926 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_convexhull, sfcgal_geometry_convexhull_3d, geom, errorMsg );
927 CHECK_SUCCESS( errorMsg, nullptr );
928 return out;
929}
930
931sfcgal::shared_geom QgsSfcgalEngine::offsetCurve( const sfcgal::geometry *geom, double distance, int, Qgis::JoinStyle, QString *errorMsg )
932{
933 sfcgal::errorHandler()->clearText( errorMsg );
934 CHECK_NOT_NULL( geom, nullptr );
935
936 sfcgal::geometry *result = nullptr;
937 result = sfcgal_geometry_offset_polygon( geom, distance );
938
939 CHECK_SUCCESS( errorMsg, nullptr );
940
941 return sfcgal::make_shared_geom( result );
942}
943
944sfcgal::shared_geom QgsSfcgalEngine::buffer2D( const sfcgal::geometry *geom, double radius, int segments, Qgis::JoinStyle joinStyle, QString *errorMsg )
945{
946 if ( joinStyle != Qgis::JoinStyle::Round )
947 qWarning() << ( u"Buffer not implemented for %1! Defaulting to round join."_s );
948
949 return offsetCurve( geom, radius, segments, joinStyle, errorMsg );
950}
951
952sfcgal::shared_geom QgsSfcgalEngine::buffer3D( const sfcgal::geometry *geom, double radius, int segments, Qgis::JoinStyle3D joinStyle3D, QString *errorMsg )
953{
954 sfcgal::errorHandler()->clearText( errorMsg );
955 CHECK_NOT_NULL( geom, nullptr );
956
957 sfcgal_buffer3d_type_t buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_FLAT;
958 switch ( joinStyle3D )
959 {
961 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_FLAT;
962 break;
964 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_ROUND;
965 break;
967 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_CYLSPHERE;
968 break;
969 }
970
971 sfcgal::geometry *result = sfcgal_geometry_buffer3d( geom, radius, segments, buffer_type );
972 CHECK_SUCCESS( errorMsg, nullptr );
973
974 return sfcgal::make_shared_geom( result );
975}
976
977sfcgal::shared_geom QgsSfcgalEngine::extrude( const sfcgal::geometry *geom, const QgsVector3D &extrusion, QString *errorMsg )
978{
979 sfcgal::errorHandler()->clearText( errorMsg );
980 CHECK_NOT_NULL( geom, nullptr );
981
982 sfcgal_geometry_t *solid = sfcgal_geometry_extrude( geom, extrusion.x(), extrusion.y(), extrusion.z() );
983
984 CHECK_SUCCESS( errorMsg, nullptr );
985
986 // sfcgal_geometry_extrude returns a SOLID
987 // This is not handled by QGIS
988 // convert it to a PolyhedralSurface
989 sfcgal::shared_geom polySurface = QgsSfcgalEngine::toPolyhedralSurface( solid, errorMsg );
990 sfcgal_geometry_delete( solid );
991
992 CHECK_SUCCESS( errorMsg, nullptr );
993
994 return polySurface;
995}
996
997sfcgal::shared_geom QgsSfcgalEngine::simplify( const sfcgal::geometry *geom, double tolerance, bool preserveTopology, QString *errorMsg )
998{
999#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
1000 ( void ) geom;
1001 ( void ) tolerance;
1002 ( void ) preserveTopology;
1003 ( void ) errorMsg;
1004 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "boundary" ) );
1005#else
1006 sfcgal::errorHandler()->clearText( errorMsg );
1007 CHECK_NOT_NULL( geom, nullptr );
1008
1009 sfcgal::geometry *result = sfcgal_geometry_simplify( geom, tolerance, preserveTopology );
1010 CHECK_SUCCESS( errorMsg, nullptr );
1011
1012 return sfcgal::make_shared_geom( result );
1013#endif
1014}
1015
1016sfcgal::shared_geom QgsSfcgalEngine::approximateMedialAxis( const sfcgal::geometry *geom, bool extendToEdges, QString *errorMsg )
1017{
1018 sfcgal::errorHandler()->clearText( errorMsg );
1019 CHECK_NOT_NULL( geom, nullptr );
1020
1021#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 3, 0 )
1022 sfcgal::geometry *result = nullptr;
1023 if ( extendToEdges )
1024 {
1025 result = sfcgal_geometry_projected_medial_axis( geom );
1026 }
1027 else
1028 {
1029 result = sfcgal_geometry_approximate_medial_axis( geom );
1030 }
1031#else
1032 Q_UNUSED( extendToEdges )
1033 sfcgal::geometry *result = sfcgal_geometry_approximate_medial_axis( geom );
1034#endif
1035 CHECK_SUCCESS( errorMsg, nullptr );
1036
1037 return sfcgal::make_shared_geom( result );
1038}
1039
1040sfcgal::shared_geom QgsSfcgalEngine::toSolid( const sfcgal::geometry *geom, QString *errorMsg )
1041{
1042 sfcgal::errorHandler()->clearText( errorMsg );
1043 CHECK_NOT_NULL( geom, nullptr );
1044
1045 sfcgal::geometry *solid = sfcgal_geometry_make_solid( geom );
1046 CHECK_SUCCESS( errorMsg, nullptr );
1047
1048 return sfcgal::make_shared_geom( solid );
1049}
1050
1051sfcgal::shared_geom QgsSfcgalEngine::toPolyhedralSurface( const sfcgal::geometry *geom, QString *errorMsg )
1052{
1053 sfcgal::errorHandler()->clearText( errorMsg );
1054 CHECK_NOT_NULL( geom, nullptr );
1055
1056 if ( sfcgal_geometry_type_id( geom ) != SFCGAL_TYPE_SOLID )
1057 {
1058 sfcgal::errorHandler()->addText( u"toPolyhedralSurface() only applies to solids"_s );
1059 return nullptr;
1060 }
1061
1062 sfcgal_geometry_t *polySurface = sfcgal_polyhedral_surface_create();
1063 for ( unsigned int shellIdx = 0; shellIdx < sfcgal_solid_num_shells( geom ); ++shellIdx )
1064 {
1065 const sfcgal_geometry_t *shell = sfcgal_solid_shell_n( geom, shellIdx );
1066#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
1067 for ( unsigned int polyIdx = 0; polyIdx < sfcgal_polyhedral_surface_num_patches( shell ); ++polyIdx )
1068 {
1069 const sfcgal_geometry_t *patch = sfcgal_polyhedral_surface_patch_n( shell, polyIdx );
1070 sfcgal_polyhedral_surface_add_patch( polySurface, sfcgal_geometry_clone( patch ) );
1071 }
1072#else
1073 for ( unsigned int polyIdx = 0; polyIdx < sfcgal_polyhedral_surface_num_polygons( shell ); ++polyIdx )
1074 {
1075 const sfcgal_geometry_t *patch = sfcgal_polyhedral_surface_polygon_n( shell, polyIdx );
1076 sfcgal_polyhedral_surface_add_polygon( polySurface, sfcgal_geometry_clone( patch ) );
1077 }
1078#endif
1079 }
1080
1081 CHECK_SUCCESS( errorMsg, nullptr );
1082 return sfcgal::make_shared_geom( polySurface );
1083}
1084
1085#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 3, 0 )
1086sfcgal::shared_geom QgsSfcgalEngine::transform( const sfcgal::geometry *geom, const QgsMatrix4x4 &mat, QString *errorMsg )
1087{
1088 sfcgal::errorHandler()->clearText( errorMsg );
1089 CHECK_NOT_NULL( geom, nullptr );
1090
1091 sfcgal::geometry *result;
1092 result = sfcgal_geometry_transform( geom, mat.constData() );
1093
1094 CHECK_SUCCESS( errorMsg, nullptr );
1095 return sfcgal::make_shared_geom( result );
1096}
1097
1098sfcgal::shared_geom QgsSfcgalEngine::split3D( const sfcgal::geometry *geom, const QgsPoint &planePoint, const QgsVector3D &planeNormal, bool closeGeometries, QString *errorMsg )
1099{
1100 sfcgal::errorHandler()->clearText( errorMsg );
1101 CHECK_NOT_NULL( geom, nullptr );
1102
1103 sfcgal::geometry *result = sfcgal_geometry_split_3d( geom, planePoint.x(), planePoint.y(), planePoint.z(), planeNormal.x(), planeNormal.y(), planeNormal.z(), closeGeometries );
1104
1105 CHECK_SUCCESS( errorMsg, nullptr );
1106 return sfcgal::make_shared_geom( result );
1107}
1108
1109std::unique_ptr<QgsSfcgalGeometry> QgsSfcgalEngine::toSfcgalGeometry( sfcgal::shared_prim &prim, sfcgal::primitiveType type, QString *errorMsg )
1110{
1111 sfcgal::errorHandler()->clearText( errorMsg );
1112 CHECK_NOT_NULL( prim.get(), nullptr );
1113
1114 return std::make_unique<QgsSfcgalGeometry>( prim, type );
1115}
1116
1117sfcgal::shared_prim QgsSfcgalEngine::createBox( double sizeX, double sizeY, double sizeZ, QString *errorMsg )
1118{
1119 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_BOX );
1120 CHECK_SUCCESS( errorMsg, nullptr );
1121
1122 sfcgal_primitive_set_parameter_double( result, "x_extent", sizeX );
1123 sfcgal_primitive_set_parameter_double( result, "y_extent", sizeY );
1124 sfcgal_primitive_set_parameter_double( result, "z_extent", sizeZ );
1125 CHECK_SUCCESS( errorMsg, nullptr );
1126
1127 return sfcgal::make_shared_prim( result );
1128}
1129
1130sfcgal::shared_prim QgsSfcgalEngine::createCone( double bottomRadius, double height, double topRadius, unsigned int radial, QString *errorMsg )
1131{
1132 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_CONE );
1133 CHECK_SUCCESS( errorMsg, nullptr );
1134
1135 sfcgal_primitive_set_parameter_double( result, "bottom_radius", bottomRadius );
1136 sfcgal_primitive_set_parameter_double( result, "height", height );
1137 sfcgal_primitive_set_parameter_double( result, "top_radius", topRadius );
1138 sfcgal_primitive_set_parameter_int( result, "num_radial", radial );
1139 CHECK_SUCCESS( errorMsg, nullptr );
1140
1141 return sfcgal::make_shared_prim( result );
1142}
1143
1144sfcgal::shared_prim QgsSfcgalEngine::createCube( double size, QString *errorMsg )
1145{
1146 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_CUBE );
1147 CHECK_SUCCESS( errorMsg, nullptr );
1148
1149 sfcgal_primitive_set_parameter_double( result, "size", size );
1150 CHECK_SUCCESS( errorMsg, nullptr );
1151
1152 return sfcgal::make_shared_prim( result );
1153}
1154
1155sfcgal::shared_prim QgsSfcgalEngine::createCylinder( double radius, double height, unsigned int radial, QString *errorMsg )
1156{
1157 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_CYLINDER );
1158 CHECK_SUCCESS( errorMsg, nullptr );
1159
1160 sfcgal_primitive_set_parameter_double( result, "radius", radius );
1161 sfcgal_primitive_set_parameter_double( result, "height", height );
1162 sfcgal_primitive_set_parameter_int( result, "num_radial", radial );
1163 CHECK_SUCCESS( errorMsg, nullptr );
1164
1165 return sfcgal::make_shared_prim( result );
1166}
1167
1168sfcgal::shared_prim QgsSfcgalEngine::createSphere( double radius, unsigned int subdivisions, QString *errorMsg )
1169{
1170 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_SPHERE );
1171 CHECK_SUCCESS( errorMsg, nullptr );
1172
1173 sfcgal_primitive_set_parameter_double( result, "radius", radius );
1174 sfcgal_primitive_set_parameter_int( result, "num_subdivisions", subdivisions );
1175 CHECK_SUCCESS( errorMsg, nullptr );
1176
1177 return sfcgal::make_shared_prim( result );
1178}
1179
1180sfcgal::shared_prim QgsSfcgalEngine::createTorus( double mainRadius, double tubeRadius, unsigned int mainRadial, unsigned int tubeRadial, QString *errorMsg )
1181{
1182 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_TORUS );
1183 CHECK_SUCCESS( errorMsg, nullptr );
1184
1185 sfcgal_primitive_set_parameter_double( result, "main_radius", mainRadius );
1186 sfcgal_primitive_set_parameter_double( result, "tube_radius", tubeRadius );
1187 sfcgal_primitive_set_parameter_int( result, "main_num_radial", mainRadial );
1188 sfcgal_primitive_set_parameter_int( result, "tube_num_radial", tubeRadial );
1189 CHECK_SUCCESS( errorMsg, nullptr );
1190
1191 return sfcgal::make_shared_prim( result );
1192}
1193
1194sfcgal::shared_geom QgsSfcgalEngine::primitiveAsPolyhedral( const sfcgal::primitive *prim, QString *errorMsg )
1195{
1196 sfcgal::errorHandler()->clearText( errorMsg );
1197 CHECK_NOT_NULL( prim, nullptr );
1198
1199 sfcgal::geometry *result = sfcgal_primitive_as_polyhedral_surface( prim );
1200 CHECK_SUCCESS( errorMsg, nullptr );
1201
1202 return sfcgal::make_shared_geom( result );
1203}
1204
1205bool QgsSfcgalEngine::primitiveIsEqual( const sfcgal::primitive *primA, const sfcgal::primitive *primB, double tolerance, QString *errorMsg )
1206{
1207 sfcgal::errorHandler()->clearText( errorMsg );
1208 CHECK_NOT_NULL( primA, false );
1209 CHECK_NOT_NULL( primB, false );
1210
1211 bool result = sfcgal_primitive_is_almost_equals( primA, primB, tolerance );
1212 CHECK_SUCCESS( errorMsg, false );
1213
1214 return result;
1215}
1216
1217sfcgal::shared_prim QgsSfcgalEngine::primitiveClone( const sfcgal::primitive *prim, QString *errorMsg )
1218{
1219 sfcgal::errorHandler()->clearText( errorMsg );
1220 CHECK_NOT_NULL( prim, nullptr );
1221
1222 sfcgal::primitive *result = sfcgal_primitive_clone( prim );
1223
1224 CHECK_SUCCESS( errorMsg, nullptr );
1225 CHECK_NOT_NULL( result, nullptr );
1226
1227 return sfcgal::make_shared_prim( result );
1228}
1229
1230double QgsSfcgalEngine::primitiveArea( const sfcgal::primitive *prim, bool withDiscretization, QString *errorMsg )
1231{
1232 sfcgal::errorHandler()->clearText( errorMsg );
1233 CHECK_NOT_NULL( prim, std::numeric_limits<double>::quiet_NaN() );
1234
1235 double area = sfcgal_primitive_area( prim, withDiscretization );
1236
1237 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1238 return area;
1239}
1240
1241double QgsSfcgalEngine::primitiveVolume( const sfcgal::primitive *prim, bool withDiscretization, QString *errorMsg )
1242{
1243 sfcgal::errorHandler()->clearText( errorMsg );
1244 CHECK_NOT_NULL( prim, std::numeric_limits<double>::quiet_NaN() );
1245
1246 const double volume = sfcgal_primitive_volume( prim, withDiscretization );
1247 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1248
1249 return volume;
1250}
1251
1252void sfcgal::to_json( json &j, const sfcgal::PrimitiveParameterDesc &p )
1253{
1254 j["name"] = p.name;
1255 j["type"] = p.type;
1256
1257 if ( std::holds_alternative<int>( p.value ) )
1258 {
1259 j["value"] = std::get<int>( p.value );
1260 }
1261 else if ( std::holds_alternative<double>( p.value ) )
1262 {
1263 j["value"] = std::get<double>( p.value );
1264 }
1265 else if ( std::holds_alternative<QgsPoint>( p.value ) )
1266 {
1267 QgsPoint point = std::get<QgsPoint>( p.value );
1268 double z = std::numeric_limits<double>::quiet_NaN();
1269 double m = std::numeric_limits<double>::quiet_NaN();
1270 if ( point.is3D() )
1271 z = point.z();
1272 if ( point.isMeasure() )
1273 m = point.m();
1274 j["value"] = std::vector<double> { point.x(), point.y(), z, m };
1275 }
1276 else if ( std::holds_alternative<QgsVector3D>( p.value ) )
1277 {
1278 QgsVector3D vect = std::get<QgsVector3D>( p.value );
1279 j["value"] = std::vector<double> { vect.x(), vect.y(), vect.z() };
1280 }
1281 else
1282 throw json::type_error::create( 306, u"Unknown type '%1'."_s.arg( p.type.c_str() ).toStdString(), nullptr );
1283}
1284
1285void sfcgal::from_json( const json &j, sfcgal::PrimitiveParameterDesc &p )
1286{
1287 j.at( "name" ).get_to( p.name );
1288 j.at( "type" ).get_to( p.type );
1289 if ( j.contains( "value" ) )
1290 {
1291 json value = j.at( "value" );
1292 if ( p.type == "int" )
1293 {
1294 p.value = value.get<int>();
1295 }
1296 else if ( p.type == "double" )
1297 {
1298 p.value = value.get<double>();
1299 }
1300 else if ( p.type == "point3" )
1301 {
1302 std::vector<double> vect;
1303 vect = value.get<std::vector<double>>();
1304 QgsPoint point(
1305 vect[0],
1306 vect[1], //
1307 ( vect.size() > 2 ? vect[2] : std::numeric_limits<double>::quiet_NaN() ), //
1308 ( vect.size() > 3 ? vect[3] : std::numeric_limits<double>::quiet_NaN() )
1309 );
1310 p.value = point;
1311 }
1312 else if ( p.type == "vector3" )
1313 {
1314 std::vector<double> vect;
1315 vect = value.get<std::vector<double>>();
1316 QgsPoint point(
1317 vect[0],
1318 vect[1], //
1319 ( vect.size() > 2 ? vect[2] : std::numeric_limits<double>::quiet_NaN() ), //
1320 ( vect.size() > 3 ? vect[3] : std::numeric_limits<double>::quiet_NaN() )
1321 );
1322 p.value = point;
1323 }
1324 else
1325 throw json::type_error::create( 306, u"Unknown type '%1'."_s.arg( p.type.c_str() ).toStdString(), nullptr );
1326 }
1327}
1328
1329QVector<sfcgal::PrimitiveParameterDesc> QgsSfcgalEngine::primitiveParameters( const sfcgal::primitive *prim, QString *errorMsg )
1330{
1331 sfcgal::errorHandler()->clearText( errorMsg );
1332 CHECK_NOT_NULL( prim, QVector<sfcgal::PrimitiveParameterDesc>() );
1333
1334 char *jsonChars = nullptr;
1335 size_t len = 0;
1336 sfcgal_primitive_parameters( prim, &jsonChars, &len );
1337 CHECK_SUCCESS( errorMsg, QVector<sfcgal::PrimitiveParameterDesc>() );
1338
1339 std::string jsonString( jsonChars, len );
1340 sfcgal_free_buffer( jsonChars );
1341
1342 QVector<sfcgal::PrimitiveParameterDesc> result;
1343 try
1344 {
1345 const auto jParams = json::parse( jsonString );
1346 for ( const auto &jParam : jParams )
1347 {
1348 result.append( jParam.get<sfcgal::PrimitiveParameterDesc>() );
1349 }
1350 }
1351 catch ( json::exception &e )
1352 {
1353 sfcgal::errorHandler()->addText( u"Caught json exception for json: %1. Error: %2"_s.arg( jsonString.c_str() ).arg( e.what() ) );
1354 }
1355
1356 return result;
1357}
1358
1359QVariant QgsSfcgalEngine::primitiveParameter( const sfcgal::primitive *prim, const QString &name, QString *errorMsg )
1360{
1361 sfcgal::errorHandler()->clearText( errorMsg );
1362 CHECK_NOT_NULL( prim, QVariant() );
1363
1364 char *jsonChars = nullptr;
1365 size_t len = 0;
1366 sfcgal_primitive_parameter( prim, name.toStdString().c_str(), &jsonChars, &len );
1367 CHECK_SUCCESS( errorMsg, QVariant() );
1368
1369 std::string jsonString( jsonChars, len );
1370 sfcgal_free_buffer( jsonChars );
1371
1372 QVariant result;
1373 try
1374 {
1375 const auto jParam = json::parse( jsonString );
1376 sfcgal::PrimitiveParameterDesc param = jParam.get<sfcgal::PrimitiveParameterDesc>();
1377 result = QVariant::fromStdVariant( param.value );
1378 }
1379 catch ( json::exception &e )
1380 {
1381 sfcgal::errorHandler()->addText( u"Caught json exception for json: %1. Error: %2"_s.arg( jsonString.c_str() ).arg( e.what() ) );
1382 }
1383
1384 return result;
1385}
1386
1387void QgsSfcgalEngine::primitiveSetParameter( sfcgal::primitive *prim, const QString &name, const QVariant &value, QString *errorMsg )
1388{
1389 sfcgal::errorHandler()->clearText( errorMsg );
1390 CHECK_NOT_NULL( prim, void() );
1391
1392 try
1393 {
1394 json jParam;
1395 sfcgal::PrimitiveParameterDesc paramDesc;
1396 paramDesc.name = name.toStdString();
1397 paramDesc.type = value.typeName();
1398 if ( paramDesc.type == "int" )
1399 paramDesc.value = value.toInt();
1400 else if ( paramDesc.type == "double" )
1401 paramDesc.value = value.toDouble();
1402 else if ( value.canConvert<QgsPoint>() )
1403 paramDesc.value = value.value<QgsPoint>();
1404 else if ( value.canConvert<QgsVector3D>() )
1405 paramDesc.value = value.value<QgsVector3D>();
1406
1407 sfcgal::to_json( jParam, paramDesc );
1408 std::string jsonStr = jParam.dump();
1409 sfcgal_primitive_set_parameter( prim, name.toStdString().c_str(), jsonStr.c_str() );
1410 CHECK_SUCCESS( errorMsg, void() );
1411 }
1412 catch ( ... )
1413 {
1414 sfcgal::errorHandler()->addText( u"Caught json exception"_s );
1415 }
1416}
1417
1418sfcgal::shared_prim QgsSfcgalEngine::primitiveTranslate( const sfcgal::primitive *prim, const QgsVector3D &translation, QString *errorMsg )
1419{
1420 sfcgal::primitive *result = sfcgal_primitive_translate( prim, translation.x(), translation.y(), translation.z() );
1421 CHECK_SUCCESS( errorMsg, nullptr );
1422
1423 return sfcgal::make_shared_prim( result );
1424}
1425
1426sfcgal::shared_geom QgsSfcgalEngine::primitiveRotate( const sfcgal::primitive *prim, double angle, const QgsVector3D &axisVector, const QgsPoint &center, QString *errorMsg )
1427{
1428 const QgsPoint rotationCenter = center.isEmpty() ? QgsPoint( 0, 0, 0 ) : center;
1429
1430 sfcgal::primitive *result = sfcgal_primitive_rotate( prim, angle, axisVector.x(), axisVector.y(), axisVector.z(), rotationCenter.x(), rotationCenter.y(), rotationCenter.z() );
1431 CHECK_SUCCESS( errorMsg, nullptr );
1432
1433 return sfcgal::make_shared_prim( result );
1434}
1435
1436sfcgal::shared_geom QgsSfcgalEngine::primitiveScale( const sfcgal::primitive *prim, const QgsVector3D &scaleFactor, const QgsPoint &center, QString *errorMsg )
1437{
1438 const QgsPoint scaleCenter = center.isEmpty() ? QgsPoint( 0, 0, 0 ) : center;
1439
1440 sfcgal::primitive *result = sfcgal_primitive_scale( prim, scaleFactor.x(), scaleFactor.y(), scaleFactor.z(), scaleCenter.x(), scaleCenter.y(), scaleCenter.z() );
1441 CHECK_SUCCESS( errorMsg, nullptr );
1442
1443 return sfcgal::make_shared_prim( result );
1444}
1445
1446#endif
1447
1448
1449#endif // #ifdef WITH_SFCGAL
JoinStyle3D
Join styles for 3D buffers.
Definition qgis.h:2286
@ CylindersAndSpheres
Cylinders along the linestring segments with spheres at the vertices.
Definition qgis.h:2289
@ Flat
Flat ends and constant width along the linestring.
Definition qgis.h:2288
@ Round
Smooth, rounded buffer around the input geometry.
Definition qgis.h:2287
JoinStyle
Join styles for buffers.
Definition qgis.h:2273
@ Round
Use rounded joins.
Definition qgis.h:2274
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:781
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