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

Improve rioxarray support #645

Merged
merged 13 commits into from
Jun 12, 2023
46 changes: 46 additions & 0 deletions geoviews/tests/test_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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",
],
hoxbro marked this conversation as resolved.
Show resolved Hide resolved
)
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)
56 changes: 36 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,37 @@ 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 geoviews, pyproj and cartopy.')

if crs is None:
return ccrs.PlateCarree()

if isinstance(crs, str) and crs.lower().startswith('epsg'):
errors = []
if isinstance(crs, str):
starts = ['epsg:', '+init=epsg:']
for start in starts:
if crs.lower().startswith(start):
try:
return ccrs.epsg(crs[len(start):].strip())
except Exception as e:
errors.append(e)
ahuang11 marked this conversation as resolved.
Show resolved Hide resolved
if isinstance(crs, int):
ahuang11 marked this conversation as resolved.
Show resolved Hide resolved
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(crs)
except Exception as e:
crs = str(crs)
ahuang11 marked this conversation as resolved.
Show resolved Hide resolved
errors.append(e)
if isinstance(crs, (str, pyproj.Proj)):
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 proj_to_cartopy(crs)
except Exception as e:
errors.append(e)
if isinstance(crs, ccrs.CRS):
return crs
hoxbro marked this conversation as resolved.
Show resolved Hide resolved

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 +685,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.coords.dims)
hoxbro marked this conversation as resolved.
Show resolved Hide resolved
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