summaryrefslogtreecommitdiffhomepage
path: root/android/lib/model/src
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson@mullvad.net>2025-08-29 14:51:10 +0200
committerDavid Göransson <david.goransson@mullvad.net>2025-09-01 08:34:16 +0200
commite02d460164fe09d5cf085cdcb3f40a9251bae633 (patch)
treecbad368a9d30876f24631a6548e7d32e2e2c9524 /android/lib/model/src
parent4f03db1de3224d666cf6fc5ab255f3629435a59a (diff)
downloadmullvadvpn-interactive-maps.tar.xz
mullvadvpn-interactive-maps.zip
Add interactive mapsinteractive-maps
Diffstat (limited to 'android/lib/model/src')
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/LatLong.kt2
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt1
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/map/Ray.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/map/Sphere.kt7
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/map/Vector3.kt89
-rw-r--r--android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/lib/model/map/Vector3Test.kt69
6 files changed, 171 insertions, 0 deletions
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/LatLong.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/LatLong.kt
index 19f757ffc3..a3cbf13761 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/LatLong.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/LatLong.kt
@@ -46,3 +46,5 @@ data class LatLong(val latitude: Latitude, val longitude: Longitude) {
const val COMPLETE_ANGLE = 360f
fun Float.toRadians() = this * Math.PI.toFloat() / (COMPLETE_ANGLE / 2)
+
+fun Float.toDegrees() = this * ((COMPLETE_ANGLE / 2) / Math.PI.toFloat())
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt
index b1df67fea6..3ea0b48cbb 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt
@@ -71,6 +71,7 @@ sealed interface RelayItem {
override val id: GeoLocationId.City,
override val name: String,
val relays: List<Relay>,
+ val latLong: LatLong,
) : Location {
override val active = relays.any { it.active }
override val hasChildren: Boolean = relays.isNotEmpty()
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/map/Ray.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/map/Ray.kt
new file mode 100644
index 0000000000..2e8eb236c8
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/map/Ray.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.model.map
+
+data class Ray(val origin: Vector3, val direction: Vector3)
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/map/Sphere.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/map/Sphere.kt
new file mode 100644
index 0000000000..73a759e938
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/map/Sphere.kt
@@ -0,0 +1,7 @@
+package net.mullvad.mullvadvpn.lib.model.map
+
+data class Sphere(val center: Vector3, val radius: Float) {
+ companion object {
+ const val RADIUS = 1f
+ }
+}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/map/Vector3.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/map/Vector3.kt
new file mode 100644
index 0000000000..e1c3901a11
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/map/Vector3.kt
@@ -0,0 +1,89 @@
+package net.mullvad.mullvadvpn.lib.model.map
+
+import kotlin.math.acos
+import kotlin.math.atan2
+import kotlin.math.cos
+import kotlin.math.sin
+import kotlin.math.sqrt
+import net.mullvad.mullvadvpn.lib.model.LatLong
+import net.mullvad.mullvadvpn.lib.model.Latitude
+import net.mullvad.mullvadvpn.lib.model.Longitude
+import net.mullvad.mullvadvpn.lib.model.toDegrees
+import net.mullvad.mullvadvpn.lib.model.toRadians
+
+data class Vector3(val x: Float, val y: Float, val z: Float) {
+ fun dot(other: Vector3): Float {
+ return x * other.x + y * other.y + z * other.z
+ }
+
+ operator fun minus(other: Vector3): Vector3 {
+ return Vector3(x - other.x, y - other.y, z - other.z)
+ }
+
+ operator fun times(scalar: Float): Vector3 {
+ return Vector3(x * scalar, y * scalar, z * scalar)
+ }
+
+ operator fun plus(other: Vector3): Vector3 {
+ return Vector3(x + other.x, y + other.y, z + other.z)
+ }
+
+ fun normalize(): Vector3 {
+ val length = sqrt(x * x + y * y + z * z)
+ return Vector3(x / length, y / length, z / length)
+ }
+
+ fun distanceTo(other: Vector3): Float {
+ val dx = x - other.x
+ val dy = y - other.y
+ val dz = z - other.z
+ return sqrt(dx * dx + dy * dy + dz * dz)
+ }
+}
+
+fun Vector3.rotateAroundX(degrees: Float): Vector3 {
+ val radians = degrees.toRadians()
+ val cosTheta = cos(radians)
+ val sinTheta = sin(radians)
+
+ val newY = cosTheta * y - sinTheta * z
+ val newZ = sinTheta * y + cosTheta * z
+
+ return Vector3(x, -newY, newZ)
+}
+
+fun Vector3.rotateAroundY(degrees: Float): Vector3 {
+ val radians = degrees.toRadians()
+ val cosTheta = cos(radians)
+ val sinTheta = sin(radians)
+
+ val newX = cosTheta * x + sinTheta * z
+ val newZ = -sinTheta * x + cosTheta * z
+
+ return Vector3(newX, y, -newZ)
+}
+
+fun Vector3.toLatLng(): LatLong {
+ // phi
+ val lat = acos(y / Sphere.RADIUS)
+
+ // theta
+ val lon = atan2(x, z)
+
+ return LatLong(
+ // This worked for some reason (camera starts at lat 90!)
+ Latitude.fromFloat(90f - lat.toDegrees()),
+ Longitude.fromFloat(-lon.toDegrees()),
+ )
+}
+
+fun LatLong.toVector3(): Vector3 {
+ val phi = this.latitude.value.toRadians()
+ val theta = this.longitude.value.toRadians()
+
+ val x = -Sphere.RADIUS * cos(phi) * sin(theta)
+ val y = Sphere.RADIUS * sin(phi)
+ val z = Sphere.RADIUS * cos(phi) * cos(theta)
+
+ return Vector3(x, y, z)
+}
diff --git a/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/lib/model/map/Vector3Test.kt b/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/lib/model/map/Vector3Test.kt
new file mode 100644
index 0000000000..c9f35692bf
--- /dev/null
+++ b/android/lib/model/src/test/kotlin/net/mullvad/mullvadvpn/lib/model/map/Vector3Test.kt
@@ -0,0 +1,69 @@
+package net.mullvad.mullvadvpn.lib.model.map
+
+import kotlin.test.assertEquals
+import net.mullvad.mullvadvpn.lib.model.LatLong
+import net.mullvad.mullvadvpn.lib.model.Latitude
+import net.mullvad.mullvadvpn.lib.model.Longitude
+import org.junit.jupiter.api.Test
+
+class Vector3Test {
+ @Test
+ fun `Y-axis center test`() {
+ assertVector3Equals(Y_POSITIVE_CENTER.toVector3(), Vector3(0f, 1f, 0f))
+ assertVector3Equals(Y_NEGATIVE_CENTER.toVector3(), Vector3(0f, -1f, 0f))
+ }
+
+ @Test
+ fun `Z-axis center test`() {
+ assertVector3Equals(Z_POSITIVE_CENTER.toVector3(), Vector3(0f, 0f, 1f))
+ assertVector3Equals(Z_NEGATIVE_CENTER.toVector3(), Vector3(0f, 0f, -1f))
+ }
+
+ @Test
+ fun `X-axis center test`() {
+ assertVector3Equals(X_POSITIVE_CENTER.toVector3(), Vector3(-1f, 0f, 0f))
+ assertVector3Equals(X_NEGATIVE_CENTER.toVector3(), Vector3(1f, 0f, 0f))
+ }
+
+ @Test
+ fun `Y-axis center to LatLng test`() {
+ assertLatLngEquals(Vector3(0f, 1f, 0f).toLatLng(), Y_POSITIVE_CENTER)
+ assertLatLngEquals(Vector3(0f, -1f, 0f).toLatLng(), Y_NEGATIVE_CENTER)
+ }
+
+ @Test
+ fun `Z-axis center to LatLng test`() {
+ assertLatLngEquals(Vector3(0f, 0f, 1f).toLatLng(), Z_POSITIVE_CENTER)
+ assertLatLngEquals(Vector3(0f, 0f, -1f).toLatLng(), Z_NEGATIVE_CENTER)
+ }
+
+ @Test
+ fun `X-axis center to LatLng test`() {
+ assertLatLngEquals(Vector3(-1f, 0f, 0f).toLatLng(), X_POSITIVE_CENTER)
+ assertLatLngEquals(Vector3(1f, 0f, 0f).toLatLng(), X_NEGATIVE_CENTER)
+ }
+
+ companion object {
+ // NORTH POLE
+ val Y_POSITIVE_CENTER = LatLong(Latitude(90f), Longitude(0f))
+ // SOUTH POLE
+ val Y_NEGATIVE_CENTER = LatLong(Latitude(-90f), Longitude(0f))
+
+ val Z_POSITIVE_CENTER = LatLong(Latitude(0f), Longitude(0f))
+ val Z_NEGATIVE_CENTER = LatLong(Latitude(0f), Longitude.fromFloat(180f))
+
+ val X_NEGATIVE_CENTER = LatLong(Latitude(0f), Longitude.fromFloat(-90f))
+ val X_POSITIVE_CENTER = LatLong(Latitude(0f), Longitude.fromFloat(90f))
+ }
+
+ fun assertVector3Equals(expected: Vector3, actual: Vector3) {
+ assertEquals(expected.x, actual.x, 0.0001f)
+ assertEquals(expected.y, actual.y, 0.0001f)
+ assertEquals(expected.z, actual.z, 0.0001f)
+ }
+
+ fun assertLatLngEquals(expected: LatLong, actual: LatLong) {
+ assertEquals(expected.latitude.distanceTo(actual.latitude), 0f, 0.0001f)
+ assertEquals(expected.longitude.distanceTo(actual.longitude), 0f, 0.0001f)
+ }
+}