yirgacheffe 1.8.1__py3-none-any.whl → 1.9.0__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
@@ -654,8 +654,8 @@ class LayerOperation(LayerMathMixin):
654
654
  for yoffset in range(0, computation_window.ysize, self.ystep):
655
655
  if callback:
656
656
  callback(yoffset / computation_window.ysize)
657
- step=self.ystep
658
- if yoffset+step > computation_window.ysize:
657
+ step = self.ystep
658
+ if yoffset + step > computation_window.ysize:
659
659
  step = computation_window.ysize - yoffset
660
660
  chunk = self._eval(computation_area, projection, yoffset, step, computation_window)
661
661
  if isinstance(chunk, (float, int)):
@@ -935,6 +935,48 @@ class LayerOperation(LayerMathMixin):
935
935
 
936
936
  return result
937
937
 
938
+ def read_array(self, x: int, y: int, width: int, height: int) -> np.ndarray:
939
+ """Read an area of pixles from the specified area of a calculated raster.
940
+
941
+ Args:
942
+ x: X axis offset for reading
943
+ y: Y axis offset for reading
944
+ width: Width of data to read
945
+ height: Height of data to read
946
+
947
+ Returns:
948
+ A numpy array containing the requested data. If the region of data read goes
949
+ beyond the bounds of the calculation that area will be filled with zeros.
950
+ """
951
+ projection = self.map_projection
952
+ if projection is None:
953
+ raise ValueError("No map projection specified for layers in expression")
954
+
955
+ computation_window = Window(0, 0, width, height)
956
+ expression_area = self.area
957
+ pixel_scale = projection.scale
958
+ left = expression_area.left + (x * pixel_scale.xstep)
959
+ top = expression_area.top + (y * pixel_scale.ystep)
960
+ computation_area = Area(
961
+ left=left,
962
+ top=top,
963
+ right=left + (width * pixel_scale.xstep),
964
+ bottom=top + (height * pixel_scale.ystep),
965
+ )
966
+
967
+ chunks = []
968
+ for yoffset in range(0, height, self.ystep):
969
+ step = self.ystep
970
+ if yoffset + step > height:
971
+ step = height - yoffset
972
+ chunk = self._eval(computation_area, projection, yoffset, step, computation_window)
973
+ if isinstance(chunk, (float, int)):
974
+ chunk = backend.full((step, computation_window.xsize), chunk)
975
+ chunks.append(chunk)
976
+ res = np.vstack(chunks)
977
+
978
+ return res
979
+
938
980
  class ShaderStyleOperation(LayerOperation):
939
981
 
940
982
  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')
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
  from typing import Any, Sequence
3
3
 
4
4
  import deprecation
5
+ from pyproj import Transformer
5
6
 
6
7
  from .. import __version__
7
8
  from .._operators import LayerMathMixin
@@ -332,21 +333,47 @@ class YirgacheffeLayer(LayerMathMixin):
332
333
  res = self._read_array(x, y, width, height)
333
334
  return backend.demote_array(res)
334
335
 
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
336
+ def latlng_for_pixel(self, x: int, y: int) -> tuple[float, float]:
337
+ """Get geo coords for pixel. This is relative to the set view window.
338
+
339
+ Args:
340
+ x: X axis position within raster
341
+ y: Y axis position within raster
342
+
343
+ Returns:
344
+ A tuple containing the (latitude, longitude).
345
+ """
346
+ projection = self.map_projection
347
+ if projection is None:
348
+ raise ValueError("Map has not projection space")
349
+ pixel_scale = projection.scale
350
+ coord_in_raster_space = (
351
+ (y * pixel_scale.ystep) + self.area.top,
352
+ (x * pixel_scale.xstep) + self.area.left,
342
353
  )
354
+ transformer = Transformer.from_crs(projection.name, "EPSG:4326")
355
+ return transformer.transform(*coord_in_raster_space)
343
356
 
344
357
  def pixel_for_latlng(self, lat: float, lng: float) -> tuple[int, int]:
345
358
  """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")
359
+ Result is rounded down to nearest pixel.
360
+
361
+ Args:
362
+ lat: Geospatial latitude in WGS84
363
+ lng: Geospatial longitude in WGS84
364
+
365
+ Returns:
366
+ A tuple containing the x, y coordinates in pixel space.
367
+ """
368
+ projection = self.map_projection
369
+ if projection is None:
370
+ raise ValueError("Map has not projection space")
371
+
372
+ transformer = Transformer.from_crs("EPSG:4326", projection.name)
373
+ x, y = transformer.transform(lng,lat)
374
+
375
+ pixel_scale = projection.scale
349
376
  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)),
377
+ round_down_pixels((x - self.area.left) / pixel_scale.xstep, abs(pixel_scale.xstep)),
378
+ round_down_pixels((y - self.area.top) / pixel_scale.ystep, abs(pixel_scale.ystep)),
352
379
  )
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.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
@@ -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=cnJgnfnfIRwrPB6sX2TVU_-s4eDyhZCPyemwIBFurxU,37723
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=EkfR3DOQ1I_tQZfpOSczs-sWFSP6uUMXdiB7xspfd4E,14954
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.0.dist-info/licenses/LICENSE,sha256=dNSHwUCJr6axStTKDEdnJtfmDdFqlE3h1NPCveqPfnY,757
23
+ yirgacheffe-1.9.0.dist-info/METADATA,sha256=ciRPPO-k-vbyd0gbg_u-uinc739ZoPuazQna9MpluCM,24186
24
+ yirgacheffe-1.9.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
+ yirgacheffe-1.9.0.dist-info/entry_points.txt,sha256=j4KgHXbVGbGyfTySc1ypBdERpfihO4WNjppvCdE9HjE,52
26
+ yirgacheffe-1.9.0.dist-info/top_level.txt,sha256=9DBFlKO2Ld3hG6TuE3qOTd3Tt8ugTiXil4AN4Wr9_y0,12
27
+ yirgacheffe-1.9.0.dist-info/RECORD,,