Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into retire_napolean
Browse files Browse the repository at this point in the history
* upstream/main:
  Enhance Cube slicing docs (SciTools#5735)
  Bump scitools/workflows from 2024.04.0 to 2024.04.1 (SciTools#5914)
  [pre-commit.ci] pre-commit autoupdate (SciTools#5913)
  Revert "Updated environment lockfiles (SciTools#5911)" (SciTools#5912)
  Shapefile user guide typos (SciTools#5759)
  Updated environment lockfiles (SciTools#5911)
  • Loading branch information
tkknight committed Apr 18, 2024
2 parents bc1c0ef + 6cc7c3d commit faf84d4
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 35 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci-manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ concurrency:
jobs:
manifest:
name: "check-manifest"
uses: scitools/workflows/.github/workflows/[email protected].0
uses: scitools/workflows/.github/workflows/[email protected].1
2 changes: 1 addition & 1 deletion .github/workflows/refresh-lockfiles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ on:

jobs:
refresh_lockfiles:
uses: scitools/workflows/.github/workflows/[email protected].0
uses: scitools/workflows/.github/workflows/[email protected].1
secrets: inherit
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ repos:
- id: no-commit-to-branch

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.3.5"
rev: "v0.3.7"
hooks:
- id: ruff
types: [file, python]
Expand Down
1 change: 1 addition & 0 deletions docs/src/common_links.inc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
.. _ruff: https://github.com/astral-sh/ruff
.. _SciTools: https://github.com/SciTools
.. _scitools-iris: https://pypi.org/project/scitools-iris/
.. _Shapely: https://shapely.readthedocs.io/en/stable/index.html
.. _sphinx: https://www.sphinx-doc.org/en/master/
.. _sphinx-apidoc: https://github.com/sphinx-contrib/apidoc
.. _test-iris-imagehash: https://github.com/SciTools/test-iris-imagehash
Expand Down
31 changes: 21 additions & 10 deletions docs/src/userguide/subsetting_a_cube.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. include:: ../common_links.inc

.. _subsetting_a_cube:

=================
Expand Down Expand Up @@ -338,26 +340,32 @@ Cube Masking
Masking from a shapefile
^^^^^^^^^^^^^^^^^^^^^^^^

Often we want to perform so kind of analysis over a complex geographical feature - only over land points or sea points:
or over a continent, a country, a river watershed or administrative region. These geographical features can often be described by shapefiles.
Shapefiles are a file format first developed for GIS software in the 1990s, and now `Natural Earth`_ maintain a large freely usable database of shapefiles of many geographical and poltical divisions,
accessible via cartopy. Users may also provide their own custom shapefiles.
Often we want to perform some kind of analysis over a complex geographical feature e.g.,

- over only land/sea points
- over a continent, country, or list of countries
- over a river watershed or lake basin
- over states or administrative regions of a country

These geographical features can often be described by `ESRI Shapefiles`_. Shapefiles are a file format first developed for GIS software in the 1990s, and `Natural Earth`_ maintain a large freely usable database of shapefiles of many geographical and political divisions,
accessible via `cartopy`_. Users may also provide their own custom shapefiles for `cartopy`_ to load, or their own underlying geometry in the same format as a shapefile geometry.

These shapefiles can be used to mask an iris cube, so that any data outside the bounds of the shapefile is hidden from further analysis or plotting.

First, we load the correct shapefile from NaturalEarth via the `Cartopy`_ instructions. Here we get one for Brazil.
The `.geometry` attribute of the records in the reader contain the shapely polygon we're interested in - once we have those we just need to provide them to
the :class:`iris.util.mask_cube_from_shapefile` function. Once plotted, we can see that only our area of interest remains in the data.
First, we load the correct shapefile from NaturalEarth via the `Cartopy_shapereader`_ instructions. Here we get one for Brazil.
The `.geometry` attribute of the records in the reader contain the `Shapely`_ polygon we're interested in. They contain the coordinates that define the polygon (or set of lines) being masked
and once we have those we just need to provide them to the :class:`iris.util.mask_cube_from_shapefile` function.
This returns a copy of the cube with a :class:`numpy.masked_array` as the data payload, where the data outside the shape is hidden by the masked array. We can see this in the following example.


.. plot:: userguide/plotting_examples/masking_brazil_plot.py
:include-source:

We can see that the dimensions of the cube haven't changed - the plot is still global. But only the data over Brazil is plotted - the rest is masked.
We can see that the dimensions of the cube haven't changed - the plot is still global. But only the data over Brazil is plotted - the rest has been masked out.

.. note::
While Iris will try to dynamically adjust the shapefile to mask cubes of different projections, it can struggle with rotated pole projections and cubes with Meridians not at 0°
Converting your Cube's coordinate system may help if you get a fully masked cube from this function.
Converting your Cube's coordinate system may help if you get a fully masked cube as the output from this function unexpectedly.


Cube Iteration
Expand Down Expand Up @@ -473,5 +481,8 @@ Similarly, Iris cubes have indexing capability::
print(cube[1, ::-2])


.. _Cartopy: https://scitools.org.uk/cartopy/docs/latest/tutorials/using_the_shapereader.html#id1
.. _Cartopy_shapereader: https://scitools.org.uk/cartopy/docs/latest/tutorials/using_the_shapereader.html#id1
.. _Natural Earth: https://www.naturalearthdata.com/
.. _ESRI Shapefiles: https://support.esri.com/en-us/technical-paper/esri-shapefile-technical-description-279


4 changes: 2 additions & 2 deletions docs/src/whatsnew/latest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ This document explains the changes made to Iris for this release
📚 Documentation
================

#. N/A
#. `@hsteptoe`_ added more detailed examples to :class:`~iris.cube.Cube` functions :func:`~iris.cube.Cube.slices` and :func:`~iris.cube.Cube.slices_over`. (:pull:`5735`)


💼 Internal
Expand All @@ -90,7 +90,7 @@ This document explains the changes made to Iris for this release
Whatsnew author names (@github name) in alphabetical order. Note that,
core dev names are automatically included by the common_links.inc:
.. _@hsteptoe: https://github.com/hsteptoe


.. comment
Expand Down
99 changes: 79 additions & 20 deletions lib/iris/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -3403,10 +3403,37 @@ def slices_over(self, ref_to_slice):
Examples
--------
For example, to get all subcubes along the time dimension::
for sub_cube in cube.slices_over('time'):
print(sub_cube)
For example, for a cube with dimensions `realization`, `time`, `latitude` and
`longitude`:
>>> fname = iris.sample_data_path('GloSea4', 'ensemble_01[01].pp')
>>> cube = iris.load_cube(fname, 'surface_temperature')
>>> print(cube.summary(shorten=True))
surface_temperature / (K) (realization: 2; time: 6; latitude: 145; longitude: 192)
To get all 12x2D longitude/latitude subcubes:
>>> for sub_cube in cube.slices_over(['realization', 'time']):
... print(sub_cube.summary(shorten=True))
surface_temperature / (K) (latitude: 145; longitude: 192)
surface_temperature / (K) (latitude: 145; longitude: 192)
surface_temperature / (K) (latitude: 145; longitude: 192)
surface_temperature / (K) (latitude: 145; longitude: 192)
surface_temperature / (K) (latitude: 145; longitude: 192)
surface_temperature / (K) (latitude: 145; longitude: 192)
surface_temperature / (K) (latitude: 145; longitude: 192)
surface_temperature / (K) (latitude: 145; longitude: 192)
surface_temperature / (K) (latitude: 145; longitude: 192)
surface_temperature / (K) (latitude: 145; longitude: 192)
surface_temperature / (K) (latitude: 145; longitude: 192)
surface_temperature / (K) (latitude: 145; longitude: 192)
To get realizations as 2x3D separate subcubes, using the `realization` dimension index:
>>> for sub_cube in cube.slices_over(0):
... print(sub_cube.summary(shorten=True))
surface_temperature / (K) (time: 6; latitude: 145; longitude: 192)
surface_temperature / (K) (time: 6; latitude: 145; longitude: 192)
Notes
-----
Expand All @@ -3421,7 +3448,7 @@ def slices_over(self, ref_to_slice):
iris.cube.Cube.slices :
Return an iterator of all subcubes given the coordinates or dimension indices.
"""
""" # noqa: D214, D406, D407, D410, D411
# Required to handle a mix between types.
if _is_single_item(ref_to_slice):
ref_to_slice = [ref_to_slice]
Expand Down Expand Up @@ -3463,29 +3490,61 @@ def slices(self, ref_to_slice, ordered=True):
A mix of input types can also be provided. They must all be
orthogonal (i.e. point to different dimensions).
ordered : bool, default=True
If True, the order which the coords to slice or data_dims
are given will be the order in which they represent the data in
the resulting cube slices. If False, the order will follow that of
the source cube. Default is True.
If True, subcube dimensions are ordered to match the dimension order
in `ref_to_slice`. If False, the order will follow that of
the source cube.
Returns
-------
An iterator of subcubes.
Examples
--------
For example, to get all 2d longitude/latitude subcubes from a
multi-dimensional cube::
for sub_cube in cube.slices(['longitude', 'latitude']):
print(sub_cube)
For example, for a cube with dimensions `realization`, `time`, `latitude` and
`longitude`:
>>> fname = iris.sample_data_path('GloSea4', 'ensemble_01[01].pp')
>>> cube = iris.load_cube(fname, 'surface_temperature')
>>> print(cube.summary(shorten=True))
surface_temperature / (K) (realization: 2; time: 6; latitude: 145; longitude: 192)
To get all 12x2D longitude/latitude subcubes:
>>> for sub_cube in cube.slices(['longitude', 'latitude']):
... print(sub_cube.summary(shorten=True))
surface_temperature / (K) (longitude: 192; latitude: 145)
surface_temperature / (K) (longitude: 192; latitude: 145)
surface_temperature / (K) (longitude: 192; latitude: 145)
surface_temperature / (K) (longitude: 192; latitude: 145)
surface_temperature / (K) (longitude: 192; latitude: 145)
surface_temperature / (K) (longitude: 192; latitude: 145)
surface_temperature / (K) (longitude: 192; latitude: 145)
surface_temperature / (K) (longitude: 192; latitude: 145)
surface_temperature / (K) (longitude: 192; latitude: 145)
surface_temperature / (K) (longitude: 192; latitude: 145)
surface_temperature / (K) (longitude: 192; latitude: 145)
surface_temperature / (K) (longitude: 192; latitude: 145)
.. warning::
Note that the dimension order returned in the sub_cubes matches the order specified
in the ``cube.slices`` call, *not* the order of the dimensions in the original cube.
To get all realizations as 2x3D separate subcubes, using the `time`, `latitude`
and `longitude` dimensions' indices:
>>> for sub_cube in cube.slices([1, 2, 3]):
... print(sub_cube.summary(shorten=True))
surface_temperature / (K) (time: 6; latitude: 145; longitude: 192)
surface_temperature / (K) (time: 6; latitude: 145; longitude: 192)
See Also
--------
iris.cube.Cube.slices :
Return an iterator of all subcubes given the coordinates or dimension indices.
iris.cube.Cube.slices_over :
Return an iterator of all subcubes along a given coordinate or
dimension index.
"""
""" # noqa: D214, D406, D407, D410, D411
if not isinstance(ordered, bool):
raise TypeError("'ordered' argument to slices must be boolean.")

Expand Down Expand Up @@ -4512,8 +4571,8 @@ def rolling_window(self, coord, aggregator, window, **kwargs):
--------
>>> import iris, iris.analysis
>>> fname = iris.sample_data_path('GloSea4', 'ensemble_010.pp')
>>> air_press = iris.load_cube(fname, 'surface_temperature')
>>> print(air_press)
>>> cube = iris.load_cube(fname, 'surface_temperature')
>>> print(cube)
surface_temperature / (K) \
(time: 6; latitude: 145; longitude: 192)
Dimension coordinates:
Expand All @@ -4537,7 +4596,7 @@ def rolling_window(self, coord, aggregator, window, **kwargs):
'Data from Met Office Unified Model'
um_version '7.6'
>>> print(air_press.rolling_window('time', iris.analysis.MEAN, 3))
>>> print(cube.rolling_window('time', iris.analysis.MEAN, 3))
surface_temperature / (K) \
(time: 4; latitude: 145; longitude: 192)
Dimension coordinates:
Expand Down

0 comments on commit faf84d4

Please sign in to comment.