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
33 changes: 0 additions & 33 deletions docs/source/concepts/field.rst

This file was deleted.

65 changes: 65 additions & 0 deletions docs/source/concepts/field/ensemble.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
.. _ensemble_component:

Ensemble component
==================

Every :py:class:`~earthkit.data.core.field.Field` may carry an *ensemble component* that
identifies which ensemble member the field belongs to. The ensemble component is accessible
via the :attr:`ensemble` attribute of a field and is represented by a subclass of
:py:class:`~earthkit.data.field.component.ensemble.EnsembleBase`.

Fields from deterministic (non-ensemble) data have an
:py:class:`~earthkit.data.field.component.ensemble.EmptyEnsemble` component where
:meth:`~earthkit.data.field.component.ensemble.EnsembleBase.member` returns ``None``.

.. code-block:: python

>>> import earthkit.data as ekd
>>> field = ekd.from_source("sample", "ens_cf_pf.grib").to_fieldlist()[2]
>>> field.ensemble.member()
'1'
>>> field.get("ensemble.member")
'1'

The ensemble component is **immutable**. Use the
:meth:`~earthkit.data.field.component.ensemble.EnsembleBase.set` method (or
:meth:`~earthkit.data.core.field.Field.set` on the field) to derive a modified copy:

.. code-block:: python

>>> new_field = field.set({"ensemble.member": "5"})
>>> new_field.ensemble.member()
'5'


Member representation
---------------------

The member value is stored internally as a string. Integer values passed during construction
or via :meth:`set` are converted automatically. ``None`` represents the absence of an
ensemble member (deterministic data).


Accessing ensemble information
-------------------------------

.. list-table::
:header-rows: 1
:widths: 32 68

* - Key
- Description
* - ``ensemble.member``
- Ensemble member identifier as a string, or ``None`` for deterministic data.
* - ``ensemble.realization``
- Alias of ``ensemble.member``.
* - ``ensemble.realisation``
- Alias of ``ensemble.member``.


How-tos
-------

- :ref:`/how-tos/field/field_overview.ipynb`
- :ref:`/how-tos/grib/grib_overview.ipynb`
- :ref:`/how-tos/xr_engine/xarray_engine_ensemble.ipynb`
121 changes: 121 additions & 0 deletions docs/source/concepts/field/field.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
.. _field_concept:

Field
=====

A :py:class:`~earthkit.data.core.field.Field` represents a single horizontal slice of a
geophysical quantity at a particular time and vertical level. It is the fundamental data
structure in EarthKit Data and bundles together:

- the **data values** — a 2-D (or 1-D unstructured) array of floating-point numbers;
- a set of **metadata components** that fully describe the field (see below).

Fields are created automatically when reading data through
:py:func:`~earthkit.data.from_source` and are normally accessed as members of a
:py:class:`~earthkit.data.core.fieldlist.FieldList`. See :doc:`fieldlist` for how to work
with collections of fields.


Component-based metadata
------------------------

The Field class is not polymorphic. Instead it is composed of a set of replaceable,
polymorphic *components*, each responsible for a distinct aspect of the metadata:

.. list-table::
:header-rows: 1
:widths: 20 25 55

* - Attribute
- Component class
- What it describes
* - ``field.parameter``
- :py:class:`~earthkit.data.field.component.parameter.ParameterBase`
- Physical quantity: variable name, units, CF names, chemical or optical properties.
* - ``field.time``
- :py:class:`~earthkit.data.field.component.time.TimeBase`
- Temporal coordinate: base datetime, forecast step, valid datetime.
* - ``field.vertical``
- :py:class:`~earthkit.data.field.component.vertical.VerticalBase`
- Vertical coordinate: level value, level type, layer bounds.
* - ``field.geography``
- :py:class:`~earthkit.data.field.component.geography.GeographyBase`
- Horizontal grid: lat/lon arrays, bounding box, projection, grid type.
* - ``field.ensemble``
- :py:class:`~earthkit.data.field.component.ensemble.EnsembleBase`
- Ensemble member identifier.
* - ``field.proc``
- :py:class:`~earthkit.data.field.component.proc.ProcBase`
- Post-processing operations applied to produce the field (e.g. accumulation).

Each component exposes its metadata through named methods (e.g.
``field.vertical.level()``) and through the generic
:meth:`~earthkit.data.core.field.Field.get` method using a ``"component.key"`` prefix:

.. code-block:: python

>>> import earthkit.data as ekd
>>> field = ekd.from_source("sample", "test.grib").to_fieldlist()[0]
>>> field.parameter.variable()
'2t'
>>> field.get("parameter.variable")
'2t'
>>> field.vertical.level()
0
>>> field.get("time.base_datetime")
datetime.datetime(2020, 1, 1, 0, 0)

For a full description of each component's available keys see the dedicated pages:
:doc:`parameter`, :doc:`time`, :doc:`vertical`, :doc:`geography`, :doc:`ensemble`,
and :doc:`proc`.


Immutability of field values
-----------------------------

Field values are immutable: :meth:`~earthkit.data.core.field.Field.values` always returns
a **copy** of the underlying array. Modifications to that copy do not affect the stored
data. This guarantees that the original data remains consistent no matter how many
downstream operations consume it.


Modifying a field
-----------------

Because both values and metadata are immutable, changes are expressed by creating a
new field via :meth:`~earthkit.data.core.field.Field.set`. The method accepts a dictionary
of ``"component.key": value`` pairs (and/or a ``"values"`` entry) and returns a new field
with the requested changes applied while leaving all other attributes unchanged:

.. code-block:: python

>>> new_field = field.set({"vertical.level": 500, "time.step": 6})
>>> new_field.vertical.level()
500
>>> new_field.time.step()
datetime.timedelta(seconds=21600)


