3
3
import geopandas as gpd
4
4
import numpy as np
5
5
from beartype import beartype
6
- from beartype .typing import Union
6
+ from beartype .typing import Optional , Union
7
7
from rasterio import profiles , transform
8
8
9
9
from eis_toolkit .exceptions import EmptyDataFrameException , InvalidParameterValueException , NonMatchingCrsException
@@ -18,6 +18,7 @@ def _idw_interpolation(
18
18
raster_height : int ,
19
19
raster_transform : transform .Affine ,
20
20
power : Number ,
21
+ search_radius : Optional [Number ],
21
22
) -> np .ndarray :
22
23
23
24
points = np .array (geodataframe .geometry .apply (lambda geom : (geom .x , geom .y )).tolist ())
@@ -34,26 +35,47 @@ def _idw_interpolation(
34
35
y = np .linspace (grid_y_min , grid_y_max , raster_height )
35
36
y = y [::- 1 ].reshape (- 1 , 1 )
36
37
37
- interpolated_values = _idw_core (points [:, 0 ], points [:, 1 ], values , x , y , power )
38
+ interpolated_values = _idw_core (points [:, 0 ], points [:, 1 ], values , x , y , power , search_radius )
38
39
interpolated_values = interpolated_values .reshape (raster_height , raster_width )
39
40
40
41
return interpolated_values
41
42
42
43
43
44
# Distance calculations
44
- def _idw_core (x , y , z , xi , yi : np .ndarray , power : Number ) -> np .ndarray :
45
+ def _idw_core (
46
+ x : np .ndarray ,
47
+ y : np .ndarray ,
48
+ z : np .ndarray ,
49
+ xi : np .ndarray ,
50
+ yi : np .ndarray ,
51
+ power : Number ,
52
+ search_radius : Optional [Number ],
53
+ ) -> np .ndarray :
45
54
over = np .zeros ((len (yi ), len (xi )))
46
55
under = np .zeros ((len (yi ), len (xi )))
47
56
for n in range (len (x )):
48
57
dist = np .hypot (xi - x [n ], yi - y [n ])
49
- # Add a small epsilon to avoid division by zero
50
- dist = np .where (dist == 0 , 1e-12 , dist )
51
- dist = dist ** power
52
58
53
- over += z [n ] / dist
54
- under += 1.0 / dist
59
+ # Exclude points outside search radius
60
+ if search_radius is not None :
61
+ mask = dist <= search_radius
62
+ if not np .any (mask ):
63
+ continue
64
+
65
+ # Add a small epsilon to avoid division by zero
66
+ dist = np .where (dist [mask ] == 0 , 1e-12 , dist [mask ]) ** power
67
+
68
+ over [mask ] += z [n ] / dist
69
+ under [mask ] += 1.0 / dist
70
+
71
+ else :
72
+ # Add a small epsilon to avoid division by zero
73
+ dist = np .where (dist == 0 , 1e-12 , dist ) ** power
74
+
75
+ over += z [n ] / dist
76
+ under += 1.0 / dist
55
77
56
- interpolated_values = over / under
78
+ interpolated_values = np . divide ( over , under , out = np . full_like ( over , np . nan ), where = under != 0 )
57
79
return interpolated_values
58
80
59
81
@@ -63,6 +85,7 @@ def idw(
63
85
target_column : str ,
64
86
raster_profile : Union [profiles .Profile , dict ],
65
87
power : Number = 2 ,
88
+ search_radius : Optional [Number ] = None ,
66
89
) -> np .ndarray :
67
90
"""Calculate inverse distance weighted (IDW) interpolation.
68
91
@@ -73,6 +96,8 @@ def idw(
73
96
crs, transform, width and height.
74
97
power: The value for determining the rate at which the weights decrease. As power increases,
75
98
the weights for distant points decrease rapidly. Defaults to 2.
99
+ search_radius: The search radius within which to consider points for interpolation.
100
+ If None, all points are used.
76
101
77
102
Returns:
78
103
Numpy array containing the interpolated values.
@@ -97,7 +122,7 @@ def idw(
97
122
raster_transform = raster_profile .get ("transform" )
98
123
99
124
interpolated_values = _idw_interpolation (
100
- geodataframe , target_column , raster_width , raster_height , raster_transform , power
125
+ geodataframe , target_column , raster_width , raster_height , raster_transform , power , search_radius
101
126
)
102
127
103
128
return interpolated_values
0 commit comments