yirgacheffe 1.9.4__tar.gz → 1.10.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.

Files changed (61) hide show
  1. {yirgacheffe-1.9.4/yirgacheffe.egg-info → yirgacheffe-1.10.0}/PKG-INFO +2 -1
  2. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/pyproject.toml +2 -1
  3. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_auto_windowing.py +5 -5
  4. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_constants.py +21 -2
  5. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_datatypes.py +46 -0
  6. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_multiband.py +7 -5
  7. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_openers.py +28 -2
  8. yirgacheffe-1.10.0/tests/test_operator_hashing.py +372 -0
  9. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_operators.py +150 -40
  10. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_pixel_coord.py +29 -26
  11. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_raster.py +33 -0
  12. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_reduce.py +4 -3
  13. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_vectors.py +9 -9
  14. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_window.py +10 -0
  15. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/__init__.py +1 -1
  16. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/_backends/enumeration.py +37 -1
  17. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/_backends/mlx.py +7 -0
  18. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/_backends/numpy.py +11 -0
  19. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/_core.py +50 -1
  20. yirgacheffe-1.9.4/yirgacheffe/_operators.py → yirgacheffe-1.10.0/yirgacheffe/_operators/__init__.py +257 -78
  21. yirgacheffe-1.10.0/yirgacheffe/_operators/cse.py +66 -0
  22. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/layers/base.py +6 -1
  23. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/layers/constant.py +4 -0
  24. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/layers/group.py +5 -1
  25. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/layers/h3layer.py +9 -0
  26. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/layers/rasters.py +12 -0
  27. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/layers/rescaled.py +11 -0
  28. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/layers/vectors.py +13 -2
  29. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/window.py +15 -3
  30. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0/yirgacheffe.egg-info}/PKG-INFO +2 -1
  31. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe.egg-info/SOURCES.txt +3 -1
  32. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe.egg-info/requires.txt +1 -0
  33. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/LICENSE +0 -0
  34. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/MANIFEST.in +0 -0
  35. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/README.md +0 -0
  36. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/setup.cfg +0 -0
  37. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_area.py +0 -0
  38. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_group.py +0 -0
  39. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_h3layer.py +0 -0
  40. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_intersection.py +0 -0
  41. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_nodata.py +0 -0
  42. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_optimisation.py +0 -0
  43. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_parallel_operators.py +0 -0
  44. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_pickle.py +0 -0
  45. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_projection.py +0 -0
  46. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_rescaling.py +0 -0
  47. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_rounding.py +0 -0
  48. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_save_with_window.py +0 -0
  49. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_sum_with_window.py +0 -0
  50. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_uniform_area_layer.py +0 -0
  51. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/tests/test_union.py +0 -0
  52. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/_backends/__init__.py +0 -0
  53. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/constants.py +0 -0
  54. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/layers/__init__.py +0 -0
  55. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/layers/area.py +0 -0
  56. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/operators.py +0 -0
  57. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/py.typed +0 -0
  58. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe/rounding.py +0 -0
  59. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe.egg-info/dependency_links.txt +0 -0
  60. {yirgacheffe-1.9.4 → yirgacheffe-1.10.0}/yirgacheffe.egg-info/entry_points.txt +0 -0
  61. {yirgacheffe-1.9.4 → yirgacheffe-1.10.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.9.4
3
+ Version: 1.10.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
@@ -37,6 +37,7 @@ Requires-Dist: mypy; extra == "dev"
37
37
  Requires-Dist: pylint; extra == "dev"
38
38
  Requires-Dist: pytest; extra == "dev"
39
39
  Requires-Dist: pytest-cov; extra == "dev"
40
+ Requires-Dist: pytest-mock; extra == "dev"
40
41
  Requires-Dist: build; extra == "dev"
41
42
  Requires-Dist: twine; extra == "dev"
42
43
  Requires-Dist: mkdocs-material; extra == "dev"
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "yirgacheffe"
9
- version = "1.9.4"
9
+ version = "1.10.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" }]
@@ -46,6 +46,7 @@ dev = [
46
46
  "pylint",
47
47
  "pytest",
48
48
  "pytest-cov",
49
+ "pytest-mock",
49
50
  "build",
50
51
  "twine",
51
52
  "mkdocs-material",
@@ -5,7 +5,7 @@ import numpy as np
5
5
  import pytest
6
6
 
7
7
  import yirgacheffe as yg
8
- from tests.helpers import gdal_dataset_with_data, make_vectors_with_mutlile_ids
8
+ from tests.helpers import gdal_dataset_with_data, make_vectors_with_multiple_ids
9
9
  from yirgacheffe.layers import ConstantLayer, RasterLayer, VectorLayer
10
10
  from yirgacheffe.window import Area
11
11
 
@@ -208,7 +208,7 @@ def test_vector_layers_add() -> None:
208
208
  (Area(-10.0, 10.0, 0.0, 0.0), 42),
209
209
  (Area(0.0, 0.0, 10, -10), 43)
210
210
  }
