Skip to content

Commit 561df28

Browse files
authored
Merge pull request #126 from quantifyearth/mwd-unbounded-non-geographic-projections-area-per-pixel-fix
Make area per pixel layer work with non-geographic projections that l…
2 parents 570d17a + c55a32f commit 561df28

File tree

3 files changed

+46
-4
lines changed

3 files changed

+46
-4
lines changed

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
* Reduce initial import time.
66

7+
### Fixed
8+
9+
* Make area per pixel layer work with GeoTiffs with non-geographic projections that lack bounds in the projection description.
10+
711
## v1.12.4 (12/02/2026)
812

913
### Added

tests/unit/test_area_per_pixel.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import math
2+
13
import numpy as np
24
import yirgacheffe as yg
35

@@ -118,7 +120,7 @@ def test_non_global_area() -> None:
118120
assert (result == 1_000_000).all()
119121

120122

121-
def test_with_no_bounds_in_tiff() -> None:
123+
def test_with_no_bounds_in_tiff_geographic() -> None:
122124
# This is based on using real GeoTIFFs that come with no BBOX in their projection
123125
# description, which caused our original implementation as PYPROJ reports
124126
# the bounds as None.
@@ -131,3 +133,41 @@ def test_with_no_bounds_in_tiff() -> None:
131133
assert area_raster.map_projection == projection
132134
assert area_raster.window == yg.Window(0, 0, 36, 18)
133135
assert area_raster.area == yg.Area(-180, 90, 180, -90, projection)
136+
137+
138+
def test_with_no_bounds_in_tiff_nongeographic() -> None:
139+
# This is based on using real GeoTIFFs that come with no BBOX in their projection
140+
# description, which caused our original implementation as PYPROJ reports
141+
# the bounds as None.
142+
failing_wkt = 'PROJCRS["World_Mollweide",BASEGEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ID["EPSG",6326]],PRIMEM["Greenwich",0,ANGLEUNIT["Degree",0.0174532925199433]]],CONVERSION["unnamed",METHOD["Mollweide"],PARAMETER["Longitude of natural origin",0,ANGLEUNIT["Degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["False easting",0,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["easting",east,ORDER[1],LENGTHUNIT["metre",1,ID["EPSG",9001]]],AXIS["northing",north,ORDER[2],LENGTHUNIT["metre",1,ID["EPSG",9001]]]]' # pylint: disable=C0301
143+
projection = yg.MapProjection(failing_wkt, 10.0, -10.0)
144+
assert projection.crs.area_of_use is None
145+
146+
# from https://epsg.io/54009 we know:
147+
west = -18040095.7
148+
south = -9020047.85
149+
east = 18040095.7
150+
north = 9020047.85
151+
152+
# The rounding is a little off here as the hard numbers above
153+
# don't quite match the version we programatically generate with
154+
# pyproj in the AreaPerPixelLayer constructor. Really Area and Window
155+
# need an "is_close" type method.
156+
expected_window = yg.Window(
157+
0, 0,
158+
math.ceil((east - west) / 10),
159+
math.floor((north - south) / 10),
160+
)
161+
expected_area = yg.Area(
162+
left=math.floor(west / 10) * 10,
163+
top=math.floor(north / 10) * 10,
164+
right=math.ceil(east / 10) * 10,
165+
bottom=math.floor(south / 10) * 10,
166+
projection=projection,
167+
)
168+
169+
# But this is a geographic CRS, so we can assume it is global
170+
with yg.area_raster(projection) as area_raster:
171+
assert area_raster.map_projection == projection
172+
assert area_raster.window == expected_window
173+
assert area_raster.area == expected_area

yirgacheffe/layers/area_per_pixel.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,10 @@ def __init__(self, projection: MapProjection):
4141
if projection.crs.area_of_use is not None:
4242
west, south, east, north = projection.crs.area_of_use.bounds
4343
name = projection.crs.area_of_use.name
44-
elif projection.crs.is_geographic:
44+
else:
4545
# assume a global bounds
4646
west, south, east, north = -180.0, -90.0, 180.0, 90.0
4747
name = projection.crs.name
48-
else:
49-
raise ValueError("CRS for this map projection has no bounds")
5048

5149
x_scale = abs(projection.xstep)
5250
y_scale = abs(projection.ystep)

0 commit comments

Comments
 (0)