Loading location/java/android/location/altitude/AltitudeConverter.java +24 −0 Original line number Diff line number Diff line Loading @@ -169,4 +169,28 @@ public final class AltitudeConverter { double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, context, s2CellIds); addMslAltitude(params, s2CellIds, geoidHeightsMeters, location); } /** * Same as {@link #addMslAltitudeToLocation(Context, Location)} except that data will not be * loaded from raw assets. Returns true if a Mean Sea Level altitude is added to the * {@code location}; otherwise, returns false and leaves the {@code location} unchanged. * * @hide */ public boolean addMslAltitudeToLocation(@NonNull Location location) { validate(location); MapParamsProto params = GeoidHeightMap.getParams(); if (params == null) { return false; } long[] s2CellIds = findMapSquare(params, location); double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, s2CellIds); if (geoidHeightsMeters == null) { return false; } addMslAltitude(params, s2CellIds, geoidHeightsMeters, location); return true; } } location/java/com/android/internal/location/altitude/GeoidHeightMap.java +46 −16 Original line number Diff line number Diff line Loading @@ -76,6 +76,17 @@ public final class GeoidHeightMap { } } /** * Same as {@link #getParams(Context)} except that null is returned if the singleton parameter * instance is not yet initialized. */ @Nullable public static MapParamsProto getParams() { synchronized (sLock) { return sParams; } } private static long getCacheKey(@NonNull MapParamsProto params, long s2CellId) { return S2CellIdUtils.getParent(s2CellId, params.cacheTileS2Level); } Loading @@ -99,7 +110,8 @@ public final class GeoidHeightMap { S2TileProto[] tiles = new S2TileProto[len]; for (int i = 0; i < len; i++) { if (s2CellIds[i] != 0) { tiles[i] = tileFunction.getTile(s2CellIds[i]); long cacheKey = getCacheKey(params, s2CellIds[i]); tiles[i] = tileFunction.getTile(cacheKey); } values[i] = Double.NaN; } Loading Loading @@ -208,19 +220,26 @@ public final class GeoidHeightMap { } /** * Returns the geoid heights in meters associated with the map cells identified by * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for a * non-zero ID. * Throws an {@link IllegalArgumentException} if the {@code s2CellIds} has an invalid length or * ID. */ @NonNull public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context, @NonNull long[] s2CellIds) throws IOException { private static void validate(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) { Preconditions.checkArgument(s2CellIds.length == 4); for (long s2CellId : s2CellIds) { Preconditions.checkArgument( s2CellId == 0 || S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level); } } /** * Returns the geoid heights in meters associated with the map cells identified by * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for a * non-zero ID. */ @NonNull public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context, @NonNull long[] s2CellIds) throws IOException { validate(params, s2CellIds); double[] heightsMeters = new double[s2CellIds.length]; if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) { return heightsMeters; Loading @@ -233,6 +252,21 @@ public final class GeoidHeightMap { throw new IOException("Unable to calculate geoid heights from raw assets."); } /** * Same as {@link #readGeoidHeights(MapParamsProto, Context, long[])} except that data will not * be loaded from raw assets. Returns the heights if present for all non-zero IDs; otherwise, * returns null. */ @Nullable 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)) { return heightsMeters; } return null; } /** * Adds to {@code heightsMeters} the geoid heights in meters associated with the map cells * identified by {@code s2CellIds}. Returns true if heights are present for all non-zero IDs; Loading Loading @@ -297,11 +331,7 @@ public final class GeoidHeightMap { mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles); } return s2CellId -> { if (s2CellId == 0) { return null; } long cacheKey = getCacheKey(params, s2CellId); return cacheKey -> { for (int i = 0; i < cacheKeys.length; i++) { if (cacheKeys[i] == cacheKey) { return loadedTiles[i]; Loading @@ -321,8 +351,8 @@ public final class GeoidHeightMap { long[] s2CellIds = new long[numMapCellsPerCacheTile]; double[] values = new double[numMapCellsPerCacheTile]; // Each cache key identifies a different sub-tile of the disk tile. TileFunction diskTileFunction = s2CellId -> diskTile; // Each cache key identifies a different sub-tile of the same disk tile. TileFunction diskTileFunction = cacheKey -> diskTile; for (int i = diskTokenIndex; i < len; i++) { if (!Objects.equals(diskTokens[i], diskTokens[diskTokenIndex]) || loadedTiles[i] != null) { Loading Loading @@ -358,10 +388,10 @@ public final class GeoidHeightMap { } } /** Defines a function-like object to retrieve tiles for map cells. */ /** Defines a function-like object to retrieve tiles for cache keys. */ private interface TileFunction { @Nullable S2TileProto getTile(long s2CellId); S2TileProto getTile(long cacheKey); } } services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java 0 → 100644 +172 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.location.altitude; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import android.content.Context; import android.location.Location; import android.location.altitude.AltitudeConverter; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.MediumTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; @MediumTest @RunWith(AndroidJUnit4.class) public class AltitudeConverterTest { private AltitudeConverter mAltitudeConverter; private Context mContext; @Before public void setUp() { mAltitudeConverter = new AltitudeConverter(); mContext = ApplicationProvider.getApplicationContext(); } @Test public void testAddMslAltitudeToLocation_expectedBehavior() throws IOException { // Interpolates between bffffc, 955554, and 000004. Location location = new Location(""); location.setLatitude(-35.246789); location.setLongitude(-44.962683); location.setAltitude(-1); location.setVerticalAccuracyMeters(1); // Requires data to be loaded from raw assets. assertThat(mAltitudeConverter.addMslAltitudeToLocation(location)).isFalse(); assertThat(location.hasMslAltitude()).isFalse(); assertThat(location.hasMslAltitudeAccuracy()).isFalse(); // Loads data from raw assets. mAltitudeConverter.addMslAltitudeToLocation(mContext, location); assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1076); assertThat(location.getMslAltitudeAccuracyMeters()).isGreaterThan(1f); assertThat(location.getMslAltitudeAccuracyMeters()).isLessThan(1.1f); // Again interpolates between bffffc, 955554, and 000004. location = new Location(""); location.setLatitude(-35.246789); location.setLongitude(-44.962683); location.setAltitude(-1); location.setVerticalAccuracyMeters(1); // Requires no data to be loaded from raw assets. assertThat(mAltitudeConverter.addMslAltitudeToLocation(location)).isTrue(); assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1076); assertThat(location.getMslAltitudeAccuracyMeters()).isGreaterThan(1f); assertThat(location.getMslAltitudeAccuracyMeters()).isLessThan(1.1f); // Results in same outcome. mAltitudeConverter.addMslAltitudeToLocation(mContext, location); assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1076); assertThat(location.getMslAltitudeAccuracyMeters()).isGreaterThan(1f); assertThat(location.getMslAltitudeAccuracyMeters()).isLessThan(1.1f); // Interpolate between 955554, 000004, 00000c, and 95554c - no vertical accuracy. location = new Location(""); location.setLatitude(-35.176383); location.setLongitude(-44.962683); location.setAltitude(-1); location.setVerticalAccuracyMeters(-1); // Invalid vertical accuracy // Requires no data to be loaded from raw assets. assertThat(mAltitudeConverter.addMslAltitudeToLocation(location)).isTrue(); assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1919); assertThat(location.hasMslAltitudeAccuracy()).isFalse(); // Results in same outcome. mAltitudeConverter.addMslAltitudeToLocation(mContext, location); assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1919); assertThat(location.hasMslAltitudeAccuracy()).isFalse(); // Interpolates somewhere else more interesting, i.e., Hawaii. location = new Location(""); location.setLatitude(19.545519); location.setLongitude(-155.998774); location.setAltitude(-1); location.setVerticalAccuracyMeters(1); // Requires data to be loaded from raw assets. assertThat(mAltitudeConverter.addMslAltitudeToLocation(location)).isFalse(); assertThat(location.hasMslAltitude()).isFalse(); assertThat(location.hasMslAltitudeAccuracy()).isFalse(); // Loads data from raw assets. mAltitudeConverter.addMslAltitudeToLocation(mContext, location); assertThat(location.getMslAltitudeMeters()).isWithin(2).of(-19.2359); assertThat(location.getMslAltitudeAccuracyMeters()).isGreaterThan(1f); assertThat(location.getMslAltitudeAccuracyMeters()).isLessThan(1.1f); } @Test public void testAddMslAltitudeToLocation_invalidLatitudeThrows() { Location location = new Location(""); location.setLongitude(-44.962683); location.setAltitude(-1); location.setLatitude(Double.NaN); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); location.setLatitude(91); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); location.setLatitude(-91); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); } @Test public void testAddMslAltitudeToLocation_invalidLongitudeThrows() { Location location = new Location(""); location.setLatitude(-35.246789); location.setAltitude(-1); location.setLongitude(Double.NaN); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); location.setLongitude(181); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); location.setLongitude(-181); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); } @Test public void testAddMslAltitudeToLocation_invalidAltitudeThrows() { Location location = new Location(""); location.setLatitude(-35.246789); location.setLongitude(-44.962683); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); location.setAltitude(Double.NaN); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); location.setAltitude(Double.POSITIVE_INFINITY); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); } } Loading
location/java/android/location/altitude/AltitudeConverter.java +24 −0 Original line number Diff line number Diff line Loading @@ -169,4 +169,28 @@ public final class AltitudeConverter { double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, context, s2CellIds); addMslAltitude(params, s2CellIds, geoidHeightsMeters, location); } /** * Same as {@link #addMslAltitudeToLocation(Context, Location)} except that data will not be * loaded from raw assets. Returns true if a Mean Sea Level altitude is added to the * {@code location}; otherwise, returns false and leaves the {@code location} unchanged. * * @hide */ public boolean addMslAltitudeToLocation(@NonNull Location location) { validate(location); MapParamsProto params = GeoidHeightMap.getParams(); if (params == null) { return false; } long[] s2CellIds = findMapSquare(params, location); double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, s2CellIds); if (geoidHeightsMeters == null) { return false; } addMslAltitude(params, s2CellIds, geoidHeightsMeters, location); return true; } }
location/java/com/android/internal/location/altitude/GeoidHeightMap.java +46 −16 Original line number Diff line number Diff line Loading @@ -76,6 +76,17 @@ public final class GeoidHeightMap { } } /** * Same as {@link #getParams(Context)} except that null is returned if the singleton parameter * instance is not yet initialized. */ @Nullable public static MapParamsProto getParams() { synchronized (sLock) { return sParams; } } private static long getCacheKey(@NonNull MapParamsProto params, long s2CellId) { return S2CellIdUtils.getParent(s2CellId, params.cacheTileS2Level); } Loading @@ -99,7 +110,8 @@ public final class GeoidHeightMap { S2TileProto[] tiles = new S2TileProto[len]; for (int i = 0; i < len; i++) { if (s2CellIds[i] != 0) { tiles[i] = tileFunction.getTile(s2CellIds[i]); long cacheKey = getCacheKey(params, s2CellIds[i]); tiles[i] = tileFunction.getTile(cacheKey); } values[i] = Double.NaN; } Loading Loading @@ -208,19 +220,26 @@ public final class GeoidHeightMap { } /** * Returns the geoid heights in meters associated with the map cells identified by * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for a * non-zero ID. * Throws an {@link IllegalArgumentException} if the {@code s2CellIds} has an invalid length or * ID. */ @NonNull public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context, @NonNull long[] s2CellIds) throws IOException { private static void validate(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) { Preconditions.checkArgument(s2CellIds.length == 4); for (long s2CellId : s2CellIds) { Preconditions.checkArgument( s2CellId == 0 || S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level); } } /** * Returns the geoid heights in meters associated with the map cells identified by * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for a * non-zero ID. */ @NonNull public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context, @NonNull long[] s2CellIds) throws IOException { validate(params, s2CellIds); double[] heightsMeters = new double[s2CellIds.length]; if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) { return heightsMeters; Loading @@ -233,6 +252,21 @@ public final class GeoidHeightMap { throw new IOException("Unable to calculate geoid heights from raw assets."); } /** * Same as {@link #readGeoidHeights(MapParamsProto, Context, long[])} except that data will not * be loaded from raw assets. Returns the heights if present for all non-zero IDs; otherwise, * returns null. */ @Nullable 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)) { return heightsMeters; } return null; } /** * Adds to {@code heightsMeters} the geoid heights in meters associated with the map cells * identified by {@code s2CellIds}. Returns true if heights are present for all non-zero IDs; Loading Loading @@ -297,11 +331,7 @@ public final class GeoidHeightMap { mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles); } return s2CellId -> { if (s2CellId == 0) { return null; } long cacheKey = getCacheKey(params, s2CellId); return cacheKey -> { for (int i = 0; i < cacheKeys.length; i++) { if (cacheKeys[i] == cacheKey) { return loadedTiles[i]; Loading @@ -321,8 +351,8 @@ public final class GeoidHeightMap { long[] s2CellIds = new long[numMapCellsPerCacheTile]; double[] values = new double[numMapCellsPerCacheTile]; // Each cache key identifies a different sub-tile of the disk tile. TileFunction diskTileFunction = s2CellId -> diskTile; // Each cache key identifies a different sub-tile of the same disk tile. TileFunction diskTileFunction = cacheKey -> diskTile; for (int i = diskTokenIndex; i < len; i++) { if (!Objects.equals(diskTokens[i], diskTokens[diskTokenIndex]) || loadedTiles[i] != null) { Loading Loading @@ -358,10 +388,10 @@ public final class GeoidHeightMap { } } /** Defines a function-like object to retrieve tiles for map cells. */ /** Defines a function-like object to retrieve tiles for cache keys. */ private interface TileFunction { @Nullable S2TileProto getTile(long s2CellId); S2TileProto getTile(long cacheKey); } }
services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java 0 → 100644 +172 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.location.altitude; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import android.content.Context; import android.location.Location; import android.location.altitude.AltitudeConverter; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.MediumTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; @MediumTest @RunWith(AndroidJUnit4.class) public class AltitudeConverterTest { private AltitudeConverter mAltitudeConverter; private Context mContext; @Before public void setUp() { mAltitudeConverter = new AltitudeConverter(); mContext = ApplicationProvider.getApplicationContext(); } @Test public void testAddMslAltitudeToLocation_expectedBehavior() throws IOException { // Interpolates between bffffc, 955554, and 000004. Location location = new Location(""); location.setLatitude(-35.246789); location.setLongitude(-44.962683); location.setAltitude(-1); location.setVerticalAccuracyMeters(1); // Requires data to be loaded from raw assets. assertThat(mAltitudeConverter.addMslAltitudeToLocation(location)).isFalse(); assertThat(location.hasMslAltitude()).isFalse(); assertThat(location.hasMslAltitudeAccuracy()).isFalse(); // Loads data from raw assets. mAltitudeConverter.addMslAltitudeToLocation(mContext, location); assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1076); assertThat(location.getMslAltitudeAccuracyMeters()).isGreaterThan(1f); assertThat(location.getMslAltitudeAccuracyMeters()).isLessThan(1.1f); // Again interpolates between bffffc, 955554, and 000004. location = new Location(""); location.setLatitude(-35.246789); location.setLongitude(-44.962683); location.setAltitude(-1); location.setVerticalAccuracyMeters(1); // Requires no data to be loaded from raw assets. assertThat(mAltitudeConverter.addMslAltitudeToLocation(location)).isTrue(); assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1076); assertThat(location.getMslAltitudeAccuracyMeters()).isGreaterThan(1f); assertThat(location.getMslAltitudeAccuracyMeters()).isLessThan(1.1f); // Results in same outcome. mAltitudeConverter.addMslAltitudeToLocation(mContext, location); assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1076); assertThat(location.getMslAltitudeAccuracyMeters()).isGreaterThan(1f); assertThat(location.getMslAltitudeAccuracyMeters()).isLessThan(1.1f); // Interpolate between 955554, 000004, 00000c, and 95554c - no vertical accuracy. location = new Location(""); location.setLatitude(-35.176383); location.setLongitude(-44.962683); location.setAltitude(-1); location.setVerticalAccuracyMeters(-1); // Invalid vertical accuracy // Requires no data to be loaded from raw assets. assertThat(mAltitudeConverter.addMslAltitudeToLocation(location)).isTrue(); assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1919); assertThat(location.hasMslAltitudeAccuracy()).isFalse(); // Results in same outcome. mAltitudeConverter.addMslAltitudeToLocation(mContext, location); assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1919); assertThat(location.hasMslAltitudeAccuracy()).isFalse(); // Interpolates somewhere else more interesting, i.e., Hawaii. location = new Location(""); location.setLatitude(19.545519); location.setLongitude(-155.998774); location.setAltitude(-1); location.setVerticalAccuracyMeters(1); // Requires data to be loaded from raw assets. assertThat(mAltitudeConverter.addMslAltitudeToLocation(location)).isFalse(); assertThat(location.hasMslAltitude()).isFalse(); assertThat(location.hasMslAltitudeAccuracy()).isFalse(); // Loads data from raw assets. mAltitudeConverter.addMslAltitudeToLocation(mContext, location); assertThat(location.getMslAltitudeMeters()).isWithin(2).of(-19.2359); assertThat(location.getMslAltitudeAccuracyMeters()).isGreaterThan(1f); assertThat(location.getMslAltitudeAccuracyMeters()).isLessThan(1.1f); } @Test public void testAddMslAltitudeToLocation_invalidLatitudeThrows() { Location location = new Location(""); location.setLongitude(-44.962683); location.setAltitude(-1); location.setLatitude(Double.NaN); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); location.setLatitude(91); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); location.setLatitude(-91); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); } @Test public void testAddMslAltitudeToLocation_invalidLongitudeThrows() { Location location = new Location(""); location.setLatitude(-35.246789); location.setAltitude(-1); location.setLongitude(Double.NaN); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); location.setLongitude(181); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); location.setLongitude(-181); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); } @Test public void testAddMslAltitudeToLocation_invalidAltitudeThrows() { Location location = new Location(""); location.setLatitude(-35.246789); location.setLongitude(-44.962683); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); location.setAltitude(Double.NaN); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); location.setAltitude(Double.POSITIVE_INFINITY); assertThrows(IllegalArgumentException.class, () -> mAltitudeConverter.addMslAltitudeToLocation(location)); } }