21#include <SFCGAL/capi/sfcgal_c.h>
22#include <nlohmann/json.hpp>
32thread_local sfcgal::ErrorHandler sSfcgalErrorHandler;
34sfcgal::ErrorHandler *sfcgal::errorHandler()
36 return &sSfcgalErrorHandler;
39void sfcgal::GeometryDeleter::operator()( sfcgal::geometry *geom )
const
41 sfcgal_geometry_delete( geom );
44sfcgal::shared_geom sfcgal::make_shared_geom( sfcgal::geometry *geom )
46 return sfcgal::shared_geom( geom, sfcgal::GeometryDeleter() );
50#if SFCGAL_VERSION >= SFCGAL_MAKE_VERSION( 2, 3, 0 )
51void sfcgal::PrimitiveDeleter::operator()( sfcgal::primitive *prim )
const
53 sfcgal_primitive_delete( prim );
56sfcgal::shared_prim sfcgal::make_shared_prim( sfcgal::primitive *prim )
58 return sfcgal::shared_prim( prim, sfcgal::PrimitiveDeleter() );
62bool sfcgal::ErrorHandler::hasSucceedOrStack( QString *errorMsg,
const char *fromFile,
const char *fromFunc,
int fromLine )
64 bool succeed = isTextEmpty();
67 addText(
"relaying error from: ", fromFile, fromFunc, fromLine );
70 errorMsg->append( errorMessages.first() );
76int sfcgal::errorCallback(
const char *fmt, ... )
82 vsnprintf( buffer,
sizeof buffer, fmt, ap );
85 sfcgal::errorHandler()->addText( QStringLiteral(
"SFCGAL error occurred: %1" ).arg( buffer ), __FILE__, __FUNCTION__, __LINE__ );
87 return static_cast<int>( strlen( buffer ) );
90int sfcgal::warningCallback(
const char *fmt, ... )
96 vsnprintf( buffer,
sizeof buffer, fmt, ap );
99 sfcgal::errorHandler()->addText( QStringLiteral(
"SFCGAL warning occurred: %1" ).arg( buffer ), __FILE__, __FUNCTION__, __LINE__ );
101 return static_cast<int>( strlen( buffer ) );
105sfcgal::ErrorHandler::ErrorHandler()
108 sfcgal_set_error_handlers( sfcgal::warningCallback, sfcgal::errorCallback );
111void sfcgal::ErrorHandler::clearText( QString *errorMsg )
113 errorMessages.clear();
120QString sfcgal::ErrorHandler::getMainText()
const
122 return errorMessages.isEmpty() ? QString() : QString(
"Error occurred: " ) + errorMessages.last();
125QString sfcgal::ErrorHandler::getFullText()
const
127 return errorMessages.isEmpty() ? QString() : getMainText() +
"\n\t\t" + errorMessages.join(
"\n\t\t" );
130bool sfcgal::ErrorHandler::isTextEmpty()
const
132 return errorMessages.isEmpty();
135void sfcgal::ErrorHandler::addText(
const QString &msg,
const char *fromFile,
const char *fromFunc,
int fromLine )
137 QString txt = QString(
"%2 (%3:%4) %1" ).arg( msg ).arg( fromFunc ).arg( fromFile ).arg( fromLine );
139 errorMessages.push_front( txt );
155static T geom_to_primtype(
156 T( *func_2d )(
const sfcgal_geometry_t * ),
157 T( *func_3d )(
const sfcgal_geometry_t * ),
158 const sfcgal::geometry *geom,
162 sfcgal::errorHandler()->clearText( errorMsg );
163 CHECK_NOT_NULL( geom, std::numeric_limits<T>::quiet_NaN() );
166 if ( func_3d && sfcgal_geometry_is_3d( geom ) )
167 result = func_3d( geom );
169 result = func_2d( geom );
171 CHECK_SUCCESS( errorMsg, std::numeric_limits<T>::quiet_NaN() );
186static T geomgeom_to_primtype(
187 T( *func_2d )(
const sfcgal_geometry_t *,
const sfcgal_geometry_t * ),
188 T( *func_3d )(
const sfcgal_geometry_t *,
const sfcgal_geometry_t * ),
189 const sfcgal::geometry *geomA,
190 const sfcgal::geometry *geomB,
194 sfcgal::errorHandler()->clearText( errorMsg );
195 CHECK_NOT_NULL( geomA,
false );
196 CHECK_NOT_NULL( geomB,
false );
199 if ( func_3d && ( sfcgal_geometry_is_3d( geomA ) || sfcgal_geometry_is_3d( geomB ) ) )
200 result = func_3d( geomA, geomB );
202 result = func_2d( geomA, geomB );
204 CHECK_SUCCESS( errorMsg, std::numeric_limits<T>::quiet_NaN() );
217static sfcgal::shared_geom geom_to_geom(
218 sfcgal::func_geom_to_geom func_2d,
219 sfcgal::func_geom_to_geom func_3d,
220 const sfcgal::geometry *geom,
224 sfcgal::errorHandler()->clearText( errorMsg );
225 CHECK_NOT_NULL( geom,
nullptr );
227 sfcgal::geometry *result =
nullptr;
228 if ( func_3d && sfcgal_geometry_is_3d( geom ) )
229 result = func_3d( geom );
231 result = func_2d( geom );
233 CHECK_SUCCESS( errorMsg,
nullptr );
234 CHECK_NOT_NULL( result,
nullptr );
236 return sfcgal::make_shared_geom( result );
249static sfcgal::shared_geom geomgeom_to_geom(
250 sfcgal::func_geomgeom_to_geom func_2d,
251 sfcgal::func_geomgeom_to_geom func_3d,
252 const sfcgal::geometry *geomA,
253 const sfcgal::geometry *geomB,
257 sfcgal::errorHandler()->clearText( errorMsg );
258 CHECK_NOT_NULL( geomA,
nullptr );
259 CHECK_NOT_NULL( geomB,
nullptr );
261 sfcgal::geometry *result =
nullptr;
262 if ( func_3d && ( sfcgal_geometry_is_3d( geomA ) || sfcgal_geometry_is_3d( geomB ) ) )
263 result = func_3d( geomA, geomB );
265 result = func_2d( geomA, geomB );
267 CHECK_SUCCESS( errorMsg,
nullptr );
268 CHECK_NOT_NULL( result,
nullptr );
270 return sfcgal::make_shared_geom( result );
279std::unique_ptr<QgsSfcgalGeometry> QgsSfcgalEngine::toSfcgalGeometry( sfcgal::shared_geom &geom, QString *errorMsg )
281 sfcgal::errorHandler()->clearText( errorMsg );
282 CHECK_NOT_NULL( geom.get(),
nullptr );
284 return std::make_unique<QgsSfcgalGeometry>( geom );
287std::unique_ptr<QgsAbstractGeometry> QgsSfcgalEngine::toAbstractGeometry(
const sfcgal::geometry *geom, QString *errorMsg )
289 std::unique_ptr<QgsAbstractGeometry> out(
nullptr );
290 sfcgal::errorHandler()->clearText( errorMsg );
291 CHECK_NOT_NULL( geom, out );
293 QByteArray wkbArray = QgsSfcgalEngine::toWkb( geom, errorMsg );
294 CHECK_SUCCESS( errorMsg, out );
301 sfcgal::errorHandler()->addText( QStringLiteral(
"WKB contains unmanaged geometry type (WKB:%1 / SFCGAL:%2" )
302 .arg(
static_cast<int>( wkbPtr.readHeader() ) )
303 .arg(
static_cast<int>( sfcgalType ) ),
304 __FILE__, __FUNCTION__, __LINE__ );
310sfcgal::shared_geom QgsSfcgalEngine::fromAbstractGeometry(
const QgsAbstractGeometry *geom, QString *errorMsg )
312 sfcgal::errorHandler()->clearText( errorMsg );
313 CHECK_NOT_NULL( geom, sfcgal::shared_geom(
nullptr ) );
315 QByteArray wkbBytes = geom->
asWkb();
317 sfcgal::geometry *out = sfcgal_io_read_wkb( wkbBytes.data(), wkbBytes.length() );
318 CHECK_SUCCESS( errorMsg,
nullptr );
320 return sfcgal::make_shared_geom( out );
323sfcgal::shared_geom QgsSfcgalEngine::cloneGeometry(
const sfcgal::geometry *geom, QString *errorMsg )
325 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_clone,
nullptr, geom, errorMsg );
326 CHECK_SUCCESS( errorMsg,
nullptr );
330QString QgsSfcgalEngine::geometryType(
const sfcgal::geometry *geom, QString *errorMsg )
332#if SFCGAL_VERSION < SFCGAL_MAKE_VERSION( 2, 1, 0 )
335 throw QgsNotSupportedException( QObject::tr(
"Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"geometryType" ) );
337 sfcgal::errorHandler()->clearText( errorMsg );
341 sfcgal_geometry_type( geom, &typeChar, &typeLen );
342 std::string typeStr( typeChar, typeLen );
343 sfcgal_free_buffer( typeChar );
345 return QString::fromStdString( typeStr );
349sfcgal::shared_geom QgsSfcgalEngine::fromWkb(
const QgsConstWkbPtr &wkbPtr, QString *errorMsg )
351 sfcgal::errorHandler()->clearText( errorMsg );
353 const unsigned char *wkbUnsignedPtr = wkbPtr;
354 sfcgal::geometry *out = sfcgal_io_read_wkb(
reinterpret_cast<const char *
>( wkbUnsignedPtr ), wkbPtr.
remaining() );
355 CHECK_SUCCESS( errorMsg,
nullptr );
357 return sfcgal::make_shared_geom( out );
360sfcgal::shared_geom QgsSfcgalEngine::fromWkt(
const QString &wkt, QString *errorMsg )
362 sfcgal::errorHandler()->clearText( errorMsg );
364 sfcgal::geometry *out = sfcgal_io_read_wkt( wkt.toStdString().c_str(), wkt.length() );
365 CHECK_SUCCESS( errorMsg,
nullptr );
367 return sfcgal::unique_geom( out );
370QByteArray QgsSfcgalEngine::toWkb(
const sfcgal::geometry *geom, QString *errorMsg )
372 sfcgal::errorHandler()->clearText( errorMsg );
373 CHECK_NOT_NULL( geom, QByteArray() );
377 sfcgal_geometry_as_wkb( geom, &wkbHex, &len );
378 QByteArray wkbArray( wkbHex,
static_cast<int>( len ) );
380#if SFCGAL_VERSION >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
381 sfcgal_free_buffer( wkbHex );
386 CHECK_SUCCESS( errorMsg, QByteArray() );
390QString QgsSfcgalEngine::toWkt(
const sfcgal::geometry *geom,
int numDecimals, QString *errorMsg )
392 sfcgal::errorHandler()->clearText( errorMsg );
393 CHECK_NOT_NULL( geom, QString() );
397 sfcgal_geometry_as_text_decim( geom, numDecimals, &wkt, &len );
398 CHECK_SUCCESS( errorMsg, QString() );
400 std::string wktString( wkt, len );
401#if SFCGAL_VERSION >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
402 sfcgal_free_buffer( wkt );
406 return QString::fromStdString( wktString );
409Qgis::WkbType QgsSfcgalEngine::wkbType(
const sfcgal::geometry *geom, QString *errorMsg )
411 sfcgal::errorHandler()->clearText( errorMsg );
414 sfcgal_geometry_type_t type = sfcgal_geometry_type_id( geom );
418 if ( sfcgal_geometry_is_3d( geom ) )
421 if ( sfcgal_geometry_is_measured( geom ) )
428 sfcgal::errorHandler()->addText( QStringLiteral(
"WKB type '%1' is not known from QGIS" ).arg( wkbType ),
429 __FILE__, __FUNCTION__, __LINE__ );
433int QgsSfcgalEngine::dimension(
const sfcgal::geometry *geom, QString *errorMsg )
435#if SFCGAL_VERSION < SFCGAL_MAKE_VERSION( 2, 1, 0 )
438 throw QgsNotSupportedException( QObject::tr(
"Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"dimension" ) );
440 int out = geom_to_primtype<int>( sfcgal_geometry_dimension,
nullptr, geom, errorMsg );
441 CHECK_SUCCESS( errorMsg, std::numeric_limits<int>::quiet_NaN() );
446int QgsSfcgalEngine::partCount(
const sfcgal::geometry *geom, QString *errorMsg )
449 sfcgal::errorHandler()->clearText( errorMsg );
450 CHECK_NOT_NULL( geom, -1 );
452 sfcgal_geometry_type_t type = sfcgal_geometry_type_id( geom );
453 CHECK_SUCCESS( errorMsg, -1 );
457 case SFCGAL_TYPE_MULTIPOINT:
458 case SFCGAL_TYPE_MULTILINESTRING:
459 case SFCGAL_TYPE_MULTIPOLYGON:
460 case SFCGAL_TYPE_MULTISOLID:
461 case SFCGAL_TYPE_GEOMETRYCOLLECTION:
462#if SFCGAL_VERSION >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
463 out = sfcgal_geometry_num_geometries( geom );
465 out = sfcgal_geometry_collection_num_geometries( geom );
468 case SFCGAL_TYPE_POLYGON:
469 out = sfcgal_polygon_num_interior_rings( geom ) + 1;
471 case SFCGAL_TYPE_SOLID:
472 out = sfcgal_solid_num_shells( geom );
474 case SFCGAL_TYPE_POLYHEDRALSURFACE:
475#if SFCGAL_VERSION >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
476 out = sfcgal_polyhedral_surface_num_patches( geom );
478 out = sfcgal_polyhedral_surface_num_polygons( geom );
481 case SFCGAL_TYPE_TRIANGULATEDSURFACE:
482#if SFCGAL_VERSION >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
483 out = sfcgal_triangulated_surface_num_patches( geom );
485 out = sfcgal_triangulated_surface_num_triangles( geom );
488 case SFCGAL_TYPE_LINESTRING:
489 out = sfcgal_linestring_num_points( geom );
491 case SFCGAL_TYPE_TRIANGLE:
494 case SFCGAL_TYPE_POINT:
501 CHECK_SUCCESS( errorMsg, -1 );
503 return static_cast<int>( out );
506bool QgsSfcgalEngine::addZValue( sfcgal::geometry *geom,
double zValue, QString *errorMsg )
508#if SFCGAL_VERSION < SFCGAL_MAKE_VERSION( 2, 1, 0 )
512 throw QgsNotSupportedException( QObject::tr(
"Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"addZValue" ) );
514 sfcgal::errorHandler()->clearText( errorMsg );
515 CHECK_NOT_NULL( geom,
false );
517 return sfcgal_geometry_force_z( geom, zValue );
521bool QgsSfcgalEngine::addMValue( sfcgal::geometry *geom,
double mValue, QString *errorMsg )
523#if SFCGAL_VERSION < SFCGAL_MAKE_VERSION( 2, 1, 0 )
527 throw QgsNotSupportedException( QObject::tr(
"Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"addMValue" ) );
529 sfcgal::errorHandler()->clearText( errorMsg );
530 CHECK_NOT_NULL( geom,
false );
532 return sfcgal_geometry_force_m( geom, mValue );
536bool QgsSfcgalEngine::dropZValue( sfcgal::geometry *geom, QString *errorMsg )
538#if SFCGAL_VERSION < SFCGAL_MAKE_VERSION( 2, 1, 0 )
541 throw QgsNotSupportedException( QObject::tr(
"Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"dropZValue" ) );
543 sfcgal::errorHandler()->clearText( errorMsg );
544 CHECK_NOT_NULL( geom,
false );
546 return sfcgal_geometry_drop_z( geom );
550bool QgsSfcgalEngine::dropMValue( sfcgal::geometry *geom, QString *errorMsg )
552#if SFCGAL_VERSION < SFCGAL_MAKE_VERSION( 2, 1, 0 )
555 throw QgsNotSupportedException( QObject::tr(
"Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"dropMValue" ) );
557 sfcgal::errorHandler()->clearText( errorMsg );
558 CHECK_NOT_NULL( geom,
false );
560 return sfcgal_geometry_drop_m( geom );
564void QgsSfcgalEngine::swapXy( sfcgal::geometry *geom, QString *errorMsg )
566#if SFCGAL_VERSION < SFCGAL_MAKE_VERSION( 2, 1, 0 )
569 throw QgsNotSupportedException( QObject::tr(
"Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"swapXy" ) );
571 sfcgal::errorHandler()->clearText( errorMsg );
572 CHECK_NOT_NULL( geom,
void() );
574 sfcgal_geometry_swap_xy( geom );
578bool QgsSfcgalEngine::isEqual(
const sfcgal::geometry *geomA,
const sfcgal::geometry *geomB,
double tolerance, QString *errorMsg )
580#if SFCGAL_VERSION < SFCGAL_MAKE_VERSION( 2, 1, 0 )
585 throw QgsNotSupportedException( QObject::tr(
"Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"isEqual" ) );
587 sfcgal::errorHandler()->clearText( errorMsg );
588 CHECK_NOT_NULL( geomA,
false );
589 CHECK_NOT_NULL( geomB,
false );
591 bool result = sfcgal_geometry_is_almost_equals( geomA, geomB, tolerance );
592 CHECK_SUCCESS( errorMsg,
false );
598bool QgsSfcgalEngine::isEmpty(
const sfcgal::geometry *geom, QString *errorMsg )
600 int res = geom_to_primtype<int>( sfcgal_geometry_is_empty,
nullptr, geom, errorMsg );
601 CHECK_SUCCESS( errorMsg,
false );
602 return static_cast<bool>( res );
605bool QgsSfcgalEngine::isValid(
const sfcgal::geometry *geom, QString *errorMsg,
QgsGeometry *errorLoc )
607 sfcgal::errorHandler()->clearText( errorMsg );
608 CHECK_NOT_NULL( geom,
false );
612 sfcgal::geometry *location;
613 result = sfcgal_geometry_is_valid_detail( geom, &reason, &location );
615 CHECK_SUCCESS( errorMsg,
false );
617 if ( reason && strlen( reason ) )
619 sfcgal::errorHandler()->addText( QString( reason ) );
623 if ( location && errorLoc )
625 std::unique_ptr<QgsAbstractGeometry> locationGeom = toAbstractGeometry( location, errorMsg );
626 CHECK_SUCCESS( errorMsg,
false );
627 errorLoc->
addPartV2( locationGeom.release() );
633bool QgsSfcgalEngine::isSimple(
const sfcgal::geometry *geom, QString *errorMsg )
635#if SFCGAL_VERSION < SFCGAL_MAKE_VERSION( 2, 1, 0 )
638 throw QgsNotSupportedException( QObject::tr(
"Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"isSimple" ) );
640 int res = geom_to_primtype<int>( sfcgal_geometry_is_simple,
nullptr, geom, errorMsg );
641 CHECK_SUCCESS( errorMsg,
false );
642 return static_cast<bool>( res );
646sfcgal::shared_geom QgsSfcgalEngine::boundary(
const sfcgal::geometry *geom, QString *errorMsg )
648#if SFCGAL_VERSION < SFCGAL_MAKE_VERSION( 2, 1, 0 )
651 throw QgsNotSupportedException( QObject::tr(
"Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"boundary" ) );
653 sfcgal::errorHandler()->clearText( errorMsg );
654 CHECK_NOT_NULL( geom,
nullptr );
656 sfcgal::geometry *boundary = sfcgal_geometry_boundary( geom );
657 CHECK_SUCCESS( errorMsg,
nullptr );
659 return sfcgal::make_shared_geom( boundary );
663QgsPoint QgsSfcgalEngine::centroid(
const sfcgal::geometry *geom, QString *errorMsg )
665#if SFCGAL_VERSION < SFCGAL_MAKE_VERSION( 2, 1, 0 )
668 throw QgsNotSupportedException( QObject::tr(
"Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"centroid" ) );
670 sfcgal::errorHandler()->clearText( errorMsg );
673 const sfcgal::geometry *result =
nullptr;
674 if ( sfcgal_geometry_is_3d( geom ) )
675 result = sfcgal_geometry_centroid_3d( geom );
677 result = sfcgal_geometry_centroid( geom );
679 CHECK_SUCCESS( errorMsg,
QgsPoint() );
680 CHECK_NOT_NULL( result,
QgsPoint() );
682 QByteArray wkbArray = QgsSfcgalEngine::toWkb( result, errorMsg );
691sfcgal::shared_geom QgsSfcgalEngine::translate(
const sfcgal::geometry *geom,
const QgsVector3D &translation, QString *errorMsg )
693#if SFCGAL_VERSION < SFCGAL_MAKE_VERSION( 2, 1, 0 )
697 throw QgsNotSupportedException( QObject::tr(
"Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"translate" ) );
699 sfcgal::errorHandler()->clearText( errorMsg );
700 CHECK_NOT_NULL( geom,
nullptr );
702 sfcgal::geometry *result;
703 if ( sfcgal_geometry_is_3d( geom ) )
704 result = sfcgal_geometry_translate_3d( geom, translation.
x(), translation.
y(), translation.
z() );
706 result = sfcgal_geometry_translate_2d( geom, translation.
x(), translation.
y() );
707 CHECK_SUCCESS( errorMsg,
nullptr );
709 return sfcgal::make_shared_geom( result );
713sfcgal::shared_geom QgsSfcgalEngine::scale(
const sfcgal::geometry *geom,
const QgsVector3D &scaleFactor,
const QgsPoint ¢er, QString *errorMsg )
715 sfcgal::errorHandler()->clearText( errorMsg );
716 CHECK_NOT_NULL( geom,
nullptr );
718 sfcgal::geometry *result;
721 result = sfcgal_geometry_scale_3d( geom, scaleFactor.
x(), scaleFactor.
y(), scaleFactor.
z() );
725 const double centerZ = center.
is3D() ? center.
z() : 0;
726 result = sfcgal_geometry_scale_3d_around_center( geom, scaleFactor.
x(), scaleFactor.
y(), scaleFactor.
z(), center.
x(), center.
y(), centerZ );
729 CHECK_SUCCESS( errorMsg,
nullptr );
730 return sfcgal::make_shared_geom( result );
733sfcgal::shared_geom QgsSfcgalEngine::rotate2D(
const sfcgal::geometry *geom,
double angle,
const QgsPoint ¢er, QString *errorMsg )
735 sfcgal::errorHandler()->clearText( errorMsg );
736 CHECK_NOT_NULL( geom,
nullptr );
738 sfcgal::geometry *result = sfcgal_geometry_rotate_2d( geom, angle, center.
x(), center.
y() );
740 CHECK_SUCCESS( errorMsg,
nullptr );
741 return sfcgal::make_shared_geom( result );
744sfcgal::shared_geom QgsSfcgalEngine::rotate3D(
const sfcgal::geometry *geom,
double angle,
const QgsVector3D &axisVector,
const QgsPoint ¢er, QString *errorMsg )
746 sfcgal::errorHandler()->clearText( errorMsg );
747 CHECK_NOT_NULL( geom,
nullptr );
749 sfcgal::geometry *result;
752 result = sfcgal_geometry_rotate_3d( geom, angle, axisVector.
x(), axisVector.
y(), axisVector.
z() );
756 result = sfcgal_geometry_rotate_3d_around_center( geom, angle, axisVector.
x(), axisVector.
y(), axisVector.
z(), center.
x(), center.
y(), center.
z() );
759 CHECK_SUCCESS( errorMsg,
nullptr );
760 return sfcgal::make_shared_geom( result );
763double QgsSfcgalEngine::distance(
const sfcgal::geometry *geomA,
const sfcgal::geometry *geomB, QString *errorMsg )
765 double out = geomgeom_to_primtype<double>( sfcgal_geometry_distance, sfcgal_geometry_distance_3d, geomA, geomB, errorMsg );
766 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
770bool QgsSfcgalEngine::distanceWithin(
const sfcgal::geometry *geomA,
const sfcgal::geometry *geomB,
double maxdistance, QString *errorMsg )
772 double dist = QgsSfcgalEngine::distance( geomA, geomB, errorMsg );
773 CHECK_SUCCESS( errorMsg,
false );
775 return dist <= maxdistance;
778double QgsSfcgalEngine::area(
const sfcgal::geometry *geom, QString *errorMsg )
780 double out = geom_to_primtype<double>( sfcgal_geometry_area, sfcgal_geometry_area_3d, geom, errorMsg );
781 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
785double QgsSfcgalEngine::length(
const sfcgal::geometry *geom, QString *errorMsg )
787#if SFCGAL_VERSION < SFCGAL_MAKE_VERSION( 2, 1, 0 )
790 throw QgsNotSupportedException( QObject::tr(
"Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"length" ) );
792 double out = geom_to_primtype<double>( sfcgal_geometry_length, sfcgal_geometry_length_3d, geom, errorMsg );
793 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
798bool QgsSfcgalEngine::intersects(
const sfcgal::geometry *geomA,
const sfcgal::geometry *geomB, QString *errorMsg )
800 int res = geomgeom_to_primtype<int>( sfcgal_geometry_intersects, sfcgal_geometry_intersects_3d, geomA, geomB, errorMsg );
801 CHECK_SUCCESS( errorMsg,
false );
802 return static_cast<bool>( res );
805sfcgal::shared_geom QgsSfcgalEngine::intersection(
const sfcgal::geometry *geomA,
const sfcgal::geometry *geomB, QString *errorMsg )
807 sfcgal::shared_geom out = geomgeom_to_geom( sfcgal_geometry_intersection, sfcgal_geometry_intersection_3d, geomA, geomB, errorMsg );
808 CHECK_SUCCESS( errorMsg,
nullptr );
812sfcgal::shared_geom QgsSfcgalEngine::difference(
const sfcgal::geometry *geomA,
const sfcgal::geometry *geomB, QString *errorMsg )
814 sfcgal::shared_geom out = geomgeom_to_geom( sfcgal_geometry_difference, sfcgal_geometry_difference_3d, geomA, geomB, errorMsg );
815 CHECK_SUCCESS( errorMsg,
nullptr );
819sfcgal::shared_geom QgsSfcgalEngine::combine(
const QVector<sfcgal::shared_geom> &geomList, QString *errorMsg )
821 sfcgal::errorHandler()->clearText( errorMsg );
822 sfcgal::geometry *combined =
nullptr;
823 for ( sfcgal::shared_geom other : geomList )
827 combined = other.get();
831 if ( sfcgal_geometry_is_3d( other.get() ) || sfcgal_geometry_is_3d( combined ) )
832 combined = sfcgal_geometry_union_3d( combined, other.get() );
834 combined = sfcgal_geometry_union( combined, other.get() );
837 sfcgal::errorHandler()->addText(
"SFCGAL produced null result." );
839 CHECK_SUCCESS( errorMsg,
nullptr );
842 return sfcgal::make_shared_geom( combined );
845sfcgal::shared_geom QgsSfcgalEngine::triangulate(
const sfcgal::geometry *geom, QString *errorMsg )
847 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_triangulate_2dz,
nullptr, geom, errorMsg );
848 CHECK_SUCCESS( errorMsg,
nullptr );
852bool QgsSfcgalEngine::covers(
const sfcgal::geometry *geomA,
const sfcgal::geometry *geomB, QString *errorMsg )
854 int res = geomgeom_to_primtype<int>( sfcgal_geometry_covers, sfcgal_geometry_covers_3d, geomA, geomB, errorMsg );
855 CHECK_SUCCESS( errorMsg,
false );
856 return static_cast<bool>( res );
859sfcgal::shared_geom QgsSfcgalEngine::envelope(
const sfcgal::geometry *geom, QString *errorMsg )
861#if SFCGAL_VERSION < SFCGAL_MAKE_VERSION( 2, 1, 0 )
864 throw QgsNotSupportedException( QObject::tr(
"Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"envelope" ) );
866 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_envelope, sfcgal_geometry_envelope_3d, geom, errorMsg );
867 CHECK_SUCCESS( errorMsg,
nullptr );
872sfcgal::shared_geom QgsSfcgalEngine::convexHull(
const sfcgal::geometry *geom, QString *errorMsg )
874 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_convexhull, sfcgal_geometry_convexhull_3d, geom, errorMsg );
875 CHECK_SUCCESS( errorMsg,
nullptr );
879sfcgal::shared_geom QgsSfcgalEngine::offsetCurve(
const sfcgal::geometry *geom,
double distance,
int,
Qgis::JoinStyle, QString *errorMsg )
881 sfcgal::errorHandler()->clearText( errorMsg );
882 CHECK_NOT_NULL( geom,
nullptr );
884 sfcgal::geometry *result =
nullptr;
885 result = sfcgal_geometry_offset_polygon( geom, distance );
887 CHECK_SUCCESS( errorMsg,
nullptr );
889 return sfcgal::make_shared_geom( result );
892sfcgal::shared_geom QgsSfcgalEngine::buffer2D(
const sfcgal::geometry *geom,
double radius,
int segments,
Qgis::JoinStyle joinStyle, QString *errorMsg )
895 qWarning() << ( QStringLiteral(
"Buffer not implemented for %1! Defaulting to round join." ) );
897 return offsetCurve( geom, radius, segments, joinStyle, errorMsg );
900sfcgal::shared_geom QgsSfcgalEngine::buffer3D(
const sfcgal::geometry *geom,
double radius,
int segments,
Qgis::JoinStyle3D joinStyle3D, QString *errorMsg )
902 sfcgal::errorHandler()->clearText( errorMsg );
903 CHECK_NOT_NULL( geom,
nullptr );
905 sfcgal_buffer3d_type_t buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_FLAT;
906 switch ( joinStyle3D )
909 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_FLAT;
912 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_ROUND;
915 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_CYLSPHERE;
919 sfcgal::geometry *result = sfcgal_geometry_buffer3d( geom, radius, segments, buffer_type );
920 CHECK_SUCCESS( errorMsg,
nullptr );
922 return sfcgal::make_shared_geom( result );
925sfcgal::shared_geom QgsSfcgalEngine::extrude(
const sfcgal::geometry *geom,
const QgsVector3D &extrusion, QString *errorMsg )
927 sfcgal::errorHandler()->clearText( errorMsg );
928 CHECK_NOT_NULL( geom,
nullptr );
930 sfcgal_geometry_t *solid = sfcgal_geometry_extrude( geom, extrusion.
x(), extrusion.
y(), extrusion.
z() );
932 CHECK_SUCCESS( errorMsg,
nullptr );
937 sfcgal_geometry_t *polySurface = sfcgal_polyhedral_surface_create();
938 for (
unsigned int shellIdx = 0; shellIdx < sfcgal_solid_num_shells( solid ); ++shellIdx )
940 const sfcgal_geometry_t *shell = sfcgal_solid_shell_n( solid, shellIdx );
941#if SFCGAL_VERSION >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
942 for (
unsigned int polyIdx = 0; polyIdx < sfcgal_polyhedral_surface_num_patches( shell ); ++polyIdx )
944 const sfcgal_geometry_t *patch = sfcgal_polyhedral_surface_patch_n( shell, polyIdx );
945 sfcgal_polyhedral_surface_add_patch( polySurface, sfcgal_geometry_clone( patch ) );
948 for (
unsigned int polyIdx = 0; polyIdx < sfcgal_polyhedral_surface_num_polygons( shell ); ++polyIdx )
950 const sfcgal_geometry_t *patch = sfcgal_polyhedral_surface_polygon_n( shell, polyIdx );
951 sfcgal_polyhedral_surface_add_polygon( polySurface, sfcgal_geometry_clone( patch ) );
956 sfcgal_geometry_delete( solid );
958 CHECK_SUCCESS( errorMsg,
nullptr );
960 return sfcgal::make_shared_geom( polySurface );
963sfcgal::shared_geom QgsSfcgalEngine::simplify(
const sfcgal::geometry *geom,
double tolerance,
bool preserveTopology, QString *errorMsg )
965#if SFCGAL_VERSION < SFCGAL_MAKE_VERSION( 2, 1, 0 )
968 ( void )preserveTopology;
970 throw QgsNotSupportedException( QObject::tr(
"Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"boundary" ) );
972 sfcgal::errorHandler()->clearText( errorMsg );
973 CHECK_NOT_NULL( geom,
nullptr );
975 sfcgal::geometry *result = sfcgal_geometry_simplify( geom, tolerance, preserveTopology );
976 CHECK_SUCCESS( errorMsg,
nullptr );
978 return sfcgal::make_shared_geom( result );
982sfcgal::shared_geom QgsSfcgalEngine::approximateMedialAxis(
const sfcgal::geometry *geom, QString *errorMsg )
984 sfcgal::errorHandler()->clearText( errorMsg );
985 CHECK_NOT_NULL( geom,
nullptr );
987 sfcgal::geometry *result = sfcgal_geometry_approximate_medial_axis( geom );
988 CHECK_SUCCESS( errorMsg,
nullptr );
990 return sfcgal::make_shared_geom( result );
994#if SFCGAL_VERSION >= SFCGAL_MAKE_VERSION( 2, 3, 0 )
995sfcgal::shared_geom QgsSfcgalEngine::transform(
const sfcgal::geometry *geom,
const QMatrix4x4 &mat, QString *errorMsg )
997 sfcgal::errorHandler()->clearText( errorMsg );
998 CHECK_NOT_NULL( geom,
nullptr );
1000 sfcgal::geometry *result;
1001 result = sfcgal_geometry_transform( geom, mat.constData() );
1003 CHECK_SUCCESS( errorMsg,
nullptr );
1004 return sfcgal::make_shared_geom( result );
1007std::unique_ptr<QgsSfcgalGeometry> QgsSfcgalEngine::toSfcgalGeometry( sfcgal::shared_prim &prim, sfcgal::primitiveType type, QString *errorMsg )
1009 sfcgal::errorHandler()->clearText( errorMsg );
1010 CHECK_NOT_NULL( prim.get(),
nullptr );
1012 return std::make_unique<QgsSfcgalGeometry>( prim, type );
1015sfcgal::shared_prim QgsSfcgalEngine::createCube(
double size, QString *errorMsg )
1017 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_CUBE );
1018 CHECK_SUCCESS( errorMsg,
nullptr );
1020 sfcgal_primitive_set_parameter_double( result,
"size", size );
1021 CHECK_SUCCESS( errorMsg,
nullptr );
1023 return sfcgal::make_shared_prim( result );
1026sfcgal::shared_geom QgsSfcgalEngine::primitiveAsPolyhedral(
const sfcgal::primitive *prim,
const QMatrix4x4 &mat, QString *errorMsg )
1028 sfcgal::errorHandler()->clearText( errorMsg );
1029 CHECK_NOT_NULL( prim,
nullptr );
1031 sfcgal::geometry *result = sfcgal_primitive_as_polyhedral_surface( prim );
1032 CHECK_SUCCESS( errorMsg,
nullptr );
1034 if ( !mat.isIdentity() )
1036 sfcgal::geometry *result2 = sfcgal_geometry_transform( result, mat.constData() );
1037 sfcgal_geometry_delete( result );
1039 CHECK_SUCCESS( errorMsg,
nullptr );
1042 return sfcgal::make_shared_geom( result );
1045bool QgsSfcgalEngine::primitiveIsEqual(
const sfcgal::primitive *primA,
const sfcgal::primitive *primB,
double tolerance, QString *errorMsg )
1047 sfcgal::errorHandler()->clearText( errorMsg );
1048 CHECK_NOT_NULL( primA,
false );
1049 CHECK_NOT_NULL( primB,
false );
1051 bool result = sfcgal_primitive_is_almost_equals( primA, primB, tolerance );
1052 CHECK_SUCCESS( errorMsg,
false );
1057sfcgal::shared_prim QgsSfcgalEngine::primitiveClone(
const sfcgal::primitive *prim, QString *errorMsg )
1059 sfcgal::errorHandler()->clearText( errorMsg );
1060 CHECK_NOT_NULL( prim,
nullptr );
1062 sfcgal::primitive *result = sfcgal_primitive_clone( prim );
1064 CHECK_SUCCESS( errorMsg,
nullptr );
1065 CHECK_NOT_NULL( result,
nullptr );
1067 return sfcgal::make_shared_prim( result );
1070double QgsSfcgalEngine::primitiveArea(
const sfcgal::primitive *prim,
bool withDiscretization, QString *errorMsg )
1072 sfcgal::errorHandler()->clearText( errorMsg );
1073 CHECK_NOT_NULL( prim, std::numeric_limits<double>::quiet_NaN() );
1075 double out = sfcgal_primitive_area( prim, withDiscretization );
1076 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1080double QgsSfcgalEngine::primitiveVolume(
const sfcgal::primitive *prim,
bool withDiscretization, QString *errorMsg )
1082 sfcgal::errorHandler()->clearText( errorMsg );
1083 CHECK_NOT_NULL( prim, std::numeric_limits<double>::quiet_NaN() );
1085 double out = sfcgal_primitive_volume( prim, withDiscretization );
1086 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1090void sfcgal::to_json( json &j,
const sfcgal::PrimitiveParameterDesc &p )
1095 if ( std::holds_alternative<int>( p.value ) )
1097 j[
"value"] = std::get<int>( p.value );
1099 else if ( std::holds_alternative<double>( p.value ) )
1101 j[
"value"] = std::get<double>( p.value );
1103 else if ( std::holds_alternative<QgsPoint>( p.value ) )
1105 QgsPoint point = std::get<QgsPoint>( p.value );
1106 double z = std::numeric_limits<double>::quiet_NaN();
1107 double m = std::numeric_limits<double>::quiet_NaN();
1112 j[
"value"] = std::vector<double> { point.
x(), point.
y(), z, m };
1114 else if ( std::holds_alternative<QgsVector3D>( p.value ) )
1116 QgsVector3D vect = std::get<QgsVector3D>( p.value );
1117 j[
"value"] = std::vector<double> { vect.
x(), vect.
y(), vect.
z() };
1120 throw json::type_error::create( 306, QStringLiteral(
"Unknown type '%1'." ).arg( p.type.c_str() ).toStdString(),
nullptr );
1123void sfcgal::from_json(
const json &j, sfcgal::PrimitiveParameterDesc &p )
1125 j.at(
"name" ).get_to( p.name );
1126 j.at(
"type" ).get_to( p.type );
1127 if ( j.contains(
"value" ) )
1129 json value = j.at(
"value" );
1130 if ( p.type ==
"int" )
1132 p.value = value.get<
int>();
1134 else if ( p.type ==
"double" )
1136 p.value = value.get<
double>();
1138 else if ( p.type ==
"point3" )
1140 std::vector<double> vect;
1141 vect = value.get<std::vector<double>>();
1143 ( vect.size() > 2 ? vect[2] : std::numeric_limits<double>::quiet_NaN() ),
1144 ( vect.size() > 3 ? vect[3] : std::numeric_limits<double>::quiet_NaN() ) );
1147 else if ( p.type ==
"vector3" )
1149 std::vector<double> vect;
1150 vect = value.get<std::vector<double>>();
1152 ( vect.size() > 2 ? vect[2] : std::numeric_limits<double>::quiet_NaN() ),
1153 ( vect.size() > 3 ? vect[3] : std::numeric_limits<double>::quiet_NaN() ) );
1157 throw json::type_error::create( 306, QStringLiteral(
"Unknown type '%1'." ).arg( p.type.c_str() ).toStdString(),
nullptr );
1161QVector<sfcgal::PrimitiveParameterDesc> QgsSfcgalEngine::primitiveParameters(
const sfcgal::primitive *prim, QString *errorMsg )
1163 sfcgal::errorHandler()->clearText( errorMsg );
1164 CHECK_NOT_NULL( prim, QVector<sfcgal::PrimitiveParameterDesc>() );
1166 char *jsonChars =
nullptr;
1168 sfcgal_primitive_parameters( prim, &jsonChars, &len );
1169 CHECK_SUCCESS( errorMsg, QVector<sfcgal::PrimitiveParameterDesc>() );
1171 std::string jsonString( jsonChars, len );
1172 sfcgal_free_buffer( jsonChars );
1174 QVector<sfcgal::PrimitiveParameterDesc> result;
1177 const auto jParams = json::parse( jsonString );
1178 for (
const auto &jParam : jParams )
1180 result.append( jParam.get<sfcgal::PrimitiveParameterDesc>() );
1183 catch ( json::exception &e )
1185 sfcgal::errorHandler()->addText( QStringLiteral(
"Caught json exception for json: %1. Error: %2" ).arg( jsonString.c_str() ).arg( e.what() ) );
1191QVariant QgsSfcgalEngine::primitiveParameter(
const sfcgal::primitive *prim,
const QString &name, QString *errorMsg )
1193 sfcgal::errorHandler()->clearText( errorMsg );
1194 CHECK_NOT_NULL( prim, QVariant() );
1196 char *jsonChars =
nullptr;
1198 sfcgal_primitive_parameter( prim, name.toStdString().c_str(), &jsonChars, &len );
1199 CHECK_SUCCESS( errorMsg, QVariant() );
1201 std::string jsonString( jsonChars, len );
1202 sfcgal_free_buffer( jsonChars );
1207 const auto jParam = json::parse( jsonString );
1208 sfcgal::PrimitiveParameterDesc param = jParam.get<sfcgal::PrimitiveParameterDesc>();
1209 result = QVariant::fromStdVariant( param.value );
1211 catch ( json::exception &e )
1213 sfcgal::errorHandler()->addText( QStringLiteral(
"Caught json exception for json: %1. Error: %2" ).arg( jsonString.c_str() ).arg( e.what() ) );
1219void QgsSfcgalEngine::primitiveSetParameter( sfcgal::primitive *prim,
const QString &name,
const QVariant &value, QString *errorMsg )
1221 sfcgal::errorHandler()->clearText( errorMsg );
1222 CHECK_NOT_NULL( prim,
void() );
1227 sfcgal::PrimitiveParameterDesc paramDesc;
1228 paramDesc.name = name.toStdString();
1229 paramDesc.type = value.typeName();
1230 if ( paramDesc.type ==
"int" )
1231 paramDesc.value = value.toInt();
1232 else if ( paramDesc.type ==
"double" )
1233 paramDesc.value = value.toDouble();
1234 else if ( value.canConvert<
QgsPoint>() )
1235 paramDesc.value = value.value<
QgsPoint>();
1239 sfcgal::to_json( jParam, paramDesc );
1240 std::string jsonStr = jParam.dump();
1241 sfcgal_primitive_set_parameter( prim, name.toStdString().c_str(), jsonStr.c_str() );
1242 CHECK_SUCCESS( errorMsg,
void() );
1246 sfcgal::errorHandler()->addText( QStringLiteral(
"Caught json exception" ) );
JoinStyle3D
Join styles for 3D buffers.
@ CylindersAndSpheres
Cylinders along the linestring segments with spheres at the vertices.
@ Flat
Flat ends and constant width along the linestring.
@ Round
Smooth, rounded buffer around the input geometry.
JoinStyle
Join styles for buffers.
@ Round
Use rounded joins.
WkbType
The WKB type describes the number of dimensions a geometry has.
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.
int remaining() const
remaining
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.
Custom exception class which is raised when an operation is not supported.
Point geometry type, with support for z-dimension and m-values.
bool fromWkb(QgsConstWkbPtr &wkb) override
Sets the geometry from a WKB string.
bool isEmpty() const override
Returns true if the geometry is empty.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
double y() const
Returns Y coordinate.
double z() const
Returns Z coordinate.
double x() const
Returns X coordinate.