Skip to content

Commit

Permalink
feat: add capability to save map as a static image for #28 (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
tsutterley authored Jul 6, 2023
1 parent 8938b88 commit c68184c
Show file tree
Hide file tree
Showing 16 changed files with 230 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
python-version: [3.8]
python-version: [3.11]
env:
OS: ${{ matrix.os }}
PYTHON: ${{ matrix.python-version }}
Expand Down
2 changes: 1 addition & 1 deletion IS2view/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
"""
import IS2view.utilities
import IS2view.version
from IS2view.api import Leaflet, layers, image_service_layer
from IS2view.convert import convert
from IS2view.io import from_file, from_rasterio, from_xarray
from IS2view.tools import widgets
from IS2view.visualization import leaflet, layers, image_service_layer
# get semantic version from setuptools-scm
__version__ = IS2view.version.version
134 changes: 126 additions & 8 deletions IS2view/visualization.py → IS2view/api.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
#!/usr/bin/env python
u"""
visualization.py
Written by Tyler Sutterley (06/2023)
api.py
Written by Tyler Sutterley (07/2023)
Plotting tools for visualizing rioxarray variables on leaflet maps
PYTHON DEPENDENCIES:
numpy: Scientific Computing Tools For Python
https://numpy.org
https://numpy.org/doc/stable/user/numpy-for-matlab-users.html
geopandas: Python tools for geographic data
http://geopandas.readthedocs.io/
ipywidgets: interactive HTML widgets for Jupyter notebooks and IPython
https://ipywidgets.readthedocs.io/en/latest/
ipyleaflet: Jupyter / Leaflet bridge enabling interactive maps
https://github.com/jupyter-widgets/ipyleaflet
matplotlib: Python 2D plotting library
http://matplotlib.org/
https://github.com/matplotlib/matplotlib
numpy: Scientific Computing Tools For Python
https://numpy.org
https://numpy.org/doc/stable/user/numpy-for-matlab-users.html
OWSLib: Pythonic interface for Open Geospatial Consortium (OGC) web services
https://owslib.readthedocs.io/
rasterio: Access to geospatial raster data
https://github.com/rasterio/rasterio
https://rasterio.readthedocs.io
xarray: N-D labeled arrays and datasets in Python
https://docs.xarray.dev/en/stable/
UPDATE HISTORY:
Updated 07/2023: renamed module from IS2view.py to api.py
add plot functions for map basemaps and added geometries
add imshow function for visualizing current leaflet map
Updated 06/2023: moved widgets functions to separate moddule
renamed module from IS2view.py to visualization.py
Updated 12/2022: added case for warping input image
Updated 11/2022: modifications for dask-chunked rasters
Written 07/2022
Expand All @@ -42,6 +48,12 @@
from traitlets.utils.bunch import Bunch

# attempt imports
try:
import geopandas as gpd
except (ImportError, ModuleNotFoundError) as exc:
warnings.filterwarnings("module")
warnings.warn("geopandas not available")
warnings.warn("Some functions will throw an exception if called")
try:
import ipywidgets
except (ImportError, ModuleNotFoundError) as exc:
Expand All @@ -64,6 +76,12 @@
warnings.filterwarnings("module")
warnings.warn("matplotlib not available")
warnings.warn("Some functions will throw an exception if called")
try:
import owslib.wms
except (ImportError, ModuleNotFoundError) as exc:
warnings.filterwarnings("module")
warnings.warn("owslib not available")
warnings.warn("Some functions will throw an exception if called")
try:
import rasterio.transform
import rasterio.warp
Expand Down Expand Up @@ -152,7 +170,7 @@
pass

# draw ipyleaflet map
class leaflet:
class Leaflet:
"""Create interactive leaflet maps for visualizing ATL14/15 data
Parameters
Expand Down Expand Up @@ -352,7 +370,7 @@ def to_geojson(self, filename, **kwargs):
"""
# dump the geometries to a geojson file
kwargs.update(self.geometries)
with open(filename, 'w') as fid:
with open(filename, mode='w') as fid:
json.dump(kwargs, fid)
# print the filename and dictionary structure
logging.info(filename)
Expand Down Expand Up @@ -404,6 +422,70 @@ def remove(self, obj):
logging.info(f"{obj} already removed from map")
pass