211
- make_vectors_with_mutlile_ids(areas, path)
211
+ make_vectors_with_multiple_ids(areas, path)
212
212
 
213
213
  burn_value = 2
214
214
  with VectorLayer.layer_from_file(
@@ -242,7 +242,7 @@ def test_vector_layers_add_unbound_rhs() -> None:
242
242
  (Area(-10.0, 10.0, 0.0, 0.0), 42),
243
243
  (Area(0.0, 0.0, 10, -10), 43)
244
244
  }
245
- make_vectors_with_mutlile_ids(areas, path)
245
+ make_vectors_with_multiple_ids(areas, path)
246
246
 
247
247
  burn_value = 2
248
248
  with VectorLayer.layer_from_file(path, None, None, None, burn_value=burn_value) as vector_layer:
@@ -269,7 +269,7 @@ def test_vector_layers_add_unbound_lhs() -> None:
269
269
  (Area(-10.0, 10.0, 0.0, 0.0), 42),
270
270
  (Area(0.0, 0.0, 10, -10), 43)
271
271
  }
272
- make_vectors_with_mutlile_ids(areas, path)
272
+ make_vectors_with_multiple_ids(areas, path)
273
273
 
274
274
  burn_value = 2
275
275
  with VectorLayer.layer_from_file(path, None, None, None, burn_value=burn_value) as vector_layer:
@@ -297,7 +297,7 @@ def test_vector_layers_multiply() -> None:
297
297
  (Area(-10.0, 10.0, 0.0, 0.0), 42),
298
298
  (Area(0.0, 0.0, 10, -10), 43)
299
299
  }
300
- make_vectors_with_mutlile_ids(areas, path)
300
+ make_vectors_with_multiple_ids(areas, path)
301
301
 
302
302
  burn_value = 2
303
303
  layer2 = VectorLayer.layer_from_file(path, None, layer1.pixel_scale, layer1.projection, burn_value=burn_value)
@@ -1,6 +1,8 @@
1
1
 
2
2
  import numpy as np
3
- import yirgacheffe
3
+ import pytest
4
+
5
+ import yirgacheffe as yg
4
6
  from yirgacheffe.layers import RasterLayer, ConstantLayer
5
7
  from yirgacheffe.operators import DataType
6
8
  from yirgacheffe.window import Area, PixelScale
@@ -23,10 +25,27 @@ def test_constant_parallel_save(monkeypatch) -> None:
23
25
  with RasterLayer.empty_raster_layer(area, scale, DataType.Float32) as result:
24
26
  with ConstantLayer(42.0) as c:
25
27
  with monkeypatch.context() as m:
26
- m.setattr(yirgacheffe.constants, "YSTEP", 1)
28
+ m.setattr(yg.constants, "YSTEP", 1)
27
29
  c.parallel_save(result)
28
30
 
29
31
  expected = np.full((20, 20), 42.0)
30
32
  actual = result.read_array(0, 0, 20, 20)
31
33
 
32
34
  assert (expected == actual).all()
35
+
36
+ @pytest.mark.parametrize("lhs,rhs,expected_equal", [
37
+ (1, 2, False),
38
+ (1, 1, True),
39
+ (1.0, 2.0, False),
40
+ (1.0, 1.0, True),
41
+ (1, 1.0, True), # This is Python standard behaviour
42
+ ])
43
+ def test_cse_hash(lhs,rhs,expected_equal) -> None:
44
+ a = yg.constant(lhs)
45
+ b = yg.constant(rhs)
46
+
47
+ assert a is not b
48
+ assert a.name != b.name
49
+
50
+ are_hashed_same = a._cse_hash == b._cse_hash
51
+ assert expected_equal == are_hashed_same
@@ -64,3 +64,49 @@ def test_float_to_int() -> None:
64
64
  expected = backend.promote(np.array([[1, 2, 3, 4], [5, 6, 7, 8]]))
