From 14448a28950d0e0378796838d89d5c82de77e315 Mon Sep 17 00:00:00 2001 From: "P. L. Lim" <2090236+pllim@users.noreply.github.com> Date: Wed, 10 May 2023 11:48:36 -0400 Subject: [PATCH 1/5] TST: Remove remote call to Danbury, CT for EarthLocation.of_address to reduce server hit --- docs/coordinates/index.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/coordinates/index.rst b/docs/coordinates/index.rst index e4d9c5f7cd9e..5f12ac554d42 100644 --- a/docs/coordinates/index.rst +++ b/docs/coordinates/index.rst @@ -347,8 +347,6 @@ names, city names, etc: >>> EarthLocation.of_address('1002 Holy Grail Court, St. Louis, MO') # doctest: +FLOAT_CMP - >>> EarthLocation.of_address('Danbury, CT') # doctest: +FLOAT_CMP - By default the `OpenStreetMap Nominatim service `_ is used, but by providing a From 87b35a16ffb35105b89e8b4dfce1f19afeb72713 Mon Sep 17 00:00:00 2001 From: Chiara Marmo Date: Fri, 5 May 2023 08:45:18 -1000 Subject: [PATCH 2/5] Add support for equatorial_radius and flattening in BaseGeodeticRepresentation. Add tests and documentation. --- astropy/coordinates/__init__.py | 1 + astropy/coordinates/earth.py | 96 +------------------ astropy/coordinates/tests/test_earth.py | 3 +- .../tests/test_geodetic_representations.py | 90 ++++++++++++----- docs/changes/coordinates/14763.feature.rst | 4 + docs/coordinates/representations.rst | 26 +++++ docs/whatsnew/6.0.rst | 28 +++++- 7 files changed, 127 insertions(+), 121 deletions(-) create mode 100644 docs/changes/coordinates/14763.feature.rst diff --git a/astropy/coordinates/__init__.py b/astropy/coordinates/__init__.py index a68f6a7bdb6b..f51cea17792d 100644 --- a/astropy/coordinates/__init__.py +++ b/astropy/coordinates/__init__.py @@ -16,6 +16,7 @@ from .earth import * from .errors import * from .funcs import * +from .geodetic import * from .matching import * from .name_resolve import * from .representation import * diff --git a/astropy/coordinates/earth.py b/astropy/coordinates/earth.py index f7f1eba81a40..261932297ef5 100644 --- a/astropy/coordinates/earth.py +++ b/astropy/coordinates/earth.py @@ -8,39 +8,29 @@ import urllib.request from warnings import warn -import erfa import numpy as np from astropy import constants as consts from astropy import units as u from astropy.units.quantity import QuantityInfoBase from astropy.utils import data -from astropy.utils.decorators import format_doc from astropy.utils.exceptions import AstropyUserWarning from .angles import Angle, Latitude, Longitude from .errors import UnknownSiteException from .matrix_utilities import matrix_transpose from .representation import ( - BaseRepresentation, CartesianDifferential, CartesianRepresentation, ) +from .geodetic import ELLIPSOIDS __all__ = [ "EarthLocation", - "BaseGeodeticRepresentation", - "WGS84GeodeticRepresentation", - "WGS72GeodeticRepresentation", - "GRS80GeodeticRepresentation", ] GeodeticLocation = collections.namedtuple("GeodeticLocation", ["lon", "lat", "height"]) -ELLIPSOIDS = {} -"""Available ellipsoids (defined in erfam.h, with numbers exposed in erfa).""" -# Note: they get filled by the creation of the geodetic classes. - OMEGA_EARTH = (1.002_737_811_911_354_48 * u.cycle / u.day).to( 1 / u.s, u.dimensionless_angles() ) @@ -905,87 +895,3 @@ def _to_value(self, unit, equivalencies=[]): equivalencies = self._equivalencies new_array = self.unit.to(unit, array_view, equivalencies=equivalencies) return new_array.view(self.dtype).reshape(self.shape) - - -geodetic_base_doc = """{__doc__} - - Parameters - ---------- - lon, lat : angle-like - The longitude and latitude of the point(s), in angular units. The - latitude should be between -90 and 90 degrees, and the longitude will - be wrapped to an angle between 0 and 360 degrees. These can also be - instances of `~astropy.coordinates.Angle` and either - `~astropy.coordinates.Longitude` not `~astropy.coordinates.Latitude`, - depending on the parameter. - height : `~astropy.units.Quantity` ['length'] - The height to the point(s). - copy : bool, optional - If `True` (default), arrays will be copied. If `False`, arrays will - be references, though possibly broadcast to ensure matching shapes. - -""" - - -@format_doc(geodetic_base_doc) -class BaseGeodeticRepresentation(BaseRepresentation): - """Base geodetic representation.""" - - attr_classes = {"lon": Longitude, "lat": Latitude, "height": u.Quantity} - - def __init_subclass__(cls, **kwargs): - super().__init_subclass__(**kwargs) - if "_ellipsoid" in cls.__dict__: - ELLIPSOIDS[cls._ellipsoid] = cls - - def __init__(self, lon, lat=None, height=None, copy=True): - if height is None and not isinstance(lon, self.__class__): - height = 0 << u.m - - super().__init__(lon, lat, height, copy=copy) - if not self.height.unit.is_equivalent(u.m): - raise u.UnitTypeError( - f"{self.__class__.__name__} requires height with units of length." - ) - - def to_cartesian(self): - """ - Converts WGS84 geodetic coordinates to 3D rectangular (geocentric) - cartesian coordinates. - """ - xyz = erfa.gd2gc( - getattr(erfa, self._ellipsoid), self.lon, self.lat, self.height - ) - return CartesianRepresentation(xyz, xyz_axis=-1, copy=False) - - @classmethod - def from_cartesian(cls, cart): - """ - Converts 3D rectangular cartesian coordinates (assumed geocentric) to - WGS84 geodetic coordinates. - """ - lon, lat, height = erfa.gc2gd( - getattr(erfa, cls._ellipsoid), cart.get_xyz(xyz_axis=-1) - ) - return cls(lon, lat, height, copy=False) - - -@format_doc(geodetic_base_doc) -class WGS84GeodeticRepresentation(BaseGeodeticRepresentation): - """Representation of points in WGS84 3D geodetic coordinates.""" - - _ellipsoid = "WGS84" - - -@format_doc(geodetic_base_doc) -class WGS72GeodeticRepresentation(BaseGeodeticRepresentation): - """Representation of points in WGS72 3D geodetic coordinates.""" - - _ellipsoid = "WGS72" - - -@format_doc(geodetic_base_doc) -class GRS80GeodeticRepresentation(BaseGeodeticRepresentation): - """Representation of points in GRS80 3D geodetic coordinates.""" - - _ellipsoid = "GRS80" diff --git a/astropy/coordinates/tests/test_earth.py b/astropy/coordinates/tests/test_earth.py index e21c390e72ff..22d2baf44b38 100644 --- a/astropy/coordinates/tests/test_earth.py +++ b/astropy/coordinates/tests/test_earth.py @@ -10,8 +10,9 @@ from astropy import constants from astropy import units as u from astropy.coordinates.angles import Latitude, Longitude -from astropy.coordinates.earth import ELLIPSOIDS, EarthLocation +from astropy.coordinates.earth import EarthLocation from astropy.coordinates.name_resolve import NameResolveError +from astropy.coordinates.geodetic import ELLIPSOIDS from astropy.time import Time from astropy.units import allclose as quantity_allclose from astropy.units.tests.test_quantity_erfa_ufuncs import vvd diff --git a/astropy/coordinates/tests/test_geodetic_representations.py b/astropy/coordinates/tests/test_geodetic_representations.py index dd3012cfe45e..0a5d4ccee13d 100644 --- a/astropy/coordinates/tests/test_geodetic_representations.py +++ b/astropy/coordinates/tests/test_geodetic_representations.py @@ -2,49 +2,68 @@ """Test geodetic representations""" import pytest -from numpy.testing import assert_array_equal from astropy import units as u -from astropy.coordinates.earth import ( +from astropy.coordinates.representation import ( + CartesianRepresentation, + REPRESENTATION_CLASSES, + BaseGeodeticRepresentation, GRS80GeodeticRepresentation, WGS72GeodeticRepresentation, WGS84GeodeticRepresentation, ) -from astropy.coordinates.representation import CartesianRepresentation -from astropy.units import allclose as quantity_allclose +from astropy.coordinates.representation.geodetic import ELLIPSOIDS +from astropy.tests.helper import assert_quantity_allclose +from astropy.units.tests.test_quantity_erfa_ufuncs import vvd + +# Preserve the original REPRESENTATION_CLASSES dict so that importing +# the test file doesn't add a persistent test subclass +from astropy.coordinates.tests.test_representation import ( # noqa: F401 + setup_function, + teardown_function, +) + +class CustomGeodetic(BaseGeodeticRepresentation): + _flattening = 0.01832 + _equatorial_radius = 4000000.0 * u.m -def test_cartesian_wgs84geodetic_roundtrip(): + +@pytest.mark.parametrize( + "geodeticrepresentation", [CustomGeodetic, WGS84GeodeticRepresentation] +) +def test_cartesian_geodetic_roundtrip(geodeticrepresentation): # Test array-valued input in the process. - s1 = CartesianRepresentation( + initial_cartesian = CartesianRepresentation( x=[1, 3000.0] * u.km, y=[7000.0, 4.0] * u.km, z=[5.0, 6000.0] * u.km ) - s2 = WGS84GeodeticRepresentation.from_representation(s1) + transformed = geodeticrepresentation.from_representation(initial_cartesian) - s3 = CartesianRepresentation.from_representation(s2) + roundtripped = CartesianRepresentation.from_representation(transformed) - s4 = WGS84GeodeticRepresentation.from_representation(s3) + assert_quantity_allclose(initial_cartesian.x, roundtripped.x) + assert_quantity_allclose(initial_cartesian.y, roundtripped.y) + assert_quantity_allclose(initial_cartesian.z, roundtripped.z) - assert quantity_allclose(s1.x, s3.x) - assert quantity_allclose(s1.y, s3.y) - assert quantity_allclose(s1.z, s3.z) - assert quantity_allclose(s2.lon, s4.lon) - assert quantity_allclose(s2.lat, s4.lat) - assert quantity_allclose(s2.height, s4.height) - - # Test initializer just for the sake of it. - s5 = WGS84GeodeticRepresentation(s2.lon, s2.lat, s2.height) +@pytest.mark.parametrize( + "geodeticrepresentation", [CustomGeodetic, WGS84GeodeticRepresentation] +) +def test_geodetic_cartesian_roundtrip(geodeticrepresentation): + initial_geodetic = geodeticrepresentation( + lon=[0.8, 1.3] * u.radian, + lat=[0.3, 0.98] * u.radian, + height=[100.0, 367.0] * u.m, + ) - assert_array_equal(s2.lon, s5.lon) - assert_array_equal(s2.lat, s5.lat) - assert_array_equal(s2.height, s5.height) + transformed = CartesianRepresentation.from_representation(initial_geodetic) + roundtripped = geodeticrepresentation.from_representation(transformed) -def vvd(val, valok, dval, func, test, status): - """Mimic routine of erfa/src/t_erfa_c.c (to help copy & paste)""" - assert quantity_allclose(val, valok * val.unit, atol=dval * val.unit) + assert_quantity_allclose(initial_geodetic.lon, roundtripped.lon) + assert_quantity_allclose(initial_geodetic.lat, roundtripped.lat) + assert_quantity_allclose(initial_geodetic.height, roundtripped.height) def test_geocentric_to_geodetic(): @@ -118,3 +137,26 @@ def test_non_angle_error(): def test_non_length_error(): with pytest.raises(u.UnitTypeError, match="units of length"): WGS84GeodeticRepresentation(10 * u.deg, 20 * u.deg, 30) + + +def test_geodetic_subclass_bad_ellipsoid(): + # Test incomplete initialization. + + msg = "module 'erfa' has no attribute 'foo'" + with pytest.raises(AttributeError, match=msg): + + class InvalidCustomGeodeticEllipsoid(BaseGeodeticRepresentation): + _ellipsoid = "foo" + + assert "foo" not in ELLIPSOIDS + assert "customgeodeticellipsoiderror" not in REPRESENTATION_CLASSES + + +def test_geodetic_subclass_missing_equatorial_radius(): + msg = "MissingCustomGeodeticAttribute requires '_ellipsoid' or '_equatorial_radius' and '_flattening'." + with pytest.raises(AttributeError, match=msg): + + class MissingCustomGeodeticAttribute(BaseGeodeticRepresentation): + _flattening = 0.075 * u.dimensionless_unscaled + + assert "customgeodeticerror" not in REPRESENTATION_CLASSES diff --git a/docs/changes/coordinates/14763.feature.rst b/docs/changes/coordinates/14763.feature.rst new file mode 100644 index 000000000000..d4733abb2abc --- /dev/null +++ b/docs/changes/coordinates/14763.feature.rst @@ -0,0 +1,4 @@ +Support has been added to create geodetic representations not just for existing ellipsoids +from ERFA, but also with explicitly provided values, by defining a subclass of +``BaseGeodeticRepresentation`` with the equatorial radius and flattening assigned to +``_equatorial_radius`` and ``_flattening`` attributes. diff --git a/docs/coordinates/representations.rst b/docs/coordinates/representations.rst index 8d66fab1ede8..4c176d12470c 100644 --- a/docs/coordinates/representations.rst +++ b/docs/coordinates/representations.rst @@ -30,6 +30,14 @@ The built-in representation classes are: * `~astropy.coordinates.CylindricalRepresentation`: cylindrical polar coordinates, represented by a cylindrical radius (``rho``), azimuthal angle (``phi``), and height (``z``). +* `~astropy.coordinates.BaseGeodeticRepresentation`: + coordinates on a surface of a spheroid (an ellipsoid with equal equatorial radii), + represented by a longitude (``lon``) a geodetical latitude (``lat``) and a height + (``height``) above the surface. The geodetical latitude is defined by the angle + between the vertical to the surface at a specific point of the spheroid and its + projection onto the equatorial plane. + The latitude is a value ranging from -90 to 90 degrees, the longitude from 0 to 360 + degrees. .. Note:: For information about using and changing the representation of @@ -685,3 +693,21 @@ In pseudo-code, this means that a class will look like:: class MyDifferential(BaseDifferential): base_representation = MyRepresentation + +.. _astropy-coordinates-create-geodetic: + +Creating Your Own Geodetic Representation +----------------------------------------- + +If you would like to use geodetic coordinates on planetary bodies other than the Earth, +you can define a new class that inherits from `~astropy.coordinates.BaseGeodeticRepresentation`. +The equatorial radius and flattening must be both assigned via the attributes +`_equatorial_radius` and `_flattening`. + +For example the spheroid describing Mars as in the +`1979 IAU standard `_ could be defined like:: + + class IAUMARS1979GeodeticRepresentation(BaseGeodeticRepresentation): + + _equatorial_radius = 3393400.0 * u.m + _flattening = 0.518650 * u.percent diff --git a/docs/whatsnew/6.0.rst b/docs/whatsnew/6.0.rst index 055b5eb86aa0..bf5407a6810a 100644 --- a/docs/whatsnew/6.0.rst +++ b/docs/whatsnew/6.0.rst @@ -12,7 +12,7 @@ the 5.3 release. In particular, this release includes: -* nothing yet +* :ref:`whatsnew-6.0-geodetic-representation-geometry` In addition to these major changes, Astropy v6.0 includes a large number of smaller improvements and bug fixes, which are described in the :ref:`changelog`. @@ -22,6 +22,32 @@ By the numbers: * X pull requests have been merged since v5.3 * X distinct people have contributed code +.. _whatsnew-6.0-geodetic-representation-geometry: + +Define Geodetic Representations via their geometric parameters +============================================================== + +The user may now define custom spheroidal models for the Earth or other planetary +bodies by subclassing `~astropy.coordinates.BaseGeodeticRepresentation` and defining +``_equatorial_radius`` and ``_flattening`` attributes:: + + + >>> from astropy.coordinates import BaseGeodeticRepresentation, WGS84GeodeticRepresentation + >>> from astropy import units as u + >>> class IAU1976EarthGeodeticRepresentation(BaseGeodeticRepresentation): + ... _equatorial_radius = 6378140 * u.m + ... _flattening = 0.3352805 * u.percent + >>> representation = IAU1976EarthGeodeticRepresentation(lon=45.8366*u.deg, + ... lat=56.1499*u.deg, height=367*u.m) + >>> representation.to_cartesian() # doctest: +FLOAT_CMP + + >>> representation.represent_as(WGS84GeodeticRepresentation) # doctest: +FLOAT_CMP + + +See :ref:`astropy-coordinates-create-geodetic` for more details. + Full change log =============== From c06d3b841a71de3836794da47555fbea2062932f Mon Sep 17 00:00:00 2001 From: Marten van Kerkwijk Date: Wed, 10 May 2023 14:28:13 +0100 Subject: [PATCH 3/5] Ensure we can use floats for flattening, since they are dimensionless --- astropy/units/quantity_helper/erfa.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/astropy/units/quantity_helper/erfa.py b/astropy/units/quantity_helper/erfa.py index b8cc02ca47ed..ae5b0ed10a54 100644 --- a/astropy/units/quantity_helper/erfa.py +++ b/astropy/units/quantity_helper/erfa.py @@ -104,7 +104,7 @@ def helper_gc2gde(f, unit_r, unit_flat, unit_xyz): return [ get_converter(unit_r, m), - get_converter(unit_flat, dimensionless_unscaled), + get_converter(_d(unit_flat), dimensionless_unscaled), get_converter(unit_xyz, m), ], ( radian, @@ -138,7 +138,7 @@ def helper_gd2gce(f, unit_r, unit_flat, unit_long, unit_lat, unit_h): return [ get_converter(unit_r, m), - get_converter(unit_flat, dimensionless_unscaled), + get_converter(_d(unit_flat), dimensionless_unscaled), get_converter(unit_long, radian), get_converter(unit_lat, radian), get_converter(unit_h, m), From 9bbf613385f963896aae6fb07b99acc9c80ed33c Mon Sep 17 00:00:00 2001 From: Marten van Kerkwijk Date: Wed, 10 May 2023 18:20:42 +0100 Subject: [PATCH 4/5] Move geodetic inside new representation module --- astropy/coordinates/__init__.py | 1 - astropy/coordinates/earth.py | 2 +- .../coordinates/representation/__init__.py | 10 ++ .../coordinates/representation/geodetic.py | 121 ++++++++++++++++++ astropy/coordinates/tests/test_earth.py | 2 +- 5 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 astropy/coordinates/representation/geodetic.py diff --git a/astropy/coordinates/__init__.py b/astropy/coordinates/__init__.py index f51cea17792d..a68f6a7bdb6b 100644 --- a/astropy/coordinates/__init__.py +++ b/astropy/coordinates/__init__.py @@ -16,7 +16,6 @@ from .earth import * from .errors import * from .funcs import * -from .geodetic import * from .matching import * from .name_resolve import * from .representation import * diff --git a/astropy/coordinates/earth.py b/astropy/coordinates/earth.py index 261932297ef5..88c5f7672057 100644 --- a/astropy/coordinates/earth.py +++ b/astropy/coordinates/earth.py @@ -23,7 +23,7 @@ CartesianDifferential, CartesianRepresentation, ) -from .geodetic import ELLIPSOIDS +from .representation.geodetic import ELLIPSOIDS __all__ = [ "EarthLocation", diff --git a/astropy/coordinates/representation/__init__.py b/astropy/coordinates/representation/__init__.py index 13356d1f78fd..f91d47e3ae56 100644 --- a/astropy/coordinates/representation/__init__.py +++ b/astropy/coordinates/representation/__init__.py @@ -6,6 +6,12 @@ from .base import BaseRepresentationOrDifferential, BaseRepresentation, BaseDifferential from .cartesian import CartesianRepresentation, CartesianDifferential from .cylindrical import CylindricalRepresentation, CylindricalDifferential +from .geodetic import ( + BaseGeodeticRepresentation, + WGS84GeodeticRepresentation, + WGS72GeodeticRepresentation, + GRS80GeodeticRepresentation, +) from .spherical import ( SphericalRepresentation, UnitSphericalRepresentation, @@ -49,4 +55,8 @@ "RadialDifferential", "CylindricalDifferential", "PhysicsSphericalDifferential", + "BaseGeodeticRepresentation", + "WGS84GeodeticRepresentation", + "WGS72GeodeticRepresentation", + "GRS80GeodeticRepresentation", ] diff --git a/astropy/coordinates/representation/geodetic.py b/astropy/coordinates/representation/geodetic.py new file mode 100644 index 000000000000..6663199a1fd2 --- /dev/null +++ b/astropy/coordinates/representation/geodetic.py @@ -0,0 +1,121 @@ +# Licensed under a 3-clause BSD style license - see LICENSE.rst + +import erfa + +from astropy import units as u +from astropy.coordinates.angles import Latitude, Longitude +from astropy.utils.decorators import format_doc + +from .base import BaseRepresentation +from .cartesian import CartesianRepresentation + + +ELLIPSOIDS = {} +"""Available ellipsoids (defined in erfam.h, with numbers exposed in erfa).""" +# Note: they get filled by the creation of the geodetic classes. + + +geodetic_base_doc = """{__doc__} + + Parameters + ---------- + lon, lat : angle-like + The longitude and latitude of the point(s), in angular units. The + latitude should be between -90 and 90 degrees, and the longitude will + be wrapped to an angle between 0 and 360 degrees. These can also be + instances of `~astropy.coordinates.Angle` and either + `~astropy.coordinates.Longitude` not `~astropy.coordinates.Latitude`, + depending on the parameter. + + height : `~astropy.units.Quantity` ['length'] + The height to the point(s). + + copy : bool, optional + If `True` (default), arrays will be copied. If `False`, arrays will + be references, though possibly broadcast to ensure matching shapes. +""" + + +@format_doc(geodetic_base_doc) +class BaseGeodeticRepresentation(BaseRepresentation): + """ + Base class for geodetic representations. + + Subclasses need to set attributes ``_equatorial_radius`` and ``_flattening`` + to quantities holding correct values (with units of length and dimensionless, + respectively), or alternatively an ``_ellipsoid`` attribute to the relevant ERFA + index (as passed in to `erfa.eform`). + """ + + attr_classes = {"lon": Longitude, "lat": Latitude, "height": u.Quantity} + + def __init_subclass__(cls, **kwargs): + if "_ellipsoid" in cls.__dict__: + equatorial_radius, flattening = erfa.eform(getattr(erfa, cls._ellipsoid)) + cls._equatorial_radius = equatorial_radius * u.m + cls._flattening = flattening * u.dimensionless_unscaled + ELLIPSOIDS[cls._ellipsoid] = cls + elif ( + "_equatorial_radius" not in cls.__dict__ + or "_flattening" not in cls.__dict__ + ): + raise AttributeError( + f"{cls.__name__} requires '_ellipsoid' or '_equatorial_radius' and '_flattening'." + ) + super().__init_subclass__(**kwargs) + + def __init__(self, lon, lat=None, height=None, copy=True): + if height is None and not isinstance(lon, self.__class__): + height = 0 << u.m + + super().__init__(lon, lat, height, copy=copy) + if not self.height.unit.is_equivalent(u.m): + raise u.UnitTypeError( + f"{self.__class__.__name__} requires height with units of length." + ) + + def to_cartesian(self): + """ + Converts geodetic coordinates to 3D rectangular (geocentric) + cartesian coordinates. + """ + xyz = erfa.gd2gce( + self._equatorial_radius, + self._flattening, + self.lon, + self.lat, + self.height, + ) + return CartesianRepresentation(xyz, xyz_axis=-1, copy=False) + + @classmethod + def from_cartesian(cls, cart): + """ + Converts 3D rectangular cartesian coordinates (assumed geocentric) to + geodetic coordinates. + """ + lon, lat, height = erfa.gc2gde( + cls._equatorial_radius, cls._flattening, cart.get_xyz(xyz_axis=-1) + ) + return cls(lon, lat, height, copy=False) + + +@format_doc(geodetic_base_doc) +class WGS84GeodeticRepresentation(BaseGeodeticRepresentation): + """Representation of points in WGS84 3D geodetic coordinates.""" + + _ellipsoid = "WGS84" + + +@format_doc(geodetic_base_doc) +class WGS72GeodeticRepresentation(BaseGeodeticRepresentation): + """Representation of points in WGS72 3D geodetic coordinates.""" + + _ellipsoid = "WGS72" + + +@format_doc(geodetic_base_doc) +class GRS80GeodeticRepresentation(BaseGeodeticRepresentation): + """Representation of points in GRS80 3D geodetic coordinates.""" + + _ellipsoid = "GRS80" diff --git a/astropy/coordinates/tests/test_earth.py b/astropy/coordinates/tests/test_earth.py index 22d2baf44b38..0c8c3a140bfb 100644 --- a/astropy/coordinates/tests/test_earth.py +++ b/astropy/coordinates/tests/test_earth.py @@ -12,7 +12,7 @@ from astropy.coordinates.angles import Latitude, Longitude from astropy.coordinates.earth import EarthLocation from astropy.coordinates.name_resolve import NameResolveError -from astropy.coordinates.geodetic import ELLIPSOIDS +from astropy.coordinates.representation.geodetic import ELLIPSOIDS from astropy.time import Time from astropy.units import allclose as quantity_allclose from astropy.units.tests.test_quantity_erfa_ufuncs import vvd From e6c0a86d6e150f439ad3fbbc97c3bf1f5b130298 Mon Sep 17 00:00:00 2001 From: Chiara Marmo Date: Wed, 10 May 2023 09:57:11 -1000 Subject: [PATCH 5/5] Improve documentation. --- docs/coordinates/representations.rst | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/docs/coordinates/representations.rst b/docs/coordinates/representations.rst index 4c176d12470c..dc496fade830 100644 --- a/docs/coordinates/representations.rst +++ b/docs/coordinates/representations.rst @@ -30,14 +30,24 @@ The built-in representation classes are: * `~astropy.coordinates.CylindricalRepresentation`: cylindrical polar coordinates, represented by a cylindrical radius (``rho``), azimuthal angle (``phi``), and height (``z``). -* `~astropy.coordinates.BaseGeodeticRepresentation`: - coordinates on a surface of a spheroid (an ellipsoid with equal equatorial radii), - represented by a longitude (``lon``) a geodetical latitude (``lat``) and a height - (``height``) above the surface. The geodetical latitude is defined by the angle - between the vertical to the surface at a specific point of the spheroid and its - projection onto the equatorial plane. - The latitude is a value ranging from -90 to 90 degrees, the longitude from 0 to 360 - degrees. + + +Astropy also offers a `~astropy.coordinates.BaseGeodeticRepresentation` useful to +create specific representations on spheroidal bodies. +This is used internally for the standard Earth ellipsoids used in +`~astropy.coordinates.EarthLocation` +(`~astropy.coordinates.WGS84GeodeticRepresentation`, +`~astropy.coordinates.WGS72GeodeticRepresentation`, and +`~astropy.coordinates.GRS80GeodeticRepresentation`), but +can also be customized; see :ref:`astropy-coordinates-create-geodetic`. +All these are coordinates on a surface of a spheroid (an ellipsoid with equal +equatorial radii), represented by a longitude (``lon``) a geodetical latitude (``lat``) +and a height (``height``) above the surface. +The geodetical latitude is defined by the angle +between the vertical to the surface at a specific point of the spheroid and its +projection onto the equatorial plane. +The latitude is a value ranging from -90 to 90 degrees, the longitude from 0 to 360 +degrees. .. Note:: For information about using and changing the representation of