yirgacheffe 1.4.0__tar.gz → 1.5.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.
Files changed (56) hide show
  1. {yirgacheffe-1.4.0/yirgacheffe.egg-info → yirgacheffe-1.5.0}/PKG-INFO +111 -55
  2. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/README.md +109 -54
  3. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/pyproject.toml +3 -2
  4. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_area.py +10 -0
  5. yirgacheffe-1.5.0/tests/test_openers.py +165 -0
  6. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_operators.py +70 -0
  7. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_union.py +1 -1
  8. yirgacheffe-1.5.0/yirgacheffe/__init__.py +11 -0
  9. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe/_backends/mlx.py +2 -1
  10. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe/_backends/numpy.py +2 -1
  11. yirgacheffe-1.5.0/yirgacheffe/_core.py +133 -0
  12. yirgacheffe-1.5.0/yirgacheffe/constants.py +8 -0
  13. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe/layers/base.py +6 -6
  14. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe/layers/constant.py +2 -7
  15. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe/layers/group.py +16 -7
  16. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe/layers/rasters.py +7 -7
  17. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe/layers/rescaled.py +3 -2
  18. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe/layers/vectors.py +15 -13
  19. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe/operators.py +84 -20
  20. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe/window.py +27 -0
  21. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0/yirgacheffe.egg-info}/PKG-INFO +111 -55
  22. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe.egg-info/SOURCES.txt +2 -0
  23. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe.egg-info/requires.txt +1 -0
  24. yirgacheffe-1.4.0/yirgacheffe/__init__.py +0 -14
  25. yirgacheffe-1.4.0/yirgacheffe/constants.py +0 -2
  26. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/LICENSE +0 -0
  27. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/MANIFEST.in +0 -0
  28. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/setup.cfg +0 -0
  29. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_auto_windowing.py +0 -0
  30. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_base.py +0 -0
  31. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_constants.py +0 -0
  32. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_datatypes.py +0 -0
  33. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_group.py +0 -0
  34. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_h3layer.py +0 -0
  35. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_intersection.py +0 -0
  36. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_multiband.py +0 -0
  37. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_optimisation.py +0 -0
  38. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_parallel_operators.py +0 -0
  39. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_pickle.py +0 -0
  40. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_raster.py +0 -0
  41. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_rescaling.py +0 -0
  42. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_rounding.py +0 -0
  43. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_save_with_window.py +0 -0
  44. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_sum_with_window.py +0 -0
  45. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_uniform_area_layer.py +0 -0
  46. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_vectors.py +0 -0
  47. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/tests/test_window.py +0 -0
  48. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe/_backends/__init__.py +0 -0
  49. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe/_backends/enumeration.py +0 -0
  50. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe/layers/__init__.py +0 -0
  51. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe/layers/area.py +0 -0
  52. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe/layers/h3layer.py +0 -0
  53. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe/rounding.py +0 -0
  54. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe.egg-info/dependency_links.txt +0 -0
  55. {yirgacheffe-1.4.0 → yirgacheffe-1.5.0}/yirgacheffe.egg-info/entry_points.txt +0 -0
  56. {yirgacheffe-1.4.0 → yirgacheffe-1.5.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.4.0
3
+ Version: 1.5.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
@@ -13,6 +13,7 @@ Requires-Dist: numpy
13
13
  Requires-Dist: gdal[numpy]
14
14
  Requires-Dist: scikit-image
15
15
  Requires-Dist: torch
16
+ Requires-Dist: dill
16
17
  Provides-Extra: dev
17
18
  Requires-Dist: mypy; extra == "dev"
18
19
  Requires-Dist: pylint; extra == "dev"
@@ -52,12 +53,12 @@ The motivation for Yirgacheffe layers is to make working with gdal data slightly
52
53
  For example, if we wanted to do a simple [Area of Habitat](https://github.com/quantifyearth/aoh-calculator/) calculation, whereby we find the pixels where a species resides by combining its range polygon, its habitat preferences, and its elevation preferences, the code would be like this:
53
54
 
54
55
  ```python
55
- from yirgacheffe.layer import RasterLayer, VectorLayer
56
+ import yirgaceffe as yg
56
57
 
57
- habitat_map = RasterLayer.layer_from_file("habitats.tif")
58
- elevation_map = RasterLayer.layer_from_file('elevation.tif')
59
- range_polygon = VectorLayer.layer_from_file('species123.geojson', raster_like=habitat_map)
60
- area_per_pixel_map = RasterLayer.layer_from_file('area_per_pixel.tif')
58
+ habitat_map = yg.read_raster("habitats.tif")
59
+ elevation_map = yg.read_raster('elevation.tif')
60
+ range_polygon = yg.read_shape_like('species123.geojson', like=habitat_map)
61
+ area_per_pixel_map = yg.read_raster('area_per_pixel.tif')
61
62
 
62
63
  refined_habitat = habitat_map.isin([...species habitat codes...])
63
64
  refined_elevation = (elevation_map >= species_min) && (elevation_map <= species_max)
@@ -70,8 +71,8 @@ print(f'area for species 123: {aoh.sum()}')
70
71
  Similarly, you could save the result to a new raster layer:
71
72
 
72
73
  ```python
73
- with RasterLayer.empty_raster_layer_like(aoh, filename="result.tif") as result:
74
- aoh.save(result)
74
+ ...
75
+ aoh.to_geotiff("result.tif")
75
76
  ```
76
77
 
77
78
  Yirgacheffe will automatically infer if you want to do an intersection of maps or a union of the maps based on the operators you use (see below for a full table). You can explicitly override that if you want.
@@ -109,27 +110,28 @@ If you have set either the intersection window or union window on a layer and yo
109
110
  If doing per-layer operations isn't applicable for your application, you can read the pixel values for all layers (including VectorLayers) by calling `read_array` similarly to how you would for gdal. The data you access will be relative to the specified window - that is, if you've called either `set_window_for_intersection` or `set_window_for_union` then `read_array` will be relative to that and Yirgacheffe will clip or expand the data with zero values as necessary.
110
111
 
111
112
 
112
- ### Todo but not supported
113
-
114
- Yirgacheffe is work in progress, so things planned but not supported currently:
115
-
116
- * Dynamic pixel scale adjustment - all raster layers must be provided at the same pixel scale currently *NOW IN EXPERIMENTAL TESTING, SEE BELOW*
117
- * A fold operation
118
- * CUDA/Metal support via CUPY/MLX
119
- * Dispatching work across multiple CPUs *NOW IN EXPERIMENTAL TESTING, SEE BELOW*
120
-
121
-
122
-
123
113
  ## Layer types
124
114
 
115
+ Note that as part of the move to the next major release, 2.0, we are adding simpler ways to create layers. Not all of those have been implemented yet, which is why this section has some inconsistencies. However, given many of the common cases are already covered, we present the new 2.0 style methods (`read_raster` and similar) here so you can write cleaner code today rather than making people wait for the final 2.0 release.
116
+
125
117
  ### RasterLayer
126
118
 
127
119
  This is your basic GDAL raster layer, which you load from a geotiff.
128
120
 
129
121
  ```python
122
+ from yirgaceffe.layers import RasterLayer
123
+
130
124
  with RasterLayer.layer_from_file('test1.tif') as layer:
131
- data = layer.read_array(0, 0, 10, 10)
132
- ...
125
+ total = layer.sum()
126
+ ```
127
+
128
+ The new 2.0 way of doing this is:
129
+
130
+ ```python
131
+ import yirgacheffe as yg
132
+
133
+ with yg.read_raster('test.tif') as layer:
134
+ total = layer.sum()
133
135
  ```
134
136
 
135
137
  You can also create empty layers ready for you to store results, either by taking the dimensions from an existing layer. In both these cases you can either provide a filename to which the data will be written, or if you do not provide a filename then the layer will only exist in memory - this will be more efficient if the layer is being used for intermediary results.
@@ -166,51 +168,30 @@ This layer will load vector data and rasterize it on demand as part of a calcula
166
168
  Because it will be rasterized you need to specify the pixel scale and map projection to be used when rasterising the data, and the common way to do that is by using one of your other layers.
167
169
 
168
170
  ```python
169
- with VectorLayer.layer_from_file('range.gpkg', 'id_no == 42', layer1.pixel_scale, layer1.projection) as layer:
170
- ...
171
- ```
172
-
173
- This class was formerly called `DynamicVectorRangeLayer`, a name now deprecated.
174
-
171
+ from yirgaceffe import WGS_84_PROJECTION
172
+ from yirgaceffe.window import PixelScale
173
+ from yirgaceffe.layers import VectorLayer
175
174
 
176
- ### UniformAreaLayer
177
-
178
- In certain calculations you find you have a layer where all the rows of data are the same - notably geotiffs that contain the area of a given pixel do this due to how conventional map projections work. It's hugely inefficient to load the full map into memory, so whilst you could just load them as `Layer` types, we recommend you do:
179
-
180
- ```python
181
- with UniformAreaLayer('area.tiff') as layer:
182
- ....
175
+ with VectorLayer.layer_from_file('range.gpkg', PixelScale(0.001, -0.001), WGS_84_PROJECTION) as layer:
176
+ ...
183
177
  ```
184
178
 
185
- Note that loading this data can still be very slow, due to how image compression works. So if you plan to use area.tiff more than once, we recommend use save an optimised version - this will do the slow uncompression once and then save a minimal file to speed up future processing:
179
+ The new 2.0 way of doing this is:
186
180
 
187
181
  ```python
188
- if not os.path.exists('yirgacheffe_area.tiff'):
189
- UniformAreaLayer.generate_narrow_area_projection('area.tiff', 'yirgacheffe_area.tiff')
190
- area_layer = UniformAreaLayer('yirgacheffe_area.tiff')
191
- ```
192
-
193
-
194
- ### ConstantLayer
182
+ import yirgacheffe as yg
195
183
 
196
- This is there to simplify code when you have some optional layers. Rather than littering your code with checks, you can just use a constant layer, which can be included in calculations and will just return an fixed value as if it wasn't there. Useful with 0.0 or 1.0 for sum or multiplication null layers.
197
-
198
- ```python
199
- try:
200
- area_layer = UniformAreaLayer('myarea.tiff')
201
- except FileDoesNotExist:
202
- area_layer = ConstantLayer(0.0)
184
+ with yg.read_shape('range.gpkg', (0.001, -0.001), WGS_84_PROJECTION) as layer:
185
+ ...
203
186
  ```
204
187
 
188
+ 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:
205
189
 
206
- ### H3CellLayer
207
-
208
- If you have H3 installed, you can generate a mask layer based on an H3 cell identifier, where pixels inside the cell will have a value of 1, and those outside will have a value of 0.
209
-
210
- Becuase it will be rasterized you need to specify the pixel scale and map projection to be used when rasterising the data, and the common way to do that is by using one of your other layers.
211
190
 
212
191
  ```python
213
- hex_cell_layer = H3CellLayer('88972eac11fffff', layer1.pixel_scale, layer1.projection)
192
+ with yg.read_raster("test.tif") as raster_layer:
193
+ with yg.read_shape_like('range.gpkg', raster_layer) as shape_layer:
194
+ ...
214
195
  ```
215
196
 
216
197
  ### GroupLayer
@@ -239,6 +220,15 @@ with GroupLayer.layer_from_directory('.') as all_tiles:
239
220
  ...
240
221
  ```
241
222
 
223
+ The new 2.0 way of doing this is:
224
+
225
+ ```python
226
+ import yirgacheffe as yg
227
+
228
+ with yg.read_rasters(['tile_N10_E10.tif', 'tile_N20_E10.tif']) as all_tiles:
229
+ ...
230
+ ```
231
+
242
232
  ### TiledGroupLayer
243
233
 
244
234
  This is a specialisation of GroupLayer, which you can use if your layers are all the same size and form a grid, as is often the case with map tiles. In this case the rendering code can be optimised and this class is significantly faster that GroupLayer.
@@ -249,11 +239,59 @@ tile2 = RasterLayer.layer_from_file('tile_N20_E10.tif')
249
239
  all_tiles = TiledGroupLayer([tile1, tile2])
250
240
  ```
251
241
 
242
+ The new 2.0 way of doing this is:
243
+
244
+ ```python
245
+ import yirgacheffe as yg
246
+
247
+ with yg.read_rasters(['tile_N10_E10.tif', 'tile_N20_E10.tif'], tiled=True) as all_tiles:
248
+ ...
249
+ ```
250
+
252
251
  Notes:
253
252
 
254
253
  * You can have missing tiles, and these will be filled in with zeros.
255
254
  * You can have tiles that overlap, so long as they still conform to the rule that all tiles are the same size and on a grid.
256
255
 
256
+ ### ConstantLayer
257
+
258
+ This is there to simplify code when you have some optional layers. Rather than littering your code with checks, you can just use a constant layer, which can be included in calculations and will just return an fixed value as if it wasn't there. Useful with 0.0 or 1.0 for sum or multiplication null layers.
259
+
260
+ ```python
261
+ try:
262
+ area_layer = RasterLayer.layer_from_file('myarea.tiff')
263
+ except FileDoesNotExist:
264
+ area_layer = ConstantLayer(0.0)
265
+ ```
266
+
267
+ ### H3CellLayer
268
+
269
+ If you have H3 installed, you can generate a mask layer based on an H3 cell identifier, where pixels inside the cell will have a value of 1, and those outside will have a value of 0.
270
+
271
+ Becuase it will be rasterized you need to specify the pixel scale and map projection to be used when rasterising the data, and the common way to do that is by using one of your other layers.
272
+
273
+ ```python
274
+ hex_cell_layer = H3CellLayer('88972eac11fffff', layer1.pixel_scale, layer1.projection)
275
+ ```
276
+
277
+
278
+ ### UniformAreaLayer
279
+
280
+ In certain calculations you find you have a layer where all the rows of data are the same - notably geotiffs that contain the area of a given pixel do this due to how conventional map projections work. It's hugely inefficient to load the full map into memory, so whilst you could just load them as `Layer` types, we recommend you do:
281
+
282
+ ```python
283
+ with UniformAreaLayer('area.tiff') as layer:
284
+ ....
285
+ ```
286
+
287
+ Note that loading this data can still be very slow, due to how image compression works. So if you plan to use area.tiff more than once, we recommend use save an optimised version - this will do the slow uncompression once and then save a minimal file to speed up future processing:
288
+
289
+ ```python
290
+ if not os.path.exists('yirgacheffe_area.tiff'):
291
+ UniformAreaLayer.generate_narrow_area_projection('area.tiff', 'yirgacheffe_area.tiff')
292
+ area_layer = UniformAreaLayer('yirgacheffe_area.tiff')
293
+ ```
294
+
257
295
 
258
296
  ## Supported operations on layers
259
297
 
@@ -280,6 +318,24 @@ with RasterLayer.layer_from_file('test1.tif') as layer1:
280
318
  calc.save(result)
281
319
  ```
282
320
 
321
+
322
+ The new 2.0 way of doing these are:
323
+
324
+ ```python
325
+ with yg.read_raster('test1.tif') as layer1:
326
+ with yg.read_raster('test2.tif') as layer2:
327
+ result = layer1 + layer2
328
+ result.to_geotiff("result.tif")
329
+ ```
330
+
331
+ or
332
+
333
+ ```python
334
+ with yg.read_raster('test1.tif') as layer1:
335
+ result = layer1 * 42.0
336
+ result.to_geotiff("result.tif")
337
+ ```
338
+
283
339
  ### Boolean testing
284
340
 
285
341
  Testing for equality, less than, less than or equal, greater than, and greater than or equal are supported on layers, along with logical or and logical and, as per this example, where `elevation_upper` and `elevation_lower` are scalar values:
@@ -28,12 +28,12 @@ The motivation for Yirgacheffe layers is to make working with gdal data slightly
28
28
  For example, if we wanted to do a simple [Area of Habitat](https://github.com/quantifyearth/aoh-calculator/) calculation, whereby we find the pixels where a species resides by combining its range polygon, its habitat preferences, and its elevation preferences, the code would be like this:
29
29
 
30
30
  ```python
31
- from yirgacheffe.layer import RasterLayer, VectorLayer
31
+ import yirgaceffe as yg
32
32
 
33
- habitat_map = RasterLayer.layer_from_file("habitats.tif")
34
- elevation_map = RasterLayer.layer_from_file('elevation.tif')
35
- range_polygon = VectorLayer.layer_from_file('species123.geojson', raster_like=habitat_map)
36
- area_per_pixel_map = RasterLayer.layer_from_file('area_per_pixel.tif')
33
+ habitat_map = yg.read_raster("habitats.tif")
34
+ elevation_map = yg.read_raster('elevation.tif')
35
+ range_polygon = yg.read_shape_like('species123.geojson', like=habitat_map)
36
+ area_per_pixel_map = yg.read_raster('area_per_pixel.tif')
37
37
 
38
38
  refined_habitat = habitat_map.isin([...species habitat codes...])
39
39
  refined_elevation = (elevation_map >= species_min) && (elevation_map <= species_max)
@@ -46,8 +46,8 @@ print(f'area for species 123: {aoh.sum()}')
46
46
  Similarly, you could save the result to a new raster layer:
47
47
 
48
48
  ```python
49
- with RasterLayer.empty_raster_layer_like(aoh, filename="result.tif") as result:
50
- aoh.save(result)
49
+ ...
50
+ aoh.to_geotiff("result.tif")
51
51
  ```
52
52
 
53
53
  Yirgacheffe will automatically infer if you want to do an intersection of maps or a union of the maps based on the operators you use (see below for a full table). You can explicitly override that if you want.
@@ -85,27 +85,28 @@ If you have set either the intersection window or union window on a layer and yo
85
85
  If doing per-layer operations isn't applicable for your application, you can read the pixel values for all layers (including VectorLayers) by calling `read_array` similarly to how you would for gdal. The data you access will be relative to the specified window - that is, if you've called either `set_window_for_intersection` or `set_window_for_union` then `read_array` will be relative to that and Yirgacheffe will clip or expand the data with zero values as necessary.
86
86
 
87
87
 
88
- ### Todo but not supported
89
-
90
- Yirgacheffe is work in progress, so things planned but not supported currently:
91
-
92
- * Dynamic pixel scale adjustment - all raster layers must be provided at the same pixel scale currently *NOW IN EXPERIMENTAL TESTING, SEE BELOW*
93
- * A fold operation
94
- * CUDA/Metal support via CUPY/MLX
95
- * Dispatching work across multiple CPUs *NOW IN EXPERIMENTAL TESTING, SEE BELOW*
96
-
97
-
98
-
99
88
  ## Layer types
100
89
 
90
+ Note that as part of the move to the next major release, 2.0, we are adding simpler ways to create layers. Not all of those have been implemented yet, which is why this section has some inconsistencies. However, given many of the common cases are already covered, we present the new 2.0 style methods (`read_raster` and similar) here so you can write cleaner code today rather than making people wait for the final 2.0 release.
91
+
101
92
  ### RasterLayer
102
93
 
103
94
  This is your basic GDAL raster layer, which you load from a geotiff.
104
95
 
105
96
  ```python
97
+ from yirgaceffe.layers import RasterLayer
98
+
106
99
  with RasterLayer.layer_from_file('test1.tif') as layer:
107
- data = layer.read_array(0, 0, 10, 10)
108
- ...
100
+ total = layer.sum()
101
+ ```
102
+
103
+ The new 2.0 way of doing this is:
104
+
105
+ ```python
106
+ import yirgacheffe as yg
107
+
108
+ with yg.read_raster('test.tif') as layer:
109
+ total = layer.sum()
109
110
  ```
110
111
 
111
112
  You can also create empty layers ready for you to store results, either by taking the dimensions from an existing layer. In both these cases you can either provide a filename to which the data will be written, or if you do not provide a filename then the layer will only exist in memory - this will be more efficient if the layer is being used for intermediary results.
@@ -142,51 +143,30 @@ This layer will load vector data and rasterize it on demand as part of a calcula
142
143
  Because it will be rasterized you need to specify the pixel scale and map projection to be used when rasterising the data, and the common way to do that is by using one of your other layers.
143
144
 
144
145
  ```python
145
- with VectorLayer.layer_from_file('range.gpkg', 'id_no == 42', layer1.pixel_scale, layer1.projection) as layer:
146
- ...
147
- ```
148
-
149
- This class was formerly called `DynamicVectorRangeLayer`, a name now deprecated.
150
-
146
+ from yirgaceffe import WGS_84_PROJECTION
147
+ from yirgaceffe.window import PixelScale
148
+ from yirgaceffe.layers import VectorLayer
151
149
 
152
- ### UniformAreaLayer
153
-
154
- In certain calculations you find you have a layer where all the rows of data are the same - notably geotiffs that contain the area of a given pixel do this due to how conventional map projections work. It's hugely inefficient to load the full map into memory, so whilst you could just load them as `Layer` types, we recommend you do:
155
-
156
- ```python
157
- with UniformAreaLayer('area.tiff') as layer:
158
- ....
150
+ with VectorLayer.layer_from_file('range.gpkg', PixelScale(0.001, -0.001), WGS_84_PROJECTION) as layer:
151
+ ...
159
152
  ```
160
153
 
161
- Note that loading this data can still be very slow, due to how image compression works. So if you plan to use area.tiff more than once, we recommend use save an optimised version - this will do the slow uncompression once and then save a minimal file to speed up future processing:
154
+ The new 2.0 way of doing this is:
162
155
 
163
156
  ```python
164
- if not os.path.exists('yirgacheffe_area.tiff'):
165
- UniformAreaLayer.generate_narrow_area_projection('area.tiff', 'yirgacheffe_area.tiff')
166
- area_layer = UniformAreaLayer('yirgacheffe_area.tiff')
167
- ```
168
-
169
-
170
- ### ConstantLayer
157
+ import yirgacheffe as yg
171
158
 
172
- This is there to simplify code when you have some optional layers. Rather than littering your code with checks, you can just use a constant layer, which can be included in calculations and will just return an fixed value as if it wasn't there. Useful with 0.0 or 1.0 for sum or multiplication null layers.
173
-
174
- ```python
175
- try:
176
- area_layer = UniformAreaLayer('myarea.tiff')
177
- except FileDoesNotExist:
178
- area_layer = ConstantLayer(0.0)
159
+ with yg.read_shape('range.gpkg', (0.001, -0.001), WGS_84_PROJECTION) as layer:
160
+ ...
179
161
  ```
180
162
 
163
+ 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:
181
164
 
182
- ### H3CellLayer
183
-
184
- If you have H3 installed, you can generate a mask layer based on an H3 cell identifier, where pixels inside the cell will have a value of 1, and those outside will have a value of 0.
185
-
186
- Becuase it will be rasterized you need to specify the pixel scale and map projection to be used when rasterising the data, and the common way to do that is by using one of your other layers.
187
165
 
188
166
  ```python
189
- hex_cell_layer = H3CellLayer('88972eac11fffff', layer1.pixel_scale, layer1.projection)
167
+ with yg.read_raster("test.tif") as raster_layer:
168
+ with yg.read_shape_like('range.gpkg', raster_layer) as shape_layer:
169
+ ...
190
170
  ```
191
171
 
192
172
  ### GroupLayer
@@ -215,6 +195,15 @@ with GroupLayer.layer_from_directory('.') as all_tiles:
215
195
  ...
216
196
  ```
217
197
 
198
+ The new 2.0 way of doing this is:
199
+
200
+ ```python
201
+ import yirgacheffe as yg
202
+
203
+ with yg.read_rasters(['tile_N10_E10.tif', 'tile_N20_E10.tif']) as all_tiles:
204
+ ...
205
+ ```
206
+
218
207
  ### TiledGroupLayer
219
208
 
220
209
  This is a specialisation of GroupLayer, which you can use if your layers are all the same size and form a grid, as is often the case with map tiles. In this case the rendering code can be optimised and this class is significantly faster that GroupLayer.
@@ -225,11 +214,59 @@ tile2 = RasterLayer.layer_from_file('tile_N20_E10.tif')
225
214
  all_tiles = TiledGroupLayer([tile1, tile2])
226
215
  ```
227
216
 
217
+ The new 2.0 way of doing this is:
218
+
219
+ ```python
220
+ import yirgacheffe as yg
221
+
222
+ with yg.read_rasters(['tile_N10_E10.tif', 'tile_N20_E10.tif'], tiled=True) as all_tiles:
223
+ ...
224
+ ```
225
+
228
226
  Notes:
229
227
 
230
228
  * You can have missing tiles, and these will be filled in with zeros.
231
229
  * You can have tiles that overlap, so long as they still conform to the rule that all tiles are the same size and on a grid.
232
230
 
231
+ ### ConstantLayer
232
+
233
+ This is there to simplify code when you have some optional layers. Rather than littering your code with checks, you can just use a constant layer, which can be included in calculations and will just return an fixed value as if it wasn't there. Useful with 0.0 or 1.0 for sum or multiplication null layers.
234
+
235
+ ```python
236
+ try:
237
+ area_layer = RasterLayer.layer_from_file('myarea.tiff')
238
+ except FileDoesNotExist:
239
+ area_layer = ConstantLayer(0.0)
240
+ ```
241
+
242
+ ### H3CellLayer
243
+
244
+ If you have H3 installed, you can generate a mask layer based on an H3 cell identifier, where pixels inside the cell will have a value of 1, and those outside will have a value of 0.
245
+
246
+ Becuase it will be rasterized you need to specify the pixel scale and map projection to be used when rasterising the data, and the common way to do that is by using one of your other layers.
247
+
248
+ ```python
249
+ hex_cell_layer = H3CellLayer('88972eac11fffff', layer1.pixel_scale, layer1.projection)
250
+ ```
251
+
252
+
253
+ ### UniformAreaLayer
254
+
255
+ In certain calculations you find you have a layer where all the rows of data are the same - notably geotiffs that contain the area of a given pixel do this due to how conventional map projections work. It's hugely inefficient to load the full map into memory, so whilst you could just load them as `Layer` types, we recommend you do:
256
+
257
+ ```python
258
+ with UniformAreaLayer('area.tiff') as layer:
259
+ ....
260
+ ```
261
+
262
+ Note that loading this data can still be very slow, due to how image compression works. So if you plan to use area.tiff more than once, we recommend use save an optimised version - this will do the slow uncompression once and then save a minimal file to speed up future processing:
263
+
264
+ ```python
265
+ if not os.path.exists('yirgacheffe_area.tiff'):
266
+ UniformAreaLayer.generate_narrow_area_projection('area.tiff', 'yirgacheffe_area.tiff')
267
+ area_layer = UniformAreaLayer('yirgacheffe_area.tiff')
268
+ ```
269
+
233
270
 
234
271
  ## Supported operations on layers
235
272
 
@@ -256,6 +293,24 @@ with RasterLayer.layer_from_file('test1.tif') as layer1:
256
293
  calc.save(result)
257
294
  ```
258
295
 
296
+
297
+ The new 2.0 way of doing these are:
298
+
299
+ ```python
300
+ with yg.read_raster('test1.tif') as layer1:
301
+ with yg.read_raster('test2.tif') as layer2:
302
+ result = layer1 + layer2
303
+ result.to_geotiff("result.tif")
304
+ ```
305
+
306
+ or
307
+
308
+ ```python
309
+ with yg.read_raster('test1.tif') as layer1:
310
+ result = layer1 * 42.0
311
+ result.to_geotiff("result.tif")
312
+ ```
313
+
259
314
  ### Boolean testing
260
315
 
261
316
  Testing for equality, less than, less than or equal, greater than, and greater than or equal are supported on layers, along with logical or and logical and, as per this example, where `elevation_upper` and `elevation_lower` are scalar values:
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "yirgacheffe"
9
- version = "1.4.0"
9
+ version = "1.5.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" }]
@@ -16,7 +16,8 @@ dependencies = [
16
16
  "numpy",
17
17
  "gdal[numpy]",
18
18
  "scikit-image",
19
- "torch"
19
+ "torch",
20
+ "dill",
20
21
  ]
21
22
  requires-python = ">=3.10"
22
23
 
@@ -19,3 +19,13 @@ def test_area_operators(lhs: Area, rhs: Area, is_equal: bool, overlaps: bool) ->
19
19
  assert (lhs != rhs) == (not is_equal)
20
20
  assert (lhs.overlaps(rhs)) == overlaps
21
21
  assert (rhs.overlaps(lhs)) == overlaps
22
+ assert not lhs.is_world
23
+ assert not rhs.is_world
24
+
25
+ def test_global_area() -> None:
26
+ area = Area.world()
27
+ assert area.is_world
28
+
29
+ other_area = Area(-10.0, 10.0, 10.0, -10.0)
30
+ assert area.overlaps(other_area)
31
+ assert other_area.overlaps(area)