65
65
  actual = result.read_array(0, 0, 4, 2)
66
66
  assert (expected == actual).all()
67
+
68
+ @pytest.mark.parametrize("array,expected_type", [
69
+ (
70
+ np.ones((2, 2)).astype(np.int8),
71
+ DataType.Int8,
72
+ ),
73
+ (
74
+ np.ones((2, 2)).astype(np.int16),
75
+ DataType.Int16,
76
+ ),
77
+ (
78
+ np.ones((2, 2)).astype(np.int32),
79
+ DataType.Int32,
80
+ ),
81
+ (
82
+ np.ones((2, 2)).astype(np.int64),
83
+ DataType.Int64,
84
+ ),
85
+ (
86
+ np.ones((2, 2)).astype(np.uint8),
87
+ DataType.UInt8,
88
+ ),
89
+ (
90
+ np.ones((2, 2)).astype(np.uint16),
91
+ DataType.UInt16,
92
+ ),
93
+ (
94
+ np.ones((2, 2)).astype(np.uint32),
95
+ DataType.UInt32,
96
+ ),
97
+ (
98
+ np.ones((2, 2)).astype(np.uint64),
99
+ DataType.UInt64,
100
+ ),
101
+ (
102
+ np.ones((2, 2)).astype(np.float32),
103
+ DataType.Float32,
104
+ ),
105
+ (
106
+ np.ones((2, 2)).astype(np.float64),
107
+ DataType.Float64,
108
+ ),
109
+ ])
110
+ def test_of_Array(array: np.ndarray, expected_type: DataType) -> None:
111
+ ytype = DataType.of_array(array)
112
+ assert ytype == expected_type
@@ -4,7 +4,7 @@ import tempfile
4
4
  import numpy as np
5
5
  from osgeo import gdal
6
6
 
7
- from tests.helpers import gdal_dataset_with_data
7
+ import yirgacheffe as yg
8
8
  from yirgacheffe.layers import RasterLayer
9
9
  from yirgacheffe.window import Area, PixelScale
10
10
 
@@ -23,9 +23,10 @@ def test_simple_two_band_image() -> None:
23
23
  )
24
24
 
25
25
  # Create a set of rasters in turn to fill each band
26
+ projection = yg.MapProjection("epsg:4326", 1.0, -1.0)
26
27
  for i in range(bands):
27
28
  data1 = np.full((2, 2), i+1)
28
- layer1 = RasterLayer(gdal_dataset_with_data((-1.0, 1.0), 1.0, data1))
29
+ layer1 = yg.from_array(data1, (-1.0, 1.0), projection)
29
30
  layer1.save(target, band=i+1)
30
31
 
31
32
  # force things to disk
@@ -33,7 +34,7 @@ def test_simple_two_band_image() -> None:
33
34
 
34
35
  #check they do what we expect
35
36
  for i in range(bands):
36
- o = RasterLayer.layer_from_file(target_path, band=i+1)
37
+ o = yg.read_raster(target_path, band=i+1)
37
38
  assert o.sum() == (4 * (i + 1))
38
39
 
39
40
  def test_stack_tifs_with_area_match() -> None:
@@ -43,9 +44,10 @@ def test_stack_tifs_with_area_match() -> None:
43
44
  # slight alignment offset when we create them)
44
45
  bands = 4
45
46
  source_layers = []
47
+ projection = yg.MapProjection("epsg:4326", 1.0, -1.0)
46
48
  for i in range(bands):
47
49
  data1 = np.full((100, 100), i+1)
48
- layer1 = RasterLayer(gdal_dataset_with_data((-100+i, 100+i), 1, data1))
50
+ layer1 = yg.from_array(data1, (-100+i, 100+i), projection)
49
51
  source_layers.append(layer1)
50
52
 
51
53
  intersection = RasterLayer.find_intersection(source_layers)
