yirgacheffe 1.8.1__py3-none-any.whl → 1.9.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of yirgacheffe might be problematic. Click here for more details.
- yirgacheffe/_operators.py +93 -2
- yirgacheffe/constants.py +4 -4
- yirgacheffe/layers/base.py +0 -19
- yirgacheffe/window.py +20 -7
- {yirgacheffe-1.8.1.dist-info → yirgacheffe-1.9.1.dist-info}/METADATA +7 -1
- {yirgacheffe-1.8.1.dist-info → yirgacheffe-1.9.1.dist-info}/RECORD +10 -10
- {yirgacheffe-1.8.1.dist-info → yirgacheffe-1.9.1.dist-info}/WHEEL +0 -0
- {yirgacheffe-1.8.1.dist-info → yirgacheffe-1.9.1.dist-info}/entry_points.txt +0 -0
- {yirgacheffe-1.8.1.dist-info → yirgacheffe-1.9.1.dist-info}/licenses/LICENSE +0 -0
- {yirgacheffe-1.8.1.dist-info → yirgacheffe-1.9.1.dist-info}/top_level.txt +0 -0
yirgacheffe/_operators.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import builtins
|
|
3
4
|
import logging
|
|
4
5
|
import math
|
|
5
6
|
import multiprocessing
|
|
@@ -21,6 +22,7 @@ import numpy as np
|
|
|
21
22
|
import numpy.typing as npt
|
|
22
23
|
from osgeo import gdal
|
|
23
24
|
from dill import dumps, loads # type: ignore
|
|
25
|
+
from pyproj import Transformer
|
|
24
26
|
|
|
25
27
|
from . import constants, __version__
|
|
26
28
|
from .rounding import round_up_pixels, round_down_pixels
|
|
@@ -290,6 +292,53 @@ class LayerMathMixin:
|
|
|
290
292
|
datatype=datatype
|
|
291
293
|
)
|
|
292
294
|
|
|
295
|
+
def latlng_for_pixel(self, x: int, y: int) -> tuple[float, float]:
|
|
296
|
+
"""Get geo coords for pixel. This is relative to the set view window.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
x: X axis position within raster
|
|
300
|
+
y: Y axis position within raster
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
A tuple containing the (latitude, longitude).
|
|
304
|
+
"""
|
|
305
|
+
projection = self.map_projection # type: ignore[attr-defined]
|
|
306
|
+
area = self.area # type: ignore[attr-defined]
|
|
307
|
+
if projection is None:
|
|
308
|
+
raise ValueError("Map has not projection space")
|
|
309
|
+
pixel_scale = projection.scale
|
|
310
|
+
coord_in_raster_space = (
|
|
311
|
+
(y * pixel_scale.ystep) + area.top,
|
|
312
|
+
(x * pixel_scale.xstep) + area.left,
|
|
313
|
+
)
|
|
314
|
+
transformer = Transformer.from_crs(projection.name, "EPSG:4326")
|
|
315
|
+
return transformer.transform(*coord_in_raster_space)
|
|
316
|
+
|
|
317
|
+
def pixel_for_latlng(self, lat: float, lng: float) -> tuple[int, int]:
|
|
318
|
+
"""Get pixel for geo coords. This is relative to the set view window.
|
|
319
|
+
Result is rounded down to nearest pixel.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
lat: Geospatial latitude in WGS84
|
|
323
|
+
lng: Geospatial longitude in WGS84
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
A tuple containing the x, y coordinates in pixel space.
|
|
327
|
+
"""
|
|
328
|
+
projection = self.map_projection # type: ignore[attr-defined]
|
|
329
|
+
area = self.area # type: ignore[attr-defined]
|
|
330
|
+
if projection is None:
|
|
331
|
+
raise ValueError("Map has not projection space")
|
|
332
|
+
|
|
333
|
+
transformer = Transformer.from_crs("EPSG:4326", projection.name)
|
|
334
|
+
x, y = transformer.transform(lng,lat)
|
|
335
|
+
|
|
336
|
+
pixel_scale = projection.scale
|
|
337
|
+
return (
|
|
338
|
+
round_down_pixels((x - area.left) / pixel_scale.xstep, builtins.abs(pixel_scale.xstep)),
|
|
339
|
+
round_down_pixels((y - area.top) / pixel_scale.ystep, builtins.abs(pixel_scale.ystep)),
|
|
340
|
+
)
|
|
341
|
+
|
|
293
342
|
|
|
294
343
|
class LayerOperation(LayerMathMixin):
|
|
295
344
|
|
|
@@ -654,8 +703,8 @@ class LayerOperation(LayerMathMixin):
|
|
|
654
703
|
for yoffset in range(0, computation_window.ysize, self.ystep):
|
|
655
704
|
if callback:
|
|
656
705
|
callback(yoffset / computation_window.ysize)
|
|
657
|
-
step=self.ystep
|
|
658
|
-
if yoffset+step > computation_window.ysize:
|
|
706
|
+
step = self.ystep
|
|
707
|
+
if yoffset + step > computation_window.ysize:
|
|
659
708
|
step = computation_window.ysize - yoffset
|
|
660
709
|
chunk = self._eval(computation_area, projection, yoffset, step, computation_window)
|
|
661
710
|
if isinstance(chunk, (float, int)):
|
|
@@ -935,6 +984,48 @@ class LayerOperation(LayerMathMixin):
|
|
|
935
984
|
|
|
936
985
|
return result
|
|
937
986
|
|
|
987
|
+
def read_array(self, x: int, y: int, width: int, height: int) -> np.ndarray:
|
|
988
|
+
"""Read an area of pixles from the specified area of a calculated raster.
|
|
989
|
+
|
|
990
|
+
Args:
|
|
991
|
+
x: X axis offset for reading
|
|
992
|
+
y: Y axis offset for reading
|
|
993
|
+
width: Width of data to read
|
|
994
|
+
height: Height of data to read
|
|
995
|
+
|
|
996
|
+
Returns:
|
|
997
|
+
A numpy array containing the requested data. If the region of data read goes
|
|
998
|
+
beyond the bounds of the calculation that area will be filled with zeros.
|
|
999
|
+
"""
|
|
1000
|
+
projection = self.map_projection
|
|
1001
|
+
if projection is None:
|
|
1002
|
+
raise ValueError("No map projection specified for layers in expression")
|
|
1003
|
+
|
|
1004
|
+
computation_window = Window(0, 0, width, height)
|
|
1005
|
+
expression_area = self.area
|
|
1006
|
+
pixel_scale = projection.scale
|
|
1007
|
+
left = expression_area.left + (x * pixel_scale.xstep)
|
|
1008
|
+
top = expression_area.top + (y * pixel_scale.ystep)
|
|
1009
|
+
computation_area = Area(
|
|
1010
|
+
left=left,
|
|
1011
|
+
top=top,
|
|
1012
|
+
right=left + (width * pixel_scale.xstep),
|
|
1013
|
+
bottom=top + (height * pixel_scale.ystep),
|
|
1014
|
+
)
|
|
1015
|
+
|
|
1016
|
+
chunks = []
|
|
1017
|
+
for yoffset in range(0, height, self.ystep):
|
|
1018
|
+
step = self.ystep
|
|
1019
|
+
if yoffset + step > height:
|
|
1020
|
+
step = height - yoffset
|
|
1021
|
+
chunk = self._eval(computation_area, projection, yoffset, step, computation_window)
|
|
1022
|
+
if isinstance(chunk, (float, int)):
|
|
1023
|
+
chunk = backend.full((step, computation_window.xsize), chunk)
|
|
1024
|
+
chunks.append(chunk)
|
|
1025
|
+
res = np.vstack(chunks)
|
|
1026
|
+
|
|
1027
|
+
return res
|
|
1028
|
+
|
|
938
1029
|
class ShaderStyleOperation(LayerOperation):
|
|
939
1030
|
|
|
940
1031
|
def _eval(self, area, projection, index, step, target_window=None):
|
yirgacheffe/constants.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import pyproj
|
|
2
|
+
|
|
1
3
|
YSTEP = 512
|
|
2
4
|
MINIMUM_CHUNKS_PER_THREAD = 1
|
|
3
5
|
|
|
4
6
|
# I don't really want this here, but it's just too useful having it exposed
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
'UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],'\
|
|
8
|
-
'AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]'
|
|
7
|
+
# This used to be a fixed string, but now it is at least programmatically generated
|
|
8
|
+
WGS_84_PROJECTION = pyproj.CRS.from_epsg(4326).to_wkt(version='WKT1_GDAL')
|
yirgacheffe/layers/base.py
CHANGED
|
@@ -331,22 +331,3 @@ class YirgacheffeLayer(LayerMathMixin):
|
|
|
331
331
|
"""
|
|
332
332
|
res = self._read_array(x, y, width, height)
|
|
333
333
|
return backend.demote_array(res)
|
|
334
|
-
|
|
335
|
-
def latlng_for_pixel(self, x_coord: int, y_coord: int) -> tuple[float, float]:
|
|
336
|
-
"""Get geo coords for pixel. This is relative to the set view window."""
|
|
337
|
-
if self._projection is None or "WGS 84" not in self._projection.name:
|
|
338
|
-
raise NotImplementedError("Not yet supported for other projections")
|
|
339
|
-
return (
|
|
340
|
-
(y_coord * self._projection.ystep) + self.area.top,
|
|
341
|
-
(x_coord * self._projection.xstep) + self.area.left
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
def pixel_for_latlng(self, lat: float, lng: float) -> tuple[int, int]:
|
|
345
|
-
"""Get pixel for geo coords. This is relative to the set view window.
|
|
346
|
-
Result is rounded down to nearest pixel."""
|
|
347
|
-
if self._projection is None or "WGS 84" not in self._projection.name:
|
|
348
|
-
raise NotImplementedError("Not yet supported for other projections")
|
|
349
|
-
return (
|
|
350
|
-
round_down_pixels((lng - self.area.left) / self._projection.xstep, abs(self._projection.xstep)),
|
|
351
|
-
round_down_pixels((lat - self.area.top) / self._projection.ystep, abs(self._projection.ystep)),
|
|
352
|
-
)
|
yirgacheffe/window.py
CHANGED
|
@@ -4,37 +4,50 @@ import sys
|
|
|
4
4
|
from collections import namedtuple
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
|
|
7
|
+
import pyproj
|
|
8
|
+
|
|
7
9
|
PixelScale = namedtuple('PixelScale', ['xstep', 'ystep'])
|
|
8
10
|
|
|
9
|
-
@dataclass
|
|
10
11
|
class MapProjection:
|
|
11
12
|
"""Records the map projection and the size of the pixels in a layer.
|
|
12
13
|
|
|
13
14
|
This superceeeds the old PixelScale class, which will be removed in version 2.0.
|
|
14
15
|
|
|
15
16
|
Args:
|
|
16
|
-
name: The map projection used.
|
|
17
|
+
name: The map projection used in WKT format, or as "epsg:xxxx" or "esri:xxxx".
|
|
17
18
|
xstep: The number of units horizontal distance a step of one pixel makes in the map projection.
|
|
18
19
|
ystep: The number of units vertical distance a step of one pixel makes in the map projection.
|
|
19
20
|
|
|
20
21
|
Attributes:
|
|
21
|
-
name: The map projection used.
|
|
22
|
+
name: The map projection used in WKT format.
|
|
22
23
|
xstep: The number of units horizontal distance a step of one pixel makes in the map projection.
|
|
23
24
|
ystep: The number of units vertical distance a step of one pixel makes in the map projection.
|
|
24
25
|
"""
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
def __init__(self, projection_string: str, xstep: float, ystep: float) -> None:
|
|
28
|
+
try:
|
|
29
|
+
self.crs = pyproj.CRS.from_string(projection_string)
|
|
30
|
+
except pyproj.exceptions.CRSError as exc:
|
|
31
|
+
raise ValueError(f"Invalid projection: {projection_string}") from exc
|
|
32
|
+
self.xstep = xstep
|
|
33
|
+
self.ystep = ystep
|
|
29
34
|
|
|
30
35
|
def __eq__(self, other) -> bool:
|
|
31
36
|
if other is None:
|
|
32
37
|
return True
|
|
33
38
|
# to avoid circular dependancies
|
|
34
39
|
from .rounding import are_pixel_scales_equal_enough # pylint: disable=C0415
|
|
35
|
-
return (self.
|
|
40
|
+
return (self.crs == other.crs) and \
|
|
36
41
|
are_pixel_scales_equal_enough([self.scale, other.scale])
|
|
37
42
|
|
|
43
|
+
@property
|
|
44
|
+
def name(self) -> str:
|
|
45
|
+
return self.crs.to_wkt()
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def epsg(self) -> int | None:
|
|
49
|
+
return self.crs.to_epsg()
|
|
50
|
+
|
|
38
51
|
@property
|
|
39
52
|
def scale(self) -> PixelScale:
|
|
40
53
|
return PixelScale(self.xstep, self.ystep)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: yirgacheffe
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.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
|
|
@@ -27,6 +27,7 @@ Requires-Dist: dill
|
|
|
27
27
|
Requires-Dist: deprecation
|
|
28
28
|
Requires-Dist: tomli
|
|
29
29
|
Requires-Dist: h3
|
|
30
|
+
Requires-Dist: pyproj
|
|
30
31
|
Provides-Extra: mlx
|
|
31
32
|
Requires-Dist: mlx; extra == "mlx"
|
|
32
33
|
Provides-Extra: dev
|
|
@@ -44,6 +45,11 @@ Dynamic: license-file
|
|
|
44
45
|
|
|
45
46
|
# Yirgacheffe: a declarative geospatial library for Python to make data-science with maps easier
|
|
46
47
|
|
|
48
|
+
[](https://github.com/quantifyearth/yirgacheffe/actions)
|
|
49
|
+
[](https://yirgacheffe.org)
|
|
50
|
+
[](https://pypi.org/project/yirgacheffe/)
|
|
51
|
+
|
|
52
|
+
|
|
47
53
|
## Overview
|
|
48
54
|
|
|
49
55
|
Yirgacheffe is an attempt to wrap raster and polygon geospatial datasets such that you can do computational work on them as a whole or at the pixel level, but without having to do a lot of the grunt work of working out where you need to be in rasters, or managing how much you can load into memory safely.
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
yirgacheffe/__init__.py,sha256=OOzfXtafPoDpAsNRC08BXjmwv0hBp-mNFCjwplGs9lY,668
|
|
2
2
|
yirgacheffe/_core.py,sha256=AU6tlqovBV_l1dNZs6AlHSw59Z0U6pStUaQZvJGiLhM,5721
|
|
3
|
-
yirgacheffe/_operators.py,sha256=
|
|
4
|
-
yirgacheffe/constants.py,sha256=
|
|
3
|
+
yirgacheffe/_operators.py,sha256=7ReNOLW9MUGN5wI2wbEnAhyaeO5ZQGTnwTib72HB8y8,39656
|
|
4
|
+
yirgacheffe/constants.py,sha256=bKUjOGNj19zwggV79lJgK7tiv51DH2-rgNOKswl2gvQ,293
|
|
5
5
|
yirgacheffe/operators.py,sha256=nw-BpnAwTjCwFtjosa8wKd2MGUuC0PJR5jACFdLhqCg,412
|
|
6
6
|
yirgacheffe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
yirgacheffe/rounding.py,sha256=ZNuAaxsWfzYETC_G9H5weY1ZOci2pihEKTVrUiIqfZw,2257
|
|
8
|
-
yirgacheffe/window.py,sha256=
|
|
8
|
+
yirgacheffe/window.py,sha256=QuyBLOwKFI0XkEQ4Bd2hdELPbJSfHL7mt5KSi7CIHcE,9505
|
|
9
9
|
yirgacheffe/_backends/__init__.py,sha256=jN-2iRrHStnPI6cNL7XhwhsROtI0EaGfIrbF5c-ECV0,334
|
|
10
10
|
yirgacheffe/_backends/enumeration.py,sha256=9bcCXz9Ssrh8Oh1iazodkx6Gm2kQBi9HQ9z9zehS4AE,1806
|
|
11
11
|
yirgacheffe/_backends/mlx.py,sha256=U1gl1lK1mZXLEET6ylF1TNs6WJ0PBEvfSk7ppn28n8w,6203
|
|
12
12
|
yirgacheffe/_backends/numpy.py,sha256=Gxx49JJH79GFEkKIpV6IyjCUcdtN5-qLlzRfylzKhS4,4142
|
|
13
13
|
yirgacheffe/layers/__init__.py,sha256=mYKjw5YTcMNv_hMy7a6K4yRzIuNUbR8WuBTw4WIAmSk,435
|
|
14
14
|
yirgacheffe/layers/area.py,sha256=wJcMHbLJBaXS4BeFbu5rYeKfgu3gvaE9hwQ5j6aw-y4,3976
|
|
15
|
-
yirgacheffe/layers/base.py,sha256=
|
|
15
|
+
yirgacheffe/layers/base.py,sha256=7b4WXuvnmCv8mR0iyCIuSEolnV8D3f2vtCaYlcJCIa8,13201
|
|
16
16
|
yirgacheffe/layers/constant.py,sha256=gtkQ98Z01CYYDgFElswtRZY4ZG3UnS5NIAoIVue5ufk,1481
|
|
17
17
|
yirgacheffe/layers/group.py,sha256=yaqf-ra_Vh59yrWcz7-OvJ1fBnTcBXZd18AfRDN5Ymo,16157
|
|
18
18
|
yirgacheffe/layers/h3layer.py,sha256=Rq1bFo7CApIh5NdBcV7hSj3hm-DszY79nhYsTRAvJ_g,9916
|
|
19
19
|
yirgacheffe/layers/rasters.py,sha256=zBE9uXm6LvAQF2_XdQzcOgJQOQWGmuPflY5JNDrUf3k,13527
|
|
20
20
|
yirgacheffe/layers/rescaled.py,sha256=gEFbXeYxX1nVn7eQYmbGww90_yc5ENmgQrD_WxXxpQE,3352
|
|
21
21
|
yirgacheffe/layers/vectors.py,sha256=A27kuTr0C9BZhHG0-cplNEa7aSNcse37Pm9xTjEzv-c,19990
|
|
22
|
-
yirgacheffe-1.
|
|
23
|
-
yirgacheffe-1.
|
|
24
|
-
yirgacheffe-1.
|
|
25
|
-
yirgacheffe-1.
|
|
26
|
-
yirgacheffe-1.
|
|
27
|
-
yirgacheffe-1.
|
|
22
|
+
yirgacheffe-1.9.1.dist-info/licenses/LICENSE,sha256=dNSHwUCJr6axStTKDEdnJtfmDdFqlE3h1NPCveqPfnY,757
|
|
23
|
+
yirgacheffe-1.9.1.dist-info/METADATA,sha256=aRcZJh0nb6tBa7W2GUXCjbtYqmPFDddbJ-nmONuqMHk,24186
|
|
24
|
+
yirgacheffe-1.9.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
25
|
+
yirgacheffe-1.9.1.dist-info/entry_points.txt,sha256=j4KgHXbVGbGyfTySc1ypBdERpfihO4WNjppvCdE9HjE,52
|
|
26
|
+
yirgacheffe-1.9.1.dist-info/top_level.txt,sha256=9DBFlKO2Ld3hG6TuE3qOTd3Tt8ugTiXil4AN4Wr9_y0,12
|
|
27
|
+
yirgacheffe-1.9.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|