Source code for pyinterpolate.idw.idw

"""
Distance calculation functions.

Authors
-------
1. Szymon Moliński | @SimonMolinsky

TODO
- eval with IDW
"""
from numpy.typing import ArrayLike

import numpy as np

from pyinterpolate.core.data_models.points import VariogramPoints
from pyinterpolate.distance.point import point_distance
from pyinterpolate.transform.geo import geometry_and_values_array


[docs] def inverse_distance_weighting(unknown_location: ArrayLike, known_locations: ArrayLike = None, known_values: ArrayLike = None, known_geometries: ArrayLike = None, no_neighbors=-1, power=2.) -> float: """ Inverse Distance Weighting with a given set of points and the unknown location. Parameters ---------- unknown_location : Iterable Array or list with coordinates of the unknown point. Its length is N-1 (number of dimensions). The unknown location `shape` should be the same as the ``known_points`` parameter `shape`, if not, then new dimension is added once - vector of points ``[x, y]`` becomes ``[[x, y]]`` for 2-dimensional data. known_locations : numpy array, optional The known locations: ``[x, y, value]``. known_values : ArrayLike, optional Observation in the i-th geometry (from ``known_geometries``). Optional parameter, if not given then ``known_locations`` must be provided. known_geometries : ArrayLike, optional Array or similar structure with geometries. It must have the same length as ``known_values``. Optional parameter, if not given then ``known_locations`` must be provided. Point type geometry. no_neighbors : int, default = -1 If default value **(-1)** then all known points will be used to estimate value at the unknown location. Can be any number within the limits ``[2, len(known_points)]``, power : float, default = 2. Power value must be larger or equal to 0. It controls weight assigned to each known point. Larger power means stronger influence of the closest neighbors, but it decreases faster. Returns ------- result : float The estimated value. Raises ------ ValueError Power parameter set to be smaller than 0. ValueError Less than 2 neighbours or more than the number of ``known_points`` neighbours are given in the ``number_of_neighbours`` parameter. Examples -------- >>> unknown_pos = (10, 10) >>> locs = np.array([ ... [11, 1, 1], ... [23, 2, 2], ... [33, 3, 3], ... [14, 44, 4], ... [13, 10, 9], ... [12, 55, 35], ... [11, 9, 7] ... ]) >>> pred = inverse_distance_weighting( ... unknown_locations=unknown_pos, ... known_values=locs[:, -1], ... known_geometries=locs[:, :-1], ... no_neighbors=2 ... ) >>> print(pred) 7.286311587314138 """ # Check power parameter if power < 0: raise ValueError('Power cannot be smaller than 0') # Get known locations if known_locations is None: known_locations = geometry_and_values_array( geometry=known_geometries, values=known_values ) # Check known points parameter # Check if known locations are in the right format known_locations = VariogramPoints(known_locations).points # Check number of neighbours parameter nn_neighbors_ge_2 = no_neighbors >= 2 nn_neighbors_le_known_points = no_neighbors <= len(known_locations) n_closest_eq_nn = nn_neighbors_ge_2 and nn_neighbors_le_known_points number_of_closest = len(known_locations) if no_neighbors == -1: pass elif n_closest_eq_nn: number_of_closest = no_neighbors else: _idw_value_error_nn(length_known=len(known_locations), nn=no_neighbors) # Pre-process unknown location parameter if not isinstance(unknown_location, np.ndarray): unknown_location = np.array(unknown_location) if len(unknown_location.shape) != len(known_locations.shape): unknown_location = unknown_location[np.newaxis, ...] # Calculate distances distances = point_distance(unknown_location, known_locations[:, :-1]) distances: np.ndarray # Check if any distance is equal to 0 - then return this value if not np.all(distances[0]): zer_pos = np.where(distances == 0) result = known_locations[zer_pos[1], -1][0] return result # Get n closest neighbours... sdists = distances.argsort() sdists = sdists[0, :number_of_closest] dists = distances[0, sdists] values = known_locations[sdists].copy() values = values[:, -1] # Create weights weights = 1 / dists**power # Estimate value result = np.sum(weights * values) / np.sum(weights) return result
def _idw_value_error_nn(length_known: int, nn: int): """ Helper function to raise ValueError when the number of closest neighbours is out of bounds. Parameters ---------- length_known : int Number of known points. nn : int Number of neighbours defined by the user. Raises ------ ValueError Less than 2 neighbours or more than the number of ``known_points`` neighbours are given in the ``number_of_neighbours`` parameter. """ raise ValueError( f'Number of closest neighbors must be between 2 ' f'and the number of known points ' f'({length_known}) and {nn} neighbours were given instead.')