# plot basemap
def plot_basemap(self, ax=None, **kwargs):
"""Plot the current basemap
Parameters
----------
ax: obj, default None
Figure axis
kwargs: dict, default {}
Additional keyword arguments for ``wms.getmap``
"""
# set default keyword arguments
kwargs.setdefault('layers', ['BlueMarble_NextGeneration'])
kwargs.setdefault('format', 'image/png')
kwargs.setdefault('srs', self.map.crs['name'])
# create figure axis if non-existent
if (ax is None):
_, ax = plt.subplots()
# get the pixel bounds and resolution of the map
(left, top), (right, bottom) = self.map.pixel_bounds
resolution = self.map.crs['resolutions'][int(self.map.zoom)]
# calculate the size of the map in pixels
kwargs.setdefault('size', [int((right-left)), int((bottom-top))])
# calculate the bounding box of the map in projected coordinates
bbox = [None]*4
bbox[0] = self.map.crs['origin'][0] + left*resolution
bbox[1] = self.map.crs['origin'][1] - bottom*resolution
bbox[2] = self.map.crs['origin'][0] + right*resolution
bbox[3] = self.map.crs['origin'][1] - top*resolution
kwargs.setdefault('bbox', bbox)
# create WMS request for basemap image at bounds and resolution
srs = kwargs['srs'].replace(':', '').lower()
url = f'https://gibs.earthdata.nasa.gov/wms/{srs}/best/wms.cgi?'
wms = owslib.wms.WebMapService(url=url, version='1.1.1')
basemap = wms.getmap(**kwargs)
# read WMS layer and plot
img = plt.imread(io.BytesIO(basemap.read()))
ax.imshow(img, extent=[bbox[0],bbox[2],bbox[1],bbox[3]])

# plot geometries
def plot_geometries(self, ax=None, **kwargs):
"""Plot the current geometries in the coordinate reference
system (``crs``) of the map
Parameters
----------
ax: obj, default None
Figure axis
kwargs: dict, default {}
Additional keyword arguments for ``plot``
"""
# return if no geometries
if (len(self.geometries['features']) == 0):
return
# create figure axis if non-existent
if (ax is None):
_, ax = plt.subplots()
# create a geopandas GeoDataFrame from the geometries
# convert coordinate reference system to map crs
gdf = gpd.GeoDataFrame.from_features(self.geometries,
crs=self.geometries['crs']).to_crs(self.crs)
# create plot with all geometries
gdf.plot(ax=ax, **kwargs)

@property
def layers(self):
"""get the map layers
Expand Down Expand Up @@ -984,6 +1066,42 @@ def add_colorbar(self, **kwargs):
self.add(self.colorbar)
plt.close()

# save the current map as an image
def imshow(self, ax=None, **kwargs):
"""Save the current map as a static image
Parameters
----------
ax: obj, default None
Figure axis
kwargs: dict, default {}
Additional keyword arguments for ``imshow``
"""
# create figure axis if non-existent
if (ax is None):
_, ax = plt.subplots()
# extract units
longname = self._ds[self._variable].attrs['long_name'].replace(' ', ' ')
units = self._ds[self._variable].attrs['units'][0]
# clip image to map bounds
visible = self.clip_image(self._ds_selected)
# color bar keywords
cbar_kwargs = dict(label=f'{longname} [{units}]', orientation='horizontal')
visible.plot.imshow(ax=ax,
norm=self.norm,
interpolation="nearest",
cmap=self.cmap,
alpha=self.opacity,
add_colorbar=True,
add_labels=True,
cbar_kwargs=cbar_kwargs,
**kwargs
)
# set image extent
ax.set_xlim(self.extent[0], self.extent[1])
ax.set_ylim(self.extent[2], self.extent[3])
ax.set_aspect('equal', adjustable='box')

@xr.register_dataset_accessor('timeseries')
class TimeSeries(HasTraits):
"""A xarray.DataArray extension for extracting and plotting a time series
Expand Down
6 changes: 0 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,14 @@ Interactive visualization and data extraction tool for ICESat-2 ATL14/15 Gridded
Dependencies
############

- `boto3: Amazon Web Services (AWS) SDK for Python <https://boto3.amazonaws.com/v1/documentation/api/latest/index.html>`_
- `bottleneck: Fast NumPy array functions written in C <https://github.com/pydata/bottleneck>`_
- `dask: Parallel computing with task scheduling <https://www.dask.org/>`_
- `h5netcdf: Pythonic interface to netCDF4 via h5py <https://h5netcdf.org/>`_
- `ipyleaflet: Interactive maps in the Jupyter notebook <https://ipyleaflet.readthedocs.io/en/latest/>`_
- `matplotlib: Python 2D plotting library <https://matplotlib.org/>`_
- `netCDF4: Python interface to the netCDF C library <https://unidata.github.io/netcdf4-python/>`_
- `numpy: Scientific Computing Tools For Python <https://numpy.org>`_
- `rasterio: Access to geospatial raster data <https://rasterio.readthedocs.io/en/latest/>`_
- `rioxarray: geospatial xarray extension powered by rasterio <https://github.com/corteva/rioxarray>`_
- `s3fs: Pythonic file interface to S3 built on top of botocore <https://s3fs.readthedocs.io/en/latest/>`_
- `setuptools_scm: manager for python package versions using scm metadata <https://pypi.org/project/setuptools-scm>`_
- `xarray: N-D labeled arrays and datasets in Python <https://docs.xarray.dev/en/stable/>`_
- `zarr: Chunked, compressed, N-dimensional arrays in Python <https://zarr.readthedocs.io/en/stable/>`_

