Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BaseBodycentricRepresentation #14851

Merged
merged 3 commits into from Jun 23, 2023
Merged

Conversation

cmarmo
Copy link
Member

@cmarmo cmarmo commented May 19, 2023

Description

This pull request adds the possibility to create geodetic representations described by [planetocentric latitudes]
(https://en.wikipedia.org/wiki/Latitude#Geodetic_and_geocentric_latitudes), west positive longitudes, longitudes spanning from 0 to 360 degrees.
The default behavior is backward compatible.
Suggested in #11170 (comment).

Towards #11170.

This work is funded by the Europlanet 2024 Research Infrastructure (RI) Grant.

@github-actions
Copy link

github-actions bot commented May 19, 2023

Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.

  • Do the proposed changes actually accomplish desired goals?
  • Do the proposed changes follow the Astropy coding guidelines?
  • Are tests added/updated as required? If so, do they follow the Astropy testing guidelines?
  • Are docs added/updated as required? If so, do they follow the Astropy documentation guidelines?
  • Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see "When to rebase and squash commits".
  • Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the "Extra CI" label. Codestyle issues can be fixed by the bot.
  • Is a change log needed? If yes, did the change log check pass? If no, add the "no-changelog-entry-needed" label. If this is a manual backport, use the "skip-changelog-checks" label unless special changelog handling is necessary.
  • Is this a big PR that makes a "What's new?" entry worthwhile and if so, is (1) a "what's new" entry included in this PR and (2) the "whatsnew-needed" label applied?
  • Is a milestone set? Milestone must be set but we cannot check for it on Actions; do not let the green checkmark fool you.
  • At the time of adding the milestone, if the milestone set requires a backport to release branch(es), apply the appropriate "backport-X.Y.x" label(s) before merge.

@pllim pllim added this to the v6.0 milestone May 19, 2023
@pllim pllim requested a review from mhvk May 19, 2023 22:02
@cmarmo
Copy link
Member Author

cmarmo commented May 19, 2023

It seems like I'm still confused about units conversions... :(

@cmarmo cmarmo force-pushed the geodetic-repr-options branch 3 times, most recently from f1a8a58 to 25fb355 Compare May 25, 2023 20:11
@cmarmo
Copy link
Member Author

cmarmo commented May 25, 2023

For the sake of completeness, this pull request makes the BaseGeodeticRepresentation closer to the Planetary Data System class Geodetic_Model.

Copy link
Contributor

@mhvk mhvk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cmarmo - Good to have these options, but I do think at least some of them should be subclasses rather than options on the base class. For _ellipsoid', I ended up agreeing it was not necessary, since having it on the baseclass gives a little overhead only at class creation time, and in the end it is really the same thing as far as the actual calculations are concerned. But here the calculations for conversion to/from cartesian are effectively separated, making the code more complicated and giving overhead for every calculation. Generally, I think each different interpretation of the coordinates should have its own (base) class -- just like PhysicsSpherical is a different class from Spherical. I would say the rule should be that the moment from_cartesian and to_cartesian depend on any class attribute, it would be better to have a different class.
So, I think we definitely should have a separate base class for the planetocentric latitudes (BasePlanetoCentric?). I think we also will be better off with a separate class for West longitudes, though given the rarity of its use, perhaps this is best implemented via a mixin class that overrides from_cartesian and to_cartesian changing the sign of the longitude appropriately.
Only for _wrap_angle, I think it is fine to leave that on the base class -- the calculations do not depend on it, one can see it as similar to changing the class of the attributes.

astropy/coordinates/representation/geodetic.py Outdated Show resolved Hide resolved
@@ -45,9 +56,16 @@ class BaseGeodeticRepresentation(BaseRepresentation):
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`).
Longitudes are east positive and span from -180 to 180 degrees by default.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have separate classes; see main comment.

astropy/coordinates/representation/geodetic.py Outdated Show resolved Hide resolved
astropy/coordinates/representation/geodetic.py Outdated Show resolved Hide resolved
astropy/coordinates/tests/test_geodetic_representations.py Outdated Show resolved Hide resolved
astropy/coordinates/tests/test_geodetic_representations.py Outdated Show resolved Hide resolved
astropy/coordinates/tests/test_geodetic_representations.py Outdated Show resolved Hide resolved
astropy/coordinates/tests/test_representation.py Outdated Show resolved Hide resolved
Copy link
Contributor

@mhvk mhvk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the change to a different base class. That does feel much better!

But, seeing this again, I wonder about the approach for wrap_angle. Really, the problem is that we have a Longitude class that has a default of 360 degrees, while we would like 180 degrees. Would it make sense to just define

class Longitude180(Longitude):
    _default_wrap_angle = Angle(180 * u.deg)

and then use that? Then anyone who wants to have a different default, can just choose to use a different Longitude class. And we don't have to deal with the new _wrap_angle attribute.

p.s. Need to think a bit about the mixin still... And I haven't looked in detail over the tests.

astropy/coordinates/representation/geodetic.py Outdated Show resolved Hide resolved
astropy/coordinates/representation/geodetic.py Outdated Show resolved Hide resolved
astropy/coordinates/representation/geodetic.py Outdated Show resolved Hide resolved
@@ -62,12 +98,21 @@ def __init_subclass__(cls, **kwargs):
raise AttributeError(
f"{cls.__name__} requires '_ellipsoid' or '_equatorial_radius' and '_flattening'."
)
if not hasattr(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small detail, but could do

if not u.Quantity(cls._wrap_angle).unit.is_equivalent(u.deg):

(but see my larger comment about _wrap_angle)

astropy/coordinates/representation/geodetic.py Outdated Show resolved Hide resolved
astropy/coordinates/representation/geodetic.py Outdated Show resolved Hide resolved
astropy/coordinates/representation/geodetic.py Outdated Show resolved Hide resolved
astropy/coordinates/representation/geodetic.py Outdated Show resolved Hide resolved
astropy/coordinates/representation/geodetic.py Outdated Show resolved Hide resolved
astropy/coordinates/tests/test_representation.py Outdated Show resolved Hide resolved
@cmarmo
Copy link
Member Author

cmarmo commented Jun 15, 2023

Thanks for your review @mhvk !
I have addressed the "obvious" fixes.
I have to better understand units and wrap angles for the rest of the comments.... I will be back ASAP.
Thanks again for your precious comments.

@cmarmo cmarmo changed the title Add planetary useful options to BaseGeodeticRepresentation Add BaseBodycentricRepresentation Jun 16, 2023
@cmarmo
Copy link
Member Author

cmarmo commented Jun 16, 2023

The tests are failing because the class WGS84BodycentricRepresentation, which I have defined in order to test the ability of using _ellipsoid with a BaseBodycentricRepresentation, persists and it is used by earth.py.
My understanding was that the teardown_function would take care of "preserving the original REPRESENTATION_CLASSES", so this failure should not happen.
Anyway it also seems like the selection of a representation only based on the ellipsoid in EarthLocation may lead to mistakes now that BaseBodycentricRepresentation also uses an ellipsoid.

On the long term, I see the importance of changing EarthLocation to a representation, but how should I proceed on the short term? Should I remove the possibility of defining a bodycentric representation using an ellipsoid?

Any suggestion? Thanks a lot!

@mhvk
Copy link
Contributor

mhvk commented Jun 17, 2023

Simplest right now would seem to not allow ellipsoid for Bodycentric. As is, ellipsoid is only defined for Earth, and it is only used in EarthLocation, so I think this is fine.

@cmarmo
Copy link
Member Author

cmarmo commented Jun 20, 2023

Hi @mhvk . I believe I have removed all the unnecessary conversions to degrees, following your comments.
I have updated the title of the pull request and the changelog to reflect what is done in the code: ie the introduction of a new BaseBodycentricRepresentation.
However, I have kept the attribute _wrap_angle and defaulted it at 360 for BaseBodycentricRepresentation as this is the default recommended by Cartographic Coordinates & Rotational Elements Working Group (see https://rdcu.be/b32WL page 26)....
I'm not completely convinced in having it removed ...

But if we stick with using _wrap_angle, we need to think a bit about how we can potentially make it faster - right now, the wrapping is done twice (I think).

I guess this is also because I don't understand this previous comment

@mhvk
Copy link
Contributor

mhvk commented Jun 22, 2023

@cmarmo - thanks for the changes. I'm still not sold on the _wrap_angle attribute - essentially you are making how data is displayed part of the class, while I feel what the class should cares about is only how data is used. And for the use of a longitude, it really doesn't matter what the wrap angle is (just like its units don't matter). I still think the way forward is to define a new Longitude180 class and use that where relevant.

However, I also think this discussion is actually distracting from the main point of this PR, which is to introduce a BaseBodycentricRepresentation class. How about splitting this up in pieces? I.e., here remove all stuff with wrap_angle and just get the basic class working.

Indeed, while I think the WestLongitude mixin class is the right approach, I'd even tend to put that in a separate PR as well, where it could include an update to the docs that make clearer what the use case is.

@cmarmo
Copy link
Member Author

cmarmo commented Jun 22, 2023

However, I also think this discussion is actually distracting from the main point of this PR, which is to introduce a BaseBodycentricRepresentation class. How about splitting this up in pieces? I.e., here remove all stuff with wrap_angle and just get the basic class working.

Indeed, while I think the WestLongitude mixin class is the right approach, I'd even tend to put that in a separate PR as well, where it could include an update to the docs that make clearer what the use case is.

I guess I'm loosing focus indeed... I need the Bodycentric representation for the frame definition, while west/east and 180/360 are not needed.... and I'm running out of time... ⏲️
Let's rescope the PR.

@mhvk
Copy link
Contributor

mhvk commented Jun 22, 2023

OK, thanks, ping me when I should look again. And apologies for not realizing this earlier!

@cmarmo
Copy link
Member Author

cmarmo commented Jun 22, 2023

And apologies for not realizing this earlier!

No problem! I'm treasuring all the comments for the follow-up! :)

@cmarmo
Copy link
Member Author

cmarmo commented Jun 22, 2023

@mhvk thanks for your patience!
Is the PR acceptable in its current status?
Should I add something in the what's new section?

Copy link
Contributor

@mhvk mhvk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! There are only nitpicks and suggestions for the docstrings left. But also: do indeed add to the whatsnew entry you already created for other geodetic representations.

lon, lat, height = erfa.gc2gde(
cls._equatorial_radius, cls._flattening, cart.get_xyz(xyz_axis=-1)
)
return cls(lon, lat, height, copy=False)
return cls(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick, but could you get this back to the default format? Be sure to delete the final , after copy=False, otherwise black will break it over several lines again.

@@ -45,6 +46,7 @@ class BaseGeodeticRepresentation(BaseRepresentation):
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`).
Longitudes are east positive and span from 0 to 360 degrees by default.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: might as well leave this out for now.

