forked from upstream-mirrors/OpenTracks
@@ -136,6 +136,7 @@ dependencies {
|
|||||||
implementation 'androidx.core:core:1.13.1'
|
implementation 'androidx.core:core:1.13.1'
|
||||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||||
implementation 'androidx.mediarouter:mediarouter:1.7.0'
|
implementation 'androidx.mediarouter:mediarouter:1.7.0'
|
||||||
|
implementation 'androidx.core:core-location-altitude:1.0.0-alpha02'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test:core:1.6.1'
|
androidTestImplementation 'androidx.test:core:1.6.1'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||||
|
|||||||
+148
@@ -0,0 +1,148 @@
|
|||||||
|
package de.dennisguse.opentracks.services.handlers;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expectation is EGM2008 from <a href="https://geographiclib.sourceforge.io/">GeographicLib</a>] EGM2008 5minute undulation data.
|
||||||
|
*/
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class AltitudeCorrectionManagerTest {
|
||||||
|
|
||||||
|
private static final double MAX_ERROR = 0.4;
|
||||||
|
|
||||||
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_Northpole() {
|
||||||
|
// given
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint.setLatitude(90);
|
||||||
|
trackPoint.setLongitude(0.1);
|
||||||
|
trackPoint.setAltitude(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
new AltitudeCorrectionManager().correctAltitude(context, trackPoint);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(-14.8980, trackPoint.getAltitude().toM(), 2 * MAX_ERROR); //TODO For some reason, the Northpole is more off.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_Southpole() {
|
||||||
|
// given
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint.setLatitude(-90);
|
||||||
|
trackPoint.setLongitude(0);
|
||||||
|
trackPoint.setAltitude(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
new AltitudeCorrectionManager().correctAltitude(context, trackPoint);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(30.15, trackPoint.getAltitude().toM(), MAX_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_Southpole2() {
|
||||||
|
// given
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint.setLatitude(-90);
|
||||||
|
trackPoint.setLongitude(180);
|
||||||
|
trackPoint.setAltitude(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
new AltitudeCorrectionManager().correctAltitude(context, trackPoint);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(30.15, trackPoint.getAltitude().toM(), MAX_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_Southpole3() {
|
||||||
|
// given
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint.setLatitude(-90);
|
||||||
|
trackPoint.setLongitude(-180);
|
||||||
|
trackPoint.setAltitude(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
new AltitudeCorrectionManager().correctAltitude(context, trackPoint);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(30.15, trackPoint.getAltitude().toM(), MAX_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_0() throws IOException {
|
||||||
|
// given
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint.setLatitude(0);
|
||||||
|
trackPoint.setLongitude(0);
|
||||||
|
trackPoint.setAltitude(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
new AltitudeCorrectionManager().correctAltitude(context, trackPoint);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(-17.2260, trackPoint.getAltitude().toM(), MAX_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_Berlin_Germany_() {
|
||||||
|
// given
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint.setLatitude(52.530644);
|
||||||
|
trackPoint.setLongitude(13.383068);
|
||||||
|
trackPoint.setAltitude(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
new AltitudeCorrectionManager().correctAltitude(context, trackPoint);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(-39.4865, trackPoint.getAltitude().toM(), MAX_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_Seattle_USA() {
|
||||||
|
// given
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
|
||||||
|
trackPoint.setLatitude(47.63153);
|
||||||
|
trackPoint.setLongitude(-122.30938);
|
||||||
|
trackPoint.setAltitude(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
new AltitudeCorrectionManager().correctAltitude(context, trackPoint);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(22.99, trackPoint.getAltitude().toM(), MAX_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_MaxUndulation() throws IOException {
|
||||||
|
// given
|
||||||
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||||
|
trackPoint.setLatitude(-8.417);
|
||||||
|
trackPoint.setLongitude(147.367);
|
||||||
|
trackPoint.setAltitude(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
new AltitudeCorrectionManager().correctAltitude(context, trackPoint);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(-85.824, trackPoint.getAltitude().toM(), MAX_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.util;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNotEquals;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.junit.runners.JUnit4;
|
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
|
||||||
|
|
||||||
@RunWith(JUnit4.class)
|
|
||||||
public class EGM2008UtilsTest {
|
|
||||||
|
|
||||||
private static final double MAX_BILINEAR_ERROR = 0.478;
|
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void fileVerification() throws IOException {
|
|
||||||
// given
|
|
||||||
int expectedLength = 18671444;
|
|
||||||
int expectedHeaderLength = 404;
|
|
||||||
|
|
||||||
String expectedHeader = """
|
|
||||||
P5
|
|
||||||
# Geoid file in PGM format for the GeographicLib::Geoid class
|
|
||||||
# Description WGS84 EGM2008, 5-minute grid
|
|
||||||
# URL http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm2008
|
|
||||||
# DateTime 2009-08-29 18:45:00
|
|
||||||
# MaxBilinearError 0.478
|
|
||||||
# RMSBilinearError 0.012
|
|
||||||
# MaxCubicError 0.294
|
|
||||||
# RMSCubicError 0.005
|
|
||||||
# Offset -108
|
|
||||||
# Scale 0.003
|
|
||||||
# Origin 90N 0E
|
|
||||||
# AREA_OR_POINT Point
|
|
||||||
# Vertical_Datum WGS84
|
|
||||||
4320 2161
|
|
||||||
65535
|
|
||||||
""";
|
|
||||||
|
|
||||||
// when
|
|
||||||
try (InputStream inputStream = context.getResources().openRawResource(EGM2008Utils.EGM2008_5_DATA)) {
|
|
||||||
assertEquals(expectedLength, inputStream.available());
|
|
||||||
|
|
||||||
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.US_ASCII);
|
|
||||||
char[] data = new char[expectedHeaderLength];
|
|
||||||
int length = reader.read(data);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(expectedHeaderLength, length);
|
|
||||||
assertEquals(expectedHeader, new String(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void data_Northpole() throws IOException {
|
|
||||||
// given
|
|
||||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
|
||||||
trackPoint.setLatitude(90);
|
|
||||||
trackPoint.setLongitude(0.1);
|
|
||||||
trackPoint.setAltitude(0);
|
|
||||||
|
|
||||||
// when
|
|
||||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(-14.8980, altitude_egm2008.correctAltitude(trackPoint.getLocation()).toM(), MAX_BILINEAR_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void data_Southpole() throws IOException {
|
|
||||||
// given
|
|
||||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
|
||||||
trackPoint.setLatitude(-90);
|
|
||||||
trackPoint.setLongitude(0);
|
|
||||||
trackPoint.setAltitude(0);
|
|
||||||
|
|
||||||
// when
|
|
||||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(30.15, altitude_egm2008.correctAltitude(trackPoint.getLocation()).toM(), MAX_BILINEAR_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void data_Southpole2() throws IOException {
|
|
||||||
// given
|
|
||||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
|
||||||
trackPoint.setLatitude(-90);
|
|
||||||
trackPoint.setLongitude(180);
|
|
||||||
trackPoint.setAltitude(0);
|
|
||||||
|
|
||||||
// when
|
|
||||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(30.15, altitude_egm2008.correctAltitude(trackPoint.getLocation()).toM(), MAX_BILINEAR_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void data_Southpole3() throws IOException {
|
|
||||||
// given
|
|
||||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
|
||||||
trackPoint.setLatitude(-90);
|
|
||||||
trackPoint.setLongitude(-180);
|
|
||||||
trackPoint.setAltitude(0);
|
|
||||||
|
|
||||||
// when
|
|
||||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(30.15, altitude_egm2008.correctAltitude(trackPoint.getLocation()).toM(), MAX_BILINEAR_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void data_0() throws IOException {
|
|
||||||
// given
|
|
||||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
|
||||||
trackPoint.setLatitude(0);
|
|
||||||
trackPoint.setLongitude(0);
|
|
||||||
trackPoint.setAltitude(0);
|
|
||||||
|
|
||||||
// when
|
|
||||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(-17.2260, altitude_egm2008.correctAltitude(trackPoint.getLocation()).toM(), MAX_BILINEAR_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void data_Berlin_Germany_() throws IOException {
|
|
||||||
// given
|
|
||||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
|
||||||
trackPoint.setLatitude(52.530644);
|
|
||||||
trackPoint.setLongitude(13.383068);
|
|
||||||
trackPoint.setAltitude(0);
|
|
||||||
|
|
||||||
// when
|
|
||||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(-39.4865, altitude_egm2008.correctAltitude(trackPoint.getLocation()).toM(), MAX_BILINEAR_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void data_Berlin_Germany_Caching() throws IOException {
|
|
||||||
// given
|
|
||||||
TrackPoint trackPoint1 = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
|
||||||
trackPoint1.setLatitude(52.530644);
|
|
||||||
trackPoint1.setLongitude(13.383068);
|
|
||||||
trackPoint1.setAltitude(0);
|
|
||||||
|
|
||||||
TrackPoint trackPoint2 = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
|
||||||
trackPoint2.setLatitude(52.530000);
|
|
||||||
trackPoint2.setLongitude(13.380000);
|
|
||||||
trackPoint2.setAltitude(0);
|
|
||||||
|
|
||||||
// when
|
|
||||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint1.getLocation());
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertNotEquals(altitude_egm2008.correctAltitude(trackPoint1.getLocation()).toM(), altitude_egm2008.correctAltitude(trackPoint2.getLocation()).toM(), 0.0001);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void data_Seattle_USA() throws IOException {
|
|
||||||
// given
|
|
||||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
|
||||||
|
|
||||||
trackPoint.setLatitude(47.63153);
|
|
||||||
trackPoint.setLongitude(-122.30938);
|
|
||||||
trackPoint.setAltitude(0);
|
|
||||||
|
|
||||||
// when
|
|
||||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(22.99, altitude_egm2008.correctAltitude(trackPoint.getLocation()).toM(), MAX_BILINEAR_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void data_MaxUndulation() throws IOException {
|
|
||||||
// given
|
|
||||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
|
||||||
trackPoint.setLatitude(-8.417);
|
|
||||||
trackPoint.setLongitude(147.367);
|
|
||||||
trackPoint.setAltitude(0);
|
|
||||||
|
|
||||||
// when
|
|
||||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(-85.824, altitude_egm2008.correctAltitude(trackPoint.getLocation()).toM(), MAX_BILINEAR_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getIndices() {
|
|
||||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
|
||||||
trackPoint.setLatitude(90);
|
|
||||||
trackPoint.setLongitude(0);
|
|
||||||
assertEquals(new EGM2008Utils.Indices(0, 0), EGM2008Utils.getIndices(trackPoint.getLocation()));
|
|
||||||
|
|
||||||
trackPoint.setLatitude(0);
|
|
||||||
trackPoint.setLongitude(0);
|
|
||||||
assertEquals(new EGM2008Utils.Indices(1080, 0), EGM2008Utils.getIndices(trackPoint.getLocation()));
|
|
||||||
|
|
||||||
trackPoint.setLatitude(-90);
|
|
||||||
trackPoint.setLongitude(180);
|
|
||||||
assertEquals(new EGM2008Utils.Indices(2160, 4320 / 2), EGM2008Utils.getIndices(trackPoint.getLocation()));
|
|
||||||
|
|
||||||
trackPoint.setLatitude(-90);
|
|
||||||
trackPoint.setLongitude(-180);
|
|
||||||
assertEquals(new EGM2008Utils.Indices(2160, 2160), EGM2008Utils.getIndices(trackPoint.getLocation()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getUndulationRaw_ok() throws IOException {
|
|
||||||
try (DataInputStream dataInputStream = new DataInputStream(context.getResources().openRawResource(EGM2008Utils.EGM2008_5_DATA))) {
|
|
||||||
assertEquals(40966, EGM2008Utils.getUndulationRaw(dataInputStream, new EGM2008Utils.Indices(0, 0)));
|
|
||||||
assertEquals(41742, EGM2008Utils.getUndulationRaw(dataInputStream, new EGM2008Utils.Indices(1081, 0)));
|
|
||||||
assertEquals(25950, EGM2008Utils.getUndulationRaw(dataInputStream, new EGM2008Utils.Indices(2160, 4319)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = EOFException.class)
|
|
||||||
public void getUndulationRaw_error() throws IOException {
|
|
||||||
try (DataInputStream dataInputStream = new DataInputStream(context.getResources().openRawResource(EGM2008Utils.EGM2008_5_DATA))) {
|
|
||||||
assertEquals(0, EGM2008Utils.getUndulationRaw(dataInputStream, new EGM2008Utils.Indices(2161, 4320)), 0.01);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -33,6 +33,7 @@ import java.util.Optional;
|
|||||||
* Time is created using the {@link de.dennisguse.opentracks.services.handlers.MonotonicClock}, because system time jump backwards.
|
* Time is created using the {@link de.dennisguse.opentracks.services.handlers.MonotonicClock}, because system time jump backwards.
|
||||||
* GPS time is ignored as for non-GPS events, we could not create timestamps.
|
* GPS time is ignored as for non-GPS events, we could not create timestamps.
|
||||||
*/
|
*/
|
||||||
|
//TODO Should be a record (with final properties)
|
||||||
public class TrackPoint {
|
public class TrackPoint {
|
||||||
|
|
||||||
private static final Duration MAX_LOCATION_AGE = Duration.ofMinutes(1);
|
private static final Duration MAX_LOCATION_AGE = Duration.ofMinutes(1);
|
||||||
|
|||||||
+16
-40
@@ -2,15 +2,15 @@ package de.dennisguse.opentracks.services.handlers;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.location.altitude.AltitudeConverter;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.core.location.LocationCompat;
|
||||||
|
import androidx.core.location.altitude.AltitudeConverterCompat;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Altitude;
|
import de.dennisguse.opentracks.data.models.Altitude;
|
||||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||||
import de.dennisguse.opentracks.util.EGM2008Utils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* More infos regarding Android 34's <a href="https://issuetracker.google.com/issues/195660815#comment1">AltitudeConverter</a>.
|
* More infos regarding Android 34's <a href="https://issuetracker.google.com/issues/195660815#comment1">AltitudeConverter</a>.
|
||||||
@@ -19,52 +19,28 @@ public class AltitudeCorrectionManager {
|
|||||||
|
|
||||||
private static final String TAG = AltitudeCorrectionManager.class.getSimpleName();
|
private static final String TAG = AltitudeCorrectionManager.class.getSimpleName();
|
||||||
|
|
||||||
private AltitudeConverter altitudeConverter;
|
|
||||||
|
|
||||||
private final EGM2008Internal altitudeConverterFallback;
|
|
||||||
|
|
||||||
public AltitudeCorrectionManager() {
|
|
||||||
this.altitudeConverterFallback = new EGM2008Internal();
|
|
||||||
this.altitudeConverter = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE ? new AltitudeConverter() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void correctAltitude(Context context, TrackPoint trackPoint) {
|
public void correctAltitude(Context context, TrackPoint trackPoint) {
|
||||||
if (!trackPoint.hasLocation() || !trackPoint.hasAltitude()) {
|
if (!trackPoint.hasLocation() || !trackPoint.hasAltitude()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && altitudeConverter != null) {
|
|
||||||
|
// TODO The following is doing IO and should not be done in main thread.
|
||||||
|
// AltitudeConverterCompat uses internally a RoomDatabase that cannot be access from main thread and thus fails on version <= 34.
|
||||||
|
Thread t = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
Location loc = trackPoint.getLocation();
|
Location loc = trackPoint.getLocation();
|
||||||
altitudeConverter.addMslAltitudeToLocation(context, loc);
|
AltitudeConverterCompat.addMslAltitudeToLocation(context, loc);
|
||||||
trackPoint.setAltitude(Altitude.EGM2008.of(loc.getMslAltitudeMeters()));
|
trackPoint.setAltitude(Altitude.EGM2008.of(LocationCompat.getMslAltitudeMeters(loc)));
|
||||||
return;
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, "Android's AltitudeConverter crashed; falling back to internal.");
|
Log.w(TAG, "Android's AltitudeConverterCompat failed with " + e.getMessage());
|
||||||
altitudeConverter = null;
|
|
||||||
// Should we fallback
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
t.start();
|
||||||
|
|
||||||
altitudeConverterFallback.correctAltitude(context, trackPoint);
|
try {
|
||||||
}
|
t.join(); // wait for thread to finish
|
||||||
|
} catch (InterruptedException e) {
|
||||||
private static class EGM2008Internal {
|
Log.w(TAG, "Android's AltitudeConverterCompat failed with " + e.getMessage());
|
||||||
|
|
||||||
private static final String TAG = EGM2008Internal.class.getSimpleName();
|
|
||||||
|
|
||||||
private EGM2008Utils.EGM2008Correction egm2008Correction;
|
|
||||||
|
|
||||||
public void correctAltitude(Context context, TrackPoint trackPoint) {
|
|
||||||
if (egm2008Correction == null || !egm2008Correction.canCorrect(trackPoint.getLocation())) {
|
|
||||||
try {
|
|
||||||
egm2008Correction = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Could not load altitude correction for " + trackPoint, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trackPoint.setAltitude(egm2008Correction.correctAltitude(trackPoint.getLocation()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.util;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.location.Location;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
|
||||||
import de.dennisguse.opentracks.data.models.Altitude;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts WGS84 altitude to EGM2008 (should be close to height above sea level).
|
|
||||||
* <p>
|
|
||||||
* Uses <a href="https://geographiclib.sourceforge.io/">GeographicLib</a>] EGM2008 5minute undulation data.
|
|
||||||
* <a href="https://geographiclib.sourceforge.io/html/geoid.html">...</a>
|
|
||||||
* <p>
|
|
||||||
* File starts at 90N, 0E (North pole) and is encoded in parallel bands as unsigned shorts.
|
|
||||||
*/
|
|
||||||
public class EGM2008Utils {
|
|
||||||
|
|
||||||
static final int EGM2008_5_DATA = R.raw.egm2008_5;
|
|
||||||
|
|
||||||
private static final int HEADER_LENGTH = 404;
|
|
||||||
|
|
||||||
private static final int RESOLUTION_IN_MINUTES = 60 / 5;
|
|
||||||
|
|
||||||
private static final int LATITUDE_CORRECTION = 360 * RESOLUTION_IN_MINUTES;
|
|
||||||
|
|
||||||
private EGM2008Utils() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static EGM2008Correction createCorrection(Context context, Location location) throws IOException {
|
|
||||||
Indices indices = getIndices(location);
|
|
||||||
|
|
||||||
try (DataInputStream dataInputStream = new DataInputStream(context.getResources().openRawResource(EGM2008_5_DATA))) {
|
|
||||||
return new EGM2008Correction(indices, dataInputStream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
static int getUndulationRaw(DataInputStream dataInputStream, Indices indices) throws IOException {
|
|
||||||
dataInputStream.reset();
|
|
||||||
int absoluteIndex = indices.getAbsoluteIndex();
|
|
||||||
return getUndulationRaw(dataInputStream, absoluteIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getUndulationRaw(DataInputStream dataInputStream, int undulationIndex) throws IOException {
|
|
||||||
dataInputStream.reset();
|
|
||||||
int index = HEADER_LENGTH + undulationIndex * 2; //byte size is 2
|
|
||||||
long ignored = dataInputStream.skip(index);
|
|
||||||
|
|
||||||
return dataInputStream.readUnsignedShort();
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
static Indices getIndices(Location location) {
|
|
||||||
double latitude = -location.getLatitude() + 90;
|
|
||||||
int latitudeIndex = (int) (latitude * RESOLUTION_IN_MINUTES);
|
|
||||||
|
|
||||||
double longitude;
|
|
||||||
if (location.getLongitude() >= 0) {
|
|
||||||
longitude = location.getLongitude();
|
|
||||||
} else {
|
|
||||||
longitude = 360 + location.getLongitude();
|
|
||||||
}
|
|
||||||
int longitudeIndex = (int) (longitude * RESOLUTION_IN_MINUTES);
|
|
||||||
|
|
||||||
if (longitudeIndex >= 360 * RESOLUTION_IN_MINUTES) {
|
|
||||||
longitudeIndex = 0;
|
|
||||||
}
|
|
||||||
return new Indices(latitudeIndex, longitudeIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class EGM2008Correction {
|
|
||||||
|
|
||||||
protected final Indices indices;
|
|
||||||
|
|
||||||
protected final int v00;
|
|
||||||
protected final int v10;
|
|
||||||
protected final int v01;
|
|
||||||
protected final int v11;
|
|
||||||
|
|
||||||
private EGM2008Correction(Indices indices, DataInputStream dataInputStream) throws IOException {
|
|
||||||
this.indices = indices;
|
|
||||||
|
|
||||||
v00 = getUndulationRaw(dataInputStream, indices);
|
|
||||||
if (!isSouthPole()) {
|
|
||||||
v10 = getUndulationRaw(dataInputStream, indices.offset(0, 1));
|
|
||||||
v01 = getUndulationRaw(dataInputStream, indices.offset(1, 0));
|
|
||||||
v11 = getUndulationRaw(dataInputStream, indices.offset(1, 1));
|
|
||||||
} else {
|
|
||||||
v10 = 0;
|
|
||||||
v01 = 0;
|
|
||||||
v11 = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean canCorrect(@NonNull Location location) {
|
|
||||||
return indices.getAbsoluteIndex() == getIndices(location).getAbsoluteIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Altitude.EGM2008 correctAltitude(@NonNull Location location) {
|
|
||||||
if (!canCorrect(location))
|
|
||||||
throw new RuntimeException("Undulation data not loaded for this location.");
|
|
||||||
if (!location.hasAltitude())
|
|
||||||
throw new RuntimeException("Location has no altitude");
|
|
||||||
|
|
||||||
double undulationRaw;
|
|
||||||
if (isSouthPole()) {
|
|
||||||
// No bilinear interpolation on South Pole (not worth the time)
|
|
||||||
undulationRaw = v00;
|
|
||||||
} else {
|
|
||||||
double fLongitude = location.getLongitude() * RESOLUTION_IN_MINUTES -
|
|
||||||
(int) (location.getLongitude() * RESOLUTION_IN_MINUTES);
|
|
||||||
|
|
||||||
double fLatitude = (-location.getLatitude() + 90) * RESOLUTION_IN_MINUTES
|
|
||||||
- (int) ((-location.getLatitude() + 90) * RESOLUTION_IN_MINUTES);
|
|
||||||
|
|
||||||
// Bilinear interpolation (optimized; taken from GeopgrahicLib/Geoid.cpp)
|
|
||||||
double
|
|
||||||
a = (1 - fLongitude) * v00 + fLongitude * v01,
|
|
||||||
b = (1 - fLongitude) * v10 + fLongitude * v11;
|
|
||||||
undulationRaw = (1 - fLatitude) * a + fLatitude * b;
|
|
||||||
}
|
|
||||||
// Bilinear interpolation (not optimized)
|
|
||||||
//undulationRaw = v00 * (1 - fLongitude) * (1 - fLatitude)
|
|
||||||
// + v10 * fLongitude * (1 - fLatitude)
|
|
||||||
// + v01 * (1 - fLongitude) * fLatitude
|
|
||||||
// + v11 * fLongitude * fLatitude;
|
|
||||||
|
|
||||||
double h = 0.003 * undulationRaw - 108;
|
|
||||||
|
|
||||||
return Altitude.EGM2008.of(location.getAltitude() - h);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSouthPole() {
|
|
||||||
return indices.latitudeIndex == 2160;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
record Indices(int latitudeIndex, int longitudeIndex) {
|
|
||||||
|
|
||||||
Indices offset(int latitudeOffset, int longitudeOffset) {
|
|
||||||
return new Indices(latitudeIndex + latitudeOffset, longitudeIndex + longitudeOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
int getAbsoluteIndex() {
|
|
||||||
return latitudeIndex * LATITUDE_CORRECTION + longitudeIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user