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

Commit 2c3265e3 authored by Ellen Poe's avatar Ellen Poe
Browse files

feat: add GeoUtils.fastDistance and draft AnnotationPlacer

parent 313bb770
Loading
Loading
Loading
Loading
+27 −10
Original line number Original line Diff line number Diff line
@@ -30,12 +30,11 @@ import kotlin.math.sqrt
 */
 */
object GeoUtils {
object GeoUtils {


    private const val EARTH_RADIUS_METERS = 6371000.0
    private const val METERS_TO_KILOMETERS = 1000.0
    private const val METERS_TO_KILOMETERS = 1000.0
    private const val METERS_TO_MILES = 1609.34
    private const val METERS_TO_MILES = 1609.34
    private const val METERS_TO_FEET = 3.28084
    private const val METERS_TO_FEET = 3.28084


    private const val SHORT_DISTANCE_THRESHOLD_METERS = 200.0

    /**
    /**
     * Formats a distance in meters to a human-readable string based on the unit preference.
     * Formats a distance in meters to a human-readable string based on the unit preference.
     *
     *
@@ -112,10 +111,31 @@ object GeoUtils {
        val a = sin(deltaLat / 2).pow(2) + cos(lat1) * cos(lat2) * sin(deltaLon / 2).pow(2)
        val a = sin(deltaLat / 2).pow(2) + cos(lat1) * cos(lat2) * sin(deltaLon / 2).pow(2)
        val c = 2 * atan2(sqrt(a), sqrt(1 - a))
        val c = 2 * atan2(sqrt(a), sqrt(1 - a))


        // Earth's radius in meters
        return EARTH_RADIUS_METERS * c
        val earthRadius = 6371000.0
    }


        return earthRadius * c
    /**
     * Calculates the distance between two points using a locally cartesian approximation.
     * This is faster than haversine for short distances but less accurate for long distances.
     * Uses the mean of the two latitudes for the longitude scaling factor.
     *
     * @param latLng1 First point
     * @param latLng2 Second point
     * @return Distance in meters
     */
    fun fastDistance(latLng1: LatLng, latLng2: LatLng): Double {
        // Calculate mean latitude for longitude scaling
        val meanLat = (latLng1.latitude + latLng2.latitude) / 2.0
        val meanLatRad = Math.toRadians(meanLat)

        // Convert latitude and longitude differences to meters
        val latDiff = (latLng2.latitude - latLng1.latitude) * Math.toRadians(EARTH_RADIUS_METERS)
        val lonDiff = (latLng2.longitude - latLng1.longitude) * Math.toRadians(
            EARTH_RADIUS_METERS * cos(meanLatRad)
        )

        // Calculate cartesian distance
        return sqrt(latDiff * latDiff + lonDiff * lonDiff)
    }
    }


