android-maps-utils icon indicating copy to clipboard operation
android-maps-utils copied to clipboard

Support altitude conversion between mean sea level (MSL) and WGS84

Open barbeau opened this issue 4 years ago • 16 comments

Is your feature request related to a problem? Please describe. Altitude is commonly represented using two different units: meters above mean sea level (MSL), and meters above the WGS84 ellipsoid. This is a good article describing the difference between the two representations: https://www.esri.com/news/arcuser/0703/geoid1of3.html

On Android, the Location API provides location altitude in WGS84: https://developer.android.com/reference/android/location/Location.html#getAltitude()

However, this is problematic if you want to display this altitude to end users, who are more commonly familiar with MSL altitude. In fact, if you display WGS84 altitude to users, many of them will report this as an error in altitude. Additionally, if you want to allow users to enter altitude into an app, users again want to enter this as MSL. Here are some examples of user preferences:

  • https://gitlab.com/mvglasow/satstat/-/issues/144
  • https://groups.google.com/forum/#!topic/gpstest_android/gbSqAhoI8Xg
  • https://github.com/barbeau/gpstest/issues/22

Therefore, a method to convert between MSL and WGS84 altitude is needed. However, this conversion isn't trivial because the difference between MSL and WGS84 altitudes changes based on where you are located on the surface of Earth.

Describe the solution you'd like Provide a utility method to convert between WGS84 and MSL altitude values.

We would need to bundle a lookup table to find the offset between WGS84 and MSL altitudes at a given latitude and longitude.

See https://github.com/barbeau/earth-gravitational-model for a project I started working on to port some of the GeoTools library to Android to do this operation. The plain text version of the lookup table (egm180.nor) is 700KB uncompressed outside the app. Additional tests would need to be done to determine the size of the lookup table when bundled with the library, and if additional compression would be helpful (e.g., storing in protocol buffer format instead of plain text).

GeoTools is licensed under L-GPL, so we couldn't adopt that code directly, but the GeoTools implementation of EarthGravitationalModel has this header:

