QGIS API Documentation 3.99.0-Master (09f76ad7019)
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" ).arg( msg ) //
142 .arg( QString::fromStdString( location.function_name() ) ) //
143 .arg( QString::fromStdString( location.file_name() ) ) //
144 .arg( location.line() );
145
146 errorMessages.push_front( txt );
147}
148
149// ===================================
150// QgsSfcgalEngine static functions
151// ===================================
152
161template<typename T>
162static T geom_to_primtype(
163 T( *func_2d )( const sfcgal_geometry_t * ),
164 T( *func_3d )( const sfcgal_geometry_t * ),
165 const sfcgal::geometry *geom,
166 QString *errorMsg
167)
168{
169 sfcgal::errorHandler()->clearText( errorMsg );
170 CHECK_NOT_NULL( geom, std::numeric_limits<T>::quiet_NaN() );
171
172 T result;
173 if ( func_3d && sfcgal_geometry_is_3d( geom ) )
174 result = func_3d( geom );
175 else
176 result = func_2d( geom );
177
178 CHECK_SUCCESS( errorMsg, std::numeric_limits<T>::quiet_NaN() );
179
180 return result;
181};
182
192template<typename T>
193static T geomgeom_to_primtype(
194 T( *func_2d )( const sfcgal_geometry_t *, const sfcgal_geometry_t * ),
195 T( *func_3d )( const sfcgal_geometry_t *, const sfcgal_geometry_t * ),
196 const sfcgal::geometry *geomA,
197 const sfcgal::geometry *geomB,
198 QString *errorMsg
199)
200{
201 sfcgal::errorHandler()->clearText( errorMsg );
202 CHECK_NOT_NULL( geomA, false );
203 CHECK_NOT_NULL( geomB, false );
204
205 T result;
206 if ( func_3d && ( sfcgal_geometry_is_3d( geomA ) || sfcgal_geometry_is_3d( geomB ) ) )
207 result = func_3d( geomA, geomB );
208 else
209 result = func_2d( geomA, geomB );
210
211 CHECK_SUCCESS( errorMsg, std::numeric_limits<T>::quiet_NaN() );
212
213 return result;
214};
215
224static sfcgal::shared_geom geom_to_geom(
225 sfcgal::func_geom_to_geom func_2d,
226 sfcgal::func_geom_to_geom func_3d,
227 const sfcgal::geometry *geom,
228 QString *errorMsg
229)
230{
231 sfcgal::errorHandler()->clearText( errorMsg );
232 CHECK_NOT_NULL( geom, nullptr );
233
234 sfcgal::geometry *result = nullptr;
235 if ( func_3d && sfcgal_geometry_is_3d( geom ) )
236 result = func_3d( geom );
237 else
238 result = func_2d( geom );
239
240 CHECK_SUCCESS( errorMsg, nullptr );
241 CHECK_NOT_NULL( result, nullptr );
242
243 return sfcgal::make_shared_geom( result );
244};
245
246
256static sfcgal::shared_geom geomgeom_to_geom(
257 sfcgal::func_geomgeom_to_geom func_2d,
258 sfcgal::func_geomgeom_to_geom func_3d,
259 const sfcgal::geometry *geomA,
260 const sfcgal::geometry *geomB,
261 QString *errorMsg
262)
263{
264 sfcgal::errorHandler()->clearText( errorMsg );
265 CHECK_NOT_NULL( geomA, nullptr );
266 CHECK_NOT_NULL( geomB, nullptr );
267
268 sfcgal::geometry *result = nullptr;
269 if ( func_3d && ( sfcgal_geometry_is_3d( geomA ) || sfcgal_geometry_is_3d( geomB ) ) )
270 result = func_3d( geomA, geomB );
271 else
272 result = func_2d( geomA, geomB );
273
274 CHECK_SUCCESS( errorMsg, nullptr );
275 CHECK_NOT_NULL( result, nullptr );
276
277 return sfcgal::make_shared_geom( result );
278};
279
280
281// ===================================
282// QgsSfcgalEngine class
283// ===================================
284
285
286std::unique_ptr<QgsSfcgalGeometry> QgsSfcgalEngine::toSfcgalGeometry( sfcgal::shared_geom &geom, QString *errorMsg )
287{
288 sfcgal::errorHandler()->clearText( errorMsg );
289 CHECK_NOT_NULL( geom.get(), nullptr );
290
291 return std::make_unique<QgsSfcgalGeometry>( geom );
292}
293
294std::unique_ptr<QgsAbstractGeometry> QgsSfcgalEngine::toAbstractGeometry( const sfcgal::geometry *geom, QString *errorMsg )
295{
296 std::unique_ptr<QgsAbstractGeometry> out( nullptr );
297 sfcgal::errorHandler()->clearText( errorMsg );
298 CHECK_NOT_NULL( geom, out );
299
300 QByteArray wkbArray = QgsSfcgalEngine::toWkb( geom, errorMsg );
301 CHECK_SUCCESS( errorMsg, out );
302
303 QgsConstWkbPtr wkbPtr( wkbArray );
304 out = QgsGeometryFactory::geomFromWkb( wkbPtr );
305 if ( !out )
306 {
307 Qgis::WkbType sfcgalType = QgsSfcgalEngine::wkbType( geom );
308 sfcgal::errorHandler()->addText( u"WKB contains unmanaged geometry type (WKB:%1 / SFCGAL:%2"_s //
309 .arg( static_cast<int>( wkbPtr.readHeader() ) ) //
310 .arg( static_cast<int>( sfcgalType ) ) );
311 }
312
313 return out;
314}
315
316sfcgal::shared_geom QgsSfcgalEngine::fromAbstractGeometry( const QgsAbstractGeometry *geom, QString *errorMsg )
317{
318 sfcgal::errorHandler()->clearText( errorMsg );
319 CHECK_NOT_NULL( geom, sfcgal::shared_geom( nullptr ) );
320
321 QByteArray wkbBytes = geom->asWkb();
322
323 sfcgal::geometry *out = sfcgal_io_read_wkb( wkbBytes.data(), wkbBytes.length() );
324 CHECK_SUCCESS( errorMsg, nullptr );
325
326 return sfcgal::make_shared_geom( out );
327}
328
329sfcgal::shared_geom QgsSfcgalEngine::cloneGeometry( const sfcgal::geometry *geom, QString *errorMsg )
330{
331 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_clone, nullptr, geom, errorMsg );
332 CHECK_SUCCESS( errorMsg, nullptr );
333 return out;
334}
335
336QString QgsSfcgalEngine::geometryType( const sfcgal::geometry *geom, QString *errorMsg )
337{
338#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
339 ( void )geom;
340 ( void )errorMsg;
341 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "geometryType" ) );
342#else
343 sfcgal::errorHandler()->clearText( errorMsg );
344
345 char *typeChar;
346 size_t typeLen;
347 sfcgal_geometry_type( geom, &typeChar, &typeLen );
348 std::string typeStr( typeChar, typeLen );
349 sfcgal_free_buffer( typeChar );
350
351 return QString::fromStdString( typeStr );
352#endif
353}
354
355sfcgal::shared_geom QgsSfcgalEngine::fromWkb( const QgsConstWkbPtr &wkbPtr, QString *errorMsg )
356{
357 sfcgal::errorHandler()->clearText( errorMsg );
358
359 const unsigned char *wkbUnsignedPtr = wkbPtr;
360 sfcgal::geometry *out = sfcgal_io_read_wkb( reinterpret_cast<const char *>( wkbUnsignedPtr ), wkbPtr.remaining() );
361 CHECK_SUCCESS( errorMsg, nullptr );
362
363 return sfcgal::make_shared_geom( out );
364}
365
366sfcgal::shared_geom QgsSfcgalEngine::fromWkt( const QString &wkt, QString *errorMsg )
367{
368 sfcgal::errorHandler()->clearText( errorMsg );
369
370 sfcgal::geometry *out = sfcgal_io_read_wkt( wkt.toStdString().c_str(), wkt.length() );
371 CHECK_SUCCESS( errorMsg, nullptr );
372
373 return sfcgal::unique_geom( out );
374}
375
376QByteArray QgsSfcgalEngine::toWkb( const sfcgal::geometry *geom, QString *errorMsg )
377{
378 sfcgal::errorHandler()->clearText( errorMsg );
379 CHECK_NOT_NULL( geom, QByteArray() );
380
381 char *wkbHex;
382 size_t len = 0;
383 sfcgal_geometry_as_wkb( geom, &wkbHex, &len );
384 QByteArray wkbArray( wkbHex, static_cast<int>( len ) );
385
386#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
387 sfcgal_free_buffer( wkbHex );
388#else
389 free( wkbHex );
390#endif
391
392 CHECK_SUCCESS( errorMsg, QByteArray() );
393 return wkbArray;
394}
395
396QString QgsSfcgalEngine::toWkt( const sfcgal::geometry *geom, int numDecimals, QString *errorMsg )
397{
398 sfcgal::errorHandler()->clearText( errorMsg );
399 CHECK_NOT_NULL( geom, QString() );
400
401 char *wkt;
402 size_t len = 0;
403 sfcgal_geometry_as_text_decim( geom, numDecimals, &wkt, &len );
404 CHECK_SUCCESS( errorMsg, QString() );
405
406 std::string wktString( wkt, len );
407#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
408 sfcgal_free_buffer( wkt );
409#else
410 free( wkt );
411#endif
412 return QString::fromStdString( wktString );
413}
414
415Qgis::WkbType QgsSfcgalEngine::wkbType( const sfcgal::geometry *geom, QString *errorMsg )
416{
417 sfcgal::errorHandler()->clearText( errorMsg );
418 CHECK_NOT_NULL( geom, Qgis::WkbType::Unknown );
419
420 sfcgal_geometry_type_t type = sfcgal_geometry_type_id( geom );
421 CHECK_SUCCESS( errorMsg, Qgis::WkbType::Unknown );
422
423 int wkbType = type;
424 if ( sfcgal_geometry_is_3d( geom ) )
425 wkbType += 1000;
426
427 if ( sfcgal_geometry_is_measured( geom ) )
428 wkbType += 2000;
429
430 Qgis::WkbType qgisType = static_cast<Qgis::WkbType>( wkbType );
431 if ( qgisType >= Qgis::WkbType::Unknown && qgisType <= Qgis::WkbType::TriangleZM )
432 return qgisType;
433
434 sfcgal::errorHandler()->addText( u"WKB type '%1' is not known from QGIS"_s.arg( wkbType ) );
436}
437
438int QgsSfcgalEngine::dimension( const sfcgal::geometry *geom, QString *errorMsg )
439{
440#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
441 ( void )geom;
442 ( void )errorMsg;
443 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "dimension" ) );
444#else
445 int out = geom_to_primtype<int>( sfcgal_geometry_dimension, nullptr, geom, errorMsg );
446 CHECK_SUCCESS( errorMsg, std::numeric_limits<int>::quiet_NaN() );
447 return out;
448#endif
449}
450
451int QgsSfcgalEngine::partCount( const sfcgal::geometry *geom, QString *errorMsg )
452{
453 size_t out;
454 sfcgal::errorHandler()->clearText( errorMsg );
455 CHECK_NOT_NULL( geom, -1 );
456
457 sfcgal_geometry_type_t type = sfcgal_geometry_type_id( geom );
458 CHECK_SUCCESS( errorMsg, -1 );
459
460 switch ( type )
461 {
462 case SFCGAL_TYPE_MULTIPOINT:
463 case SFCGAL_TYPE_MULTILINESTRING:
464 case SFCGAL_TYPE_MULTIPOLYGON:
465 case SFCGAL_TYPE_MULTISOLID:
466 case SFCGAL_TYPE_GEOMETRYCOLLECTION:
467#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
468 out = sfcgal_geometry_num_geometries( geom );
469#else
470 out = sfcgal_geometry_collection_num_geometries( geom );
471#endif
472 break;
473 case SFCGAL_TYPE_POLYGON:
474 out = sfcgal_polygon_num_interior_rings( geom ) + 1;
475 break;
476 case SFCGAL_TYPE_SOLID:
477 out = sfcgal_solid_num_shells( geom );
478 break;
479 case SFCGAL_TYPE_POLYHEDRALSURFACE:
480#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
481 out = sfcgal_polyhedral_surface_num_patches( geom );
482#else
483 out = sfcgal_polyhedral_surface_num_polygons( geom );
484#endif
485 break;
486 case SFCGAL_TYPE_TRIANGULATEDSURFACE:
487#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
488 out = sfcgal_triangulated_surface_num_patches( geom );
489#else
490 out = sfcgal_triangulated_surface_num_triangles( geom );
491#endif
492 break;
493 case SFCGAL_TYPE_LINESTRING:
494 out = sfcgal_linestring_num_points( geom );
495 break;
496 case SFCGAL_TYPE_TRIANGLE:
497 out = 3;
498 break;
499 case SFCGAL_TYPE_POINT:
500 out = 1;
501 break;
502 default:
503 out = -1;
504 }
505
506 CHECK_SUCCESS( errorMsg, -1 );
507
508 return static_cast<int>( out );
509}
510
511bool QgsSfcgalEngine::addZValue( sfcgal::geometry *geom, double zValue, QString *errorMsg )
512{
513#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
514 ( void )geom;
515 ( void )zValue;
516 ( void )errorMsg;
517 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "addZValue" ) );
518#else
519 sfcgal::errorHandler()->clearText( errorMsg );
520 CHECK_NOT_NULL( geom, false );
521
522 return sfcgal_geometry_force_z( geom, zValue );
523#endif
524}
525
526bool QgsSfcgalEngine::addMValue( sfcgal::geometry *geom, double mValue, QString *errorMsg )
527{
528#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
529 ( void )geom;
530 ( void )mValue;
531 ( void )errorMsg;
532 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "addMValue" ) );
533#else
534 sfcgal::errorHandler()->clearText( errorMsg );
535 CHECK_NOT_NULL( geom, false );
536
537 return sfcgal_geometry_force_m( geom, mValue );
538#endif
539}
540
541bool QgsSfcgalEngine::dropZValue( sfcgal::geometry *geom, QString *errorMsg )
542{
543#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
544 ( void )geom;
545 ( void )errorMsg;
546 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "dropZValue" ) );
547#else
548 sfcgal::errorHandler()->clearText( errorMsg );
549 CHECK_NOT_NULL( geom, false );
550
551 return sfcgal_geometry_drop_z( geom );
552#endif
553}
554
555bool QgsSfcgalEngine::dropMValue( sfcgal::geometry *geom, QString *errorMsg )
556{
557#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
558 ( void )geom;
559 ( void )errorMsg;
560 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "dropMValue" ) );
561#else
562 sfcgal::errorHandler()->clearText( errorMsg );
563 CHECK_NOT_NULL( geom, false );
564
565 return sfcgal_geometry_drop_m( geom );
566#endif
567}
568
569void QgsSfcgalEngine::swapXy( sfcgal::geometry *geom, QString *errorMsg )
570{
571#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
572 ( void )geom;
573 ( void )errorMsg;
574 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "swapXy" ) );
575#else
576 sfcgal::errorHandler()->clearText( errorMsg );
577 CHECK_NOT_NULL( geom, void() );
578
579 sfcgal_geometry_swap_xy( geom );
580#endif
581}
582
583bool QgsSfcgalEngine::isEqual( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, double tolerance, QString *errorMsg )
584{
585#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
586 ( void )geomA;
587 ( void )geomB;
588 ( void )tolerance;
589 ( void )errorMsg;
590 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "isEqual" ) );
591#else
592 sfcgal::errorHandler()->clearText( errorMsg );
593 CHECK_NOT_NULL( geomA, false );
594 CHECK_NOT_NULL( geomB, false );
595
596 bool result = sfcgal_geometry_is_almost_equals( geomA, geomB, tolerance );
597 CHECK_SUCCESS( errorMsg, false );
598
599 return result;
600#endif
601}
602
603bool QgsSfcgalEngine::isEmpty( const sfcgal::geometry *geom, QString *errorMsg )
604{
605 int res = geom_to_primtype<int>( sfcgal_geometry_is_empty, nullptr, geom, errorMsg );
606 CHECK_SUCCESS( errorMsg, false );
607 return static_cast<bool>( res );
608}
609
610bool QgsSfcgalEngine::isValid( const sfcgal::geometry *geom, QString *errorMsg, QgsGeometry *errorLoc )
611{
612 sfcgal::errorHandler()->clearText( errorMsg );
613 CHECK_NOT_NULL( geom, false );
614
615 bool result = false;
616 char *reason;
617 sfcgal::geometry *location;
618 result = sfcgal_geometry_is_valid_detail( geom, &reason, &location );
619
620 CHECK_SUCCESS( errorMsg, false );
621
622 if ( reason && strlen( reason ) )
623 {
624 sfcgal::errorHandler()->addText( QString( reason ) );
625 free( reason );
626 }
627
628 if ( location && errorLoc )
629 {
630 std::unique_ptr<QgsAbstractGeometry> locationGeom = toAbstractGeometry( location, errorMsg );
631 CHECK_SUCCESS( errorMsg, false );
632 errorLoc->addPartV2( locationGeom.release() );
633 }
634
635 return result;
636}
637
638bool QgsSfcgalEngine::isSimple( const sfcgal::geometry *geom, QString *errorMsg )
639{
640#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
641 ( void )geom;
642 ( void )errorMsg;
643 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "isSimple" ) );
644#else
645 int res = geom_to_primtype<int>( sfcgal_geometry_is_simple, nullptr, geom, errorMsg );
646 CHECK_SUCCESS( errorMsg, false );
647 return static_cast<bool>( res );
648#endif
649}
650
651sfcgal::shared_geom QgsSfcgalEngine::boundary( const sfcgal::geometry *geom, QString *errorMsg )
652{
653#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
654 ( void )geom;
655 ( void )errorMsg;
656 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "boundary" ) );
657#else
658 sfcgal::errorHandler()->clearText( errorMsg );
659 CHECK_NOT_NULL( geom, nullptr );
660
661 sfcgal::geometry *boundary = sfcgal_geometry_boundary( geom );
662 CHECK_SUCCESS( errorMsg, nullptr );
663
664 return sfcgal::make_shared_geom( boundary );
665#endif
666}
667
668QgsPoint QgsSfcgalEngine::centroid( const sfcgal::geometry *geom, QString *errorMsg )
669{
670#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
671 ( void )geom;
672 ( void )errorMsg;
673 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "centroid" ) );
674#else
675 sfcgal::errorHandler()->clearText( errorMsg );
676 CHECK_NOT_NULL( geom, QgsPoint() );
677
678 const sfcgal::geometry *result = nullptr;
679 if ( sfcgal_geometry_is_3d( geom ) )
680 result = sfcgal_geometry_centroid_3d( geom );
681 else
682 result = sfcgal_geometry_centroid( geom );
683
684 CHECK_SUCCESS( errorMsg, QgsPoint() );
685 CHECK_NOT_NULL( result, QgsPoint() );
686
687 QByteArray wkbArray = QgsSfcgalEngine::toWkb( result, errorMsg );
688 QgsConstWkbPtr wkbPtr( wkbArray );
689 QgsPoint out;
690 out.fromWkb( wkbPtr );
691
692 return out;
693#endif
694}
695
696sfcgal::shared_geom QgsSfcgalEngine::translate( const sfcgal::geometry *geom, const QgsVector3D &translation, QString *errorMsg )
697{
698#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
699 ( void )geom;
700 ( void )translation;
701 ( void )errorMsg;
702 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "translate" ) );
703#else
704 sfcgal::errorHandler()->clearText( errorMsg );
705 CHECK_NOT_NULL( geom, nullptr );
706
707 sfcgal::geometry *result;
708 if ( sfcgal_geometry_is_3d( geom ) )
709 result = sfcgal_geometry_translate_3d( geom, translation.x(), translation.y(), translation.z() );
710 else
711 result = sfcgal_geometry_translate_2d( geom, translation.x(), translation.y() );
712 CHECK_SUCCESS( errorMsg, nullptr );
713
714 return sfcgal::make_shared_geom( result );
715#endif
716}
717
718sfcgal::shared_geom QgsSfcgalEngine::scale( const sfcgal::geometry *geom, const QgsVector3D &scaleFactor, const QgsPoint &center, QString *errorMsg )
719{
720 sfcgal::errorHandler()->clearText( errorMsg );
721 CHECK_NOT_NULL( geom, nullptr );
722
723 sfcgal::geometry *result;
724 if ( center.isEmpty() )
725 {
726 result = sfcgal_geometry_scale_3d( geom, scaleFactor.x(), scaleFactor.y(), scaleFactor.z() );
727 }
728 else
729 {
730 const double centerZ = center.is3D() ? center.z() : 0;
731 result = sfcgal_geometry_scale_3d_around_center( geom, scaleFactor.x(), scaleFactor.y(), scaleFactor.z(), center.x(), center.y(), centerZ );
732 }
733
734 CHECK_SUCCESS( errorMsg, nullptr );
735 return sfcgal::make_shared_geom( result );
736}
737
738sfcgal::shared_geom QgsSfcgalEngine::rotate2D( const sfcgal::geometry *geom, double angle, const QgsPoint &center, QString *errorMsg )
739{
740 sfcgal::errorHandler()->clearText( errorMsg );
741 CHECK_NOT_NULL( geom, nullptr );
742
743 sfcgal::geometry *result = sfcgal_geometry_rotate_2d( geom, angle, center.x(), center.y() );
744
745 CHECK_SUCCESS( errorMsg, nullptr );
746 return sfcgal::make_shared_geom( result );
747}
748
749sfcgal::shared_geom QgsSfcgalEngine::rotate3D( const sfcgal::geometry *geom, double angle, const QgsVector3D &axisVector, const QgsPoint &center, QString *errorMsg )
750{
751 sfcgal::errorHandler()->clearText( errorMsg );
752 CHECK_NOT_NULL( geom, nullptr );
753
754 sfcgal::geometry *result;
755 if ( center.isEmpty() )
756 {
757 result = sfcgal_geometry_rotate_3d( geom, angle, axisVector.x(), axisVector.y(), axisVector.z() );
758 }
759 else
760 {
761 result = sfcgal_geometry_rotate_3d_around_center( geom, angle, axisVector.x(), axisVector.y(), axisVector.z(), center.x(), center.y(), center.z() );
762 }
763
764 CHECK_SUCCESS( errorMsg, nullptr );
765 return sfcgal::make_shared_geom( result );
766}
767
768double QgsSfcgalEngine::distance( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
769{
770 double out = geomgeom_to_primtype<double>( sfcgal_geometry_distance, sfcgal_geometry_distance_3d, geomA, geomB, errorMsg );
771 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
772 return out;
773}
774
775bool QgsSfcgalEngine::distanceWithin( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, double maxdistance, QString *errorMsg )
776{
777 double dist = QgsSfcgalEngine::distance( geomA, geomB, errorMsg );
778 CHECK_SUCCESS( errorMsg, false );
779
780 return dist <= maxdistance;
781}
782
783double QgsSfcgalEngine::area( const sfcgal::geometry *geom, QString *errorMsg )
784{
785 double out = geom_to_primtype<double>( sfcgal_geometry_area, sfcgal_geometry_area_3d, geom, errorMsg );
786 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
787 return out;
788}
789
790double QgsSfcgalEngine::length( const sfcgal::geometry *geom, QString *errorMsg )
791{
792#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
793 ( void )geom;
794 ( void )errorMsg;
795 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "length" ) );
796#else
797 double out = geom_to_primtype<double>( sfcgal_geometry_length, sfcgal_geometry_length_3d, geom, errorMsg );
798 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
799 return out;
800#endif
801}
802
803bool QgsSfcgalEngine::intersects( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
804{
805 int res = geomgeom_to_primtype<int>( sfcgal_geometry_intersects, sfcgal_geometry_intersects_3d, geomA, geomB, errorMsg );
806 CHECK_SUCCESS( errorMsg, false );
807 return static_cast<bool>( res );
808}
809
810sfcgal::shared_geom QgsSfcgalEngine::intersection( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
811{
812 sfcgal::shared_geom out = geomgeom_to_geom( sfcgal_geometry_intersection, sfcgal_geometry_intersection_3d, geomA, geomB, errorMsg );
813 CHECK_SUCCESS( errorMsg, nullptr );
814 return out;
815}
816
817sfcgal::shared_geom QgsSfcgalEngine::difference( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
818{
819 sfcgal::shared_geom out = geomgeom_to_geom( sfcgal_geometry_difference, sfcgal_geometry_difference_3d, geomA, geomB, errorMsg );
820 CHECK_SUCCESS( errorMsg, nullptr );
821 return out;
822}
823
824sfcgal::shared_geom QgsSfcgalEngine::combine( const QVector<sfcgal::shared_geom> &geomList, QString *errorMsg )
825{
826 sfcgal::errorHandler()->clearText( errorMsg );
827 sfcgal::geometry *combined = nullptr;
828 for ( sfcgal::shared_geom other : geomList )
829 {
830 if ( !combined )
831 {
832 combined = other.get();
833 continue;
834 }
835
836 if ( sfcgal_geometry_is_3d( other.get() ) || sfcgal_geometry_is_3d( combined ) )
837 combined = sfcgal_geometry_union_3d( combined, other.get() );
838 else
839 combined = sfcgal_geometry_union( combined, other.get() );
840
841 if ( !combined )
842 sfcgal::errorHandler()->addText( "SFCGAL produced null result." );
843
844 CHECK_SUCCESS( errorMsg, nullptr );
845 }
846
847 return sfcgal::make_shared_geom( combined );
848}
849
850sfcgal::shared_geom QgsSfcgalEngine::triangulate( const sfcgal::geometry *geom, QString *errorMsg )
851{
852 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_triangulate_2dz, nullptr, geom, errorMsg );
853 CHECK_SUCCESS( errorMsg, nullptr );
854 return out;
855}
856
857bool QgsSfcgalEngine::covers( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
858{
859 int res = geomgeom_to_primtype<int>( sfcgal_geometry_covers, sfcgal_geometry_covers_3d, geomA, geomB, errorMsg );
860 CHECK_SUCCESS( errorMsg, false );
861 return static_cast<bool>( res );
862}
863
864sfcgal::shared_geom QgsSfcgalEngine::envelope( const sfcgal::geometry *geom, QString *errorMsg )
865{
866#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
867 ( void )geom;
868 ( void )errorMsg;
869 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "envelope" ) );
870#else
871 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_envelope, sfcgal_geometry_envelope_3d, geom, errorMsg );
872 CHECK_SUCCESS( errorMsg, nullptr );
873 return out;
874#endif
875}
876
877sfcgal::shared_geom QgsSfcgalEngine::convexHull( const sfcgal::geometry *geom, QString *errorMsg )
878{
879 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_convexhull, sfcgal_geometry_convexhull_3d, geom, errorMsg );
880 CHECK_SUCCESS( errorMsg, nullptr );
881 return out;
882}
883
884sfcgal::shared_geom QgsSfcgalEngine::offsetCurve( const sfcgal::geometry *geom, double distance, int, Qgis::JoinStyle, QString *errorMsg )
885{
886 sfcgal::errorHandler()->clearText( errorMsg );
887 CHECK_NOT_NULL( geom, nullptr );
888
889 sfcgal::geometry *result = nullptr;
890 result = sfcgal_geometry_offset_polygon( geom, distance );
891
892 CHECK_SUCCESS( errorMsg, nullptr );
893
894 return sfcgal::make_shared_geom( result );
895}
896
897sfcgal::shared_geom QgsSfcgalEngine::buffer2D( const sfcgal::geometry *geom, double radius, int segments, Qgis::JoinStyle joinStyle, QString *errorMsg )
898{
899 if ( joinStyle != Qgis::JoinStyle::Round )
900 qWarning() << ( u"Buffer not implemented for %1! Defaulting to round join."_s );
901
902 return offsetCurve( geom, radius, segments, joinStyle, errorMsg );
903}
904
905sfcgal::shared_geom QgsSfcgalEngine::buffer3D( const sfcgal::geometry *geom, double radius, int segments, Qgis::JoinStyle3D joinStyle3D, QString *errorMsg )
906{
907 sfcgal::errorHandler()->clearText( errorMsg );
908 CHECK_NOT_NULL( geom, nullptr );
909
910 sfcgal_buffer3d_type_t buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_FLAT;
911 switch ( joinStyle3D )
912 {
914 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_FLAT;
915 break;
917 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_ROUND;
918 break;
920 buffer_type = sfcgal_buffer3d_type_t::SFCGAL_BUFFER3D_CYLSPHERE;
921 break;
922 }
923
924 sfcgal::geometry *result = sfcgal_geometry_buffer3d( geom, radius, segments, buffer_type );
925 CHECK_SUCCESS( errorMsg, nullptr );
926
927 return sfcgal::make_shared_geom( result );
928}
929
930sfcgal::shared_geom QgsSfcgalEngine::extrude( const sfcgal::geometry *geom, const QgsVector3D &extrusion, QString *errorMsg )
931{
932 sfcgal::errorHandler()->clearText( errorMsg );
933 CHECK_NOT_NULL( geom, nullptr );
934
935 sfcgal_geometry_t *solid = sfcgal_geometry_extrude( geom, extrusion.x(), extrusion.y(), extrusion.z() );
936
937 CHECK_SUCCESS( errorMsg, nullptr );
938
939 // sfcgal_geometry_extrude returns a SOLID
940 // This is not handled by QGIS
941 // convert it to a PolyhedralSurface
942 sfcgal_geometry_t *polySurface = sfcgal_polyhedral_surface_create();
943 for ( unsigned int shellIdx = 0; shellIdx < sfcgal_solid_num_shells( solid ); ++shellIdx )
944 {
945 const sfcgal_geometry_t *shell = sfcgal_solid_shell_n( solid, shellIdx );
946#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
947 for ( unsigned int polyIdx = 0; polyIdx < sfcgal_polyhedral_surface_num_patches( shell ); ++polyIdx )
948 {
949 const sfcgal_geometry_t *patch = sfcgal_polyhedral_surface_patch_n( shell, polyIdx );
950 sfcgal_polyhedral_surface_add_patch( polySurface, sfcgal_geometry_clone( patch ) );
951 }
952#else
953 for ( unsigned int polyIdx = 0; polyIdx < sfcgal_polyhedral_surface_num_polygons( shell ); ++polyIdx )
954 {
955 const sfcgal_geometry_t *patch = sfcgal_polyhedral_surface_polygon_n( shell, polyIdx );
956 sfcgal_polyhedral_surface_add_polygon( polySurface, sfcgal_geometry_clone( patch ) );
957 }
958#endif
959 }
960
961 sfcgal_geometry_delete( solid );
962
963 CHECK_SUCCESS( errorMsg, nullptr );
964
965 return sfcgal::make_shared_geom( polySurface );
966}
967
968sfcgal::shared_geom QgsSfcgalEngine::simplify( const sfcgal::geometry *geom, double tolerance, bool preserveTopology, QString *errorMsg )
969{
970#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
971 ( void )geom;
972 ( void )tolerance;
973 ( void )preserveTopology;
974 ( void )errorMsg;
975 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "boundary" ) );
976#else
977 sfcgal::errorHandler()->clearText( errorMsg );
978 CHECK_NOT_NULL( geom, nullptr );
979
980 sfcgal::geometry *result = sfcgal_geometry_simplify( geom, tolerance, preserveTopology );
981 CHECK_SUCCESS( errorMsg, nullptr );
982
983 return sfcgal::make_shared_geom( result );
984#endif
985}
986
987sfcgal::shared_geom QgsSfcgalEngine::approximateMedialAxis( const sfcgal::geometry *geom, QString *errorMsg )
988{
989 sfcgal::errorHandler()->clearText( errorMsg );
990 CHECK_NOT_NULL( geom, nullptr );
991
992 sfcgal::geometry *result = sfcgal_geometry_approximate_medial_axis( geom );
993 CHECK_SUCCESS( errorMsg, nullptr );
994
995 return sfcgal::make_shared_geom( result );
996}
997
998
999#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 3, 0 )
1000sfcgal::shared_geom QgsSfcgalEngine::transform( const sfcgal::geometry *geom, const QMatrix4x4 &mat, QString *errorMsg )
1001{
1002 sfcgal::errorHandler()->clearText( errorMsg );
1003 CHECK_NOT_NULL( geom, nullptr );
1004
1005 sfcgal::geometry *result;
1006 result = sfcgal_geometry_transform( geom, mat.constData() );
1007
1008 CHECK_SUCCESS( errorMsg, nullptr );
1009 return sfcgal::make_shared_geom( result );
1010}
1011
1012std::unique_ptr<QgsSfcgalGeometry> QgsSfcgalEngine::toSfcgalGeometry( sfcgal::shared_prim &prim, sfcgal::primitiveType type, QString *errorMsg )
1013{
1014 sfcgal::errorHandler()->clearText( errorMsg );
1015 CHECK_NOT_NULL( prim.get(), nullptr );
1016
1017 return std::make_unique<QgsSfcgalGeometry>( prim, type );
1018}
1019
1020sfcgal::shared_prim QgsSfcgalEngine::createCube( double size, QString *errorMsg )
1021{
1022 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_CUBE );
1023 CHECK_SUCCESS( errorMsg, nullptr );
1024
1025 sfcgal_primitive_set_parameter_double( result, "size", size );
1026 CHECK_SUCCESS( errorMsg, nullptr );
1027
1028 return sfcgal::make_shared_prim( result );
1029}
1030
1031sfcgal::shared_geom QgsSfcgalEngine::primitiveAsPolyhedral( const sfcgal::primitive *prim, const QMatrix4x4 &mat, QString *errorMsg )
1032{
1033 sfcgal::errorHandler()->clearText( errorMsg );
1034 CHECK_NOT_NULL( prim, nullptr );
1035
1036 sfcgal::geometry *result = sfcgal_primitive_as_polyhedral_surface( prim );
1037 CHECK_SUCCESS( errorMsg, nullptr );
1038
1039 if ( !mat.isIdentity() )
1040 {
1041 sfcgal::geometry *result2 = sfcgal_geometry_transform( result, mat.constData() );
1042 sfcgal_geometry_delete( result );
1043 result = result2;
1044 CHECK_SUCCESS( errorMsg, nullptr );
1045 }
1046
1047 return sfcgal::make_shared_geom( result );
1048}
1049
1050bool QgsSfcgalEngine::primitiveIsEqual( const sfcgal::primitive *primA, const sfcgal::primitive *primB, double tolerance, QString *errorMsg )
1051{
1052 sfcgal::errorHandler()->clearText( errorMsg );
1053 CHECK_NOT_NULL( primA, false );
1054 CHECK_NOT_NULL( primB, false );
1055
1056 bool result = sfcgal_primitive_is_almost_equals( primA, primB, tolerance );
1057 CHECK_SUCCESS( errorMsg, false );
1058
1059 return result;
1060}
1061
1062sfcgal::shared_prim QgsSfcgalEngine::primitiveClone( const sfcgal::primitive *prim, QString *errorMsg )
1063{
1064 sfcgal::errorHandler()->clearText( errorMsg );
1065 CHECK_NOT_NULL( prim, nullptr );
1066
1067 sfcgal::primitive *result = sfcgal_primitive_clone( prim );
1068
1069 CHECK_SUCCESS( errorMsg, nullptr );
1070 CHECK_NOT_NULL( result, nullptr );
1071
1072 return sfcgal::make_shared_prim( result );
1073}
1074
1075double QgsSfcgalEngine::primitiveArea( const sfcgal::primitive *prim, bool withDiscretization, QString *errorMsg )
1076{
1077 sfcgal::errorHandler()->clearText( errorMsg );
1078 CHECK_NOT_NULL( prim, std::numeric_limits<double>::quiet_NaN() );
1079
1080 double out = sfcgal_primitive_area( prim, withDiscretization );
1081 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1082 return out;
1083}
1084
1085double QgsSfcgalEngine::primitiveVolume( const sfcgal::primitive *prim, bool withDiscretization, QString *errorMsg )
1086{
1087 sfcgal::errorHandler()->clearText( errorMsg );
1088 CHECK_NOT_NULL( prim, std::numeric_limits<double>::quiet_NaN() );
1089
1090 double out = sfcgal_primitive_volume( prim, withDiscretization );
1091 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1092 return out;
1093}
1094
1095void sfcgal::to_json( json &j, const sfcgal::PrimitiveParameterDesc &p )
1096{
1097 j["name"] = p.name;
1098 j["type"] = p.type;
1099
1100 if ( std::holds_alternative<int>( p.value ) )
1101 {
1102 j["value"] = std::get<int>( p.value );
1103 }
1104 else if ( std::holds_alternative<double>( p.value ) )
1105 {
1106 j["value"] = std::get<double>( p.value );
1107 }
1108 else if ( std::holds_alternative<QgsPoint>( p.value ) )
1109 {
1110 QgsPoint point = std::get<QgsPoint>( p.value );
1111 double z = std::numeric_limits<double>::quiet_NaN();
1112 double m = std::numeric_limits<double>::quiet_NaN();
1113 if ( point.is3D() )
1114 z = point.z();
1115 if ( point.isMeasure() )
1116 m = point.m();
1117 j["value"] = std::vector<double> { point.x(), point.y(), z, m };
1118 }
1119 else if ( std::holds_alternative<QgsVector3D>( p.value ) )
1120 {
1121 QgsVector3D vect = std::get<QgsVector3D>( p.value );
1122 j["value"] = std::vector<double> { vect.x(), vect.y(), vect.z() };
1123 }
1124 else
1125 throw json::type_error::create( 306, u"Unknown type '%1'."_s.arg( p.type.c_str() ).toStdString(), nullptr );
1126}
1127
1128void sfcgal::from_json( const json &j, sfcgal::PrimitiveParameterDesc &p )
1129{
1130 j.at( "name" ).get_to( p.name );
1131 j.at( "type" ).get_to( p.type );
1132 if ( j.contains( "value" ) )
1133 {
1134 json value = j.at( "value" );
1135 if ( p.type == "int" )
1136 {
1137 p.value = value.get<int>();
1138 }
1139 else if ( p.type == "double" )
1140 {
1141 p.value = value.get<double>();
1142 }
1143 else if ( p.type == "point3" )
1144 {
1145 std::vector<double> vect;
1146 vect = value.get<std::vector<double>>();
1147 QgsPoint point( vect[0], vect[1], //
1148 ( vect.size() > 2 ? vect[2] : std::numeric_limits<double>::quiet_NaN() ), //
1149 ( vect.size() > 3 ? vect[3] : std::numeric_limits<double>::quiet_NaN() ) );
1150 p.value = point;
1151 }
1152 else if ( p.type == "vector3" )
1153 {
1154 std::vector<double> vect;
1155 vect = value.get<std::vector<double>>();
1156 QgsPoint point( vect[0], vect[1], //
1157 ( vect.size() > 2 ? vect[2] : std::numeric_limits<double>::quiet_NaN() ), //
1158 ( vect.size() > 3 ? vect[3] : std::numeric_limits<double>::quiet_NaN() ) );
1159 p.value = point;
1160 }
1161 else
1162 throw json::type_error::create( 306, u"Unknown type '%1'."_s.arg( p.type.c_str() ).toStdString(), nullptr );
1163 }
1164}
1165
1166QVector<sfcgal::PrimitiveParameterDesc> QgsSfcgalEngine::primitiveParameters( const sfcgal::primitive *prim, QString *errorMsg )
1167{
1168 sfcgal::errorHandler()->clearText( errorMsg );
1169 CHECK_NOT_NULL( prim, QVector<sfcgal::PrimitiveParameterDesc>() );
1170
1171 char *jsonChars = nullptr;
1172 size_t len = 0;
1173 sfcgal_primitive_parameters( prim, &jsonChars, &len );
1174 CHECK_SUCCESS( errorMsg, QVector<sfcgal::PrimitiveParameterDesc>() );
1175
1176 std::string jsonString( jsonChars, len );
1177 sfcgal_free_buffer( jsonChars );
1178
1179 QVector<sfcgal::PrimitiveParameterDesc> result;
1180 try
1181 {
1182 const auto jParams = json::parse( jsonString );
1183 for ( const auto &jParam : jParams )
1184 {
1185 result.append( jParam.get<sfcgal::PrimitiveParameterDesc>() );
1186 }
1187 }
1188 catch ( json::exception &e )
1189 {
1190 sfcgal::errorHandler()->addText( u"Caught json exception for json: %1. Error: %2"_s.arg( jsonString.c_str() ).arg( e.what() ) );
1191 }
1192
1193 return result;
1194}
1195
1196QVariant QgsSfcgalEngine::primitiveParameter( const sfcgal::primitive *prim, const QString &name, QString *errorMsg )
1197{
1198 sfcgal::errorHandler()->clearText( errorMsg );
1199 CHECK_NOT_NULL( prim, QVariant() );
1200
1201 char *jsonChars = nullptr;
1202 size_t len = 0;
1203 sfcgal_primitive_parameter( prim, name.toStdString().c_str(), &jsonChars, &len );
1204 CHECK_SUCCESS( errorMsg, QVariant() );
1205
1206 std::string jsonString( jsonChars, len );
1207 sfcgal_free_buffer( jsonChars );
1208
1209 QVariant result;
1210 try
1211 {
1212 const auto jParam = json::parse( jsonString );
1213 sfcgal::PrimitiveParameterDesc param = jParam.get<sfcgal::PrimitiveParameterDesc>();
1214 result = QVariant::fromStdVariant( param.value );
1215 }
1216 catch ( json::exception &e )
1217 {
1218 sfcgal::errorHandler()->addText( u"Caught json exception for json: %1. Error: %2"_s.arg( jsonString.c_str() ).arg( e.what() ) );
1219 }
1220
1221 return result;
1222}
1223
1224void QgsSfcgalEngine::primitiveSetParameter( sfcgal::primitive *prim, const QString &name, const QVariant &value, QString *errorMsg )
1225{
1226 sfcgal::errorHandler()->clearText( errorMsg );
1227 CHECK_NOT_NULL( prim, void() );
1228
1229 try
1230 {
1231 json jParam;
1232 sfcgal::PrimitiveParameterDesc paramDesc;
1233 paramDesc.name = name.toStdString();
1234 paramDesc.type = value.typeName();
1235 if ( paramDesc.type == "int" )
1236 paramDesc.value = value.toInt();
1237 else if ( paramDesc.type == "double" )
1238 paramDesc.value = value.toDouble();
1239 else if ( value.canConvert<QgsPoint>() )
1240 paramDesc.value = value.value<QgsPoint>();
1241 else if ( value.canConvert<QgsVector3D>() )
1242 paramDesc.value = value.value<QgsVector3D>();
1243
1244 sfcgal::to_json( jParam, paramDesc );
1245 std::string jsonStr = jParam.dump();
1246 sfcgal_primitive_set_parameter( prim, name.toStdString().c_str(), jsonStr.c_str() );
1247 CHECK_SUCCESS( errorMsg, void() );
1248 }
1249 catch ( ... )
1250 {
1251 sfcgal::errorHandler()->addText( u"Caught json exception"_s );
1252 }
1253}
1254
1255#endif
1256
1257
1258#endif // #ifdef WITH_SFCGAL
JoinStyle3D
Join styles for 3D buffers.
Definition qgis.h:2192
@ CylindersAndSpheres
Cylinders along the linestring segments with spheres at the vertices.
Definition qgis.h:2195
@ Flat
Flat ends and constant width along the linestring.
Definition qgis.h:2194
@ Round
Smooth, rounded buffer around the input geometry.
Definition qgis.h:2193
JoinStyle
Join styles for buffers.
Definition qgis.h:2179
@ Round
Use rounded joins.
Definition qgis.h:2180
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:280
@ Unknown
Unknown.
Definition qgis.h:281
@ TriangleZM
TriangleZM.
Definition qgis.h:345
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:139
int remaining() const
remaining
Definition qgswkbptr.h:196
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:165
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:760
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:52
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:54
double x() const
Returns X coordinate.
Definition qgsvector3d.h:50