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

feat: minor updates for R004 #43

Merged
merged 8 commits into from
Jul 31, 2024
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ _*.c
*.shp
*.shx
*.tif
*.vrt
*.zarr
# Logs and databases #
######################
Expand Down
105 changes: 51 additions & 54 deletions IS2view/api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python
u"""
api.py
Written by Tyler Sutterley (04/2024)
Written by Tyler Sutterley (06/2024)
Plotting tools for visualizing rioxarray variables on leaflet maps

PYTHON DEPENDENCIES:
Expand All @@ -28,6 +28,7 @@
https://xyzservices.readthedocs.io/en/stable/

UPDATE HISTORY:
Updated 06/2024: use wrapper to importlib for optional dependencies
Updated 04/2024: add connections and functions for changing variables
and other attributes of the leaflet map visualization
simplify and generalize mapping between observables and functionals
Expand Down Expand Up @@ -56,20 +57,21 @@
import collections.abc
from traitlets import HasTraits, Float, Tuple, observe
from traitlets.utils.bunch import Bunch
from IS2view.utilities import import_dependency

# attempt imports
try:
import geopandas as gpd
except (AttributeError, ImportError, ModuleNotFoundError) as exc:
logging.debug("geopandas not available")
try:
import ipywidgets
except (AttributeError, ImportError, ModuleNotFoundError) as exc:
logging.debug("ipywidgets not available")
try:
import ipyleaflet
except (AttributeError, ImportError, ModuleNotFoundError) as exc:
logging.debug("ipyleaflet not available")
gpd = import_dependency('geopandas')
ipywidgets = import_dependency('ipywidgets')
ipyleaflet = import_dependency('ipyleaflet')
owslib = import_dependency('owslib')
owslib.wms = import_dependency('owslib.wms')
rio = import_dependency('rasterio')
rio.transform = import_dependency('rasterio.transform')
rio.warp = import_dependency('rasterio.warp')
xr = import_dependency('xarray')
xyzservices = import_dependency('xyzservices')

# attempt matplotlib imports
try:
import matplotlib
import matplotlib.cm as cm
Expand All @@ -81,23 +83,6 @@
matplotlib.rcParams['mathtext.default'] = 'regular'
except (AttributeError, ImportError, ModuleNotFoundError) as exc:
logging.critical("matplotlib not available")
try:
import owslib.wms
except (AttributeError, ImportError, ModuleNotFoundError) as exc:
logging.debug("owslib not available")
try:
import rasterio.transform
import rasterio.warp
except (AttributeError, ImportError, ModuleNotFoundError) as exc:
logging.critical("rasterio not available")
try:
import xarray as xr
except (AttributeError, ImportError, ModuleNotFoundError) as exc:
logging.critical("xarray not available")
try:
import xyzservices
except (AttributeError, ImportError, ModuleNotFoundError) as exc:
logging.debug("xyzservices not available")

# set environmental variable for anonymous s3 access
os.environ['AWS_NO_SIGN_REQUEST'] = 'YES'
Expand Down Expand Up @@ -230,6 +215,8 @@ def _tile_provider(provider):

# create traitlets of basemap providers
basemaps = _load_dict(providers)
# set default map dimensions
_default_layout = ipywidgets.Layout(width='70%', height='600px')

# draw ipyleaflet map
class Leaflet:
Expand All @@ -241,6 +228,8 @@ class Leaflet:
``ipyleaflet.Map``
basemap : obj or NoneType
Basemap for the ``ipyleaflet.Map``
layout : obj, default ``ipywidgets.Layout(width='70%', height='600px')``
Layout for the ``ipyleaflet.Map``
attribution : bool, default False
Include layer attributes on leaflet map
scale_control : bool, default False
Expand Down Expand Up @@ -280,6 +269,7 @@ class Leaflet:
def __init__(self, projection, **kwargs):
# set default keyword arguments
kwargs.setdefault('map', None)
kwargs.setdefault('layout', _default_layout)
kwargs.setdefault('attribution', False)
kwargs.setdefault('full_screen_control', False)
kwargs.setdefault('scale_control', False)
Expand All @@ -300,7 +290,9 @@ def __init__(self, projection, **kwargs):
zoom=kwargs['zoom'], max_zoom=5,
attribution_control=kwargs['attribution'],
basemap=kwargs['basemap'],
crs=projections['EPSG:3413'])
crs=projections['EPSG:3413'],
layout=kwargs['layout']
)
self.crs = 'EPSG:3413'
elif (projection == 'South'):
kwargs.setdefault('basemap',
Expand All @@ -310,7 +302,9 @@ def __init__(self, projection, **kwargs):
zoom=kwargs['zoom'], max_zoom=5,
attribution_control=kwargs['attribution'],
basemap=kwargs['basemap'],
crs=projections['EPSG:3031'])
crs=projections['EPSG:3031'],
layout=kwargs['layout']
)
self.crs = 'EPSG:3031'
else:
# use a predefined ipyleaflet map
Expand Down Expand Up @@ -508,7 +502,7 @@ def plot_basemap(self, ax=None, **kwargs):
ax: obj, default None
Figure axis
kwargs: dict, default {}
Additional keyword arguments for ``wms.getmap``
Additional keyword arguments for ``owslib.wms.getmap``
"""
# set default keyword arguments
kwargs.setdefault('layers', ['BlueMarble_NextGeneration'])
Expand Down Expand Up @@ -777,12 +771,8 @@ def plot(self, m, **kwargs):
# reduce to variable and lag
self._variable = copy.copy(kwargs['variable'])
self.lag = int(kwargs['lag'])
if (self._ds[self._variable].ndim == 3) and ('time' in self._ds[self._variable].dims):
self._ds_selected = self._ds[self._variable].sel(time=self._ds.time[self.lag])
elif (self._ds[self._variable].ndim == 3) and ('band' in self._ds[self._variable].dims):
self._ds_selected = self._ds[self._variable].sel(band=1)
else:
self._ds_selected = self._ds[self._variable]
# select data variable
self.set_dataset()
# get the normalization bounds
self.get_norm_bounds(**kwargs)
# create matplotlib normalization
Expand Down Expand Up @@ -911,7 +901,7 @@ def get_bounds(self):
"""get the bounds of the leaflet map in geographical coordinates
"""
self.get_bbox()
lon, lat = rasterio.warp.transform(
lon, lat = rio.warp.transform(
self.crs['name'], 'EPSG:4326',
[self.sw['x'], self.ne['x']],
[self.sw['y'], self.ne['y']])
Expand Down Expand Up @@ -1000,7 +990,7 @@ def clip_image(self, ds):
# attempt to get the coordinate reference system of the dataset
self.get_crs()
# convert map bounds to coordinate reference system of image
minx, miny, maxx, maxy = rasterio.warp.transform_bounds(
minx, miny, maxx, maxy = rio.warp.transform_bounds(
self.crs['name'], self._ds.rio.crs,
self.sw['x'], self.sw['y'],
self.ne['x'], self.ne['y'])
Expand All @@ -1023,14 +1013,14 @@ def clip_image(self, ds):
# warp image to map bounds and resolution
# input and output affine transformations
src_transform = ds.rio.transform()
dst_transform = rasterio.transform.from_origin(minx, maxy,
dst_transform = rio.transform.from_origin(minx, maxy,
self.resolution, self.resolution)
# allocate for output warped image
dst_width = int((maxx - minx)//self.resolution)
dst_height = int((maxy - miny)//self.resolution)
dst_data = np.zeros((dst_height, dst_width), dtype=ds.dtype.type)
# warp image to output resolution
rasterio.warp.reproject(source=ds.values, destination=dst_data,
rio.warp.reproject(source=ds.values, destination=dst_data,
src_transform=src_transform,
src_crs=self._ds.rio.crs,
src_nodata=np.nan,
Expand Down Expand Up @@ -1148,21 +1138,28 @@ def set_observables(self, widget, **kwargs):
except (AttributeError, NameError, ValueError) as exc:
pass

def set_variable(self, sender):
"""update the dataframe variable for a new selected variable
def set_dataset(self):
"""Select the dataset for the selected variable
and time lag
"""
# only update variable if a new final
if isinstance(sender['new'], str):
self._variable = sender['new']
else:
return
# reduce to variable and lag
if (self._ds[self._variable].ndim == 3) and ('time' in self._ds[self._variable].dims):
self._ds_selected = self._ds[self._variable].sel(time=self._ds.time[self.lag])
elif (self._ds[self._variable].ndim == 3) and ('band' in self._ds[self._variable].dims):
self._ds_selected = self._ds[self._variable].sel(band=1)
else:
self._ds_selected = self._ds[self._variable]

def set_variable(self, sender):
"""update the plotted variable
"""
# only update variable if a new final
if isinstance(sender['new'], str):
self._variable = sender['new']
else:
return
# reduce to variable and lag
self.set_dataset()
# check if dynamic normalization is enabled
if self._dynamic:
self.get_norm_bounds()
Expand Down Expand Up @@ -1263,7 +1260,7 @@ def handle_click(self, **kwargs):
else:
self._ds.rio.set_crs(crs)
# get the clicked point in dataset coordinate reference system
x, y = rasterio.warp.transform('EPSG:4326', crs, [lon], [lat])
x, y = rio.warp.transform('EPSG:4326', crs, [lon], [lat])
# find nearest point in dataset
self._data = self._ds_selected.sel(x=x, y=y, method='nearest').values[0]
self._units = self._ds[self._variable].attrs['units']
Expand Down Expand Up @@ -1575,7 +1572,7 @@ def point(self, ax, **kwargs):
"""
# convert point to dataset coordinate reference system
lon, lat = self.geometry['coordinates']
x, y = rasterio.warp.transform(self.crs, self._ds.rio.crs, [lon], [lat])
x, y = rio.warp.transform(self.crs, self._ds.rio.crs, [lon], [lat])
# output time series for point
self._data = np.zeros_like(self._ds.time)
# reduce dataset to geometry
Expand Down Expand Up @@ -1629,7 +1626,7 @@ def transect(self, ax, **kwargs):
"""
# convert linestring to dataset coordinate reference system
lon, lat = np.transpose(self.geometry['coordinates'])
x, y = rasterio.warp.transform(self.crs, self._ds.rio.crs, lon, lat)
x, y = rio.warp.transform(self.crs, self._ds.rio.crs, lon, lat)
# get coordinates of each grid cell
gridx, gridy = np.meshgrid(self._ds.x, self._ds.y)
# clip ice area to geometry
Expand Down Expand Up @@ -1997,7 +1994,7 @@ def transect(self, ax, **kwargs):
"""
# convert linestring to dataset coordinate reference system
lon, lat = np.transpose(self.geometry['coordinates'])
x, y = rasterio.warp.transform(self.crs, self._ds.rio.crs, lon, lat)
x, y = rio.warp.transform(self.crs, self._ds.rio.crs, lon, lat)
# get coordinates of each grid cell
gridx, gridy = np.meshgrid(self._ds.x, self._ds.y)
# clip variable to geometry and create mask
Expand Down
14 changes: 5 additions & 9 deletions IS2view/convert.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
convert.py
Written by Tyler Sutterley (08/2023)
Written by Tyler Sutterley (06/2024)
Utilities for converting gridded ICESat-2 files from native netCDF4

PYTHON DEPENDENCIES:
Expand All @@ -13,6 +13,7 @@
https://docs.xarray.dev/en/stable/

UPDATE HISTORY:
Updated 06/2024: use wrapper to importlib for optional dependencies
Updated 08/2023: use h5netcdf as the netCDF4 driver for xarray
Updated 07/2023: use logging instead of warnings for import attempts
Updated 06/2023: using pathlib to define and expand paths
Expand All @@ -23,16 +24,11 @@
import logging
import pathlib
import numpy as np
from IS2view.utilities import import_dependency

# attempt imports
try:
import h5netcdf
except (AttributeError, ImportError, ModuleNotFoundError) as exc:
logging.critical("h5netcdf not available")
try:
import xarray as xr
except (AttributeError, ImportError, ModuleNotFoundError) as exc:
logging.critical("xarray not available")
h5netcdf = import_dependency('h5netcdf')
xr = import_dependency('xarray')

# default groups to skip
_default_skip_groups = ('METADATA', 'orbit_info', 'quality_assessment',)
Expand Down
22 changes: 7 additions & 15 deletions IS2view/io.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
io.py
Written by Tyler Sutterley (10/2023)
Written by Tyler Sutterley (06/2024)
Utilities for reading gridded ICESat-2 files using rasterio and xarray

PYTHON DEPENDENCIES:
Expand All @@ -18,6 +18,7 @@
https://docs.xarray.dev/en/stable/

UPDATE HISTORY:
Updated 06/2024: use wrapper to importlib for optional dependencies
Updated 10/2023: use dask.delayed to read multiple files in parallel
Updated 08/2023: use xarray h5netcdf to read files streaming from s3
add open_dataset function for opening multiple granules
Expand All @@ -27,22 +28,13 @@
"""
from __future__ import annotations
import os
import logging
from IS2view.utilities import import_dependency

# attempt imports
try:
import rioxarray
import rioxarray.merge
except (AttributeError, ImportError, ModuleNotFoundError) as exc:
logging.critical("rioxarray not available")
try:
import dask
except (AttributeError, ImportError, ModuleNotFoundError) as exc:
logging.critical("dask not available")
try:
import xarray as xr
except (AttributeError, ImportError, ModuleNotFoundError) as exc:
logging.critical("xarray not available")
rioxarray = import_dependency('rioxarray')
rioxarray.merge = import_dependency('rioxarray.merge')
dask = import_dependency('dask')
xr = import_dependency('xarray')

# set environmental variable for anonymous s3 access
os.environ['AWS_NO_SIGN_REQUEST'] = 'YES'
Expand Down
Loading