yirgacheffe 1.6.1__tar.gz → 1.7.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.

Potentially problematic release.


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

Files changed (57) hide show
  1. {yirgacheffe-1.6.1/yirgacheffe.egg-info → yirgacheffe-1.7.1}/PKG-INFO +11 -9
  2. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/README.md +8 -8
  3. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/pyproject.toml +7 -1
  4. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_base.py +7 -13
  5. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_h3layer.py +32 -18
  6. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_intersection.py +5 -5
  7. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_multiband.py +1 -1
  8. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_openers.py +55 -5
  9. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_operators.py +60 -2
  10. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_optimisation.py +26 -24
  11. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_pickle.py +4 -5
  12. yirgacheffe-1.7.1/tests/test_projection.py +32 -0
  13. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_raster.py +13 -4
  14. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_rescaling.py +26 -21
  15. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_vectors.py +5 -1
  16. yirgacheffe-1.7.1/yirgacheffe/__init__.py +18 -0
  17. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/_core.py +12 -14
  18. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/layers/area.py +4 -4
  19. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/layers/base.py +105 -63
  20. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/layers/constant.py +3 -3
  21. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/layers/group.py +13 -15
  22. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/layers/h3layer.py +17 -17
  23. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/layers/rasters.py +14 -15
  24. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/layers/rescaled.py +12 -9
  25. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/layers/vectors.py +130 -45
  26. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/operators.py +152 -39
  27. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/window.py +41 -0
  28. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1/yirgacheffe.egg-info}/PKG-INFO +11 -9
  29. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe.egg-info/SOURCES.txt +1 -0
  30. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe.egg-info/requires.txt +2 -0
  31. yirgacheffe-1.6.1/yirgacheffe/__init__.py +0 -11
  32. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/LICENSE +0 -0
  33. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/MANIFEST.in +0 -0
  34. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/setup.cfg +0 -0
  35. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_area.py +0 -0
  36. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_auto_windowing.py +0 -0
  37. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_constants.py +0 -0
  38. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_datatypes.py +0 -0
  39. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_group.py +0 -0
  40. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_nodata.py +0 -0
  41. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_parallel_operators.py +0 -0
  42. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_rounding.py +0 -0
  43. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_save_with_window.py +0 -0
  44. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_sum_with_window.py +0 -0
  45. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_uniform_area_layer.py +0 -0
  46. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_union.py +0 -0
  47. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/tests/test_window.py +0 -0
  48. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/_backends/__init__.py +0 -0
  49. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/_backends/enumeration.py +0 -0
  50. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/_backends/mlx.py +0 -0
  51. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/_backends/numpy.py +0 -0
  52. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/constants.py +0 -0
  53. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/layers/__init__.py +0 -0
  54. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe/rounding.py +0 -0
  55. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe.egg-info/dependency_links.txt +0 -0
  56. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/yirgacheffe.egg-info/entry_points.txt +0 -0
  57. {yirgacheffe-1.6.1 → yirgacheffe-1.7.1}/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.6.1
3
+ Version: 1.7.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
@@ -14,6 +14,8 @@ Requires-Dist: gdal[numpy]
14
14
  Requires-Dist: scikit-image
15
15
  Requires-Dist: torch
16
16
  Requires-Dist: dill
17
+ Requires-Dist: deprecation
18
+ Requires-Dist: tomli
17
19
  Provides-Extra: dev
18
20
  Requires-Dist: mypy; extra == "dev"
19
21
  Requires-Dist: pylint; extra == "dev"
@@ -57,7 +59,7 @@ import yirgaceffe as yg
57
59
 
58
60
  habitat_map = yg.read_raster("habitats.tif")
59
61
  elevation_map = yg.read_raster('elevation.tif')
60
- range_polygon = yg.read_shape_like('species123.geojson', like=habitat_map)
62
+ range_polygon = yg.read_shape('species123.geojson')
61
63
  area_per_pixel_map = yg.read_raster('area_per_pixel.tif')
62
64
 
63
65
  refined_habitat = habitat_map.isin([...species habitat codes...])
