CoastalME (Coastal Modelling Environment)
Simulates the long-term behaviour of complex coastlines
Loading...
Searching...
No Matches
spatial_interpolation.cpp
Go to the documentation of this file.
1//===============================================================================================================================
41//===============================================================================================================================
42#include <stdexcept>
43
45#include "cme.h"
46
47#ifdef _OPENMP
48#include <omp.h>
49#endif
50
51//===============================================================================================================================
58//===============================================================================================================================
59SpatialInterpolator::SpatialInterpolator(std::vector<Point2D> const& points,
60 std::vector<double> const& values,
61 int k_neighbors,
62 double power)
63 : m_values(values), m_kdtree(nullptr), m_k_neighbors(k_neighbors), m_power(power), m_owns_kdtree(true)
64{
65 if (points.size() != values.size())
66 throw std::invalid_argument("Points and values must have same size");
67
68 if (points.empty())
69 throw std::invalid_argument("Cannot create interpolator with empty data");
70
71 // Copy input points into point cloud structure
72 m_cloud.pts = points;
73
74 // Build k-d tree for fast spatial queries
75 // max_leaf_size=10 is a good balance between build time and query speed
76 m_kdtree = new KDTree(2, m_cloud, {10 /* max leaf size */});
77 m_kdtree->buildIndex();
78}
79
81 KDTree* kdtree,
82 std::vector<double> const& values,
83 int k_neighbors,
84 double power)
85 : m_cloud(cloud), m_values(values), m_kdtree(kdtree), m_k_neighbors(k_neighbors), m_power(power), m_owns_kdtree(false)
86{
87}
88
94
95//===============================================================================================================================
110//===============================================================================================================================
111double SpatialInterpolator::Interpolate(double x, double y) const
112{
113 // Prepare query point
114 double const query_pt[2] = {x, y};
115
116 // Find k nearest neighbors (or all points if fewer than k exist)
117 size_t const k = std::min((size_t) m_k_neighbors, m_cloud.pts.size());
118 std::vector<unsigned int> indices(k);
119 std::vector<double> sq_dists(k); // Squared distances (faster than actual distances)
120
121 long unsigned int num_found = m_kdtree->knnSearch(query_pt, k,
122 indices.data(),
123 sq_dists.data());
124
125 if (num_found == 0)
126 throw std::runtime_error("knnSearch found no neighbors");
127
128 // SPECIAL CASE: Query point coincides with an input point
129 // Return exact value to avoid division by zero
130 if (sq_dists[0] < EPSILON)
131 return m_values[indices[0]];
132
133 // Inverse Distance Weighting (IDW)
134 // Formula: result = Σ(w_i * v_i) / Σ(w_i) where w_i = 1/dist_i^power
135 double sum_weights = 0.0;
136 double sum_weighted_values = 0.0;
137
138 if (bFPIsEqual(m_power, 2.0, TOLERANCE))
139 {
140 // *** OPTIMIZED PATH for power=2.0 ***
141 // Since weight = 1/dist^2 and we have sq_dist = dist^2,
142 // we can use: weight = 1/sq_dist
143 // This avoids both sqrt() and pow() calls
144 for (size_t i = 0; i < num_found; i++)
145 {
146 double weight = 1.0 / sq_dists[i]; // 1/dist^2 = 1/sq_dist
147 sum_weights += weight;
148 sum_weighted_values += weight * m_values[indices[i]];
149 }
150 }
151 else
152 {
153 // *** GENERAL CASE for arbitrary power ***
154 // Need to calculate actual distance and apply pow()
155 for (size_t i = 0; i < num_found; i++)
156 {
157 double dist = std::sqrt(sq_dists[i]);
158 double weight = 1.0 / std::pow(dist, m_power);
159 sum_weights += weight;
160 sum_weighted_values += weight * m_values[indices[i]];
161 }
162 }
163
164 return sum_weighted_values / sum_weights;
165}
166
167void SpatialInterpolator::Interpolate(std::vector<Point2D> const& query_points,
168 std::vector<double>& results) const
169{
170 results.resize(query_points.size());
171 size_t const k = std::min((size_t) m_k_neighbors, m_cloud.pts.size());
172
173#ifdef _OPENMP
174 #pragma omp parallel
175 {
176 // Thread-local storage to avoid allocation overhead
177 std::vector<unsigned int> indices(k);
178 std::vector<double> sq_dists(k);
179
180 #pragma omp for schedule(guided, 128) nowait
181 for (size_t i = 0; i < query_points.size(); i++)
182 {
183 double const query_pt[2] = {query_points[i].x, query_points[i].y};
184
185 long unsigned int num_found = m_kdtree->knnSearch(query_pt, k,
186 indices.data(),
187 sq_dists.data());
188
189 if (num_found == 0)
190 {
191 results[i] = 0.0; // or m_dMissingValue
192 continue;
193 }
194
195 // Check if we're exactly on a data point
196 if (sq_dists[0] < EPSILON)
197 {
198 results[i] = m_values[indices[0]];
199 continue;
200 }
201
202 // IDW calculation - optimized for power=2.0
203 double sum_weights = 0.0;
204 double sum_weighted_values = 0.0;
205
206 if (bFPIsEqual(m_power, 2.0, TOLERANCE))
207 {
208 for (size_t j = 0; j < num_found; j++)
209 {
210 double weight = 1.0 / sq_dists[j];
211 sum_weights += weight;
212 sum_weighted_values += weight * m_values[indices[j]];
213 }
214 }
215 else
216 {
217 for (size_t j = 0; j < num_found; j++)
218 {
219 double dist = std::sqrt(sq_dists[j]);
220 double weight = 1.0 / std::pow(dist, m_power);
221 sum_weights += weight;
222 sum_weighted_values += weight * m_values[indices[j]];
223 }
224 }
225
226 results[i] = sum_weighted_values / sum_weights;
227 }
228 }
229#else
230 // Serial fallback - still optimized with reused buffers
231 std::vector<unsigned int> indices(k);
232 std::vector<double> sq_dists(k);
233
234 for (size_t i = 0; i < query_points.size(); i++)
235 {
236 results[i] = Interpolate(query_points[i].x, query_points[i].y);
237 }
238#endif
239}
240
241//===============================================================================================================================
256//===============================================================================================================================
257
258//===============================================================================================================================
266//===============================================================================================================================
267DualSpatialInterpolator::DualSpatialInterpolator(std::vector<Point2D> const& points,
268 std::vector<double> const& values_x,
269 std::vector<double> const& values_y,
270 int k_neighbors,
271 double power)
272 : m_values_x(values_x), m_values_y(values_y), m_kdtree(nullptr), m_k_neighbors(k_neighbors), m_power(power)
273{
274 if (points.size() != values_x.size() || points.size() != values_y.size())
275 throw std::invalid_argument("Points and values must have same size");
276
277 if (points.empty())
278 throw std::invalid_argument("Cannot create interpolator with empty data");
279
280 // Copy input points
281 m_cloud.pts = points;
282
283 // Build k-d tree (shared for both X and Y interpolation)
284 m_kdtree = new KDTree(2, m_cloud, {10 /* max leaf size */});
285 m_kdtree->buildIndex();
286}
287
292
294 double& result_x, double& result_y,
295 std::vector<unsigned int>& indices,
296 std::vector<double>& sq_dists) const
297{
298 double const query_pt[2] = {x, y};
299 size_t const k = std::min((size_t) m_k_neighbors, m_cloud.pts.size());
300
301 long unsigned int num_found = m_kdtree->knnSearch(query_pt, k,
302 indices.data(),
303 sq_dists.data());
304
305 if (num_found == 0)
306 {
307 result_x = result_y = 0.0;
308 return;
309 }
310
311 // Check if we're exactly on a data point
312 if (sq_dists[0] < EPSILON)
313 {
314 result_x = m_values_x[indices[0]];
315 result_y = m_values_y[indices[0]];
316 return;
317 }
318
319 // IDW for both X and Y simultaneously - optimized for power=2.0
320 double sum_weights = 0.0;
321 double sum_weighted_x = 0.0;
322 double sum_weighted_y = 0.0;
323
324 if (bFPIsEqual(m_power, 2.0, TOLERANCE))
325 {
326 for (size_t i = 0; i < num_found; i++)
327 {
328 double weight = 1.0 / sq_dists[i];
329 sum_weights += weight;
330 sum_weighted_x += weight * m_values_x[indices[i]];
331 sum_weighted_y += weight * m_values_y[indices[i]];
332 }
333 }
334 else
335 {
336 for (size_t i = 0; i < num_found; i++)
337 {
338 double dist = std::sqrt(sq_dists[i]);
339 double weight = 1.0 / std::pow(dist, m_power);
340 sum_weights += weight;
341 sum_weighted_x += weight * m_values_x[indices[i]];
342 sum_weighted_y += weight * m_values_y[indices[i]];
343 }
344 }
345
346 result_x = sum_weighted_x / sum_weights;
347 result_y = sum_weighted_y / sum_weights;
348}
349
350void DualSpatialInterpolator::Interpolate(std::vector<Point2D> const& query_points,
351 std::vector<double>& results_x,
352 std::vector<double>& results_y) const
353{
354 size_t const n = query_points.size();
355 results_x.resize(n);
356 results_y.resize(n);
357
358 // Early exit for empty query
359 if (n == 0) return;
360
361 size_t const k = std::min((size_t) m_k_neighbors, m_cloud.pts.size());
362
363#ifdef _OPENMP
364 // Use guided scheduling to reduce overhead
365 #pragma omp parallel
366 {
367 // Thread-local storage
368 std::vector<unsigned int> indices(k);
369 std::vector<double> sq_dists(k);
370
371 #pragma omp for schedule(guided, 128) nowait
372 for (size_t i = 0; i < query_points.size(); i++)
373 {
374 InterpolatePoint(query_points[i].x, query_points[i].y,
375 results_x[i], results_y[i],
376 indices, sq_dists);
377 }
378 }
379#else
380 // Serial fallback
381 std::vector<unsigned int> indices(k);
382 std::vector<double> sq_dists(k);
383
384 for (size_t i = 0; i < query_points.size(); i++)
385 {
386 InterpolatePoint(query_points[i].x, query_points[i].y,
387 results_x[i], results_y[i],
388 indices, sq_dists);
389 }
390#endif
391}
void Interpolate(std::vector< Point2D > const &query_points, std::vector< double > &results_x, std::vector< double > &results_y) const
void InterpolatePoint(double x, double y, double &result_x, double &result_y, std::vector< unsigned int > &indices, std::vector< double > &sq_dists) const
std::vector< double > m_values_y
DualSpatialInterpolator(std::vector< Point2D > const &points, std::vector< double > const &values_x, std::vector< double > const &values_y, int k_neighbors=12, double power=2.0)
static constexpr double EPSILON
std::vector< double > m_values_x
std::vector< double > m_values
static constexpr double EPSILON
SpatialInterpolator(std::vector< Point2D > const &points, std::vector< double > const &values, int k_neighbors=12, double power=2.0)
double Interpolate(double x, double y) const
This file contains global definitions for CoastalME.
double const TOLERANCE
Definition cme.h:725
bool bFPIsEqual(const T d1, const T d2, const T dEpsilon)
Definition cme.h:1213
nanoflann::KDTreeSingleIndexAdaptor< nanoflann::L2_Simple_Adaptor< double, PointCloud >, PointCloud, 2 > KDTree