Loading cardinal-android/app/src/main/java/earth/maps/cardinal/data/GeoUtils.kt +27 −10 Original line number Original line Diff line number Diff line Loading @@ -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. * * Loading Loading @@ -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) } } /** /** Loading @@ -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( Loading cardinal-android/app/src/main/java/earth/maps/cardinal/data/LatLng.kt +4 −0 Original line number Original line Diff line number Diff line Loading @@ -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 cardinal-android/app/src/main/java/earth/maps/cardinal/routing/RoutingOptions.kt +1 −0 Original line number Original line Diff line number Diff line Loading @@ -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) Loading cardinal-android/app/src/main/java/earth/maps/cardinal/ui/util/AnnotationPlacer.kt 0 → 100644 +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() } } cardinal-android/app/src/test/java/earth/maps/cardinal/data/GeoUtilsTest.kt +60 −0 Original line number Original line Diff line number Diff line Loading @@ -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) } } } Loading
cardinal-android/app/src/main/java/earth/maps/cardinal/data/GeoUtils.kt +27 −10 Original line number Original line Diff line number Diff line Loading @@ -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. * * Loading Loading @@ -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) } } /** /** Loading @@ -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( Loading
cardinal-android/app/src/main/java/earth/maps/cardinal/data/LatLng.kt +4 −0 Original line number Original line Diff line number Diff line Loading @@ -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
cardinal-android/app/src/main/java/earth/maps/cardinal/routing/RoutingOptions.kt +1 −0 Original line number Original line Diff line number Diff line Loading @@ -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) Loading
cardinal-android/app/src/main/java/earth/maps/cardinal/ui/util/AnnotationPlacer.kt 0 → 100644 +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() } }
cardinal-android/app/src/test/java/earth/maps/cardinal/data/GeoUtilsTest.kt +60 −0 Original line number Original line Diff line number Diff line Loading @@ -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) } } }