@@ -177,22 +179,22 @@ with VectorLayer.layer_from_file('range.gpkg', PixelScale(0.001, -0.001), WGS_84
177
179
  ...
178
180
  ```
179
181
 
180
- The new 2.0 way of doing this is:
182
+ The new 2.0 way of doing this is, if you plan to use the vector layer in calculation with other raster layers that will have projection information:
181
183
 
182
184
  ```python
183
185
  import yirgacheffe as yg
184
186
 
185
- with yg.read_shape('range.gpkg', (0.001, -0.001), WGS_84_PROJECTION) as layer:
187
+ with yg.read_shape('range.gpkg') as layer:
186
188
  ...
187
189
  ```
188
190
 
189
- It is more common that when a shape file is loaded that its pixel size and projection will want to be made to match that of an existing raster (as per the opening area of habitat example). For that there is the following convenience method:
190
-
191
+ Of if you plan to use the layer on its own and want to specify a rasterisation projection you can do:
191
192
 
192
193
  ```python
193
- with yg.read_raster("test.tif") as raster_layer:
194
- with yg.read_shape_like('range.gpkg', raster_layer) as shape_layer:
195
- ...
194
+ import yirgacheffe as yg
195
+
196
+ with yg.read_shape('range.gpkg', (yg.WGS_84_PROJECTION, (0.001, -0.001))) as layer:
197
+ ...
196
198
  ```
197
199
 
198
200
  ### GroupLayer
@@ -32,7 +32,7 @@ import yirgaceffe as yg
32
32
 
33
33
  habitat_map = yg.read_raster("habitats.tif")
34
34
  elevation_map = yg.read_raster('elevation.tif')
35
- range_polygon = yg.read_shape_like('species123.geojson', like=habitat_map)
35
+ range_polygon = yg.read_shape('species123.geojson')
36
36
  area_per_pixel_map = yg.read_raster('area_per_pixel.tif')
37
37
 
38
38
  refined_habitat = habitat_map.isin([...species habitat codes...])
@@ -152,22 +152,22 @@ with VectorLayer.layer_from_file('range.gpkg', PixelScale(0.001, -0.001), WGS_84
152
152
  ...
153
153
  ```
154
154
 
155
- The new 2.0 way of doing this is:
155
+ The new 2.0 way of doing this is, if you plan to use the vector layer in calculation with other raster layers that will have projection information:
156
156
 
157
157
  ```python
158
158
  import yirgacheffe as yg
159
159
 
160
- with yg.read_shape('range.gpkg', (0.001, -0.001), WGS_84_PROJECTION) as layer:
160
+ with yg.read_shape('range.gpkg') as layer:
161
161
  ...
162
162
  ```
163
163
 
164
- It is more common that when a shape file is loaded that its pixel size and projection will want to be made to match that of an existing raster (as per the opening area of habitat example). For that there is the following convenience method:
165
-
164
+ Of if you plan to use the layer on its own and want to specify a rasterisation projection you can do:
166
165
 
167
166
  ```python
168
- with yg.read_raster("test.tif") as raster_layer:
169
- with yg.read_shape_like('range.gpkg', raster_layer) as shape_layer:
170
- ...
167
+ import yirgacheffe as yg
168
+
169
+ with yg.read_shape('range.gpkg', (yg.WGS_84_PROJECTION, (0.001, -0.001))) as layer:
170
+ ...
171
171
  ```
172
172
 
173
173
  ### GroupLayer
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "yirgacheffe"
9
- version = "1.6.1"
9
+ version = "1.7.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" }]
@@ -18,6 +18,8 @@ dependencies = [
18
18
  "scikit-image",
19
19
  "torch",
20
20
  "dill",
21
+ "deprecation",
22
+ "tomli"
21
23
  ]
22
24
  requires-python = ">=3.10"
23
25
 
@@ -37,3 +39,7 @@ ignore_missing_imports = true
37
39
  [[tool.mypy.overrides]]
38
40
  module = "h3.*"
39
41
  ignore_missing_imports = true
42
+
43
+ [[tool.mypy.overrides]]
44
+ module = "deprecation.*"
45
+ ignore_missing_imports = true
@@ -5,13 +5,12 @@ import pytest
5
5
 
6
6
  from yirgacheffe import WGS_84_PROJECTION
7
7
  from yirgacheffe.layers import YirgacheffeLayer
8
- from yirgacheffe.window import Area, PixelScale
8
+ from yirgacheffe.window import Area, MapProjection
9
9
 
10
10
  def test_pixel_to_latlng_unsupported_projection() -> None:
11
11
  layer = YirgacheffeLayer(
12
12
  Area(-10, 10, 10, -10),
13
- PixelScale(0.02, -0.02),
14
- "OTHER PROJECTION"
13
+ MapProjection("OTHER PROJECTION", 0.02, -0.02),
15
14
  )
16
15
  with pytest.raises(NotImplementedError):
17
16
  _ = layer.latlng_for_pixel(10, 10)
@@ -19,8 +18,7 @@ def test_pixel_to_latlng_unsupported_projection() -> None:
19
18
  def test_pixel_from_latlng_unsupported_projection() -> None:
20
19
  layer = YirgacheffeLayer(
21
20
  Area(-10, 10, 10, -10),
22
- PixelScale(0.02, -0.02),
23
- "OTHER PROJECTION"
21
+ MapProjection("OTHER PROJECTION", 0.02, -0.02),
24
22
  )
25
23
  with pytest.raises(NotImplementedError):
26
24
  _ = layer.pixel_for_latlng(10.0, 10.0)
@@ -63,8 +61,7 @@ def test_pixel_from_latlng_unsupported_projection() -> None:
63
61
  def test_latlng_for_pixel(area: Area, pixel: Tuple[int,int], expected: Tuple[float,float]) -> None:
64
62
  layer = YirgacheffeLayer(
65
63
  area,
66
- PixelScale(0.2, -0.2),
67
- WGS_84_PROJECTION
64
+ MapProjection(WGS_84_PROJECTION, 0.2, -0.2),
68
65
  )
69
66
  result = layer.latlng_for_pixel(*pixel)
70
67
  assert math.isclose(result[0], expected[0])
