Adds to AltitudeConverter a method that returns a geoid height at the location (go/msat:geoid-heights-altitude-hal-design).
Note that the implementation uses *fake* assets for calculating expiration distances, specifically, a copy of the geoid height assets. Real assets will be added in followup CLs.
Test: FrameworksMockingServicesTests:AltitudeConverterTest
Bug: 304375846
Change-Id: I78bc3c9f9d814f750c38c627ee9af8dc27183e2a
diff --git a/Android.bp b/Android.bp
index 9c56733..e12f74f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -95,6 +95,7 @@
":platform-compat-native-aidl",
// AIDL sources from external directories
+ ":android.frameworks.location.altitude-V2-java-source",
":android.hardware.biometrics.common-V4-java-source",
":android.hardware.biometrics.fingerprint-V3-java-source",
":android.hardware.biometrics.face-V4-java-source",
diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
index 3dc024ef..6f88912 100644
--- a/location/java/android/location/altitude/AltitudeConverter.java
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -19,9 +19,11 @@
import android.annotation.NonNull;
import android.annotation.WorkerThread;
import android.content.Context;
+import android.frameworks.location.altitude.GetGeoidHeightRequest;
+import android.frameworks.location.altitude.GetGeoidHeightResponse;
import android.location.Location;
-import com.android.internal.location.altitude.GeoidHeightMap;
+import com.android.internal.location.altitude.GeoidMap;
import com.android.internal.location.altitude.S2CellIdUtils;
import com.android.internal.location.altitude.nano.MapParamsProto;
import com.android.internal.util.Preconditions;
@@ -37,7 +39,7 @@
* <pre>
* Brian Julian and Michael Angermann.
* "Resource efficient and accurate altitude conversion to Mean Sea Level."
- * To appear in 2023 IEEE/ION Position, Location and Navigation Symposium (PLANS).
+ * 2023 IEEE/ION Position, Location and Navigation Symposium (PLANS).
* </pre>
*/
public final class AltitudeConverter {
@@ -45,8 +47,8 @@
private static final double MAX_ABS_VALID_LATITUDE = 90;
private static final double MAX_ABS_VALID_LONGITUDE = 180;
- /** Manages a mapping of geoid heights associated with S2 cells. */
- private final GeoidHeightMap mGeoidHeightMap = new GeoidHeightMap();
+ /** Manages a mapping of geoid heights and expiration distances associated with S2 cells. */
+ private final GeoidMap mGeoidMap = new GeoidMap();
/**
* Creates an instance that manages an independent cache to optimized conversions of locations
@@ -78,75 +80,87 @@
/**
* Returns the four S2 cell IDs for the map square associated with the {@code location}.
*
- * <p>The first map cell contains the location, while the others are located horizontally,
- * vertically, and diagonally, in that order, with respect to the S2 (i,j) coordinate system. If
- * the diagonal map cell does not exist (i.e., the location is near an S2 cube vertex), its
- * corresponding ID is set to zero.
+ * <p>The first map cell, denoted z11 in the appendix of the referenced paper above, contains
+ * the location. The others are the map cells denoted z21, z12, and z22, in that order.
*/
- @NonNull
- private static long[] findMapSquare(@NonNull MapParamsProto params,
+ private static long[] findMapSquare(@NonNull MapParamsProto geoidHeightParams,
@NonNull Location location) {
long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
location.getLongitude());
// Cell-space properties and coordinates.
- int sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level);
+ int sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - geoidHeightParams.mapS2Level);
int maxIj = 1 << S2CellIdUtils.MAX_LEVEL;
- long s0 = S2CellIdUtils.getParent(s2CellId, params.mapS2Level);
- int f0 = S2CellIdUtils.getFace(s2CellId);
- int i0 = S2CellIdUtils.getI(s2CellId);
- int j0 = S2CellIdUtils.getJ(s2CellId);
- int i1 = i0 + sizeIj;
- int j1 = j0 + sizeIj;
+ long z11 = S2CellIdUtils.getParent(s2CellId, geoidHeightParams.mapS2Level);
+ int f11 = S2CellIdUtils.getFace(s2CellId);
+ int i1 = S2CellIdUtils.getI(s2CellId);
+ int j1 = S2CellIdUtils.getJ(s2CellId);
+ int i2 = i1 + sizeIj;
+ int j2 = j1 + sizeIj;
// Non-boundary region calculation - simplest and most common case.
- if (i1 < maxIj && j1 < maxIj) {
- return new long[]{
- s0,
- S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i1, j0), params.mapS2Level),
- S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i0, j1), params.mapS2Level),
- S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i1, j1), params.mapS2Level)
- };
+ if (i2 < maxIj && j2 < maxIj) {
+ return new long[]{z11, S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f11, i2, j1),
+ geoidHeightParams.mapS2Level), S2CellIdUtils.getParent(
+ S2CellIdUtils.fromFij(f11, i1, j2), geoidHeightParams.mapS2Level),
+ S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f11, i2, j2),
+ geoidHeightParams.mapS2Level)};
}
- // Boundary region calculation.
+ // Boundary region calculation
long[] edgeNeighbors = new long[4];
- S2CellIdUtils.getEdgeNeighbors(s0, edgeNeighbors);
- long s1 = edgeNeighbors[1];
- long s2 = edgeNeighbors[2];
- long s3;
- if (f0 % 2 == 1) {
- S2CellIdUtils.getEdgeNeighbors(s1, edgeNeighbors);
- if (i1 < maxIj) {
- s3 = edgeNeighbors[2];
- } else {
- s3 = s1;
- s1 = edgeNeighbors[1];
- }
- } else {
- S2CellIdUtils.getEdgeNeighbors(s2, edgeNeighbors);
- if (j1 < maxIj) {
- s3 = edgeNeighbors[1];
- } else {
- s3 = s2;
- s2 = edgeNeighbors[3];
- }
- }
+ S2CellIdUtils.getEdgeNeighbors(z11, edgeNeighbors);
+ long z11W = edgeNeighbors[0];
+ long z11S = edgeNeighbors[1];
+ long z11E = edgeNeighbors[2];
+ long z11N = edgeNeighbors[3];
+
+ long[] otherEdgeNeighbors = new long[4];
+ S2CellIdUtils.getEdgeNeighbors(z11W, otherEdgeNeighbors);
+ S2CellIdUtils.getEdgeNeighbors(z11S, edgeNeighbors);
+ long z11Sw = findCommonNeighbor(edgeNeighbors, otherEdgeNeighbors, z11);
+ S2CellIdUtils.getEdgeNeighbors(z11E, otherEdgeNeighbors);
+ long z11Se = findCommonNeighbor(edgeNeighbors, otherEdgeNeighbors, z11);
+ S2CellIdUtils.getEdgeNeighbors(z11N, edgeNeighbors);
+ long z11Ne = findCommonNeighbor(edgeNeighbors, otherEdgeNeighbors, z11);
+
+ long z21 = (f11 % 2 == 1 && i2 >= maxIj) ? z11Sw : z11S;
+ long z12 = (f11 % 2 == 0 && j2 >= maxIj) ? z11Ne : z11E;
+ long z22 = (z21 == z11Sw) ? z11S : (z12 == z11Ne) ? z11E : z11Se;
// Reuse edge neighbors' array to avoid an extra allocation.
- edgeNeighbors[0] = s0;
- edgeNeighbors[1] = s1;
- edgeNeighbors[2] = s2;
- edgeNeighbors[3] = s3;
+ edgeNeighbors[0] = z11;
+ edgeNeighbors[1] = z21;
+ edgeNeighbors[2] = z12;
+ edgeNeighbors[3] = z22;
return edgeNeighbors;
}
/**
+ * Returns the first common non-z11 neighbor found between the two arrays of edge neighbors. If
+ * such a common neighbor does not exist, returns z11.
+ */
+ private static long findCommonNeighbor(long[] edgeNeighbors, long[] otherEdgeNeighbors,
+ long z11) {
+ for (long edgeNeighbor : edgeNeighbors) {
+ if (edgeNeighbor == z11) {
+ continue;
+ }
+ for (long otherEdgeNeighbor : otherEdgeNeighbors) {
+ if (edgeNeighbor == otherEdgeNeighbor) {
+ return edgeNeighbor;
+ }
+ }
+ }
+ return z11;
+ }
+
+ /**
* Adds to {@code location} the bilinearly interpolated Mean Sea Level altitude. In addition, a
* Mean Sea Level altitude accuracy is added if the {@code location} has a valid vertical
* accuracy; otherwise, does not add a corresponding accuracy.
*/
- private static void addMslAltitude(@NonNull MapParamsProto params,
+ private static void addMslAltitude(@NonNull MapParamsProto geoidHeightParams,
@NonNull double[] geoidHeightsMeters, @NonNull Location location) {
double h0 = geoidHeightsMeters[0];
double h1 = geoidHeightsMeters[1];
@@ -158,7 +172,7 @@
// employ the simplified unit square formulation.
long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
location.getLongitude());
- double sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level);
+ double sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - geoidHeightParams.mapS2Level);
double wi = (S2CellIdUtils.getI(s2CellId) % sizeIj) / sizeIj;
double wj = (S2CellIdUtils.getJ(s2CellId) % sizeIj) / sizeIj;
double offsetMeters = h0 + (h1 - h0) * wi + (h2 - h0) * wj + (h3 - h1 - h2 + h0) * wi * wj;
@@ -167,8 +181,8 @@
if (location.hasVerticalAccuracy()) {
double verticalAccuracyMeters = location.getVerticalAccuracyMeters();
if (Double.isFinite(verticalAccuracyMeters) && verticalAccuracyMeters >= 0) {
- location.setMslAltitudeAccuracyMeters(
- (float) Math.hypot(verticalAccuracyMeters, params.modelRmseMeters));
+ location.setMslAltitudeAccuracyMeters((float) Math.hypot(verticalAccuracyMeters,
+ geoidHeightParams.modelRmseMeters));
}
}
}
@@ -191,10 +205,11 @@
public void addMslAltitudeToLocation(@NonNull Context context, @NonNull Location location)
throws IOException {
validate(location);
- MapParamsProto params = GeoidHeightMap.getParams(context);
- long[] s2CellIds = findMapSquare(params, location);
- double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, context, s2CellIds);
- addMslAltitude(params, geoidHeightsMeters, location);
+ MapParamsProto geoidHeightParams = GeoidMap.getGeoidHeightParams(context);
+ long[] mapCells = findMapSquare(geoidHeightParams, location);
+ double[] geoidHeightsMeters = mGeoidMap.readGeoidHeights(geoidHeightParams, context,
+ mapCells);
+ addMslAltitude(geoidHeightParams, geoidHeightsMeters, location);
}
/**
@@ -206,18 +221,68 @@
*/
public boolean addMslAltitudeToLocation(@NonNull Location location) {
validate(location);
- MapParamsProto params = GeoidHeightMap.getParams();
- if (params == null) {
+ MapParamsProto geoidHeightParams = GeoidMap.getGeoidHeightParams();
+ if (geoidHeightParams == null) {
return false;
}
- long[] s2CellIds = findMapSquare(params, location);
- double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, s2CellIds);
+ long[] mapCells = findMapSquare(geoidHeightParams, location);
+ double[] geoidHeightsMeters = mGeoidMap.readGeoidHeights(geoidHeightParams, mapCells);
if (geoidHeightsMeters == null) {
return false;
}
- addMslAltitude(params, geoidHeightsMeters, location);
+ addMslAltitude(geoidHeightParams, geoidHeightsMeters, location);
return true;
}
+
+ /**
+ * Returns the geoid height (a.k.a. geoid undulation) at the location specified in {@code
+ * request}. The geoid height at a location is defined as the difference between an altitude
+ * measured above the World Geodetic System 1984 reference ellipsoid (WGS84) and its
+ * corresponding Mean Sea Level altitude.
+ *
+ * <p>Must be called off the main thread as data may be loaded from raw assets.
+ *
+ * @throws IOException if an I/O error occurs when loading data from raw assets.
+ * @throws IllegalArgumentException if the {@code request} has an invalid latitude or longitude.
+ * Specifically, the latitude must be between -90 and 90 (both
+ * inclusive), and the longitude must be between -180 and 180
+ * (both inclusive).
+ * @hide
+ */
+ @WorkerThread
+ public @NonNull GetGeoidHeightResponse getGeoidHeight(@NonNull Context context,
+ @NonNull GetGeoidHeightRequest request) throws IOException {
+ // Create a valid location from which the geoid height and its accuracy will be extracted.
+ Location location = new Location("");
+ location.setLatitude(request.latitudeDegrees);
+ location.setLongitude(request.longitudeDegrees);
+ location.setAltitude(0.0);
+ location.setVerticalAccuracyMeters(0.0f);
+
+ addMslAltitudeToLocation(context, location);
+ // The geoid height for a location with zero WGS84 altitude is equal in value to the
+ // negative of corresponding MSL altitude.
+ double geoidHeightMeters = -location.getMslAltitudeMeters();
+ // The geoid height error for a location with zero vertical accuracy is equal in value to
+ // the corresponding MSL altitude accuracy.
+ float geoidHeightErrorMeters = location.getMslAltitudeAccuracyMeters();
+
+ MapParamsProto expirationDistanceParams = GeoidMap.getExpirationDistanceParams(context);
+ long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
+ location.getLongitude());
+ long[] mapCell = {S2CellIdUtils.getParent(s2CellId, expirationDistanceParams.mapS2Level)};
+ double expirationDistanceMeters = mGeoidMap.readExpirationDistances(
+ expirationDistanceParams, context, mapCell)[0];
+ float additionalGeoidHeightErrorMeters = (float) expirationDistanceParams.modelRmseMeters;
+
+ GetGeoidHeightResponse response = new GetGeoidHeightResponse();
+ response.geoidHeightMeters = geoidHeightMeters;
+ response.geoidHeightErrorMeters = geoidHeightErrorMeters;
+ response.expirationDistanceMeters = expirationDistanceMeters;
+ response.additionalGeoidHeightErrorMeters = additionalGeoidHeightErrorMeters;
+ response.success = true;
+ return response;
+ }
}
diff --git a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java b/location/java/com/android/internal/location/altitude/GeoidMap.java
similarity index 65%
rename from location/java/com/android/internal/location/altitude/GeoidHeightMap.java
rename to location/java/com/android/internal/location/altitude/GeoidMap.java
index 8067050..9bf5689 100644
--- a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
+++ b/location/java/com/android/internal/location/altitude/GeoidMap.java
@@ -34,11 +34,12 @@
import java.util.Objects;
/**
- * Manages a mapping of geoid heights associated with S2 cells, referred to as MAP CELLS.
+ * Manages a mapping of geoid heights and expiration distances associated with S2 cells, referred to
+ * as MAP CELLS.
*
* <p>Tiles are used extensively to reduce the number of entries needed to be stored in memory and
- * on disk. A tile associates geoid heights with all map cells of a common parent at a specified S2
- * level.
+ * on disk. A tile associates geoid heights or expiration distances with all map cells of a common
+ * parent at a specified S2 level.
*
* <p>Since bilinear interpolation considers at most four map cells at a time, at most four tiles
* are simultaneously stored in memory. These tiles, referred to as CACHE TILES, are each keyed by
@@ -48,42 +49,79 @@
* The latter tiles, referred to as DISK TILES, are each keyed by its common parent's S2 cell token,
* referred to as a DISK TOKEN.
*/
-public final class GeoidHeightMap {
+public final class GeoidMap {
- private static final Object sLock = new Object();
+ private static final Object GEOID_HEIGHT_PARAMS_LOCK = new Object();
- @GuardedBy("sLock")
+ private static final Object EXPIRATION_DISTANCE_PARAMS_LOCK = new Object();
+
+ @GuardedBy("GEOID_HEIGHT_PARAMS_LOCK")
@Nullable
- private static MapParamsProto sParams;
+ private static MapParamsProto sGeoidHeightParams;
- /** Defines a cache large enough to hold all cache tiles needed for interpolation. */
- private final LruCache<Long, S2TileProto> mCacheTiles = new LruCache<>(4);
+ @GuardedBy("EXPIRATION_DISTANCE_PARAMS_LOCK")
+ @Nullable
+ private static MapParamsProto sExpirationDistanceParams;
/**
- * Returns the singleton parameter instance for a spherically projected geoid height map and its
- * corresponding tile management.
+ * Defines a cache large enough to hold all geoid height cache tiles needed for interpolation.
+ */
+ private final LruCache<Long, S2TileProto> mGeoidHeightCacheTiles = new LruCache<>(4);
+
+ /**
+ * Defines a cache large enough to hold all expiration distance cache tiles needed for
+ * interpolation.
+ */
+ private final LruCache<Long, S2TileProto> mExpirationDistanceCacheTiles = new LruCache<>(4);
+
+ /**
+ * Returns the singleton parameter instance for geoid height parameters of a spherically
+ * projected map.
*/
@NonNull
- public static MapParamsProto getParams(@NonNull Context context) throws IOException {
- synchronized (sLock) {
- if (sParams == null) {
- try (InputStream is = context.getApplicationContext().getAssets().open(
- "geoid_height_map/map-params.pb")) {
- sParams = MapParamsProto.parseFrom(is.readAllBytes());
- }
+ public static MapParamsProto getGeoidHeightParams(@NonNull Context context) throws IOException {
+ synchronized (GEOID_HEIGHT_PARAMS_LOCK) {
+ if (sGeoidHeightParams == null) {
+ // TODO: b/304375846 - Configure with disk tile prefix once resources are updated.
+ sGeoidHeightParams = parseParams(context);
}
- return sParams;
+ return sGeoidHeightParams;
}
}
/**
- * Same as {@link #getParams(Context)} except that null is returned if the singleton parameter
- * instance is not yet initialized.
+ * Returns the singleton parameter instance for expiration distance parameters of a spherically
+ * projected
+ * map.
+ */
+ @NonNull
+ public static MapParamsProto getExpirationDistanceParams(@NonNull Context context)
+ throws IOException {
+ synchronized (EXPIRATION_DISTANCE_PARAMS_LOCK) {
+ if (sExpirationDistanceParams == null) {
+ // TODO: b/304375846 - Configure with disk tile prefix once resources are updated.
+ sExpirationDistanceParams = parseParams(context);
+ }
+ return sExpirationDistanceParams;
+ }
+ }
+
+ @NonNull
+ private static MapParamsProto parseParams(@NonNull Context context) throws IOException {
+ try (InputStream is = context.getApplicationContext().getAssets().open(
+ "geoid_height_map/map-params.pb")) {
+ return MapParamsProto.parseFrom(is.readAllBytes());
+ }
+ }
+
+ /**
+ * Same as {@link #getGeoidHeightParams(Context)} except that null is returned if the singleton
+ * parameter instance is not yet initialized.
*/
@Nullable
- public static MapParamsProto getParams() {
- synchronized (sLock) {
- return sParams;
+ public static MapParamsProto getGeoidHeightParams() {
+ synchronized (GEOID_HEIGHT_PARAMS_LOCK) {
+ return sGeoidHeightParams;
}
}
@@ -93,18 +131,17 @@
@NonNull
private static String getDiskToken(@NonNull MapParamsProto params, long s2CellId) {
- return S2CellIdUtils.getToken(
- S2CellIdUtils.getParent(s2CellId, params.diskTileS2Level));
+ return S2CellIdUtils.getToken(S2CellIdUtils.getParent(s2CellId, params.diskTileS2Level));
}
/**
* Adds to {@code values} values in the unit interval [0, 1] for the map cells identified by
- * {@code s2CellIds}. Returns true if values are present for all IDs; otherwise, returns false
- * and adds NaNs for absent values.
+ * {@code s2CellIds}. Returns true if values are present for all IDs; otherwise, adds NaNs for
+ * absent values and returns false.
*/
private static boolean getUnitIntervalValues(@NonNull MapParamsProto params,
- @NonNull TileFunction tileFunction,
- @NonNull long[] s2CellIds, @NonNull double[] values) {
+ @NonNull TileFunction tileFunction, @NonNull long[] s2CellIds,
+ @NonNull double[] values) {
int len = s2CellIds.length;
S2TileProto[] tiles = new S2TileProto[len];
@@ -137,9 +174,8 @@
@SuppressWarnings("ReferenceEquality")
private static void mergeByteBufferValues(@NonNull MapParamsProto params,
- @NonNull long[] s2CellIds,
- @NonNull S2TileProto[] tiles,
- int tileIndex, @NonNull double[] values) {
+ @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex,
+ @NonNull double[] values) {
byte[] bytes = tiles[tileIndex].byteBuffer;
if (bytes == null || bytes.length == 0) {
return;
@@ -163,24 +199,22 @@
}
private static void mergeByteJpegValues(@NonNull MapParamsProto params,
- @NonNull long[] s2CellIds,
- @NonNull S2TileProto[] tiles,
- int tileIndex, @NonNull double[] values) {
+ @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex,
+ @NonNull double[] values) {
mergeByteImageValues(params, tiles[tileIndex].byteJpeg, s2CellIds, tiles, tileIndex,
values);
}
private static void mergeBytePngValues(@NonNull MapParamsProto params,
- @NonNull long[] s2CellIds,
- @NonNull S2TileProto[] tiles,
- int tileIndex, @NonNull double[] values) {
+ @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex,
+ @NonNull double[] values) {
mergeByteImageValues(params, tiles[tileIndex].bytePng, s2CellIds, tiles, tileIndex, values);
}
@SuppressWarnings("ReferenceEquality")
private static void mergeByteImageValues(@NonNull MapParamsProto params, @NonNull byte[] bytes,
- @NonNull long[] s2CellIds,
- @NonNull S2TileProto[] tiles, int tileIndex, @NonNull double[] values) {
+ @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex,
+ @NonNull double[] values) {
if (bytes == null || bytes.length == 0) {
return;
}
@@ -219,7 +253,7 @@
* ID.
*/
private static void validate(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) {
- Preconditions.checkArgument(s2CellIds.length == 4);
+ Preconditions.checkArgument(s2CellIds.length <= 4);
for (long s2CellId : s2CellIds) {
Preconditions.checkArgument(S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level);
}
@@ -233,15 +267,38 @@
@NonNull
public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context,
@NonNull long[] s2CellIds) throws IOException {
+ return readMapValues(params, context, s2CellIds, mGeoidHeightCacheTiles);
+ }
+
+ /**
+ * Returns the expiration distances in meters associated with the map cells identified by
+ * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for
+ * an ID.
+ */
+ @NonNull
+ public double[] readExpirationDistances(@NonNull MapParamsProto params,
+ @NonNull Context context, @NonNull long[] s2CellIds) throws IOException {
+ return readMapValues(params, context, s2CellIds, mExpirationDistanceCacheTiles);
+ }
+
+ /**
+ * Returns the map values in meters associated with the map cells identified by
+ * {@code s2CellIds}. Throws an {@link IOException} if a map value cannot be calculated for an
+ * ID.
+ */
+ @NonNull
+ private static double[] readMapValues(@NonNull MapParamsProto params, @NonNull Context context,
+ @NonNull long[] s2CellIds, @NonNull LruCache<Long, S2TileProto> cacheTiles)
+ throws IOException {
validate(params, s2CellIds);
- double[] heightsMeters = new double[s2CellIds.length];
- if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) {
- return heightsMeters;
+ double[] mapValuesMeters = new double[s2CellIds.length];
+ if (getMapValues(params, cacheTiles::get, s2CellIds, mapValuesMeters)) {
+ return mapValuesMeters;
}
- TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds);
- if (getGeoidHeights(params, loadedTiles, s2CellIds, heightsMeters)) {
- return heightsMeters;
+ TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds, cacheTiles);
+ if (getMapValues(params, loadedTiles, s2CellIds, mapValuesMeters)) {
+ return mapValuesMeters;
}
throw new IOException("Unable to calculate geoid heights from raw assets.");
}
@@ -255,32 +312,33 @@
public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) {
validate(params, s2CellIds);
double[] heightsMeters = new double[s2CellIds.length];
- if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) {
+ if (getMapValues(params, mGeoidHeightCacheTiles::get, s2CellIds, heightsMeters)) {
return heightsMeters;
}
return null;
}
/**
- * Adds to {@code heightsMeters} the geoid heights in meters associated with the map cells
+ * Adds to {@code mapValuesMeters} the map values in meters associated with the map cells
* identified by {@code s2CellIds}. Returns true if heights are present for all IDs; otherwise,
- * returns false and adds NaNs for absent heights.
+ * adds NaNs for absent heights and returns false.
*/
- private boolean getGeoidHeights(@NonNull MapParamsProto params,
+ private static boolean getMapValues(@NonNull MapParamsProto params,
@NonNull TileFunction tileFunction, @NonNull long[] s2CellIds,
- @NonNull double[] heightsMeters) {
- boolean allFound = getUnitIntervalValues(params, tileFunction, s2CellIds, heightsMeters);
- for (int i = 0; i < heightsMeters.length; i++) {
+ @NonNull double[] mapValuesMeters) {
+ boolean allFound = getUnitIntervalValues(params, tileFunction, s2CellIds, mapValuesMeters);
+ for (int i = 0; i < mapValuesMeters.length; i++) {
// NaNs are properly preserved.
- heightsMeters[i] *= params.modelAMeters;
- heightsMeters[i] += params.modelBMeters;
+ mapValuesMeters[i] *= params.modelAMeters;
+ mapValuesMeters[i] += params.modelBMeters;
}
return allFound;
}
@NonNull
- private TileFunction loadFromCacheAndDisk(@NonNull MapParamsProto params,
- @NonNull Context context, @NonNull long[] s2CellIds) throws IOException {
+ private static TileFunction loadFromCacheAndDisk(@NonNull MapParamsProto params,
+ @NonNull Context context, @NonNull long[] s2CellIds,
+ @NonNull LruCache<Long, S2TileProto> cacheTiles) throws IOException {
int len = s2CellIds.length;
// Enable batch loading by finding all cache keys upfront.
@@ -296,7 +354,7 @@
if (diskTokens[i] != null) {
continue;
}
- loadedTiles[i] = mCacheTiles.get(cacheKeys[i]);
+ loadedTiles[i] = cacheTiles.get(cacheKeys[i]);
diskTokens[i] = getDiskToken(params, cacheKeys[i]);
// Batch across common cache key.
@@ -319,7 +377,7 @@
"geoid_height_map/tile-" + diskTokens[i] + ".pb")) {
tile = S2TileProto.parseFrom(is.readAllBytes());
}
- mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles);
+ mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles, cacheTiles);
}
return cacheKey -> {
@@ -332,9 +390,10 @@
};
}
- private void mergeFromDiskTile(@NonNull MapParamsProto params, @NonNull S2TileProto diskTile,
- @NonNull long[] cacheKeys, @NonNull String[] diskTokens, int diskTokenIndex,
- @NonNull S2TileProto[] loadedTiles) throws IOException {
+ private static void mergeFromDiskTile(@NonNull MapParamsProto params,
+ @NonNull S2TileProto diskTile, @NonNull long[] cacheKeys, @NonNull String[] diskTokens,
+ int diskTokenIndex, @NonNull S2TileProto[] loadedTiles,
+ @NonNull LruCache<Long, S2TileProto> cacheTiles) throws IOException {
int len = cacheKeys.length;
int numMapCellsPerCacheTile = 1 << (2 * (params.mapS2Level - params.cacheTileS2Level));
@@ -375,7 +434,7 @@
}
// Side load into tile cache.
- mCacheTiles.put(cacheKeys[i], loadedTiles[i]);
+ cacheTiles.put(cacheKeys[i], loadedTiles[i]);
}
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a54a48a..8e35b74 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -139,6 +139,7 @@
libs: [
"services.net",
+ "android.frameworks.location.altitude-V2-java",
"android.hardware.common-V2-java",
"android.hardware.light-V2.0-java",
"android.hardware.gnss-V2-java",
@@ -160,7 +161,6 @@
],
static_libs: [
- "android.frameworks.location.altitude-V2-java", // AIDL
"android.frameworks.vibrator-V1-java", // AIDL
"android.hardware.authsecret-V1.0-java",
"android.hardware.authsecret-V1-java",
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java b/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
index 8d9a6c5..9a143d5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
@@ -21,6 +21,8 @@
import static org.junit.Assert.assertThrows;
import android.content.Context;
+import android.frameworks.location.altitude.GetGeoidHeightRequest;
+import android.frameworks.location.altitude.GetGeoidHeightResponse;
import android.location.Location;
import android.location.altitude.AltitudeConverter;
@@ -176,4 +178,20 @@
assertThrows(IllegalArgumentException.class,
() -> mAltitudeConverter.addMslAltitudeToLocation(mContext, location));
}
+
+ @Test
+ public void testGetGeoidHeight_expectedBehavior() throws IOException {
+ GetGeoidHeightRequest request = new GetGeoidHeightRequest();
+ request.latitudeDegrees = -35.334815;
+ request.longitudeDegrees = -45;
+ // Requires data to be loaded from raw assets.
+ GetGeoidHeightResponse response = mAltitudeConverter.getGeoidHeight(mContext, request);
+ assertThat(response.geoidHeightMeters).isWithin(2).of(-5.0622);
+ assertThat(response.geoidHeightErrorMeters).isGreaterThan(0f);
+ assertThat(response.geoidHeightErrorMeters).isLessThan(1f);
+ assertThat(response.expirationDistanceMeters).isWithin(1).of(-6.33);
+ assertThat(response.additionalGeoidHeightErrorMeters).isGreaterThan(0f);
+ assertThat(response.additionalGeoidHeightErrorMeters).isLessThan(1f);
+ assertThat(response.success).isTrue();
+ }
}