Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert AreaDefinitions to odc geoboxes #545

Merged
merged 14 commits into from
Dec 13, 2023
Merged
1 change: 1 addition & 0 deletions continuous_integration/environment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ dependencies:
- pytest-lazy-fixture
- importlib-metadata
- sphinx-reredirects
- odc-geo
17 changes: 17 additions & 0 deletions pyresample/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@
except ImportError:
da = None

try:
import odc.geo as odc_geo
except ModuleNotFoundError:
odc_geo = None

Check warning on line 63 in pyresample/geometry.py

View check run for this annotation

Codecov / codecov/patch

pyresample/geometry.py#L62-L63

Added lines #L62 - L63 were not covered by tests

from pyproj import CRS
from pyproj.enums import TransformDirection

Expand Down Expand Up @@ -354,7 +359,7 @@
new_dim1_sides = []
new_dim2_sides = []
for dim1_side, dim2_side in zip(dim1_sides, dim2_sides):
# FIXME: ~(~np.isfinite(dim1_side) | ~np.isfinite(dim1_side))

Check notice on line 362 in pyresample/geometry.py

View check run for this annotation

codefactor.io / CodeFactor

pyresample/geometry.py#L362

unresolved comment '# FIXME: ~(~np.isfinite(dim1_side) | ~np.isfinite(dim1_side))' (C100)
is_valid_mask = ~(np.isnan(dim1_side) | np.isnan(dim2_side))
if not is_valid_mask.any():
raise ValueError("Can't compute boundary coordinates. At least one side is completely invalid.")
Expand All @@ -363,7 +368,7 @@
return new_dim1_sides, new_dim2_sides

def _get_bbox_slices(self, vertices_per_side):
# FIXME: This currently replicate values if heigh/width < row_num/col_num !

Check notice on line 371 in pyresample/geometry.py

View check run for this annotation

codefactor.io / CodeFactor

pyresample/geometry.py#L371

unresolved comment '# FIXME: This currently replicate values if heigh/width < row_num/col_num !' (C100)
height, width = self.shape
if vertices_per_side is None:
row_num = height
Expand Down Expand Up @@ -630,7 +635,7 @@
(i.e. full disc geostationary area, Robinson projection, polar projections, ...)
by default only 50 points are selected.
"""
# FIXME: Add logic for out-of-earth disk projections

Check notice on line 638 in pyresample/geometry.py

View check run for this annotation

codefactor.io / CodeFactor

pyresample/geometry.py#L638

unresolved comment '# FIXME: Add logic for out-of-earth disk projections' (C100)
if self.is_geostationary:
return self._get_geostationary_boundary_sides(vertices_per_side=vertices_per_side, coordinates="geographic")
sides_lons, sides_lats = self._get_sides(coord_fun=self.get_lonlats, vertices_per_side=vertices_per_side)
Expand Down Expand Up @@ -1998,6 +2003,18 @@
return "EPSG:{}".format(self.crs.to_epsg())
return self.crs.to_proj4()

def to_odc_geobox(self):
"""Convert AreaDefinition to ODC GeoBox.

See: https://odc-geo.readthedocs.io/en/latest/
"""
if odc_geo is None:
raise ModuleNotFoundError("Please install 'odc-geo' to use this method.")

return odc_geo.geobox.GeoBox.from_bbox(bbox=self.area_extent, crs=self.crs,
resolution=odc_geo.Resolution(x=self.pixel_size_x, y=-self.pixel_size_y),
tight=True)

def create_areas_def(self):
"""Generate YAML formatted representation of this area.

Expand Down Expand Up @@ -2723,7 +2740,7 @@
Each list element is a numpy array representing a specific side of the geometry.
The order of the sides are [top", "right", "bottom", "left"]
"""
# FIXME: Add logic for out-of-earth-disk

Check notice on line 2743 in pyresample/geometry.py

View check run for this annotation

codefactor.io / CodeFactor

pyresample/geometry.py#L2743

unresolved comment '# FIXME: Add logic for out-of-earth-disk' (C100)
if self.is_geostationary:
return self._get_geostationary_boundary_sides(vertices_per_side=vertices_per_side,
coordinates="projection")
Expand Down
39 changes: 39 additions & 0 deletions pyresample/test/test_geometry/test_area.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,45 @@ def test_cartopy_crs_latlon_bounds(self, create_test_area):
latlong_crs = area_def.to_cartopy_crs()
np.testing.assert_allclose(latlong_crs.bounds, [-180, 180, -90, 90])

def test_to_odc_geobox_odc_missing(self, monkeypatch, stere_area):
"""Test odc-geo not installed."""
area = stere_area

with monkeypatch.context() as m:
m.setattr(pyresample.geometry, "odc_geo", None)

with pytest.raises(ModuleNotFoundError):
area.to_odc_geobox()

def test_to_odc_geobox(self, stere_area, create_test_area):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we xfail or skip if odc is missing? I think pytest has a decorator specifically for skipping if import fails.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good Idea. I will also add a test for missing odc-geosince that is not tested yet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And thanks for using the create_test_area fixture. It makes my work on Pyresample 2.0 much easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a test for the case where odc-geo is not installed and changed the setup to include it as a requirement for testing. I thought about it and I think the test should not be skipped if odc-geo is not installed instead odc-geo should be required for testing as it is now.

"""Test conversion from area definition to odc GeoBox."""
from odc.geo.geobox import GeoBox

europe = stere_area
seviri = create_test_area(
{
'proj': 'geos',
'lon_0': 0.0,
'a': 6378169.00,
'b': 6356583.80,
'h': 35785831.00,
'units': 'm'},
123,
123,
(-5500000, -5500000, 5500000, 5500000))

for area_def in [europe, seviri]:
geobox = area_def.to_odc_geobox()

assert isinstance(geobox, GeoBox)

# Affine coefficiants
af = geobox.affine
assert af.a == area_def.pixel_size_x
assert af.e == -area_def.pixel_size_y
assert af.xoff == area_def.area_extent[0]
assert af.yoff == area_def.area_extent[3]

def test_dump(self, create_test_area):
"""Test exporting area defs."""
from io import StringIO
Expand Down
8 changes: 7 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,22 @@
requirements.append('importlib_metadata')

test_requires = ['rasterio', 'dask', 'xarray', 'cartopy>=0.20.0', 'pillow', 'matplotlib', 'scipy', 'zarr',
'pytest-lazy-fixtures', 'shapely']
'pytest-lazy-fixtures', 'shapely', 'odc-geo']
extras_require = {'numexpr': ['numexpr'],
'quicklook': ['matplotlib', 'cartopy>=0.20.0', 'pillow'],
'rasterio': ['rasterio'],
'dask': ['dask>=0.16.1'],
'cf': ['xarray'],
'gradient_search': ['shapely'],
'xarray_bilinear': ['xarray', 'dask', 'zarr'],
'odc-geo': ['odc-geo'],
'tests': test_requires}

all_extras = []
for extra_deps in extras_require.values():
all_extras.extend(extra_deps)
extras_require['all'] = list(set(all_extras))

if sys.platform.startswith("win"):
extra_compile_args = []
else:
Expand Down
Loading