@@ -93,8 +90,7 @@ def test_latlng_for_pixel(area: Area, pixel: Tuple[int,int], expected: Tuple[flo
93
90
  def test_pixel_for_latlng(area: Area, coord: Tuple[float,float], expected: Tuple[int,int]) -> None:
94
91
  layer = YirgacheffeLayer(
95
92
  area,
96
- PixelScale(0.2, -0.2),
97
- WGS_84_PROJECTION
93
+ MapProjection(WGS_84_PROJECTION, 0.2, -0.2),
98
94
  )
99
95
  result = layer.pixel_for_latlng(*coord)
100
96
  assert result == expected
@@ -149,8 +145,7 @@ def test_latlng_for_pixel_with_intersection(
149
145
  ) -> None:
150
146
  layer = YirgacheffeLayer(
151
147
  area,
152
- PixelScale(0.2, -0.2),
153
- WGS_84_PROJECTION
148
+ MapProjection(WGS_84_PROJECTION, 0.2, -0.2),
154
149
  )
155
150
  layer.set_window_for_intersection(window)
156
151
  result = layer.latlng_for_pixel(*pixel)
@@ -188,8 +183,7 @@ def test_pixel_for_latlng_with_intersection(
188
183
  ) -> None:
189
184
  layer = YirgacheffeLayer(
190
185
  area,
191
- PixelScale(0.2, -0.2),
192
- WGS_84_PROJECTION
186
+ MapProjection(WGS_84_PROJECTION, 0.2, -0.2),
193
187
  )
194
188
  layer.set_window_for_intersection(window)
195
189
  result = layer.pixel_for_latlng(*coord)
@@ -5,7 +5,7 @@ from osgeo import gdal
5
5
 
6
6
  from yirgacheffe import WGS_84_PROJECTION
7
7
  from yirgacheffe.layers import RasterLayer, H3CellLayer
8
- from yirgacheffe.window import Area, PixelScale
8
+ from yirgacheffe.window import Area, MapProjection
9
9
  from yirgacheffe._backends import backend
10
10
 
11
11
  # work around of pylint
@@ -21,7 +21,7 @@ demote_array = backend.demote_array
21
21
  )
22
22
  def test_h3_layer(cell_id: str, is_valid: bool, expected_zoom: int) -> None:
23
23
  if is_valid:
24
- with H3CellLayer(cell_id, PixelScale(0.001, -0.001), WGS_84_PROJECTION) as layer:
24
+ with H3CellLayer(cell_id, MapProjection(WGS_84_PROJECTION, 0.001, -0.001)) as layer:
25
25
  assert layer.zoom == expected_zoom
26
26
  assert layer.projection == WGS_84_PROJECTION
27
27
 
@@ -35,7 +35,7 @@ def test_h3_layer(cell_id: str, is_valid: bool, expected_zoom: int) -> None:
35
35
  assert one_count != 0
36
36
  else:
37
37
  with pytest.raises(ValueError):
38
- with H3CellLayer(cell_id, PixelScale(0.001, -0.001), WGS_84_PROJECTION) as _layer:
38
+ with H3CellLayer(cell_id, MapProjection(WGS_84_PROJECTION, 0.001, -0.001)) as _layer:
39
39
  pass
40
40
 
41
41
  @pytest.mark.parametrize(
@@ -53,8 +53,10 @@ def test_h3_layer(cell_id: str, is_valid: bool, expected_zoom: int) -> None:
53
53
  def test_h3_layer_magnifications(lat: float, lng: float) -> None:
54
54
  for zoom in range(6, 10):
55
55
  cell_id = h3.latlng_to_cell(lat, lng, zoom)
56
- h3_layer = H3CellLayer(cell_id, PixelScale(0.000898315284120,-0.000898315284120), WGS_84_PROJECTION)
57
-
56
+ h3_layer = H3CellLayer(
57
+ cell_id,
58
+ MapProjection(WGS_84_PROJECTION, 0.000898315284120,-0.000898315284120)
59
+ )
58
60
  on_cell_count = h3_layer.sum()
59
61
  total_count = h3_layer.window.xsize * h3_layer.window.ysize
60
62
  assert 0 < on_cell_count < total_count
@@ -74,14 +76,18 @@ def test_h3_layer_magnifications(lat: float, lng: float) -> None:
74
76
  def test_h3_layer_not_clipped(lat: float, lng: float) -> None:
75
77
  for zoom in range(6, 10):
76
78
  cell_id = h3.latlng_to_cell(lat, lng, zoom)
77
- scale = PixelScale(0.000898315284120,-0.000898315284120)
78
- h3_layer = H3CellLayer(cell_id, scale, WGS_84_PROJECTION)
79
+ projection = MapProjection(
80
+ WGS_84_PROJECTION,
81
+ 0.000898315284120,
82
+ -0.000898315284120
83
+ )
84
+ h3_layer = H3CellLayer(cell_id, projection)
79
85
 
80
86
  on_cell_count = h3_layer.sum()
81
87
  assert on_cell_count > 0.0
82
88
 
83
89
  before_window = h3_layer.window
84
- abs_xstep, abs_ystep = abs(scale.xstep), abs(scale.ystep)
90
+ abs_xstep, abs_ystep = abs(projection.xstep), abs(projection.ystep)
85
91
  expanded_area = Area(
86
92
  left=h3_layer.area.left - (12 * abs_xstep),
87
93
  top=h3_layer.area.top + (12 * abs_ystep),
@@ -115,14 +121,18 @@ def test_h3_layer_not_clipped(lat: float, lng: float) -> None:
115
121
  def test_h3_layer_clipped(lat: float, lng: float) -> None:
116
122
  for zoom in range(6, 8):
117
123
  cell_id = h3.latlng_to_cell(lat, lng, zoom)
118
- scale = PixelScale(0.000898315284120,-0.000898315284120)
119
- h3_layer = H3CellLayer(cell_id, scale, WGS_84_PROJECTION)
124
+ projection = MapProjection(
125
+ WGS_84_PROJECTION,
126
+ 0.000898315284120,
127
+ -0.000898315284120
128
+ )
129
+ h3_layer = H3CellLayer(cell_id, projection)
120
130
 
121
131
  on_cell_count = h3_layer.sum()
122
132
  assert on_cell_count > 0.0
123
133
 
124
134
  before_window = h3_layer.window
125
- abs_xstep, abs_ystep = abs(scale.xstep), abs(scale.ystep)
135
+ abs_xstep, abs_ystep = abs(projection.xstep), abs(projection.ystep)
126
136
  expanded_area = Area(
127
137
  left=h3_layer.area.left + (2 * abs_xstep),
128
138
  top=h3_layer.area.top - (2 * abs_ystep),
@@ -151,8 +161,8 @@ def test_h3_layer_clipped(lat: float, lng: float) -> None:
151
161
  )
152
162
  def test_h3_layer_wrapped_on_projection(lat: float, lng: float) -> None:
153
163
  cell_id = h3.latlng_to_cell(lat, lng, 3)
154
- scale = PixelScale(0.01, -0.01)
155
- h3_layer = H3CellLayer(cell_id, scale, WGS_84_PROJECTION)
164
+ projection = MapProjection(WGS_84_PROJECTION, 0.01, -0.01)
165
+ h3_layer = H3CellLayer(cell_id, projection)
156
166
 
157
167
  # Just sanity check this test has caught a cell that wraps the entire planet and is testing
158
168
  # what we think it is testing:
@@ -165,14 +175,14 @@ def test_h3_layer_wrapped_on_projection(lat: float, lng: float) -> None:
165
175
  # check they are all of a similarish size - we had a bug early on where we'd
166
176
  # mistakenly invert the area for the band, counting all the cells across the planet
167
177
  for cell_id in h3.grid_ring(cell_id, 1):
168
- neighbour = H3CellLayer(cell_id, scale, WGS_84_PROJECTION)
178
+ neighbour = H3CellLayer(cell_id, projection)
169
179
  neighbour_area = neighbour.sum()
170
180
  # We're happy if they're within 10% for now
171
181
  assert abs((neighbour_area - area) / area) < 0.1
172
182
 
173
183
 
174
184
  before_window = h3_layer.window
175
- _, abs_ystep = abs(scale.xstep), abs(scale.ystep)
185
+ _, abs_ystep = abs(projection.xstep), abs(projection.ystep)
176
186
  expanded_area = Area(
177
187
  left=h3_layer.area.left,
178
188
  top=h3_layer.area.top + (22 * abs_ystep),
@@ -198,7 +208,11 @@ def test_h3_layer_overlapped():
198
208
  # This is based on a regression, where somehow I made tiles not tesselate properly
199
209
  left, top = (121.26706, 19.45338)
200
210
  right, bottom = (121.62494, 19.18478)
201
- scale = PixelScale(0.000898315284120,-0.000898315284120)
211
+ projection = MapProjection(
212
+ WGS_84_PROJECTION,
213
+ 0.000898315284120,
214
+ -0.000898315284120
215
+ )
202
216
 
203
217
  cells = h3.geo_to_cells(h3.LatLngPoly([
204
218
  (top, left),
@@ -208,13 +222,13 @@ def test_h3_layer_overlapped():
208
222
  ],), 7)
209
223
 
210
224
  tiles = [
211
- H3CellLayer(cell_id, scale, WGS_84_PROJECTION)
225
+ H3CellLayer(cell_id, projection)
212
226
  for cell_id in cells]
213
227
 
214
228
  union = RasterLayer.find_union(tiles)
215
229
  union = union.grow(0.02)
216
230
 
217
- scratch = RasterLayer.empty_raster_layer(union, scale, gdal.GDT_Float64)
231
+ scratch = RasterLayer.empty_raster_layer(union, projection.scale, gdal.GDT_Float64)
218
232
 
219
233
  # In scratch we should only have 0 or 1 values, but if there are any overlaps we should get 2s...
220
234
 
@@ -2,7 +2,7 @@ import pytest
2
2
  from osgeo import gdal
3
3
 
4
4
  from tests.helpers import gdal_dataset_of_region, gdal_empty_dataset_of_region
5
- from yirgacheffe.window import Area, PixelScale, Window
5
+ from yirgacheffe.window import Area, MapProjection, Window
6
6
  from yirgacheffe.layers import RasterLayer, ConstantLayer, H3CellLayer
7
7
  from yirgacheffe import WGS_84_PROJECTION
8
8
 
@@ -149,10 +149,10 @@ def test_intersection_stability():
149
149
  # a rounding error that causes set_window_for_* methods to wobble depending on how far
150
150
  # away from the top left thing where. adding round_down_pixels fixed this.
151
151
  cells = ["874b93aaeffffff", "874b93a85ffffff", "874b93aa3ffffff", "874b93a84ffffff", "874b93a80ffffff"]
152
- scale = PixelScale(0.000898315284120,-0.000898315284120)
152
+ projection = MapProjection(WGS_84_PROJECTION, 0.000898315284120,-0.000898315284120)
153
153
 
154
154
  tiles = [
155
- H3CellLayer(cell_id, scale, WGS_84_PROJECTION)
155
+ H3CellLayer(cell_id, projection)
156
156
  for cell_id in cells]
157
157
 
158
158
  # composing the same tiles within different areas should not cause them to
@@ -160,8 +160,8 @@ def test_intersection_stability():
160
160
  union = RasterLayer.find_union(tiles)
161
161
  superunion = union.grow(0.02)
162
162
 
163
- scratch1 = RasterLayer.empty_raster_layer(union, scale, gdal.GDT_Float64, name='s1')
164
- scratch2 = RasterLayer.empty_raster_layer(superunion, scale, gdal.GDT_Float64, name='s2')
163
+ scratch1 = RasterLayer.empty_raster_layer(union, projection.scale, gdal.GDT_Float64, name='s1')
164
+ scratch2 = RasterLayer.empty_raster_layer(superunion, projection.scale, gdal.GDT_Float64, name='s2')
165
165
 
166
166
  relative_offsets = {}
167
167
 
@@ -16,7 +16,7 @@ def test_simple_two_band_image() -> None:
16
16
  bands = 4
17
17
  target = RasterLayer.empty_raster_layer(
18
18
  Area(-1, 1, 1, -1),
19
- PixelScale(1.0, 1.0),
19
+ PixelScale(1.0, -1.0),
20
20
  gdal.GDT_Byte,
21
21
  filename=target_path,
22
22
  bands=bands
@@ -8,7 +8,7 @@ import pytest
8
8
  import yirgacheffe as yg
9
9
  from yirgacheffe import WGS_84_PROJECTION
10
10
  from yirgacheffe.layers import InvalidRasterBand
11
- from yirgacheffe.window import Area, PixelScale, Window
11
+ from yirgacheffe.window import Area, MapProjection, Window
12
12
  from tests.helpers import gdal_dataset_of_region, gdal_multiband_dataset_with_data, \
13
13
  make_vectors_with_id, make_vectors_with_mutlile_ids
14
14
 
@@ -75,7 +75,7 @@ def test_open_multiband_raster() -> None:
75
75
 
76
76
  def test_shape_from_nonexistent_file() -> None:
77
77
  with pytest.raises(FileNotFoundError):
78
- _ = yg.read_shape("this_file_does_not_exist.gpkg", (1.0, -1.0), WGS_84_PROJECTION)
78
+ _ = yg.read_shape("this_file_does_not_exist.gpkg", (WGS_84_PROJECTION, (1.0, -1.0)))
79
79
 
80
80
  def test_open_gpkg() -> None:
81
81
  with tempfile.TemporaryDirectory() as tempdir:
@@ -83,19 +83,47 @@ def test_open_gpkg() -> None:
83
83
  area = Area(-10.0, 10.0, 10.0, 0.0)
84
84
  make_vectors_with_id(42, {area}, path)
85
85
 
86
- with yg.read_shape(path, PixelScale(1.0, -1.0), WGS_84_PROJECTION) as layer:
86
+ with yg.read_shape(path, (WGS_84_PROJECTION, (1.0, -1.0))) as layer:
87
87
  assert layer.area == area
88
88
  assert layer.geo_transform == (area.left, 1.0, 0.0, area.top, 0.0, -1.0)
89
89
  assert layer.window == Window(0, 0, 20, 10)
90
+ assert layer.map_projection == MapProjection(WGS_84_PROJECTION, 1.0, -1.0)
90
91
  assert layer.projection == WGS_84_PROJECTION
91
92
 
93
+ def test_open_gpkg_with_mapprojection() -> None:
94
+ with tempfile.TemporaryDirectory() as tempdir:
95
+ path = os.path.join(tempdir, "test.gpkg")
96
+ area = Area(-10.0, 10.0, 10.0, 0.0)
97
+ make_vectors_with_id(42, {area}, path)
98
+
99
+ with yg.read_shape(path, MapProjection(WGS_84_PROJECTION, 1.0, -1.0)) as layer:
100
+ assert layer.area == area
101
+ assert layer.geo_transform == (area.left, 1.0, 0.0, area.top, 0.0, -1.0)
102
+ assert layer.window == Window(0, 0, 20, 10)
103
+ assert layer.map_projection == MapProjection(WGS_84_PROJECTION, 1.0, -1.0)
104
+ assert layer.projection == WGS_84_PROJECTION
105
+
106
+ def test_open_gpkg_with_no_projection() -> None:
107
+ with tempfile.TemporaryDirectory() as tempdir:
108
+ path = os.path.join(tempdir, "test.gpkg")
109
+ area = Area(-10.0, 10.0, 10.0, 0.0)
110
+ make_vectors_with_id(42, {area}, path)
111
+
112
+ with yg.read_shape(path) as layer:
113
+ assert layer.area == area
114
+ assert layer.projection is None
115
+ with pytest.raises(AttributeError):
116
+ _ = layer.geo_transform
117
+ with pytest.raises(AttributeError):
118
+ _ = layer.window
119
+
92
120
  def test_open_gpkg_direct_scale() -> None:
93
121
  with tempfile.TemporaryDirectory() as tempdir:
94
122
  path = Path(tempdir) / "test.gpkg"
95
123
  area = Area(-10.0, 10.0, 10.0, 0.0)
96
124
  make_vectors_with_id(42, {area}, path)
97
125
 
98
- with yg.read_shape(path, (1.0, -1.0), WGS_84_PROJECTION) as layer:
126
+ with yg.read_shape(path, (WGS_84_PROJECTION, (1.0, -1.0))) as layer:
99
127
  assert layer.area == area
100
128
  assert layer.geo_transform == (area.left, 1.0, 0.0, area.top, 0.0, -1.0)
101
129
  assert layer.window == Window(0, 0, 20, 10)
@@ -110,7 +138,7 @@ def test_open_gpkg_with_filter() -> None:
110
138
  }
111
139
  make_vectors_with_mutlile_ids(areas, path)
112
140
 
113
- with yg.read_shape(path, (1.0, -1.0), WGS_84_PROJECTION, "id_no=42") as layer:
141
+ with yg.read_shape(path, (WGS_84_PROJECTION, (1.0, -1.0)), "id_no=42") as layer:
114
142
  assert layer.area == Area(-10.0, 10.0, 0.0, 0.0)
115
143
  assert layer.geo_transform == (-10.0, 1.0, 0.0, 10.0, 0.0, -1.0)
116
144
  assert layer.window == Window(0, 0, 10, 10)
@@ -163,3 +191,25 @@ def test_open_two_raster_areas_side_by_side(tiled):
163
191
  with yg.read_raster(path1) as raster1:
164
192
  with yg.read_raster(path2) as raster2:
165
193
  assert group.sum() == raster1.sum() + raster2.sum()
194
+
195
+ @pytest.mark.parametrize("tiled", [False, True])
196
+ def test_open_two_raster_by_glob(tiled):
197
+ with tempfile.TemporaryDirectory() as tempdir:
198
+ temppath = Path(tempdir)
199
+ path1 = temppath / "test1.tif"
200
+ area1 = Area(-10, 10, 10, -10)
201
+ dataset1 = gdal_dataset_of_region(area1, 0.2, filename=path1)
202
+ dataset1.Close()
203
+
204
+ path2 = temppath / "test2.tif"
205
+ area2 = Area(10, 10, 30, -10)
206
+ dataset2 = gdal_dataset_of_region(area2, 0.2, filename=path2)
207
+ dataset2.Close()
208
+
209
+ with yg.read_rasters(temppath.glob("*.tif"), tiled=tiled) as group:
210
+ assert group.area == Area(-10, 10, 30, -10)
211
+ assert group.window == Window(0, 0, 200, 100)
212
+
213
+ with yg.read_raster(path1) as raster1:
214
+ with yg.read_raster(path2) as raster2:
215
+ assert group.sum() == raster1.sum() + raster2.sum()
@@ -1,16 +1,18 @@
1
1
  import os
2
2
  import random
3
3
  import tempfile
4
+ from pathlib import Path
4
5
 
5
6
  import numpy as np
6
7
  import pytest
7
8
  import torch
8
9
 
9
10
  import yirgacheffe
10
- from yirgacheffe.layers import RasterLayer, ConstantLayer
11
+ from yirgacheffe.window import Area, PixelScale
12
+ from yirgacheffe.layers import ConstantLayer, RasterLayer, VectorLayer
11
13
  from yirgacheffe.operators import LayerOperation, DataType
12
14
  from yirgacheffe._backends import backend
13
- from tests.helpers import gdal_dataset_with_data
15
+ from tests.helpers import gdal_dataset_with_data, gdal_dataset_of_region, make_vectors_with_id
14
16
 
15
17
  def test_add_byte_layers() -> None:
16
18
  data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]]).astype(np.uint8)
@@ -51,6 +53,15 @@ def test_error_of_pixel_scale_wrong_three_param() -> None:
51
53
  with pytest.raises(ValueError):
52
54
  _ = LayerOperation.where(layer1, layer2, layer3)
53
55
 
56
+ def test_incompatible_source_and_destination_projections() -> None:
57
+ data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
58
+
59
+ layer1 = RasterLayer(gdal_dataset_with_data((0.0, 0.0), 0.02, data1))
60
+
61
+ with RasterLayer.empty_raster_layer(layer1.area, PixelScale(1.0, -1.0), layer1.datatype) as result:
62
+ with pytest.raises(ValueError):
63
+ layer1.save(result)
64
+
54
65
  @pytest.mark.parametrize("skip,expected_steps", [
55
66
  (1, [0.0, 0.5, 1.0]),
56
67
  (2, [0.0, 1.0]),
@@ -1497,3 +1508,50 @@ def test_to_geotiff_parallel_thread_and_sum(monkeypatch) -> None:
1497
1508
  expected = data1 * 2
1498
1509
  actual = result.read_array(0, 0, 4, 2)
1499
1510
  assert (expected == actual).all()
1511
+
1512
+ def test_raster_and_vector() -> None:
1513
+ with tempfile.TemporaryDirectory() as tempdir:
1514
+ raster = RasterLayer(gdal_dataset_of_region(Area(-10, 10, 10, -10), 1.0))
1515
+ assert raster.sum() > 0.0
1516
+
1517
+ path = Path(tempdir) / "test.gpkg"
1518
+ area = Area(-5.0, 5.0, 5.0, -5.0)
1519
+ make_vectors_with_id(42, {area}, path)
1520
+ assert path.exists
1521
+
1522
+ vector = VectorLayer.layer_from_file(path, None, PixelScale(1.0, -1.0), yirgacheffe.WGS_84_PROJECTION)
1523
+
1524
+ calc = raster * vector
1525
+ assert calc.sum() > 0.0
1526
+ assert calc.sum() < raster.sum()
1527
+
1528
+ def test_raster_and_vector_mixed_projection() -> None:
1529
+ with tempfile.TemporaryDirectory() as tempdir:
1530
+ raster = RasterLayer(gdal_dataset_of_region(Area(-10, 10, 10, -10), 1.1))
1531
+ assert raster.sum() > 0.0
1532
+
1533
+ path = Path(tempdir) / "test.gpkg"
1534
+ area = Area(-5.0, 5.0, 5.0, -5.0)
1535
+ make_vectors_with_id(42, {area}, path)
1536
+ assert path.exists
1537
+
1538
+ vector = VectorLayer.layer_from_file(path, None, PixelScale(1.0, -1.0), yirgacheffe.WGS_84_PROJECTION)
1539
+
1540
+ with pytest.raises(ValueError):
1541
+ _ = raster * vector
1542
+
1543
+ def test_raster_and_vector_no_scale_on_vector() -> None:
1544
+ with tempfile.TemporaryDirectory() as tempdir:
1545
+ raster = RasterLayer(gdal_dataset_of_region(Area(-10, 10, 10, -10), 1.0))
1546
+ assert raster.sum() > 0.0
1547
+
1548
+ path = Path(tempdir) / "test.gpkg"
1549
+ area = Area(-5.0, 5.0, 5.0, -5.0)
1550
+ make_vectors_with_id(42, {area}, path)
1551
+ assert path.exists
1552
+
1553
+ vector = VectorLayer.layer_from_file(path, None, None, None)
1554
+
1555
+ calc = raster * vector
1556
+ assert calc.sum() > 0.0
1557
+ assert calc.sum() < raster.sum()
@@ -4,8 +4,8 @@ import numpy as np
4
4
  import pytest
5
5
 
6
6
  from yirgacheffe import WGS_84_PROJECTION
7
- from yirgacheffe.layers import PixelScale, RasterLayer, H3CellLayer
8
- from yirgacheffe.window import Area
7
+ from yirgacheffe.layers import RasterLayer, H3CellLayer
8
+ from yirgacheffe.window import Area, MapProjection
9
9
  import yirgacheffe.operators as yo
10
10
 
11
11
  class NaiveH3CellLayer(H3CellLayer):
@@ -14,13 +14,14 @@ class NaiveH3CellLayer(H3CellLayer):
14
14
  version that checks for every cell."""
15
15
 
16
16
  def read_array(self, xoffset, yoffset, xsize, ysize): # pylint: disable=W0237
17
+ assert self._projection is not None
17
18
  res = np.zeros((ysize, xsize), dtype=float)
18
- start_x = self._active_area.left + (xoffset * self._pixel_scale.xstep)
19
- start_y = self._active_area.top + (yoffset * self._pixel_scale.ystep)
19
+ start_x = self.area.left + (xoffset * self._projection.xstep)
20
+ start_y = self.area.top + (yoffset * self._projection.ystep)
20
21
  for ypixel in range(ysize):
21
- lat = start_y + (ypixel * self._pixel_scale.ystep)
22
+ lat = start_y + (ypixel * self._projection.ystep)
22
23
  for xpixel in range(xsize):
23
- lng = start_x + (xpixel * self._pixel_scale.xstep)
24
+ lng = start_x + (xpixel * self._projection.xstep)
24
25
  this_cell = h3.latlng_to_cell(lat, lng, self.zoom)
25
26
  if this_cell == self.cell_id:
26
27
  res[ypixel][xpixel] = 1.0
@@ -41,8 +42,9 @@ class NaiveH3CellLayer(H3CellLayer):
41
42
  def test_h3_vs_naive(lat: float, lng: float) -> None:
42
43
  for zoom in range(5, 9):
43
44
  cell_id = h3.latlng_to_cell(lat, lng, zoom)
44
- optimised_layer = H3CellLayer(cell_id, PixelScale(0.000898315284120,-0.000898315284120), "NOTUSED")
45
- naive_layer = NaiveH3CellLayer(cell_id, PixelScale(0.000898315284120,-0.000898315284120), "NOTUSED")
45
+ projection = MapProjection(WGS_84_PROJECTION, 0.000898315284120,-0.000898315284120)
46
+ optimised_layer = H3CellLayer(cell_id, projection)
47
+ naive_layer = NaiveH3CellLayer(cell_id, projection)
46
48
 
47
49
  optimised_cell_count = optimised_layer.sum()
48
50
  naive_cell_count = naive_layer.sum()
@@ -65,17 +67,17 @@ def test_h3_vs_naive(lat: float, lng: float) -> None:
65
67
  def test_h3_vs_naive_for_union(lat: float, lng: float) -> None:
66
68
  for zoom in range(7, 9):
67
69
  cell_id = h3.latlng_to_cell(lat, lng, zoom)
68
- scale = PixelScale(0.000898315284120,-0.000898315284120)
69
- optimised_layer = H3CellLayer(cell_id, scale, "NOTUSED")
70
- naive_layer = NaiveH3CellLayer(cell_id, scale, "NOTUSED")
70
+ projection = MapProjection(WGS_84_PROJECTION, 0.000898315284120,-0.000898315284120)
71
+ optimised_layer = H3CellLayer(cell_id, projection)
72
+ naive_layer = NaiveH3CellLayer(cell_id, projection)
71
73
 
72
74
  before_cell_count = optimised_layer.sum()
73
75
 
74
76
  superset_area = Area(
75
- left=optimised_layer.area.left - (5 * scale.xstep),
76
- right=optimised_layer.area.right + (5 * scale.xstep),
77
- top=optimised_layer.area.top - (5 * scale.ystep),
78
- bottom=optimised_layer.area.bottom + (5 * scale.ystep),
77
+ left=optimised_layer.area.left - (5 * projection.xstep),
78
+ right=optimised_layer.area.right + (5 * projection.xstep),
79
+ top=optimised_layer.area.top - (5 * projection.ystep),
80
+ bottom=optimised_layer.area.bottom + (5 * projection.ystep),
79
81
  )
80
82
  optimised_layer.set_window_for_union(superset_area)
81
83
  naive_layer.set_window_for_union(superset_area)
@@ -101,17 +103,17 @@ def test_h3_vs_naive_for_union(lat: float, lng: float) -> None:
101
103
  def test_h3_vs_naive_for_intersection(lat: float, lng: float) -> None:
102
104
  for zoom in range(7, 9):
103
105
  cell_id = h3.latlng_to_cell(lat, lng, zoom)
104
- scale = PixelScale(0.000898315284120,-0.000898315284120)
105
- optimised_layer = H3CellLayer(cell_id, scale, "NOTUSED")
106
- naive_layer = NaiveH3CellLayer(cell_id, scale, "NOTUSED")
106
+ projection = MapProjection(WGS_84_PROJECTION, 0.000898315284120,-0.000898315284120)
107
+ optimised_layer = H3CellLayer(cell_id, projection)
108
+ naive_layer = NaiveH3CellLayer(cell_id, projection)
107
109
 
108
110
  before_cell_count = optimised_layer.sum()
109
111
 
110
112
  subset_area = Area(
111
- left=optimised_layer.area.left + (2 * scale.xstep),
112
- right=optimised_layer.area.right - (2 * scale.xstep),
113
- top=optimised_layer.area.top + (2 * scale.ystep),
114
- bottom=optimised_layer.area.bottom - (2 * scale.ystep),
113
+ left=optimised_layer.area.left + (2 * projection.xstep),
114
+ right=optimised_layer.area.right - (2 * projection.xstep),
115
+ top=optimised_layer.area.top + (2 * projection.ystep),
116
+ bottom=optimised_layer.area.bottom - (2 * projection.ystep),
115
117
  )
116
118
  optimised_layer.set_window_for_intersection(subset_area)
117
119
  naive_layer.set_window_for_intersection(subset_area)
@@ -143,8 +145,8 @@ def test_h3_vs_naive_for_intersection(lat: float, lng: float) -> None:
143
145
  def test_cells_dont_overlap(cell_id):
144
146
 
145
147
  cluster = h3.grid_disk(cell_id, 1)
146
- scale = PixelScale(0.000898315284120,-0.000898315284120)
147
- layers = [H3CellLayer(x, scale, WGS_84_PROJECTION) for x in cluster]
148
+ projection = MapProjection(WGS_84_PROJECTION, 0.000898315284120,-0.000898315284120)
149
+ layers = [H3CellLayer(x, projection) for x in cluster]
148
150
 
149
151
  union = RasterLayer.find_union(layers)
150
152
  for layer in layers: