yirgacheffe 1.7.9__tar.gz → 1.8.1__tar.gz

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.
Files changed (59) hide show
  1. {yirgacheffe-1.7.9/yirgacheffe.egg-info → yirgacheffe-1.8.1}/PKG-INFO +8 -4
  2. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/README.md +1 -1
  3. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/pyproject.toml +10 -3
  4. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_area.py +3 -0
  5. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_auto_windowing.py +2 -2
  6. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_base.py +6 -7
  7. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_intersection.py +30 -0
  8. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_rounding.py +1 -2
  9. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_union.py +11 -0
  10. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/__init__.py +2 -0
  11. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/_backends/enumeration.py +20 -0
  12. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/_backends/mlx.py +4 -2
  13. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/_backends/numpy.py +4 -2
  14. yirgacheffe-1.8.1/yirgacheffe/_core.py +163 -0
  15. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/_operators.py +25 -28
  16. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/area.py +5 -3
  17. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/base.py +20 -27
  18. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/constant.py +4 -2
  19. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/group.py +11 -11
  20. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/h3layer.py +4 -2
  21. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/rasters.py +18 -18
  22. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/rescaled.py +3 -3
  23. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/vectors.py +65 -66
  24. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/rounding.py +4 -3
  25. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/window.py +52 -97
  26. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1/yirgacheffe.egg-info}/PKG-INFO +8 -4
  27. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe.egg-info/requires.txt +4 -0
  28. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe.egg-info/top_level.txt +1 -0
  29. yirgacheffe-1.7.9/yirgacheffe/_core.py +0 -183
  30. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/LICENSE +0 -0
  31. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/MANIFEST.in +0 -0
  32. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/setup.cfg +0 -0
  33. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_constants.py +0 -0
  34. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_datatypes.py +0 -0
  35. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_group.py +0 -0
  36. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_h3layer.py +0 -0
  37. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_multiband.py +0 -0
  38. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_nodata.py +0 -0
  39. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_openers.py +0 -0
  40. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_operators.py +0 -0
  41. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_optimisation.py +0 -0
  42. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_parallel_operators.py +0 -0
  43. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_pickle.py +0 -0
  44. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_projection.py +0 -0
  45. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_raster.py +0 -0
  46. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_rescaling.py +0 -0
  47. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_save_with_window.py +0 -0
  48. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_sum_with_window.py +0 -0
  49. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_uniform_area_layer.py +0 -0
  50. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_vectors.py +0 -0
  51. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_window.py +0 -0
  52. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/_backends/__init__.py +0 -0
  53. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/constants.py +0 -0
  54. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/__init__.py +0 -0
  55. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/operators.py +0 -0
  56. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/py.typed +0 -0
  57. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe.egg-info/SOURCES.txt +0 -0
  58. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe.egg-info/dependency_links.txt +0 -0
  59. {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe.egg-info/entry_points.txt +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yirgacheffe
3
- Version: 1.7.9
3
+ Version: 1.8.1
4
4
  Summary: Abstraction of gdal datasets for doing basic math operations
5
5
  Author-email: Michael Dales <mwd24@cam.ac.uk>
6
6
  License-Expression: ISC
7
- Project-URL: Homepage, https://github.com/quantifyearth/yirgacheffe
7
+ Project-URL: Homepage, https://yirgacheffe.org/
8
8
  Project-URL: Repository, https://github.com/quantifyearth/yirgacheffe.git
9
9
  Project-URL: Issues, https://github.com/quantifyearth/yirgacheffe/issues
10
- Project-URL: Changelog, https://github.com/quantifyearth/yirgacheffe/blob/main/CHANGES.md
10
+ Project-URL: Changelog, https://yirgacheffe.org/latest/changelog/
11
11
  Keywords: gdal,gis,geospatial,declarative
12
12
  Classifier: Development Status :: 5 - Production/Stable
13
13
  Classifier: Intended Audience :: Science/Research
@@ -36,9 +36,13 @@ Requires-Dist: pytest; extra == "dev"
36
36
  Requires-Dist: pytest-cov; extra == "dev"
37
37
  Requires-Dist: build; extra == "dev"
38
38
  Requires-Dist: twine; extra == "dev"
39
+ Requires-Dist: mkdocs-material; extra == "dev"
40
+ Requires-Dist: mkdocstrings-python; extra == "dev"
41
+ Requires-Dist: mike; extra == "dev"
42
+ Requires-Dist: mkdocs-gen-files; extra == "dev"
39
43
  Dynamic: license-file
40
44
 
41
- # Yirgacheffe: a gdal wrapper that does the tricky bits
45
+ # Yirgacheffe: a declarative geospatial library for Python to make data-science with maps easier
42
46
 
43
47
  ## Overview
44
48
 
@@ -1,4 +1,4 @@
1
- # Yirgacheffe: a gdal wrapper that does the tricky bits
1
+ # Yirgacheffe: a declarative geospatial library for Python to make data-science with maps easier
2
2
 
3
3
  ## Overview
4
4
 
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "yirgacheffe"
9
- version = "1.7.9"
9
+ version = "1.8.1"
10
10
  description = "Abstraction of gdal datasets for doing basic math operations"
11
11
  readme = "README.md"
12
12
  authors = [{ name = "Michael Dales", email = "mwd24@cam.ac.uk" }]
@@ -44,13 +44,17 @@ dev = [
44
44
  "pytest-cov",
45
45
  "build",
46
46
  "twine",
47
+ "mkdocs-material",
48
+ "mkdocstrings-python",
49
+ "mike",
50
+ "mkdocs-gen-files",
47
51
  ]
48
52
 
49
53
  [project.urls]
50
- Homepage = "https://github.com/quantifyearth/yirgacheffe"
54
+ Homepage = "https://yirgacheffe.org/"
51
55
  Repository = "https://github.com/quantifyearth/yirgacheffe.git"
52
56
  Issues = "https://github.com/quantifyearth/yirgacheffe/issues"
53
- Changelog = "https://github.com/quantifyearth/yirgacheffe/blob/main/CHANGES.md"
57
+ Changelog = "https://yirgacheffe.org/latest/changelog/"
54
58
 
55
59
  [project.scripts]
56
60
  realpython = "reader.__main__:main"
@@ -69,3 +73,6 @@ ignore_missing_imports = true
69
73
  [[tool.mypy.overrides]]
70
74
  module = "deprecation.*"
71
75
  ignore_missing_imports = true
76
+
77
+ [tool.setuptools.packages.find]
78
+ exclude = ["site*", "docs*", "tests*"]
@@ -29,3 +29,6 @@ def test_global_area() -> None:
29
29
  other_area = Area(-10.0, 10.0, 10.0, -10.0)
30
30
  assert area.overlaps(other_area)
31
31
  assert other_area.overlaps(area)
32
+
33
+ def test_wrong_types_on_eq() -> None:
34
+ assert Area(-10, 10, 10, -10) != 42
@@ -4,7 +4,7 @@ import tempfile
4
4
  import numpy as np
5
5
  import pytest
6
6
 
7
- import yirgacheffe
7
+ import yirgacheffe as yg
8
8
  from tests.helpers import gdal_dataset_with_data, make_vectors_with_mutlile_ids
9
9
  from yirgacheffe.layers import ConstantLayer, RasterLayer, VectorLayer
10
10
  from yirgacheffe.window import Area
@@ -315,7 +315,7 @@ def test_vector_layers_multiply() -> None:
315
315
  expected = np.array([[2, 0], [0, 8]])
316
316
  assert (expected == actual).all()
317
317
 
318
- @pytest.mark.skipif(yirgacheffe._backends.BACKEND != "NUMPY", reason="Only applies for numpy")
318
+ @pytest.mark.skipif(yg._backends.BACKEND != "NUMPY", reason="Only applies for numpy")
319
319
  def test_parallel_save_windows() -> None:
320
320
  data1 = np.array([[1, 2], [3, 4]])
321
321
  data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120], [130, 140, 150, 160]])