Download
########
Expand Down
Binary file added doc/source/_assets/map.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions doc/source/api_reference/api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
===
api
===

Plotting tools for visualizing `rioxarray <https://corteva.github.io/rioxarray/stable/>`_ variables on `ipyleaflet <https://ipyleaflet.readthedocs.io/en/latest/>`_ maps

`Source code`__

.. __: https://github.com/tsutterley/IS2view/blob/main/IS2view/api.py

General Attributes and Methods
==============================

.. autoclass:: IS2view.api.Leaflet
:members:

.. autofunction:: IS2view.api.image_service_layer

.. autoclass:: IS2view.api.LeafletMap
:members:

.. autoclass:: IS2view.api.TimeSeries
:members:

.. autoclass:: IS2view.api.Transect
:members:
26 changes: 0 additions & 26 deletions doc/source/api_reference/visualization.rst

This file was deleted.

16 changes: 11 additions & 5 deletions doc/source/getting_started/Citations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,25 @@ Dependencies

This software is also dependent on other commonly used Python packages:

- `boto3: Amazon Web Services (AWS) SDK for Python <https://boto3.amazonaws.com/v1/documentation/api/latest/index.html>`_
- `bottleneck: Fast NumPy array functions written in C <https://github.com/pydata/bottleneck>`_
- `dask: Parallel computing with task scheduling <https://www.dask.org/>`_
- `h5netcdf: Pythonic interface to netCDF4 via h5py <https://h5netcdf.org/>`_
- `ipyleaflet: Interactive maps in the Jupyter notebook <https://ipyleaflet.readthedocs.io/en/latest/>`_
- `matplotlib: Python 2D plotting library <https://matplotlib.org/>`_
- `netCDF4: Python interface to the netCDF C library <https://unidata.github.io/netcdf4-python/>`_
- `numpy: Scientific Computing Tools For Python <https://numpy.org>`_
- `rasterio: Access to geospatial raster data <https://rasterio.readthedocs.io/en/latest/>`_
- `rioxarray: geospatial xarray extension powered by rasterio <https://github.com/corteva/rioxarray>`_
- `s3fs: Pythonic file interface to S3 built on top of botocore <https://s3fs.readthedocs.io/en/latest/>`_
- `setuptools_scm: manager for python package versions using scm metadata <https://pypi.org/project/setuptools-scm>`_
- `xarray: N-D labeled arrays and datasets in Python <https://docs.xarray.dev/en/stable/>`_

Optional Dependencies
---------------------

- `boto3: Amazon Web Services (AWS) SDK for Python <https://boto3.amazonaws.com/v1/documentation/api/latest/index.html>`_
- `bottleneck: Fast NumPy array functions written in C <https://github.com/pydata/bottleneck>`_
- `dask: Parallel computing with task scheduling <https://www.dask.org/>`_
- `geopandas: Python tools for geographic data <http://geopandas.readthedocs.io/>`_
- `h5netcdf: Pythonic interface to netCDF4 via h5py <https://h5netcdf.org/>`_
- `OWSLib: Pythonic interface for Open Geospatial Consortium (OGC) web services <https://owslib.readthedocs.io/>`_
- `s3fs: Pythonic file interface to S3 built on top of botocore <https://s3fs.readthedocs.io/en/latest/>`_
- `zarr: Chunked, compressed, N-dimensional arrays in Python <https://zarr.readthedocs.io/en/stable/>`_

Disclaimer
Expand Down
2 changes: 1 addition & 1 deletion doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ Interactive visualization and data extraction tool for ICESat-2 ATL14/15 Gridded
:hidden:
:caption: API Reference

api_reference/api.rst
api_reference/convert.rst
api_reference/io.rst
api_reference/tools.rst
api_reference/utilities.rst
api_reference/visualization.rst

.. toctree::
:maxdepth: 1
Expand Down
12 changes: 12 additions & 0 deletions doc/source/release_notes/release-v0.0.3.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
##################
`Release v0.0.3`__
##################

* ``fix``: include ``version.txt`` in manifest
* ``fix``: NSIDC request type on s3 is now ``'application/x-netcdf'``
* ``refactor``: use ``setuptools_scm`` to set version (`#26 <https://github.com/tsutterley/IS2view/pull/26>`_)
* ``refactor``: split ``widgets`` and ``leaflet`` tools into separate modules (`#26 <https://github.com/tsutterley/IS2view/pull/26>`_)
* ``docs``: add ATL14/15 region map (`#27 <https://github.com/tsutterley/IS2view/pull/27>`_)
* ``feat``: add capability to save map as a static image for `#28 <https://github.com/tsutterley/IS2view/issues/28>`_ (`#29 <https://github.com/tsutterley/IS2view/pull/29>`_)

.. __: https://github.com/tsutterley/IS2view/releases/tag/0.0.3
Loading

0 comments on commit c68184c

Please sign in to comment.