GeoPoint
GeoPoint represents a geographic coordinate with latitude, longitude, and optional altitude. It is the fundamental building block for positioning markers, circles, and other map overlays.
Interface and Implementation
Section titled “Interface and Implementation”GeoPoint Interface
Section titled “GeoPoint Interface”interface GeoPoint { val latitude: Double val longitude: Double val altitude: Double?}GeoPointImpl
Section titled “GeoPointImpl”The main implementation provides immutable geographic coordinates:
data class GeoPointImpl( override val latitude: Double, override val longitude: Double, override val altitude: Double = 0.0) : GeoPointCreation Methods
Section titled “Creation Methods”Factory Methods
Section titled “Factory Methods”// Standard creation - latitude first (common pattern)GeoPointImpl.fromLatLong(37.7749, -122.4194)
// Alternative - longitude firstGeoPointImpl.fromLongLat(-122.4194, 37.7749)
// From existing GeoPointGeoPointImpl.from(existingGeoPoint)
// Direct constructorGeoPointImpl(latitude = 37.7749, longitude = -122.4194, altitude = 100.0)Usage Examples
Section titled “Usage Examples”// Basic marker positioningval sanFrancisco = GeoPointImpl.fromLatLong(37.7749, -122.4194)
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapViewMapView(state = mapViewState) { Marker( position = sanFrancisco, icon = DefaultIcon(label = "SF") )}// With altitude for 3D positioningval mountEverest = GeoPointImpl( latitude = 27.9881, longitude = 86.9250, altitude = 8848.0 // meters)Coordinate System
Section titled “Coordinate System”Latitude
Section titled “Latitude”- Range: -90.0 to 90.0 degrees
- -90.0: South Pole
- 0.0: Equator
- 90.0: North Pole
Longitude
Section titled “Longitude”- Range: -180.0 to 180.0 degrees
- -180.0: International Date Line (west side)
- 0.0: Prime Meridian (Greenwich)
- 180.0: International Date Line (east side)
Altitude
Section titled “Altitude”- Optional: Can be
nullor0.0for 2D positioning - Units: Meters above sea level
- Usage: 3D positioning, elevation data
Validation and Normalization
Section titled “Validation and Normalization”Validation
Section titled “Validation”fun GeoPoint.isValid(): Boolean = latitude in -90.0..90.0 && longitude in -180.0..180.0
// Usageval point = GeoPointImpl.fromLatLong(37.7749, -122.4194)if (point.isValid()) { // Use the point}Normalization
Section titled “Normalization”fun GeoPoint.normalize(): GeoPointImpl
// Usageval invalidPoint = GeoPointImpl.fromLatLong(100.0, 200.0) // Invalid coordinatesval validPoint = invalidPoint.normalize() // Clamped to valid rangeDistance and Bearing Calculations
Section titled “Distance and Bearing Calculations”Distance Between Points
Section titled “Distance Between Points”fun GeoPoint.distanceTo(other: GeoPoint): Double
// Usageval sf = GeoPointImpl.fromLatLong(37.7749, -122.4194)val nyc = GeoPointImpl.fromLatLong(40.7128, -74.0060)
val distanceMeters = sf.distanceTo(nyc)val distanceKm = distanceMeters / 1000.0
println("Distance SF to NYC: ${distanceKm}km")Bearing (Direction)
Section titled “Bearing (Direction)”fun GeoPoint.headingTo(other: GeoPoint): Double
// Usageval bearing = sf.headingTo(nyc) // Returns bearing in degrees (0-360)println("Bearing from SF to NYC: ${bearing}°")Offset Point
Section titled “Offset Point”fun GeoPoint.offset(distance: Double, heading: Double): GeoPointImpl
// Usageval sf = GeoPointImpl.fromLatLong(37.7749, -122.4194)val pointNorth = sf.offset(1000.0, 0.0) // 1km north of SFval pointEast = sf.offset(1000.0, 90.0) // 1km east of SFInterpolation
Section titled “Interpolation”Spherical Interpolation
Section titled “Spherical Interpolation”Considers Earth’s curvature (recommended for geographic calculations):
fun GeoPoint.interpolateTo(other: GeoPoint, fraction: Double): GeoPointImpl
// Usageval sf = GeoPointImpl.fromLatLong(37.7749, -122.4194)val nyc = GeoPointImpl.fromLatLong(40.7128, -74.0060)
val halfway = sf.interpolateTo(nyc, 0.5) // Midpoint considering Earth's curvatureval quarterWay = sf.interpolateTo(nyc, 0.25)Linear Interpolation
Section titled “Linear Interpolation”Ignores Earth’s curvature (faster but less accurate for long distances):
fun GeoPoint.linearInterpolateTo(other: GeoPoint, fraction: Double): GeoPointImpl
// Usageval linearMidpoint = sf.linearInterpolateTo(nyc, 0.5)Practical Examples
Section titled “Practical Examples”Route Waypoints
Section titled “Route Waypoints”@Composablefun RouteWithWaypoints() { val start = GeoPointImpl.fromLatLong(37.7749, -122.4194) val end = GeoPointImpl.fromLatLong(37.7849, -122.4094)
// Create waypoints along the route val waypoints = (0..10).map { i -> start.interpolateTo(end, i / 10.0) }
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView MapView(state = mapViewState) { // Draw route Polyline( points = waypoints, strokeColor = Color.Blue, strokeWidth = 3.dp )
// Waypoint markers waypoints.forEachIndexed { index, point -> Marker( position = point, icon = DefaultIcon( label = "$index", scale = 0.7f ) ) } }}Proximity Detection
Section titled “Proximity Detection”@Composablefun ProximityExample() { val userLocation = GeoPointImpl.fromLatLong(37.7749, -122.4194) val poi = GeoPointImpl.fromLatLong(37.7759, -122.4184)
val distance = userLocation.distanceTo(poi) val isNearby = distance < 100.0 // Within 100 meters
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView MapView(state = mapViewState) { Marker( position = userLocation, icon = DefaultIcon( fillColor = Color.Blue, label = "You" ) )
Marker( position = poi, icon = DefaultIcon( fillColor = if (isNearby) Color.Green else Color.Red, label = "POI" ) )
// Proximity circle Circle( center = poi, radiusMeters = 100.0, strokeColor = Color.Green.copy(alpha = 0.5f), fillColor = Color.Green.copy(alpha = 0.1f) ) }}Dynamic Position Updates
Section titled “Dynamic Position Updates”@Composablefun MovingMarkerExample() { val path = listOf( GeoPointImpl.fromLatLong(37.7749, -122.4194), GeoPointImpl.fromLatLong(37.7759, -122.4184), GeoPointImpl.fromLatLong(37.7769, -122.4174) )
var currentPosition by remember { mutableStateOf(path.first()) } var pathIndex by remember { mutableStateOf(0) }
LaunchedEffect(Unit) { while (true) { delay(2000) pathIndex = (pathIndex + 1) % path.size currentPosition = path[pathIndex] } }
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView MapView(state = mapViewState) { // Moving marker Marker( position = currentPosition, icon = DefaultIcon( fillColor = Color.Red, label = "📍" ) )
// Path preview Polyline( points = path, strokeColor = Color.Gray, strokeWidth = 2.dp ) }}Equality and Hashing
Section titled “Equality and Hashing”GeoPointImpl uses tolerance-based equality to handle floating-point precision issues:
val point1 = GeoPointImpl.fromLatLong(37.7749, -122.4194)val point2 = GeoPointImpl.fromLatLong(37.7749000001, -122.4194000001)
println(point1 == point2) // true - within toleranceThe tolerance is set to 1e-7 degrees, which is approximately 1 centimeter at the equator.
URL Formatting
Section titled “URL Formatting”fun toUrlValue(precision: Int = 6): String
// Usageval point = GeoPointImpl.fromLatLong(37.7749, -122.4194)val urlString = point.toUrlValue() // "37.774900,-122.419400"val preciseString = point.toUrlValue(precision = 8) // More decimal placesBest Practices
Section titled “Best Practices”- Use Factory Methods: Prefer
fromLatLong()for clarity about parameter order - Validate Input: Check coordinates are within valid ranges when accepting user input
- Consider Earth’s Curvature: Use spherical interpolation for geographic accuracy
- Performance: Use linear interpolation for frequent calculations where precision is less critical
- Altitude: Include altitude for 3D applications, use 0.0 for 2D mapping
- Immutability: GeoPointImpl is immutable - create new instances for different coordinates