Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4987500b authored by Brian Julian's avatar Brian Julian
Browse files

Adds to AltitudeConverter a method that returns a geoid height at the location...

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
parent a709f956
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -95,6 +95,7 @@ filegroup {
        ":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",
+128 −63
Original line number Diff line number Diff line
@@ -19,9 +19,11 @@ package android.location.altitude;
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 @@ import java.io.IOException;
 * <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 @@ public final class AltitudeConverter {
    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 @@ public final class AltitudeConverter {
    /**
     * 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 @@ public final class AltitudeConverter {
        // 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 @@ public final class AltitudeConverter {
        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 final class AltitudeConverter {
    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 final class AltitudeConverter {
     */
    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;
    }
}
+124 −65

File changed and moved.

Preview size limit exceeded, changes collapsed.

+1 −1
Original line number Diff line number Diff line
@@ -139,6 +139,7 @@ java_library_static {

    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 @@ java_library_static {
    ],

    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",
+18 −0
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import static com.google.common.truth.Truth.assertThat;
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 @@ public class AltitudeConverterTest {
        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();
    }
}