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

Improvements for load_tiff functionality #233

Merged
merged 5 commits into from
Sep 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions geoviews/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
Points, Path, Polygons, Shape, Dataset, RGB,
Contours, Graph, TriMesh, Nodes, EdgePaths,
QuadMesh, VectorField, HexTiles, Labels)
from .util import load_tiff, from_xarray # noqa (API import)
from .operation import project # noqa (API import)
from . import data # noqa (API import)
from . import operation # noqa (API import)
Expand Down
100 changes: 29 additions & 71 deletions geoviews/element/geo.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import warnings

import param
import numpy as np
from cartopy import crs as ccrs
Expand Down Expand Up @@ -33,7 +31,8 @@
except:
WebMapTileService = None

from ..util import path_to_geom, polygon_to_geom, geom_to_array, process_crs
from ..util import (path_to_geom, polygon_to_geom, geom_to_array,
load_tiff, from_xarray)

geographic_types = (GoogleTiles, cFeature, BaseGeometry)

Expand Down Expand Up @@ -263,6 +262,17 @@ class Image(_Element, HvImage):

group = param.String(default='Image')

@classmethod
def load_tiff(cls, filename, crs=None, apply_transform=False,
nan_nodata=False, **kwargs):
return load_tiff(filename, crs, apply_transform, **kwargs)

@classmethod
def from_xarray(cls, da, crs=None, apply_transform=False,
nan_nodata=False, **kwargs):
return from_xarray(da, crs, apply_transform, **kwargs)



class QuadMesh(_Element, HvQuadMesh):
"""
Expand All @@ -286,6 +296,16 @@ class QuadMesh(_Element, HvQuadMesh):

_binned = True

@classmethod
def load_tiff(cls, filename, crs=None, apply_transform=False,
nan_nodata=False, **kwargs):
return load_tiff(filename, crs, apply_transform, **kwargs)

@classmethod
def from_xarray(cls, da, crs=None, apply_transform=False,
nan_nodata=False, **kwargs):
return from_xarray(da, crs, apply_transform, **kwargs)

def trimesh(self):
trimesh = super(QuadMesh, self).trimesh()
node_params = util.get_param_values(trimesh.nodes)
Expand Down Expand Up @@ -318,76 +338,14 @@ class RGB(_Element, HvRGB):
is automatically appended to this list.""")

@classmethod
def load_tiff(cls, filename, crs=None, apply_transform=False, **kwargs):
"""
Returns an RGB or Image element loaded from a geotiff file.

The data is loaded using xarray and rasterio. If a crs attribute
is present on the loaded data it will attempt to decode it into
a cartopy projection otherwise it will default to a non-geographic
HoloViews element.
"""
try:
import xarray as xr
except:
raise ImportError('Loading tiffs requires xarray to be installed')

with warnings.catch_warnings():
warnings.filterwarnings('ignore')
da = xr.open_rasterio(filename)
return cls.from_xarray(da, crs, apply_transform, **kwargs)

def load_tiff(cls, filename, crs=None, apply_transform=False,
nan_nodata=False, **kwargs):
return load_tiff(filename, crs, apply_transform, **kwargs)

@classmethod
def from_xarray(cls, da, crs=None, apply_transform=False, **kwargs):
"""
Returns an RGB or Image element given an xarray DataArray
loaded using xr.open_rasterio.

If a crs attribute is present on the loaded data it will
attempt to decode it into a cartopy projection otherwise it
will default to a non-geographic HoloViews element.
"""
if crs:
kwargs['crs'] = crs
elif hasattr(da, 'crs'):
try:
kwargs['crs'] = process_crs(da.crs)
except:
param.main.warning('Could not decode projection from crs string %r, '
'defaulting to non-geographic element.' % da.crs)

coords = list(da.coords)
y, x = coords[1:]
bands = len(da.coords[coords[0]])
if apply_transform:
from affine import Affine
transform = Affine(*da.attrs['transform'][:6])
nx, ny = da.sizes[x], da.sizes[y]
xs, ys = np.meshgrid(np.arange(nx)+0.5, np.arange(ny)+0.5) * transform
data = (xs, ys)
else:
xres, yres = da.attrs['res'] if 'res' in da.attrs else (1, 1)
xs = da.coords[x][::-1] if xres < 0 else da.coords[x]
ys = da.coords[y][::-1] if yres < 0 else da.coords[y]
data = (xs, ys)
data += tuple(da[b].values for b in range(bands))

if 'datatype' not in kwargs:
kwargs['datatype'] = ['xarray', 'grid', 'image']