Instead, I think here is the place to mention how lon, lat, and height are defined relative to the ellipsoid.

Alternatively, you could clarify the definition in geodetic_base_doc above (and then not use it for bodycentric)

Subclasses need to set attributes ``_equatorial_radius`` and ``_flattening``
to quantities holding correct values (with units of length and dimensionless,
respectively).
Longitudes are east positive and span from 0 to 360 degrees by default.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I'd remove this for now and instead describe how the parameters are used (and how it is different from geodetic).

Alternatively, do not use geodetic_base_doc, but instead write a complete docstring here that also describes how lat is defined.

lon,
lat,
height,
copy=False,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, could you bring this on a single line?

@@ -0,0 +1,2 @@
Add ``BaseBodycentricRepresentation``, a new spheroidal representation for bodycentric
latitudes and longitudes spanning from 0 to 360 degrees.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove the "spanning from 0 to 360 degrees" here too.

`~astropy.coordinates.BaseBodycentricRepresentation` is the coordinate representation
recommended by the Cartographic Coordinates & Rotational Elements Working Group
(see for example its `2019 report <https://rdcu.be/b32WL>`_): the bodicentric latitude
and longitude are spherical latitude and longitude originated at the barycenter of the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

originated at -> relative to

docs/coordinates/representations.rst Show resolved Hide resolved
@@ -48,6 +50,13 @@ 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.coordinates.BaseBodycentricRepresentation` is the coordinate representation
recommended by the Cartographic Coordinates & Rotational Elements Working Group
(see for example its `2019 report <https://rdcu.be/b32WL>`_): the bodicentric latitude
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bodicentric -> bodycentric

