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 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
- WGS_84_PROJECTION = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,'\
6
- 'AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],'\
7
- 'UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],'\
8
- 'AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]'
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')
@@ -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
- name : str
27
- xstep : float
28
- ystep : float
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.name == other.name) and \
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.8.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
+ [![CI](https://github.com/quantifyearth/yirgacheffe/actions/workflows/pull-request.yml/badge.svg?branch=main)](https://github.com/quantifyearth/yirgacheffe/actions)
49
+ [![Documentation](https://img.shields.io/badge/docs-yirgacheffe.org-blue)](https://yirgacheffe.org)
50
+ [![PyPI version](https://img.shields.io/pypi/v/yirgacheffe)](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=OiR4pCVILmdXDmG37YILJuYjcxZlRussrJC7DeyoOts,36070
4
- yirgacheffe/constants.py,sha256=uCWJwec3-ND-zVxYbsk1sdHKANl3ToNCTPg7MZb0j2g,434
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=WTCqX4Tw24siD0l13Pxl-GPufiC5M1OwZfhrNmJe_Gg,8986
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=dQQLFKP05QK4C73N1DCDYT8D8eD15HuSQKN5KrABeHg,14289
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.8.1.dist-info/licenses/LICENSE,sha256=dNSHwUCJr6axStTKDEdnJtfmDdFqlE3h1NPCveqPfnY,757
23
- yirgacheffe-1.8.1.dist-info/METADATA,sha256=iPbaENoRBTey7wYvg3OUTKjkulgoFS0wcwJoRJ2HcYQ,23797
24
- yirgacheffe-1.8.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
- yirgacheffe-1.8.1.dist-info/entry_points.txt,sha256=j4KgHXbVGbGyfTySc1ypBdERpfihO4WNjppvCdE9HjE,52
26
- yirgacheffe-1.8.1.dist-info/top_level.txt,sha256=9DBFlKO2Ld3hG6TuE3qOTd3Tt8ugTiXil4AN4Wr9_y0,12
27
- yirgacheffe-1.8.1.dist-info/RECORD,,
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,,