Example project: Appease Detekt (#3125)

pull/3010/merge
Phil Oliver 2025-09-16 19:38:54 -04:00 zatwierdzone przez GitHub
rodzic 07d798d506
commit 299dac415d
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
5 zmienionych plików z 235 dodań i 276 usunięć

Wyświetl plik

@ -22,17 +22,14 @@ import android.os.Parcelable
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /** Generic [Parcel.readParcelable] Android 13 compatibility extension. */
* Generic [Parcel.readParcelable] Android 13 compatibility extension. private inline fun <reified T : Parcelable> Parcel.readParcelableCompat(loader: ClassLoader?): T? =
*/ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU) {
private inline fun <reified T : Parcelable> Parcel.readParcelableCompat(loader: ClassLoader?): T? {
return if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
readParcelable(loader) readParcelable(loader)
} else { } else {
readParcelable(loader, T::class.java) readParcelable(loader, T::class.java)
} }
}
@Parcelize @Parcelize
enum class MessageStatus : Parcelable { enum class MessageStatus : Parcelable {
@ -41,17 +38,16 @@ enum class MessageStatus : Parcelable {
QUEUED, // Waiting to send to the mesh as soon as we connect to the device QUEUED, // Waiting to send to the mesh as soon as we connect to the device
ENROUTE, // Delivered to the radio, but no ACK or NAK received ENROUTE, // Delivered to the radio, but no ACK or NAK received
DELIVERED, // We received an ack DELIVERED, // We received an ack
ERROR // We received back a nak, message not delivered ERROR, // We received back a nak, message not delivered
} }
/** /** A parcelable version of the protobuf MeshPacket + Data subpacket. */
* A parcelable version of the protobuf MeshPacket + Data subpacket.
*/
@Serializable @Serializable
data class DataPacket( data class DataPacket(
var to: String? = ID_BROADCAST, // a nodeID string, or ID_BROADCAST for broadcast var to: String? = ID_BROADCAST, // a nodeID string, or ID_BROADCAST for broadcast
val bytes: ByteArray?, val bytes: ByteArray?,
val dataType: Int, // A port number for this packet (formerly called DataType, see portnums.proto for new usage instructions) // A port number for this packet (formerly called DataType, see portnums.proto for new usage instructions)
val dataType: Int,
var from: String? = ID_LOCAL, // a nodeID string, or ID_LOCAL for localhost var from: String? = ID_LOCAL, // a nodeID string, or ID_LOCAL for localhost
var time: Long = System.currentTimeMillis(), // msecs since 1970 var time: Long = System.currentTimeMillis(), // msecs since 1970
var id: Int = 0, // 0 means unassigned var id: Int = 0, // 0 means unassigned
@ -61,55 +57,57 @@ data class DataPacket(
var wantAck: Boolean = true, // If true, the receiver should send an ack back var wantAck: Boolean = true, // If true, the receiver should send an ack back
) : Parcelable { ) : Parcelable {
/** /** If there was an error with this message, this string describes what was wrong. */
* If there was an error with this message, this string describes what was wrong.
*/
var errorMessage: String? = null var errorMessage: String? = null
/** /** Syntactic sugar to make it easy to create text messages */
* Syntactic sugar to make it easy to create text messages constructor(
*/ to: String?,
constructor(to: String?, channel: Int, text: String) : this( channel: Int,
text: String,
) : this(
to = to, to = to,
bytes = text.encodeToByteArray(), bytes = text.encodeToByteArray(),
dataType = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE, dataType = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE,
channel = channel channel = channel,
) )
/** /** If this is a text message, return the string, otherwise null */
* If this is a text message, return the string, otherwise null
*/
val text: String? val text: String?
get() = if (dataType == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE) { get() =
bytes?.decodeToString() if (dataType == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE) {
} else { bytes?.decodeToString()
null } else {
} null
}
val alert: String? val alert: String?
get() = if (dataType == Portnums.PortNum.ALERT_APP_VALUE) { get() =
bytes?.decodeToString() if (dataType == Portnums.PortNum.ALERT_APP_VALUE) {
} else { bytes?.decodeToString()
null } else {
} null
}
constructor(to: String?, channel: Int, waypoint: MeshProtos.Waypoint) : this( constructor(
to = to, to: String?,
bytes = waypoint.toByteArray(), channel: Int,
dataType = Portnums.PortNum.WAYPOINT_APP_VALUE, waypoint: MeshProtos.Waypoint,
channel = channel ) : this(to = to, bytes = waypoint.toByteArray(), dataType = Portnums.PortNum.WAYPOINT_APP_VALUE, channel = channel)
)
val waypoint: MeshProtos.Waypoint? val waypoint: MeshProtos.Waypoint?
get() = if (dataType == Portnums.PortNum.WAYPOINT_APP_VALUE) { get() =
MeshProtos.Waypoint.parseFrom(bytes) if (dataType == Portnums.PortNum.WAYPOINT_APP_VALUE) {
} else { MeshProtos.Waypoint.parseFrom(bytes)
null } else {
} null
}
// Autogenerated comparision, because we have a byte array // Autogenerated comparision, because we have a byte array
constructor(parcel: Parcel) : this( constructor(
parcel: Parcel,
) : this(
parcel.readString(), parcel.readString(),
parcel.createByteArray(), parcel.createByteArray(),
parcel.readInt(), parcel.readInt(),
@ -169,9 +167,7 @@ data class DataPacket(
parcel.writeInt(if (wantAck) 1 else 0) parcel.writeInt(if (wantAck) 1 else 0)
} }
override fun describeContents(): Int { override fun describeContents(): Int = 0
return 0
}
// Update our object from our parcel (used for inout parameters // Update our object from our parcel (used for inout parameters
fun readFromParcel(parcel: Parcel) { fun readFromParcel(parcel: Parcel) {
@ -203,14 +199,11 @@ data class DataPacket(
const val PKC_CHANNEL_INDEX = 8 const val PKC_CHANNEL_INDEX = 8
fun nodeNumToDefaultId(n: Int): String = "!%08x".format(n) fun nodeNumToDefaultId(n: Int): String = "!%08x".format(n)
fun idToDefaultNodeNum(id: String?): Int? = runCatching { id?.toLong(16)?.toInt() }.getOrNull() fun idToDefaultNodeNum(id: String?): Int? = runCatching { id?.toLong(16)?.toInt() }.getOrNull()
override fun createFromParcel(parcel: Parcel): DataPacket { override fun createFromParcel(parcel: Parcel): DataPacket = DataPacket(parcel)
return DataPacket(parcel)
}
override fun newArray(size: Int): Array<DataPacket?> { override fun newArray(size: Int): Array<DataPacket?> = arrayOfNulls(size)
return arrayOfNulls(size)
}
} }
} }

Wyświetl plik

@ -23,21 +23,22 @@ import kotlinx.parcelize.Parcelize
// MyNodeInfo sent via special protobuf from radio // MyNodeInfo sent via special protobuf from radio
@Parcelize @Parcelize
data class MyNodeInfo( data class MyNodeInfo(
val myNodeNum: Int, val myNodeNum: Int,
val hasGPS: Boolean, val hasGPS: Boolean,
val model: String?, val model: String?,
val firmwareVersion: String?, val firmwareVersion: String?,
val couldUpdate: Boolean, // this application contains a software load we _could_ install if you want val couldUpdate: Boolean, // this application contains a software load we _could_ install if you want
val shouldUpdate: Boolean, // this device has old firmware val shouldUpdate: Boolean, // this device has old firmware
val currentPacketId: Long, val currentPacketId: Long,
val messageTimeoutMsec: Int, val messageTimeoutMsec: Int,
val minAppVersion: Int, val minAppVersion: Int,
val maxChannels: Int, val maxChannels: Int,
val hasWifi: Boolean, val hasWifi: Boolean,
val channelUtilization: Float, val channelUtilization: Float,
val airUtilTx: Float, val airUtilTx: Float,
val deviceId: String?, val deviceId: String?,
) : Parcelable { ) : Parcelable {
/** A human readable description of the software/hardware version */ /** A human readable description of the software/hardware version */
val firmwareString: String get() = "$model $firmwareVersion" val firmwareString: String
} get() = "$model $firmwareVersion"
}

Wyświetl plik

@ -19,9 +19,9 @@ package com.geeksville.mesh
import android.graphics.Color import android.graphics.Color
import android.os.Parcelable import android.os.Parcelable
import com.geeksville.mesh.util.anonymize
import com.geeksville.mesh.util.bearing import com.geeksville.mesh.util.bearing
import com.geeksville.mesh.util.latLongToMeter import com.geeksville.mesh.util.latLongToMeter
import com.geeksville.mesh.util.anonymize
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
// //
@ -38,33 +38,27 @@ data class MeshUser(
val role: Int = 0, val role: Int = 0,
) : Parcelable { ) : Parcelable {
override fun toString(): String { override fun toString(): String = "MeshUser(id=${id.anonymize}, " +
return "MeshUser(id=${id.anonymize}, " + "longName=${longName.anonymize}, " +
"longName=${longName.anonymize}, " + "shortName=${shortName.anonymize}, " +
"shortName=${shortName.anonymize}, " + "hwModel=$hwModelString, " +
"hwModel=$hwModelString, " + "isLicensed=$isLicensed, " +
"isLicensed=$isLicensed, " + "role=$role)"
"role=$role)"
}
/** Create our model object from a protobuf. /** Create our model object from a protobuf. */
constructor(p: MeshProtos.User) : this(p.id, p.longName, p.shortName, p.hwModel, p.isLicensed, p.roleValue)
/**
* a string version of the hardware model, converted into pretty lowercase and changing _ to -, and p to dot or null
* if unset
*/ */
constructor(p: MeshProtos.User) : this(
p.id,
p.longName,
p.shortName,
p.hwModel,
p.isLicensed,
p.roleValue
)
/** a string version of the hardware model, converted into pretty lowercase and changing _ to -, and p to dot
* or null if unset
* */
val hwModelString: String? val hwModelString: String?
get() = get() =
if (hwModel == MeshProtos.HardwareModel.UNSET) null if (hwModel == MeshProtos.HardwareModel.UNSET) {
else hwModel.name.replace('_', '-').replace('p', '.').lowercase() null
} else {
hwModel.name.replace('_', '-').replace('p', '.').lowercase()
}
} }
@Parcelize @Parcelize
@ -80,16 +74,22 @@ data class Position(
) : Parcelable { ) : Parcelable {
companion object { companion object {
/// Convert to a double representation of degrees // / Convert to a double representation of degrees
fun degD(i: Int) = i * 1e-7 fun degD(i: Int) = i * 1e-7
fun degI(d: Double) = (d * 1e7).toInt() fun degI(d: Double) = (d * 1e7).toInt()
fun currentTime() = (System.currentTimeMillis() / 1000).toInt() fun currentTime() = (System.currentTimeMillis() / 1000).toInt()
} }
/** Create our model object from a protobuf. If time is unspecified in the protobuf, the provided default time will be used. /**
* Create our model object from a protobuf. If time is unspecified in the protobuf, the provided default time will
* be used.
*/ */
constructor(position: MeshProtos.Position, defaultTime: Int = currentTime()) : this( constructor(
position: MeshProtos.Position,
defaultTime: Int = currentTime(),
) : this(
// We prefer the int version of lat/lon but if not available use the depreciated legacy version // We prefer the int version of lat/lon but if not available use the depreciated legacy version
degD(position.latitudeI), degD(position.latitudeI),
degD(position.longitudeI), degD(position.longitudeI),
@ -98,28 +98,25 @@ data class Position(
position.satsInView, position.satsInView,
position.groundSpeed, position.groundSpeed,
position.groundTrack, position.groundTrack,
position.precisionBits position.precisionBits,
) )
/// @return distance in meters to some other node (or null if unknown) // / @return distance in meters to some other node (or null if unknown)
fun distance(o: Position) = latLongToMeter(latitude, longitude, o.latitude, o.longitude) fun distance(o: Position) = latLongToMeter(latitude, longitude, o.latitude, o.longitude)
/// @return bearing to the other position in degrees // / @return bearing to the other position in degrees
fun bearing(o: Position) = bearing(latitude, longitude, o.latitude, o.longitude) fun bearing(o: Position) = bearing(latitude, longitude, o.latitude, o.longitude)
// If GPS gives a crap position don't crash our app // If GPS gives a crap position don't crash our app
fun isValid(): Boolean { fun isValid(): Boolean = latitude != 0.0 &&
return latitude != 0.0 && longitude != 0.0 && longitude != 0.0 &&
(latitude >= -90 && latitude <= 90.0) && (latitude >= -90 && latitude <= 90.0) &&
(longitude >= -180 && longitude <= 180) (longitude >= -180 && longitude <= 180)
}
override fun toString(): String { override fun toString(): String =
return "Position(lat=${latitude.anonymize}, lon=${longitude.anonymize}, alt=${altitude.anonymize}, time=${time})" "Position(lat=${latitude.anonymize}, lon=${longitude.anonymize}, alt=${altitude.anonymize}, time=$time)"
}
} }
@Parcelize @Parcelize
data class DeviceMetrics( data class DeviceMetrics(
val time: Int = currentTime(), // default to current time in secs (NOT MILLISECONDS!) val time: Int = currentTime(), // default to current time in secs (NOT MILLISECONDS!)
@ -133,16 +130,11 @@ data class DeviceMetrics(
fun currentTime() = (System.currentTimeMillis() / 1000).toInt() fun currentTime() = (System.currentTimeMillis() / 1000).toInt()
} }
/** Create our model object from a protobuf. /** Create our model object from a protobuf. */
*/ constructor(
constructor(p: TelemetryProtos.DeviceMetrics, telemetryTime: Int = currentTime()) : this( p: TelemetryProtos.DeviceMetrics,
telemetryTime, telemetryTime: Int = currentTime(),
p.batteryLevel, ) : this(telemetryTime, p.batteryLevel, p.voltage, p.channelUtilization, p.airUtilTx, p.uptimeSeconds)
p.voltage,
p.channelUtilization,
p.airUtilTx,
p.uptimeSeconds,
)
} }
@Parcelize @Parcelize
@ -172,7 +164,7 @@ data class NodeInfo(
var deviceMetrics: DeviceMetrics? = null, var deviceMetrics: DeviceMetrics? = null,
var channel: Int = 0, var channel: Int = 0,
var environmentMetrics: EnvironmentMetrics? = null, var environmentMetrics: EnvironmentMetrics? = null,
var hopsAway: Int = 0 var hopsAway: Int = 0,
) : Parcelable { ) : Parcelable {
val colors: Pair<Int, Int> val colors: Pair<Int, Int>
@ -184,13 +176,17 @@ data class NodeInfo(
return (if (brightness > 0.5) Color.BLACK else Color.WHITE) to Color.rgb(r, g, b) return (if (brightness > 0.5) Color.BLACK else Color.WHITE) to Color.rgb(r, g, b)
} }
val batteryLevel get() = deviceMetrics?.batteryLevel val batteryLevel
val voltage get() = deviceMetrics?.voltage get() = deviceMetrics?.batteryLevel
val batteryStr get() = if (batteryLevel in 1..100) String.format("%d%%", batteryLevel) else ""
/** val voltage
* true if the device was heard from recently get() = deviceMetrics?.voltage
*/
val batteryStr
get() = if (batteryLevel in 1..100) String.format("%d%%", batteryLevel) else ""
/** true if the device was heard from recently */
@Suppress("MagicNumber")
val isOnline: Boolean val isOnline: Boolean
get() { get() {
val now = System.currentTimeMillis() / 1000 val now = System.currentTimeMillis() / 1000
@ -198,35 +194,39 @@ data class NodeInfo(
return (now - lastHeard <= timeout) return (now - lastHeard <= timeout)
} }
/// return the position if it is valid, else null // / return the position if it is valid, else null
val validPosition: Position? val validPosition: Position?
get() { get() {
return position?.takeIf { it.isValid() } return position?.takeIf { it.isValid() }
} }
/// @return distance in meters to some other node (or null if unknown) // / @return distance in meters to some other node (or null if unknown)
fun distance(o: NodeInfo?): Int? { fun distance(o: NodeInfo?): Int? {
val p = validPosition val p = validPosition
val op = o?.validPosition val op = o?.validPosition
return if (p != null && op != null) p.distance(op).toInt() else null return if (p != null && op != null) p.distance(op).toInt() else null
} }
/// @return bearing to the other position in degrees // / @return bearing to the other position in degrees
fun bearing(o: NodeInfo?): Int? { fun bearing(o: NodeInfo?): Int? {
val p = validPosition val p = validPosition
val op = o?.validPosition val op = o?.validPosition
return if (p != null && op != null) p.bearing(op).toInt() else null return if (p != null && op != null) p.bearing(op).toInt() else null
} }
/// @return a nice human readable string for the distance, or null for unknown // / @return a nice human readable string for the distance, or null for unknown
fun distanceStr(o: NodeInfo?, prefUnits: Int = 0) = distance(o)?.let { dist -> fun distanceStr(o: NodeInfo?, prefUnits: Int = 0) = distance(o)?.let { dist ->
when { when {
dist == 0 -> null // same point dist == 0 -> null // same point
prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC_VALUE && dist < 1000 -> "%.0f m".format(dist.toDouble()) prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC_VALUE && dist < 1000 ->
prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC_VALUE && dist >= 1000 -> "%.1f km".format(dist / 1000.0) "%.0f m".format(dist.toDouble())
prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.IMPERIAL_VALUE && dist < 1609 -> "%.0f ft".format(dist.toDouble()*3.281) prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC_VALUE && dist >= 1000 ->
prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.IMPERIAL_VALUE && dist >= 1609 -> "%.1f mi".format(dist / 1609.34) "%.1f km".format(dist / 1000.0)
prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.IMPERIAL_VALUE && dist < 1609 ->
"%.0f ft".format(dist.toDouble() * 3.281)
prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.IMPERIAL_VALUE && dist >= 1609 ->
"%.1f mi".format(dist / 1609.34)
else -> null else -> null
} }
} }
} }

Wyświetl plik

@ -15,6 +15,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
@file:Suppress("TooManyFunctions")
package com.geeksville.mesh.util package com.geeksville.mesh.util
import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.MeshProtos
@ -24,6 +26,8 @@ import mil.nga.mgrs.MGRS
import mil.nga.mgrs.utm.UTM import mil.nga.mgrs.utm.UTM
import org.osmdroid.util.BoundingBox import org.osmdroid.util.BoundingBox
import org.osmdroid.util.GeoPoint import org.osmdroid.util.GeoPoint
import java.util.Locale
import kotlin.math.PI
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.acos import kotlin.math.acos
import kotlin.math.atan2 import kotlin.math.atan2
@ -31,78 +35,72 @@ import kotlin.math.cos
import kotlin.math.log2 import kotlin.math.log2
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.sin import kotlin.math.sin
import kotlin.math.PI
/******************************************************************************* /**
* ****************************************************************************
* Revive some of my old Gaggle source code... * Revive some of my old Gaggle source code...
* *
* GNU Public License, version 2 * GNU Public License, version 2 All other distribution of Gaggle must conform to the terms of the GNU Public License,
* All other distribution of Gaggle must conform to the terms of the GNU Public License, version 2. The full * version 2. The full text of this license is included in the Gaggle source, see assets/manual/gpl-2.0.txt.
* text of this license is included in the Gaggle source, see assets/manual/gpl-2.0.txt. * ****************************************************************************
******************************************************************************/ */
object GPSFormat { object GPSFormat {
fun DEC(p: Position): String { fun dec(p: Position): String =
return String.format("%.5f %.5f", p.latitude, p.longitude).replace(",", ".") String.format(Locale.getDefault(), "%.5f %.5f", p.latitude, p.longitude).replace(",", ".")
}
fun DMS(p: Position): String { @Suppress("MagicNumber")
fun dms(p: Position): String {
val lat = degreesToDMS(p.latitude, true) val lat = degreesToDMS(p.latitude, true)
val lon = degreesToDMS(p.longitude, false) val lon = degreesToDMS(p.longitude, false)
fun string(a: Array<String>) = String.format("%s°%s'%.5s\"%s", a[0], a[1], a[2], a[3]) fun string(a: Array<String>) = String.format(Locale.getDefault(), "%s°%s'%.5s\"%s", a[0], a[1], a[2], a[3])
return string(lat) + " " + string(lon) return string(lat) + " " + string(lon)
} }
fun UTM(p: Position): String { fun utm(p: Position): String {
val UTM = UTM.from(Point.point(p.longitude, p.latitude)) val utm = UTM.from(Point.point(p.longitude, p.latitude))
return String.format( return String.format(
Locale.getDefault(),
"%s%s %.6s %.7s", "%s%s %.6s %.7s",
UTM.zone, utm.zone,
UTM.toMGRS().band, utm.toMGRS().band,
UTM.easting, utm.easting,
UTM.northing utm.northing,
) )
} }
fun MGRS(p: Position): String { fun mgrs(p: Position): String {
val MGRS = MGRS.from(Point.point(p.longitude, p.latitude)) val mgrs = MGRS.from(Point.point(p.longitude, p.latitude))
return String.format( return String.format(
Locale.getDefault(),
"%s%s %s%s %05d %05d", "%s%s %s%s %05d %05d",
MGRS.zone, mgrs.zone,
MGRS.band, mgrs.band,
MGRS.column, mgrs.column,
MGRS.row, mgrs.row,
MGRS.easting, mgrs.easting,
MGRS.northing mgrs.northing,
) )
} }
fun toDEC(latitude: Double, longitude: Double): String { fun toDEC(latitude: Double, longitude: Double): String = "%.5f %.5f".format(latitude, longitude).replace(",", ".")
return "%.5f %.5f".format(latitude, longitude).replace(",", ".")
}
@Suppress("MagicNumber")
fun toDMS(latitude: Double, longitude: Double): String { fun toDMS(latitude: Double, longitude: Double): String {
val lat = degreesToDMS(latitude, true) val lat = degreesToDMS(latitude, true)
val lon = degreesToDMS(longitude, false) val lon = degreesToDMS(longitude, false)
fun string(a: Array<String>) = "%s°%s'%.5s\"%s".format(a[0], a[1], a[2], a[3]) fun string(a: Array<String>) = "%s°%s'%.5s\"%s".format(Locale.getDefault(), a[0], a[1], a[2], a[3])
return string(lat) + " " + string(lon) return string(lat) + " " + string(lon)
} }
fun toUTM(latitude: Double, longitude: Double): String { fun toUTM(latitude: Double, longitude: Double): String {
val UTM = UTM.from(Point.point(longitude, latitude)) val utm = UTM.from(Point.point(longitude, latitude))
return "%s%s %.6s %.7s".format(UTM.zone, UTM.toMGRS().band, UTM.easting, UTM.northing) return "%s%s %.6s %.7s".format(Locale.getDefault(), utm.zone, utm.toMGRS().band, utm.easting, utm.northing)
} }
fun toMGRS(latitude: Double, longitude: Double): String { fun toMGRS(latitude: Double, longitude: Double): String {
val MGRS = MGRS.from(Point.point(longitude, latitude)) val mgrs = MGRS.from(Point.point(longitude, latitude))
return "%s%s %s%s %05d %05d".format( return "%s%s %s%s %05d %05d"
MGRS.zone, .format(Locale.getDefault(), mgrs.zone, mgrs.band, mgrs.column, mgrs.row, mgrs.easting, mgrs.northing)
MGRS.band,
MGRS.column,
MGRS.row,
MGRS.easting,
MGRS.northing
)
} }
} }
@ -113,75 +111,71 @@ object GPSFormat {
* @param isLatitude * @param isLatitude
* @return a string like 120deg * @return a string like 120deg
*/ */
fun degreesToDMS( @Suppress("MagicNumber")
_degIn: Double, fun degreesToDMS(degIn: Double, isLatitude: Boolean): Array<String> {
isLatitude: Boolean var degIn = degIn
): Array<String> {
var degIn = _degIn
val isPos = degIn >= 0 val isPos = degIn >= 0
val dirLetter = val dirLetter =
if (isLatitude) if (isPos) 'N' else 'S' else if (isPos) 'E' else 'W' if (isLatitude) if (isPos) 'N' else 'S'
else if (isPos) {
'E'
} else {
'W'
}
degIn = abs(degIn) degIn = abs(degIn)
val degOut = degIn.toInt() val degOut = degIn.toInt()
val minutes = 60 * (degIn - degOut) val minutes = 60 * (degIn - degOut)
val minwhole = minutes.toInt() val minwhole = minutes.toInt()
val seconds = (minutes - minwhole) * 60 val seconds = (minutes - minwhole) * 60
return arrayOf( return arrayOf(degOut.toString(), minwhole.toString(), seconds.toString(), dirLetter.toString())
degOut.toString(), minwhole.toString(),
seconds.toString(),
dirLetter.toString()
)
} }
fun degreesToDM(_degIn: Double, isLatitude: Boolean): Array<String> { @Suppress("MagicNumber")
var degIn = _degIn fun degreesToDM(degIn: Double, isLatitude: Boolean): Array<String> {
var degIn = degIn
val isPos = degIn >= 0 val isPos = degIn >= 0
val dirLetter = val dirLetter =
if (isLatitude) if (isPos) 'N' else 'S' else if (isPos) 'E' else 'W' if (isLatitude) if (isPos) 'N' else 'S'
else if (isPos) {
'E'
} else {
'W'
}
degIn = abs(degIn) degIn = abs(degIn)
val degOut = degIn.toInt() val degOut = degIn.toInt()
val minutes = 60 * (degIn - degOut) val minutes = 60 * (degIn - degOut)
val seconds = 0 val seconds = 0
return arrayOf( return arrayOf(degOut.toString(), minutes.toString(), seconds.toString(), dirLetter.toString())
degOut.toString(), minutes.toString(),
seconds.toString(),
dirLetter.toString()
)
} }
fun degreesToD(_degIn: Double, isLatitude: Boolean): Array<String> { fun degreesToD(degIn: Double, isLatitude: Boolean): Array<String> {
var degIn = _degIn var degIn = degIn
val isPos = degIn >= 0 val isPos = degIn >= 0
val dirLetter = val dirLetter =
if (isLatitude) if (isPos) 'N' else 'S' else if (isPos) 'E' else 'W' if (isLatitude) if (isPos) 'N' else 'S'
else if (isPos) {
'E'
} else {
'W'
}
degIn = abs(degIn) degIn = abs(degIn)
val degOut = degIn val degOut = degIn
val minutes = 0 val minutes = 0
val seconds = 0 val seconds = 0
return arrayOf( return arrayOf(degOut.toString(), minutes.toString(), seconds.toString(), dirLetter.toString())
degOut.toString(), minutes.toString(),
seconds.toString(),
dirLetter.toString()
)
} }
/** /**
* A not super efficent mapping from a starting lat/long + a distance at a * A not super efficent mapping from a starting lat/long + a distance at a certain direction
* certain direction
* *
* @param lat * @param lat
* @param longitude * @param longitude
* @param distMeters * @param distMeters
* @param theta * @param theta in radians, 0 == north
* in radians, 0 == north
* @return an array with lat and long * @return an array with lat and long
*/ */
fun addDistance( @Suppress("MagicNumber")
lat: Double, fun addDistance(lat: Double, longitude: Double, distMeters: Double, theta: Double): DoubleArray {
longitude: Double,
distMeters: Double,
theta: Double
): DoubleArray {
val dx = distMeters * sin(theta) // theta measured clockwise val dx = distMeters * sin(theta) // theta measured clockwise
// from due north // from due north
val dy = distMeters * cos(theta) // dx, dy same units as R val dy = distMeters * cos(theta) // dx, dy same units as R
@ -190,20 +184,14 @@ fun addDistance(
return doubleArrayOf(lat + dLat, longitude + dLong) return doubleArrayOf(lat + dLat, longitude + dLong)
} }
/** /** @return distance in meters along the surface of the earth (ish) */
* @return distance in meters along the surface of the earth (ish) @Suppress("MagicNumber")
*/ fun latLongToMeter(latA: Double, lngA: Double, latB: Double, lngB: Double): Double {
fun latLongToMeter(
lat_a: Double,
lng_a: Double,
lat_b: Double,
lng_b: Double
): Double {
val pk = (180 / PI) val pk = (180 / PI)
val a1 = lat_a / pk val a1 = latA / pk
val a2 = lng_a / pk val a2 = lngA / pk
val b1 = lat_b / pk val b1 = latB / pk
val b2 = lng_b / pk val b2 = lngB / pk
val t1 = cos(a1) * cos(a2) * cos(b1) * cos(b2) val t1 = cos(a1) * cos(a2) * cos(b1) * cos(b2)
val t2 = cos(a1) * sin(a2) * cos(b1) * sin(b2) val t2 = cos(a1) * sin(a2) * cos(b1) * sin(b2)
val t3 = sin(a1) * sin(b1) val t3 = sin(a1) * sin(b1)
@ -213,14 +201,8 @@ fun latLongToMeter(
} }
// Same as above, but takes Mesh Position proto. // Same as above, but takes Mesh Position proto.
fun positionToMeter(a: MeshProtos.Position, b: MeshProtos.Position): Double { fun positionToMeter(a: MeshProtos.Position, b: MeshProtos.Position): Double =
return latLongToMeter( latLongToMeter(a.latitudeI * 1e-7, a.longitudeI * 1e-7, b.latitudeI * 1e-7, b.longitudeI * 1e-7)
a.latitudeI * 1e-7,
a.longitudeI * 1e-7,
b.latitudeI * 1e-7,
b.longitudeI * 1e-7
)
}
/** /**
* Convert degrees/mins/secs to a single double * Convert degrees/mins/secs to a single double
@ -231,44 +213,24 @@ fun positionToMeter(a: MeshProtos.Position, b: MeshProtos.Position): Double {
* @param isPostive * @param isPostive
* @return * @return
*/ */
fun DMSToDegrees( @Suppress("MagicNumber")
degrees: Int, fun dmsToDegrees(degrees: Int, minutes: Int, seconds: Float, isPostive: Boolean): Double =
minutes: Int, (if (isPostive) 1 else -1) * (degrees + minutes / 60.0 + seconds / 3600.0)
seconds: Float,
isPostive: Boolean
): Double {
return (if (isPostive) 1 else -1) * (degrees + minutes / 60.0 + seconds / 3600.0)
}
fun DMSToDegrees( @Suppress("MagicNumber")
degrees: Double, fun dmsToDegrees(degrees: Double, minutes: Double, seconds: Double, isPostive: Boolean): Double =
minutes: Double, (if (isPostive) 1 else -1) * (degrees + minutes / 60.0 + seconds / 3600.0)
seconds: Double,
isPostive: Boolean
): Double {
return (if (isPostive) 1 else -1) * (degrees + minutes / 60.0 + seconds / 3600.0)
}
/** /**
* Computes the bearing in degrees between two points on Earth. * Computes the bearing in degrees between two points on Earth.
* *
* @param lat1 * @param lat1 Latitude of the first point
* Latitude of the first point * @param lon1 Longitude of the first point
* @param lon1 * @param lat2 Latitude of the second point
* Longitude of the first point * @param lon2 Longitude of the second point
* @param lat2 * @return Bearing between the two points in degrees. A value of 0 means due north.
* Latitude of the second point
* @param lon2
* Longitude of the second point
* @return Bearing between the two points in degrees. A value of 0 means due
* north.
*/ */
fun bearing( fun bearing(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
lat1: Double,
lon1: Double,
lat2: Double,
lon2: Double
): Double {
val lat1Rad = Math.toRadians(lat1) val lat1Rad = Math.toRadians(lat1)
val lat2Rad = Math.toRadians(lat2) val lat2Rad = Math.toRadians(lat2)
val deltaLonRad = Math.toRadians(lon2 - lon1) val deltaLonRad = Math.toRadians(lon2 - lon1)
@ -277,17 +239,16 @@ fun bearing(
return radToBearing(atan2(y, x)) return radToBearing(atan2(y, x))
} }
/** /** Converts an angle in radians to degrees */
* Converts an angle in radians to degrees @Suppress("MagicNumber")
*/ fun radToBearing(rad: Double): Double = (Math.toDegrees(rad) + 360) % 360
fun radToBearing(rad: Double): Double {
return (Math.toDegrees(rad) + 360) % 360
}
/** /**
* Calculates the zoom level required to fit the entire [BoundingBox] inside the map view. * Calculates the zoom level required to fit the entire [BoundingBox] inside the map view.
*
* @return The zoom level as a Double value. * @return The zoom level as a Double value.
*/ */
@Suppress("MagicNumber")
fun BoundingBox.requiredZoomLevel(): Double { fun BoundingBox.requiredZoomLevel(): Double {
val topLeft = GeoPoint(this.latNorth, this.lonWest) val topLeft = GeoPoint(this.latNorth, this.lonWest)
val bottomRight = GeoPoint(this.latSouth, this.lonEast) val bottomRight = GeoPoint(this.latSouth, this.lonEast)
@ -300,6 +261,7 @@ fun BoundingBox.requiredZoomLevel(): Double {
/** /**
* Creates a new bounding box with adjusted dimensions based on the provided [zoomFactor]. * Creates a new bounding box with adjusted dimensions based on the provided [zoomFactor].
*
* @return A new [BoundingBox] with added [zoomFactor]. Example: * @return A new [BoundingBox] with added [zoomFactor]. Example:
* ``` * ```
* // Setting the zoom level directly using setZoom() * // Setting the zoom level directly using setZoom()
@ -322,6 +284,6 @@ fun BoundingBox.zoomIn(zoomFactor: Double): BoundingBox {
center.latitude + newLatDiff / 2, center.latitude + newLatDiff / 2,
center.longitude + newLonDiff / 2, center.longitude + newLonDiff / 2,
center.latitude - newLatDiff / 2, center.latitude - newLatDiff / 2,
center.longitude - newLonDiff / 2 center.longitude - newLonDiff / 2,
) )
} }

Wyświetl plik

@ -50,6 +50,7 @@ class MainActivity : AppCompatActivity() {
private var isMeshServiceBound = false private var isMeshServiceBound = false
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU) @RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
@Suppress("TooGenericExceptionCaught", "LongMethod")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
this.enableEdgeToEdge() this.enableEdgeToEdge()
@ -109,6 +110,7 @@ class MainActivity : AppCompatActivity() {
val meshtasticReceiver: BroadcastReceiver = val meshtasticReceiver: BroadcastReceiver =
object : BroadcastReceiver() { object : BroadcastReceiver() {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@Suppress("ReturnCount")
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
if (intent == null) { if (intent == null) {
Log.w(TAG, "Received null intent") Log.w(TAG, "Received null intent")
@ -166,7 +168,7 @@ class MainActivity : AppCompatActivity() {
Log.d(TAG, "Position App NodeInfo: $ni") Log.d(TAG, "Position App NodeInfo: $ni")
mainTextView.text = "Position App NodeInfo: $ni" mainTextView.text = "Position App NodeInfo: $ni"
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() Log.e(TAG, "onReceive: $e")
return return
} }
} }
@ -189,7 +191,8 @@ class MainActivity : AppCompatActivity() {
while (!bindMeshService()) { while (!bindMeshService()) {
try { try {
Thread.sleep(1000) @Suppress("MagicNumber")
Thread.sleep(1_000)
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
Log.e(TAG, "Binding interrupted", e) Log.e(TAG, "Binding interrupted", e)
break break