docs/coordinates/representations.rst Outdated Show resolved Hide resolved
Copy link
Member

@eerovaher eerovaher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm letting @mhvk do the main review, but I do have a few comments.

Comment on lines 148 to 150
x_spheroid = self._equatorial_radius * coslat * coslon
y_spheroid = self._equatorial_radius * coslat * sinlon
z_spheroid = self._equatorial_radius * (1 - self._flattening) * sinlat
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These variables are unused.

Suggested change
x_spheroid = self._equatorial_radius * coslat * coslon
y_spheroid = self._equatorial_radius * coslat * sinlon
z_spheroid = self._equatorial_radius * (1 - self._flattening) * sinlat

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed! They are no longer needed as the calculation is done for r already.

z_spheroid = self._equatorial_radius * (1 - self._flattening) * sinlat
r = (
self._equatorial_radius
* np.sqrt(coslat**2 + ((1 - self._flattening) * sinlat) ** 2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use np.hypot() here:

Suggested change
* np.sqrt(coslat**2 + ((1 - self._flattening) * sinlat) ** 2)
* np.hypot(coslat, (1 - self._flattening) * sinlat)

Comment on lines 172 to 175
[
WGS84GeodeticRepresentation,
IAUMARS2000BodycentricRepresentation,
],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this fit on one line?

Suggested change
[
WGS84GeodeticRepresentation,
IAUMARS2000BodycentricRepresentation,
],
[WGS84GeodeticRepresentation, IAUMARS2000BodycentricRepresentation],

If it does then the other similar cases should be updated analogously.

Comment on lines 52 to 53
degrees, the height is the elevation above the surface of the spheroid (measured on the
perpendicular to the surface).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be replaced with the following?

Suggested change
degrees, the height is the elevation above the surface of the spheroid (measured on the
perpendicular to the surface).
degrees, the height is the elevation above the surface of the spheroid (measured
perpendicular to the surface).

Copy link
Contributor

@mhvk mhvk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cmarmo - only some nitpicks left beyond @eerovaher's comments. With those, I'll be happy to merge!


Subclasses need to set attributes ``_equatorial_radius`` and ``_flattening``
to quantities holding correct values (with units of length and dimensionless,
respectively). the bodicentric latitude and longitude are spherical latitude
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"the bodicentric" -> "The bodycentric"

Comment on lines 148 to 150
x_spheroid = self._equatorial_radius * coslat * coslon
y_spheroid = self._equatorial_radius * coslat * sinlon
z_spheroid = self._equatorial_radius * (1 - self._flattening) * sinlat
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed! They are no longer needed as the calculation is done for r already.

d = np.hypot(p, cart.z)
lat = np.arctan2(cart.z, p)
p_spheroid = cls._equatorial_radius * np.cos(lat)
z_spheroid = (cls._equatorial_radius * (1 - cls._flattening)) * np.sin(lat)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Total nitpick, but outer parentheses are not necessary.

create specific representations on spheroidal bodies.
This is used internally for the standard Earth ellipsoids used in
`~astropy.coordinates.BaseGeodeticRepresentation` is used internally for the standard
Earth ellipsoids used in
`~astropy.coordinates.EarthLocation`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe combine with last line while you are making the last changes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I'm not sure to understand this comment....

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meant to combine the two lines to give```
Earth ellipsoids used in ~astropy.coordinates.EarthLocation

recommended by the Cartographic Coordinates & Rotational Elements Working Group
(see for example its `2019 report <https://rdcu.be/b32WL>`_): the bodycentric latitude
and longitude are spherical latitude and longitude relative to the barycenter of the
body, the height is the distance from the spheroid surface on the line of sight.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"the height is the distance from the spheroid surface on the line of sight" -> "height is the distance from the surface (measured radially)."

@@ -706,11 +717,13 @@ In pseudo-code, this means that a class will look like::

.. _astropy-coordinates-create-geodetic:

Creating Your Own Geodetic Representation
-----------------------------------------
Creating Your Own Geodetic and Bodycentric Representation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe make plural: "Representations"

@mhvk
Copy link
Contributor

mhvk commented Jun 23, 2023

@cmarmo - separately from this PR, my sense would be to try to follow this up with adding some of the I/O and WCS system things you have been mentioning rather than worry about west longitude and wrap angles!

create specific representations on spheroidal bodies.
This is used internally for the standard Earth ellipsoids used in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't mean to have this part about the internal use to be removed!

@@ -110,7 +110,7 @@ class BaseBodycentricRepresentation(BaseRepresentation):

Subclasses need to set attributes ``_equatorial_radius`` and ``_flattening``
to quantities holding correct values (with units of length and dimensionless,
respectively). the bodicentric latitude and longitude are spherical latitude
respectively). the bodycentric latitude and longitude are spherical latitude
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still need to capitalize "The"

The latitude is a value ranging from -90 to 90 degrees, the longitude from 0 to 360
degrees.

`~astropy.coordinates.BaseGeodeticRepresentation` is used internally for the standard
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, sorry, you moved it here! This is better!

Copy link
Contributor

@mhvk mhvk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all looks great now. I think I'll just merge, the one uncapitalized "t" can be done at some other time!

@mhvk mhvk merged commit 9e3d836 into astropy:main Jun 23, 2023
29 checks passed
@mhvk
Copy link
Contributor

mhvk commented Jun 23, 2023

Thanks, @cmarmo!!

@cmarmo
Copy link
Member Author

cmarmo commented Jun 23, 2023

Thanks @mhvk for your patience!

@cmarmo cmarmo deleted the geodetic-repr-options branch June 23, 2023 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants