.. _coordinate_systems:
Coordinate systems
==================
Gravity and magnetic data are usually observed at points above the Earth
surface, which can be referenced through different coordinate systems.
When processing these data, applying corrections, performing forward models and
inversions, we should take into account which type of coordinate system are we
using and if our choice introduces errors into these computations.
Harmonica can handle three different 3D coordinate systems:
- :ref:`Cartesian coordinates `
- :ref:`Geodetic coordinates `
- :ref:`Spherical coordinates `
.. _cartesian_coordinates:
Cartesian coordinates
---------------------
Cartesian (or plain) coordinates are referenced by a system defined by three
orthogonal axis: two for the horizontal directions and one for the vertical
one. Harmonica name the two horizontal axis as *easting* and *northing*,
while the vertical one is referenced as *upward*, emphasizing its pointing
direction. This means that positive values implies points above the zeroth
plane, while negative values refer to points below. Harmonica assumes that
the *easting*, *northing* and *upward* coordinates are given in meters.
For example, we can define a prism below the horizontal plane at zeroth
height:
.. jupyter-execute::
import verde as vd
# Define boundaries of the rectangular prism (in meters)
west, east, south, north = -20, 20, -20, 20
bottom, top = -40, -20
prism = [west, east, south, north, bottom, top]
Or a regular grid of points at 100 meters above the zeroth plane:
.. jupyter-execute::
# Define a regular grid of observation points (coordinates in meters)
coordinates = vd.grid_coordinates(
region=(-40, 40, -40, 40), shape=(5, 5), extra_coords=100
)
easting, northing, upward = coordinates[:]
print("easting:", easting)
print("northing:", northing)
print("upward:", upward)
Because the *upward* axis points in the upward direction, it's easy to check
if a given point is lower or higher than another one:
.. jupyter-execute::
# Define two points
point_1 = (30, 20, -67)
point_2 = (30, 20, -58)
print("Point 1 is higher than point 2?", point_1[2] > point_2[2])
.. _geodetic_coordinates:
Geodetic coordinates
--------------------
Geodetic (or geographic) coordinates use a reference ellipsoid for defining
the *longitude* (:math:`\lambda`), *latitude* (:math:`\varphi`) and *height*
(:math:`h`) coordinates of points (see :ref:`geodetic coordinates figure`).
The reference ellipsoid is a mathematical surface that approximates the
figure of the Earth and it's used to define point coordinates.
.. figure:: ../_static/figures/geodetic-coordinate-system.svg
:name: geodetic coordinates figure
:width: 90%
:alt: Figure showing a reference ellipsoid along with an observation point "p". It also shows the geodetic coordinates of this observation point: its longitude, latitude and geodetic height.
Figure: Geodetic coordinates
Reference ellipsoid and a point **p** along with a geocentric Cartesian
system (:math:`X`, :math:`Y`, :math:`Z`). Where :math:`a` and :math:`b`
are the semimajor and semiminor axes of the ellipsoid, while the
:math:`\lambda`, :math:`\varphi` and :math:`h` represent the geodetic
coordinates of the point **p** in this geodetic coordinate system, where
:math:`\lambda` is the *longitude*, :math:`\varphi` the *latitude* and
:math:`h` the *height*.
The :math:`\phi` is the *spherical latitude* of point **p** (see
:ref:`spherical_coordinates`).
This figure is a modified version of [Oliveira2021]_.
Harmonica assumes that *longitude*, *latitude* are given in decimal degrees
and the ellipsoidal height is given in meters. Positive values of *height*
refer to points outside the ellipsoid, while negative values refer to points
that live inside it.
Spatial data are usually given in geodetic coordinates, along with the
reference ellipsoid on which they are defined.
For example, let's define a regular grid of points (separated by equal
angles) at 2km above the ellipsoid using :mod:`verde`.
.. jupyter-execute::
coordinates = vd.grid_coordinates(
region=(-70, -65, -35, -30), shape=(6, 6), extra_coords=2e3
)
longitude, latitude, height = coordinates[:]
print("longitude:", longitude)
print("latitude:", latitude)
print("height:", height)
Some processes need to know the reference ellipsoid used to define the
geodetic coordinates of points. :mod:`boule` offers several ellipsoids that
are commonly used on geophysical applications. Harmonica will ask for
a :class:`boule.Ellipsoid` instance as argument if it needs the reference
ellipsoid.
Lets define the WGS84 ellipsoide using :mod:`boule`:
.. jupyter-execute::
import boule as bl
ellipsoid = bl.WGS84
print(ellipsoid)
Some other processes are only designed to work under Cartesian coordinates.
We can easily *transform* geodetic coordinates to Cartesian by applying
map **projections**. This can be done through :mod:`pyproj`.
As an example, lets project the *longitude* and *latitude* coordinates of the
previously generated grid using a Mercator projection:
.. jupyter-execute::
import pyproj
# Define a Mercator projection through pyproj
projection = pyproj.Proj(proj="merc", ellps="WGS84")
# Project the longitude and latitude coordinates of the grid points
longitude, latitude = coordinates[:2]
easting, northing = projection(longitude, latitude)
print("easting:", easting)
print("northing:", northing)
Remember that this process implies projecting the geodetic coordinates onto
a flat surface, what carries projection errors. For small regions, these
errors are small, but for regional and global regions, these can heavily
increase. Projections can also be used to recover geodetic coordinates from
Cartesian ones, by setting the ``inverse`` argument to ``True``.
.. _spherical_coordinates:
Spherical coordinates
---------------------
Spherical coordinates (a.k.a spherical geocentric coordinates) are defined
by a coordinate system whose origin is located on the center of the Earth.
Each point can be represented by its *longitude* (:math:`\lambda`),
*spherical latitude* (:math:`\phi`) and *radius* (:math:`r`) (see
:ref:`spherical coordinates figure`).
.. important::
The *longitude* coordinates defined in *spherical coordinates* and in
*geodetic coordinates* are equivalent.
Nevertheless, the *spherical latitude* and the (geodetic) *latitude* are
not. They would be the same if the reference ellipsoid were a sphere.
The *longitude* and *spherical latitude* are angles given in decimal degrees,
while the *radius* is the Euclidean distance between the point and the origin
of the system (in meters).
Although this reference system is rarely used for storing data, it's used for
some non-Cartesian forward models, like tesseroids (spherical prisms).
.. figure:: ../_static/figures/spherical-coordinate-system.svg
:name: spherical coordinates figure
:width: 50%
:alt: Figure showing an observation point "p" defined in a spherical coordinate system.
Figure: Spherical coordinates
Point **p** defined in a spherical coordinate system, where
:math:`\lambda` is the *longitude*, :math:`\phi` the *latitude* and
:math:`r` the *radius*. The spherical coordinates are defined upon
a geocentric Cartesian system (:math:`X`, :math:`Y`, :math:`Z`) whose
origin is located in the center of the Earth.
Let's define a regular grid of points in spherical coordinates, located at
the same radius equal to the *mean radius of the Earth*.
.. jupyter-execute::
coordinates = vd.grid_coordinates(
region=(-70, -65, -35, -30),
shape=(6, 6),
extra_coords=ellipsoid.mean_radius,
)
longitude, sph_latitude, radius = coordinates[:]
print("longitude:", longitude)
print("spherical latitude:", sph_latitude)
print("radius:", radius)
We can convert spherical coordinates to geodetic ones through
:meth:`boule.Ellipsoid.spherical_to_geodetic`:
.. jupyter-execute::
coordinates_geodetic = ellipsoid.spherical_to_geodetic(*coordinates)
longitude, latitude, height = coordinates_geodetic[:]
print("longitude:", longitude)
print("latitude:", latitude)
print("height:", height)
While the conversion of spherical coordinates into geodetic ones can be
carried out through :meth:`boule.Ellipsoid.geodetic_to_spherical`:
.. jupyter-execute::
coordinates_spherical = ellipsoid.geodetic_to_spherical(*coordinates_geodetic)
longitude, sph_latitude, radius = coordinates_spherical[:]
print("longitude:", longitude)
print("spherical latitude:", sph_latitude)
print("radius:", radius)