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.
- {yirgacheffe-1.7.9/yirgacheffe.egg-info → yirgacheffe-1.8.1}/PKG-INFO +8 -4
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/README.md +1 -1
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/pyproject.toml +10 -3
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_area.py +3 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_auto_windowing.py +2 -2
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_base.py +6 -7
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_intersection.py +30 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_rounding.py +1 -2
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_union.py +11 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/__init__.py +2 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/_backends/enumeration.py +20 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/_backends/mlx.py +4 -2
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/_backends/numpy.py +4 -2
- yirgacheffe-1.8.1/yirgacheffe/_core.py +163 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/_operators.py +25 -28
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/area.py +5 -3
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/base.py +20 -27
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/constant.py +4 -2
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/group.py +11 -11
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/h3layer.py +4 -2
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/rasters.py +18 -18
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/rescaled.py +3 -3
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/vectors.py +65 -66
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/rounding.py +4 -3
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/window.py +52 -97
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1/yirgacheffe.egg-info}/PKG-INFO +8 -4
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe.egg-info/requires.txt +4 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe.egg-info/top_level.txt +1 -0
- yirgacheffe-1.7.9/yirgacheffe/_core.py +0 -183
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/LICENSE +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/MANIFEST.in +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/setup.cfg +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_constants.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_datatypes.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_group.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_h3layer.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_multiband.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_nodata.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_openers.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_operators.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_optimisation.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_parallel_operators.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_pickle.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_projection.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_raster.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_rescaling.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_save_with_window.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_sum_with_window.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_uniform_area_layer.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_vectors.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/tests/test_window.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/_backends/__init__.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/constants.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/layers/__init__.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/operators.py +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe/py.typed +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe.egg-info/SOURCES.txt +0 -0
- {yirgacheffe-1.7.9 → yirgacheffe-1.8.1}/yirgacheffe.egg-info/dependency_links.txt +0 -0
- {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.
|
|
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://
|
|
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://
|
|
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
|
|
45
|
+
# Yirgacheffe: a declarative geospatial library for Python to make data-science with maps easier
|
|
42
46
|
|
|
43
47
|
## Overview
|
|
44
48
|
|
|
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "yirgacheffe"
|
|
9
|
-
version = "1.
|
|
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://
|
|
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://
|
|
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*"]
|
|
@@ -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(
|
|
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:
|
|
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:
|
|
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:
|
|
144
|
-
expected:
|
|
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:
|
|
182
|
-
expected:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
270
|
+
filename: Path | str,
|
|
269
271
|
and_sum: bool = False,
|
|
270
|
-
parallelism:
|
|
271
|
-
) ->
|
|
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:
|
|
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:
|
|
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) ->
|
|
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:
|
|
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) ->
|
|
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
|
-
) ->
|
|
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
|
|
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
|
-
) ->
|
|
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:
|
|
897
|
+
filename: Path | str,
|
|
896
898
|
and_sum: bool = False,
|
|
897
|
-
parallelism:
|
|
898
|
-
) ->
|
|
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
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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)
|