rastr 0.6.0__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of rastr might be problematic. Click here for more details.

rastr/__init__.py CHANGED
@@ -0,0 +1,7 @@
1
+ from rastr.meta import RasterMeta
2
+ from rastr.raster import Raster
3
+
4
+ __all__ = [
5
+ "Raster",
6
+ "RasterMeta",
7
+ ]
rastr/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.6.0'
32
- __version_tuple__ = version_tuple = (0, 6, 0)
31
+ __version__ = version = '0.7.0'
32
+ __version_tuple__ = version_tuple = (0, 7, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
rastr/create.py CHANGED
@@ -11,7 +11,9 @@ from affine import Affine
11
11
  from pyproj import CRS
12
12
  from shapely.geometry import Point
13
13
 
14
+ from rastr.gis.crs import get_affine_sign
14
15
  from rastr.gis.fishnet import create_point_grid, get_point_grid_shape
16
+ from rastr.gis.interpolate import interpn_kernel
15
17
  from rastr.meta import RasterMeta
16
18
  from rastr.raster import Raster
17
19
 
@@ -183,9 +185,10 @@ def rasterize_gdf(
183
185
  shape = get_point_grid_shape(bounds=expanded_bounds, cell_size=cell_size)
184
186
 
185
187
  # Create the affine transform for rasterization
188
+ xs, ys = get_affine_sign(raster_meta.crs)
186
189
  transform = Affine.translation(
187
190
  expanded_bounds[0], expanded_bounds[3]
188
- ) * Affine.scale(cell_size, -cell_size)
191
+ ) * Affine.scale(xs * cell_size, ys * cell_size)
189
192
 
190
193
  # Create rasters for each target column using rasterio.features.rasterize
191
194
  rasters = []
@@ -312,14 +315,31 @@ def raster_from_point_cloud(
312
315
  Raises:
313
316
  ValueError: If any (x, y) points are duplicated, or if they are all collinear.
314
317
  """
315
- from scipy.interpolate import LinearNDInterpolator
316
- from scipy.spatial import KDTree, QhullError
317
-
318
- x = np.asarray(x).ravel()
319
- y = np.asarray(y).ravel()
320
- z = np.asarray(z).ravel()
321
318
  crs = CRS.from_user_input(crs)
319
+ x, y, z = _validate_xyz(
320
+ np.asarray(x).ravel(), np.asarray(y).ravel(), np.asarray(z).ravel()
321
+ )
322
322
 
323
+ raster_meta, shape = RasterMeta.infer(x, y, cell_size=cell_size, crs=crs)
324
+ arr = interpn_kernel(
325
+ points=np.column_stack((x, y)),
326
+ values=z,
327
+ xi=np.column_stack(_get_grid(raster_meta, shape=shape)),
328
+ ).reshape(shape)
329
+
330
+ # We only support float rasters for now; we should preserve the input dtype if
331
+ # possible
332
+ if z.dtype in (np.float16, np.float32, np.float64):
333
+ arr = arr.astype(z.dtype)
334
+ else:
335
+ arr = arr.astype(np.float64)
336
+
337
+ return Raster(arr=arr, raster_meta=raster_meta)
338
+
339
+
340
+ def _validate_xyz(
341
+ x: np.ndarray, y: np.ndarray, z: np.ndarray
342
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
323
343
  # Validate input arrays
324
344
  if len(x) != len(y) or len(x) != len(z):
325
345
  msg = "Length of x, y, and z must be equal."
@@ -343,53 +363,18 @@ def raster_from_point_cloud(
343
363
  msg = "Duplicate (x, y) points found. Each (x, y) point must be unique."
344
364
  raise ValueError(msg)
345
365
 
346
- # Heuristic for cell size if not provided
347
- if cell_size is None:
348
- # Half the 5th percentile of nearest neighbor distances between the (x,y) points
349
- tree = KDTree(xy_points)
350
- distances, _ = tree.query(xy_points, k=2)
351
- distances: np.ndarray
352
- cell_size = float(np.percentile(distances[distances > 0], 5)) / 2
353
-
354
- # Compute bounds from data
355
- minx, miny, maxx, maxy = np.min(x), np.min(y), np.max(x), np.max(y)
356
-
357
- # Compute grid shape
358
- width = int(np.ceil((maxx - minx) / cell_size))
359
- height = int(np.ceil((maxy - miny) / cell_size))
360
- shape = (height, width)
366
+ return x, y, z
361
367
 
362
- # Compute transform: upper left corner is (minx, maxy)
363
- transform = Affine.translation(minx, maxy) * Affine.scale(cell_size, -cell_size)
364
368
 
365
- # Create grid coordinates for raster cells
369
+ def _get_grid(
370
+ raster_meta: RasterMeta, *, shape: tuple[int, int]
371
+ ) -> tuple[np.ndarray, np.ndarray]:
372
+ """Get coordinates for raster cell centres based on raster metadata and shape."""
366
373
  rows, cols = np.indices(shape)
367
374
  xs, ys = rasterio.transform.xy(
368
- transform=transform, rows=rows, cols=cols, offset="center"
375
+ transform=raster_meta.transform, rows=rows, cols=cols, offset="center"
369
376
  )
370
377
  grid_x = np.array(xs).ravel()
371
378
  grid_y = np.array(ys).ravel()
372
379
 
373
- # Perform interpolation
374
- try:
375
- interpolator = LinearNDInterpolator(
376
- points=xy_points, values=z, fill_value=np.nan
377
- )
378
- except QhullError as err:
379
- msg = (
380
- "Failed to interpolate. This may be due to insufficient or "
381
- "degenerate input points. Ensure that the (x, y) points are not all "
382
- "collinear (i.e. that the convex hull is non-degenerate)."
383
- )
384
- raise ValueError(msg) from err
385
-
386
- grid_values = np.array(interpolator(np.column_stack((grid_x, grid_y))))
387
-
388
- arr = grid_values.reshape(shape).astype(np.float32)
389
-
390
- raster_meta = RasterMeta(
391
- cell_size=cell_size,
392
- crs=crs,
393
- transform=transform,
394
- )
395
- return Raster(arr=arr, raster_meta=raster_meta)
380
+ return grid_x, grid_y
rastr/gis/crs.py ADDED
@@ -0,0 +1,67 @@
1
+ from __future__ import annotations
2
+
3
+ import warnings
4
+ from typing import Literal
5
+
6
+ from pyproj import CRS
7
+
8
+
9
+ def get_affine_sign(crs: CRS | str) -> tuple[Literal[+1, -1], Literal[+1, -1]]:
10
+ """Return (x_sign, y_sign) for an Affine scale, given a CRS.
11
+
12
+ Some coordinate systems may use unconventional axis directions, in which case
13
+ the correct direction may not be possible to infer correctly. In these cases,
14
+ the assumption is that x increases to the right, and y increases upwards.
15
+ """
16
+ crs = CRS.from_user_input(crs)
17
+
18
+ # Try to detect horizontal axis directions from CRS metadata
19
+ dir_x, dir_y, *_ = [(a.direction or "").lower() for a in crs.axis_info]
20
+
21
+ try:
22
+ if _is_conventional_direction(dir_x):
23
+ x_sign = +1
24
+ else:
25
+ x_sign = -1
26
+ except NotImplementedError:
27
+ msg = (
28
+ f"Could not determine x-axis direction from CRS axis info '{dir_x}'. "
29
+ "Falling back to +1 (increasing to the right)."
30
+ )
31
+ warnings.warn(msg, stacklevel=2)
32
+ x_sign = +1
33
+
34
+ try:
35
+ if _is_conventional_direction(dir_y):
36
+ y_sign = -1
37
+ else:
38
+ y_sign = +1
39
+ except NotImplementedError:
40
+ msg = (
41
+ f"Could not determine y-axis direction from CRS axis info '{dir_y}'. "
42
+ "Falling back to -1 (increasing upwards)."
43
+ )
44
+ warnings.warn(msg, stacklevel=2)
45
+ y_sign = -1
46
+
47
+ return x_sign, y_sign
48
+
49
+
50
+ def _is_conventional_direction(direction: str) -> bool:
51
+ """Return True if the axis direction indicates positive increase."""
52
+ if (
53
+ "north" in direction
54
+ or "up" in direction
55
+ or "east" in direction
56
+ or "right" in direction
57
+ ):
58
+ return True
59
+ elif (
60
+ "south" in direction
61
+ or "down" in direction
62
+ or "west" in direction
63
+ or "left" in direction
64
+ ):
65
+ return False
66
+ else:
67
+ raise NotImplementedError
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import numpy as np
6
+
7
+ if TYPE_CHECKING:
8
+ from collections.abc import Callable
9
+
10
+
11
+ def interpn_kernel(
12
+ points: np.ndarray,
13
+ values: np.ndarray,
14
+ *,
15
+ xi: np.ndarray,
16
+ kernel: Callable[[np.ndarray], np.ndarray] | None = None,
17
+ ) -> np.ndarray:
18
+ """Interpolate scattered data to new points, with optional kernel transformation.
19
+
20
+ For example, you could provide a kernel to transform cartesian coordinate points
21
+ to polar coordinates before interpolation, giving interpolation which follows the
22
+ circular pattern of the data.
23
+
24
+ Args:
25
+ points: Array of shape (n_points, n_dimensions) representing the input points.
26
+ values: Array of shape (n_points,) representing the values at each input point.
27
+ xi: Array of shape (m_points, n_dimensions) representing the points to
28
+ interpolate to.
29
+ kernel: Optional function to transform points (and xi) before interpolation.
30
+ """
31
+ from scipy.interpolate import LinearNDInterpolator
32
+ from scipy.spatial import QhullError
33
+
34
+ if kernel is not None:
35
+ xi = kernel(xi)
36
+ points = kernel(points)
37
+ try:
38
+ interpolator = LinearNDInterpolator(
39
+ points=points, values=values, fill_value=np.nan
40
+ )
41
+ except QhullError as err:
42
+ msg = (
43
+ "Failed to interpolate. This may be due to insufficient or "
44
+ "degenerate input points. Ensure that the (x, y) points are not all "
45
+ "collinear (i.e. that the convex hull is non-degenerate)."
46
+ )
47
+ raise ValueError(msg) from err
48
+
49
+ grid_values = np.array(interpolator(xi))
50
+ return grid_values
rastr/gis/smooth.py CHANGED
@@ -8,6 +8,7 @@ from __future__ import annotations
8
8
  from typing import TYPE_CHECKING, TypeVar
9
9
 
10
10
  import numpy as np
11
+ from numpy.lib.stride_tricks import sliding_window_view
11
12
  from shapely.geometry import LineString, Polygon
12
13
  from typing_extensions import assert_never
13
14
 
@@ -55,7 +56,8 @@ def _catmull_rom(
55
56
  subdivs: int = 8,
56
57
  ) -> list[tuple[float, float]]:
57
58
  arr = np.asarray(coords, dtype=float)
58
- if arr.shape[0] < 2:
59
+ n = arr.shape[0]
60
+ if n < 2:
59
61
  return arr.tolist()
60
62
 
61
63
  is_closed = np.allclose(arr[0], arr[-1])
@@ -70,63 +72,80 @@ def _catmull_rom(
70
72
  ]
71
73
  )
72
74
 
73
- new_ls = [tuple(arr[1])]
74
- for k in range(len(arr) - 3):
75
- slice4 = arr[k : k + 4]
76
- tangents = [0.0]
77
- for j in range(3):
78
- dist = float(np.linalg.norm(slice4[j + 1] - slice4[j]))
79
- tangents.append(float(tangents[-1] + dist**alpha))
80
-
81
- # Resample: subdivs-1 samples strictly between t1 and t2
82
- seg_len = (tangents[2] - tangents[1]) / float(subdivs)
83
- if subdivs > 1:
84
- ts = np.linspace(tangents[1] + seg_len, tangents[2] - seg_len, subdivs - 1)
85
- else:
86
- ts = np.array([])
87
-
88
- interpolants = _recursive_eval(slice4, tangents, ts)
89
- new_ls.extend(interpolants)
90
- new_ls.append(tuple(slice4[2]))
91
- return new_ls
92
-
93
-
94
- def _recursive_eval(
95
- slice4: NDArray, tangents: list[float], ts: NDArray
96
- ) -> list[tuple[float, float]]:
75
+ # Shape of (segments, 4, D)
76
+ segments = sliding_window_view(arr, (4, arr.shape[1]))[:, 0, :]
77
+
78
+ # Distances and tangent values
79
+ diffs = np.diff(segments, axis=1)
80
+ dists = np.linalg.norm(diffs, axis=2)
81
+ tangents = np.concatenate(
82
+ [np.zeros((len(dists), 1)), np.cumsum(dists**alpha, axis=1)], axis=1
83
+ )
84
+
85
+ # Build ts per segment
86
+ if subdivs > 1:
87
+ seg_lens = (tangents[:, 2] - tangents[:, 1]) / subdivs
88
+ u = np.linspace(1, subdivs - 1, subdivs - 1)
89
+ ts = tangents[:, [1]] + seg_lens[:, None] * u # (N-3, subdivs-1)
90
+ else:
91
+ ts = np.empty((len(segments), 0))
92
+
93
+ # Vectorize over segments
94
+ out_segments = []
95
+ for seg, tang, tvals in zip(segments, tangents, ts, strict=True):
96
+ if tvals.size:
97
+ out_segments.append(
98
+ _recursive_eval(seg, np.asarray(tang), np.asarray(tvals))
99
+ )
100
+ if out_segments:
101
+ all_midpoints = np.vstack(out_segments)
102
+ else:
103
+ all_midpoints = np.empty((0, arr.shape[1]))
104
+
105
+ # Gather final output in order
106
+ result = [tuple(arr[1])]
107
+ idx = 0
108
+ for k in range(len(segments)):
109
+ block = all_midpoints[idx : idx + max(subdivs - 1, 0)]
110
+ result.extend(map(tuple, block))
111
+ result.append(tuple(segments[k, 2]))
112
+ idx += max(subdivs - 1, 0)
113
+
114
+ return result
115
+
116
+
117
+ def _recursive_eval(slice4: NDArray, tangents: NDArray, ts: NDArray) -> NDArray:
97
118
  """De Boor/De Casteljau-style recursive linear interpolation over 4 control points.
98
119
 
99
120
  Parameterized by the non-uniform 'tangents' values.
100
121
  """
101
- # N.B. comments are LLM-generated
102
-
103
- out = []
104
- for tp in ts:
105
- # Start with the 4 control points for this segment
106
- points = slice4.copy()
107
- # Perform 3 levels of linear interpolation (De Casteljau's algorithm)
108
- for r in range(1, 4):
109
- idx = max(r - 2, 0)
110
- new_points = []
111
- # Interpolate between points at this level
112
- for i in range(4 - r):
113
- # Compute denominator for parameterization
114
- denom = tangents[i + r - idx] - tangents[i + idx]
115
- if denom == 0:
116
- # If degenerate (coincident tangents), use midpoint
117
- left_w = right_w = 0.5
118
- else:
119
- # Otherwise, compute weights for linear interpolation
120
- left_w = (tangents[i + r - idx] - tp) / denom
121
- right_w = (tp - tangents[i + idx]) / denom
122
- # Weighted average of the two points
123
- pt = left_w * points[i] + right_w * points[i + 1]
124
- new_points.append(pt)
125
- # Move to the next level with the new set of points
126
- points = np.array(new_points)
127
- # The final point is the interpolated value for this parameter tp
128
- out.append(tuple(points[0]))
129
- return out
122
+ slice4 = np.asarray(slice4, dtype=float)
123
+ tangents = np.asarray(tangents, dtype=float)
124
+ ts = np.asarray(ts, dtype=float)
125
+ bigm = ts.shape[0]
126
+ bigd = slice4.shape[1]
127
+
128
+ # Initialize points for all ts, shape (M, 4, D)
129
+ points = np.broadcast_to(slice4, (bigm, 4, bigd)).copy()
130
+
131
+ # Recursive interpolation, but vectorized across all ts
132
+ for r in range(1, 4):
133
+ idx = max(r - 2, 0)
134
+ denom = tangents[r - idx : 4 - idx] - tangents[idx : 4 - r + idx]
135
+ denom = np.where(denom == 0, np.finfo(float).eps, denom) # avoid div 0
136
+
137
+ # Compute weights for all parameter values at once
138
+ left_w = (tangents[r - idx : 4 - idx][None, :] - ts[:, None]) / denom
139
+ right_w = 1 - left_w
140
+
141
+ # Weighted sums between consecutive points
142
+ points = (
143
+ left_w[..., None] * points[:, 0 : 4 - r, :]
144
+ + right_w[..., None] * points[:, 1 : 5 - r, :]
145
+ )
146
+
147
+ # Result is first (and only) point at this level
148
+ return points[:, 0, :]
130
149
 
131
150
 
132
151
  def _get_coords(
rastr/meta.py CHANGED
@@ -7,6 +7,8 @@ from affine import Affine
7
7
  from pydantic import BaseModel, InstanceOf
8
8
  from pyproj import CRS
9
9
 
10
+ from rastr.gis.crs import get_affine_sign
11
+
10
12
  if TYPE_CHECKING:
11
13
  from numpy.typing import NDArray
12
14
  from typing_extensions import Self
@@ -85,3 +87,99 @@ class RasterMeta(BaseModel, extra="forbid"):
85
87
  y_idx = np.arange(n_rows) + 0.5
86
88
  _, y_coords = self.transform * (x_idx, y_idx) # type: ignore[reportAssignmentType] overloaded tuple size in affine
87
89
  return y_coords
90
+
91
+ @classmethod
92
+ def infer(
93
+ cls,
94
+ x: np.ndarray,
95
+ y: np.ndarray,
96
+ *,
97
+ cell_size: float | None = None,
98
+ crs: CRS,
99
+ ) -> tuple[Self, tuple[int, int]]:
100
+ """Automatically get recommended raster metadata (and shape) using data bounds.
101
+
102
+ The cell size can be provided, or a heuristic will be used based on the spacing
103
+ of the (x, y) points.
104
+ """
105
+ # Heuristic for cell size if not provided
106
+ if cell_size is None:
107
+ cell_size = infer_cell_size(x, y)
108
+
109
+ shape = infer_shape(x, y, cell_size=cell_size)
110
+ transform = infer_transform(x, y, cell_size=cell_size, crs=crs)
111
+
112
+ raster_meta = cls(
113
+ cell_size=cell_size,
114
+ crs=crs,
115
+ transform=transform,
116
+ )
117
+ return raster_meta, shape
118
+
119
+
120
+ def infer_transform(
121
+ x: np.ndarray,
122
+ y: np.ndarray,
123
+ *,
124
+ cell_size: float | None = None,
125
+ crs: CRS,
126
+ ) -> Affine:
127
+ """Infer a suitable raster transform based on the bounds of (x, y) data points."""
128
+ if cell_size is None:
129
+ cell_size = infer_cell_size(x, y)
130
+
131
+ (xs, ys) = get_affine_sign(crs)
132
+ return Affine.translation(*infer_origin(x, y)) * Affine.scale(
133
+ xs * cell_size, ys * cell_size
134
+ )
135
+
136
+
137
+ def infer_origin(x: np.ndarray, y: np.ndarray) -> tuple[float, float]:
138
+ """Infer a suitable raster origin based on the bounds of (x, y) data points."""
139
+ # Compute bounds from data
140
+ minx, _miny, _maxx, maxy = np.min(x), np.min(y), np.max(x), np.max(y)
141
+
142
+ origin = (minx, maxy)
143
+ return origin
144
+
145
+
146
+ def infer_shape(
147
+ x: np.ndarray, y: np.ndarray, *, cell_size: float | None = None
148
+ ) -> tuple[int, int]:
149
+ """Infer a suitable raster shape based on the bounds of (x, y) data points."""
150
+ if cell_size is None:
151
+ cell_size = infer_cell_size(x, y)
152
+
153
+ # Compute bounds from data
154
+ minx, miny, maxx, maxy = np.min(x), np.min(y), np.max(x), np.max(y)
155
+
156
+ # Compute grid shape
157
+ width = int(np.ceil((maxx - minx) / cell_size))
158
+ height = int(np.ceil((maxy - miny) / cell_size))
159
+ shape = (height, width)
160
+
161
+ return shape
162
+
163
+
164
+ def infer_cell_size(x: np.ndarray, y: np.ndarray) -> float:
165
+ """Infer a suitable cell size based on the spacing of (x, y) data points.
166
+
167
+ When points are distributed regularly, this corresponds to roughly half the distance
168
+ between neighboring points.
169
+
170
+ When distributed irregularly, the size is more influenced by the densest clusters of
171
+ points, i.e. the cell size will be small enough to capture the detail in these
172
+ clusters.
173
+
174
+ This is based on a heuristic which has been found to work well in practice.
175
+ """
176
+ from scipy.spatial import KDTree
177
+
178
+ # Half the 5th percentile of nearest neighbor distances between the (x,y) points
179
+ xy_points = np.column_stack((x, y))
180
+ tree = KDTree(xy_points)
181
+ distances, _ = tree.query(xy_points, k=2)
182
+ distances: np.ndarray
183
+ cell_size = float(np.percentile(distances[distances > 0], 5)) / 2
184
+
185
+ return cell_size
rastr/raster.py CHANGED
@@ -726,7 +726,7 @@ class Raster(BaseModel):
726
726
 
727
727
  def __str__(self) -> str:
728
728
  cls = self.__class__
729
- mean = np.nanmean(self.arr)
729
+ mean = self.mean()
730
730
  return f"{cls.__name__}(shape={self.arr.shape}, {mean=})"
731
731
 
732
732
  def __repr__(self) -> str:
@@ -874,6 +874,68 @@ class Raster(BaseModel):
874
874
  new_raster.arr = filled_arr
875
875
  return new_raster
876
876
 
877
+ def replace(
878
+ self, to_replace: float | dict[float, float], value: float | None = None
879
+ ) -> Self:
880
+ """Replace values in the raster with other values.
881
+
882
+ Creates a new raster with the specified values replaced. This is useful for
883
+ operations like replacing zeros with NaNs, or vice versa.
884
+
885
+ The method supports two interfaces:
886
+ 1. Single replacement: `raster.replace(to_replace=0, value=np.nan)`
887
+ 2. Multiple replacements using a dictionary:
888
+ `raster.replace({0: np.nan, -999: np.nan})`
889
+
890
+ Args:
891
+ to_replace: Value to be replaced, or a dictionary mapping values to
892
+ their replacements.
893
+ value: Replacement value. Required when to_replace is a float, must be
894
+ None when to_replace is a dict.
895
+
896
+ Examples:
897
+ >>> # Replace a single value
898
+ >>> raster.replace(to_replace=0, value=np.nan)
899
+ >>> # Replace multiple values
900
+ >>> raster.replace({0: np.nan, -999: np.nan})
901
+ """
902
+ # Determine the replacement map
903
+ if isinstance(to_replace, dict):
904
+ if value is not None:
905
+ msg = "value must be None when to_replace is a dict"
906
+ raise ValueError(msg)
907
+ map_ = to_replace
908
+ else:
909
+ if value is None:
910
+ msg = "value must be specified when to_replace is a float"
911
+ raise ValueError(msg)
912
+ map_ = {to_replace: value}
913
+
914
+ # Start with a copy of the array
915
+ replaced_arr = self.arr.copy()
916
+
917
+ # Check if we need to convert to float (if assigning NaN to non-float array)
918
+ needs_float = any(
919
+ np.isnan(new_val) for new_val in map_.values()
920
+ ) and not np.issubdtype(replaced_arr.dtype, np.floating)
921
+ if needs_float:
922
+ replaced_arr = replaced_arr.astype(float)
923
+
924
+ # Apply each replacement based on the original array values
925
+ # to prevent chained replacements
926
+ for old_val, new_val in map_.items():
927
+ # Handle NaN specially since NaN != NaN
928
+ if np.isnan(old_val):
929
+ mask = np.isnan(self.arr)
930
+ else:
931
+ mask = self.arr == old_val
932
+
933
+ replaced_arr[mask] = new_val
934
+
935
+ new_raster = self.model_copy()
936
+ new_raster.arr = replaced_arr
937
+ return new_raster
938
+
877
939
  def copy(self) -> Self: # type: ignore[override]
878
940
  """Create a copy of the raster.
879
941
 
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rastr
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Geospatial Raster datatype library for Python.
5
5
  Project-URL: Source Code, https://github.com/tonkintaylor/rastr
6
6
  Project-URL: Bug Tracker, https://github.com/tonkintaylor/rastr/issues
7
7
  Project-URL: Releases, https://github.com/tonkintaylor/rastr/releases
8
- Project-URL: Source Archive, https://github.com/tonkintaylor/rastr/archive/a1b755ea9cde7f81b2963dcb12917090019f2b47.zip
8
+ Project-URL: Source Archive, https://github.com/tonkintaylor/rastr/archive/2b485cc676121c82f468dca7733e444c3033abbe.zip
9
9
  Author-email: Tonkin & Taylor Limited <Sub-DisciplineData+AnalyticsStaff@tonkintaylor.co.nz>, Nathan McDougall <nmcdougall@tonkintaylor.co.nz>, Ben Karl <bkarl@tonkintaylor.co.nz>
10
10
  License-Expression: MIT
11
11
  License-File: LICENSE
@@ -0,0 +1,17 @@
1
+ rastr/__init__.py,sha256=z26KywZdRKwO-N5Qc34SuuGGwH8Y812csKORc3S4SYU,113
2
+ rastr/_version.py,sha256=uLbRjFSUZAgfl7V7O8zKV5Db36k7tz87ZIVq3l2SWs0,704
3
+ rastr/create.py,sha256=jT2X7mgJoMapnRz-M11dJoKFidaf0k_qleR5zxnRAnw,13195
4
+ rastr/io.py,sha256=RPhypnSNhLaWYdGRzctM9aTXbw9_TuMjvhMvDyUZavk,3640
5
+ rastr/meta.py,sha256=lUZVodFzhnzLI1sr7SgiM9XN9D-n7nXvs0voWTJYlMg,5980
6
+ rastr/raster.py,sha256=9yib1g0HOzPepaLCu_ApEbfUynb0IjQzCD__FEOco1c,54219
7
+ rastr/arr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ rastr/arr/fill.py,sha256=ZSd9mcfzYafkAes2G2q8hJGlxhW47kI2brPf--jds3o,1029
9
+ rastr/gis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ rastr/gis/crs.py,sha256=9K57Ys6P32v0uzap-l7L_HbjolJMX-ETuRB_rN30Qz0,1953
11
+ rastr/gis/fishnet.py,sha256=nAiJ_DuSQP326pLM9JmI8A4QwWWgVu7Mae1K1dWjDc4,3108
12
+ rastr/gis/interpolate.py,sha256=DzjtD5ynnwKP7TrwPiK3P0dOy5ZRzME9bV8-7tn5TFk,1697
13
+ rastr/gis/smooth.py,sha256=FeEiO9RNyX9bP_yM2bvgQPLGyF0lHjll20ur52ehp1c,5182
14
+ rastr-0.7.0.dist-info/METADATA,sha256=yc3gYgbIgMLAoUeubfE0rXiJtN1b9YPbz4Yjewrr0QE,5724
15
+ rastr-0.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ rastr-0.7.0.dist-info/licenses/LICENSE,sha256=7qUsx93G2ATTRLZiSYuQofwAX_uWvrqnAiMK8PzxvNc,1080
17
+ rastr-0.7.0.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- rastr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- rastr/_version.py,sha256=MAYWefOLb6kbIRub18WSzK6ggSjz1LNLy9aDRlX9Ea4,704
3
- rastr/create.py,sha256=8SpaQPx2bprqwbEqSntHBMb6-v7xSjOB6ZULb9w2hho,13801
4
- rastr/io.py,sha256=RPhypnSNhLaWYdGRzctM9aTXbw9_TuMjvhMvDyUZavk,3640
5
- rastr/meta.py,sha256=5iDvGkYe8iMMkPV6gSL04jNcLRhuRNFqe9AppUpp55E,2928
6
- rastr/raster.py,sha256=Qf56i5NjBPNI8hNgjsfDDzgVhOBNTmL7e0ZGhFrwDs0,51835
7
- rastr/arr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- rastr/arr/fill.py,sha256=ZSd9mcfzYafkAes2G2q8hJGlxhW47kI2brPf--jds3o,1029
9
- rastr/gis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- rastr/gis/fishnet.py,sha256=nAiJ_DuSQP326pLM9JmI8A4QwWWgVu7Mae1K1dWjDc4,3108
11
- rastr/gis/smooth.py,sha256=6-mQlJVyU2gnSbrROWeNaSVhwxvA-Lf1vhM93fNKu4s,4825
12
- rastr-0.6.0.dist-info/METADATA,sha256=_Pm8xg0FbV-xD73De_tbhJn17eu0bdy34uKkupABa3g,5724
13
- rastr-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
- rastr-0.6.0.dist-info/licenses/LICENSE,sha256=7qUsx93G2ATTRLZiSYuQofwAX_uWvrqnAiMK8PzxvNc,1080
15
- rastr-0.6.0.dist-info/RECORD,,
File without changes