/**
 * Transforms vertical coordinates using coefficients from the
 * <A HREF="http://earth-info.nima.mil/GandG/wgs84/gravitymod/wgs84_180/wgs84_180.html">Earth
 * Gravitational Model</A>.
 * <p>
 * <strong>Aknowledgement</strong><br>
 * This class is an adaption of Fortran code
 * <code><a href="http://earth-info.nga.mil/GandG/wgs84/gravitymod/wgs84_180/clenqt.for">clenqt.for</a></code>
 * from the <cite>National Geospatial-Intelligence Agency</cite> and available in public domain. The
 * <cite>normalized geopotential coefficients</cite> file bundled in this module is an adaptation of
 * <code><a href="http://earth-info.nima.mil/GandG/wgs84/gravitymod/wgs84_180/egm180.nor">egm180.nor</a></code>
 * file, with some spaces trimmed.

So the original Fortran implementation and the egm180.nor file are public domain. There may be other OSS implementations I'm not aware of too that could be used as building blocks.

Describe alternatives you've considered

  1. Use a web service like the Google Elevation API or EPSG.io. However, doing a network call for this operation seems excessive, especially if it's something you want to continuously calculate in real-time.
  2. Have the Android Location API provide MSL location directly (e.g., Location.getAltitudeMsl()). However, it's not clear if all GNSS receiver chipsets provide MSL altitude that could be passed through the Android framework and if AOSP would support this change, although in my experience this data is readily available from many handsets via NMEA (see below). This solution doesn't help if you want to do MSL<->WGS84 conversion from a source other than the Location API.
  3. Parse MSL altitude from NMEA data for each GNSS fix - The NMEA standard for GNSS receivers supports MSL altitude as part of $GPGGA sentences. Android allows GNSS chipset OEMs to provide NMEA sentences via the Android Location OnNmeaMessageListener API. However, there is no guarantee that a particular device will support MSL altitude via NMEA - it's not required by Android. And similar to above, this only helps with locations from the Location API.
  4. Have the Android Location API provide MSL/WGS84 offset lookup table (e.g., static method Location.getWgs84MslOffset(lat, lon)) - This is a generalized solution that would work for converting any altitude but is more involved that the above Location API-based solution as it would imply native layer implementing/exposing the lookup database. Unclear if this would be supported by OEMs and/or AOSP.

Additional context

  • Open issue where I could use this feature in one of my own apps - https://github.com/barbeau/gpstest/issues/296
  • Port of GeoTools EarthGravitationalModel to Android - https://github.com/barbeau/earth-gravitational-model

barbeau avatar Apr 30 '20 20:04 barbeau

This issue has been automatically marked as stale because it has not had recent activity. Please comment here if it is still valid so that we can reprioritize. Thank you!

stale[bot] avatar Aug 29 '20 14:08 stale[bot]

Still valid

barbeau avatar Aug 29 '20 15:08 barbeau

This issue has been automatically marked as stale because it has not had recent activity. Please comment here if it is still valid so that we can reprioritize. Thank you!

stale[bot] avatar Dec 28 '20 01:12 stale[bot]

This issue has been automatically marked as stale because it has not had recent activity. Please comment here if it is still valid so that we can reprioritize. Thank you!

stale[bot] avatar Jun 02 '21 16:06 stale[bot]

Related to this issue (and further justifying the need for a canonical source of altitude MSL) - there seems to be a bug impacting NMEA sentences which results in the wrong MSL altitude being returned: https://issuetracker.google.com/issues/191674805

I've observed this on two devices (Pixel 5 and Samsung Galaxy S21+), but based on anecdotal evidence from others it seems to be more widespread. The error seems to be larger on some devices than others - see also https://github.com/barbeau/gpstest/issues/503.

barbeau avatar Jun 21 '21 18:06 barbeau

I started looking into what it would take for apps (or AMU) to bundle their own geoid model to be able to calculate the altitude MSL value (H) themselves.

In short, to bundle the latest EGM2008 model adds around 16-17MB to the app APK size.

OpenTracks is one open-source app that has already done this:

  • https://github.com/OpenTracksApp/OpenTracks/blob/2843d1945099790d9022bf5d672800b9606a6ab4/src/main/java/de/dennisguse/opentracks/util/EGM2008Utils.java
  • https://github.com/OpenTracksApp/OpenTracks/blob/2843d1945099790d9022bf5d672800b9606a6ab4/src/androidTest/java/de/dennisguse/opentracks/util/EGM2008UtilsTest.java

They bundle the geographiclib EGM2008 model:

  • https://github.com/OpenTracksApp/OpenTracks/blob/2843d1945099790d9022bf5d672800b9606a6ab4/src/main/res/raw/egm2008_5.pgm

The resulting OpenTracks APK size is 21MB. More notes are here.

I believe the impact on AMU would be the same additional 16-17MB to add this feature (it's currently about 217KB).

@arriolac Given what appear to be widespread issues with altitude MSL quality in NMEA streams, and the sizeable impact of adding the geoid model to an app or library to be able to calculate the geoid offset ourselves, it really seems like the best place for this functionality to live is within the Android Location API, especially if the device already has this knowledge somewhere.

I added a comment to this effect on the above AOSP issue at: https://issuetracker.google.com/issues/191674805#comment4

barbeau avatar Jun 24 '21 15:06 barbeau

EGM96 is around only 2MB and it seems good enough for me.

sudokai avatar Jun 25 '21 10:06 sudokai

@sudokai That's true, although the EGM96 2MB file is also at a lesser grid resolution (15 vs 5 minutes) than the smallest EGM2008 model. I think "good enough" certainly depends on your use case.

Here's the full table of the different models and their resolution (grid) and size from GeographicLib for reference:

Name Geoid grid (minutes) size (MB)
egm84-30 EGM84 30' 0.6
egm84-15 EGM84 15' 2.1
egm96-15 EGM96 15' 2.1
egm96-5 EGM96 5' 19
egm2008-5 EGM2008 5' 19
egm2008-2_5 EGM2008 2.5' 75
egm2008-1 EGM2008 1' 470

barbeau avatar Jun 25 '21 13:06 barbeau

I've opened a feature request issue for a reliable first-class Android API to get the H and N values directly from the platform (vs parsing NMEA sentences or bundling your own geoid model) on the main Android issue tracker here: https://issuetracker.google.com/issues/195660815

Please star it if you'd like to see a solution directly in Android!

If implemented this would solve the problem presumably for Android T and higher, although we still need a solution for Android S and lower, so this ticket is still relevant.

barbeau avatar Aug 05 '21 22:08 barbeau

This issue has been automatically marked as stale because it has not had recent activity. Please comment here if it is still valid so that we can reprioritize. Thank you!

stale[bot] avatar Jan 03 '22 20:01 stale[bot]

short, to bundle the latest EGM2008 model adds around 16-17MB to the app APK size.

Okay, I investigated it. In short you should bandle not uncompressed 16 bit integer pgm, but float tiff (yes, as in jpeg/tiff standard) and that tiff should be GeoTIFF or better GTG standard, because a) it is float just like the orginal data inside gsb file, not quantised to 16 bit as in 16 bit tiff or PGM and it is lossless if correctly converted with for example https://github.com/OSGeo/PROJ-data/blob/master/grid_tools/ntv2_to_gtiff.py b) it should be 1'x1' EGM2008 or XGM2019e-5399. As for where to get, here. https://www.usna.edu/Users/oceano/pguth/md_help/html/egm96.htm in there https://www.agisoft.com/downloads/geoids/

Frankly speaking the original EGM2008 grid is 2.5'x2.5' so it all sounds kinda strange but whatever (P.S. apparently NGA did provide 1'x1' variant which became EPSG:3859).

2019e is 2'x2'. Can be found here: https://dataservices.gfz-potsdam.de/icgem/showshort.php?id=escidoc:4529896

Or I can recommend using some online API for this instead of doing strange stuff as implementing float operations in java.

Some interesting read https://lists.osgeo.org/pipermail/proj/2019-December/009121.html and this https://github.com/OSGeo/PROJ/pull/3033#event-5983808829

ValZapod avatar Feb 01 '22 04:02 ValZapod

This issue has been automatically marked as stale because it has not had recent activity. Please comment here if it is still valid so that we can reprioritize. Thank you!

stale[bot] avatar Jun 12 '22 16:06 stale[bot]

I learnt a LOT about this!

be 1'x1' EGM2008

Apparently this is codified: https://epsg.org/transformation_3859/WGS-84-to-EGM2008-height-2.html?sessionkey=931maihcrm which operates on EPSG:4979 (this ECEF, not the other 2 WGS 84 in its main ensembly) is one of two variants of https://epsg.org/crs_3855/EGM2008-height.html (the other one 2.5'x2.5' is https://epsg.org/transformation_3858/WGS-84-to-EGM2008-height-1.html).

This is how complex it is: first of all there are 3 types of WGS 84 in the main ensembly and members of that ensembly are dynamic, while those 3 are static, the latest dynamic one is G2139 frame realization and it is what GPS (America's GNSS) antennas use, as standard says. Each member of main ensembly has its own line of frame realisations, so G2139 is 3 different EPSG codes. https://epsg.org/search/by-name/?query=G2139&sessionkey=5ssq4ahhc9 you need geocentic one, which is ECEF (EPSG:9753).

So gps is using EPSG:9753, and we then search github on this issue. Apparently, we are not the first one with questions for this complexity https://github.com/opengisch/QField/issues/2855#issue-1224326798

9753 is also dynamic datum, so will require epoch calculations.

ValZapod avatar Jun 12 '22 16:06 ValZapod

Apparently it MATTERS. Surprise. See comment in https://rtklibexplorer.wordpress.com/2021/01/08/exploring-kinematic-single-receiver-solutions-with-rtklib-and-the-u-blox-f9p/

I noticed there are decimeter level biases in the “static” part of the PPP solutions and it seems to be a datum issue. It looks like the CORS station coordinates in the PPK.conf file correspond to IGS08 @ epoch 2005 while the PPP coordinates would be in IGS14 at the epoch of the precise orbit and clock products (~2021).

ValZapod avatar Jul 13 '22 04:07 ValZapod

Hi, this is a great thread! What's the progress on this? I was investigating exactly the same issue on the Qt platform for Android, the bug report is here: https://bugreports.qt.io/browse/QTBUG-106049

junwoo091400 avatar Sep 06 '22 08:09 junwoo091400

@junwoo091400 I would suggest starring this feature request for Android to directly support a dedicated API for this info - https://google.com/issues/195660815. You can also "thumbs up" this GitHub issue to vote for it.

barbeau avatar Sep 06 '22 14:09 barbeau

Looks like this has been quietly added in AOSP, in U DP1: https://developer.android.com/reference/android/location/altitude/AltitudeConverter

public void addMslAltitudeToLocation (Context context, Location location)

rockgecko-development avatar Feb 12 '23 13:02 rockgecko-development

@rockgecko-development You're right! Support for this feature is live in Android U DP1, so I'm going to close this issue.

You can also use this API to get the geoid offset at a lat/lon by passing in a location with a lat/lon and with WGS84 altitude 0 - you will get the negative geoid offset back via Location.getMslAltitudeMeters().

So something like:

location.setLatitude(...);
location.setLongitude(...);
location.setAltitude(0);
altitudeConverter.addMslAltitudeToLocation(context, location);
double geoidOffset = -LocationCompat.getMslAltitudeMeters(location);

Note that it shouldn't be run on the main thread because it potentially does I/O.

Thanks to everyone that worked on this in the Android team, it will be a very helpful utility! And thanks to everyone who upvoted the Google issue to express their interest in the feature.

barbeau avatar Feb 13 '23 15:02 barbeau