Arithmetic operations
----------------------

Fields support element-wise arithmetic directly (``+``, ``-``, ``*``, ``/``). Each
operation returns a new field whose data is the result of the operation. The metadata
(parameter, time, vertical, geography, ensemble, proc) of the **left-hand operand** is
retained in the result without modification:

.. code-block:: python

>>> fl = ekd.from_source("sample", "tuv_pl.grib").to_fieldlist()
>>> result = fl[0] + fl[1]
>>> result.parameter.variable() == fl[0].parameter.variable()
True


How-tos
-------

- :ref:`/how-tos/field/field_overview.ipynb`
- :ref:`/how-tos/grib/grib_overview.ipynb`
- :ref:`/how-tos/grib/grib_modify_values.ipynb`
- :ref:`/how-tos/grib/grib_modify_metadata.ipynb`
114 changes: 114 additions & 0 deletions docs/source/concepts/field/geography.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
.. _geography_component:

Geography component
===================

Every :py:class:`~earthkit.data.core.field.Field` carries a *geography component* that describes
the horizontal spatial coordinate system of the field. The geography component is accessible via
the :attr:`geography` attribute of a field and is represented by a subclass of
:py:class:`~earthkit.data.field.component.geography.GeographyBase`.

.. code-block:: python

>>> import earthkit.data as ekd
>>> field = ekd.from_source("sample", "test.grib").to_fieldlist()[0]
>>> field.geography.shape
(19, 36)
>>> field.geography.area()
(70, -20, 35, 40)
>>> field.geography.grid_type()
'regular_ll'

The same information is available through the generic :meth:`~earthkit.data.core.field.Field.get`
interface using the ``"geography."`` prefix:

.. code-block:: python

>>> field.get("geography.area")
(70, -20, 35, 40)
>>> field.get("geography.shape")
(19, 36)

The geography component is **immutable**. Use the
:meth:`~earthkit.data.field.component.geography.GeographyBase.set` method (or
:meth:`~earthkit.data.core.field.Field.set` on the field) to derive a modified copy.
When changing the grid it is typically necessary to supply new field values to match the
new grid shape:

.. code-block:: python

>>> import numpy as np
>>> values = np.random.rand(19, 36)
>>> new_field = field.set({"geography.grid_spec": [10, 10], "values": values})
>>> new_field.geography.area()
(90.0, 0, -90.0, 360.0)


Geography types
---------------

The appropriate geography subclass is determined automatically from the data:

- **LatLonGeography** — irregular or curvilinear lat/lon grid where every point has an
explicit latitude and longitude.
- **MeshedLatLonGeography** — regular lat/lon grid defined by distinct (1-D) latitude and
longitude arrays that form a full Cartesian mesh.
- **GridsSpecBasedGeography** — grid defined by an eckit-geo grid specification string or
dictionary (requires optional eckit-geo grid support).
- **EmptyGeography** — placeholder used when no coordinate information is available.


Accessing geography information
--------------------------------

All geography keys are accessible through :meth:`~earthkit.data.core.field.Field.get` with the
``"geography."`` prefix, and can therefore be used in
:meth:`~earthkit.data.core.fieldlist.FieldList.sel`,
:meth:`~earthkit.data.core.fieldlist.FieldList.order_by`, and
:meth:`~earthkit.data.core.fieldlist.FieldList.metadata`.

.. list-table::
:header-rows: 1
:widths: 32 68

* - Key
- Description
* - ``geography.latitudes``
- Array of latitude values for every grid point. ``dtype`` argument supported for type control.
* - ``geography.longitudes``
- Array of longitude values for every grid point. ``dtype`` argument supported for type control.
* - ``geography.distinct_latitudes``
- 1-D array of unique latitude values for regular grids, or ``None`` for irregular grids.
* - ``geography.distinct_longitudes``
- 1-D array of unique longitude values for regular grids, or ``None`` for irregular grids.
* - ``geography.x``
- Array of x-coordinates in the native CRS. ``dtype`` argument supported.
* - ``geography.y``
- Array of y-coordinates in the native CRS. ``dtype`` argument supported.
* - ``geography.shape``
- Grid shape as a tuple of integers (e.g. ``(latitude_size, longitude_size)``).
* - ``geography.projection``
- :py:class:`~earthkit.data.utils.projections.Projection` object describing the CRS, or ``None``.
* - ``geography.bounding_box``
- :py:class:`~earthkit.data.utils.bbox.BoundingBox` for the grid extent.
* - ``geography.area``
- Bounding box as a ``(north, west, south, east)`` tuple of floats.
* - ``geography.grid_type``
- String identifying the grid type (e.g. ``"regular_ll"``, ``"reduced_gg"``).
* - ``geography.grid_spec``
- Grid specification. Can be used to construct a new geography of the same type.
* - ``geography.grid``
- Grid definition understood by eckit-geo (when grid support is available).
* - ``geography.unique_grid_id``
- A hashable identifier that is the same for two fields sharing an identical grid.


How-tos
-------

- :ref:`/how-tos/field/field_overview.ipynb`
- :ref:`/how-tos/grib/grib_overview.ipynb`
- :ref:`/how-tos/grib/grib_lat_lon_value_ll.ipynb`
- :ref:`/how-tos/grib/grib_lat_lon_value_rgg.ipynb`
- :ref:`/how-tos/grib/grib_nearest_gridpoint.ipynb`
- :ref:`/how-tos/misc/projection.ipynb`
15 changes: 15 additions & 0 deletions docs/source/concepts/field/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.. _concepts-field:

Field
===========================

.. toctree::
:maxdepth: 1

field.rst
vertical.rst
parameter.rst
time.rst
ensemble.rst
geography.rst
proc.rst
Loading
Loading