yirgacheffe 1.8.1__tar.gz → 1.9.0__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.
Potentially problematic release.
This version of yirgacheffe might be problematic. Click here for more details.
- {yirgacheffe-1.8.1/yirgacheffe.egg-info → yirgacheffe-1.9.0}/PKG-INFO +7 -1
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/README.md +5 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/pyproject.toml +2 -1
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_base.py +61 -26
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_h3layer.py +1 -1
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_openers.py +1 -3
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_operators.py +81 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_pickle.py +1 -1
- yirgacheffe-1.9.0/tests/test_projection.py +45 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_vectors.py +2 -4
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/_operators.py +44 -2
- yirgacheffe-1.9.0/yirgacheffe/constants.py +8 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/layers/base.py +39 -12
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/window.py +20 -7
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0/yirgacheffe.egg-info}/PKG-INFO +7 -1
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe.egg-info/requires.txt +1 -0
- yirgacheffe-1.8.1/tests/test_projection.py +0 -32
- yirgacheffe-1.8.1/yirgacheffe/constants.py +0 -8
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/LICENSE +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/MANIFEST.in +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/setup.cfg +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_area.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_auto_windowing.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_constants.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_datatypes.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_group.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_intersection.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_multiband.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_nodata.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_optimisation.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_parallel_operators.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_raster.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_rescaling.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_rounding.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_save_with_window.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_sum_with_window.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_uniform_area_layer.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_union.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/tests/test_window.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/__init__.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/_backends/__init__.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/_backends/enumeration.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/_backends/mlx.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/_backends/numpy.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/_core.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/layers/__init__.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/layers/area.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/layers/constant.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/layers/group.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/layers/h3layer.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/layers/rasters.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/layers/rescaled.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/layers/vectors.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/operators.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/py.typed +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe/rounding.py +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe.egg-info/SOURCES.txt +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe.egg-info/dependency_links.txt +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe.egg-info/entry_points.txt +0 -0
- {yirgacheffe-1.8.1 → yirgacheffe-1.9.0}/yirgacheffe.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: yirgacheffe
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
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
|
|
@@ -27,6 +27,7 @@ Requires-Dist: dill
|
|
|
27
27
|
Requires-Dist: deprecation
|
|
28
28
|
Requires-Dist: tomli
|
|
29
29
|
Requires-Dist: h3
|
|
30
|
+
Requires-Dist: pyproj
|
|
30
31
|
Provides-Extra: mlx
|
|
31
32
|
Requires-Dist: mlx; extra == "mlx"
|
|
32
33
|
Provides-Extra: dev
|
|
@@ -44,6 +45,11 @@ Dynamic: license-file
|
|
|
44
45
|
|
|
45
46
|
# Yirgacheffe: a declarative geospatial library for Python to make data-science with maps easier
|
|
46
47
|
|
|
48
|
+
[](https://github.com/quantifyearth/yirgacheffe/actions)
|
|
49
|
+
[](https://yirgacheffe.org)
|
|
50
|
+
[](https://pypi.org/project/yirgacheffe/)
|
|
51
|
+
|
|
52
|
+
|
|
47
53
|
## Overview
|
|
48
54
|
|
|
49
55
|
Yirgacheffe is an attempt to wrap raster and polygon geospatial datasets such that you can do computational work on them as a whole or at the pixel level, but without having to do a lot of the grunt work of working out where you need to be in rasters, or managing how much you can load into memory safely.
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Yirgacheffe: a declarative geospatial library for Python to make data-science with maps easier
|
|
2
2
|
|
|
3
|
+
[](https://github.com/quantifyearth/yirgacheffe/actions)
|
|
4
|
+
[](https://yirgacheffe.org)
|
|
5
|
+
[](https://pypi.org/project/yirgacheffe/)
|
|
6
|
+
|
|
7
|
+
|
|
3
8
|
## Overview
|
|
4
9
|
|
|
5
10
|
Yirgacheffe is an attempt to wrap raster and polygon geospatial datasets such that you can do computational work on them as a whole or at the pixel level, but without having to do a lot of the grunt work of working out where you need to be in rasters, or managing how much you can load into memory safely.
|
|
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "yirgacheffe"
|
|
9
|
-
version = "1.
|
|
9
|
+
version = "1.9.0"
|
|
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" }]
|
|
@@ -30,6 +30,7 @@ dependencies = [
|
|
|
30
30
|
"deprecation",
|
|
31
31
|
"tomli",
|
|
32
32
|
"h3",
|
|
33
|
+
"pyproj",
|
|
33
34
|
]
|
|
34
35
|
requires-python = ">=3.10"
|
|
35
36
|
|
|
@@ -1,136 +1,166 @@
|
|
|
1
1
|
import math
|
|
2
|
+
import os
|
|
3
|
+
import tempfile
|
|
2
4
|
|
|
3
5
|
import pytest
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
import yirgacheffe as yg
|
|
6
8
|
from yirgacheffe.layers import YirgacheffeLayer
|
|
7
9
|
from yirgacheffe.window import Area, MapProjection
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
layer = YirgacheffeLayer(
|
|
11
|
-
Area(-10, 10, 10, -10),
|
|
12
|
-
MapProjection("OTHER PROJECTION", 0.02, -0.02),
|
|
13
|
-
)
|
|
14
|
-
with pytest.raises(NotImplementedError):
|
|
15
|
-
_ = layer.latlng_for_pixel(10, 10)
|
|
11
|
+
from tests.helpers import make_vectors_with_id
|
|
16
12
|
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
def test_pixel_to_latlng_no_projection() -> None:
|
|
14
|
+
with tempfile.TemporaryDirectory() as tempdir:
|
|
15
|
+
path = os.path.join(tempdir, "test.gpkg")
|
|
16
|
+
area = Area(-10.0, 10.0, 10.0, 0.0)
|
|
17
|
+
make_vectors_with_id(42, {area}, path)
|
|
18
|
+
with yg.read_shape(path) as layer:
|
|
19
|
+
with pytest.raises(ValueError):
|
|
20
|
+
_ = layer.latlng_for_pixel(10, 10)
|
|
21
|
+
|
|
22
|
+
def test_latlng_to_pixel_no_projection() -> None:
|
|
23
|
+
with tempfile.TemporaryDirectory() as tempdir:
|
|
24
|
+
path = os.path.join(tempdir, "test.gpkg")
|
|
25
|
+
area = Area(-10.0, 10.0, 10.0, 0.0)
|
|
26
|
+
make_vectors_with_id(42, {area}, path)
|
|
27
|
+
with yg.read_shape(path) as layer:
|
|
28
|
+
with pytest.raises(ValueError):
|
|
29
|
+
_ = layer.pixel_for_latlng(10.0, 10.0)
|
|
24
30
|
|
|
25
31
|
@pytest.mark.parametrize(
|
|
26
|
-
"area,pixel,expected",
|
|
32
|
+
"area,projection,pixel,expected",
|
|
27
33
|
[
|
|
28
34
|
(
|
|
29
35
|
Area(-10, 10, 10, -10),
|
|
36
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
30
37
|
(0, 0),
|
|
31
38
|
(10.0, -10.0)
|
|
32
39
|
),
|
|
33
40
|
(
|
|
34
41
|
Area(-10, 10, 10, -10),
|
|
42
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
35
43
|
(1, 1),
|
|
36
44
|
(9.8, -9.8)
|
|
37
45
|
),
|
|
38
46
|
(
|
|
39
47
|
Area(-10, 10, 10, -10),
|
|
48
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
40
49
|
(101, 101),
|
|
41
50
|
(-10.2, 10.2)
|
|
42
51
|
),
|
|
43
52
|
(
|
|
44
53
|
Area(-10, 10, 10, -10),
|
|
54
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
45
55
|
(-1, -1),
|
|
46
56
|
(10.2, -10.2)
|
|
47
57
|
),
|
|
48
58
|
(
|
|
49
59
|
Area(10, 10, 20, -10),
|
|
60
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
50
61
|
(1, 1),
|
|
51
62
|
(9.8, 10.2)
|
|
52
63
|
),
|
|
53
64
|
(
|
|
54
65
|
Area(-10, -10, 10, -20),
|
|
66
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
55
67
|
(1, 1),
|
|
56
68
|
(-10.2, -9.8)
|
|
57
69
|
),
|
|
58
70
|
]
|
|
59
71
|
)
|
|
60
|
-
def test_latlng_for_pixel(
|
|
72
|
+
def test_latlng_for_pixel(
|
|
73
|
+
area: Area,
|
|
74
|
+
projection: MapProjection,
|
|
75
|
+
pixel: tuple[int, int],
|
|
76
|
+
expected: tuple[float, float]
|
|
77
|
+
) -> None:
|
|
61
78
|
layer = YirgacheffeLayer(
|
|
62
79
|
area,
|
|
63
|
-
|
|
80
|
+
projection,
|
|
64
81
|
)
|
|
65
82
|
result = layer.latlng_for_pixel(*pixel)
|
|
66
83
|
assert math.isclose(result[0], expected[0])
|
|
67
84
|
assert math.isclose(result[1], expected[1])
|
|
68
85
|
|
|
69
86
|
@pytest.mark.parametrize(
|
|
70
|
-
"area,coord,expected",
|
|
87
|
+
"area,projection,coord,expected",
|
|
71
88
|
[
|
|
72
89
|
(
|
|
73
90
|
Area(-10, 10, 10, -10),
|
|
91
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
74
92
|
(10.0, -10.0),
|
|
75
93
|
(0, 0)
|
|
76
94
|
),
|
|
77
95
|
(
|
|
78
96
|
Area(-10, 10, 10, -10),
|
|
97
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
79
98
|
(9.8, -9.8),
|
|
80
99
|
(1, 1)
|
|
81
100
|
),
|
|
82
101
|
(
|
|
83
102
|
Area(-10, 10, 10, -10),
|
|
103
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
84
104
|
(0.0, 0.0),
|
|
85
105
|
(50, 50)
|
|
86
106
|
),
|
|
87
107
|
]
|
|
88
108
|
)
|
|
89
|
-
def test_pixel_for_latlng(
|
|
109
|
+
def test_pixel_for_latlng(
|
|
110
|
+
area: Area,
|
|
111
|
+
projection: MapProjection,
|
|
112
|
+
coord: tuple[float, float],
|
|
113
|
+
expected: tuple[int, int]
|
|
114
|
+
) -> None:
|
|
90
115
|
layer = YirgacheffeLayer(
|
|
91
116
|
area,
|
|
92
|
-
|
|
117
|
+
projection,
|
|
93
118
|
)
|
|
94
119
|
result = layer.pixel_for_latlng(*coord)
|
|
95
120
|
assert result == expected
|
|
96
121
|
|
|
97
|
-
|
|
98
122
|
@pytest.mark.parametrize(
|
|
99
|
-
"area,window,pixel,expected",
|
|
123
|
+
"area,window,projection,pixel,expected",
|
|
100
124
|
[
|
|
101
125
|
(
|
|
102
126
|
Area(-10, 10, 10, -10),
|
|
103
127
|
Area(-5, 5, 5, -5),
|
|
128
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
104
129
|
(0, 0),
|
|
105
130
|
(5.0, -5.0)
|
|
106
131
|
),
|
|
107
132
|
(
|
|
108
133
|
Area(-10, 10, 10, -10),
|
|
109
134
|
Area(-5, 5, 5, -5),
|
|
135
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
110
136
|
(1, 1),
|
|
111
137
|
(4.8, -4.8)
|
|
112
138
|
),
|
|
113
139
|
(
|
|
114
140
|
Area(-10, 10, 10, -10),
|
|
115
141
|
Area(-5, 5, 5, -5),
|
|
142
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
116
143
|
(101, 101),
|
|
117
144
|
(-15.2, 15.2)
|
|
118
145
|
),
|
|
119
146
|
(
|
|
120
147
|
Area(-10, 10, 10, -10),
|
|
121
148
|
Area(-5, 5, 5, -5),
|
|
149
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
122
150
|
(-1, -1),
|
|
123
151
|
(5.2, -5.2)
|
|
124
152
|
),
|
|
125
153
|
(
|
|
126
154
|
Area(10, 10, 20, -10),
|
|
127
155
|
Area(15, 5, 20, -5),
|
|
156
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
128
157
|
(1, 1),
|
|
129
158
|
(4.8, 15.2)
|
|
130
159
|
),
|
|
131
160
|
(
|
|
132
161
|
Area(-10, -10, 10, -20),
|
|
133
162
|
Area(-5, -15, 5, -20),
|
|
163
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
134
164
|
(1, 1),
|
|
135
165
|
(-15.2, -4.8)
|
|
136
166
|
),
|
|
@@ -139,12 +169,13 @@ def test_pixel_for_latlng(area: Area, coord: tuple[float, float], expected: tupl
|
|
|
139
169
|
def test_latlng_for_pixel_with_intersection(
|
|
140
170
|
area: Area,
|
|
141
171
|
window: Area,
|
|
172
|
+
projection: MapProjection,
|
|
142
173
|
pixel: tuple[int, int],
|
|
143
174
|
expected: tuple[float, float]
|
|
144
175
|
) -> None:
|
|
145
176
|
layer = YirgacheffeLayer(
|
|
146
177
|
area,
|
|
147
|
-
|
|
178
|
+
projection,
|
|
148
179
|
)
|
|
149
180
|
layer.set_window_for_intersection(window)
|
|
150
181
|
result = layer.latlng_for_pixel(*pixel)
|
|
@@ -152,23 +183,26 @@ def test_latlng_for_pixel_with_intersection(
|
|
|
152
183
|
assert math.isclose(result[1], expected[1])
|
|
153
184
|
|
|
154
185
|
@pytest.mark.parametrize(
|
|
155
|
-
"area,window,coord,expected",
|
|
186
|
+
"area,window,projection,coord,expected",
|
|
156
187
|
[
|
|
157
188
|
(
|
|
158
189
|
Area(-10, 10, 10, -10),
|
|
159
190
|
Area(-5, 5, 5, -5),
|
|
191
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
160
192
|
(5.0, -5.0),
|
|
161
193
|
(0, 0)
|
|
162
194
|
),
|
|
163
195
|
(
|
|
164
196
|
Area(-10, 10, 10, -10),
|
|
165
197
|
Area(-5, 5, 5, -5),
|
|
198
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
166
199
|
(4.8, -4.8),
|
|
167
200
|
(1, 1)
|
|
168
201
|
),
|
|
169
202
|
(
|
|
170
203
|
Area(-10, 10, 10, -10),
|
|
171
204
|
Area(-5, 5, 5, -5),
|
|
205
|
+
MapProjection("epsg:4326", 0.2, -0.2),
|
|
172
206
|
(0.0, 0.0),
|
|
173
207
|
(25, 25)
|
|
174
208
|
),
|
|
@@ -177,12 +211,13 @@ def test_latlng_for_pixel_with_intersection(
|
|
|
177
211
|
def test_pixel_for_latlng_with_intersection(
|
|
178
212
|
area: Area,
|
|
179
213
|
window: Area,
|
|
214
|
+
projection: MapProjection,
|
|
180
215
|
coord: tuple[float, float],
|
|
181
216
|
expected: tuple[int, int]
|
|
182
217
|
) -> None:
|
|
183
218
|
layer = YirgacheffeLayer(
|
|
184
219
|
area,
|
|
185
|
-
|
|
220
|
+
projection,
|
|
186
221
|
)
|
|
187
222
|
layer.set_window_for_intersection(window)
|
|
188
223
|
result = layer.pixel_for_latlng(*coord)
|
|
@@ -23,7 +23,7 @@ def test_h3_layer(cell_id: str, is_valid: bool, expected_zoom: int) -> None:
|
|
|
23
23
|
if is_valid:
|
|
24
24
|
with H3CellLayer(cell_id, MapProjection(WGS_84_PROJECTION, 0.001, -0.001)) as layer:
|
|
25
25
|
assert layer.zoom == expected_zoom
|
|
26
|
-
assert layer.
|
|
26
|
+
assert layer.map_projection.epsg == 4326
|
|
27
27
|
|
|
28
28
|
# without getting too deep, we'd expect a mix of zeros and ones in the data
|
|
29
29
|
window = layer.window
|
|
@@ -90,7 +90,6 @@ def test_open_gpkg() -> None:
|
|
|
90
90
|
assert layer.geo_transform == (area.left, 1.0, 0.0, area.top, 0.0, -1.0)
|
|
91
91
|
assert layer.window == Window(0, 0, 20, 10)
|
|
92
92
|
assert layer.map_projection == MapProjection(WGS_84_PROJECTION, 1.0, -1.0)
|
|
93
|
-
assert layer.projection == WGS_84_PROJECTION
|
|
94
93
|
|
|
95
94
|
def test_open_gpkg_with_mapprojection() -> None:
|
|
96
95
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
@@ -103,7 +102,6 @@ def test_open_gpkg_with_mapprojection() -> None:
|
|
|
103
102
|
assert layer.geo_transform == (area.left, 1.0, 0.0, area.top, 0.0, -1.0)
|
|
104
103
|
assert layer.window == Window(0, 0, 20, 10)
|
|
105
104
|
assert layer.map_projection == MapProjection(WGS_84_PROJECTION, 1.0, -1.0)
|
|
106
|
-
assert layer.projection == WGS_84_PROJECTION
|
|
107
105
|
|
|
108
106
|
def test_open_gpkg_with_no_projection() -> None:
|
|
109
107
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
@@ -129,7 +127,7 @@ def test_open_gpkg_direct_scale() -> None:
|
|
|
129
127
|
assert layer.area == area
|
|
130
128
|
assert layer.geo_transform == (area.left, 1.0, 0.0, area.top, 0.0, -1.0)
|
|
131
129
|
assert layer.window == Window(0, 0, 20, 10)
|
|
132
|
-
assert layer.
|
|
130
|
+
assert layer.map_projection == MapProjection(WGS_84_PROJECTION, 1.0, -1.0)
|
|
133
131
|
|
|
134
132
|
def test_open_gpkg_with_filter() -> None:
|
|
135
133
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
@@ -1596,3 +1596,84 @@ def test_isnan() -> None:
|
|
|
1596
1596
|
actual = result.read_array(0, 0, 4, 2)
|
|
1597
1597
|
expected = data1 == 5.0
|
|
1598
1598
|
assert (expected == actual).all()
|
|
1599
|
+
|
|
1600
|
+
@pytest.mark.parametrize("blocksize", [1, 2, 4, 8])
|
|
1601
|
+
def test_add_byte_layers_read_array_all(monkeypatch, blocksize) -> None:
|
|
1602
|
+
with monkeypatch.context() as m:
|
|
1603
|
+
m.setattr(yirgacheffe.constants, "YSTEP", blocksize)
|
|
1604
|
+
data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
|
|
1605
|
+
data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]])
|
|
1606
|
+
|
|
1607
|
+
with (
|
|
1608
|
+
RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1)) as layer1,
|
|
1609
|
+
RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2)) as layer2,
|
|
1610
|
+
):
|
|
1611
|
+
comp = layer1 + layer2
|
|
1612
|
+
expected = data1 + data2
|
|
1613
|
+
actual = comp.read_array(0, 0, 4, 2)
|
|
1614
|
+
assert (expected == actual).all()
|
|
1615
|
+
|
|
1616
|
+
@pytest.mark.parametrize("blocksize", [1, 2, 4, 8])
|
|
1617
|
+
def test_add_byte_layers_read_array_partial_horizontal(monkeypatch, blocksize) -> None:
|
|
1618
|
+
with monkeypatch.context() as m:
|
|
1619
|
+
m.setattr(yirgacheffe.constants, "YSTEP", blocksize)
|
|
1620
|
+
data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
|
|
1621
|
+
data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]])
|
|
1622
|
+
|
|
1623
|
+
with (
|
|
1624
|
+
RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1)) as layer1,
|
|
1625
|
+
RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2)) as layer2,
|
|
1626
|
+
):
|
|
1627
|
+
comp = layer1 + layer2
|
|
1628
|
+
expected = (data1 + data2)[0:3,1:3]
|
|
1629
|
+
actual = comp.read_array(1, 0, 2, 2)
|
|
1630
|
+
assert (expected == actual).all()
|
|
1631
|
+
|
|
1632
|
+
@pytest.mark.parametrize("blocksize", [1, 2, 4, 8])
|
|
1633
|
+
def test_add_byte_layers_read_array_partial_vertical(monkeypatch, blocksize) -> None:
|
|
1634
|
+
with monkeypatch.context() as m:
|
|
1635
|
+
m.setattr(yirgacheffe.constants, "YSTEP", blocksize)
|
|
1636
|
+
data1 = np.array([[1, 2], [3, 4], [5, 6], [7, 8]])
|
|
1637
|
+
data2 = np.array([[10, 20], [30, 40], [50, 60], [70, 80]])
|
|
1638
|
+
|
|
1639
|
+
with (
|
|
1640
|
+
RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1)) as layer1,
|
|
1641
|
+
RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2)) as layer2,
|
|
1642
|
+
):
|
|
1643
|
+
comp = layer1 + layer2
|
|
1644
|
+
expected = (data1 + data2)[1:3,0:3]
|
|
1645
|
+
actual = comp.read_array(0, 1, 2, 2)
|
|
1646
|
+
assert (expected == actual).all()
|
|
1647
|
+
|
|
1648
|
+
@pytest.mark.parametrize("blocksize", [1, 2, 4, 8])
|
|
1649
|
+
def test_add_byte_layers_read_array_partial(monkeypatch, blocksize) -> None:
|
|
1650
|
+
with monkeypatch.context() as m:
|
|
1651
|
+
m.setattr(yirgacheffe.constants, "YSTEP", blocksize)
|
|
1652
|
+
data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]])
|
|
1653
|
+
data2 = np.array([[10, 10, 10, 10], [20, 20, 20, 20], [30, 30, 30, 30], [40, 40, 40, 40]])
|
|
1654
|
+
|
|
1655
|
+
with (
|
|
1656
|
+
RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1)) as layer1,
|
|
1657
|
+
RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2)) as layer2,
|
|
1658
|
+
):
|
|
1659
|
+
comp = layer1 + layer2
|
|
1660
|
+
expected = (data1 + data2)[1:3,1:3]
|
|
1661
|
+
actual = comp.read_array(1, 1, 2, 2)
|
|
1662
|
+
assert (expected == actual).all()
|
|
1663
|
+
|
|
1664
|
+
@pytest.mark.parametrize("blocksize", [1, 2, 4, 8])
|
|
1665
|
+
def test_add_byte_layers_read_array_superset(monkeypatch, blocksize) -> None:
|
|
1666
|
+
with monkeypatch.context() as m:
|
|
1667
|
+
m.setattr(yirgacheffe.constants, "YSTEP", blocksize)
|
|
1668
|
+
data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]])
|
|
1669
|
+
data2 = np.array([[10, 10, 10, 10], [20, 20, 20, 20], [30, 30, 30, 30], [40, 40, 40, 40]])
|
|
1670
|
+
|
|
1671
|
+
with (
|
|
1672
|
+
RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1)) as layer1,
|
|
1673
|
+
RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data2)) as layer2,
|
|
1674
|
+
):
|
|
1675
|
+
comp = layer1 + layer2
|
|
1676
|
+
inner_expected = data1 + data2
|
|
1677
|
+
expected = np.pad(inner_expected, (1, 1))
|
|
1678
|
+
actual = comp.read_array(-1, -1, 6, 6)
|
|
1679
|
+
assert (expected == actual).all()
|
|
@@ -48,7 +48,7 @@ def test_pickle_dyanamic_vector_layer() -> None:
|
|
|
48
48
|
assert restore.area == area
|
|
49
49
|
assert restore.geo_transform == (area.left, 1.0, 0.0, area.top, 0.0, -1.0)
|
|
50
50
|
assert restore.window == Window(0, 0, 20, 10)
|
|
51
|
-
assert restore.
|
|
51
|
+
assert restore.map_projection == MapProjection(WGS_84_PROJECTION, 1.0, -1.0)
|
|
52
52
|
|
|
53
53
|
del layer
|
|
54
54
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import pyproj
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from yirgacheffe.window import MapProjection
|
|
5
|
+
from yirgacheffe.rounding import MINIMAL_DEGREE_OF_INTEREST
|
|
6
|
+
|
|
7
|
+
@pytest.mark.parametrize("name", [
|
|
8
|
+
"epsg:4326",
|
|
9
|
+
"esri:54009",
|
|
10
|
+
pyproj.CRS.from_string("epsg:4326").to_wkt(),
|
|
11
|
+
pyproj.CRS.from_string("esri:54009").to_wkt(),
|
|
12
|
+
])
|
|
13
|
+
def test_scale_from_projection(name) -> None:
|
|
14
|
+
projection = MapProjection(name, 0.1, -0.1)
|
|
15
|
+
assert projection.name == pyproj.CRS.from_string(name).to_wkt()
|
|
16
|
+
assert projection.xstep == 0.1
|
|
17
|
+
assert projection.ystep == -0.1
|
|
18
|
+
scale = projection.scale
|
|
19
|
+
assert scale.xstep == 0.1
|
|
20
|
+
assert scale.ystep == -0.1
|
|
21
|
+
|
|
22
|
+
PROJ_A = "epsg:4326"
|
|
23
|
+
PROJ_B = "esri:54009"
|
|
24
|
+
|
|
25
|
+
@pytest.mark.parametrize(
|
|
26
|
+
"lhs,rhs,is_equal",
|
|
27
|
+
[
|
|
28
|
+
(MapProjection(PROJ_A, 0.1, -0.1), MapProjection(PROJ_A, 0.1, -0.1), True),
|
|
29
|
+
(MapProjection(PROJ_A, 0.1, -0.1), MapProjection(PROJ_B, 0.1, -0.1), False),
|
|
30
|
+
(MapProjection(PROJ_A, 0.1, -0.1), MapProjection(PROJ_A, 0.1, 0.1), False),
|
|
31
|
+
(MapProjection(PROJ_A, 0.1, -0.1), MapProjection(PROJ_A, -0.1, 0.1), False),
|
|
32
|
+
(MapProjection(PROJ_A, 0.1, -0.1), MapProjection(PROJ_A, 0.1 + (MINIMAL_DEGREE_OF_INTEREST / 2), -0.1), True),
|
|
33
|
+
(MapProjection(PROJ_A, 0.1, -0.1), MapProjection(PROJ_A, 0.1 - (MINIMAL_DEGREE_OF_INTEREST / 2), -0.1), True),
|
|
34
|
+
(MapProjection(PROJ_A, 0.1, -0.1), MapProjection(PROJ_A, 0.1, -0.1 + (MINIMAL_DEGREE_OF_INTEREST / 2)), True),
|
|
35
|
+
(MapProjection(PROJ_A, 0.1, -0.1), MapProjection(PROJ_A, 0.1, -0.1 - (MINIMAL_DEGREE_OF_INTEREST / 2)), True),
|
|
36
|
+
]
|
|
37
|
+
)
|
|
38
|
+
def test_projection_equality(lhs: MapProjection, rhs : MapProjection, is_equal: bool) -> None:
|
|
39
|
+
assert MINIMAL_DEGREE_OF_INTEREST > 0.0
|
|
40
|
+
assert (lhs == rhs) == is_equal
|
|
41
|
+
assert (lhs != rhs) == (not is_equal)
|
|
42
|
+
|
|
43
|
+
def test_invalid_projection_name() -> None:
|
|
44
|
+
with pytest.raises(ValueError):
|
|
45
|
+
_ = MapProjection("random name", 1.0, -1.0)
|
|
@@ -29,8 +29,7 @@ def test_basic_dynamic_vector_layer() -> None:
|
|
|
29
29
|
assert layer.area == area
|
|
30
30
|
assert layer.geo_transform == (area.left, 1.0, 0.0, area.top, 0.0, -1.0)
|
|
31
31
|
assert layer.window == Window(0, 0, 20, 10)
|
|
32
|
-
assert layer.
|
|
33
|
-
assert layer.map_projection.name == WGS_84_PROJECTION
|
|
32
|
+
assert layer.map_projection.epsg == 4326
|
|
34
33
|
|
|
35
34
|
# The astype here is to catch escaping MLX types...
|
|
36
35
|
res = layer.read_array(0, 0, 20, 20).astype(int)
|
|
@@ -46,8 +45,7 @@ def test_rastered_vector_layer() -> None:
|
|
|
46
45
|
assert layer.area == area
|
|
47
46
|
assert layer.geo_transform == (area.left, 1.0, 0.0, area.top, 0.0, -1.0)
|
|
48
47
|
assert layer.window == Window(0, 0, 20, 10)
|
|
49
|
-
assert layer.
|
|
50
|
-
assert layer.map_projection.name == WGS_84_PROJECTION
|
|
48
|
+
assert layer.map_projection.epsg == 4326
|
|
51
49
|
|
|
52
50
|
def test_basic_dynamic_vector_layer_no_filter_match() -> None:
|
|
53
51
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
@@ -654,8 +654,8 @@ class LayerOperation(LayerMathMixin):
|
|
|
654
654
|
for yoffset in range(0, computation_window.ysize, self.ystep):
|
|
655
655
|
if callback:
|
|
656
656
|
callback(yoffset / computation_window.ysize)
|
|
657
|
-
step=self.ystep
|
|
658
|
-
if yoffset+step > computation_window.ysize:
|
|
657
|
+
step = self.ystep
|
|
658
|
+
if yoffset + step > computation_window.ysize:
|
|
659
659
|
step = computation_window.ysize - yoffset
|
|
660
660
|
chunk = self._eval(computation_area, projection, yoffset, step, computation_window)
|
|
661
661
|
if isinstance(chunk, (float, int)):
|
|
@@ -935,6 +935,48 @@ class LayerOperation(LayerMathMixin):
|
|
|
935
935
|
|
|
936
936
|
return result
|
|
937
937
|
|
|
938
|
+
def read_array(self, x: int, y: int, width: int, height: int) -> np.ndarray:
|
|
939
|
+
"""Read an area of pixles from the specified area of a calculated raster.
|
|
940
|
+
|
|
941
|
+
Args:
|
|
942
|
+
x: X axis offset for reading
|
|
943
|
+
y: Y axis offset for reading
|
|
944
|
+
width: Width of data to read
|
|
945
|
+
height: Height of data to read
|
|
946
|
+
|
|
947
|
+
Returns:
|
|
948
|
+
A numpy array containing the requested data. If the region of data read goes
|
|
949
|
+
beyond the bounds of the calculation that area will be filled with zeros.
|
|
950
|
+
"""
|
|
951
|
+
projection = self.map_projection
|
|
952
|
+
if projection is None:
|
|
953
|
+
raise ValueError("No map projection specified for layers in expression")
|
|
954
|
+
|
|
955
|
+
computation_window = Window(0, 0, width, height)
|
|
956
|
+
expression_area = self.area
|
|
957
|
+
pixel_scale = projection.scale
|
|
958
|
+
left = expression_area.left + (x * pixel_scale.xstep)
|
|
959
|
+
top = expression_area.top + (y * pixel_scale.ystep)
|
|
960
|
+
computation_area = Area(
|
|
961
|
+
left=left,
|
|
962
|
+
top=top,
|
|
963
|
+
right=left + (width * pixel_scale.xstep),
|
|
964
|
+
bottom=top + (height * pixel_scale.ystep),
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
chunks = []
|
|
968
|
+
for yoffset in range(0, height, self.ystep):
|
|
969
|
+
step = self.ystep
|
|
970
|
+
if yoffset + step > height:
|
|
971
|
+
step = height - yoffset
|
|
972
|
+
chunk = self._eval(computation_area, projection, yoffset, step, computation_window)
|
|
973
|
+
if isinstance(chunk, (float, int)):
|
|
974
|
+
chunk = backend.full((step, computation_window.xsize), chunk)
|
|
975
|
+
chunks.append(chunk)
|
|
976
|
+
res = np.vstack(chunks)
|
|
977
|
+
|
|
978
|
+
return res
|
|
979
|
+
|
|
938
980
|
class ShaderStyleOperation(LayerOperation):
|
|
939
981
|
|
|
940
982
|
def _eval(self, area, projection, index, step, target_window=None):
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import pyproj
|
|
2
|
+
|
|
3
|
+
YSTEP = 512
|
|
4
|
+
MINIMUM_CHUNKS_PER_THREAD = 1
|
|
5
|
+
|
|
6
|
+
# I don't really want this here, but it's just too useful having it exposed
|
|
7
|
+
# This used to be a fixed string, but now it is at least programmatically generated
|
|
8
|
+
WGS_84_PROJECTION = pyproj.CRS.from_epsg(4326).to_wkt(version='WKT1_GDAL')
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
from typing import Any, Sequence
|
|
3
3
|
|
|
4
4
|
import deprecation
|
|
5
|
+
from pyproj import Transformer
|
|
5
6
|
|
|
6
7
|
from .. import __version__
|
|
7
8
|
from .._operators import LayerMathMixin
|
|
@@ -332,21 +333,47 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
332
333
|
res = self._read_array(x, y, width, height)
|
|
333
334
|
return backend.demote_array(res)
|
|
334
335
|
|
|
335
|
-
def latlng_for_pixel(self,
|
|
336
|
-
"""Get geo coords for pixel. This is relative to the set view window.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
336
|
+
def latlng_for_pixel(self, x: int, y: int) -> tuple[float, float]:
|
|
337
|
+
"""Get geo coords for pixel. This is relative to the set view window.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
x: X axis position within raster
|
|
341
|
+
y: Y axis position within raster
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
A tuple containing the (latitude, longitude).
|
|
345
|
+
"""
|
|
346
|
+
projection = self.map_projection
|
|
347
|
+
if projection is None:
|
|
348
|
+
raise ValueError("Map has not projection space")
|
|
349
|
+
pixel_scale = projection.scale
|
|
350
|
+
coord_in_raster_space = (
|
|
351
|
+
(y * pixel_scale.ystep) + self.area.top,
|
|
352
|
+
(x * pixel_scale.xstep) + self.area.left,
|
|
342
353
|
)
|
|
354
|
+
transformer = Transformer.from_crs(projection.name, "EPSG:4326")
|
|
355
|
+
return transformer.transform(*coord_in_raster_space)
|
|
343
356
|
|
|
344
357
|
def pixel_for_latlng(self, lat: float, lng: float) -> tuple[int, int]:
|
|
345
358
|
"""Get pixel for geo coords. This is relative to the set view window.
|
|
346
|
-
Result is rounded down to nearest pixel.
|
|
347
|
-
|
|
348
|
-
|
|
359
|
+
Result is rounded down to nearest pixel.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
lat: Geospatial latitude in WGS84
|
|
363
|
+
lng: Geospatial longitude in WGS84
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
A tuple containing the x, y coordinates in pixel space.
|
|
367
|
+
"""
|
|
368
|
+
projection = self.map_projection
|
|
369
|
+
if projection is None:
|
|
370
|
+
raise ValueError("Map has not projection space")
|
|
371
|
+
|
|
372
|
+
transformer = Transformer.from_crs("EPSG:4326", projection.name)
|
|
373
|
+
x, y = transformer.transform(lng,lat)
|
|
374
|
+
|
|
375
|
+
pixel_scale = projection.scale
|
|
349
376
|
return (
|
|
350
|
-
round_down_pixels((
|
|
351
|
-
round_down_pixels((
|
|
377
|
+
round_down_pixels((x - self.area.left) / pixel_scale.xstep, abs(pixel_scale.xstep)),
|
|
378
|
+
round_down_pixels((y - self.area.top) / pixel_scale.ystep, abs(pixel_scale.ystep)),
|
|
352
379
|
)
|
|
@@ -4,37 +4,50 @@ import sys
|
|
|
4
4
|
from collections import namedtuple
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
|
|
7
|
+
import pyproj
|
|
8
|
+
|
|
7
9
|
PixelScale = namedtuple('PixelScale', ['xstep', 'ystep'])
|
|
8
10
|
|
|
9
|
-
@dataclass
|
|
10
11
|
class MapProjection:
|
|
11
12
|
"""Records the map projection and the size of the pixels in a layer.
|
|
12
13
|
|
|
13
14
|
This superceeeds the old PixelScale class, which will be removed in version 2.0.
|
|
14
15
|
|
|
15
16
|
Args:
|
|
16
|
-
name: The map projection used.
|
|
17
|
+
name: The map projection used in WKT format, or as "epsg:xxxx" or "esri:xxxx".
|
|
17
18
|
xstep: The number of units horizontal distance a step of one pixel makes in the map projection.
|
|
18
19
|
ystep: The number of units vertical distance a step of one pixel makes in the map projection.
|
|
19
20
|
|
|
20
21
|
Attributes:
|
|
21
|
-
name: The map projection used.
|
|
22
|
+
name: The map projection used in WKT format.
|
|
22
23
|
xstep: The number of units horizontal distance a step of one pixel makes in the map projection.
|
|
23
24
|
ystep: The number of units vertical distance a step of one pixel makes in the map projection.
|
|
24
25
|
"""
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
def __init__(self, projection_string: str, xstep: float, ystep: float) -> None:
|
|
28
|
+
try:
|
|
29
|
+
self.crs = pyproj.CRS.from_string(projection_string)
|
|
30
|
+
except pyproj.exceptions.CRSError as exc:
|
|
31
|
+
raise ValueError(f"Invalid projection: {projection_string}") from exc
|
|
32
|
+
self.xstep = xstep
|
|
33
|
+
self.ystep = ystep
|
|
29
34
|
|
|
30
35
|
def __eq__(self, other) -> bool:
|
|
31
36
|
if other is None:
|
|
32
37
|
return True
|
|
33
38
|
# to avoid circular dependancies
|
|
34
39
|
from .rounding import are_pixel_scales_equal_enough # pylint: disable=C0415
|
|
35
|
-
return (self.
|
|
40
|
+
return (self.crs == other.crs) and \
|
|
36
41
|
are_pixel_scales_equal_enough([self.scale, other.scale])
|
|
37
42
|
|
|
43
|
+
@property
|
|
44
|
+
def name(self) -> str:
|
|
45
|
+
return self.crs.to_wkt()
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def epsg(self) -> int | None:
|
|
49
|
+
return self.crs.to_epsg()
|
|
50
|
+
|
|
38
51
|
@property
|
|
39
52
|
def scale(self) -> PixelScale:
|
|
40
53
|
return PixelScale(self.xstep, self.ystep)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: yirgacheffe
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
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
|
|
@@ -27,6 +27,7 @@ Requires-Dist: dill
|
|
|
27
27
|
Requires-Dist: deprecation
|
|
28
28
|
Requires-Dist: tomli
|
|
29
29
|
Requires-Dist: h3
|
|
30
|
+
Requires-Dist: pyproj
|
|
30
31
|
Provides-Extra: mlx
|
|
31
32
|
Requires-Dist: mlx; extra == "mlx"
|
|
32
33
|
Provides-Extra: dev
|
|
@@ -44,6 +45,11 @@ Dynamic: license-file
|
|
|
44
45
|
|
|
45
46
|
# Yirgacheffe: a declarative geospatial library for Python to make data-science with maps easier
|
|
46
47
|
|
|
48
|
+
[](https://github.com/quantifyearth/yirgacheffe/actions)
|
|
49
|
+
[](https://yirgacheffe.org)
|
|
50
|
+
[](https://pypi.org/project/yirgacheffe/)
|
|
51
|
+
|
|
52
|
+
|
|
47
53
|
## Overview
|
|
48
54
|
|
|
49
55
|
Yirgacheffe is an attempt to wrap raster and polygon geospatial datasets such that you can do computational work on them as a whole or at the pixel level, but without having to do a lot of the grunt work of working out where you need to be in rasters, or managing how much you can load into memory safely.
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
|
-
from yirgacheffe.window import MapProjection
|
|
4
|
-
from yirgacheffe.rounding import MINIMAL_DEGREE_OF_INTEREST
|
|
5
|
-
|
|
6
|
-
def test_scale_from_projection() -> None:
|
|
7
|
-
projection = MapProjection("PROJ", 0.1, -0.1)
|
|
8
|
-
assert projection.name == "PROJ"
|
|
9
|
-
assert projection.xstep == 0.1
|
|
10
|
-
assert projection.ystep == -0.1
|
|
11
|
-
|
|
12
|
-
scale = projection.scale
|
|
13
|
-
assert scale.xstep == 0.1
|
|
14
|
-
assert scale.ystep == -0.1
|
|
15
|
-
|
|
16
|
-
@pytest.mark.parametrize(
|
|
17
|
-
"lhs,rhs,is_equal",
|
|
18
|
-
[
|
|
19
|
-
(MapProjection("A", 0.1, -0.1), MapProjection("A", 0.1, -0.1), True),
|
|
20
|
-
(MapProjection("A", 0.1, -0.1), MapProjection("B", 0.1, -0.1), False),
|
|
21
|
-
(MapProjection("A", 0.1, -0.1), MapProjection("A", 0.1, 0.1), False),
|
|
22
|
-
(MapProjection("A", 0.1, -0.1), MapProjection("A", -0.1, 0.1), False),
|
|
23
|
-
(MapProjection("A", 0.1, -0.1), MapProjection("A", 0.1 + (MINIMAL_DEGREE_OF_INTEREST / 2), -0.1), True),
|
|
24
|
-
(MapProjection("A", 0.1, -0.1), MapProjection("A", 0.1 - (MINIMAL_DEGREE_OF_INTEREST / 2), -0.1), True),
|
|
25
|
-
(MapProjection("A", 0.1, -0.1), MapProjection("A", 0.1, -0.1 + (MINIMAL_DEGREE_OF_INTEREST / 2)), True),
|
|
26
|
-
(MapProjection("A", 0.1, -0.1), MapProjection("A", 0.1, -0.1 - (MINIMAL_DEGREE_OF_INTEREST / 2)), True),
|
|
27
|
-
]
|
|
28
|
-
)
|
|
29
|
-
def test_projection_equality(lhs: MapProjection, rhs : MapProjection, is_equal: bool) -> None:
|
|
30
|
-
assert MINIMAL_DEGREE_OF_INTEREST > 0.0
|
|
31
|
-
assert (lhs == rhs) == is_equal
|
|
32
|
-
assert (lhs != rhs) == (not is_equal)
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
YSTEP = 512
|
|
2
|
-
MINIMUM_CHUNKS_PER_THREAD = 1
|
|
3
|
-
|
|
4
|
-
# I don't really want this here, but it's just too useful having it exposed
|
|
5
|
-
WGS_84_PROJECTION = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,'\
|
|
6
|
-
'AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],'\
|
|
7
|
-
'UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],'\
|
|
8
|
-
'AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|