21#include <SFCGAL/capi/sfcgal_c.h>
22#include <nlohmann/json.hpp>
30using namespace Qt::StringLiterals;
36thread_local sfcgal::ErrorHandler sSfcgalErrorHandler;
38sfcgal::ErrorHandler *sfcgal::errorHandler()
40 return &sSfcgalErrorHandler;
43void sfcgal::GeometryDeleter::operator()( sfcgal::geometry *geom )
const
45 sfcgal_geometry_delete( geom );
48sfcgal::shared_geom sfcgal::make_shared_geom( sfcgal::geometry *geom )
50 return sfcgal::shared_geom( geom, sfcgal::GeometryDeleter() );
54#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 3, 0 )
55void sfcgal::PrimitiveDeleter::operator()( sfcgal::primitive *prim )
const
57 sfcgal_primitive_delete( prim );
60sfcgal::shared_prim sfcgal::make_shared_prim( sfcgal::primitive *prim )
62 return sfcgal::shared_prim( prim, sfcgal::PrimitiveDeleter() );
66bool sfcgal::ErrorHandler::hasSucceedOrStack( QString *errorMsg,
const std::source_location &location )
68 bool succeed = isTextEmpty();
71 addText(
"relaying error from: ", location );
74 errorMsg->append( errorMessages.first() );
80int sfcgal::errorCallback(
const char *fmt, ... )
86 vsnprintf( buffer,
sizeof buffer, fmt, ap );
89 sfcgal::errorHandler()->addText( u
"SFCGAL error occurred: %1"_s.arg( buffer ) );
91 return static_cast<int>( strlen( buffer ) );
94int sfcgal::warningCallback(
const char *fmt, ... )
100 vsnprintf( buffer,
sizeof buffer, fmt, ap );
103 sfcgal::errorHandler()->addText( u
"SFCGAL warning occurred: %1"_s.arg( buffer ) );
105 return static_cast<int>( strlen( buffer ) );
109sfcgal::ErrorHandler::ErrorHandler()
112 sfcgal_set_error_handlers( sfcgal::warningCallback, sfcgal::errorCallback );
115void sfcgal::ErrorHandler::clearText( QString *errorMsg )
117 errorMessages.clear();
124QString sfcgal::ErrorHandler::getMainText()
const
126 return errorMessages.isEmpty() ? QString() : QString(
"Error occurred: " ) + errorMessages.last();
129QString sfcgal::ErrorHandler::getFullText()
const
131 return errorMessages.isEmpty() ? QString() : getMainText() +
"\n\t\t" + errorMessages.join(
"\n\t\t" );
134bool sfcgal::ErrorHandler::isTextEmpty()
const
136 return errorMessages.isEmpty();
139void sfcgal::ErrorHandler::addText(
const QString &msg,
const std::source_location &location )
141 QString txt = QString(
"%2 (%3:%4) %1" )
143 .arg( QString::fromStdString( location.function_name() ) )
144 .arg( QString::fromStdString( location.file_name() ) )
145 .arg( location.line() );
147 errorMessages.push_front( txt );
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 )
164 sfcgal::errorHandler()->clearText( errorMsg );
165 CHECK_NOT_NULL( geom, std::numeric_limits<T>::quiet_NaN() );
168 if ( func_3d && sfcgal_geometry_is_3d( geom ) )
169 result = func_3d( geom );
171 result = func_2d( geom );
173 CHECK_SUCCESS( errorMsg, std::numeric_limits<T>::quiet_NaN() );
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,
195 sfcgal::errorHandler()->clearText( errorMsg );
196 CHECK_NOT_NULL( geomA,
false );
197 CHECK_NOT_NULL( geomB,
false );
200 if ( func_3d && ( sfcgal_geometry_is_3d( geomA ) || sfcgal_geometry_is_3d( geomB ) ) )
201 result = func_3d( geomA, geomB );
203 result = func_2d( geomA, geomB );
205 CHECK_SUCCESS( errorMsg, std::numeric_limits<T>::quiet_NaN() );
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 )
220 sfcgal::errorHandler()->clearText( errorMsg );
221 CHECK_NOT_NULL( geom,
nullptr );
223 sfcgal::geometry *result =
nullptr;
224 if ( func_3d && sfcgal_geometry_is_3d( geom ) )
225 result = func_3d( geom );
227 result = func_2d( geom );
229 CHECK_SUCCESS( errorMsg,
nullptr );
230 CHECK_NOT_NULL( result,
nullptr );
232 return sfcgal::make_shared_geom( result );
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 )
247 sfcgal::errorHandler()->clearText( errorMsg );
248 CHECK_NOT_NULL( geomA,
nullptr );
249 CHECK_NOT_NULL( geomB,
nullptr );
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 );
255 result = func_2d( geomA, geomB );
257 CHECK_SUCCESS( errorMsg,
nullptr );
258 CHECK_NOT_NULL( result,
nullptr );
260 return sfcgal::make_shared_geom( result );
269std::unique_ptr<QgsSfcgalGeometry> QgsSfcgalEngine::toSfcgalGeometry( sfcgal::shared_geom &geom, QString *errorMsg )
271 sfcgal::errorHandler()->clearText( errorMsg );
272 CHECK_NOT_NULL( geom.get(),
nullptr );
274 return std::make_unique<QgsSfcgalGeometry>( geom );
277std::unique_ptr<QgsAbstractGeometry> QgsSfcgalEngine::toAbstractGeometry(
const sfcgal::geometry *geom, QString *errorMsg )
279 std::unique_ptr<QgsAbstractGeometry> out(
nullptr );
280 sfcgal::errorHandler()->clearText( errorMsg );
281 CHECK_NOT_NULL( geom, out );
283 QByteArray wkbArray = QgsSfcgalEngine::toWkb( geom, errorMsg );
284 CHECK_SUCCESS( errorMsg, out );
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 ) )
301sfcgal::shared_geom QgsSfcgalEngine::fromAbstractGeometry(
const QgsAbstractGeometry *geom, QString *errorMsg )
303 sfcgal::errorHandler()->clearText( errorMsg );
304 CHECK_NOT_NULL( geom, sfcgal::shared_geom(
nullptr ) );
306 QByteArray wkbBytes = geom->
asWkb();
308 sfcgal::geometry *out = sfcgal_io_read_wkb( wkbBytes.data(), wkbBytes.length() );
309 CHECK_SUCCESS( errorMsg,
nullptr );
311 return sfcgal::make_shared_geom( out );
314sfcgal::shared_geom QgsSfcgalEngine::cloneGeometry(
const sfcgal::geometry *geom, QString *errorMsg )
316 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_clone,
nullptr, geom, errorMsg );
317 CHECK_SUCCESS( errorMsg,
nullptr );
321QString QgsSfcgalEngine::geometryType(
const sfcgal::geometry *geom, QString *errorMsg )
323#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
326 throw QgsNotSupportedException( QObject::tr(
"Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"geometryType" ) );
328 sfcgal::errorHandler()->clearText( errorMsg );
332 sfcgal_geometry_type( geom, &typeChar, &typeLen );
333 std::string typeStr( typeChar, typeLen );
334 sfcgal_free_buffer( typeChar );
336 return QString::fromStdString( typeStr );
340sfcgal::shared_geom QgsSfcgalEngine::fromWkb(
const QgsConstWkbPtr &wkbPtr, QString *errorMsg )
342 sfcgal::errorHandler()->clearText( errorMsg );
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 );
348 return sfcgal::make_shared_geom( out );
351sfcgal::shared_geom QgsSfcgalEngine::fromWkt(
const QString &wkt, QString *errorMsg )
353 sfcgal::errorHandler()->clearText( errorMsg );
355 sfcgal::geometry *out = sfcgal_io_read_wkt( wkt.toStdString().c_str(), wkt.length() );
356 CHECK_SUCCESS( errorMsg,
nullptr );
358 return sfcgal::unique_geom( out );
361QByteArray QgsSfcgalEngine::toWkb(
const sfcgal::geometry *geom, QString *errorMsg )
363 sfcgal::errorHandler()->clearText( errorMsg );
364 CHECK_NOT_NULL( geom, QByteArray() );
368 sfcgal_geometry_as_wkb( geom, &wkbHex, &len );
369 QByteArray wkbArray( wkbHex,
static_cast<int>( len ) );
371#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
372 sfcgal_free_buffer( wkbHex );
377 CHECK_SUCCESS( errorMsg, QByteArray() );
381QString QgsSfcgalEngine::toWkt(
const sfcgal::geometry *geom,
int numDecimals, QString *errorMsg )
383 sfcgal::errorHandler()->clearText( errorMsg );
384 CHECK_NOT_NULL( geom, QString() );
388 sfcgal_geometry_as_text_decim( geom, numDecimals, &wkt, &len );
389 CHECK_SUCCESS( errorMsg, QString() );
391 std::string wktString( wkt, len );
392#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
393 sfcgal_free_buffer( wkt );
397 return QString::fromStdString( wktString );
400Qgis::WkbType QgsSfcgalEngine::wkbType(
const sfcgal::geometry *geom, QString *errorMsg )
402 sfcgal::errorHandler()->clearText( errorMsg );
405 sfcgal_geometry_type_t type = sfcgal_geometry_type_id( geom );
409 if ( sfcgal_geometry_is_3d( geom ) )
412 if ( sfcgal_geometry_is_measured( geom ) )
419 sfcgal::errorHandler()->addText( u
"WKB type '%1' is not known from QGIS"_s.arg( wkbType ) );
423int QgsSfcgalEngine::dimension(
const sfcgal::geometry *geom, QString *errorMsg )
425#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
428 throw QgsNotSupportedException( QObject::tr(
"Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"dimension" ) );
430 int out = geom_to_primtype<int>( sfcgal_geometry_dimension,
nullptr, geom, errorMsg );
431 CHECK_SUCCESS( errorMsg, std::numeric_limits<int>::quiet_NaN() );
436int QgsSfcgalEngine::partCount(
const sfcgal::geometry *geom, QString *errorMsg )
439 sfcgal::errorHandler()->clearText( errorMsg );
440 CHECK_NOT_NULL( geom, -1 );
442 sfcgal_geometry_type_t type = sfcgal_geometry_type_id( geom );
443 CHECK_SUCCESS( errorMsg, -1 );
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 );
455 out = sfcgal_geometry_collection_num_geometries( geom );
458 case SFCGAL_TYPE_POLYGON:
459 out = sfcgal_polygon_num_interior_rings( geom ) + 1;
461 case SFCGAL_TYPE_SOLID:
462 out = sfcgal_solid_num_shells( geom );
464 case SFCGAL_TYPE_POLYHEDRALSURFACE:
465#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
466 out = sfcgal_polyhedral_surface_num_patches( geom );
468 out = sfcgal_polyhedral_surface_num_polygons( geom );
471 case SFCGAL_TYPE_TRIANGULATEDSURFACE:
472#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
473 out = sfcgal_triangulated_surface_num_patches( geom );
475 out = sfcgal_triangulated_surface_num_triangles( geom );
478 case SFCGAL_TYPE_LINESTRING:
479 out = sfcgal_linestring_num_points( geom );
481 case SFCGAL_TYPE_TRIANGLE:
484 case SFCGAL_TYPE_POINT:
491 CHECK_SUCCESS( errorMsg, -1 );
493 return static_cast<int>( out );
496bool QgsSfcgalEngine::addZValue( sfcgal::geometry *geom,
double zValue, QString *errorMsg )
498#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
502 throw QgsNotSupportedException( QObject::tr(
"Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"addZValue" ) );
504 sfcgal::errorHandler()->clearText( errorMsg );
505 CHECK_NOT_NULL( geom,
false );
507 return sfcgal_geometry_force_z( geom, zValue );
511bool QgsSfcgalEngine::addMValue( sfcgal::geometry *geom,
double mValue, QString *errorMsg )
513#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
517 throw QgsNotSupportedException( QObject::tr(
"Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"addMValue" ) );
519 sfcgal::errorHandler()->clearText( errorMsg );
520 CHECK_NOT_NULL( geom,
false );
522 return sfcgal_geometry_force_m( geom, mValue );
526bool QgsSfcgalEngine::dropZValue( sfcgal::geometry *geom, QString *errorMsg )
528#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
531 throw QgsNotSupportedException( QObject::tr(
"Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"dropZValue" ) );
533 sfcgal::errorHandler()->clearText( errorMsg );
534 CHECK_NOT_NULL( geom,
false );
536 return sfcgal_geometry_drop_z( geom );
540bool QgsSfcgalEngine::dropMValue( sfcgal::geometry *geom, QString *errorMsg )
542#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
545 throw QgsNotSupportedException( QObject::tr(
"Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"dropMValue" ) );
547 sfcgal::errorHandler()->clearText( errorMsg );
548 CHECK_NOT_NULL( geom,
false );
550 return sfcgal_geometry_drop_m( geom );
554void QgsSfcgalEngine::swapXy( sfcgal::geometry *geom, QString *errorMsg )
556#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
559 throw QgsNotSupportedException( QObject::tr(
"Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"swapXy" ) );
561 sfcgal::errorHandler()->clearText( errorMsg );
562 CHECK_NOT_NULL( geom,
void() );
564 sfcgal_geometry_swap_xy( geom );
568bool QgsSfcgalEngine::isEqual(
const sfcgal::geometry *geomA,
const sfcgal::geometry *geomB,
double tolerance, QString *errorMsg )
570#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
575 throw QgsNotSupportedException( QObject::tr(
"Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"isEqual" ) );
577 sfcgal::errorHandler()->clearText( errorMsg );
578 CHECK_NOT_NULL( geomA,
false );
579 CHECK_NOT_NULL( geomB,
false );
581 bool result = sfcgal_geometry_is_almost_equals( geomA, geomB, tolerance );
582 CHECK_SUCCESS( errorMsg,
false );
588bool QgsSfcgalEngine::isEmpty(
const sfcgal::geometry *geom, QString *errorMsg )
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 );
595bool QgsSfcgalEngine::isValid(
const sfcgal::geometry *geom, QString *errorMsg,
QgsGeometry *errorLoc )
597 sfcgal::errorHandler()->clearText( errorMsg );
598 CHECK_NOT_NULL( geom,
false );
602 sfcgal::geometry *location;
603 result = sfcgal_geometry_is_valid_detail( geom, &reason, &location );
605 CHECK_SUCCESS( errorMsg,
false );
607 if ( reason && strlen( reason ) )
609 sfcgal::errorHandler()->addText( QString( reason ) );
613 if ( location && errorLoc )
615 std::unique_ptr<QgsAbstractGeometry> locationGeom = toAbstractGeometry( location, errorMsg );
616 CHECK_SUCCESS( errorMsg,
false );
617 errorLoc->
addPartV2( locationGeom.release() );
623bool QgsSfcgalEngine::isSimple(
const sfcgal::geometry *geom, QString *errorMsg )
625#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
628 throw QgsNotSupportedException( QObject::tr(
"Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"isSimple" ) );
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 );
636sfcgal::shared_geom QgsSfcgalEngine::boundary(
const sfcgal::geometry *geom, QString *errorMsg )
638#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
641 throw QgsNotSupportedException( QObject::tr(
"Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"boundary" ) );
643 sfcgal::errorHandler()->clearText( errorMsg );
644 CHECK_NOT_NULL( geom,
nullptr );
646 sfcgal::geometry *boundary = sfcgal_geometry_boundary( geom );
647 CHECK_SUCCESS( errorMsg,
nullptr );
649 return sfcgal::make_shared_geom( boundary );
653QgsPoint QgsSfcgalEngine::centroid(
const sfcgal::geometry *geom, QString *errorMsg )
655#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
658 throw QgsNotSupportedException( QObject::tr(
"Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"centroid" ) );
660 sfcgal::errorHandler()->clearText( errorMsg );
663 const sfcgal::geometry *result =
nullptr;
664 if ( sfcgal_geometry_is_3d( geom ) )
665 result = sfcgal_geometry_centroid_3d( geom );
667 result = sfcgal_geometry_centroid( geom );
669 CHECK_SUCCESS( errorMsg,
QgsPoint() );
670 CHECK_NOT_NULL( result,
QgsPoint() );
672 QByteArray wkbArray = QgsSfcgalEngine::toWkb( result, errorMsg );
681sfcgal::shared_geom QgsSfcgalEngine::translate(
const sfcgal::geometry *geom,
const QgsVector3D &translation, QString *errorMsg )
683#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
685 ( void ) translation;
687 throw QgsNotSupportedException( QObject::tr(
"Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"translate" ) );
689 sfcgal::errorHandler()->clearText( errorMsg );
690 CHECK_NOT_NULL( geom,
nullptr );
692 sfcgal::geometry *result;
693 if ( sfcgal_geometry_is_3d( geom ) )
694 result = sfcgal_geometry_translate_3d( geom, translation.
x(), translation.
y(), translation.
z() );
696 result = sfcgal_geometry_translate_2d( geom, translation.
x(), translation.
y() );
697 CHECK_SUCCESS( errorMsg,
nullptr );
699 return sfcgal::make_shared_geom( result );
703sfcgal::shared_geom QgsSfcgalEngine::scale(
const sfcgal::geometry *geom,
const QgsVector3D &scaleFactor,
const QgsPoint ¢er, QString *errorMsg )
705 sfcgal::errorHandler()->clearText( errorMsg );
706 CHECK_NOT_NULL( geom,
nullptr );
708 sfcgal::geometry *result;
711 result = sfcgal_geometry_scale_3d( geom, scaleFactor.
x(), scaleFactor.
y(), scaleFactor.
z() );
715 const double centerZ = center.
is3D() ? center.
z() : 0;
716 result = sfcgal_geometry_scale_3d_around_center( geom, scaleFactor.
x(), scaleFactor.
y(), scaleFactor.
z(), center.
x(), center.
y(), centerZ );
719 CHECK_SUCCESS( errorMsg,
nullptr );
720 return sfcgal::make_shared_geom( result );
723sfcgal::shared_geom QgsSfcgalEngine::rotate2D(
const sfcgal::geometry *geom,
double angle,
const QgsPoint ¢er, QString *errorMsg )
725 sfcgal::errorHandler()->clearText( errorMsg );
726 CHECK_NOT_NULL( geom,
nullptr );
728 sfcgal::geometry *result = sfcgal_geometry_rotate_2d( geom, angle, center.
x(), center.
y() );
730 CHECK_SUCCESS( errorMsg,
nullptr );
731 return sfcgal::make_shared_geom( result );
734sfcgal::shared_geom QgsSfcgalEngine::rotate3D(
const sfcgal::geometry *geom,
double angle,
const QgsVector3D &axisVector,
const QgsPoint ¢er, QString *errorMsg )
736 sfcgal::errorHandler()->clearText( errorMsg );
737 CHECK_NOT_NULL( geom,
nullptr );
739 sfcgal::geometry *result;
742 result = sfcgal_geometry_rotate_3d( geom, angle, axisVector.
x(), axisVector.
y(), axisVector.
z() );
746 result = sfcgal_geometry_rotate_3d_around_center( geom, angle, axisVector.
x(), axisVector.
y(), axisVector.
z(), center.
x(), center.
y(), center.
z() );
749 CHECK_SUCCESS( errorMsg,
nullptr );
750 return sfcgal::make_shared_geom( result );
753double QgsSfcgalEngine::distance(
const sfcgal::geometry *geomA,
const sfcgal::geometry *geomB, QString *errorMsg )
755 double out = geomgeom_to_primtype<double>( sfcgal_geometry_distance, sfcgal_geometry_distance_3d, geomA, geomB, errorMsg );
756 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
760bool QgsSfcgalEngine::distanceWithin(
const sfcgal::geometry *geomA,
const sfcgal::geometry *geomB,
double maxdistance, QString *errorMsg )
762 double dist = QgsSfcgalEngine::distance( geomA, geomB, errorMsg );
763 CHECK_SUCCESS( errorMsg,
false );
765 return dist <= maxdistance;
768double QgsSfcgalEngine::area(
const sfcgal::geometry *geom, QString *errorMsg )
770 double out = geom_to_primtype<double>( sfcgal_geometry_area, sfcgal_geometry_area_3d, geom, errorMsg );
771 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
775double QgsSfcgalEngine::length(
const sfcgal::geometry *geom, QString *errorMsg )
777#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
780 throw QgsNotSupportedException( QObject::tr(
"Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"length" ) );
782 double out = geom_to_primtype<double>( sfcgal_geometry_length, sfcgal_geometry_length_3d, geom, errorMsg );
783 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
788bool QgsSfcgalEngine::intersects(
const sfcgal::geometry *geomA,
const sfcgal::geometry *geomB, QString *errorMsg )
790 int res = geomgeom_to_primtype<int>( sfcgal_geometry_intersects, sfcgal_geometry_intersects_3d, geomA, geomB, errorMsg );
791 CHECK_SUCCESS( errorMsg,
false );
792 return static_cast<bool>( res );
795sfcgal::shared_geom QgsSfcgalEngine::intersection(
const sfcgal::geometry *geomA,
const sfcgal::geometry *geomB, QString *errorMsg )
797 sfcgal::shared_geom out = geomgeom_to_geom( sfcgal_geometry_intersection, sfcgal_geometry_intersection_3d, geomA, geomB, errorMsg );
798 CHECK_SUCCESS( errorMsg,
nullptr );
802sfcgal::shared_geom QgsSfcgalEngine::difference(
const sfcgal::geometry *geomA,
const sfcgal::geometry *geomB, QString *errorMsg )
804 sfcgal::shared_geom out = geomgeom_to_geom( sfcgal_geometry_difference, sfcgal_geometry_difference_3d, geomA, geomB, errorMsg );
805 CHECK_SUCCESS( errorMsg,
nullptr );
809sfcgal::shared_geom QgsSfcgalEngine::combine(
const QVector<sfcgal::shared_geom> &geomList, QString *errorMsg )
811 sfcgal::errorHandler()->clearText( errorMsg );
812 sfcgal::geometry *combined =
nullptr;
813 for ( sfcgal::shared_geom other : geomList )
817 combined = other.get();
821 if ( sfcgal_geometry_is_3d( other.get() ) || sfcgal_geometry_is_3d( combined ) )
822 combined = sfcgal_geometry_union_3d( combined, other.get() );
824 combined = sfcgal_geometry_union( combined, other.get() );
827 sfcgal::errorHandler()->addText(
"SFCGAL produced null result." );
829 CHECK_SUCCESS( errorMsg,
nullptr );
832 return sfcgal::make_shared_geom( combined );
835sfcgal::shared_geom QgsSfcgalEngine::triangulate(
const sfcgal::geometry *geom, QString *errorMsg )
837 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_triangulate_2dz,
nullptr, geom, errorMsg );
838 CHECK_SUCCESS( errorMsg,
nullptr );
842bool QgsSfcgalEngine::covers(
const sfcgal::geometry *geomA,
const sfcgal::geometry *geomB, QString *errorMsg )
844 int res = geomgeom_to_primtype<int>( sfcgal_geometry_covers, sfcgal_geometry_covers_3d, geomA, geomB, errorMsg );
845 CHECK_SUCCESS( errorMsg,
false );
846 return static_cast<bool>( res );
849sfcgal::shared_geom QgsSfcgalEngine::envelope(
const sfcgal::geometry *geom, QString *errorMsg )
851#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
854 throw QgsNotSupportedException( QObject::tr(
"Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"envelope" ) );
856 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_envelope, sfcgal_geometry_envelope_3d, geom, errorMsg );
857 CHECK_SUCCESS( errorMsg,
nullptr );
862sfcgal::shared_geom QgsSfcgalEngine::convexHull(
const sfcgal::geometry *geom, QString *errorMsg )
864 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_convexhull, sfcgal_geometry_convexhull_3d, geom, errorMsg );
865 CHECK_SUCCESS( errorMsg,
nullptr );
869sfcgal::shared_geom QgsSfcgalEngine::offsetCurve(
const sfcgal::geometry *geom,
double distance,
int,
Qgis::JoinStyle, QString *errorMsg )
871 sfcgal::errorHandler()->clearText( errorMsg );
872 CHECK_NOT_NULL( geom,
nullptr );
874 sfcgal::geometry *result =
nullptr;
875 result = sfcgal_geometry_offset_polygon( geom, distance );
877 CHECK_SUCCESS( errorMsg,
nullptr );
879 return sfcgal::make_shared_geom( result );
882sfcgal::shared_geom QgsSfcgalEngine::buffer2D(
const sfcgal::geometry *geom,
double radius,
int segments,
Qgis::JoinStyle joinStyle, QString *errorMsg )
885 qWarning() << ( u
"Buffer not implemented for %1! Defaulting to round join."_s );
887 return offsetCurve( geom, radius, segments, joinStyle, errorMsg );
890sfcgal::shared_geom QgsSfcgalEngine::buffer3D(
const sfcgal::geometry *geom,
double radius,
int segments,
Qgis::JoinStyle3D joinStyle3D, QString *errorMsg )
892 sfcgal::errorHandler()->clearText( errorMsg );
893 CHECK_NOT_NULL( geom,
nullptr );
895 sfcgal_buffer3d_type_t buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_FLAT;
896 switch ( joinStyle3D )
899 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_FLAT;
902 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_ROUND;
905 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_CYLSPHERE;
909 sfcgal::geometry *result = sfcgal_geometry_buffer3d( geom, radius, segments, buffer_type );
910 CHECK_SUCCESS( errorMsg,
nullptr );
912 return sfcgal::make_shared_geom( result );
915sfcgal::shared_geom QgsSfcgalEngine::extrude(
const sfcgal::geometry *geom,
const QgsVector3D &extrusion, QString *errorMsg )
917 sfcgal::errorHandler()->clearText( errorMsg );
918 CHECK_NOT_NULL( geom,
nullptr );
920 sfcgal_geometry_t *solid = sfcgal_geometry_extrude( geom, extrusion.
x(), extrusion.
y(), extrusion.
z() );
922 CHECK_SUCCESS( errorMsg,
nullptr );
927 sfcgal_geometry_t *polySurface = sfcgal_polyhedral_surface_create();
928 for (
unsigned int shellIdx = 0; shellIdx < sfcgal_solid_num_shells( solid ); ++shellIdx )
930 const sfcgal_geometry_t *shell = sfcgal_solid_shell_n( solid, shellIdx );
931#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
932 for (
unsigned int polyIdx = 0; polyIdx < sfcgal_polyhedral_surface_num_patches( shell ); ++polyIdx )
934 const sfcgal_geometry_t *patch = sfcgal_polyhedral_surface_patch_n( shell, polyIdx );
935 sfcgal_polyhedral_surface_add_patch( polySurface, sfcgal_geometry_clone( patch ) );
938 for (
unsigned int polyIdx = 0; polyIdx < sfcgal_polyhedral_surface_num_polygons( shell ); ++polyIdx )
940 const sfcgal_geometry_t *patch = sfcgal_polyhedral_surface_polygon_n( shell, polyIdx );
941 sfcgal_polyhedral_surface_add_polygon( polySurface, sfcgal_geometry_clone( patch ) );
946 sfcgal_geometry_delete( solid );
948 CHECK_SUCCESS( errorMsg,
nullptr );
950 return sfcgal::make_shared_geom( polySurface );
953sfcgal::shared_geom QgsSfcgalEngine::simplify(
const sfcgal::geometry *geom,
double tolerance,
bool preserveTopology, QString *errorMsg )
955#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
958 ( void ) preserveTopology;
960 throw QgsNotSupportedException( QObject::tr(
"Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg(
"boundary" ) );
962 sfcgal::errorHandler()->clearText( errorMsg );
963 CHECK_NOT_NULL( geom,
nullptr );
965 sfcgal::geometry *result = sfcgal_geometry_simplify( geom, tolerance, preserveTopology );
966 CHECK_SUCCESS( errorMsg,
nullptr );
968 return sfcgal::make_shared_geom( result );
972sfcgal::shared_geom QgsSfcgalEngine::approximateMedialAxis(
const sfcgal::geometry *geom, QString *errorMsg )
974 sfcgal::errorHandler()->clearText( errorMsg );
975 CHECK_NOT_NULL( geom,
nullptr );
977 sfcgal::geometry *result = sfcgal_geometry_approximate_medial_axis( geom );
978 CHECK_SUCCESS( errorMsg,
nullptr );
980 return sfcgal::make_shared_geom( result );
984#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 3, 0 )
985sfcgal::shared_geom QgsSfcgalEngine::transform(
const sfcgal::geometry *geom,
const QMatrix4x4 &mat, QString *errorMsg )
987 sfcgal::errorHandler()->clearText( errorMsg );
988 CHECK_NOT_NULL( geom,
nullptr );
990 sfcgal::geometry *result;
991 result = sfcgal_geometry_transform( geom, mat.constData() );
993 CHECK_SUCCESS( errorMsg,
nullptr );
994 return sfcgal::make_shared_geom( result );
997std::unique_ptr<QgsSfcgalGeometry> QgsSfcgalEngine::toSfcgalGeometry( sfcgal::shared_prim &prim, sfcgal::primitiveType type, QString *errorMsg )
999 sfcgal::errorHandler()->clearText( errorMsg );
1000 CHECK_NOT_NULL( prim.get(),
nullptr );
1002 return std::make_unique<QgsSfcgalGeometry>( prim, type );
1005sfcgal::shared_prim QgsSfcgalEngine::createCube(
double size, QString *errorMsg )
1007 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_CUBE );
1008 CHECK_SUCCESS( errorMsg,
nullptr );
1010 sfcgal_primitive_set_parameter_double( result,
"size", size );
1011 CHECK_SUCCESS( errorMsg,
nullptr );
1013 return sfcgal::make_shared_prim( result );
1016sfcgal::shared_geom QgsSfcgalEngine::primitiveAsPolyhedral(
const sfcgal::primitive *prim,
const QMatrix4x4 &mat, QString *errorMsg )
1018 sfcgal::errorHandler()->clearText( errorMsg );
1019 CHECK_NOT_NULL( prim,
nullptr );
1021 sfcgal::geometry *result = sfcgal_primitive_as_polyhedral_surface( prim );
1022 CHECK_SUCCESS( errorMsg,
nullptr );
1024 if ( !mat.isIdentity() )
1026 sfcgal::geometry *result2 = sfcgal_geometry_transform( result, mat.constData() );
1027 sfcgal_geometry_delete( result );
1029 CHECK_SUCCESS( errorMsg,
nullptr );
1032 return sfcgal::make_shared_geom( result );
1035bool QgsSfcgalEngine::primitiveIsEqual(
const sfcgal::primitive *primA,
const sfcgal::primitive *primB,
double tolerance, QString *errorMsg )
1037 sfcgal::errorHandler()->clearText( errorMsg );
1038 CHECK_NOT_NULL( primA,
false );
1039 CHECK_NOT_NULL( primB,
false );
1041 bool result = sfcgal_primitive_is_almost_equals( primA, primB, tolerance );
1042 CHECK_SUCCESS( errorMsg,
false );
1047sfcgal::shared_prim QgsSfcgalEngine::primitiveClone(
const sfcgal::primitive *prim, QString *errorMsg )
1049 sfcgal::errorHandler()->clearText( errorMsg );
1050 CHECK_NOT_NULL( prim,
nullptr );
1052 sfcgal::primitive *result = sfcgal_primitive_clone( prim );
1054 CHECK_SUCCESS( errorMsg,
nullptr );
1055 CHECK_NOT_NULL( result,
nullptr );
1057 return sfcgal::make_shared_prim( result );
1060double QgsSfcgalEngine::primitiveArea(
const sfcgal::primitive *prim,
bool withDiscretization, QString *errorMsg )
1062 sfcgal::errorHandler()->clearText( errorMsg );
1063 CHECK_NOT_NULL( prim, std::numeric_limits<double>::quiet_NaN() );
1065 double out = sfcgal_primitive_area( prim, withDiscretization );
1066 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1070double QgsSfcgalEngine::primitiveVolume(
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_volume( prim, withDiscretization );
1076 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1080void sfcgal::to_json( json &j,
const sfcgal::PrimitiveParameterDesc &p )
1085 if ( std::holds_alternative<int>( p.value ) )
1087 j[
"value"] = std::get<int>( p.value );
1089 else if ( std::holds_alternative<double>( p.value ) )
1091 j[
"value"] = std::get<double>( p.value );
1093 else if ( std::holds_alternative<QgsPoint>( p.value ) )
1095 QgsPoint point = std::get<QgsPoint>( p.value );
1096 double z = std::numeric_limits<double>::quiet_NaN();
1097 double m = std::numeric_limits<double>::quiet_NaN();
1102 j[
"value"] = std::vector<double> { point.
x(), point.
y(), z, m };
1104 else if ( std::holds_alternative<QgsVector3D>( p.value ) )
1106 QgsVector3D vect = std::get<QgsVector3D>( p.value );
1107 j[
"value"] = std::vector<double> { vect.
x(), vect.
y(), vect.
z() };
1110 throw json::type_error::create( 306, u
"Unknown type '%1'."_s.arg( p.type.c_str() ).toStdString(),
nullptr );
1113void sfcgal::from_json(
const json &j, sfcgal::PrimitiveParameterDesc &p )
1115 j.at(
"name" ).get_to( p.name );
1116 j.at(
"type" ).get_to( p.type );
1117 if ( j.contains(
"value" ) )
1119 json value = j.at(
"value" );
1120 if ( p.type ==
"int" )
1122 p.value = value.get<
int>();
1124 else if ( p.type ==
"double" )
1126 p.value = value.get<
double>();
1128 else if ( p.type ==
"point3" )
1130 std::vector<double> vect;
1131 vect = value.get<std::vector<double>>();
1135 ( vect.size() > 2 ? vect[2] : std::numeric_limits<double>::quiet_NaN() ),
1136 ( vect.size() > 3 ? vect[3] : std::numeric_limits<double>::quiet_NaN() )
1140 else if ( p.type ==
"vector3" )
1142 std::vector<double> vect;
1143 vect = value.get<std::vector<double>>();
1147 ( vect.size() > 2 ? vect[2] : std::numeric_limits<double>::quiet_NaN() ),
1148 ( vect.size() > 3 ? vect[3] : std::numeric_limits<double>::quiet_NaN() )
1153 throw json::type_error::create( 306, u
"Unknown type '%1'."_s.arg( p.type.c_str() ).toStdString(),
nullptr );
1157QVector<sfcgal::PrimitiveParameterDesc> QgsSfcgalEngine::primitiveParameters(
const sfcgal::primitive *prim, QString *errorMsg )
1159 sfcgal::errorHandler()->clearText( errorMsg );
1160 CHECK_NOT_NULL( prim, QVector<sfcgal::PrimitiveParameterDesc>() );
1162 char *jsonChars =
nullptr;
1164 sfcgal_primitive_parameters( prim, &jsonChars, &len );
1165 CHECK_SUCCESS( errorMsg, QVector<sfcgal::PrimitiveParameterDesc>() );
1167 std::string jsonString( jsonChars, len );
1168 sfcgal_free_buffer( jsonChars );
1170 QVector<sfcgal::PrimitiveParameterDesc> result;
1173 const auto jParams = json::parse( jsonString );
1174 for (
const auto &jParam : jParams )
1176 result.append( jParam.get<sfcgal::PrimitiveParameterDesc>() );
1179 catch ( json::exception &e )
1181 sfcgal::errorHandler()->addText( u
"Caught json exception for json: %1. Error: %2"_s.arg( jsonString.c_str() ).arg( e.what() ) );
1187QVariant QgsSfcgalEngine::primitiveParameter(
const sfcgal::primitive *prim,
const QString &name, QString *errorMsg )
1189 sfcgal::errorHandler()->clearText( errorMsg );
1190 CHECK_NOT_NULL( prim, QVariant() );
1192 char *jsonChars =
nullptr;
1194 sfcgal_primitive_parameter( prim, name.toStdString().c_str(), &jsonChars, &len );
1195 CHECK_SUCCESS( errorMsg, QVariant() );
1197 std::string jsonString( jsonChars, len );
1198 sfcgal_free_buffer( jsonChars );
1203 const auto jParam = json::parse( jsonString );
1204 sfcgal::PrimitiveParameterDesc param = jParam.get<sfcgal::PrimitiveParameterDesc>();
1205 result = QVariant::fromStdVariant( param.value );
1207 catch ( json::exception &e )
1209 sfcgal::errorHandler()->addText( u
"Caught json exception for json: %1. Error: %2"_s.arg( jsonString.c_str() ).arg( e.what() ) );
1215void QgsSfcgalEngine::primitiveSetParameter( sfcgal::primitive *prim,
const QString &name,
const QVariant &value, QString *errorMsg )
1217 sfcgal::errorHandler()->clearText( errorMsg );
1218 CHECK_NOT_NULL( prim,
void() );
1223 sfcgal::PrimitiveParameterDesc paramDesc;
1224 paramDesc.name = name.toStdString();
1225 paramDesc.type = value.typeName();
1226 if ( paramDesc.type ==
"int" )
1227 paramDesc.value = value.toInt();
1228 else if ( paramDesc.type ==
"double" )
1229 paramDesc.value = value.toDouble();
1230 else if ( value.canConvert<
QgsPoint>() )
1231 paramDesc.value = value.value<
QgsPoint>();
1235 sfcgal::to_json( jParam, paramDesc );
1236 std::string jsonStr = jParam.dump();
1237 sfcgal_primitive_set_parameter( prim, name.toStdString().c_str(), jsonStr.c_str() );
1238 CHECK_SUCCESS( errorMsg,
void() );
1242 sfcgal::errorHandler()->addText( u
"Caught json exception"_s );
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.