QGIS API Documentation 4.1.0-Master (01362494303)
Loading...
Searching...
No Matches
qgssfcgalengine.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgssfcgalengine.cpp
3 ----------------
4 begin : May 2025
5 copyright : (C) 2025 by Oslandia
6 email : benoit dot de dot mezzo at oslandia dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#ifdef WITH_SFCGAL
19#include "qgssfcgalengine.h"
20
21#include <SFCGAL/capi/sfcgal_c.h>
22#include <nlohmann/json.hpp>
23
24#include "qgsgeometry.h"
25#include "qgsgeometryfactory.h"
26#include "qgssfcgalgeometry.h"
27
28#include <QString>
29
30using namespace Qt::StringLiterals;
31
32// ===================================
33// sfcgal namespace
34// ===================================
35
36thread_local sfcgal::ErrorHandler sSfcgalErrorHandler;
37
38sfcgal::ErrorHandler *sfcgal::errorHandler()
39{
40 return &sSfcgalErrorHandler;
41}
42
43void sfcgal::GeometryDeleter::operator()( sfcgal::geometry *geom ) const
44{
45 sfcgal_geometry_delete( geom );
46}
47
48sfcgal::shared_geom sfcgal::make_shared_geom( sfcgal::geometry *geom )
49{
50 return sfcgal::shared_geom( geom, sfcgal::GeometryDeleter() );
51}
52
53
54#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 3, 0 )
55void sfcgal::PrimitiveDeleter::operator()( sfcgal::primitive *prim ) const
56{
57 sfcgal_primitive_delete( prim );
58}
59
60sfcgal::shared_prim sfcgal::make_shared_prim( sfcgal::primitive *prim )
61{
62 return sfcgal::shared_prim( prim, sfcgal::PrimitiveDeleter() );
63}
64#endif
65
66bool sfcgal::ErrorHandler::hasSucceedOrStack( QString *errorMsg, const std::source_location &location )
67{
68 bool succeed = isTextEmpty();
69 if ( !succeed )
70 {
71 addText( "relaying error from: ", location );
72 if ( errorMsg )
73 {
74 errorMsg->append( errorMessages.first() );
75 }
76 }
77 return succeed;
78}
79
80int sfcgal::errorCallback( const char *fmt, ... )
81{
82 va_list ap;
83 char buffer[1024];
84
85 va_start( ap, fmt );
86 vsnprintf( buffer, sizeof buffer, fmt, ap );
87 va_end( ap );
88
89 sfcgal::errorHandler()->addText( u"SFCGAL error occurred: %1"_s.arg( buffer ) );
90
91 return static_cast<int>( strlen( buffer ) );
92}
93
94int sfcgal::warningCallback( const char *fmt, ... )
95{
96 va_list ap;
97 char buffer[1024];
98
99 va_start( ap, fmt );
100 vsnprintf( buffer, sizeof buffer, fmt, ap );
101 va_end( ap );
102
103 sfcgal::errorHandler()->addText( u"SFCGAL warning occurred: %1"_s.arg( buffer ) );
104
105 return static_cast<int>( strlen( buffer ) );
106}
107
108
109sfcgal::ErrorHandler::ErrorHandler()
110{
111 sfcgal_init(); // empty but called
112 sfcgal_set_error_handlers( sfcgal::warningCallback, sfcgal::errorCallback );
113}
114
115void sfcgal::ErrorHandler::clearText( QString *errorMsg )
116{
117 errorMessages.clear();
118 if ( errorMsg )
119 {
120 errorMsg->clear();
121 }
122}
123
124QString sfcgal::ErrorHandler::getMainText() const
125{
126 return errorMessages.isEmpty() ? QString() : QString( "Error occurred: " ) + errorMessages.last();
127}
128
129QString sfcgal::ErrorHandler::getFullText() const
130{
131 return errorMessages.isEmpty() ? QString() : getMainText() + "\n\t\t" + errorMessages.join( "\n\t\t" );
132}
133
134bool sfcgal::ErrorHandler::isTextEmpty() const
135{
136 return errorMessages.isEmpty();
137}
138
139void sfcgal::ErrorHandler::addText( const QString &msg, const std::source_location &location )
140{
141 QString txt = QString( "%2 (%3:%4) %1" )
142 .arg( msg ) //
143 .arg( QString::fromStdString( location.function_name() ) ) //
144 .arg( QString::fromStdString( location.file_name() ) ) //
145 .arg( location.line() );
146
147 errorMessages.push_front( txt );
148}
149
150// ===================================
151// QgsSfcgalEngine static functions
152// ===================================
153
162template<typename T> static T geom_to_primtype( T ( *func_2d )( const sfcgal_geometry_t * ), T ( *func_3d )( const sfcgal_geometry_t * ), const sfcgal::geometry *geom, QString *errorMsg )
163{
164 sfcgal::errorHandler()->clearText( errorMsg );
165 CHECK_NOT_NULL( geom, std::numeric_limits<T>::quiet_NaN() );
166
167 T result;
168 if ( func_3d && sfcgal_geometry_is_3d( geom ) )
169 result = func_3d( geom );
170 else
171 result = func_2d( geom );
172
173 CHECK_SUCCESS( errorMsg, std::numeric_limits<T>::quiet_NaN() );
174
175 return result;
176};
177
187template<typename T> static T geomgeom_to_primtype(
188 T ( *func_2d )( const sfcgal_geometry_t *, const sfcgal_geometry_t * ),
189 T ( *func_3d )( const sfcgal_geometry_t *, const sfcgal_geometry_t * ),
190 const sfcgal::geometry *geomA,
191 const sfcgal::geometry *geomB,
192 QString *errorMsg
193)
194{
195 sfcgal::errorHandler()->clearText( errorMsg );
196 CHECK_NOT_NULL( geomA, false );
197 CHECK_NOT_NULL( geomB, false );
198
199 T result;
200 if ( func_3d && ( sfcgal_geometry_is_3d( geomA ) || sfcgal_geometry_is_3d( geomB ) ) )
201 result = func_3d( geomA, geomB );
202 else
203 result = func_2d( geomA, geomB );
204
205 CHECK_SUCCESS( errorMsg, std::numeric_limits<T>::quiet_NaN() );
206
207 return result;
208};
209
218static sfcgal::shared_geom geom_to_geom( sfcgal::func_geom_to_geom func_2d, sfcgal::func_geom_to_geom func_3d, const sfcgal::geometry *geom, QString *errorMsg )
219{
220 sfcgal::errorHandler()->clearText( errorMsg );
221 CHECK_NOT_NULL( geom, nullptr );
222
223 sfcgal::geometry *result = nullptr;
224 if ( func_3d && sfcgal_geometry_is_3d( geom ) )
225 result = func_3d( geom );
226 else
227 result = func_2d( geom );
228
229 CHECK_SUCCESS( errorMsg, nullptr );
230 CHECK_NOT_NULL( result, nullptr );
231
232 return sfcgal::make_shared_geom( result );
233};
234
235
245static sfcgal::shared_geom geomgeom_to_geom( sfcgal::func_geomgeom_to_geom func_2d, sfcgal::func_geomgeom_to_geom func_3d, const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, QString *errorMsg )
246{
247 sfcgal::errorHandler()->clearText( errorMsg );
248 CHECK_NOT_NULL( geomA, nullptr );
249 CHECK_NOT_NULL( geomB, nullptr );
250
251 sfcgal::geometry *result = nullptr;
252 if ( func_3d && ( sfcgal_geometry_is_3d( geomA ) || sfcgal_geometry_is_3d( geomB ) ) )
253 result = func_3d( geomA, geomB );
254 else
255 result = func_2d( geomA, geomB );
256
257 CHECK_SUCCESS( errorMsg, nullptr );
258 CHECK_NOT_NULL( result, nullptr );
259
260 return sfcgal::make_shared_geom( result );
261};
262
263
264// ===================================
265// QgsSfcgalEngine class
266// ===================================
267
268
269std::unique_ptr<QgsSfcgalGeometry> QgsSfcgalEngine::toSfcgalGeometry( sfcgal::shared_geom &geom, QString *errorMsg )
270{
271 sfcgal::errorHandler()->clearText( errorMsg );
272 CHECK_NOT_NULL( geom.get(), nullptr );
273
274 return std::make_unique<QgsSfcgalGeometry>( geom );
275}
276
277std::unique_ptr<QgsAbstractGeometry> QgsSfcgalEngine::toAbstractGeometry( const sfcgal::geometry *geom, QString *errorMsg )
278{
279 std::unique_ptr<QgsAbstractGeometry> out( nullptr );
280 sfcgal::errorHandler()->clearText( errorMsg );
281 CHECK_NOT_NULL( geom, out );
282
283 QByteArray wkbArray = QgsSfcgalEngine::toWkb( geom, errorMsg );
284 CHECK_SUCCESS( errorMsg, out );
285
286 QgsConstWkbPtr wkbPtr( wkbArray );
287 out = QgsGeometryFactory::geomFromWkb( wkbPtr );
288 if ( !out )
289 {
290 Qgis::WkbType sfcgalType = QgsSfcgalEngine::wkbType( geom );
291 sfcgal::errorHandler()->addText(
292 u"WKB contains unmanaged geometry type (WKB:%1 / SFCGAL:%2"_s //
293 .arg( static_cast<int>( wkbPtr.readHeader() ) ) //
294 .arg( static_cast<int>( sfcgalType ) )
295 );
296 }
297
298 return out;
299}
300
301sfcgal::shared_geom QgsSfcgalEngine::fromAbstractGeometry( const QgsAbstractGeometry *geom, QString *errorMsg )
302{
303 sfcgal::errorHandler()->clearText( errorMsg );
304 CHECK_NOT_NULL( geom, sfcgal::shared_geom( nullptr ) );
305
306 QByteArray wkbBytes = geom->asWkb();
307
308 sfcgal::geometry *out = sfcgal_io_read_wkb( wkbBytes.data(), wkbBytes.length() );
309 CHECK_SUCCESS( errorMsg, nullptr );
310
311 return sfcgal::make_shared_geom( out );
312}
313
314sfcgal::shared_geom QgsSfcgalEngine::cloneGeometry( const sfcgal::geometry *geom, QString *errorMsg )
315{
316 sfcgal::shared_geom out = geom_to_geom( sfcgal_geometry_clone, nullptr, geom, errorMsg );
317 CHECK_SUCCESS( errorMsg, nullptr );
318 return out;
319}
320
321QString QgsSfcgalEngine::geometryType( const sfcgal::geometry *geom, QString *errorMsg )
322{
323#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
324 ( void ) geom;
325 ( void ) errorMsg;
326 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "geometryType" ) );
327#else
328 sfcgal::errorHandler()->clearText( errorMsg );
329
330 char *typeChar;
331 size_t typeLen;
332 sfcgal_geometry_type( geom, &typeChar, &typeLen );
333 std::string typeStr( typeChar, typeLen );
334 sfcgal_free_buffer( typeChar );
335
336 return QString::fromStdString( typeStr );
337#endif
338}
339
340sfcgal::shared_geom QgsSfcgalEngine::fromWkb( const QgsConstWkbPtr &wkbPtr, QString *errorMsg )
341{
342 sfcgal::errorHandler()->clearText( errorMsg );
343
344 const unsigned char *wkbUnsignedPtr = wkbPtr;
345 sfcgal::geometry *out = sfcgal_io_read_wkb( reinterpret_cast<const char *>( wkbUnsignedPtr ), wkbPtr.remaining() );
346 CHECK_SUCCESS( errorMsg, nullptr );
347
348 return sfcgal::make_shared_geom( out );
349}
350
351sfcgal::shared_geom QgsSfcgalEngine::fromWkt( const QString &wkt, QString *errorMsg )
352{
353 sfcgal::errorHandler()->clearText( errorMsg );
354
355 sfcgal::geometry *out = sfcgal_io_read_wkt( wkt.toStdString().c_str(), wkt.length() );
356 CHECK_SUCCESS( errorMsg, nullptr );
357
358 return sfcgal::unique_geom( out );
359}
360
361QByteArray QgsSfcgalEngine::toWkb( const sfcgal::geometry *geom, QString *errorMsg )
362{
363 sfcgal::errorHandler()->clearText( errorMsg );
364 CHECK_NOT_NULL( geom, QByteArray() );
365
366 char *wkbHex;
367 size_t len = 0;
368 sfcgal_geometry_as_wkb( geom, &wkbHex, &len );
369 CHECK_SUCCESS( errorMsg, QByteArray() );
370 QByteArray wkbArray( wkbHex, static_cast<int>( len ) );
371
372#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
373 sfcgal_free_buffer( wkbHex );
374#else
375 free( wkbHex );
376#endif
377
378 return wkbArray;
379}
380
381QString QgsSfcgalEngine::toWkt( const sfcgal::geometry *geom, int numDecimals, QString *errorMsg )
382{
383 sfcgal::errorHandler()->clearText( errorMsg );
384 CHECK_NOT_NULL( geom, QString() );
385
386 char *wkt;
387 size_t len = 0;
388 sfcgal_geometry_as_text_decim( geom, numDecimals, &wkt, &len );
389 CHECK_SUCCESS( errorMsg, QString() );
390
391 std::string wktString( wkt, len );
392#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
393 sfcgal_free_buffer( wkt );
394#else
395 free( wkt );
396#endif
397 return QString::fromStdString( wktString );
398}
399
400Qgis::WkbType QgsSfcgalEngine::wkbType( const sfcgal::geometry *geom, QString *errorMsg )
401{
402 sfcgal::errorHandler()->clearText( errorMsg );
403 CHECK_NOT_NULL( geom, Qgis::WkbType::Unknown );
404
405 sfcgal_geometry_type_t type = sfcgal_geometry_type_id( geom );
406 CHECK_SUCCESS( errorMsg, Qgis::WkbType::Unknown );
407
408 int wkbType = type;
409 if ( sfcgal_geometry_is_3d( geom ) )
410 wkbType += 1000;
411
412 if ( sfcgal_geometry_is_measured( geom ) )
413 wkbType += 2000;
414
415 Qgis::WkbType qgisType = static_cast<Qgis::WkbType>( wkbType );
416 if ( qgisType >= Qgis::WkbType::Unknown && qgisType <= Qgis::WkbType::TriangleZM )
417 return qgisType;
418
419 sfcgal::errorHandler()->addText( u"WKB type '%1' is not known from QGIS"_s.arg( wkbType ) );
421}
422
423int QgsSfcgalEngine::dimension( const sfcgal::geometry *geom, QString *errorMsg )
424{
425#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
426 ( void ) geom;
427 ( void ) errorMsg;
428 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "dimension" ) );
429#else
430 int out = geom_to_primtype<int>( sfcgal_geometry_dimension, nullptr, geom, errorMsg );
431 CHECK_SUCCESS( errorMsg, std::numeric_limits<int>::quiet_NaN() );
432 return out;
433#endif
434}
435
436int QgsSfcgalEngine::partCount( const sfcgal::geometry *geom, QString *errorMsg )
437{
438 size_t out;
439 sfcgal::errorHandler()->clearText( errorMsg );
440 CHECK_NOT_NULL( geom, -1 );
441
442 sfcgal_geometry_type_t type = sfcgal_geometry_type_id( geom );
443 CHECK_SUCCESS( errorMsg, -1 );
444
445 switch ( type )
446 {
447 case SFCGAL_TYPE_MULTIPOINT:
448 case SFCGAL_TYPE_MULTILINESTRING:
449 case SFCGAL_TYPE_MULTIPOLYGON:
450 case SFCGAL_TYPE_MULTISOLID:
451 case SFCGAL_TYPE_GEOMETRYCOLLECTION:
452#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
453 out = sfcgal_geometry_num_geometries( geom );
454#else
455 out = sfcgal_geometry_collection_num_geometries( geom );
456#endif
457 break;
458 case SFCGAL_TYPE_POLYGON:
459 out = sfcgal_polygon_num_interior_rings( geom ) + 1;
460 break;
461 case SFCGAL_TYPE_SOLID:
462 out = sfcgal_solid_num_shells( geom );
463 break;
464 case SFCGAL_TYPE_POLYHEDRALSURFACE:
465#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
466 out = sfcgal_polyhedral_surface_num_patches( geom );
467#else
468 out = sfcgal_polyhedral_surface_num_polygons( geom );
469#endif
470 break;
471 case SFCGAL_TYPE_TRIANGULATEDSURFACE:
472#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
473 out = sfcgal_triangulated_surface_num_patches( geom );
474#else
475 out = sfcgal_triangulated_surface_num_triangles( geom );
476#endif
477 break;
478 case SFCGAL_TYPE_LINESTRING:
479 out = sfcgal_linestring_num_points( geom );
480 break;
481 case SFCGAL_TYPE_TRIANGLE:
482 out = 3;
483 break;
484 case SFCGAL_TYPE_POINT:
485 out = 1;
486 break;
487 default:
488 out = -1;
489 }
490
491 CHECK_SUCCESS( errorMsg, -1 );
492
493 return static_cast<int>( out );
494}
495
496bool QgsSfcgalEngine::addZValue( sfcgal::geometry *geom, double zValue, QString *errorMsg )
497{
498#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
499 ( void ) geom;
500 ( void ) zValue;
501 ( void ) errorMsg;
502 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "addZValue" ) );
503#else
504 sfcgal::errorHandler()->clearText( errorMsg );
505 CHECK_NOT_NULL( geom, false );
506
507 return sfcgal_geometry_force_z( geom, zValue );
508#endif
509}
510
511bool QgsSfcgalEngine::addMValue( sfcgal::geometry *geom, double mValue, QString *errorMsg )
512{
513#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
514 ( void ) geom;
515 ( void ) mValue;
516 ( void ) errorMsg;
517 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "addMValue" ) );
518#else
519 sfcgal::errorHandler()->clearText( errorMsg );
520 CHECK_NOT_NULL( geom, false );
521
522 return sfcgal_geometry_force_m( geom, mValue );
523#endif
524}
525
526bool QgsSfcgalEngine::dropZValue( sfcgal::geometry *geom, QString *errorMsg )
527{
528#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
529 ( void ) geom;
530 ( void ) errorMsg;
531 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "dropZValue" ) );
532#else
533 sfcgal::errorHandler()->clearText( errorMsg );
534 CHECK_NOT_NULL( geom, false );
535
536 return sfcgal_geometry_drop_z( geom );
537#endif
538}
539
540bool QgsSfcgalEngine::dropMValue( sfcgal::geometry *geom, QString *errorMsg )
541{
542#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
543 ( void ) geom;
544 ( void ) errorMsg;
545 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "dropMValue" ) );
546#else
547 sfcgal::errorHandler()->clearText( errorMsg );
548 CHECK_NOT_NULL( geom, false );
549
550 return sfcgal_geometry_drop_m( geom );
551#endif
552}
553
554void QgsSfcgalEngine::swapXy( sfcgal::geometry *geom, QString *errorMsg )
555{
556#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
557 ( void ) geom;
558 ( void ) errorMsg;
559 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "swapXy" ) );
560#else
561 sfcgal::errorHandler()->clearText( errorMsg );
562 CHECK_NOT_NULL( geom, void() );
563
564 sfcgal_geometry_swap_xy( geom );
565#endif
566}
567
568bool QgsSfcgalEngine::isEqual( const sfcgal::geometry *geomA, const sfcgal::geometry *geomB, double tolerance, QString *errorMsg )
569{
570#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
571 ( void ) geomA;
572 ( void ) geomB;
573 ( void ) tolerance;
574 ( void ) errorMsg;
575 throw QgsNotSupportedException( QObject::tr( "Using %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "isEqual" ) );
576#else
577 sfcgal::errorHandler()->clearText( errorMsg );
578 CHECK_NOT_NULL( geomA, false );
579 CHECK_NOT_NULL( geomB, false );
580
581 bool result = sfcgal_geometry_is_almost_equals( geomA, geomB, tolerance );
582 CHECK_SUCCESS( errorMsg, false );
583
584 return result;
585#endif
586}
587
588bool QgsSfcgalEngine::isEmpty( const sfcgal::geometry *geom, QString *errorMsg )
589{
590 int res = geom_to_primtype<int>( sfcgal_geometry_is_empty, nullptr, geom, errorMsg );
591 CHECK_SUCCESS( errorMsg, false );
592 return static_cast<bool>( res );
593}
594
595bool QgsSfcgalEngine::isValid( const sfcgal::geometry *geom, QString *errorMsg, 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::shared_geom polySurface = QgsSfcgalEngine::toPolyhedralSurface( solid, errorMsg );
928 sfcgal_geometry_delete( solid );
929
930 CHECK_SUCCESS( errorMsg, nullptr );
931
932 return polySurface;
933}
934
935sfcgal::shared_geom QgsSfcgalEngine::simplify( const sfcgal::geometry *geom, double tolerance, bool preserveTopology, QString *errorMsg )
936{
937#if SFCGAL_VERSION_NUM < SFCGAL_MAKE_VERSION( 2, 1, 0 )
938 ( void ) geom;
939 ( void ) tolerance;
940 ( void ) preserveTopology;
941 ( void ) errorMsg;
942 throw QgsNotSupportedException( QObject::tr( "Calculating %1 requires a QGIS build based on SFCGAL 2.1 or later" ).arg( "boundary" ) );
943#else
944 sfcgal::errorHandler()->clearText( errorMsg );
945 CHECK_NOT_NULL( geom, nullptr );
946
947 sfcgal::geometry *result = sfcgal_geometry_simplify( geom, tolerance, preserveTopology );
948 CHECK_SUCCESS( errorMsg, nullptr );
949
950 return sfcgal::make_shared_geom( result );
951#endif
952}
953
954sfcgal::shared_geom QgsSfcgalEngine::approximateMedialAxis( const sfcgal::geometry *geom, bool extendToEdges, QString *errorMsg )
955{
956 sfcgal::errorHandler()->clearText( errorMsg );
957 CHECK_NOT_NULL( geom, nullptr );
958
959#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 3, 0 )
960 sfcgal::geometry *result = nullptr;
961 if ( extendToEdges )
962 {
963 result = sfcgal_geometry_projected_medial_axis( geom );
964 }
965 else
966 {
967 result = sfcgal_geometry_approximate_medial_axis( geom );
968 }
969#else
970 Q_UNUSED( extendToEdges )
971 sfcgal::geometry *result = sfcgal_geometry_approximate_medial_axis( geom );
972#endif
973 CHECK_SUCCESS( errorMsg, nullptr );
974
975 return sfcgal::make_shared_geom( result );
976}
977
978sfcgal::shared_geom QgsSfcgalEngine::toSolid( const sfcgal::geometry *geom, QString *errorMsg )
979{
980 sfcgal::errorHandler()->clearText( errorMsg );
981 CHECK_NOT_NULL( geom, nullptr );
982
983 sfcgal::geometry *solid = sfcgal_geometry_make_solid( geom );
984 CHECK_SUCCESS( errorMsg, nullptr );
985
986 return sfcgal::make_shared_geom( solid );
987}
988
989sfcgal::shared_geom QgsSfcgalEngine::toPolyhedralSurface( const sfcgal::geometry *geom, QString *errorMsg )
990{
991 sfcgal::errorHandler()->clearText( errorMsg );
992 CHECK_NOT_NULL( geom, nullptr );
993
994 if ( sfcgal_geometry_type_id( geom ) != SFCGAL_TYPE_SOLID )
995 {
996 sfcgal::errorHandler()->addText( u"toPolyhedralSurface() only applies to solids"_s );
997 return nullptr;
998 }
999
1000 sfcgal_geometry_t *polySurface = sfcgal_polyhedral_surface_create();
1001 for ( unsigned int shellIdx = 0; shellIdx < sfcgal_solid_num_shells( geom ); ++shellIdx )
1002 {
1003 const sfcgal_geometry_t *shell = sfcgal_solid_shell_n( geom, shellIdx );
1004#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 1, 0 )
1005 for ( unsigned int polyIdx = 0; polyIdx < sfcgal_polyhedral_surface_num_patches( shell ); ++polyIdx )
1006 {
1007 const sfcgal_geometry_t *patch = sfcgal_polyhedral_surface_patch_n( shell, polyIdx );
1008 sfcgal_polyhedral_surface_add_patch( polySurface, sfcgal_geometry_clone( patch ) );
1009 }
1010#else
1011 for ( unsigned int polyIdx = 0; polyIdx < sfcgal_polyhedral_surface_num_polygons( shell ); ++polyIdx )
1012 {
1013 const sfcgal_geometry_t *patch = sfcgal_polyhedral_surface_polygon_n( shell, polyIdx );
1014 sfcgal_polyhedral_surface_add_polygon( polySurface, sfcgal_geometry_clone( patch ) );
1015 }
1016#endif
1017 }
1018
1019 CHECK_SUCCESS( errorMsg, nullptr );
1020 return sfcgal::make_shared_geom( polySurface );
1021}
1022
1023#if SFCGAL_VERSION_NUM >= SFCGAL_MAKE_VERSION( 2, 3, 0 )
1024sfcgal::shared_geom QgsSfcgalEngine::transform( const sfcgal::geometry *geom, const QgsMatrix4x4 &mat, QString *errorMsg )
1025{
1026 sfcgal::errorHandler()->clearText( errorMsg );
1027 CHECK_NOT_NULL( geom, nullptr );
1028
1029 sfcgal::geometry *result;
1030 result = sfcgal_geometry_transform( geom, mat.constData() );
1031
1032 CHECK_SUCCESS( errorMsg, nullptr );
1033 return sfcgal::make_shared_geom( result );
1034}
1035
1036std::unique_ptr<QgsSfcgalGeometry> QgsSfcgalEngine::toSfcgalGeometry( sfcgal::shared_prim &prim, sfcgal::primitiveType type, QString *errorMsg )
1037{
1038 sfcgal::errorHandler()->clearText( errorMsg );
1039 CHECK_NOT_NULL( prim.get(), nullptr );
1040
1041 return std::make_unique<QgsSfcgalGeometry>( prim, type );
1042}
1043
1044sfcgal::shared_prim QgsSfcgalEngine::createCube( double size, QString *errorMsg )
1045{
1046 sfcgal::primitive *result = sfcgal_primitive_create( SFCGAL_TYPE_CUBE );
1047 CHECK_SUCCESS( errorMsg, nullptr );
1048
1049 sfcgal_primitive_set_parameter_double( result, "size", size );
1050 CHECK_SUCCESS( errorMsg, nullptr );
1051
1052 return sfcgal::make_shared_prim( result );
1053}
1054
1055sfcgal::shared_geom QgsSfcgalEngine::primitiveAsPolyhedral( const sfcgal::primitive *prim, const QgsMatrix4x4 &mat, QString *errorMsg )
1056{
1057 sfcgal::errorHandler()->clearText( errorMsg );
1058 CHECK_NOT_NULL( prim, nullptr );
1059
1060 sfcgal::geometry *result = sfcgal_primitive_as_polyhedral_surface( prim );
1061 CHECK_SUCCESS( errorMsg, nullptr );
1062
1063 if ( !mat.isIdentity() )
1064 {
1065 sfcgal::geometry *result2 = sfcgal_geometry_transform( result, mat.constData() );
1066 sfcgal_geometry_delete( result );
1067 result = result2;
1068 CHECK_SUCCESS( errorMsg, nullptr );
1069 }
1070
1071 return sfcgal::make_shared_geom( result );
1072}
1073
1074bool QgsSfcgalEngine::primitiveIsEqual( const sfcgal::primitive *primA, const sfcgal::primitive *primB, double tolerance, QString *errorMsg )
1075{
1076 sfcgal::errorHandler()->clearText( errorMsg );
1077 CHECK_NOT_NULL( primA, false );
1078 CHECK_NOT_NULL( primB, false );
1079
1080 bool result = sfcgal_primitive_is_almost_equals( primA, primB, tolerance );
1081 CHECK_SUCCESS( errorMsg, false );
1082
1083 return result;
1084}
1085
1086sfcgal::shared_prim QgsSfcgalEngine::primitiveClone( const sfcgal::primitive *prim, QString *errorMsg )
1087{
1088 sfcgal::errorHandler()->clearText( errorMsg );
1089 CHECK_NOT_NULL( prim, nullptr );
1090
1091 sfcgal::primitive *result = sfcgal_primitive_clone( prim );
1092
1093 CHECK_SUCCESS( errorMsg, nullptr );
1094 CHECK_NOT_NULL( result, nullptr );
1095
1096 return sfcgal::make_shared_prim( result );
1097}
1098
1099double QgsSfcgalEngine::primitiveArea( const sfcgal::primitive *prim, bool withDiscretization, QString *errorMsg )
1100{
1101 sfcgal::errorHandler()->clearText( errorMsg );
1102 CHECK_NOT_NULL( prim, std::numeric_limits<double>::quiet_NaN() );
1103
1104 double out = sfcgal_primitive_area( prim, withDiscretization );
1105 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1106 return out;
1107}
1108
1109double QgsSfcgalEngine::primitiveVolume( const sfcgal::primitive *prim, bool withDiscretization, QString *errorMsg )
1110{
1111 sfcgal::errorHandler()->clearText( errorMsg );
1112 CHECK_NOT_NULL( prim, std::numeric_limits<double>::quiet_NaN() );
1113
1114 double out = sfcgal_primitive_volume( prim, withDiscretization );
1115 CHECK_SUCCESS( errorMsg, std::numeric_limits<double>::quiet_NaN() );
1116 return out;
1117}
1118
1119void sfcgal::to_json( json &j, const sfcgal::PrimitiveParameterDesc &p )
1120{
1121 j["name"] = p.name;
1122 j["type"] = p.type;
1123
1124 if ( std::holds_alternative<int>( p.value ) )
1125 {
1126 j["value"] = std::get<int>( p.value );
1127 }
1128 else if ( std::holds_alternative<double>( p.value ) )
1129 {
1130 j["value"] = std::get<double>( p.value );
1131 }
1132 else if ( std::holds_alternative<QgsPoint>( p.value ) )
1133 {
1134 QgsPoint point = std::get<QgsPoint>( p.value );
1135 double z = std::numeric_limits<double>::quiet_NaN();
1136 double m = std::numeric_limits<double>::quiet_NaN();
1137 if ( point.is3D() )
1138 z = point.z();
1139 if ( point.isMeasure() )
1140 m = point.m();
1141 j["value"] = std::vector<double> { point.x(), point.y(), z, m };
1142 }
1143 else if ( std::holds_alternative<QgsVector3D>( p.value ) )
1144 {
1145 QgsVector3D vect = std::get<QgsVector3D>( p.value );
1146 j["value"] = std::vector<double> { vect.x(), vect.y(), vect.z() };
1147 }
1148 else
1149 throw json::type_error::create( 306, u"Unknown type '%1'."_s.arg( p.type.c_str() ).toStdString(), nullptr );
1150}
1151
1152void sfcgal::from_json( const json &j, sfcgal::PrimitiveParameterDesc &p )
1153{
1154 j.at( "name" ).get_to( p.name );
1155 j.at( "type" ).get_to( p.type );
1156 if ( j.contains( "value" ) )
1157 {
1158 json value = j.at( "value" );
1159 if ( p.type == "int" )
1160 {
1161 p.value = value.get<int>();
1162 }
1163 else if ( p.type == "double" )
1164 {
1165 p.value = value.get<double>();
1166 }
1167 else if ( p.type == "point3" )
1168 {
1169 std::vector<double> vect;
1170 vect = value.get<std::vector<double>>();
1171 QgsPoint point(
1172 vect[0],
1173 vect[1], //
1174 ( vect.size() > 2 ? vect[2] : std::numeric_limits<double>::quiet_NaN() ), //
1175 ( vect.size() > 3 ? vect[3] : std::numeric_limits<double>::quiet_NaN() )
1176 );
1177 p.value = point;
1178 }
1179 else if ( p.type == "vector3" )
1180 {
1181 std::vector<double> vect;
1182 vect = value.get<std::vector<double>>();
1183 QgsPoint point(
1184 vect[0],
1185 vect[1], //
1186 ( vect.size() > 2 ? vect[2] : std::numeric_limits<double>::quiet_NaN() ), //
1187 ( vect.size() > 3 ? vect[3] : std::numeric_limits<double>::quiet_NaN() )
1188 );
1189 p.value = point;
1190 }
1191 else
1192 throw json::type_error::create( 306, u"Unknown type '%1'."_s.arg( p.type.c_str() ).toStdString(), nullptr );
1193 }
1194}
1195
1196QVector<sfcgal::PrimitiveParameterDesc> QgsSfcgalEngine::primitiveParameters( const sfcgal::primitive *prim, QString *errorMsg )
1197{
1198 sfcgal::errorHandler()->clearText( errorMsg );
1199 CHECK_NOT_NULL( prim, QVector<sfcgal::PrimitiveParameterDesc>() );
1200
1201 char *jsonChars = nullptr;
1202 size_t len = 0;
1203 sfcgal_primitive_parameters( prim, &jsonChars, &len );
1204 CHECK_SUCCESS( errorMsg, QVector<sfcgal::PrimitiveParameterDesc>() );
1205
1206 std::string jsonString( jsonChars, len );
1207 sfcgal_free_buffer( jsonChars );
1208
1209 QVector<sfcgal::PrimitiveParameterDesc> result;
1210 try
1211 {
1212 const auto jParams = json::parse( jsonString );
1213 for ( const auto &jParam : jParams )
1214 {
1215 result.append( jParam.get<sfcgal::PrimitiveParameterDesc>() );
1216 }
1217 }
1218 catch ( json::exception &e )
1219 {
1220 sfcgal::errorHandler()->addText( u"Caught json exception for json: %1. Error: %2"_s.arg( jsonString.c_str() ).arg( e.what() ) );
1221 }
1222
1223 return result;
1224}
1225
1226QVariant QgsSfcgalEngine::primitiveParameter( const sfcgal::primitive *prim, const QString &name, QString *errorMsg )
1227{
1228 sfcgal::errorHandler()->clearText( errorMsg );
1229 CHECK_NOT_NULL( prim, QVariant() );
1230
1231 char *jsonChars = nullptr;
1232 size_t len = 0;
1233 sfcgal_primitive_parameter( prim, name.toStdString().c_str(), &jsonChars, &len );
1234 CHECK_SUCCESS( errorMsg, QVariant() );
1235
1236 std::string jsonString( jsonChars, len );
1237 sfcgal_free_buffer( jsonChars );
1238
1239 QVariant result;
1240 try
1241 {
1242 const auto jParam = json::parse( jsonString );
1243 sfcgal::PrimitiveParameterDesc param = jParam.get<sfcgal::PrimitiveParameterDesc>();
1244 result = QVariant::fromStdVariant( param.value );
1245 }
1246 catch ( json::exception &e )
1247 {
1248 sfcgal::errorHandler()->addText( u"Caught json exception for json: %1. Error: %2"_s.arg( jsonString.c_str() ).arg( e.what() ) );
1249 }
1250
1251 return result;
1252}
1253
1254void QgsSfcgalEngine::primitiveSetParameter( sfcgal::primitive *prim, const QString &name, const QVariant &value, QString *errorMsg )
1255{
1256 sfcgal::errorHandler()->clearText( errorMsg );
1257 CHECK_NOT_NULL( prim, void() );
1258
1259 try
1260 {
1261 json jParam;
1262 sfcgal::PrimitiveParameterDesc paramDesc;
1263 paramDesc.name = name.toStdString();
1264 paramDesc.type = value.typeName();
1265 if ( paramDesc.type == "int" )
1266 paramDesc.value = value.toInt();
1267 else if ( paramDesc.type == "double" )
1268 paramDesc.value = value.toDouble();
1269 else if ( value.canConvert<QgsPoint>() )
1270 paramDesc.value = value.value<QgsPoint>();
1271 else if ( value.canConvert<QgsVector3D>() )
1272 paramDesc.value = value.value<QgsVector3D>();
1273
1274 sfcgal::to_json( jParam, paramDesc );
1275 std::string jsonStr = jParam.dump();
1276 sfcgal_primitive_set_parameter( prim, name.toStdString().c_str(), jsonStr.c_str() );
1277 CHECK_SUCCESS( errorMsg, void() );
1278 }
1279 catch ( ... )
1280 {
1281 sfcgal::errorHandler()->addText( u"Caught json exception"_s );
1282 }
1283}
1284
1285#endif
1286
1287
1288#endif // #ifdef WITH_SFCGAL
JoinStyle3D
Join styles for 3D buffers.
Definition qgis.h:2255
@ CylindersAndSpheres
Cylinders along the linestring segments with spheres at the vertices.
Definition qgis.h:2258
@ Flat
Flat ends and constant width along the linestring.
Definition qgis.h:2257
@ Round
Smooth, rounded buffer around the input geometry.
Definition qgis.h:2256
JoinStyle
Join styles for buffers.
Definition qgis.h:2242
@ Round
Use rounded joins.
Definition qgis.h:2243
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
@ Unknown
Unknown.
Definition qgis.h:295
@ TriangleZM
TriangleZM.
Definition qgis.h:359
Abstract base class for all geometries.
bool isMeasure() const
Returns true if the geometry contains m values.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
virtual QByteArray asWkb(WkbFlags flags=QgsAbstractGeometry::WkbFlags()) const =0
Returns a WKB representation of the geometry.
A const WKB pointer.
Definition qgswkbptr.h:211
int remaining() const
remaining
Definition qgswkbptr.h:292
static std::unique_ptr< QgsAbstractGeometry > geomFromWkb(QgsConstWkbPtr &wkb)
Construct geometry from a WKB string.
A geometry is the spatial representation of a feature.
Qgis::GeometryOperationResult addPartV2(const QVector< QgsPointXY > &points, Qgis::WkbType wkbType=Qgis::WkbType::Unknown)
Adds a new part to a the geometry.
A simple 4x4 matrix implementation useful for transformation in 3D space.
bool isIdentity() const
Returns whether this matrix is an identity matrix.
const double * constData() const
Returns pointer to the matrix data (stored in column-major order).
Custom exception class which is raised when an operation is not supported.
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
bool fromWkb(QgsConstWkbPtr &wkb) override
Sets the geometry from a WKB string.
Definition qgspoint.cpp:173
double z
Definition qgspoint.h:58
double x
Definition qgspoint.h:56
bool isEmpty() const override
Returns true if the geometry is empty.
Definition qgspoint.cpp: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