34#include <QtConcurrentMap>
36using namespace Qt::StringLiterals;
44QIcon QgsThinPlateSplineAlgorithmBase::icon()
const
49QString QgsThinPlateSplineAlgorithmBase::svgIconPath()
const
54QString QgsThinPlateSplineAlgorithmBase::group()
const
56 return QObject::tr(
"Interpolation" );
59QString QgsThinPlateSplineAlgorithmBase::groupId()
const
61 return u
"interpolation"_s;
64QList<QgsAcademicReference> QgsThinPlateSplineAlgorithmBase::academicReferences()
const
67 { u
"Donato, G."_s, u
"Belongie, S."_s },
69 u
"Approximation Methods for Thin Plate Spline Mappings and Principal Warps"_s,
70 u
"In Heyden, A., Sparr, G., Nielsen, M., Johansen, P. (Eds.): Computer Vision - ECCV 2002: 7th European Conference on Computer Vision, Copenhagen, Denmark, May 28-31, 2002, Proceedings, Part III, Lecture Notes in Computer Science."_s,
71 u
"Springer-Verlag Heidelberg"_s,
76 =
QgsAcademicReference::createWebPage( { u
"Elonen, J."_s }, 2005, u
"Thin Plate Spline editor - an example program in C++"_s, u
"http://elonen.iki.fi/code/tpsdemo/index.html"_s );
77 return { donatoReference, elonenReference };
80QList<QgsProcessingAlgorithm::ExternalLink> QgsThinPlateSplineAlgorithmBase::externalLinks()
const
83 QgsProcessingAlgorithm::ExternalLink { QObject::tr(
"SAGA tool source code" ), u
"https://sourceforge.net/p/saga-gis/code/ci/33d1062b7120c696c9dd258378c48d86dc33560c/tree/saga-gis/src/tools/grid/grid_spline/Gridding_Spline_TPS_Local.cpp"_s }
87void QgsThinPlateSplineAlgorithmBase::addCommonParameters()
89 auto inputParam = std::make_unique<QgsProcessingParameterFeatureSource>( u
"INPUT"_s, QObject::tr(
"Point layer" ), QList<int> {
static_cast< int >(
Qgis::ProcessingSourceType::VectorPoint ) } );
90 inputParam->setHelp( QObject::tr(
"Vector point layer containing scattered control points with 3D coordinate or attribute values." ) );
91 addParameter( inputParam.release() );
94 fieldParam->setHelp( QObject::tr(
"Numeric attribute field containing the values (elevation/Z) to interpolate." ) );
95 addParameter( fieldParam.release() );
98 regularizationParam->setHelp(
100 "Regularization parameter (lambda), where a value of 0 produces an exact spline interpolation passing precisely "
101 "through all control points. Values > 0 introduce smoothing/tension to reduce noise and flatten high-frequency variations."
104 addParameter( regularizationParam.release() );
107void QgsThinPlateSplineAlgorithmBase::addOutputParameters()
109 auto extentParam = std::make_unique<QgsProcessingParameterExtent>( u
"EXTENT"_s, QObject::tr(
"Extent" ), QVariant(),
false );
110 extentParam->setHelp( QObject::tr(
"Bounding box defining the extent of the output raster grid." ) );
111 addParameter( extentParam.release() );
113 auto pixelSizeParam = std::make_unique<QgsProcessingParameterInterpolationPixelSize>( u
"PIXEL_SIZE"_s, QObject::tr(
"Output raster size" ), u
"INTERPOLATION_DATA"_s, u
"EXTENT"_s, 0.1 );
114 pixelSizeParam->setHelp( QObject::tr(
"Pixel size in layer units used to calculate output grid dimensions." ) );
115 addParameter( pixelSizeParam.release() );
118 outputNodataParam->setHelp( QObject::tr(
"The NODATA value to use in the output raster." ) );
120 addParameter( outputNodataParam.release() );
122 auto creationOptsParam = std::make_unique<QgsProcessingParameterString>( u
"CREATION_OPTIONS"_s, QObject::tr(
"Creation options" ), QVariant(),
false,
true );
123 creationOptsParam->setHelp( QObject::tr(
"The raster creation options for the output raster. These options control things like colorimetry, compression, etc." ) );
124 creationOptsParam->setMetadata( QVariantMap( { { u
"widget_wrapper"_s, QVariantMap( { { u
"widget_type"_s, u
"rasteroptions"_s } } ) } } ) );
126 addParameter( creationOptsParam.release() );
128 auto outputParam = std::make_unique<QgsProcessingParameterRasterDestination>( u
"OUTPUT"_s, QObject::tr(
"Interpolated" ) );
129 addParameter( outputParam.release() );
136 throw QgsProcessingException( QObject::tr(
"This algorithm requires a QGIS build with GSL support enabled." ) );
139 mSource.reset( parameterAsSource( parameters, u
"INPUT"_s, context ) );
143 mFieldName = parameterAsString( parameters, u
"FIELD"_s, context );
144 mRegularization = parameterAsDouble( parameters, u
"REGULARIZATION"_s, context );
146 mExtent = parameterAsExtent( parameters, u
"EXTENT"_s, context, mSource->sourceCrs() );
148 mPixelSize = parameterAsDouble( parameters, u
"PIXEL_SIZE"_s, context );
149 mOutputPath = parameterAsOutputLayer( parameters, u
"OUTPUT"_s, context );
150 mCreationOptions = parameterAsString( parameters, u
"CREATION_OPTIONS"_s, context ).trimmed();
151 mNoDataValue = parameterAsDouble( parameters, u
"NODATA"_s, context );
153 mFieldIndex = mSource->fields().lookupField( mFieldName );
154 if ( mFieldIndex < 0 )
156 throw QgsProcessingException( QObject::tr(
"Attribute field '%1' was not found in input layer." ).arg( mFieldName ) );
170 bool operator<(
const Neighbor &other )
const {
return distSq < other.distSq; }
175 qsizetype totalCells = 0;
176 qsizetype validCells = 0;
177 qsizetype unsolvableCells = 0;
178 std::size_t minNeighbors = std::numeric_limits<std::size_t>::max();
179 std::size_t maxNeighbors = 0;
180 unsigned long long sumNeighbors = 0;
181 double minDistance = std::numeric_limits<double>::max();
182 double maxDistance = 0.0;
183 double sumDistance = 0.0;
184 unsigned long long countDistances = 0;
186 void merge(
const CellStats &other )
188 totalCells += other.totalCells;
189 validCells += other.validCells;
190 unsolvableCells += other.unsolvableCells;
191 if ( other.minNeighbors != std::numeric_limits<std::size_t>::max() )
193 minNeighbors = std::min( minNeighbors, other.minNeighbors );
194 maxNeighbors = std::max( maxNeighbors, other.maxNeighbors );
196 sumNeighbors += other.sumNeighbors;
197 if ( other.minDistance != std::numeric_limits<double>::max() )
199 minDistance = std::min( minDistance, other.minDistance );
200 maxDistance = std::max( maxDistance, other.maxDistance );
202 sumDistance += other.sumDistance;
203 countDistances += other.countDistances;
210 std::vector<double> values;
214 typedef QHash<QgsFeatureId, double> FeatureZValueHash;
222QgsLocalThinPlateSplineAlgorithm::QgsLocalThinPlateSplineAlgorithm() =
default;
224QString QgsLocalThinPlateSplineAlgorithm::name()
const
226 return u
"localtpsinterpolation"_s;
229QString QgsLocalThinPlateSplineAlgorithm::displayName()
const
231 return QObject::tr(
"Thin Plate Spline interpolation (local)" );
234QStringList QgsLocalThinPlateSplineAlgorithm::tags()
const
236 return QObject::tr(
"tps,thin,plate,spline,interpolation,surface" ).split(
',' );
239QString QgsLocalThinPlateSplineAlgorithm::shortDescription()
const
241 return QObject::tr(
"Generates a Thin Plate Spline interpolation from scattered vector points." );
244QString QgsLocalThinPlateSplineAlgorithm::shortHelpString()
const
247 "This algorithm creates a 'Thin Plate Spline' (TPS) surface for each grid point based on scattered data points "
248 "within a specified local search distance. The number of points evaluated per cell can be constrained "
249 "to a maximum number of closest neighbors.\n\n"
250 "Thin Plate Splines minimize the integral of the squared second derivatives, creating a smooth surface "
251 "resembling a bent thin metal plate. Regularisation allows softening the exact fitting constraint to smooth out noise.\n\n"
252 "This algorithm is a port of the SAGA 'Thin Plate Spline' tool."
256QgsLocalThinPlateSplineAlgorithm *QgsLocalThinPlateSplineAlgorithm::createInstance()
const
258 return new QgsLocalThinPlateSplineAlgorithm();
261void QgsLocalThinPlateSplineAlgorithm::initAlgorithm(
const QVariantMap & )
263 addCommonParameters();
265 auto radiusParam = std::make_unique<QgsProcessingParameterDistance>( u
"SEARCH_RADIUS"_s, QObject::tr(
"Maximum Search Distance" ), 1000.0, u
"INPUT"_s,
false, 0.0 );
266 radiusParam->setHelp( QObject::tr(
"Local maximum search radius. Points farther than this distance from a grid cell center are ignored." ) );
267 addParameter( radiusParam.release() );
270 maxPtsParam->setHelp( QObject::tr(
"Maximum number of nearest points within the search distance to evaluate per grid cell." ) );
271 addParameter( maxPtsParam.release() );
274 minPtsParam->setHelp( QObject::tr(
"Minimum required points within search distance. At least 3 points are mandatory to solve a 2D spline; cells with fewer points are assigned NoData." ) );
275 addParameter( minPtsParam.release() );
277 addOutputParameters();
282 QGS_MARK_ALGORITHM_SOURCE
284 processBase( parameters, context, feedback );
286 const double searchRadius = parameterAsDouble( parameters, u
"SEARCH_RADIUS"_s, context );
287 const int maxPoints = parameterAsInt( parameters, u
"SEARCH_POINTS_MAX"_s, context );
288 const int minPoints = parameterAsInt( parameters, u
"SEARCH_POINTS_MIN"_s, context );
292 multiStepFeedback.setStepWeights( { 0.05, 0.95 } );
294 multiStepFeedback.setCurrentStep( 0 );
295 multiStepFeedback.pushInfo( QObject::tr(
"Building spatial index…" ) );
297 FeatureZValueHash zValues;
303 const long count = mSource->featureCount();
304 const double step = count > 0 ? 100.0 / count : 1;
306 long long current = 0;
309 [&zValues, ¤t, &multiStepFeedback, step,
this](
const QgsFeature &feature ) ->
bool {
310 multiStepFeedback.setProgress( current * step );
316 const QVariant fieldValue = feature.
attribute( mFieldIndex );
319 zValues[feature.
id()] = fieldValue.toDouble();
326 if ( multiStepFeedback.isCanceled() )
329 if ( zValues.size() < 3 )
331 throw QgsProcessingException( QObject::tr(
"At least 3 valid points with non-null Z attributes are required to form a Thin Plate Spline." ) );
334 const FeatureZValueHash constZValues = std::as_const( zValues );
336 multiStepFeedback.setCurrentStep( 1 );
337 multiStepFeedback.pushInfo( QObject::tr(
"Interpolating…" ) );
339 int cols = std::max( 1,
static_cast<int>( std::ceil( mExtent.width() / mPixelSize ) ) );
340 int rows = std::max( 1,
static_cast<int>( std::ceil( mExtent.height() / mPixelSize ) ) );
342 auto writer = std::make_unique<QgsRasterFileWriter>( mOutputPath );
343 writer->setOutputProviderKey( u
"gdal"_s );
344 if ( !mCreationOptions.isEmpty() )
346 writer->setCreationOptions( mCreationOptions.split(
'|' ) );
348 std::unique_ptr<QgsRasterDataProvider> provider( writer->createOneBandRaster(
Qgis::DataType::Float32, cols, rows, mExtent, mSource->sourceCrs() ) );
351 throw QgsProcessingException( QObject::tr(
"Could not write destination raster file: %1" ).arg( mOutputPath ) );
352 if ( !provider->isValid() )
355 provider->setNoDataValue( 1, mNoDataValue );
356 provider->setEditable(
true );
359 iter.startRasterRead( 1, cols, rows, mExtent );
367 std::unique_ptr<QgsRasterBlock> outputBlock;
369 CellStats globalStats;
371 while ( iter.readNextRasterPart( 1, iterCols, iterRows, outputBlock, topLeftCol, topLeftRow, &blockExtent ) )
373 if ( multiStepFeedback.isCanceled() )
376 QVector<int> tileRowIndices( iterRows );
377 std::iota( tileRowIndices.begin(), tileRowIndices.end(), 0 );
379 int completedTileRows = 0;
381 const CellStats tileStats = QtConcurrent::blockingMappedReduced<CellStats>(
383 [iterCols, maxPoints, topLeftRow, topLeftCol, searchRadius, minPoints, &constZValues, &kdTree, &multiStepFeedback,
this](
int r ) -> RowResult {
386 rowResult.values.resize( iterCols );
388 if ( multiStepFeedback.isCanceled() )
393 std::vector<Neighbor> neighbors;
395 const int globalRow = topLeftRow + r;
396 const double y = mExtent.yMaximum() - ( globalRow + 0.5 ) * mPixelSize;
398 for (
int c = 0;
c < iterCols; ++
c )
400 const int globalCol = topLeftCol +
c;
401 const double x = mExtent.xMinimum() + ( globalCol + 0.5 ) * mPixelSize;
405 const auto it = constZValues.constFind( data.
id );
406 if ( it != constZValues.constEnd() )
408 const double pX = data.
coords.first;
409 const double pY = data.
coords.second;
410 const double dx = pX - x;
411 const double dy = pY - y;
412 neighbors.emplace_back( Neighbor { pX, pY, it.value(), dx * dx + dy * dy } );
416 const std::size_t neighborCount = neighbors.size();
417 rowResult.stats.totalCells++;
418 rowResult.stats.minNeighbors = std::min( rowResult.stats.minNeighbors, neighborCount );
419 rowResult.stats.maxNeighbors = std::max( rowResult.stats.maxNeighbors, neighborCount );
420 rowResult.stats.sumNeighbors += neighborCount;
422 if (
static_cast< int >( neighborCount ) < minPoints )
424 rowResult.values[
c] = mNoDataValue;
428 int n =
static_cast< int >( neighborCount );
432 std::partial_sort( neighbors.begin(), neighbors.begin() + n, neighbors.end() );
436 for (
int i = 0; i < n; ++i )
438 const double dist = std::sqrt( neighbors[i].distSq );
439 rowResult.stats.minDistance = std::min( rowResult.stats.minDistance, dist );
440 rowResult.stats.maxDistance = std::max( rowResult.stats.maxDistance, dist );
441 rowResult.stats.sumDistance += dist;
442 rowResult.stats.countDistances++;
451 const int systemSize = n + 3;
455 double meanDist = 0.0;
456 for (
int i = 0; i < n; ++i )
458 const double iX = neighbors[i].x;
459 const double iY = neighbors[i].y;
460 for (
int j = i + 1; j < n; ++j )
462 const double dx = iX - neighbors[j].x;
463 const double dy = iY - neighbors[j].y;
464 const double distanceSquared = dx * dx + dy * dy;
465 const double distance = std::sqrt( distanceSquared );
466 const double baseVal = ( distance > 0.0 ) ? ( distanceSquared * std::log( distance ) ) : 0.0;
468 meanDist += distance * 2.0;
469 solver.setValue( i, j, baseVal );
470 solver.setValue( j, i, baseVal );
473 meanDist /= ( n * n );
476 for (
int i = 0; i < n; ++i )
479 solver.setValue( i, i, mRegularization * ( meanDist * meanDist ) );
482 solver.setValue( i, n, 1.0 );
483 solver.setValue( i, n + 1, neighbors[i].x );
484 solver.setValue( i, n + 2, neighbors[i].y );
486 solver.setValue( n, i, 1.0 );
487 solver.setValue( n + 1, i, neighbors[i].x );
488 solver.setValue( n + 2, i, neighbors[i].y );
491 solver.setRightHandSide( i, neighbors[i].z );
494 for (
int i = n; i < n + 3; ++i )
496 for (
int j = n; j < n + 3; ++j )
499 solver.setValue( i, j, 0.0 );
502 solver.setRightHandSide( i, 0.0 );
511 rowResult.values[
c] = mNoDataValue;
512 rowResult.stats.unsolvableCells++;
516 rowResult.stats.validCells++;
519 double zVal = W[n] + W[n + 1] * x + W[n + 2] * y;
520 for (
int i = 0; i < n; ++i )
522 double distSq = neighbors[i].distSq;
523 double U = ( distSq > 0.0 ) ? ( distSq * 0.5 * std::log( distSq ) ) : 0.0;
527 rowResult.values[
c] = zVal;
532 [&completedTileRows, feedback, iterRows, iterCols, &outputBlock, &iter]( CellStats &accumulatedStats,
const RowResult &rowResult ) {
533 accumulatedStats.merge( rowResult.stats );
535 for (
int c = 0;
c < iterCols; ++
c )
537 outputBlock->setValue( rowResult.r,
c, rowResult.values[
c] );
542 const double currentBlockProgress =
static_cast< double >( completedTileRows ) / iterRows;
543 const double blockProgressFraction = iter.progress( 1, currentBlockProgress );
544 const double overallProgress = 100.0 * ( 0.05 + 0.95 * blockProgressFraction );
545 feedback->setProgress( overallProgress );
549 globalStats.merge( tileStats );
551 if ( multiStepFeedback.isCanceled() )
554 if ( !provider->writeBlock( outputBlock.get(), 1, topLeftCol, topLeftRow ) )
556 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( provider->error().summary() ) );
558 multiStepFeedback.setProgress( 100.0 * iter.progress( 1 ) );
560 provider->setEditable(
false );
562 iter.stopRasterRead( 1 );
564 if ( globalStats.unsolvableCells > 0 )
566 multiStepFeedback.pushWarning( QObject::tr(
"The thin plate spline could not be solved for %1 raster cells. Try increasing the maximum search distance." ).arg( globalStats.unsolvableCells ) );
569 if ( globalStats.validCells > 0 )
571 const double meanNeighbors =
static_cast< double >( globalStats.sumNeighbors ) / globalStats.totalCells;
572 multiStepFeedback.pushInfo( QObject::tr(
"Neighbor count statistics:" ) );
573 multiStepFeedback.pushInfo( QObject::tr(
"• Minimum: %1" ).arg( globalStats.minNeighbors ) );
574 multiStepFeedback.pushInfo( QObject::tr(
"• Maximum: %1" ).arg( globalStats.maxNeighbors ) );
575 multiStepFeedback.pushInfo( QObject::tr(
"• Mean: %1" ).arg( QString::number( meanNeighbors,
'f', 2 ) ) );
578 if ( globalStats.countDistances > 0 )
580 const double meanDistance = globalStats.sumDistance / globalStats.countDistances;
581 multiStepFeedback.pushInfo( QObject::tr(
"Neighbor distance statistics:" ) );
582 multiStepFeedback.pushInfo( QObject::tr(
"• Minimum: %1" ).arg( QString::number( globalStats.minDistance,
'f', 4 ) ) );
583 multiStepFeedback.pushInfo( QObject::tr(
"• Maximum: %1" ).arg( QString::number( globalStats.maxDistance,
'f', 4 ) ) );
584 multiStepFeedback.pushInfo( QObject::tr(
"• Mean: %1" ).arg( QString::number( meanDistance,
'f', 4 ) ) );
587 if (
static_cast< int >( globalStats.maxNeighbors ) < minPoints )
589 multiStepFeedback.pushWarning(
590 QObject::tr(
"Maximum neighbors found within search radius was too small (got %1, required at least %2), output raster is empty" ).arg( globalStats.maxNeighbors ).arg( minPoints )
595 outputs.insert( u
"OUTPUT"_s, mOutputPath );
604QgsGlobalThinPlateSplineAlgorithm::QgsGlobalThinPlateSplineAlgorithm() =
default;
606QString QgsGlobalThinPlateSplineAlgorithm::name()
const
608 return u
"globaltpsinterpolation"_s;
611QString QgsGlobalThinPlateSplineAlgorithm::displayName()
const
613 return QObject::tr(
"Thin Plate Spline interpolation (global)" );
616QStringList QgsGlobalThinPlateSplineAlgorithm::tags()
const
618 return QObject::tr(
"tps,thin,plate,spline,interpolation,surface" ).split(
',' );
621QString QgsGlobalThinPlateSplineAlgorithm::shortDescription()
const
623 return QObject::tr(
"Generates a Thin Plate Spline interpolation from scattered vector points." );
626QString QgsGlobalThinPlateSplineAlgorithm::shortHelpString()
const
629 "This algorithm calculates a single global Thin Plate Spline surface passing through all input points simultaneously.\n\n"
630 "Thin Plate Splines minimize the integral of the squared second derivatives, creating a smooth surface "
631 "resembling a bent thin metal plate. Regularisation allows softening the exact fitting constraint to smooth out noise.\n\n"
632 "A global Thin Plate Spline interpolation constructs and solves a single linear system across all control points up front. "
633 "It guarantees a continuous surface without spatial windowing boundaries, but requires high memory "
634 "and computation time for datasets with large point counts.\n\n"
635 "This algorithm is a port of the SAGA 'Thin Plate Spline' tool."
639QgsGlobalThinPlateSplineAlgorithm *QgsGlobalThinPlateSplineAlgorithm::createInstance()
const
641 return new QgsGlobalThinPlateSplineAlgorithm();
644void QgsGlobalThinPlateSplineAlgorithm::initAlgorithm(
const QVariantMap & )
646 addCommonParameters();
647 addOutputParameters();
662 QGS_MARK_ALGORITHM_SOURCE
664 processBase( parameters, context, feedback );
668 multiStepFeedback.setStepWeights( { 0.10, 0.90 } );
670 multiStepFeedback.setCurrentStep( 0 );
671 multiStepFeedback.pushInfo( QObject::tr(
"Collecting control points and building global TPS matrix…" ) );
678 const long count = mSource->featureCount();
679 const double step = count > 0 ? 100.0 / count : 1;
681 std::vector<ControlPoint> globalPoints;
683 long long current = 0;
686 if ( multiStepFeedback.isCanceled() )
691 const QVariant fieldValue = feat.
attribute( mFieldIndex );
695 globalPoints.push_back( ControlPoint { pt.
x(), pt.
y(), fieldValue.toDouble() } );
699 multiStepFeedback.setProgress( current * step );
702 if ( multiStepFeedback.isCanceled() )
705 if ( globalPoints.size() < 3 )
707 throw QgsProcessingException( QObject::tr(
"At least 3 valid points with non-null Z attributes are required to form a Thin Plate Spline." ) );
709 const int n =
static_cast<int>( globalPoints.size() );
711 constexpr double MAX_MEMORY_MB = 1024.0;
712 constexpr double MAX_MEMORY_BYTES = MAX_MEMORY_MB * 1024 * 1024;
714 const double matrixSizeBytes = (
static_cast<double>( n + 3 ) * ( n + 3 ) *
sizeof( double ) );
716 if ( matrixSizeBytes > MAX_MEMORY_BYTES )
718 const int maxPointsAllowed =
static_cast<int>( std::sqrt( MAX_MEMORY_BYTES /
sizeof(
double ) ) ) - 3;
722 "Global Thin Plate Spline failed: Input layer contains %1 points, requiring approximately %2 MB of RAM for matrix operations. "
723 "Global TPS solving is limited to %3 MB of RAM (approximately %4 points) to prevent memory allocation crashes. "
724 "Please use the 'Thin Plate Spline interpolation (local)' algorithm for large point layers."
727 .arg( QString::number( matrixSizeBytes / ( 1024.0 * 1024.0 ),
'f', 1 ) )
728 .arg( MAX_MEMORY_MB )
729 .arg( maxPointsAllowed )
733 const int systemSize = n + 3;
735 QVector<double> globalW;
737 double meanDist = 0.0;
738 for (
int i = 0; i < n; ++i )
740 for (
int j = i + 1; j < n; ++j )
742 const double dx = globalPoints[i].x - globalPoints[j].x;
743 const double dy = globalPoints[i].y - globalPoints[j].y;
744 const double distSq = dx * dx + dy * dy;
745 const double dist = std::sqrt( distSq );
746 const double baseVal = ( distSq > 0.0 ) ? ( distSq * 0.5 * std::log( distSq ) ) : 0.0;
747 meanDist += dist * 2.0;
748 globalSolver.setValue( i, j, baseVal );
749 globalSolver.setValue( j, i, baseVal );
752 meanDist /= (
static_cast<double>( n ) * n );
755 for (
int i = 0; i < n; ++i )
757 globalSolver.setValue( i, i, mRegularization * ( meanDist * meanDist ) );
759 globalSolver.setValue( i, n, 1.0 );
760 globalSolver.setValue( i, n + 1, globalPoints[i].x );
761 globalSolver.setValue( i, n + 2, globalPoints[i].y );
763 globalSolver.setValue( n, i, 1.0 );
764 globalSolver.setValue( n + 1, i, globalPoints[i].x );
765 globalSolver.setValue( n + 2, i, globalPoints[i].y );
767 globalSolver.setRightHandSide( i, globalPoints[i].z );
769 for (
int i = n; i < n + 3; ++i )
771 for (
int j = n; j < n + 3; ++j )
773 globalSolver.setValue( i, j, 0.0 );
775 globalSolver.setRightHandSide( i, 0.0 );
778 multiStepFeedback.pushInfo( QObject::tr(
"Solving global linear system (%1 x %1)…" ).arg( systemSize ) );
788 multiStepFeedback.setCurrentStep( 1 );
789 multiStepFeedback.pushInfo( QObject::tr(
"Interpolating…" ) );
791 int cols = std::max( 1,
static_cast<int>( std::ceil( mExtent.width() / mPixelSize ) ) );
792 int rows = std::max( 1,
static_cast<int>( std::ceil( mExtent.height() / mPixelSize ) ) );
794 auto writer = std::make_unique<QgsRasterFileWriter>( mOutputPath );
795 writer->setOutputProviderKey( u
"gdal"_s );
796 if ( !mCreationOptions.isEmpty() )
798 writer->setCreationOptions( mCreationOptions.split(
'|' ) );
800 std::unique_ptr<QgsRasterDataProvider> provider( writer->createOneBandRaster(
Qgis::DataType::Float32, cols, rows, mExtent, mSource->sourceCrs() ) );
803 throw QgsProcessingException( QObject::tr(
"Could not write destination raster file: %1" ).arg( mOutputPath ) );
804 if ( !provider->isValid() )
807 provider->setNoDataValue( 1, mNoDataValue );
808 provider->setEditable(
true );
811 iter.startRasterRead( 1, cols, rows, mExtent );
819 std::unique_ptr<QgsRasterBlock> outputBlock;
820 const std::vector<ControlPoint> &constGlobalPoints = std::as_const( globalPoints );
821 const double a0 = globalW[n];
822 const double ax = globalW[n + 1];
823 const double ay = globalW[n + 2];
825 while ( iter.readNextRasterPart( 1, iterCols, iterRows, outputBlock, topLeftCol, topLeftRow, &blockExtent ) )
827 if ( multiStepFeedback.isCanceled() )
830 QVector<int> tileRowIndices( iterRows );
831 std::iota( tileRowIndices.begin(), tileRowIndices.end(), 0 );
833 int completedTileRows = 0;
835 QtConcurrent::blockingMappedReduced<int>(
837 [iterCols, topLeftRow, topLeftCol, &multiStepFeedback, a0, ax, ay, n, &globalW, &constGlobalPoints,
this](
int r ) -> RowResult {
840 rowResult.values.resize( iterCols );
842 if ( multiStepFeedback.isCanceled() )
845 const int globalRow = topLeftRow + r;
846 const double y = mExtent.yMaximum() - ( globalRow + 0.5 ) * mPixelSize;
848 for (
int c = 0;
c < iterCols; ++
c )
850 const int globalCol = topLeftCol +
c;
851 const double x = mExtent.xMinimum() + ( globalCol + 0.5 ) * mPixelSize;
853 double zVal = a0 + ax * x + ay * y;
854 for (
int i = 0; i < n; ++i )
856 const double dx = constGlobalPoints[i].x - x;
857 const double dy = constGlobalPoints[i].y - y;
858 const double distSq = dx * dx + dy * dy;
861 zVal += globalW[i] * ( distSq * 0.5 * std::log( distSq ) );
864 rowResult.values[
c] = zVal;
869 [&completedTileRows, feedback, iterRows, iterCols, &outputBlock, &iter](
int &,
const RowResult &rowResult ) {
870 for (
int c = 0;
c < iterCols; ++
c )
872 outputBlock->setValue( rowResult.r,
c, rowResult.values[
c] );
878 const double currentBlockProgress =
static_cast< double >( completedTileRows ) / iterRows;
879 feedback->setProgress( 100.0 * ( 0.10 + 0.90 * iter.progress( 1, currentBlockProgress ) ) );
883 if ( multiStepFeedback.isCanceled() )
886 if ( !provider->writeBlock( outputBlock.get(), 1, topLeftCol, topLeftRow ) )
888 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( provider->error().summary() ) );
890 multiStepFeedback.setProgress( 100.0 * iter.progress( 1 ) );
892 provider->setEditable(
false );
894 iter.stopRasterRead( 1 );
897 outputs.insert( u
"OUTPUT"_s, mOutputPath );
@ VectorPoint
Vector point layers.
@ Numeric
Accepts numeric fields.
@ Float32
Thirty two bit floating point (float).
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
@ LuWithSvdFallback
Try LU first; fallback to SVD on singularity.
@ Double
Double/float values.
Encapsulates an academic reference and formats it according to style guidelines.
static QgsAcademicReference createPresentation(const QStringList &authors, int year, const QString &title, const QString &meeting, const QString &publisher=QString(), const QString &pages=QString())
Creates a conference paper or presentation reference.
static QgsAcademicReference createWebPage(const QStringList &authors, int year, const QString &title, const QString &url)
Creates a web page or online resource reference.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
Contains utility functions for solving matrix operations.
static bool isAvailable()
Returns true if the matrix solver functionality is available on the current system.
Contains information about the context in which a processing algorithm is executed.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
Processing feedback object for multi-step operations.
Iterator for sequentially processing raster cells.
A rectangle specified with double values.
A container for data stored inside a QgsSpatialIndexKDBush index.
QgsFeatureId id
Feature ID.
std::pair< double, double > coords
Pair of coordinate data.
A very fast static spatial index for 2D points based on a flat KD-tree.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool operator<(const QVariant &v1, const QVariant &v2)
Compares two QVariant values and returns whether the first is less than the second.
Encapsulates details of an external link describing an algorithm's behavior or source.