@@ -66,5 +68,5 @@ def test_stack_tifs_with_area_match() -> None:
66
68
 
67
69
  #check they do what we expect
68
70
  for i in range(bands):
69
- o = RasterLayer.layer_from_file(target_path, band=i+1)
71
+ o = yg.read_raster(target_path, band=i+1)
70
72
  assert o.sum() == ((100 - (bands - 1)) * (100 - (bands - 1)) * (i + 1))
@@ -12,7 +12,7 @@ from yirgacheffe.layers import InvalidRasterBand, RasterLayer
12
12
  from yirgacheffe.window import Area, MapProjection, Window
13
13
  from yirgacheffe.operators import DataType
14
14
  from tests.helpers import gdal_dataset_of_region, gdal_multiband_dataset_with_data, \
15
- make_vectors_with_id, make_vectors_with_mutlile_ids
15
+ make_vectors_with_id, make_vectors_with_multiple_ids
16
16
 
17
17
  def test_raster_from_nonexistent_file() -> None:
18
18
  with pytest.raises(FileNotFoundError):
@@ -136,7 +136,7 @@ def test_open_gpkg_with_filter() -> None:
136
136
  (Area(-10.0, 10.0, 0.0, 0.0), 42),
137
137
  (Area(0.0, 0.0, 10, -10), 43)
138
138
  }
139
- make_vectors_with_mutlile_ids(areas, path)
139
+ make_vectors_with_multiple_ids(areas, path)
140
140
 
141
141
  with yg.read_shape(path, (WGS_84_PROJECTION, (1.0, -1.0)), "id_no=42") as layer:
142
142
  assert layer.area == Area(-10.0, 10.0, 0.0, 0.0)
@@ -264,3 +264,29 @@ def test_constant() -> None:
264
264
  expected = np.full((20, 20), 42.0)
265
265
  actual = result.read_array(0, 0, 20, 20)
266
266
  assert (expected == actual).all()
