package com.example.aprs import kotlin.math.floor import kotlin.math.abs import kotlin.math.sin import kotlin.math.cos import kotlin.math.atan import kotlin.math.sqrt import kotlin.math.PI import kotlin.math.ln object LocationUtils { // Convert decimal degrees to APRS position format: DDMM.MM N / DDDMM.MM E fun toAprs(lat: Double, lon: Double): String { val latHem = if (lat >= 0) "N" else "S" val lonHem = if (lon >= 0) "E" else "W" val latAbs = abs(lat) val lonAbs = abs(lon) val latDeg = floor(latAbs).toInt() val latMin = (latAbs - latDeg) * 60.0 val lonDeg = floor(lonAbs).toInt() val lonMin = (lonAbs - lonDeg) * 60.0 return String.format( "%02d%05.2f%s/%03d%05.2f%s", latDeg, latMin, latHem, lonDeg, lonMin, lonHem ) } // Maidenhead locator (6 chars) from lat/lon fun toMaidenhead(lat: Double, lon: Double): String { var adjLon = lon + 180.0 var adjLat = lat + 90.0 val fieldLon = (adjLon / 20.0).toInt() val fieldLat = (adjLat / 10.0).toInt() val squareLon = ((adjLon % 20) / 2).toInt() val squareLat = ((adjLat % 10) / 1).toInt() val subsLon = (((adjLon - fieldLon * 20 - squareLon * 2) * 60) / 5).toInt() val subsLat = (((adjLat - fieldLat * 10 - squareLat * 1) * 60) / 2.5).toInt() val a = 'A'.code val fieldChars = charArrayOf((a + fieldLon).toChar(), (a + fieldLat).toChar()) val squareChars = charArrayOf(('0'.code + squareLon).toChar(), ('0'.code + squareLat).toChar()) val subsChars = charArrayOf((a + subsLon).toChar(), (a + subsLat).toChar()) return String(charArrayOf(fieldChars[0], fieldChars[1], squareChars[0], squareChars[1], subsChars[0], subsChars[1])) } // Convert lat/lon to UTM coordinates fun toUTM(lat: Double, lon: Double): String { val k0 = 0.9996 val a = 6378137.0 val eSquared = 0.00669438 val e = sqrt(eSquared) val ePrimeSquared = eSquared / (1 - eSquared) val latRad = lat * PI / 180.0 val lonRad = lon * PI / 180.0 val zone = ((lon + 180) / 6).toInt() + 1 val lonOrigin = (zone - 1) * 6 - 180 + 3 val lonOriginRad = lonOrigin * PI / 180.0 val n = a / sqrt(1 - eSquared * sin(latRad) * sin(latRad)) val T = tan(latRad) * tan(latRad) val C = ePrimeSquared * cos(latRad) * cos(latRad) val A = cos(latRad) * (lonRad - lonOriginRad) val M = a * ((1 - eSquared / 4 - 3 * eSquared * eSquared / 64 - 5 * eSquared * eSquared * eSquared / 256) * latRad - (3 * eSquared / 8 + 3 * eSquared * eSquared / 32 + 45 * eSquared * eSquared * eSquared / 1024) * sin(2 * latRad) + (15 * eSquared * eSquared / 256 + 45 * eSquared * eSquared * eSquared / 1024) * sin(4 * latRad) - (35 * eSquared * eSquared * eSquared / 3072) * sin(6 * latRad)) val UTMEasting = (k0 * n * (A + (1 - T + C) * A * A * A / 6 + (5 - 18 * T + T * T + 72 * C - 58 * ePrimeSquared) * A * A * A * A * A / 120) + 500000.0).toLong() val UTMNorthing = (k0 * (M + n * tan(latRad) * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24 + (61 - 58 * T + T * T + 600 * C - 330 * ePrimeSquared) * A * A * A * A * A * A / 720)) + (if (lat < 0) 10000000.0 else 0.0)).toLong() val zoneLetter = getUtmZoneLetter(lat, lon) return String.format("%d%s %06d %07d", zone, zoneLetter, UTMEasting, UTMNorthing) } private fun tan(rad: Double): Double = sin(rad) / cos(rad) private fun getUtmZoneLetter(lat: Double, lon: Double): String { val letters = "CDEFGHJKLMNPQRSTUVWXX" val latIndex = ((lat + 80) / 8).toInt().coerceIn(0, letters.length - 1) return letters[latIndex].toString() } }