Skip to content

Commit

Permalink
Improve rioxarray support (#645)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrew <15331990+ahuang11@users.noreply.github.com>
  • Loading branch information
hoxbro and ahuang11 committed Jun 27, 2023
1 parent f5c1355 commit f7f6fcd
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
SETUPTOOLS_ENABLE_FEATURES: "legacy-editable"
USE_PYGEOS: '0'
steps:
- uses: holoviz-dev/holoviz_tasks/install@v0.1a9
- uses: holoviz-dev/holoviz_tasks/install@v0.1a13
with:
name: unit_test_suite
python-version: ${{ matrix.python-version }}
Expand Down
47 changes: 47 additions & 0 deletions geoviews/tests/test_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import cartopy.crs as ccrs
import pytest

import geoviews as gv
from geoviews.util import from_xarray, process_crs

try:
import rioxarray as rxr
except ImportError:
rxr = None


@pytest.mark.parametrize(
"raw_crs",
[
"+init=epsg:26911",
"4326",
4326,
"epsg:4326",
"EPSG: 4326",
ccrs.PlateCarree(),
],
)
def test_process_crs(raw_crs) -> None:
crs = process_crs(raw_crs)
assert isinstance(crs, ccrs.CRS)


# To avoid '+init=<authority>:<code>' syntax is deprecated.
@pytest.mark.filterwarnings("ignore::FutureWarning")
def test_process_crs_raises_error():
with pytest.raises(
ValueError, match="must be defined as a EPSG code, proj4 string"
):
process_crs(43823)


@pytest.mark.skipif(rxr is None, reason="Needs rioxarray to be installed")
def test_from_xarray():
file = (
"https://github.com/holoviz/hvplot/raw/main/hvplot/tests/data/RGB-red.byte.tif"
)
output = from_xarray(rxr.open_rasterio(file))

assert isinstance(output, gv.RGB)
assert sorted(map(str, output.kdims)) == ["x", "y"]
assert isinstance(output.crs, ccrs.CRS)
53 changes: 33 additions & 20 deletions geoviews/util.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import warnings

import numpy as np
import param
import shapely
import shapely.geometry as sgeom
from cartopy import crs as ccrs
Expand All @@ -15,7 +14,7 @@
from shapely.geometry.base import BaseMultipartGeometry
from shapely.ops import transform

from ._warnings import deprecated
from ._warnings import deprecated, warn

geom_types = (MultiLineString, LineString, MultiPolygon, Polygon,
LinearRing, Point, MultiPoint)
Expand Down Expand Up @@ -580,28 +579,34 @@ def process_crs(crs):
"""
try:
import cartopy.crs as ccrs
import geoviews as gv # noqa
import pyproj
except ImportError:
raise ImportError('Geographic projection support requires GeoViews and cartopy.')
raise ImportError('Geographic projection support requires pyproj and cartopy.')

if crs is None:
return ccrs.PlateCarree()
elif isinstance(crs, ccrs.CRS):
return crs

if isinstance(crs, str) and crs.lower().startswith('epsg'):
errors = []
if isinstance(crs, str):
try:
crs = ccrs.epsg(crs[5:].lstrip().rstrip())
except Exception:
raise ValueError("Could not parse EPSG code as CRS, must be of the format 'EPSG: {code}.'")
elif isinstance(crs, int):
crs = ccrs.epsg(crs)
elif isinstance(crs, str) or is_pyproj(crs):
return ccrs.epsg("".join([c for c in crs if c.isdigit()]))
except Exception as e:
errors.append(e)
if isinstance(crs, int):
try:
crs = proj_to_cartopy(crs)
except Exception:
raise ValueError("Could not parse EPSG code as CRS, must be of the format 'proj4: {proj4 string}.'")
elif not isinstance(crs, ccrs.CRS):
raise ValueError("Projection must be defined as a EPSG code, proj4 string, cartopy CRS or pyproj.Proj.")
return crs
return ccrs.epsg(crs)
except Exception as e:
crs = str(crs)
errors.append(e)
if isinstance(crs, (str, pyproj.Proj)):
try:
return proj_to_cartopy(crs)
except Exception as e:
errors.append(e)

raise ValueError("Projection must be defined as a EPSG code, proj4 string, cartopy CRS or pyproj.Proj.") from Exception(*errors)


def load_tiff(filename, crs=None, apply_transform=False, nan_nodata=False, **kwargs):
Expand Down Expand Up @@ -677,13 +682,21 @@ def from_xarray(da, crs=None, apply_transform=False, nan_nodata=False, **kwargs)
if crs:
kwargs['crs'] = crs
elif hasattr(da, 'crs'):
# xarray.open_rasterio (not supported since April 2023)
try:
kwargs['crs'] = process_crs(da.crs)
except Exception:
param.main.warning('Could not decode projection from crs string %r, '
'defaulting to non-geographic element.' % da.crs)
warn(f'Could not decode projection from crs string {da.crs}, '
'defaulting to non-geographic element.')
elif hasattr(da, 'rio') and da.rio.crs is not None:
# rioxarray.open_rasterio
try:
kwargs['crs'] = process_crs(da.rio.crs.to_proj4())
except Exception:
warn(f'Could not decode projection from crs string {da.rio.crs}, '
'defaulting to non-geographic element.')

coords = list(da.coords)
coords = list(da.dims)
if coords not in (['band', 'y', 'x'], ['y', 'x']):
from .element.geo import Dataset, HvDataset
el = Dataset if 'crs' in kwargs else HvDataset
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ def run(self):
'nbsmoke >=0.2.0',
'pytest',
'fiona',
'rioxarray',
],
}

Expand Down

0 comments on commit f7f6fcd

Please sign in to comment.