@@ -1,5 +1,4 @@
1
1
  import math
2
- from typing import Tuple
3
2
 
4
3
  import pytest
5
4
 
@@ -58,7 +57,7 @@ def test_pixel_from_latlng_unsupported_projection() -> None:
58
57
  ),
59
58
  ]
60
59
  )
61
- def test_latlng_for_pixel(area: Area, pixel: Tuple[int,int], expected: Tuple[float,float]) -> None:
60
+ def test_latlng_for_pixel(area: Area, pixel: tuple[int, int], expected: tuple[float, float]) -> None:
62
61
  layer = YirgacheffeLayer(
63
62
  area,
64
63
  MapProjection(WGS_84_PROJECTION, 0.2, -0.2),
@@ -87,7 +86,7 @@ def test_latlng_for_pixel(area: Area, pixel: Tuple[int,int], expected: Tuple[flo
87
86
  ),
88
87
  ]
89
88
  )
90
- def test_pixel_for_latlng(area: Area, coord: Tuple[float,float], expected: Tuple[int,int]) -> None:
89
+ def test_pixel_for_latlng(area: Area, coord: tuple[float, float], expected: tuple[int, int]) -> None:
91
90
  layer = YirgacheffeLayer(
92
91
  area,
93
92
  MapProjection(WGS_84_PROJECTION, 0.2, -0.2),
@@ -140,8 +139,8 @@ def test_pixel_for_latlng(area: Area, coord: Tuple[float,float], expected: Tuple
140
139
  def test_latlng_for_pixel_with_intersection(
141
140
  area: Area,
142
141
  window: Area,
143
- pixel: Tuple[int,int],
144
- expected: Tuple[float,float]
142
+ pixel: tuple[int, int],
143
+ expected: tuple[float, float]
145
144
  ) -> None:
146
145
  layer = YirgacheffeLayer(
147
146
  area,
@@ -178,8 +177,8 @@ def test_latlng_for_pixel_with_intersection(
178
177
  def test_pixel_for_latlng_with_intersection(
179
178
  area: Area,
180
179
  window: Area,
181
- coord: Tuple[float,float],
182
- expected: Tuple[int,int]
180
+ coord: tuple[float, float],
181
+ expected: tuple[int, int]
183
182
  ) -> None:
184
183
  layer = YirgacheffeLayer(
185
184
  area,
@@ -59,6 +59,9 @@ def test_find_intersection_with_constant() -> None:
59
59
  intersection = RasterLayer.find_intersection(layers)
60
60
  assert intersection == layers[0].area
61
61
 
62
+ for layer in layers:
63
+ layer.set_window_for_intersection(intersection)
64
+
62
65
  def test_find_intersection_with_vector_unbound() -> None:
63
66
  with tempfile.TemporaryDirectory() as tempdir:
64
67
  path = Path(tempdir) / "test.gpkg"
@@ -74,6 +77,10 @@ def test_find_intersection_with_vector_unbound() -> None:
74
77
  intersection = RasterLayer.find_intersection(layers)
75
78
  assert intersection == vector.area
76
79
 
80
+ raster.set_window_for_intersection(intersection)
81
+ with pytest.raises(ValueError):
82
+ vector.set_window_for_intersection(intersection)
83
+
77
84
  def test_find_intersection_with_vector_bound() -> None:
78
85
  with tempfile.TemporaryDirectory() as tempdir:
79
86
  path = Path(tempdir) / "test.gpkg"
@@ -89,6 +96,29 @@ def test_find_intersection_with_vector_bound() -> None:
89
96
  intersection = RasterLayer.find_intersection(layers)
90
97
  assert intersection == vector.area
91
98
 
99
+ for layer in layers:
100
+ layer.set_window_for_intersection(intersection)
101
+
102
+ def test_find_intersection_with_vector_awkward_rounding() -> None:
103
+ with tempfile.TemporaryDirectory() as tempdir:
104
+ path = Path(tempdir) / "test.gpkg"
105
+ area = Area(left=-90, top=45, right=90, bottom=-45)
106
+ make_vectors_with_id(42, {area}, path)
107
+ assert path.exists
108
+
109
+ raster = RasterLayer(gdal_dataset_of_region(Area(left=-180, top=90, right=180, bottom=-90), 18.0))
110
+ vector = VectorLayer.layer_from_file(path, None, raster.map_projection.scale, raster.map_projection.name)
111
+
112
+ rounded_area = Area(left=-90, top=54, right=90, bottom=-54)
113
+ assert vector.area == rounded_area
114
+
115
+ layers = [raster, vector]
116
+ intersection = RasterLayer.find_intersection(layers)
117
+ assert intersection == vector.area
118
+
119
+ for layer in layers:
120
+ layer.set_window_for_intersection(intersection)
121
+
92
122
  def test_find_intersection_different_pixel_pitch() -> None:
93
123
  layers = [
94
124
  RasterLayer(gdal_dataset_of_region(Area(-10, 10, 10, -10), 0.02)),
@@ -1,5 +1,4 @@
1
1
  import sys
2
- from typing import List
3
2
 
4
3
  import pytest
5
4
 
@@ -84,5 +83,5 @@ def test_pixel_rounding_down(pixels: float, scale: float, expected: int) -> None
84
83
  ),
85
84
  ]
86
85
  )
87
- def test_pixel_scale_comparison(pixel_scales: List[PixelScale], expected: bool) -> None:
86
+ def test_pixel_scale_comparison(pixel_scales: list[PixelScale], expected: bool) -> None:
88
87
  assert are_pixel_scales_equal_enough(pixel_scales) == expected
@@ -49,6 +49,9 @@ def test_find_union_distinct() -> None:
49
49
  union = RasterLayer.find_union(layers)
50
50
  assert union == Area(-110, 10, 110, -10)
51
51
 
52
+ for layer in layers:
53
+ layer.set_window_for_union(union)
54
+
52
55
  def test_find_union_with_null() -> None:
53
56
  layers = [
54
57
  RasterLayer(gdal_dataset_of_region(Area(-10, 10, 10, -10), 0.02)),
@@ -80,6 +83,11 @@ def test_find_union_with_vector_unbound() -> None:
80
83
  union = RasterLayer.find_union(layers)
81
84
  assert union == vector.area
82
85
 
86
+ raster.set_window_for_union(union)
87
+ with pytest.raises(ValueError):
88
+ vector.set_window_for_union(union)
89
+
90
+
83
91
  def test_find_union_with_vector_bound() -> None:
84
92
  with tempfile.TemporaryDirectory() as tempdir:
85
93
  path = Path(tempdir) / "test.gpkg"
@@ -95,6 +103,9 @@ def test_find_union_with_vector_bound() -> None:
95
103
  union = RasterLayer.find_union(layers)
96
104
  assert union == vector.area
97
105
 
106
+ for layer in layers:
107
+ layer.set_window_for_union(union)
108
+
98
109
  @pytest.mark.parametrize("scale", [0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09])
99
110
  def test_set_union_self(scale) -> None:
100
111
  layer = RasterLayer(gdal_dataset_of_region(Area(-10, 10, 10, -10), scale))
@@ -14,5 +14,7 @@ except ModuleNotFoundError:
14
14
 
15
15
  from ._core import read_raster, read_rasters, read_shape, read_shape_like, constant, read_narrow_raster
16
16
  from .constants import WGS_84_PROJECTION
17
+ from .window import Area, MapProjection, Window
18
+ from ._backends.enumeration import dtype as DataType
17
19
 
18
20
  gdal.UseExceptions()
@@ -41,6 +41,26 @@ class operators(Enum):
41
41
  ISNAN = 36
42
42
 
43
43
  class dtype(Enum):
44
+ """Represents the type of data returned by a layer.
45
+
46
+ This enumeration defines the valid data types supported by Yirgacheffe, and is
47
+ what is returned by calling `datatype` on a layer or expression, and can be
48
+ passed to `astype` to convert values between types.
49
+
50
+ Attributes:
51
+ Float32: 32 bit floating point value
52
+ Float64: 64 bit floating point value
53
+ Byte: Unsigned 8 bit integer value
54
+ Int8: Signed 8 bit integer value
55
+ Int16: Signed 16 bit integer value
56
+ Int32: Signed 32 bit integer value
57
+ Int64: Signed 64 bit integer value
58
+ UInt8: Unsigned 8 bit integer value
59
+ UInt16: Unsigned 16 bit integer value
60
+ UInt32: Unsigned 32 bit integer value
61
+ UInt64: Unsigned 64 bit integer value
62
+ """
63
+
44
64
  Float32 = gdal.GDT_Float32
45
65
  Float64 = gdal.GDT_Float64
46
66
  Byte = gdal.GDT_Byte
@@ -1,4 +1,6 @@
1
- from typing import Callable, Dict
1
+ from __future__ import annotations
2
+
3
+ from typing import Callable
2
4
 
3
5
  import numpy as np
4
6
  import mlx.core as mx # type: ignore
@@ -184,7 +186,7 @@ def backend_to_dtype(val):
184
186
  def astype_op(data, datatype):
185
187
  return data.astype(dtype_to_backend(datatype))
186
188
 
187
- operator_map : Dict[op,Callable] = {
189
+ operator_map: dict[op, Callable] = {
188
190
  op.ADD: mx.array.__add__,
189
191
  op.SUB: mx.array.__sub__,
190
192
  op.MUL: mul_op,
@@ -1,4 +1,6 @@
1
- from typing import Callable, Dict
1
+ from __future__ import annotations
2
+
3
+ from typing import Callable
2
4
 
3
5
  import numpy as np
4
6
  import torch
@@ -122,7 +124,7 @@ def backend_to_dtype(val):
122
124
  def astype_op(data, datatype):
123
125
  return data.astype(dtype_to_backend(datatype))
124
126
 
125
- operator_map : Dict[op,Callable] = {
127
+ operator_map: dict[op, Callable] = {
126
128
  op.ADD: np.ndarray.__add__,
127
129
  op.SUB: np.ndarray.__sub__,
128
130
  op.MUL: np.ndarray.__mul__,
@@ -0,0 +1,163 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Sequence
5
+
6
+ from .layers.area import UniformAreaLayer
7
+ from .layers.base import YirgacheffeLayer
8
+ from .layers.constant import ConstantLayer
9
+ from .layers.group import GroupLayer, TiledGroupLayer
10
+ from .layers.rasters import RasterLayer
11
+ from .layers.vectors import VectorLayer
12
+ from .window import MapProjection
13
+ from ._backends.enumeration import dtype as DataType
14
+
15
+ def read_raster(
16
+ filename: Path | str,
17
+ band: int = 1,
18
+ ignore_nodata: bool = False,
19
+ ) -> RasterLayer:
20
+ """Open a raster file (e.g., GeoTIFF).
21
+
22
+ Args:
23
+ filename: Path of raster file to open.
24
+ band: For multi-band rasters, which band to use (defaults to first if not specified).
25
+ ignore_nodata: If the GeoTIFF has a NODATA value, don't substitute that value for NaN.
26
+
27
+ Returns:
28
+ An layer representing the raster data.
29
+
30
+ Examples:
31
+ >>> import yirgacheffe as yg
32
+ >>> with yg.read_raster('test.tif') as layer:
33
+ ... total = layer.sum()
34
+ """
35
+ return RasterLayer.layer_from_file(filename, band, ignore_nodata)
36
+
37
+ def read_narrow_raster(
38
+ filename: Path | str,
39
+ band: int = 1,
40
+ ignore_nodata: bool = False,
41
+ ) -> RasterLayer:
42
+ """Open a 1 pixel wide raster file as a global raster.
43
+
44
+ This exists for the special use case where an area per pixel raster would have the same value per horizontal row
45
+ (e.g., a WGS84 map projection). For that case you can use this to load a raster that is 1 pixel wide and have
46
+ it automatically expanded to act like a global raster in calculations.
47
+
48
+ Args:
49
+ filename: Path of raster file to open.
50
+ band: For multi-band rasters, which band to use (defaults to first if not specified).
51
+ ignore_nodata: If the GeoTIFF has a NODATA value, don't substitute that value for NaN.
52
+
53
+ Returns:
54
+ An layer representing the raster data.
55
+ """
56
+ return UniformAreaLayer.layer_from_file(filename, band, ignore_nodata)
57
+
58
+ def read_rasters(
59
+ filenames : Sequence[Path | str],
60
+ tiled: bool=False
61
+ ) -> GroupLayer:
62
+ """Open a set of raster files (e.g., GeoTIFFs) as a single layer.
63
+
64
+ Args:
65
+ filenames: List of paths of raster files to open.
66
+ tiled: If you know that the rasters for a regular tileset, then setting this flag allows
67
+ Yirgacheffe to perform certain optimisations that significantly improve performance for
68
+ this use case.
69
+
70
+ Returns:
71
+ An layer representing the raster data.
72
+
73
+ Examples:
74
+ >>> import yirgacheffe as yg
75
+ >>> with yg.read_rasters(['tile_N10_E10.tif', 'tile_N20_E10.tif']) as all_tiles:
76
+ ... ...
77
+ """
78
+ if not tiled:
79
+ return GroupLayer.layer_from_files(filenames)
80
+ else:
81
+ return TiledGroupLayer.layer_from_files(filenames)
82
+
83
+ def read_shape(
84
+ filename: Path | str,
85
+ projection: MapProjection | tuple[str, tuple[float, float]] | None = None,
86
+ where_filter: str | None = None,
87
+ datatype: DataType | None = None,
88
+ burn_value: int | float | str = 1,
89
+ ) -> VectorLayer:
90
+ """Open a polygon file (e.g., GeoJSON, GPKG, or ESRI Shape File).
91
+
92
+ Args:
93
+ filename: Path of vector file to open.
94
+ projection: The map projection to use.
95
+ where_filter: For use with files with many entries (e.g., GPKG), applies this filter to the data.
96
+ datatype: Specify the data type of the raster data generated.
97
+ burn_value: The value of each pixel in the polygon.
98
+
99
+ Returns:
100
+ An layer representing the vector data.
101
+
102
+ Examples:
103
+ >>> import yirgacheffe as yg
104
+ >>> with yg.read_shape('range.gpkg') as layer:
105
+ ... ...
106
+ """
107
+
108
+ if projection is not None:
109
+ if not isinstance(projection, MapProjection):
110
+ projection_name, scale_tuple = projection
111
+ projection = MapProjection(projection_name, scale_tuple[0], scale_tuple[1])
112
+
113
+ return VectorLayer._future_layer_from_file(
114
+ filename,
115
+ where_filter,
116
+ projection,
117
+ datatype,
118
+ burn_value,
119
+ )
120
+
121
+ def read_shape_like(
122
+ filename: Path | str,
123
+ like: YirgacheffeLayer,
124
+ where_filter: str | None = None,
125
+ datatype: DataType | None = None,
126
+ burn_value: int | float | str = 1,
127
+ ) -> VectorLayer:
128
+ """Open a polygon file (e.g., GeoJSON, GPKG, or ESRI Shape File).
129
+
130
+ Args:
131
+ filename: Path of vector file to open.
132
+ like: Another layer that has a projection and pixel scale set. This layer will
133
+ use the same projection and pixel scale as that one.
134
+ where_filter: For use with files with many entries (e.g., GPKG), applies this filter to the data.
135
+ datatype: Specify the data type of the raster data generated.
136
+ burn_value: The value of each pixel in the polygon.
137
+
138
+ Returns:
139
+ An layer representing the vector data.
140
+ """
141
+ return VectorLayer.layer_from_file_like(
142
+ filename,
143
+ like,
144
+ where_filter,
145
+ datatype,
146
+ burn_value,
147
+ )
148
+
149
+ def constant(value: int | float) -> ConstantLayer:
150
+ """Generate a layer that has the same value in all pixels regardless of scale, projection, and area.
151
+
152
+ Generally this should not be necessary unless you must have the constant as the first term in an
153
+ expression, as Yirgacheffe will automatically convert numbers into constant layers. However if the
154
+ constant is the first term in the expression it must be wrapped by this call otherwise Python will
155
+ not know that it should be part of the Yirgacheffe expression.
156
+
157
+ Args:
158
+ value: The value to be in each pixel of the expression term.
159
+
160
+ Returns:
161
+ A constant layer of the provided value.
162
+ """
163
+ return ConstantLayer(value)
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging
2
4
  import math
3
5
  import multiprocessing
@@ -12,7 +14,7 @@ from enum import Enum
12
14
  from multiprocessing import Semaphore, Process
13
15
  from multiprocessing.managers import SharedMemoryManager
14
16
  from pathlib import Path
15
- from typing import Callable, Dict, List, Optional, Union
17
+ from typing import Callable
16
18
 
17
19
  import deprecation
18
20
  import numpy as np
@@ -265,10 +267,10 @@ class LayerMathMixin:
265
267
 
266
268
  def to_geotiff(
267
269
  self,
268
- filename: Union[Path,str],
270
+ filename: Path | str,
269
271
  and_sum: bool = False,
270
- parallelism:Optional[Union[int,bool]]=None
271
- ) -> Optional[float]:
272
+ parallelism: int | bool | None = None
273
+ ) -> float | None:
272
274
  return LayerOperation(self).to_geotiff(filename, and_sum, parallelism)
273
275
 
274
276
  def sum(self):
@@ -398,7 +400,7 @@ class LayerOperation(LayerMathMixin):
398
400
  def area(self) -> Area:
399
401
  return self._get_operation_area(self.map_projection)
400
402
 
401
- def _get_operation_area(self, projection: Optional[MapProjection]) -> Area:
403
+ def _get_operation_area(self, projection: MapProjection | None) -> Area:
402
404
  lhs_area = self.lhs._get_operation_area(projection)
403
405
  try:
404
406
  rhs_area = self.rhs._get_operation_area(projection)
@@ -482,7 +484,7 @@ class LayerOperation(LayerMathMixin):
482
484
 
483
485
  @property
484
486
  def datatype(self) -> DataType:
485
- internal_types: List[DataType] = [
487
+ internal_types: list[DataType] = [
486
488
  self.lhs.datatype
487
489
  ]
488
490
  if self.rhs is not None:
@@ -511,7 +513,7 @@ class LayerOperation(LayerMathMixin):
511
513
  return projection
512
514
 
513
515
  @property
514
- def map_projection(self) -> Optional[MapProjection]:
516
+ def map_projection(self) -> MapProjection | None:
515
517
  try:
516
518
  projection = self.lhs.map_projection
517
519
  except AttributeError:
@@ -530,7 +532,7 @@ class LayerOperation(LayerMathMixin):
530
532
  projection: MapProjection,
531
533
  index: int,
532
534
  step: int,
533
- target_window:Optional[Window]=None
535
+ target_window: Window | None = None
534
536
  ):
535
537
 
536
538
  if self.buffer_padding:
@@ -607,7 +609,7 @@ class LayerOperation(LayerMathMixin):
607
609
  res = chunk_max
608
610
  return res
609
611
 
610
- def save(self, destination_layer, and_sum=False, callback=None, band=1) -> Optional[float]:
612
+ def save(self, destination_layer, and_sum=False, callback=None, band=1) -> float | None:
611
613
  """
612
614
  Calling save will write the output of the operation to the provied layer.
613
615
  If you provide sum as true it will additionall compute the sum and return that.
@@ -723,7 +725,7 @@ class LayerOperation(LayerMathMixin):
723
725
  callback=None,
724
726
  parallelism=None,
725
727
  band=1
726
- ) -> Optional[float]:
728
+ ) -> float | None:
727
729
  assert (destination_layer is not None) or and_sum
728
730
  try:
729
731
  computation_window = self.window
@@ -763,7 +765,7 @@ class LayerOperation(LayerMathMixin):
763
765
  or (computation_window.ysize != destination_window.ysize):
764
766
  raise ValueError("Destination raster window size does not match input raster window size.")
765
767
 
766
- np_type_map : Dict[int, np.dtype] = {
768
+ np_type_map: dict[int, np.dtype] = {
767
769
  gdal.GDT_Byte: np.dtype('byte'),
768
770
  gdal.GDT_Float32: np.dtype('float32'),
769
771
  gdal.GDT_Float64: np.dtype('float64'),
@@ -882,7 +884,7 @@ class LayerOperation(LayerMathMixin):
882
884
  callback=None,
883
885
  parallelism=None,
884
886
  band=1
885
- ) -> Optional[float]:
887
+ ) -> float | None:
886
888
  if destination_layer is None:
887
889
  raise ValueError("Layer is required")
888
890
  return self._parallel_save(destination_layer, and_sum, callback, parallelism, band)
@@ -892,25 +894,20 @@ class LayerOperation(LayerMathMixin):
892
894
 
893
895
  def to_geotiff(
894
896
  self,
895
- filename: Union[Path,str],
897
+ filename: Path | str,
896
898
  and_sum: bool = False,
897
- parallelism:Optional[Union[int,bool]] = None
898
- ) -> Optional[float]:
899
+ parallelism: int | bool | None = None
900
+ ) -> float | None:
899
901
  """Saves a calculation to a raster file, optionally also returning the sum of pixels.
900
902
 
901
- Parameters
902
- ----------
903
- filename : Path
904
- Path of the raster to save the result to.
905
- and_sum : bool, default=False
906
- If true then the function will also calculate the sum of the raster as it goes and return that value.
907
- parallelism : int or bool, optional, default=None
908
- If passed, attempt to use multiple CPU cores up to the number provided, or if set to True, yirgacheffe
909
- will pick a sensible value.
910
-
911
- Returns
912
- -------
913
- float, optional
903
+ Args:
904
+ filename: Path of the raster to save the result to.
905
+ and_sum: If true then the function will also calculate the sum of the raster as it goes and return
906
+ that value.
907
+ parallelism: If passed, attempt to use multiple CPU cores up to the number provided, or if set to True,
908
+ yirgacheffe will pick a sensible value.
909
+
910
+ Returns:
914
911
  Either returns None, or the sum of the pixels in the resulting raster if `and_sum` was specified.
915
912
  """
916
913
 
@@ -1,6 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  from math import ceil, floor
2
4
  from pathlib import Path
3
- from typing import Any, Optional, Union
5
+ from typing import Any
4
6
 
5
7
  import numpy
6
8
  from osgeo import gdal
@@ -19,7 +21,7 @@ class UniformAreaLayer(RasterLayer):
19
21
  """
20
22
 
21
23
  @staticmethod
22
- def generate_narrow_area_projection(source_filename: Union[Path,str], target_filename: Union[Path,str]) -> None:
24
+ def generate_narrow_area_projection(source_filename: Path | str, target_filename: Path | str) -> None:
23
25
  source = gdal.Open(source_filename, gdal.GA_ReadOnly)
24
26
  if source is None:
25
27
  raise FileNotFoundError(source_filename)
@@ -57,7 +59,7 @@ class UniformAreaLayer(RasterLayer):
57
59
  return False
58
60
  return True
59
61
 
60
- def __init__(self, dataset, name: Optional[str] = None, band: int = 1, ignore_nodata: bool = False):
62
+ def __init__(self, dataset, name: str | None = None, band: int = 1, ignore_nodata: bool = False):
61
63
  if dataset.RasterXSize > 1:
62
64
  raise ValueError("Expected a shrunk dataset")
63
65
  self.databand = dataset.GetRasterBand(1).ReadAsArray(0, 0, 1, dataset.RasterYSize)