# Copyright 2016-2019 Doug Latornell, 43ravens
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A collection of constants and functions for use by figure modules in the
:kbd:`nowcast.figures` namespace(s).
.. note::
These constants and functions are intended for use *only* by :kbd:`nowcast.figures`
modules in order to avoid coupling between figure generation code and code
used for other purposes.
If you find that you want to use one of these functions outside of those
namespaces please talk refactor the function into a tools package.
"""
from types import SimpleNamespace
import arrow
from mpl_toolkits.basemap import Basemap
import numpy
#: Dictionary of map parameters namespace objects.
#: Items are intended for use as :kbd:`map_params` arguments in
#: :py:func:`nowcast.figures.shared.projected_lon_lat_axes` calls.
#: Each item must have the following attributes:
#:
#: :kbd:`projection`
#: (:py:obj:`str`)
#: Map projection to use.
#: Please see https://matplotlib.org/basemap/users/mapsetup.html for
#: the list of available projections.
#: Look for the value of the :kbd:`projection` argument in the
#: :py:func:`~mpl_toolkits.basemap.Basemap` calls in the code examples.
#: :kbd:`ll_lon`
#: (:py:obj:`float` or :py:obj:`int`)
#: Longitude of the lower left corner of the map region.
#: :kbd:`ur_lon`
#: (:py:obj:`float` or :py:obj:`int`)
#: Longitude of the upper right corner of the map region.
#: :kbd:`ll_lat`
#: (:py:obj:`float` or :py:obj:`int`)
#: Latitude of the lower left corner of the map region.
#: :kbd:`ur_lat`
#: (:py:obj:`float` or :py:obj:`int`)
#: Latitude of the upper right corner of the map region.
#: :kbd:`lon_0_offset`
#: (:py:obj:`float` or :py:obj:`int`)
#: Central longitude offset.
#: This is a tuning value that is used in combination with the map
#: region corner values above to position the map region withing the
#: plotting axes.
#: Unfortunately,
#: there is no evident algorithm to find its value.
#: The recommended process is to make trial-and-error plots of a
#: a model variable field and the bathymetry,
#: starting with large map region,
#: and gradually reducing the size of the map region and adjusting
#: the :kbd:`lon_0_offset` value.
#: :kbd:`meridians`
#: (:py:class:`numpy.ndarray`)
#: Array of longitudes to mark with grid lines and axis tick labels.
#: :kbd:`parallels`
#: (:py:class:`numpy.ndarray`)
#: Array of latitudes to mark with grid lines and axis tick labels.
MAP_PARAMS = {
"full domain": SimpleNamespace(
projection="lcc",
ll_lon=-68.3,
ur_lon=-53.45,
ll_lat=37.425,
ur_lat=49.1,
lon_0_offset=391.8,
meridians=numpy.arange(-72, -50, 2),
parallels=numpy.arange(38, 50, 1),
)
}
[docs]def localize_time(data_array, time_coord="time_counter", local_datetime=None):
"""Offset :kbd:`data_array` times to account for local time zone
difference from UTC and add :kbd:`tz_name` attribute to :kbd:`data_array`.
.. note::
This function is intended for use just before presentation/output
of :kbd:`data_array`. It is strongly recommended to do all date/time
calculations in UTC to avoid time change issues.
:param data_array: Data array or dataset object to adjust time values of.
:type data_array: :py:class:`xarray.DataArray` or :py:class:`xarray.Dataset`
:param str time_coord: Optional name of the time coordinate.
:param local_datetime: Optional timezone-aware local date/time to use as
basis to calculate offset from UTC. The 1st element
of :kbd:`data_array` is used when
:kbd:`local_datetime` is :py:class:`None`.
:type local_datetime: :py:class:`arrow.Arrow`
"""
time_values = getattr(data_array, time_coord).values
if local_datetime is None:
local_datetime = arrow.get(str(time_values[0])).to("local")
tz_offset = local_datetime.tzinfo.utcoffset(local_datetime.datetime)
tz_name = local_datetime.tzinfo.tzname(local_datetime.datetime)
numpy_offset = numpy.timedelta64(tz_offset.days, "D") + numpy.timedelta64(
tz_offset.seconds, "s"
)
data_array[time_coord] = time_values + numpy_offset
data_array.attrs["tz_name"] = tz_name
[docs]def projected_lon_lat_axes(
ax,
map_params,
bathy,
theme,
meridians_labels=(False, False, True, True),
parallels_labels=(True, True, False, False),
):
"""Use Basemap (https://matplotlib.org/basemap/) to calculate transformed
coordinates for :kbd:`ax` according to the projection and other parameters
in :kbd:`map_params`.
The :kbd:`(x, y)` arrays that are returned are the transformed coordinates to use
for plotting on :kbd:`ax`.
:kbd:`ax` will be labelled with longitudes and latitudes on the sides indicated
by :kbd:`meridians_labels` and :kbd:`parallels_labels` and it will show a
longitude/latitude grid.
:param ax: Axes to calculate transformed coordinates for.
:type ax: :py:class:`matplotlib.axes.Axes`
:param map_params: Namespace of map parameters to use for calculation of
transformed coordinates.
Please see :py:data:`nowcast.figures.shared.MAP_PARAMS`
for definitions of the namespace attributes,
and examples.
:type map_params: :py:class:`types.SimpleNamespace`
:param bathy: A bathymetry dataset that provides the longitude and latitudes
arrays on which variables to be plotted are calculated.
The longitude and latitude variables names in the dataset are
assumed to be :kbd:`nav_lon` and :kbd:`nav_lat`.
.. note::
This dataset *must* be neither masked nor scaled.
That is accomplished by using the :kbd:`mask_and_scale=False`
argument in :py:func:`xarray.open_dataset`.
:type bathy: :py:class:`xarray.Dataset`
:param theme: Module-like object that defines the style elements for the
figure. See :py:mod:`nowcast.figures.website_theme` for an
example.
:type theme: :py:mod:`nowcast.figures.website_theme`
:param meridians_labels: Axes spines on which to show longitude labels.
:type meridians_labels: 4-tuple of booleans (top, bottom, left, right)
:param parallels_labels: Axes spines on which to show latitude labels.
:type parallels_labels: 4-tuple of booleans (top, bottom, left, right)
:return: Transformed x and y coordinates to use for plotting on :kbd:`ax`.
:rtype: 2-tuple of :py:class:`numpy.ndarray`
"""
central_lon = (
(map_params.ur_lon - map_params.ll_lon) / 2
+ map_params.ll_lon
+ map_params.lon_0_offset
)
central_lat = (map_params.ur_lat - map_params.ll_lat) / 2 + map_params.ll_lat
m = Basemap(
ax=ax,
projection=map_params.projection,
lon_0=central_lon,
lat_0=central_lat,
llcrnrlon=map_params.ll_lon,
urcrnrlon=map_params.ur_lon,
llcrnrlat=map_params.ll_lat,
urcrnrlat=map_params.ur_lat,
)
# lon/lat grid
m.drawmeridians(
map_params.meridians,
labels=meridians_labels,
textcolor=theme.COLOURS["text"]["axis"],
fontproperties=theme.FONTS["axis small"],
)
m.drawparallels(
map_params.parallels,
labels=parallels_labels,
textcolor=theme.COLOURS["text"]["axis"],
fontproperties=theme.FONTS["axis small"],
)
x, y = m(bathy.nav_lon.values, bathy.nav_lat.values)
return x, y