    /**
    /**
@@ -126,13 +146,10 @@ object GeoUtils {
     * @return A BoundingBox representing the area around the center point
     * @return A BoundingBox representing the area around the center point
     */
     */
    fun createBoundingBoxAroundPoint(center: LatLng, radiusMeters: Double): BoundingBox {
    fun createBoundingBoxAroundPoint(center: LatLng, radiusMeters: Double): BoundingBox {
        // Earth's radius in meters
        val earthRadius = 6371000.0

        // Calculate the approximate delta in degrees for the given radius
        // Calculate the approximate delta in degrees for the given radius
        val latDelta = Math.toDegrees(radiusMeters / earthRadius)
        val latDelta = Math.toDegrees(radiusMeters / EARTH_RADIUS_METERS)
        val lonDelta =
        val lonDelta =
            Math.toDegrees(radiusMeters / (earthRadius * cos(Math.toRadians(center.latitude))))
            Math.toDegrees(radiusMeters / (EARTH_RADIUS_METERS * cos(Math.toRadians(center.latitude))))


        // Create the bounding box with north, south, east, west boundaries
        // Create the bounding box with north, south, east, west boundaries
        return BoundingBox(
        return BoundingBox(
+4 −0
Original line number Original line Diff line number Diff line
@@ -25,4 +25,8 @@ data class LatLng(
    fun distanceTo(other: LatLng): Double {
    fun distanceTo(other: LatLng): Double {
        return GeoUtils.haversineDistance(this, other)
        return GeoUtils.haversineDistance(this, other)
    }
    }

    fun fastDistanceTo(other: LatLng): Double {
        return GeoUtils.fastDistance(this, other)
    }
}
}
 No newline at end of file
+1 −0
Original line number Original line Diff line number Diff line
@@ -36,6 +36,7 @@ abstract class RoutingOptions {
        val gson = GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
        val gson = GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
            .create()
            .create()
        val wrapper = object {
        val wrapper = object {
            val alternates = 5
            val costing_options = mapOf(costingType to this@RoutingOptions)
            val costing_options = mapOf(costingType to this@RoutingOptions)
        }
        }
        return gson.toJson(wrapper)
        return gson.toJson(wrapper)
+12 −0
Original line number Original line Diff line number Diff line
package earth.maps.cardinal.ui.util

import earth.maps.cardinal.data.LatLng
import uniffi.ferrostar.GeographicCoordinate
import javax.inject.Inject

class AnnotationPlacer @Inject constructor() {

    fun placeAnnotations(routes: List<List<LatLng>>): List<LatLng> {
        return emptyList()
    }
}
+60 −0
Original line number Original line Diff line number Diff line
@@ -124,4 +124,64 @@ class GeoUtilsTest {
        assertEquals(center.longitude + lonDelta, boundingBox.east, 0.01)
        assertEquals(center.longitude + lonDelta, boundingBox.east, 0.01)
        assertEquals(center.longitude - lonDelta, boundingBox.west, 0.01)
        assertEquals(center.longitude - lonDelta, boundingBox.west, 0.01)
    }
    }

    @Test
    fun fastDistance_samePoint() {
        val latLng1 = LatLng(37.7749, -122.4194) // San Francisco
        val latLng2 = LatLng(37.7749, -122.4194) // Same point
        val expectedDistance = 0.0
        val actualDistance = GeoUtils.fastDistance(latLng1, latLng2)
        assertEquals(expectedDistance, actualDistance, 0.01)
    }

    @Test
    fun fastDistance_shortDistance_equals_haversine() {
        // Short distance between two nearby points in San Francisco
        val point1 = LatLng(37.7749, -122.4194)
        val point2 = LatLng(37.7750, -122.4195) // About 14m apart
        val expectedDistance = GeoUtils.haversineDistance(point1, point2)
        val actualDistance = GeoUtils.fastDistance(point1, point2)
        assertEquals(expectedDistance, actualDistance, 0.01)
    }


    @Test
    fun fastDistance_shortDistance() {
        // Short distance between two nearby points in San Francisco
        val point1 = LatLng(37.7749, -122.4194)
        val point2 = LatLng(37.7750, -122.4195) // About 14m apart
        val expectedDistance = 14.173617226379271
        val actualDistance = GeoUtils.fastDistance(point1, point2)
        assertEquals(expectedDistance, actualDistance, 0.01)
    }

    @Test
    fun fastDistance_mediumDistance() {
        // Distance between San Francisco and Oakland (across the bay)
        val sf = LatLng(37.7749, -122.4194)
        val oakland = LatLng(37.8044, -122.2711)
        val expectedDistance = 13438.14841287118
        val actualDistance = GeoUtils.fastDistance(sf, oakland)
        assertEquals(expectedDistance, actualDistance, 0.01)
    }

    @Test
    fun fastDistance_longDistance() {
        // Distance between New York City and Los Angeles
        val nyc = LatLng(40.7128, -74.0060)
        val la = LatLng(34.0522, -118.2437)
        val expectedDistance = 3978193.533035539
        val actualDistance = GeoUtils.fastDistance(nyc, la)
        assertEquals(expectedDistance, actualDistance, 0.01)
    }

    @Test
    fun fastDistance_highLatitude() {
        // Test at high latitude (near Arctic Circle)
        val point1 = LatLng(66.5, 0.0)
        val point2 = LatLng(66.6, 0.1)
        val expectedDistance = 11967.60736579976
        val actualDistance = GeoUtils.fastDistance(point1, point2)
        assertEquals(expectedDistance, actualDistance, 0.01)
    }
}
}