QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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 QByteArray wkbArray( wkbHex, static_cast<int>( len ) );
370
371#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
372 sfcgal_free_buffer( wkbHex );
373#else
374 free( wkbHex );
375#endif
376
377 CHECK_SUCCESS( errorMsg, QByteArray() );
378 return wkbArray;
379}
380
381QString QgsSfcgalEngine::toWkt( const sfcgal::geometry *geom, int numDecimals, QString *errorMsg )
382{
383 sfcgal::errorHandler()->clearText( errorMsg );
384 CHECK_NOT_NULL( geom, QString() );
385
386 char *wkt;
387 size_t len = 0;
388 sfcgal_geometry_as_text_decim( geom, numDecimals, &wkt, &len );
389 CHECK_SUCCESS( errorMsg, QString() );
390
391 std::string wktString( wkt, len );
392#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
393 sfcgal_free_buffer( wkt );
394#else
395 free( wkt );
396#endif
397 return QString::fromStdString( wktString );
398}
399
400Qgis::WkbType QgsSfcgalEngine::wkbType( const sfcgal::geometry *geom, QString *errorMsg )
401{
402 sfcgal::errorHandler()->clearText( errorMsg );
403 CHECK_NOT_NULL( geom, Qgis::WkbType::Unknown );
404
405 sfcgal_geometry_type_t type = sfcgal_geometry_type_id( geom );
406 CHECK_SUCCESS( errorMsg, Qgis::WkbType::Unknown );
407
408 int wkbType = type;
409 if ( sfcgal_geometry_is_3d( geom ) )
410 wkbType += 1000;
411
412 if ( sfcgal_geometry_is_measured( geom ) )
413 wkbType += 2000;
414
415 Qgis::WkbType qgisType = static_cast<Qgis::WkbType>( wkbType );
416 if ( qgisType >= Qgis::WkbType::Unknown && qgisType <= Qgis::WkbType::TriangleZM )
417 return qgisType;
418
419 sfcgal::errorHandler()->addText( u"WKB type '%1' is not known from QGIS"_s.arg( wkbType ) );
421}
422
423int QgsSfcgalEngine::dimension( const sfcgal::geometry *geom, QString *errorMsg )
424{
425#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
426 ( void ) geom;
427 ( void ) errorMsg;
428 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "dimension" ) );
429#else
430 int out = geom_to_primtype<int>( sfcgal_geometry_dimension, nullptr, geom, errorMsg );
431 CHECK_SUCCESS( errorMsg, std::numeric_limits<int>::quiet_NaN() );
432 return out;
433#endif
434}
435
436int QgsSfcgalEngine::partCount( const sfcgal::geometry *geom, QString *errorMsg )
437{
438 size_t out;
439 sfcgal::errorHandler()->clearText( errorMsg );
440 CHECK_NOT_NULL( geom, -1 );
441
442 sfcgal_geometry_type_t type = sfcgal_geometry_type_id( geom );
443 CHECK_SUCCESS( errorMsg, -1 );
444
445 switch ( type )
446 {
447 case SFCGAL_TYPE_MULTIPOINT:
448 case SFCGAL_TYPE_MULTILINESTRING:
449 case SFCGAL_TYPE_MULTIPOLYGON:
450 case SFCGAL_TYPE_MULTISOLID:
451 case SFCGAL_TYPE_GEOMETRYCOLLECTION:
452#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
453 out = sfcgal_geometry_num_geometries( geom );
454#else
455 out = sfcgal_geometry_collection_num_geometries( geom );
456#endif
457 break;
458 case SFCGAL_TYPE_POLYGON:
459 out = sfcgal_polygon_num_interior_rings( geom ) + 1;
460 break;
461 case SFCGAL_TYPE_SOLID:
462 out = sfcgal_solid_num_shells( geom );
463 break;
464 case SFCGAL_TYPE_POLYHEDRALSURFACE:
465#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
466 out = sfcgal_polyhedral_surface_num_patches( geom );
467#else
468 out = sfcgal_polyhedral_surface_num_polygons( geom );
469#endif
470 break;
471 case SFCGAL_TYPE_TRIANGULATEDSURFACE:
472#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
473 out = sfcgal_triangulated_surface_num_patches( geom );
474#else
475 out = sfcgal_triangulated_surface_num_triangles( geom );
476#endif
477 break;
478 case SFCGAL_TYPE_LINESTRING:
479 out = sfcgal_linestring_num_points( geom );
480 break;
481 case SFCGAL_TYPE_TRIANGLE:
482 out = 3;
483 break;
484 case SFCGAL_TYPE_POINT:
485 out = 1;
486 break;
487 default:
488 out = -1;
489 }
490
491 CHECK_SUCCESS( errorMsg, -1 );
492
493 return static_cast<int>( out );
494}
495
496bool QgsSfcgalEngine::addZValue( sfcgal::geometry *geom, double zValue, QString *errorMsg )
497{
498#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
499 ( void ) geom;
500 ( void ) zValue;
501 ( void ) errorMsg;
502 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "addZValue" ) );
503#else
504 sfcgal::errorHandler()->clearText( errorMsg );
505 CHECK_NOT_NULL( geom, false );
506
507 return sfcgal_geometry_force_z( geom, zValue );
508#endif
509}
510
511bool QgsSfcgalEngine::addMValue( sfcgal::geometry *geom, double mValue, QString *errorMsg )
512{
513#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
514 ( void ) geom;
515 ( void ) mValue;
516 ( void ) errorMsg;
517 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "addMValue" ) );
518#else
519 sfcgal::errorHandler()->clearText( errorMsg );
520 CHECK_NOT_NULL( geom, false );
521
522 return sfcgal_geometry_force_m( geom, mValue );
523#endif
524}
525
526bool QgsSfcgalEngine::dropZValue( sfcgal::geometry *geom, QString *errorMsg )
527{
528#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
529 ( void ) geom;
530 ( void ) errorMsg;
531 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "dropZValue" ) );
532#else
533 sfcgal::errorHandler()->clearText( errorMsg );
534 CHECK_NOT_NULL( geom, false );
535
536 return sfcgal_geometry_drop_z( geom );
537#endif
538}
539
540bool QgsSfcgalEngine::dropMValue( sfcgal::geometry *geom, QString *errorMsg )
541{
542#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
543 ( void ) geom;
544 ( void ) errorMsg;
545 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "dropMValue" ) );
546#else
547 sfcgal::errorHandler()->clearText( errorMsg );
548 CHECK_NOT_NULL( geom, false );
549
550 return sfcgal_geometry_drop_m( geom );
551#endif
552}
553
554void QgsSfcgalEngine::swapXy( sfcgal::geometry *geom, QString *errorMsg )
555{
556#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
557 ( void ) geom;
558 ( void ) errorMsg;
559 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "swapXy" ) );
560#else
561 sfcgal::errorHandler()->clearText( errorMsg );
562 CHECK_NOT_NULL( geom, void() );
563
564 sfcgal_geometry_swap_xy( geom );
565#endif
566}
567
568bool QgsSfcgalEngine::isEqual( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, double tolerance, QString *errorMsg )
569{
570#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
571 ( void ) geomA;
572 ( void ) geomB;
573 ( void ) tolerance;
574 ( void ) errorMsg;
575 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "isEqual" ) );
576#else
577 sfcgal::errorHandler()->clearText( errorMsg );
578 CHECK_NOT_NULL( geomA, false );
579 CHECK_NOT_NULL( geomB, false );
580
581 bool result = sfcgal_geometry_is_almost_equals( geomA, geomB, tolerance );
582 CHECK_SUCCESS( errorMsg, false );
583
584 return result;
585#endif
586}
587
588bool QgsSfcgalEngine::isEmpty( const sfcgal::geometry *geom, QString *errorMsg )
589{
590 int res = geom_to_primtype<int>( sfcgal_geometry_is_empty, nullptr, geom, errorMsg );
591 CHECK_SUCCESS( errorMsg, false );
592 return static_cast<bool>( res );
593}
594
595bool QgsSfcgalEngine::isValid( const sfcgal::geometry *geom, QString *errorMsg, QgsGeometry *errorLoc )
596{
597 sfcgal::errorHandler()->clearText( errorMsg );
598 CHECK_NOT_NULL( geom, false );
599
600 bool result = false;
601 char *reason;
602 sfcgal::geometry *location;
603 result = sfcgal_geometry_is_valid_detail( geom, &reason, &location );
604
605 CHECK_SUCCESS( errorMsg, false );
606
607 if ( reason && strlen( reason ) )
608 {
609 sfcgal::errorHandler()->addText( QString( reason ) );
610 free( reason );
611 }
612
613 if ( location && errorLoc )
614 {
615 std::unique_ptr<QgsAbstractGeometry> locationGeom = toAbstractGeometry( location, errorMsg );
616 CHECK_SUCCESS( errorMsg, false );
617 errorLoc->addPartV2( locationGeom.release() );
618 }
619
620 return result;
621}
622
623bool QgsSfcgalEngine::isSimple( const sfcgal::geometry *geom, QString *errorMsg )
624{
625#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
626 ( void ) geom;
627 ( void ) errorMsg;
628 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "isSimple" ) );
629#else
630 int res = geom_to_primtype<int>( sfcgal_geometry_is_simple, nullptr, geom, errorMsg );
631 CHECK_SUCCESS( errorMsg, false );
632 return static_cast<bool>( res );
633#endif
634}
635
636sfcgal::shared_geom QgsSfcgalEngine::boundary( const sfcgal::geometry *geom, QString *errorMsg )
637{
638#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
639 ( void ) geom;
640 ( void ) errorMsg;
641 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "boundary" ) );
642#else
643 sfcgal::errorHandler()->clearText( errorMsg );
644 CHECK_NOT_NULL( geom, nullptr );
645
646 sfcgal::geometry *boundary = sfcgal_geometry_boundary( geom );
647 CHECK_SUCCESS( errorMsg, nullptr );
648
649 return sfcgal::make_shared_geom( boundary );
650#endif
651}
652
653QgsPoint QgsSfcgalEngine::centroid( const sfcgal::geometry *geom, QString *errorMsg )
654{
655#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
656 ( void ) geom;
657 ( void ) errorMsg;
658 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "centroid" ) );
659#else
660 sfcgal::errorHandler()->clearText( errorMsg );
661 CHECK_NOT_NULL( geom, QgsPoint() );
662
663 const sfcgal::geometry *result = nullptr;
664 if ( sfcgal_geometry_is_3d( geom ) )
665 result = sfcgal_geometry_centroid_3d( geom );
666 else
667 result = sfcgal_geometry_centroid( geom );
668
669 CHECK_SUCCESS( errorMsg, QgsPoint() );
670 CHECK_NOT_NULL( result, QgsPoint() );
671
672 QByteArray wkbArray = QgsSfcgalEngine::toWkb( result, errorMsg );
673 QgsConstWkbPtr wkbPtr( wkbArray );
674 QgsPoint out;
675 out.fromWkb( wkbPtr );
676
677 return out;
678#endif
679}
680
681sfcgal::shared_geom QgsSfcgalEngine::translate( const sfcgal::geometry *geom, const QgsVector3D &translation, QString *errorMsg )
682{
683#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
684 ( void ) geom;
685 ( void ) translation;
686 ( void ) errorMsg;
687 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "translate" ) );
688#else
689 sfcgal::errorHandler()->clearText( errorMsg );
690 CHECK_NOT_NULL( geom, nullptr );
691
692 sfcgal::geometry *result;
693 if ( sfcgal_geometry_is_3d( geom ) )
694 result = sfcgal_geometry_translate_3d( geom, translation.x(), translation.y(), translation.z() );
695 else
696 result = sfcgal_geometry_translate_2d( geom, translation.x(), translation.y() );
697 CHECK_SUCCESS( errorMsg, nullptr );
698
699 return sfcgal::make_shared_geom( result );
700#endif
701}
702
703sfcgal::shared_geom QgsSfcgalEngine::scale( const sfcgal::geometry *geom, const QgsVector3D &scaleFactor, const QgsPoint &center, QString *errorMsg )
704{
705 sfcgal::errorHandler()->clearText( errorMsg );
706 CHECK_NOT_NULL( geom, nullptr );
707
708 sfcgal::geometry *result;
709 if ( center.isEmpty() )
710 {
711 result = sfcgal_geometry_scale_3d( geom, scaleFactor.x(), scaleFactor.y(), scaleFactor.z() );
712 }
713 else
714 {
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 );
717 }
718
719 CHECK_SUCCESS( errorMsg, nullptr );
720 return sfcgal::make_shared_geom( result );
721}
722
723sfcgal::shared_geom QgsSfcgalEngine::rotate2D( const sfcgal::geometry *geom, double angle, const QgsPoint &center, QString *errorMsg )
724{
725 sfcgal::errorHandler()->clearText( errorMsg );
726 CHECK_NOT_NULL( geom, nullptr );
727
728 sfcgal::geometry *result = sfcgal_geometry_rotate_2d( geom, angle, center.x(), center.y() );
729
730 CHECK_SUCCESS( errorMsg, nullptr );
731 return sfcgal::make_shared_geom( result );
732}
733
734sfcgal::shared_geom QgsSfcgalEngine::rotate3D( const sfcgal::geometry *geom, double angle, const QgsVector3D &axisVector, const QgsPoint &center, QString *errorMsg )
735{
736 sfcgal::errorHandler()->clearText( errorMsg );
737 CHECK_NOT_NULL( geom, nullptr );
738
739 sfcgal::geometry *result;
740 if ( center.isEmpty() )
741 {
742 result = sfcgal_geometry_rotate_3d( geom, angle, axisVector.x(), axisVector.y(), axisVector.z() );
743 }
744 else
745 {
746 result = sfcgal_geometry_rotate_3d_around_center( geom, angle, axisVector.x(), axisVector.y(), axisVector.z(), center.x(), center.y(), center.z() );
747 }
748
749 CHECK_SUCCESS( errorMsg, nullptr );
750 return sfcgal::make_shared_geom( result );
751}
752
753double QgsSfcgalEngine::distance( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
754{
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() );
757 return out;
758}
759
760bool QgsSfcgalEngine::distanceWithin( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, double maxdistance, QString *errorMsg )
761{
762 double dist = QgsSfcgalEngine::distance( geomA, geomB, errorMsg );
763 CHECK_SUCCESS( errorMsg, false );
764
765 return dist <= maxdistance;
766}
767
768double QgsSfcgalEngine::area( const sfcgal::geometry *geom, QString *errorMsg )
769{
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() );
772 return out;
773}
774
775double QgsSfcgalEngine::length( const sfcgal::geometry *geom, QString *errorMsg )
776{
777#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
778 ( void ) geom;
779 ( void ) errorMsg;
780 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "length" ) );
781#else
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() );
784 return out;
785#endif
786}
787
788bool QgsSfcgalEngine::intersects( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
789{
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 );
793}
794
795sfcgal::shared_geom QgsSfcgalEngine::intersection( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
796{
797 sfcgal::shared_geom out = geomgeom_to_geom( sfcgal_geometry_intersection, sfcgal_geometry_intersection_3d, geomA, geomB, errorMsg );
798 CHECK_SUCCESS( errorMsg, nullptr );
799 return out;
800}
801
802sfcgal::shared_geom QgsSfcgalEngine::difference( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
803{
804 sfcgal::shared_geom out = geomgeom_to_geom( sfcgal_geometry_difference, sfcgal_geometry_difference_3d, geomA, geomB, errorMsg );
805 CHECK_SUCCESS( errorMsg, nullptr );
806 return out;
807}
808
809sfcgal::shared_geom QgsSfcgalEngine::combine( const QVector<sfcgal::shared_geom> &geomList, QString *errorMsg )
810{
811 sfcgal::errorHandler()->clearText( errorMsg );
812 sfcgal::geometry *combined = nullptr;
813 for ( sfcgal::shared_geom other : geomList )
814 {
815 if ( !combined )
816 {
817 combined = other.get();
818 continue;
819 }
820
821 if ( sfcgal_geometry_is_3d( other.get() ) || sfcgal_geometry_is_3d( combined ) )
822 combined = sfcgal_geometry_union_3d( combined, other.get() );
823 else
824 combined = sfcgal_geometry_union( combined, other.get() );
825
826 if ( !combined )
827 sfcgal::errorHandler()->addText( "SFCGAL produced null result." );
828
829 CHECK_SUCCESS( errorMsg, nullptr );
830 }
831
832 return sfcgal::make_shared_geom( combined );
833}
834
835sfcgal::shared_geom QgsSfcgalEngine::triangulate( const sfcgal::geometry *geom, QString *errorMsg )
836{
837 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_triangulate_2dz, nullptr, geom, errorMsg );
838 CHECK_SUCCESS( errorMsg, nullptr );
839 return out;
840}
841
842bool QgsSfcgalEngine::covers( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
843{
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 );
847}
848
849sfcgal::shared_geom QgsSfcgalEngine::envelope( const sfcgal::geometry *geom, QString *errorMsg )
850{
851#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
852 ( void ) geom;
853 ( void ) errorMsg;
854 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "envelope" ) );
855#else
856 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_envelope, sfcgal_geometry_envelope_3d, geom, errorMsg );
857 CHECK_SUCCESS( errorMsg, nullptr );
858 return out;
859#endif
860}
861
862sfcgal::shared_geom QgsSfcgalEngine::convexHull( const sfcgal::geometry *geom, QString *errorMsg )
863{
864 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_convexhull, sfcgal_geometry_convexhull_3d, geom, errorMsg );
865 CHECK_SUCCESS( errorMsg, nullptr );
866 return out;
867}
868
869sfcgal::shared_geom QgsSfcgalEngine::offsetCurve( const sfcgal::geometry *geom, double distance, int, Qgis::JoinStyle, QString *errorMsg )
870{
871 sfcgal::errorHandler()->clearText( errorMsg );
872 CHECK_NOT_NULL( geom, nullptr );
873
874 sfcgal::geometry *result = nullptr;
875 result = sfcgal_geometry_offset_polygon( geom, distance );
876
877 CHECK_SUCCESS( errorMsg, nullptr );
878
879 return sfcgal::make_shared_geom( result );
880}
881
882sfcgal::shared_geom QgsSfcgalEngine::buffer2D( const sfcgal::geometry *geom, double radius, int segments, Qgis::JoinStyle joinStyle, QString *errorMsg )
883{
884 if ( joinStyle != Qgis::JoinStyle::Round )
885 qWarning() << ( u"Buffer not implemented for %1! Defaulting to round join."_s );
886
887 return offsetCurve( geom, radius, segments, joinStyle, errorMsg );
888}
889
890sfcgal::shared_geom QgsSfcgalEngine::buffer3D( const sfcgal::geometry *geom, double radius, int segments, Qgis::JoinStyle3D joinStyle3D, QString *errorMsg )
891{
892 sfcgal::errorHandler()->clearText( errorMsg );
893 CHECK_NOT_NULL( geom, nullptr );
894
895 sfcgal_buffer3d_type_t buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_FLAT;
896 switch ( joinStyle3D )
897 {
899 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_FLAT;
900 break;
902 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_ROUND;
903 break;
905 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_CYLSPHERE;
906 break;
907 }
908
909 sfcgal::geometry *result = sfcgal_geometry_buffer3d( geom, radius, segments, buffer_type );
910 CHECK_SUCCESS( errorMsg, nullptr );
911
912 return sfcgal::make_shared_geom( result );
913}
914
915sfcgal::shared_geom QgsSfcgalEngine::extrude( const sfcgal::geometry *geom, const QgsVector3D &extrusion, QString *errorMsg )
916{
917 sfcgal::errorHandler()->clearText( errorMsg );
918 CHECK_NOT_NULL( geom, nullptr );
919
920 sfcgal_geometry_t *solid = sfcgal_geometry_extrude( geom, extrusion.x(), extrusion.y(), extrusion.z() );
921
922 CHECK_SUCCESS( errorMsg, nullptr );
923
924 // sfcgal_geometry_extrude returns a SOLID
925 // This is not handled by QGIS
926 // convert it to a PolyhedralSurface
927 sfcgal_geometry_t *polySurface = sfcgal_polyhedral_surface_create();
928 for ( unsigned int shellIdx = 0; shellIdx < sfcgal_solid_num_shells( solid ); ++shellIdx )
929 {
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 )
933 {
934 const sfcgal_geometry_t *patch = sfcgal_polyhedral_surface_patch_n( shell, polyIdx );
935 sfcgal_polyhedral_surface_add_patch( polySurface, sfcgal_geometry_clone( patch ) );
936 }
937#else
938 for ( unsigned int polyIdx = 0; polyIdx < sfcgal_polyhedral_surface_num_polygons( shell ); ++polyIdx )
939 {
940 const sfcgal_geometry_t *patch = sfcgal_polyhedral_surface_polygon_n( shell, polyIdx );
941 sfcgal_polyhedral_surface_add_polygon( polySurface, sfcgal_geometry_clone( patch ) );
942 }
943#endif
944 }
945
946 sfcgal_geometry_delete( solid );
947
948 CHECK_SUCCESS( errorMsg, nullptr );
949
950 return sfcgal::make_shared_geom( polySurface );
951}
952
953sfcgal::shared_geom QgsSfcgalEngine::simplify( const sfcgal::geometry *geom, double tolerance, bool preserveTopology, QString *errorMsg )
954{
955#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
956 ( void ) geom;
957 ( void ) tolerance;
958 ( void ) preserveTopology;
959 ( void ) errorMsg;
960 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "boundary" ) );
961#else
962 sfcgal::errorHandler()->clearText( errorMsg );
963 CHECK_NOT_NULL( geom, nullptr );
964
965 sfcgal::geometry *result = sfcgal_geometry_simplify( geom, tolerance, preserveTopology );
966 CHECK_SUCCESS( errorMsg, nullptr );
967
968 return sfcgal::make_shared_geom( result );
969#endif
970}
971
972sfcgal::shared_geom QgsSfcgalEngine::approximateMedialAxis( const sfcgal::geometry *geom, QString *errorMsg )
973{
974 sfcgal::errorHandler()->clearText( errorMsg );
975 CHECK_NOT_NULL( geom, nullptr );
976
977 sfcgal::geometry *result = sfcgal_geometry_approximate_medial_axis( geom );
978 CHECK_SUCCESS( errorMsg, nullptr );
979
980 return sfcgal::make_shared_geom( result );
981}
982
983
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 )
986{
987 sfcgal::errorHandler()->clearText( errorMsg );
988 CHECK_NOT_NULL( geom, nullptr );
989
990 sfcgal::geometry *result;
991 result = sfcgal_geometry_transform( geom, mat.constData() );
992
993 CHECK_SUCCESS( errorMsg, nullptr );
994 return sfcgal::make_shared_geom( result );
995}
996
997std::unique_ptr<QgsSfcgalGeometry> QgsSfcgalEngine::toSfcgalGeometry( sfcgal::shared_prim &prim, sfcgal::primitiveType type, QString *errorMsg )
998{
999 sfcgal::errorHandler()->clearText( errorMsg );
1000 CHECK_NOT_NULL( prim.get(), nullptr );
1001
1002 return std::make_unique<QgsSfcgalGeometry>( prim, type );
1003}
1004
1005sfcgal::shared_prim QgsSfcgalEngine::createCube( double size, QString *errorMsg )
1006{
1007 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_CUBE );
1008 CHECK_SUCCESS( errorMsg, nullptr );
1009
1010 sfcgal_primitive_set_parameter_double( result, "size", size );
1011 CHECK_SUCCESS( errorMsg, nullptr );
1012
1013 return sfcgal::make_shared_prim( result );
1014}
1015
1016sfcgal::shared_geom QgsSfcgalEngine::primitiveAsPolyhedral( const sfcgal::primitive *prim, const QMatrix4x4 &mat, QString *errorMsg )
1017{
1018 sfcgal::errorHandler()->clearText( errorMsg );
1019 CHECK_NOT_NULL( prim, nullptr );
1020
1021 sfcgal::geometry *result = sfcgal_primitive_as_polyhedral_surface( prim );
1022 CHECK_SUCCESS( errorMsg, nullptr );
1023
1024 if ( !mat.isIdentity() )
1025 {
1026 sfcgal::geometry *result2 = sfcgal_geometry_transform( result, mat.constData() );
1027 sfcgal_geometry_delete( result );
1028 result = result2;
1029 CHECK_SUCCESS( errorMsg, nullptr );
1030 }
1031
1032 return sfcgal::make_shared_geom( result );
1033}
1034
1035bool QgsSfcgalEngine::primitiveIsEqual( const sfcgal::primitive *primA, const sfcgal::primitive *primB, double tolerance, QString *errorMsg )
1036{
1037 sfcgal::errorHandler()->clearText( errorMsg );
1038 CHECK_NOT_NULL( primA, false );
1039 CHECK_NOT_NULL( primB, false );
1040
1041 bool result = sfcgal_primitive_is_almost_equals( primA, primB, tolerance );
1042 CHECK_SUCCESS( errorMsg, false );
1043
1044 return result;
1045}
1046
1047sfcgal::shared_prim QgsSfcgalEngine::primitiveClone( const sfcgal::primitive *prim, QString *errorMsg )
1048{
1049 sfcgal::errorHandler()->clearText( errorMsg );
1050 CHECK_NOT_NULL( prim, nullptr );
1051
1052 sfcgal::primitive *result = sfcgal_primitive_clone( prim );
1053
1054 CHECK_SUCCESS( errorMsg, nullptr );
1055 CHECK_NOT_NULL( result, nullptr );
1056
1057 return sfcgal::make_shared_prim( result );
1058}
1059
1060double QgsSfcgalEngine::primitiveArea( const sfcgal::primitive *prim, bool withDiscretization, QString *errorMsg )
1061{
1062 sfcgal::errorHandler()->clearText( errorMsg );
1063 CHECK_NOT_NULL( prim, std::numeric_limits<double>::quiet_NaN() );
1064
1065 double out = sfcgal_primitive_area( prim, withDiscretization );
1066 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1067 return out;
1068}
1069
1070double QgsSfcgalEngine::primitiveVolume( const sfcgal::primitive *prim, bool withDiscretization, QString *errorMsg )
1071{
1072 sfcgal::errorHandler()->clearText( errorMsg );
1073 CHECK_NOT_NULL( prim, std::numeric_limits<double>::quiet_NaN() );
1074
1075 double out = sfcgal_primitive_volume( prim, withDiscretization );
1076 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1077 return out;
1078}
1079
1080void sfcgal::to_json( json &j, const sfcgal::PrimitiveParameterDesc &p )
1081{
1082 j["name"] = p.name;
1083 j["type"] = p.type;
1084
1085 if ( std::holds_alternative<int>( p.value ) )
1086 {
1087 j["value"] = std::get<int>( p.value );
1088 }
1089 else if ( std::holds_alternative<double>( p.value ) )
1090 {
1091 j["value"] = std::get<double>( p.value );
1092 }
1093 else if ( std::holds_alternative<QgsPoint>( p.value ) )
1094 {
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();
1098 if ( point.is3D() )
1099 z = point.z();
1100 if ( point.isMeasure() )
1101 m = point.m();
1102 j["value"] = std::vector<double> { point.x(), point.y(), z, m };
1103 }
1104 else if ( std::holds_alternative<QgsVector3D>( p.value ) )
1105 {
1106 QgsVector3D vect = std::get<QgsVector3D>( p.value );
1107 j["value"] = std::vector<double> { vect.x(), vect.y(), vect.z() };
1108 }
1109 else
1110 throw json::type_error::create( 306, u"Unknown type '%1'."_s.arg( p.type.c_str() ).toStdString(), nullptr );
1111}
1112
1113void sfcgal::from_json( const json &j, sfcgal::PrimitiveParameterDesc &p )
1114{
1115 j.at( "name" ).get_to( p.name );
1116 j.at( "type" ).get_to( p.type );
1117 if ( j.contains( "value" ) )
1118 {
1119 json value = j.at( "value" );
1120 if ( p.type == "int" )
1121 {
1122 p.value = value.get<int>();
1123 }
1124 else if ( p.type == "double" )
1125 {
1126 p.value = value.get<double>();
1127 }
1128 else if ( p.type == "point3" )
1129 {
1130 std::vector<double> vect;
1131 vect = value.get<std::vector<double>>();
1132 QgsPoint point(
1133 vect[0],
1134 vect[1], //
1135 ( vect.size() > 2 ? vect[2] : std::numeric_limits<double>::quiet_NaN() ), //
1136 ( vect.size() > 3 ? vect[3] : std::numeric_limits<double>::quiet_NaN() )
1137 );
1138 p.value = point;
1139 }
1140 else if ( p.type == "vector3" )
1141 {
1142 std::vector<double> vect;
1143 vect = value.get<std::vector<double>>();
1144 QgsPoint point(
1145 vect[0],
1146 vect[1], //
1147 ( vect.size() > 2 ? vect[2] : std::numeric_limits<double>::quiet_NaN() ), //
1148 ( vect.size() > 3 ? vect[3] : std::numeric_limits<double>::quiet_NaN() )
1149 );
1150 p.value = point;
1151 }
1152 else
1153 throw json::type_error::create( 306, u"Unknown type '%1'."_s.arg( p.type.c_str() ).toStdString(), nullptr );
1154 }
1155}
1156
1157QVector<sfcgal::PrimitiveParameterDesc> QgsSfcgalEngine::primitiveParameters( const sfcgal::primitive *prim, QString *errorMsg )
1158{
1159 sfcgal::errorHandler()->clearText( errorMsg );
1160 CHECK_NOT_NULL( prim, QVector<sfcgal::PrimitiveParameterDesc>() );
1161
1162 char *jsonChars = nullptr;
1163 size_t len = 0;
1164 sfcgal_primitive_parameters( prim, &jsonChars, &len );
1165 CHECK_SUCCESS( errorMsg, QVector<sfcgal::PrimitiveParameterDesc>() );
1166
1167 std::string jsonString( jsonChars, len );
1168 sfcgal_free_buffer( jsonChars );
1169
1170 QVector<sfcgal::PrimitiveParameterDesc> result;
1171 try
1172 {
1173 const auto jParams = json::parse( jsonString );
1174 for ( const auto &jParam : jParams )
1175 {
1176 result.append( jParam.get<sfcgal::PrimitiveParameterDesc>() );
1177 }
1178 }
1179 catch ( json::exception &e )
1180 {
1181 sfcgal::errorHandler()->addText( u"Caught json exception for json: %1. Error: %2"_s.arg( jsonString.c_str() ).arg( e.what() ) );
1182 }
1183
1184 return result;
1185}
1186
1187QVariant QgsSfcgalEngine::primitiveParameter( const sfcgal::primitive *prim, const QString &name, QString *errorMsg )
1188{
1189 sfcgal::errorHandler()->clearText( errorMsg );
1190 CHECK_NOT_NULL( prim, QVariant() );
1191
1192 char *jsonChars = nullptr;
1193 size_t len = 0;
1194 sfcgal_primitive_parameter( prim, name.toStdString().c_str(), &jsonChars, &len );
1195 CHECK_SUCCESS( errorMsg, QVariant() );
1196
1197 std::string jsonString( jsonChars, len );
1198 sfcgal_free_buffer( jsonChars );
1199
1200 QVariant result;
1201 try
1202 {
1203 const auto jParam = json::parse( jsonString );
1204 sfcgal::PrimitiveParameterDesc param = jParam.get<sfcgal::PrimitiveParameterDesc>();
1205 result = QVariant::fromStdVariant( param.value );
1206 }
1207 catch ( json::exception &e )
1208 {
1209 sfcgal::errorHandler()->addText( u"Caught json exception for json: %1. Error: %2"_s.arg( jsonString.c_str() ).arg( e.what() ) );
1210 }
1211
1212 return result;
1213}
1214
1215void QgsSfcgalEngine::primitiveSetParameter( sfcgal::primitive *prim, const QString &name, const QVariant &value, QString *errorMsg )
1216{
1217 sfcgal::errorHandler()->clearText( errorMsg );
1218 CHECK_NOT_NULL( prim, void() );
1219
1220 try
1221 {
1222 json jParam;
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>();
1232 else if ( value.canConvert<QgsVector3D>() )
1233 paramDesc.value = value.value<QgsVector3D>();
1234
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() );
1239 }
1240 catch ( ... )
1241 {
1242 sfcgal::errorHandler()->addText( u"Caught json exception"_s );
1243 }
1244}
1245
1246#endif
1247
1248
1249#endif // #ifdef WITH_SFCGAL
JoinStyle3D
Join styles for 3D buffers.
Definition qgis.h:2214
@ CylindersAndSpheres
Cylinders along the linestring segments with spheres at the vertices.
Definition qgis.h:2217
@ Flat
Flat ends and constant width along the linestring.
Definition qgis.h:2216
@ Round
Smooth, rounded buffer around the input geometry.
Definition qgis.h:2215
JoinStyle
Join styles for buffers.
Definition qgis.h:2201
@ Round
Use rounded joins.
Definition qgis.h:2202
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.
Custom exception class which is raised when an operation is not supported.
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
bool fromWkb(QgsConstWkbPtr &wkb) override
Sets the geometry from a WKB string.
Definition qgspoint.cpp:173
double z
Definition qgspoint.h:58
double x
Definition qgspoint.h:56
bool isEmpty() const override
Returns true if the geometry is empty.
Definition qgspoint.cpp:766
double m
Definition qgspoint.h:59
double y
Definition qgspoint.h:57
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:60
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:62
double x() const
Returns X coordinate.
Definition qgsvector3d.h:58