- Kotlin + Jetpack Compose (Material 3) - Конвертация в APRS, Maidenhead, DMS форматы - Полноэкранный режим - Иконка приложения - Min SDK: 21, Target SDK: 34 Версия: 1.0.0 Дата: 2026-03-02 Автор: UA1ZBE Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
108 lines
3.9 KiB
Kotlin
108 lines
3.9 KiB
Kotlin
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()
|
|
}
|
|
}
|