From 10a50f835899c9401f7f611fff2df0eadf599cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20G=C3=B6ransson?= Date: Wed, 20 Mar 2024 14:01:03 +0100 Subject: [PATCH] Base animation duration on actual distance estimation --- .../mullvadvpn/lib/map/CameraAnimation.kt | 8 +++-- .../mullvadvpn/lib/map/internal/Constants.kt | 5 ++-- .../lib/map/internal/MapGLRenderer.kt | 4 +-- .../net/mullvad/mullvadvpn/model/LatLong.kt | 29 ++++++++++++++++++- .../mullvad/mullvadvpn/model/LatLongTest.kt | 25 ++++++++++++++-- 5 files changed, 60 insertions(+), 11 deletions(-) diff --git a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/CameraAnimation.kt b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/CameraAnimation.kt index 00fcf97ab755..454e6c45160c 100644 --- a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/CameraAnimation.kt +++ b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/CameraAnimation.kt @@ -38,7 +38,7 @@ fun animatedCameraPosition( } val distance = - remember(targetCameraLocation) { targetCameraLocation.distanceTo(previousLocation).toInt() } + remember(targetCameraLocation) { targetCameraLocation.seppDistanceTo(previousLocation) } val duration = distance.toAnimationDuration() val longitudeAnimation = remember { Animatable(targetCameraLocation.longitude.value) } @@ -97,5 +97,7 @@ fun animatedCameraPosition( ) } -private fun Int.toAnimationDuration() = - (this * DISTANCE_DURATION_SCALE_FACTOR).coerceIn(MIN_ANIMATION_MILLIS, MAX_ANIMATION_MILLIS) +private fun Float.toAnimationDuration(): Int = + (this * DISTANCE_DURATION_SCALE_FACTOR) + .toInt() + .coerceIn(MIN_ANIMATION_MILLIS, MAX_ANIMATION_MILLIS) diff --git a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/Constants.kt b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/Constants.kt index dfd80d547d18..a0aacc34c670 100644 --- a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/Constants.kt +++ b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/Constants.kt @@ -4,9 +4,10 @@ internal const val VERTEX_COMPONENT_SIZE = 3 internal const val COLOR_COMPONENT_SIZE = 4 internal const val MATRIX_SIZE = 16 -// Constant what will talk the distance in LatLng multiply it to determine the animation duration, +// Constant what will take the distance in km between two LatLong, multiply it to determine the +// animation duration, // the result is then confined to the MIN_ANIMATION_MILLIS and MAX_ANIMATION_MILLIS -internal const val DISTANCE_DURATION_SCALE_FACTOR = 20 +internal const val DISTANCE_DURATION_SCALE_FACTOR = 0.4f internal const val MIN_ANIMATION_MILLIS = 1300 internal const val MAX_ANIMATION_MILLIS = 2500 // The cut off where we go from a short animation (camera pans) to a far animation (camera pans + diff --git a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/MapGLRenderer.kt b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/MapGLRenderer.kt index bf44e5ee140f..41ac903fb17c 100644 --- a/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/MapGLRenderer.kt +++ b/android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/MapGLRenderer.kt @@ -13,7 +13,7 @@ import net.mullvad.mullvadvpn.lib.map.data.LocationMarkerColors import net.mullvad.mullvadvpn.lib.map.data.MapViewState import net.mullvad.mullvadvpn.lib.map.internal.shapes.Globe import net.mullvad.mullvadvpn.lib.map.internal.shapes.LocationMarker -import net.mullvad.mullvadvpn.model.COMPLETE_ANGLE +import net.mullvad.mullvadvpn.model.toRadians internal class MapGLRenderer(private val resources: Resources) : GLSurfaceView.Renderer { @@ -79,8 +79,6 @@ internal class MapGLRenderer(private val resources: Resources) : GLSurfaceView.R } } - private fun Float.toRadians() = this * Math.PI.toFloat() / (COMPLETE_ANGLE / 2) - private fun toOffsetY(cameraPosition: CameraPosition): Float { val percent = cameraPosition.verticalBias val z = cameraPosition.zoom - 1f diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LatLong.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LatLong.kt index ae047130e8ba..d6749a16a2d2 100644 --- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LatLong.kt +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/LatLong.kt @@ -1,11 +1,13 @@ package net.mullvad.mullvadvpn.model +import kotlin.math.cos import kotlin.math.pow import kotlin.math.sqrt +import net.mullvad.mullvadvpn.model.Latitude.Companion.mean data class LatLong(val latitude: Latitude, val longitude: Longitude) { - fun distanceTo(other: LatLong): Float = + fun degreeDistanceTo(other: LatLong): Float = sqrt( latitude.distanceTo(other.latitude).pow(2f) + (longitude.distanceTo(other.longitude).pow(2f)) @@ -16,6 +18,31 @@ data class LatLong(val latitude: Latitude, val longitude: Longitude) { operator fun minus(other: LatLong) = LatLong(latitude - other.latitude, longitude - other.longitude) + + /** + * Calculate the distance between two points on the earth's surface using the spherical earth + * projected to a plane. ( This method has some drawbacks and shortcomings for extreme values + * closer to the Poles but should be good enough for our use case. ) Reference: + * https://en.wikipedia.org/wiki/Geographical_distance#Spherical_Earth_projected_to_a_plane + * + * @param other the other point to calculate the distance to. + * @return the estimated distance in kilometers. + */ + fun seppDistanceTo(other: LatLong): Float = + EARTH_RADIUS * + sqrt( + latitude.distanceTo(other.latitude).toRadians().pow(2) + + (cos(mean(latitude, other.latitude).value.toRadians()) * + longitude.distanceTo(other.longitude).toRadians()) + .pow(2) + ) + + companion object { + // Average radius of the earth in kilometers + const val EARTH_RADIUS = 6371.009f + } } const val COMPLETE_ANGLE = 360f + +fun Float.toRadians() = this * Math.PI.toFloat() / (COMPLETE_ANGLE / 2) diff --git a/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatLongTest.kt b/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatLongTest.kt index 6644e25e82f5..43a1f297ecdf 100644 --- a/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatLongTest.kt +++ b/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/model/LatLongTest.kt @@ -2,12 +2,13 @@ package net.mullvad.mullvadvpn.model import kotlin.math.sqrt import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test class LatLongTest { @Test - fun `distance between two LatLong should be same as hypotenuse`() { + fun `degree distance between two LatLong should be same as hypotenuse`() { val latLong1 = LatLong(Latitude(30f), Longitude(40f)) val latLong2 = LatLong(Latitude(-40f), Longitude(170f)) @@ -15,6 +16,26 @@ class LatLongTest { val longDiff = latLong1.longitude.distanceTo(latLong2.longitude) val hypotenuse = sqrt(latDiff * latDiff + longDiff * longDiff) - assertEquals(hypotenuse, latLong1.distanceTo(latLong2)) + assertEquals(hypotenuse, latLong1.degreeDistanceTo(latLong2)) + } + + @Test + fun `ensure seppDistance respects lateral value is respected`() { + // Malmö & New York is a shorter distance than Malmö & Johannesburg, but the degree + // difference is larger since they are at a higher latitude. + + // Malmo 55.6050° N, 13.0038° E + val malmo = LatLong(Latitude(55.6050f), Longitude(13.0038f)) + + // New York 40.7128° N, 74.0060° W + val newYork = LatLong(Latitude(40.7128f), Longitude(-74.0060f)) + + // Johannesburg 26.2041° S, 28.0473° E + val johannesburg = LatLong(Latitude(-26.2041f), Longitude(28.0473f)) + + val malmoToNewYork = malmo.seppDistanceTo(newYork) + val malmoToJohannesburg = malmo.seppDistanceTo(johannesburg) + + assertTrue { malmoToNewYork < malmoToJohannesburg } } }