Skip to content
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
14 changes: 8 additions & 6 deletions docs/api/animations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@ Animations

This section provides the API reference for animation functions in ``mapflow``.

.. admonition:: Animation
:class: dropdown
.. dropdown:: Animation

.. autoclass:: mapflow.Animation
:members: __call__

.. admonition:: animation
:class: dropdown
.. dropdown:: QuiverAnimation

.. autoclass:: mapflow.QuiverAnimation
:members: quiver

.. dropdown:: animate

.. autofunction:: mapflow.animate

.. admonition:: animate_quiver
:class: dropdown
.. dropdown:: animate_quiver

.. autofunction:: mapflow.animate_quiver
9 changes: 3 additions & 6 deletions docs/api/static.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,15 @@ Static plots

This section provides the API reference for static plotting functions in ``mapflow``.

.. admonition:: PlotModel
:class: dropdown
.. dropdown:: PlotModel

.. autoclass:: mapflow.PlotModel
:members: __call__

.. admonition:: plot_da
:class: dropdown
.. dropdown:: plot_da

.. autofunction:: mapflow.plot_da

.. admonition:: plot_da_quiver
:class: dropdown
.. dropdown:: plot_da_quiver

.. autofunction:: mapflow.plot_da_quiver
64 changes: 61 additions & 3 deletions docs/how_to_use.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The main function of ``mapflow`` is ``animate``, which creates a video from an `
.. raw:: html

<video width="640" height="480" controls>
<source src="_static/animation.mp4" type="video/mp4">
<source src="../_static/animation.mp4" type="video/mp4">
</video>

Creating a static plot
Expand All @@ -42,17 +42,75 @@ Creating a static plot
:align: center
:width: 75%

Quiver plots
------------

You can also create quiver plots to visualize vector fields. The `plot_da_quiver` function takes two `xarray.DataArray` objects representing the U and V components of the vector field.

.. code-block:: python

import xarray as xr
from mapflow import plot_da_quiver

ds = xr.tutorial.load_dataset("air_temperature_gradient").isel(time=0)
plot_da_quiver(u=ds["dTdx"], v=ds["dTdy"], subsample=4)

Similarly, you can create quiver animations using the `animate_quiver` function.

.. code-block:: python

import xarray as xr
from mapflow import animate_quiver

ds = xr.tutorial.load_dataset("air_temperature_gradient")
animate_quiver(u=ds["dTdx"], v=ds["dTdy"], path='quiver_animation.mkv', subsample=3)

Advanced Usage: `PlotModel` and `Animation` classes
---------------------------------------------------

For more control and efficiency when creating multiple plots or animations of the same geographic domain, you can use the `PlotModel` and `Animation` classes directly. These classes pre-compute geographic borders, which can save time.

Using `PlotModel`:

.. code-block:: python

import xarray as xr
from mapflow import PlotModel

ds = xr.tutorial.open_dataset("era5-2mt-2019-03-uk.grib")
da = ds["t2m"].isel(time=0)

p = PlotModel(x=da.longitude, y=da.latitude)
p(da)

Using `Animation`:

.. code-block:: python

import xarray as xr
from mapflow import Animation

ds = xr.tutorial.open_dataset("era5-2mt-2019-03-uk.grib")
da = ds["t2m"].isel(time=slice(120))

animation = Animation(x=da.longitude, y=da.latitude, verbose=1)
animation(da, "animation.mp4")

Key Features
------------

``mapflow`` is designed to be intuitive and requires minimal user input. Here are some of the key features that make it easy to use:

* **Automatic Coordinate Detection**: ``mapflow`` automatically detects the names of the x, y, and time coordinates in your ``xarray.DataArray``. If it fails to find them, you can specify them using the ``x``, ``y``, and ``time_name`` arguments.
* **Automatic Coordinate Detection**: ``mapflow`` automatically detects the names of the x, y, and time coordinates in your ``xarray.DataArray``. If it fails to find them, you can specify them using the ``x_name``, ``y_name``, and ``time_name`` arguments.

* **Automatic CRS Detection**: The library automatically tries to determine the Coordinate Reference System (CRS) from your data. If no CRS is found, you can pass it directly using the ``crs`` argument.

* **Robust Colorbars**: ``mapflow`` generates a colorbar that is robust to outliers, ensuring that your data is visualized clearly. You can also customize the colorbar using the ``vmin``, ``vmax``, and ``cmap`` arguments.
* **Robust Colorbars**: ``mapflow`` generates a colorbar that is robust to outliers by default, using the 0.01 and 99.9 quantiles. You can also customize the colorbar using the ``vmin``, ``vmax``, and ``cmap`` arguments, or even pass a custom `matplotlib.colors.Normalize` object via the `norm` argument.

* **Integrated World Borders**: ``mapflow`` includes a built-in set of world borders for plotting. If you need to use custom borders, you can provide them as a ``geopandas.GeoSeries`` or ``geopandas.GeoDataFrame`` using the ``borders`` argument.

* **One-line Alternative to Cartopy**: The ``plot_da`` function provides a simple, one-line alternative to creating maps with ``cartopy``, making it quick and easy to visualize your geospatial data.

* **Flexible Output**: Animations can be saved in various formats, including `.mp4`, `.mov`, and `.avi`.

* **Parallel Processing**: Frame generation for animations is done in parallel to speed up the process. You can control the number of parallel jobs with the `n_jobs` argument.
95 changes: 50 additions & 45 deletions mapflow/_classic.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ class PlotModel:
plots of the same geographic domain, as it pre-computes geographic borders.

Args:
x, y: Coordinates for the plot.
crs: Coordinate Reference System. Defaults to 4326.
x (np.ndarray): Array of x-coordinates (e.g., longitudes).
y (np.ndarray): Array of y-coordinates (e.g., latitudes).
crs (int | str | CRS, optional): Coordinate Reference System.
Defaults to 4326 (WGS84).
borders (gpd.GeoDataFrame | gpd.GeoSeries | None): Custom borders to use.
If None, defaults to world borders from a packaged GeoPackage.

Expand Down Expand Up @@ -262,26 +264,23 @@ def plot_da(da: xr.DataArray, x_name=None, y_name=None, crs=4326, **kwargs):
can be reused for multiple plots.

Args:
da: xarray DataArray with 2D data to plot. Must have appropriate coordinates.
x_name: Name of the x-coordinate dimension. If None, will attempt to guess.
y_name: Name of the y-coordinate dimension. If None, will attempt to guess.
crs: Coordinate Reference System. Can be:
- EPSG code (e.g., 4326 for WGS84)
- PROJ string
- pyproj.CRS object
- If the DataArray has a 'crs' attribute, that will be used by default
**kwargs: Additional arguments passed to PlotModel.__call__(), including:
- figsize: Tuple (width, height) in inches
- qmin/qmax: Quantile ranges for color scaling (0-100)
- vmin/vmax: Explicit value ranges for color scaling
- log: Whether to use logarithmic color scale
- cmap: Colormap name
- norm: Custom normalization
- shading: Color shading method
- shrink: Colorbar shrink factor
- label: Colorbar label
- title: Plot title
- show: Whether to display the plot
da (xr.DataArray): xarray DataArray with 2D data to plot. Must have appropriate coordinates.
x_name (str, optional): Name of the x-coordinate dimension. If None, will attempt to guess from `["x", "lon", "longitude"]`.
y_name (str, optional): Name of the y-coordinate dimension. If None, will attempt to guess from `["y", "lat", "latitude"]`.
crs (int | str | CRS, optional): Coordinate Reference System. Can be an EPSG code, a PROJ string, or a pyproj.CRS object.
If the DataArray has a 'crs' attribute, that will be used by default. Defaults to 4326 (WGS84).
**kwargs: Additional arguments passed to `PlotModel.__call__`, including:
- `figsize` (tuple, optional): Figure size (width, height) in inches.
- `qmin`/`qmax` (float, optional): Quantile ranges for color scaling (0-100).
- `vmin`/`vmax` (float, optional): Explicit value ranges for color scaling.
- `log` (bool, optional): Whether to use a logarithmic color scale.
- `cmap` (str, optional): Colormap name.
- `norm` (matplotlib.colors.Normalize, optional): Custom normalization object.
- `shading` (str, optional): Color shading method.
- `shrink` (float, optional): Colorbar shrink factor.
- `label` (str, optional): Colorbar label.
- `title` (str, optional): Plot title.
- `show` (bool, optional): Whether to display the plot.

Example:
.. code-block:: python
Expand All @@ -293,7 +292,7 @@ def plot_da(da: xr.DataArray, x_name=None, y_name=None, crs=4326, **kwargs):
plot_da(da=ds['t2m'].isel(time=0))

See Also:
PlotModel: The underlying plotting class used by this function
:class:`PlotModel`: The underlying plotting class used by this function.
"""
actual_x_name = guess_coord_name(da.coords, X_NAME_CANDIDATES, x_name, "x")
actual_y_name = guess_coord_name(da.coords, Y_NAME_CANDIDATES, y_name, "y")
Expand All @@ -310,7 +309,11 @@ def plot_da(da: xr.DataArray, x_name=None, y_name=None, crs=4326, **kwargs):


class Animation:
"""
"""A class for creating animations from 3D data with geographic borders.

This class is useful for creating multiple animations of the same geographic
domain, as it pre-computes geographic borders.

Args:
x (np.ndarray): Array of x-coordinates (e.g., longitudes).
y (np.ndarray): Array of y-coordinates (e.g., latitudes).
Expand Down Expand Up @@ -383,8 +386,7 @@ def __call__(
n_jobs=None,
timeout="auto",
):
"""
Generates an animation from a sequence of 2D data arrays.
"""Generates an animation from a sequence of 2D data arrays.

The method processes the input data, optionally upsamples it for smoother
transitions, generates individual frames in parallel, and then compiles
Expand Down Expand Up @@ -417,6 +419,8 @@ def __call__(
dpi (int, optional): Dots per inch for the saved frames. Defaults to 180.
n_jobs (int, optional): Number of parallel jobs for frame generation.
Defaults to 2/3 of CPU cores.
timeout (int | str, optional): Timeout for the ffmpeg command in seconds.
Defaults to "auto", which sets the timeout to `max(20, 0.1 * data_len)`.
"""
norm = self.plot._norm(data, vmin, vmax, qmin, qmax, norm, log)
self._animate(
Expand Down Expand Up @@ -564,8 +568,7 @@ def animate(
verbose: int = 0,
**kwargs,
):
"""
Creates an animation from an xarray DataArray.
"""Creates an animation from an xarray DataArray.

This function prepares data from an xarray DataArray (e.g., handling
geographic coordinates, extracting time information for titles) and
Expand All @@ -576,32 +579,32 @@ def animate(
path (str): Output path for the video file. Supported formats are avi, mov
and mp4.
time_name (str, optional): Name of the time coordinate in `da`. If None,
it's guessed from ['time', 't', 'times']. Defaults to None.
it's guessed from `["time", "t", "times"]`. Defaults to None.
x_name (str, optional): Name of the x-coordinate (e.g., longitude) in `da`.
If None, it's guessed from ['x', 'lon', 'longitude']. Defaults to None.
If None, it's guessed from `["x", "lon", "longitude"]`. Defaults to None.
y_name (str, optional): Name of the y-coordinate (e.g., latitude) in `da`.
If None, it's guessed from ['y', 'lat', 'latitude']. Defaults to None.
If None, it's guessed from `["y", "lat", "latitude"]`. Defaults to None.
crs (int | str | CRS, optional): Coordinate Reference System of the data.
Defaults to 4326 (WGS84).
borders (gpd.GeoDataFrame | gpd.GeoSeries | None, optional):
Custom borders to use for plotting. If None, defaults to
world borders. Defaults to None.
verbose (int, optional): Verbosity level for the Animation class.
Defaults to 0.
**kwargs: Additional keyword arguments passed to the Animation class, including:
- cmap (str): Colormap for the plot. Defaults to "jet".
- norm (matplotlib.colors.Normalize): Custom normalization object.
- log (bool): Use logarithmic color scale. Defaults to False.
- qmin (float): Minimum quantile for color normalization. Defaults to 0.01.
- qmax (float): Maximum quantile for color normalization. Defaults to 99.9.
- vmin (float): Minimum value for color normalization. Overrides qmin.
- vmax (float): Maximum value for color normalization. Overrides qmax.
- time_format (str): Strftime format for time in titles. Defaults to "%Y-%m-%dT%H".
- upsample_ratio (int): Factor to upsample data temporally. Defaults to 4.
- fps (int): Frames per second for the video. Defaults to 24.
- n_jobs (int): Number of parallel jobs for frame generation.
- dpi (int): Dots per inch for the saved frames. Defaults to 180.
- timeout (str | int): Timeout for video creation. Defaults to 'auto'.
**kwargs: Additional keyword arguments passed to the `Animation` class, including:
- `cmap` (str, optional): Colormap for the plot.
- `norm` (matplotlib.colors.Normalize, optional): Custom normalization object.
- `log` (bool, optional): Use logarithmic color scale.
- `qmin` (float, optional): Minimum quantile for color normalization.
- `qmax` (float, optional): Maximum quantile for color normalization.
- `vmin` (float, optional): Minimum value for color normalization.
- `vmax` (float, optional): Maximum value for color normalization.
- `time_format` (str, optional): Strftime format for time in titles.
- `upsample_ratio` (int, optional): Factor to upsample data temporally.
- `fps` (int, optional): Frames per second for the video.
- `n_jobs` (int, optional): Number of parallel jobs for frame generation.
- `dpi` (int, optional): Dots per inch for the saved frames.
- `timeout` (str | int, optional): Timeout for video creation.


.. code-block:: python
Expand All @@ -612,6 +615,8 @@ def animate(
ds = xr.tutorial.open_dataset("era5-2mt-2019-03-uk.grib")
animate(da=ds['t2m'].isel(time=slice(120)), path='animation.mp4')

See Also:
:class:`Animation`: The underlying animation class used by this function.
"""
actual_time_name = guess_coord_name(da.coords, TIME_NAME_CANDIDATES, time_name, "time")
actual_x_name = guess_coord_name(da.coords, X_NAME_CANDIDATES, x_name, "x")
Expand Down
48 changes: 46 additions & 2 deletions mapflow/_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,21 @@ def check_ffmpeg():


def guess_coord_name(da_coords, candidates, provided_name, coord_type_for_error) -> str:
"""
Guesses the coordinate name if not provided.
"""Guesses the coordinate name if not provided.

Iterates through da_coords, compares lowercased names with candidates.

Args:
da_coords (list): The coordinates from a DataArray.
candidates (tuple): Plausible names for the coordinate.
provided_name (str or None): The name given by the user, if any.
coord_type_for_error (str): The type of coordinate being searched for, used for error messages.

Returns:
str: The identified coordinate name.

Raises:
ValueError: If the coordinate name cannot be guessed.
"""
if provided_name is not None:
return provided_name
Expand All @@ -43,6 +55,18 @@ def guess_coord_name(da_coords, candidates, provided_name, coord_type_for_error)


def process_crs(da, crs):
"""Processes the Coordinate Reference System (CRS).

If a CRS is not provided, it attempts to extract it from the DataArray's
`spatial_ref` coordinate. Defaults to EPSG:4326 if not found.

Args:
da (xr.DataArray): The input DataArray.
crs (int | str | CRS | None): The user-provided CRS.

Returns:
CRS: A pyproj.CRS object.
"""
if crs is None:
if "spatial_ref" in da.coords:
crs = da.spatial_ref.attrs.get("crs_wkt", 4326)
Expand All @@ -52,6 +76,26 @@ def process_crs(da, crs):


def check_da(da, time_name, x_name, y_name, crs):
"""Validates and preprocesses the input DataArray.

This function performs several checks and modifications:
- Ensures the input is an xarray.DataArray.
- Verifies that the specified time, x, and y coordinates exist.
- Processes the CRS.
- Wraps longitudes to the -180 to 180 range for geographic CRS.
- Sorts the DataArray by its coordinates.
- Ensures the DataArray is 3-dimensional and transposes it to (`time`, `y`, `x`).

Args:
da (xr.DataArray): The DataArray to check.
time_name (str): The name of the time coordinate.
x_name (str): The name of the x-coordinate.
y_name (str): The name of the y-coordinate.
crs (int | str | CRS | None): The user-provided CRS.

Returns:
tuple[xr.DataArray, CRS]: A tuple containing the processed DataArray and the CRS object.
"""
if not isinstance(da, xr.DataArray):
raise TypeError(f"Expected xarray.DataArray, got {type(da)}")
for dim in (x_name, y_name, time_name):
Expand Down
Loading
Loading