267
+
268
+ def test_create_simple_float() -> None:
269
+ projection = MapProjection(WGS_84_PROJECTION, 1.0, -1.0)
270
+ data = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
271
+ with yg.from_array(data, (-2.0, 1.0), projection) as layer:
272
+ expected_area = Area(left=-2.0, right=2.0, top=1.0, bottom=-1.0)
273
+
274
+ assert layer.map_projection == projection
275
+ assert layer.area == expected_area
276
+ assert layer.datatype == DataType.Float64
277
+
278
+ actual = layer.read_array(0, 0, 4, 2)
279
+ assert (data == actual).all()
280
+
281
+ def test_create_simple_direct_projection() -> None:
282
+ expected_projection = MapProjection(WGS_84_PROJECTION, 1.0, -1.0)
283
+ data = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
284
+ with yg.from_array(data, (-2.0, 1.0), (WGS_84_PROJECTION, (1.0, -1.0))) as layer:
285
+ expected_area = Area(left=-2.0, right=2.0, top=1.0, bottom=-1.0)
286
+
287
+ assert layer.map_projection == expected_projection
288
+ assert layer.area == expected_area
289
+ assert layer.datatype == DataType.Int64
290
+
291
+ actual = layer.read_array(0, 0, 4, 2)
292
+ assert (data == actual).all()
@@ -0,0 +1,372 @@
1
+ import tempfile
2
+ from pathlib import Path
3
+
4
+ import numpy as np
5
+ import pytest
6
+
7
+ from tests.helpers import make_vectors_with_id, make_vectors_with_multiple_ids, gdal_dataset_of_region, \
8
+ gdal_multiband_dataset_with_data
9
+ import yirgacheffe as yg
10
+ from yirgacheffe.layers import H3CellLayer
11
+ from yirgacheffe._operators.cse import CSECacheTable
12
+ from yirgacheffe._backends import backend
13
+
14
+ def test_simple_constant_expression() -> None:
15
+ with (
16
+ yg.constant(1) as lhs,
17
+ yg.constant(2) as rhs,
18
+ ):
19
+ calc0 = lhs + rhs
20
+ calc1 = lhs + rhs
21
+ calc2 = rhs + lhs
22
+
23
+ # One day this test should fail when we take commutative operators into account
24
+ assert calc0 is not calc1
25
+ assert calc0._cse_hash == calc1._cse_hash
26
+ assert calc1._cse_hash != calc2._cse_hash
27
+
28
+ def test_simple_raster_expression() -> None:
29
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
30
+ data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]])
31
+
32
+ with(
33
+ yg.from_array(data1, (0, 0), ("epsg:4326", (1.0, -1.0))) as layer1,
34
+ yg.from_array(data2, (0, 0), ("epsg:4326", (1.0, -1.0))) as layer2,
35
+ ):
36
+ calc0 = layer1 + layer2
37
+ calc1 = layer1 + layer2
38
+ calc2 = layer1 + layer1
39
+
40
+ assert calc0 is not calc1
41
+ assert calc0._cse_hash == calc1._cse_hash
42
+ assert calc1._cse_hash != calc2._cse_hash
43
+
44
+ def test_raster_different_datatype() -> None:
45
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
46
+
47
+ with(
48
+ yg.from_array(data1.astype(np.int16), (0, 0), ("epsg:4326", (1.0, -1.0))) as layer1,
49
+ yg.from_array(data1.astype(np.float32), (0, 0), ("epsg:4326", (1.0, -1.0))) as layer2,
50
+ ):
51
+ assert layer1.datatype == yg.DataType.Int16
52
+ assert layer2.datatype == yg.DataType.Float32
53
+
54
+ assert layer1._cse_hash is not None
55
+ assert layer2._cse_hash is not None
56
+ assert layer1._cse_hash != layer2._cse_hash
57
+
58
+ def test_raster_ignore_nodata() -> None:
59
+ with tempfile.TemporaryDirectory() as tempdir:
60
+ path = Path(tempdir) / "test.tif"
61
+ area = yg.Area(-10, 10, 10, -10)
62
+ _ = gdal_dataset_of_region(area, 0.02, filename=path)
63
+
64
+ with (
65
+ yg.read_raster(path, ignore_nodata=True) as layer1,
66
+ yg.read_raster(path, ignore_nodata=False) as layer2,
67
+ ):
68
+ assert layer1._cse_hash is not None
69
+ assert layer2._cse_hash is not None
70
+ assert layer1._cse_hash != layer2._cse_hash
71
+
72
+ def test_raster_different_bands() -> None:
73
+ with tempfile.TemporaryDirectory() as tempdir:
74
+ path = Path(tempdir) / "test.tif"
75
+ data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
76
+ data2 = np.array([[10.0, 20.0, 30.0, 40.0], [50.0, 60.0, 70.0, 80.0]])
77
+
78
+ datas = [data1, data2]
79
+ _ = gdal_multiband_dataset_with_data((0.0, 0.0), 0.02, datas, filename=path)
80
+
81
+ with (
82
+ yg.read_raster(path, band=1) as layer1,
83
+ yg.read_raster(path, band=2) as layer2,
84
+ ):
85
+ assert layer1._cse_hash is not None
86
+ assert layer2._cse_hash is not None
87
+ assert layer1._cse_hash != layer2._cse_hash
88
+
89
+ def test_simple_vector_expression() -> None:
90
+ with tempfile.TemporaryDirectory() as tempdir:
91
+ path = Path(tempdir) / "test.gpkg"
92
+ area = yg.Area(-10.0, 10.0, 10.0, 0.0)
93
+ make_vectors_with_id(42, {area}, path)
94
+
95
+ with yg.read_shape(path) as shape:
96
+ assert shape._cse_hash is not None
97
+ calc = shape * 2
98
+ assert calc._cse_hash is not None
99
+
100
+ def test_vector_different_burn() -> None:
101
+ with tempfile.TemporaryDirectory() as tempdir:
102
+ path = Path(tempdir) / "test.gpkg"
103
+ area = yg.Area(-10.0, 10.0, 10.0, 0.0)
104
+ make_vectors_with_id(42, {area}, path)
105
+
106
+ with (
107
+ yg.read_shape(path, burn_value=1) as shape1,
108
+ yg.read_shape(path, burn_value=2) as shape2,
109
+ ):
110
+ assert shape1._cse_hash is not None
111
+ assert shape2._cse_hash is not None
112
+ assert shape1._cse_hash != shape2._cse_hash
113
+
114
+ def test_vector_different_where() -> None:
115
+ with tempfile.TemporaryDirectory() as tempdir:
116
+ path = Path(tempdir) / "test.gpkg"
117
+ areas = {
118
+ (yg.Area(0.0, 0.0, 10, -10), 42),
119
+ (yg.Area(0.0, 0.0, 10, -10), 43)
120
+ }
121
+ make_vectors_with_multiple_ids(areas, path)
122
+
123
+ with (
124
+ yg.read_shape(path) as shape0,
125
+ yg.read_shape(path, where_filter="id_no=42") as shape1,
126
+ yg.read_shape(path, where_filter="id_no=43") as shape2,
127
+ ):
128
+ assert shape0._cse_hash is not None
129
+ assert shape1._cse_hash is not None
130
+ assert shape2._cse_hash is not None
131
+ assert shape0._cse_hash != shape1._cse_hash
132
+ assert shape1._cse_hash != shape2._cse_hash
133
+ assert shape0._cse_hash != shape2._cse_hash
134
+
135
+ def test_vector_different_datatype() -> None:
136
+ with tempfile.TemporaryDirectory() as tempdir:
137
+ path = Path(tempdir) / "test.gpkg"
138
+ area = yg.Area(-10.0, 10.0, 10.0, 0.0)
139
+ make_vectors_with_id(42, {area}, path)
140
+
141
+ with (
142
+ yg.read_shape(path, datatype=yg.DataType.Int16) as shape1,
143
+ yg.read_shape(path, datatype=yg.DataType.Float32) as shape2,
144
+ ):
145
+ assert shape1._cse_hash is not None
146
+ assert shape2._cse_hash is not None
147
+ assert shape1._cse_hash != shape2._cse_hash
148
+
149
+ def test_simple_group_layer() -> None:
150
+ with tempfile.TemporaryDirectory() as tempdir:
151
+ path1 = Path(tempdir) / "1.tif"
152
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
153
+ with yg.from_array(data1, (0, 0), ("epsg:4326", (1.0, -1.0))) as layer:
154
+ layer.to_geotiff(path1)
155
+
156
+ path2 = Path(tempdir) / "2.tif"
157
+ data2 = np.array([[10, 20, 30, 40], [50, 60, 70, 80]])
158
+ with yg.from_array(data2, (0, 0), ("epsg:4326", (1.0, -1.0))) as layer:
159
+ layer.to_geotiff(path2)
160
+
161
+ with (
162
+ yg.read_rasters([path1, path2]) as group0,
163
+ yg.read_rasters([path1, path2]) as group1,
164
+ yg.read_rasters([path1]) as group2,
165
+ ):
166
+ assert group0._cse_hash is not None
167
+ assert group1._cse_hash is not None
168
+ assert group2._cse_hash is not None
169
+ assert group0._cse_hash == group1._cse_hash
170
+ assert group1._cse_hash != group2._cse_hash
171
+
172
+ def test_simple_h3_layers() -> None:
173
+ with (
174
+ H3CellLayer("88972eac11fffff", yg.MapProjection("epsg:4326", 0.001, -0.001)) as layer0,
175
+ H3CellLayer("88972eac11fffff", yg.MapProjection("epsg:4326", 0.001, -0.001)) as layer1,
176
+ H3CellLayer("88972eac19fffff", yg.MapProjection("epsg:4326", 0.001, -0.001)) as layer2,
177
+ H3CellLayer("88972eac11fffff", yg.MapProjection("epsg:4326", 0.002, -0.002)) as layer3,
178
+ ):
179
+ assert layer0._cse_hash is not None
180
+ assert layer1._cse_hash is not None
181
+ assert layer2._cse_hash is not None
182
+ assert layer3._cse_hash is not None
183
+ assert layer0._cse_hash == layer1._cse_hash
184
+ assert layer1._cse_hash != layer2._cse_hash
185
+ assert layer1._cse_hash != layer3._cse_hash
186
+
187
+ def test_mixed_raster_constant() -> None:
188
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
189
+
190
+ with(
191
+ yg.from_array(data1, (0, 0), ("epsg:4326", (1.0, -1.0))) as layer1,
192
+ yg.constant(2) as layer2,
193
+ yg.constant(2.0) as layer3,
194
+ ):
195
+ calc0 = layer1 + layer2
196
+ calc1 = layer1 + layer2
197
+ calc2 = layer1 * layer2
198
+ calc3 = layer1 + layer3
199
+
200
+ assert calc0 is not calc1
201
+ assert layer2 is not layer3
202
+ assert calc0._cse_hash == calc1._cse_hash
203
+ assert calc1._cse_hash != calc2._cse_hash
204
+ assert calc1._cse_hash == calc3._cse_hash
205
+
206
+ def test_cse_simple(mocker, monkeypatch) -> None:
207
+ with monkeypatch.context() as m:
208
+ m.setattr(yg.constants, "YSTEP", 1)
209
+
210
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
211
+ with (
212
+ yg.from_array(data1, (0, 0), ("epsg:4326", (1.0, -1.0))) as lhs,
213
+ yg.constant(3) as rhs,
214
+ ):
215
+ calc = (lhs + rhs) * (lhs + rhs)
216
+
217
+ # this is an API violation, but let's check the table used for CSE
218
+ hash_table = CSECacheTable(calc, calc.window)
219
+
220
+ assert len(hash_table) == 4
221
+
222
+ top_level_hash = calc._cse_hash
223
+ common_term_hash = (lhs + rhs)._cse_hash
224
+ lhs_hash = lhs._cse_hash
225
+ rhs_hash = rhs._cse_hash
226
+
227
+ assert hash_table._table[(top_level_hash, calc.window)] == (1, None)
228
+ assert hash_table._table[(common_term_hash, calc.window)] == (2, None)
229
+ assert hash_table._table[(lhs_hash, calc.window)] == (1, None)
230
+ assert hash_table._table[(rhs_hash, calc.window)] == (1, None)
231
+
232
+ lhs_spy = mocker.spy(lhs, '_read_array_with_window')
233
+ rhs_spy = mocker.spy(rhs, '_read_array_for_area')
234
+
235
+ expected = (data1 + 3) * (data1 + 3)
236
+ actual = calc.read_array(0, 0, 4, 2)
237
+ assert (expected == actual).all()
238
+
239
+ assert lhs_spy.call_count == 2
240
+ assert rhs_spy.call_count == 2
241
+
242
+ def test_simple_aoh_style_range_check(mocker, monkeypatch) -> None:
243
+ with monkeypatch.context() as m:
244
+ m.setattr(yg.constants, "YSTEP", 1)
245
+
246
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
247
+ with yg.from_array(data1, (0, 0), ("epsg:4326", (1.0, -1.0))) as lhs:
248
+ calc = (lhs > 2) & (lhs < 7)
249
+
250
+ # this is an API violation, but let's check the table used for CSE
251
+ hash_table = CSECacheTable(calc, calc.window)
252
+
253
+ assert len(hash_table) == 6
254
+ assert hash_table._table[(lhs._cse_hash, calc.window)] == (2, None)
255
+ for k, v in hash_table._table.items():
256
+ if k == (lhs._cse_hash, calc.window):
257
+ continue
258
+ assert v == (1, None)
259
+
260
+ lhs_spy = mocker.spy(lhs, '_read_array_with_window')
261
+
262
+ expected = (data1 > 2) & (data1 < 7)
263
+
264
+ actual = calc.read_array(0, 0, 4, 2)
265
+ assert (expected == actual).all()
266
+
267
+ assert lhs_spy.call_count == 2
268
+
269
+ def test_caching_versus_boundary_expansion(monkeypatch) -> None:
270
+ # If you have a layer that is the source for a convolution, then the window read from it is
271
+ # expanded, so if we have the same layer in and out a convolution, it'll be read at different
272
+ # window sizes, and cause a mess with caching
273
+ with monkeypatch.context() as m:
274
+ m.setattr(yg.constants, "YSTEP", 1)
275
+
276
+ matrix = np.array([[0.0, 1.0, 0.0], [1.0, 1.0, 1.0], [0.0, 1.0, 0.0]])
277
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [12, 13, 14, 15]]).astype(np.float32)
278
+ with yg.from_array(data1, (0, 0), ("epsg:4326", (1.0, -1.0))) as lhs:
279
+ calc = lhs.conv2d(matrix) * lhs
280
+
281
+ # this is an API violation, but let's check the table used for CSE
282
+ hash_table = CSECacheTable(calc, calc.window)
283
+ assert len(hash_table) == 4
284
+ for val in hash_table._table.values():
285
+ assert val == (1, None) # i.e., the two lhs values did not get put in the same hash table row
286
+
287
+ @pytest.mark.parametrize("sequence", [
288
+ [1, 2, 3],
289
+ (1, 2, 3),
290
+ {1, 2, 3},
291
+ ])
292
+ def test_isin_hashable(sequence) -> None:
293
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
294
+ with yg.from_array(data1, (0, 0), ("epsg:4326", (1.0, -1.0))) as layer:
295
+ calc = layer.isin(sequence)
296
+
297
+ assert calc._cse_hash is not None
298
+
299
+ expected = np.isin(data1, list(sequence))
300
+ actual = calc.read_array(0, 0, 4, 2)
301
+ assert (expected == actual).all()
302
+
303
+ def test_nan_to_num_hashable() -> None:
304
+ data1 = np.array([[1, float("nan"), float("inf"), float("-inf")], [5, 6, 7, 8]])
305
+ with yg.from_array(data1, (0, 0), ("epsg:4326", (1.0, -1.0))) as layer:
306
+ calc = layer.nan_to_num(2, 3, 4)
307
+
308
+ assert calc._cse_hash is not None
309
+
310
+ expected = np.nan_to_num(data1, nan=2, posinf=3, neginf=4)
311
+ actual = calc.read_array(0, 0, 4, 2)
312
+ assert (expected == actual).all()
313
+
314
+ def test_unary_numpy_apply_hashable() -> None:
315
+ data1 = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
316
+ with yg.from_array(data1, (0, 0), ("epsg:4326", (1.0, -1.0))) as layer:
317
+
318
+ def simple_add(chunk):
319
+ return chunk + 1.0
320
+
321
+ comp = layer.numpy_apply(simple_add)
322
+
323
+ assert comp._cse_hash is not None
324
+
325
+ def test_cse_cache_table_reset() -> None:
326
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
327
+ with (
328
+ yg.from_array(data1, (0, 0), ("epsg:4326", (1.0, -1.0))) as lhs,
329
+ yg.constant(3) as rhs,
330
+ ):
331
+ calc = (lhs + rhs) * (lhs + rhs)
332
+ cse_cache = CSECacheTable(calc, calc.window)
333
+
334
+ term = lhs + rhs
335
+ term_hash = term._cse_hash
336
+
337
+ assert cse_cache.get_data(term_hash, calc.window) is None
338
+
339
+ cse_cache.set_data(term_hash, calc.window, backend.promote(data1))
340
+
341
+ cache_result = cse_cache.get_data(term_hash, calc.window)
342
+ assert cache_result is not None
343
+ assert (backend.demote_array(cache_result) == data1).all()
344
+
345
+ cse_cache.reset_cache()
346
+
347
+ assert cse_cache.get_data(term_hash, calc.window) is None
348
+
349
+ def test_cse_cache_table_cache_miss_on_different_window_size() -> None:
350
+ data1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
351
+ with (
352
+ yg.from_array(data1, (0, 0), ("epsg:4326", (1.0, -1.0))) as lhs,
353
+ yg.constant(3) as rhs,
354
+ ):
355
+ calc = (lhs + rhs) * (lhs + rhs)
356
+ cse_cache = CSECacheTable(calc, calc.window)
357
+
358
+ term = lhs + rhs
359
+ term_hash = term._cse_hash
360
+
361
+ assert cse_cache.get_data(term_hash, calc.window) is None
362
+
363
+ cse_cache.set_data(term_hash, calc.window, backend.promote(data1))
364
+
365
+ cache_result = cse_cache.get_data(term_hash, calc.window)
366
+ assert (backend.demote_array(cache_result) == data1).all()
367
+
368
+ assert cse_cache.get_data(term_hash, calc.window.grow(1)) is None
369
+ # This just tests that we're not stuck on id(window) check, we do test proper window equality
370
+ cache_result = cse_cache.get_data(term_hash, calc.window.grow(0))
371
+ assert cache_result is not None
372
+ assert (backend.demote_array(cache_result) == data1).all()