if xs.ndim > 1:
el = QuadMesh if 'crs' in kwargs else HvQuadMesh
el = el(data, [x, y], **kwargs)
elif bands < 3:
el = Image if 'crs' in kwargs else HvImage
el = el(data, [x, y], **kwargs)
else:
vdims = cls.vdims[:bands]
el = cls if 'crs' in kwargs else HvRGB
el = el(data, [x, y], vdims, **kwargs)
el.data.attrs = da.attrs
return el
def from_xarray(cls, da, crs=None, apply_transform=False,
nan_nodata=False, **kwargs):
return from_xarray(da, crs, apply_transform, **kwargs)



Expand Down
131 changes: 131 additions & 0 deletions geoviews/util.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import warnings

import param
import numpy as np
import shapely.geometry as sgeom

Expand Down Expand Up @@ -377,3 +380,131 @@ def process_crs(crs):
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


def load_tiff(filename, crs=None, apply_transform=False, nan_nodata=False, **kwargs):
"""
Returns an RGB or Image element loaded from a geotiff file.

The data is loaded using xarray and rasterio. If a crs attribute
is present on the loaded data it will attempt to decode it into
a cartopy projection otherwise it will default to a non-geographic
HoloViews element.

Arguments
---------
filename: string
Filename pointing to geotiff file to load
crs: Cartopy CRS or EPSG string (optional)
Overrides CRS inferred from the data
apply_transform: boolean
Whether to apply affine transform if defined on the data
nan_nodata: boolean
If data contains nodata values convert them to NaNs
**kwargs:
Keyword arguments passed to the HoloViews/GeoViews element

Returns
-------
element: Image/RGB/QuadMesh element

"""
try:
import xarray as xr
except:
raise ImportError('Loading tiffs requires xarray to be installed')

with warnings.catch_warnings():
warnings.filterwarnings('ignore')
da = xr.open_rasterio(filename)
return from_xarray(da, crs, apply_transform, nan_nodata, **kwargs)


def from_xarray(da, crs=None, apply_transform=False, nan_nodata=False, **kwargs):
"""
Returns an RGB or Image element given an xarray DataArray
loaded using xr.open_rasterio.

If a crs attribute is present on the loaded data it will
attempt to decode it into a cartopy projection otherwise it
will default to a non-geographic HoloViews element.

Arguments
---------
da: xarray.DataArray
DataArray to convert to element
crs: Cartopy CRS or EPSG string (optional)
Overrides CRS inferred from the data
apply_transform: boolean
Whether to apply affine transform if defined on the data
nan_nodata: boolean
If data contains nodata values convert them to NaNs
**kwargs:
Keyword arguments passed to the HoloViews/GeoViews element

Returns
-------
element: Image/RGB/QuadMesh element
"""
if crs:
kwargs['crs'] = crs
elif hasattr(da, 'crs'):
try:
kwargs['crs'] = process_crs(da.crs)
except:
param.main.warning('Could not decode projection from crs string %r, '
'defaulting to non-geographic element.' % da.crs)

coords = list(da.coords)
if coords not in (['band', 'y', 'x'], ['y', 'x']):
from .element.geo import Dataset, HvDataset
el = Dataset if 'crs' in kwargs else HvDataset
return el(da, **kwargs)

if len(coords) == 2:
y, x = coords
bands = 1
else:
y, x = coords[1:]
bands = len(da.coords[coords[0]])

if apply_transform:
from affine import Affine
transform = Affine(*da.attrs['transform'][:6])
nx, ny = da.sizes[x], da.sizes[y]
xs, ys = np.meshgrid(np.arange(nx)+0.5, np.arange(ny)+0.5) * transform
data = (xs, ys)
else:
xres, yres = da.attrs['res'] if 'res' in da.attrs else (1, 1)
xs = da.coords[x][::-1] if xres < 0 else da.coords[x]
ys = da.coords[y][::-1] if yres < 0 else da.coords[y]

data = (xs, ys)
for b in range(bands):
values = da[b].values
if nan_nodata and da.attrs.get('nodatavals', []):

values = values.astype(float)
for d in da.attrs['nodatavals']:
values[values==d] = np.NaN
data += (values,)

if 'datatype' not in kwargs:
kwargs['datatype'] = ['xarray', 'grid', 'image']

if xs.ndim > 1:
from .element.geo import QuadMesh, HvQuadMesh
el = QuadMesh if 'crs' in kwargs else HvQuadMesh
el = el(data, [x, y], **kwargs)
elif bands < 3:
from .element.geo import Image, HvImage
el = Image if 'crs' in kwargs else HvImage
el = el(data, [x, y], **kwargs)
else:
from .element.geo import RGB, HvRGB
el = RGB if 'crs' in kwargs else HvRGB
vdims = el.vdims[:bands]
el = el(data, [x, y], vdims, **kwargs)
if hasattr(el.data, 'attrs'):
el.data.attrs = da.attrs
return el