kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
Example project: Appease Detekt (#3125)
rodzic
07d798d506
commit
299dac415d
|
@ -22,17 +22,14 @@ import android.os.Parcelable
|
|||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Generic [Parcel.readParcelable] Android 13 compatibility extension.
|
||||
*/
|
||||
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) {
|
||||
/** 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) {
|
||||
@Suppress("DEPRECATION")
|
||||
readParcelable(loader)
|
||||
} else {
|
||||
readParcelable(loader, T::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
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
|
||||
ENROUTE, // Delivered to the radio, but no ACK or NAK received
|
||||
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
|
||||
data class DataPacket(
|
||||
var to: String? = ID_BROADCAST, // a nodeID string, or ID_BROADCAST for broadcast
|
||||
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 time: Long = System.currentTimeMillis(), // msecs since 1970
|
||||
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
|
||||
) : 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
|
||||
|
||||
/**
|
||||
* Syntactic sugar to make it easy to create text messages
|
||||
*/
|
||||
constructor(to: String?, channel: Int, text: String) : this(
|
||||
/** Syntactic sugar to make it easy to create text messages */
|
||||
constructor(
|
||||
to: String?,
|
||||
channel: Int,
|
||||
text: String,
|
||||
) : this(
|
||||
to = to,
|
||||
bytes = text.encodeToByteArray(),
|
||||
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?
|
||||
get() = if (dataType == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE) {
|
||||
bytes?.decodeToString()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
get() =
|
||||
if (dataType == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE) {
|
||||
bytes?.decodeToString()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val alert: String?
|
||||
get() = if (dataType == Portnums.PortNum.ALERT_APP_VALUE) {
|
||||
bytes?.decodeToString()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
get() =
|
||||
if (dataType == Portnums.PortNum.ALERT_APP_VALUE) {
|
||||
bytes?.decodeToString()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
constructor(to: String?, channel: Int, waypoint: MeshProtos.Waypoint) : this(
|
||||
to = to,
|
||||
bytes = waypoint.toByteArray(),
|
||||
dataType = Portnums.PortNum.WAYPOINT_APP_VALUE,
|
||||
channel = channel
|
||||
)
|
||||
constructor(
|
||||
to: String?,
|
||||
channel: Int,
|
||||
waypoint: MeshProtos.Waypoint,
|
||||
) : this(to = to, bytes = waypoint.toByteArray(), dataType = Portnums.PortNum.WAYPOINT_APP_VALUE, channel = channel)
|
||||
|
||||
val waypoint: MeshProtos.Waypoint?
|
||||
get() = if (dataType == Portnums.PortNum.WAYPOINT_APP_VALUE) {
|
||||
MeshProtos.Waypoint.parseFrom(bytes)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
get() =
|
||||
if (dataType == Portnums.PortNum.WAYPOINT_APP_VALUE) {
|
||||
MeshProtos.Waypoint.parseFrom(bytes)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// Autogenerated comparision, because we have a byte array
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
constructor(
|
||||
parcel: Parcel,
|
||||
) : this(
|
||||
parcel.readString(),
|
||||
parcel.createByteArray(),
|
||||
parcel.readInt(),
|
||||
|
@ -169,9 +167,7 @@ data class DataPacket(
|
|||
parcel.writeInt(if (wantAck) 1 else 0)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
override fun describeContents(): Int = 0
|
||||
|
||||
// Update our object from our parcel (used for inout parameters
|
||||
fun readFromParcel(parcel: Parcel) {
|
||||
|
@ -203,14 +199,11 @@ data class DataPacket(
|
|||
const val PKC_CHANNEL_INDEX = 8
|
||||
|
||||
fun nodeNumToDefaultId(n: Int): String = "!%08x".format(n)
|
||||
|
||||
fun idToDefaultNodeNum(id: String?): Int? = runCatching { id?.toLong(16)?.toInt() }.getOrNull()
|
||||
|
||||
override fun createFromParcel(parcel: Parcel): DataPacket {
|
||||
return DataPacket(parcel)
|
||||
}
|
||||
override fun createFromParcel(parcel: Parcel): DataPacket = DataPacket(parcel)
|
||||
|
||||
override fun newArray(size: Int): Array<DataPacket?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
override fun newArray(size: Int): Array<DataPacket?> = arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,21 +23,22 @@ import kotlinx.parcelize.Parcelize
|
|||
// MyNodeInfo sent via special protobuf from radio
|
||||
@Parcelize
|
||||
data class MyNodeInfo(
|
||||
val myNodeNum: Int,
|
||||
val hasGPS: Boolean,
|
||||
val model: String?,
|
||||
val firmwareVersion: String?,
|
||||
val couldUpdate: Boolean, // this application contains a software load we _could_ install if you want
|
||||
val shouldUpdate: Boolean, // this device has old firmware
|
||||
val currentPacketId: Long,
|
||||
val messageTimeoutMsec: Int,
|
||||
val minAppVersion: Int,
|
||||
val maxChannels: Int,
|
||||
val hasWifi: Boolean,
|
||||
val channelUtilization: Float,
|
||||
val airUtilTx: Float,
|
||||
val deviceId: String?,
|
||||
val myNodeNum: Int,
|
||||
val hasGPS: Boolean,
|
||||
val model: String?,
|
||||
val firmwareVersion: String?,
|
||||
val couldUpdate: Boolean, // this application contains a software load we _could_ install if you want
|
||||
val shouldUpdate: Boolean, // this device has old firmware
|
||||
val currentPacketId: Long,
|
||||
val messageTimeoutMsec: Int,
|
||||
val minAppVersion: Int,
|
||||
val maxChannels: Int,
|
||||
val hasWifi: Boolean,
|
||||
val channelUtilization: Float,
|
||||
val airUtilTx: Float,
|
||||
val deviceId: String?,
|
||||
) : Parcelable {
|
||||
/** A human readable description of the software/hardware version */
|
||||
val firmwareString: String get() = "$model $firmwareVersion"
|
||||
}
|
||||
/** A human readable description of the software/hardware version */
|
||||
val firmwareString: String
|
||||
get() = "$model $firmwareVersion"
|
||||
}
|
||||
|
|
|
@ -19,9 +19,9 @@ package com.geeksville.mesh
|
|||
|
||||
import android.graphics.Color
|
||||
import android.os.Parcelable
|
||||
import com.geeksville.mesh.util.anonymize
|
||||
import com.geeksville.mesh.util.bearing
|
||||
import com.geeksville.mesh.util.latLongToMeter
|
||||
import com.geeksville.mesh.util.anonymize
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
//
|
||||
|
@ -38,33 +38,27 @@ data class MeshUser(
|
|||
val role: Int = 0,
|
||||
) : Parcelable {
|
||||
|
||||
override fun toString(): String {
|
||||
return "MeshUser(id=${id.anonymize}, " +
|
||||
"longName=${longName.anonymize}, " +
|
||||
"shortName=${shortName.anonymize}, " +
|
||||
"hwModel=$hwModelString, " +
|
||||
"isLicensed=$isLicensed, " +
|
||||
"role=$role)"
|
||||
}
|
||||
override fun toString(): String = "MeshUser(id=${id.anonymize}, " +
|
||||
"longName=${longName.anonymize}, " +
|
||||
"shortName=${shortName.anonymize}, " +
|
||||
"hwModel=$hwModelString, " +
|
||||
"isLicensed=$isLicensed, " +
|
||||
"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?
|
||||
get() =
|
||||
if (hwModel == MeshProtos.HardwareModel.UNSET) null
|
||||
else hwModel.name.replace('_', '-').replace('p', '.').lowercase()
|
||||
if (hwModel == MeshProtos.HardwareModel.UNSET) {
|
||||
null
|
||||
} else {
|
||||
hwModel.name.replace('_', '-').replace('p', '.').lowercase()
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
|
@ -80,16 +74,22 @@ data class Position(
|
|||
) : Parcelable {
|
||||
|
||||
companion object {
|
||||
/// Convert to a double representation of degrees
|
||||
// / Convert to a double representation of degrees
|
||||
fun degD(i: Int) = i * 1e-7
|
||||
|
||||
fun degI(d: Double) = (d * 1e7).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
|
||||
degD(position.latitudeI),
|
||||
degD(position.longitudeI),
|
||||
|
@ -98,28 +98,25 @@ data class Position(
|
|||
position.satsInView,
|
||||
position.groundSpeed,
|
||||
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)
|
||||
|
||||
/// @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)
|
||||
|
||||
// If GPS gives a crap position don't crash our app
|
||||
fun isValid(): Boolean {
|
||||
return latitude != 0.0 && longitude != 0.0 &&
|
||||
(latitude >= -90 && latitude <= 90.0) &&
|
||||
(longitude >= -180 && longitude <= 180)
|
||||
}
|
||||
fun isValid(): Boolean = latitude != 0.0 &&
|
||||
longitude != 0.0 &&
|
||||
(latitude >= -90 && latitude <= 90.0) &&
|
||||
(longitude >= -180 && longitude <= 180)
|
||||
|
||||
override fun toString(): String {
|
||||
return "Position(lat=${latitude.anonymize}, lon=${longitude.anonymize}, alt=${altitude.anonymize}, time=${time})"
|
||||
}
|
||||
override fun toString(): String =
|
||||
"Position(lat=${latitude.anonymize}, lon=${longitude.anonymize}, alt=${altitude.anonymize}, time=$time)"
|
||||
}
|
||||
|
||||
|
||||
@Parcelize
|
||||
data class DeviceMetrics(
|
||||
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()
|
||||
}
|
||||
|
||||
/** Create our model object from a protobuf.
|
||||
*/
|
||||
constructor(p: TelemetryProtos.DeviceMetrics, telemetryTime: Int = currentTime()) : this(
|
||||
telemetryTime,
|
||||
p.batteryLevel,
|
||||
p.voltage,
|
||||
p.channelUtilization,
|
||||
p.airUtilTx,
|
||||
p.uptimeSeconds,
|
||||
)
|
||||
/** Create our model object from a protobuf. */
|
||||
constructor(
|
||||
p: TelemetryProtos.DeviceMetrics,
|
||||
telemetryTime: Int = currentTime(),
|
||||
) : this(telemetryTime, p.batteryLevel, p.voltage, p.channelUtilization, p.airUtilTx, p.uptimeSeconds)
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
|
@ -172,7 +164,7 @@ data class NodeInfo(
|
|||
var deviceMetrics: DeviceMetrics? = null,
|
||||
var channel: Int = 0,
|
||||
var environmentMetrics: EnvironmentMetrics? = null,
|
||||
var hopsAway: Int = 0
|
||||
var hopsAway: Int = 0,
|
||||
) : Parcelable {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
val batteryLevel get() = deviceMetrics?.batteryLevel
|
||||
val voltage get() = deviceMetrics?.voltage
|
||||
val batteryStr get() = if (batteryLevel in 1..100) String.format("%d%%", batteryLevel) else ""
|
||||
val batteryLevel
|
||||
get() = deviceMetrics?.batteryLevel
|
||||
|
||||
/**
|
||||
* true if the device was heard from recently
|
||||
*/
|
||||
val voltage
|
||||
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
|
||||
get() {
|
||||
val now = System.currentTimeMillis() / 1000
|
||||
|
@ -198,35 +194,39 @@ data class NodeInfo(
|
|||
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?
|
||||
get() {
|
||||
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? {
|
||||
val p = validPosition
|
||||
val op = o?.validPosition
|
||||
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? {
|
||||
val p = validPosition
|
||||
val op = o?.validPosition
|
||||
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 ->
|
||||
when {
|
||||
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 -> "%.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)
|
||||
prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC_VALUE && dist < 1000 ->
|
||||
"%.0f m".format(dist.toDouble())
|
||||
prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC_VALUE && dist >= 1000 ->
|
||||
"%.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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
@file:Suppress("TooManyFunctions")
|
||||
|
||||
package com.geeksville.mesh.util
|
||||
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
|
@ -24,6 +26,8 @@ import mil.nga.mgrs.MGRS
|
|||
import mil.nga.mgrs.utm.UTM
|
||||
import org.osmdroid.util.BoundingBox
|
||||
import org.osmdroid.util.GeoPoint
|
||||
import java.util.Locale
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.acos
|
||||
import kotlin.math.atan2
|
||||
|
@ -31,78 +35,72 @@ import kotlin.math.cos
|
|||
import kotlin.math.log2
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.PI
|
||||
|
||||
/*******************************************************************************
|
||||
/**
|
||||
* ****************************************************************************
|
||||
* Revive some of my old Gaggle source code...
|
||||
*
|
||||
* GNU Public License, version 2
|
||||
* All other distribution of Gaggle must conform to the terms of the GNU Public License, version 2. The full
|
||||
* text of this license is included in the Gaggle source, see assets/manual/gpl-2.0.txt.
|
||||
******************************************************************************/
|
||||
|
||||
* GNU Public License, version 2 All other distribution of Gaggle must conform to the terms of the GNU Public License,
|
||||
* version 2. The full text of this license is included in the Gaggle source, see assets/manual/gpl-2.0.txt.
|
||||
* ****************************************************************************
|
||||
*/
|
||||
object GPSFormat {
|
||||
fun DEC(p: Position): String {
|
||||
return String.format("%.5f %.5f", p.latitude, p.longitude).replace(",", ".")
|
||||
}
|
||||
fun dec(p: Position): String =
|
||||
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 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)
|
||||
}
|
||||
|
||||
fun UTM(p: Position): String {
|
||||
val UTM = UTM.from(Point.point(p.longitude, p.latitude))
|
||||
fun utm(p: Position): String {
|
||||
val utm = UTM.from(Point.point(p.longitude, p.latitude))
|
||||
return String.format(
|
||||
Locale.getDefault(),
|
||||
"%s%s %.6s %.7s",
|
||||
UTM.zone,
|
||||
UTM.toMGRS().band,
|
||||
UTM.easting,
|
||||
UTM.northing
|
||||
utm.zone,
|
||||
utm.toMGRS().band,
|
||||
utm.easting,
|
||||
utm.northing,
|
||||
)
|
||||
}
|
||||
|
||||
fun MGRS(p: Position): String {
|
||||
val MGRS = MGRS.from(Point.point(p.longitude, p.latitude))
|
||||
fun mgrs(p: Position): String {
|
||||
val mgrs = MGRS.from(Point.point(p.longitude, p.latitude))
|
||||
return String.format(
|
||||
Locale.getDefault(),
|
||||
"%s%s %s%s %05d %05d",
|
||||
MGRS.zone,
|
||||
MGRS.band,
|
||||
MGRS.column,
|
||||
MGRS.row,
|
||||
MGRS.easting,
|
||||
MGRS.northing
|
||||
mgrs.zone,
|
||||
mgrs.band,
|
||||
mgrs.column,
|
||||
mgrs.row,
|
||||
mgrs.easting,
|
||||
mgrs.northing,
|
||||
)
|
||||
}
|
||||
|
||||
fun toDEC(latitude: Double, longitude: Double): String {
|
||||
return "%.5f %.5f".format(latitude, longitude).replace(",", ".")
|
||||
}
|
||||
fun toDEC(latitude: Double, longitude: Double): String = "%.5f %.5f".format(latitude, longitude).replace(",", ".")
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun toDMS(latitude: Double, longitude: Double): String {
|
||||
val lat = degreesToDMS(latitude, true)
|
||||
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)
|
||||
}
|
||||
|
||||
fun toUTM(latitude: Double, longitude: Double): String {
|
||||
val UTM = UTM.from(Point.point(longitude, latitude))
|
||||
return "%s%s %.6s %.7s".format(UTM.zone, UTM.toMGRS().band, UTM.easting, UTM.northing)
|
||||
val utm = UTM.from(Point.point(longitude, latitude))
|
||||
return "%s%s %.6s %.7s".format(Locale.getDefault(), utm.zone, utm.toMGRS().band, utm.easting, utm.northing)
|
||||
}
|
||||
|
||||
fun toMGRS(latitude: Double, longitude: Double): String {
|
||||
val MGRS = MGRS.from(Point.point(longitude, latitude))
|
||||
return "%s%s %s%s %05d %05d".format(
|
||||
MGRS.zone,
|
||||
MGRS.band,
|
||||
MGRS.column,
|
||||
MGRS.row,
|
||||
MGRS.easting,
|
||||
MGRS.northing
|
||||
)
|
||||
val mgrs = MGRS.from(Point.point(longitude, latitude))
|
||||
return "%s%s %s%s %05d %05d"
|
||||
.format(Locale.getDefault(), mgrs.zone, mgrs.band, mgrs.column, mgrs.row, mgrs.easting, mgrs.northing)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,75 +111,71 @@ object GPSFormat {
|
|||
* @param isLatitude
|
||||
* @return a string like 120deg
|
||||
*/
|
||||
fun degreesToDMS(
|
||||
_degIn: Double,
|
||||
isLatitude: Boolean
|
||||
): Array<String> {
|
||||
var degIn = _degIn
|
||||
@Suppress("MagicNumber")
|
||||
fun degreesToDMS(degIn: Double, isLatitude: Boolean): Array<String> {
|
||||
var degIn = degIn
|
||||
val isPos = degIn >= 0
|
||||
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)
|
||||
val degOut = degIn.toInt()
|
||||
val minutes = 60 * (degIn - degOut)
|
||||
val minwhole = minutes.toInt()
|
||||
val seconds = (minutes - minwhole) * 60
|
||||
return arrayOf(
|
||||
degOut.toString(), minwhole.toString(),
|
||||
seconds.toString(),
|
||||
dirLetter.toString()
|
||||
)
|
||||
return arrayOf(degOut.toString(), minwhole.toString(), seconds.toString(), dirLetter.toString())
|
||||
}
|
||||
|
||||
fun degreesToDM(_degIn: Double, isLatitude: Boolean): Array<String> {
|
||||
var degIn = _degIn
|
||||
@Suppress("MagicNumber")
|
||||
fun degreesToDM(degIn: Double, isLatitude: Boolean): Array<String> {
|
||||
var degIn = degIn
|
||||
val isPos = degIn >= 0
|
||||
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)
|
||||
val degOut = degIn.toInt()
|
||||
val minutes = 60 * (degIn - degOut)
|
||||
val seconds = 0
|
||||
return arrayOf(
|
||||
degOut.toString(), minutes.toString(),
|
||||
seconds.toString(),
|
||||
dirLetter.toString()
|
||||
)
|
||||
return arrayOf(degOut.toString(), minutes.toString(), seconds.toString(), dirLetter.toString())
|
||||
}
|
||||
|
||||
fun degreesToD(_degIn: Double, isLatitude: Boolean): Array<String> {
|
||||
var degIn = _degIn
|
||||
fun degreesToD(degIn: Double, isLatitude: Boolean): Array<String> {
|
||||
var degIn = degIn
|
||||
val isPos = degIn >= 0
|
||||
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)
|
||||
val degOut = degIn
|
||||
val minutes = 0
|
||||
val seconds = 0
|
||||
return arrayOf(
|
||||
degOut.toString(), minutes.toString(),
|
||||
seconds.toString(),
|
||||
dirLetter.toString()
|
||||
)
|
||||
return arrayOf(degOut.toString(), minutes.toString(), seconds.toString(), dirLetter.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* A not super efficent mapping from a starting lat/long + a distance at a
|
||||
* certain direction
|
||||
* A not super efficent mapping from a starting lat/long + a distance at a certain direction
|
||||
*
|
||||
* @param lat
|
||||
* @param longitude
|
||||
* @param distMeters
|
||||
* @param theta
|
||||
* in radians, 0 == north
|
||||
* @param theta in radians, 0 == north
|
||||
* @return an array with lat and long
|
||||
*/
|
||||
fun addDistance(
|
||||
lat: Double,
|
||||
longitude: Double,
|
||||
distMeters: Double,
|
||||
theta: Double
|
||||
): DoubleArray {
|
||||
@Suppress("MagicNumber")
|
||||
fun addDistance(lat: Double, longitude: Double, distMeters: Double, theta: Double): DoubleArray {
|
||||
val dx = distMeters * sin(theta) // theta measured clockwise
|
||||
// from due north
|
||||
val dy = distMeters * cos(theta) // dx, dy same units as R
|
||||
|
@ -190,20 +184,14 @@ fun addDistance(
|
|||
return doubleArrayOf(lat + dLat, longitude + dLong)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return distance in meters along the surface of the earth (ish)
|
||||
*/
|
||||
fun latLongToMeter(
|
||||
lat_a: Double,
|
||||
lng_a: Double,
|
||||
lat_b: Double,
|
||||
lng_b: Double
|
||||
): Double {
|
||||
/** @return distance in meters along the surface of the earth (ish) */
|
||||
@Suppress("MagicNumber")
|
||||
fun latLongToMeter(latA: Double, lngA: Double, latB: Double, lngB: Double): Double {
|
||||
val pk = (180 / PI)
|
||||
val a1 = lat_a / pk
|
||||
val a2 = lng_a / pk
|
||||
val b1 = lat_b / pk
|
||||
val b2 = lng_b / pk
|
||||
val a1 = latA / pk
|
||||
val a2 = lngA / pk
|
||||
val b1 = latB / pk
|
||||
val b2 = lngB / pk
|
||||
val t1 = cos(a1) * cos(a2) * cos(b1) * cos(b2)
|
||||
val t2 = cos(a1) * sin(a2) * cos(b1) * sin(b2)
|
||||
val t3 = sin(a1) * sin(b1)
|
||||
|
@ -213,14 +201,8 @@ fun latLongToMeter(
|
|||
}
|
||||
|
||||
// Same as above, but takes Mesh Position proto.
|
||||
fun positionToMeter(a: MeshProtos.Position, b: MeshProtos.Position): Double {
|
||||
return latLongToMeter(
|
||||
a.latitudeI * 1e-7,
|
||||
a.longitudeI * 1e-7,
|
||||
b.latitudeI * 1e-7,
|
||||
b.longitudeI * 1e-7
|
||||
)
|
||||
}
|
||||
fun positionToMeter(a: MeshProtos.Position, b: MeshProtos.Position): Double =
|
||||
latLongToMeter(a.latitudeI * 1e-7, a.longitudeI * 1e-7, b.latitudeI * 1e-7, b.longitudeI * 1e-7)
|
||||
|
||||
/**
|
||||
* Convert degrees/mins/secs to a single double
|
||||
|
@ -231,44 +213,24 @@ fun positionToMeter(a: MeshProtos.Position, b: MeshProtos.Position): Double {
|
|||
* @param isPostive
|
||||
* @return
|
||||
*/
|
||||
fun DMSToDegrees(
|
||||
degrees: Int,
|
||||
minutes: Int,
|
||||
seconds: Float,
|
||||
isPostive: Boolean
|
||||
): Double {
|
||||
return (if (isPostive) 1 else -1) * (degrees + minutes / 60.0 + seconds / 3600.0)
|
||||
}
|
||||
@Suppress("MagicNumber")
|
||||
fun dmsToDegrees(degrees: Int, minutes: Int, seconds: Float, isPostive: Boolean): Double =
|
||||
(if (isPostive) 1 else -1) * (degrees + minutes / 60.0 + seconds / 3600.0)
|
||||
|
||||
fun DMSToDegrees(
|
||||
degrees: Double,
|
||||
minutes: Double,
|
||||
seconds: Double,
|
||||
isPostive: Boolean
|
||||
): Double {
|
||||
return (if (isPostive) 1 else -1) * (degrees + minutes / 60.0 + seconds / 3600.0)
|
||||
}
|
||||
@Suppress("MagicNumber")
|
||||
fun dmsToDegrees(degrees: Double, minutes: Double, seconds: Double, isPostive: Boolean): Double =
|
||||
(if (isPostive) 1 else -1) * (degrees + minutes / 60.0 + seconds / 3600.0)
|
||||
|
||||
/**
|
||||
* Computes the bearing in degrees between two points on Earth.
|
||||
*
|
||||
* @param lat1
|
||||
* Latitude of the first point
|
||||
* @param lon1
|
||||
* Longitude of the first point
|
||||
* @param lat2
|
||||
* 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.
|
||||
* @param lat1 Latitude of the first point
|
||||
* @param lon1 Longitude of the first point
|
||||
* @param lat2 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(
|
||||
lat1: Double,
|
||||
lon1: Double,
|
||||
lat2: Double,
|
||||
lon2: Double
|
||||
): Double {
|
||||
fun bearing(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
|
||||
val lat1Rad = Math.toRadians(lat1)
|
||||
val lat2Rad = Math.toRadians(lat2)
|
||||
val deltaLonRad = Math.toRadians(lon2 - lon1)
|
||||
|
@ -277,17 +239,16 @@ fun bearing(
|
|||
return radToBearing(atan2(y, x))
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an angle in radians to degrees
|
||||
*/
|
||||
fun radToBearing(rad: Double): Double {
|
||||
return (Math.toDegrees(rad) + 360) % 360
|
||||
}
|
||||
/** Converts an angle in radians to degrees */
|
||||
@Suppress("MagicNumber")
|
||||
fun radToBearing(rad: Double): Double = (Math.toDegrees(rad) + 360) % 360
|
||||
|
||||
/**
|
||||
* Calculates the zoom level required to fit the entire [BoundingBox] inside the map view.
|
||||
*
|
||||
* @return The zoom level as a Double value.
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
fun BoundingBox.requiredZoomLevel(): Double {
|
||||
val topLeft = GeoPoint(this.latNorth, this.lonWest)
|
||||
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].
|
||||
*
|
||||
* @return A new [BoundingBox] with added [zoomFactor]. Example:
|
||||
* ```
|
||||
* // Setting the zoom level directly using setZoom()
|
||||
|
@ -322,6 +284,6 @@ fun BoundingBox.zoomIn(zoomFactor: Double): BoundingBox {
|
|||
center.latitude + newLatDiff / 2,
|
||||
center.longitude + newLonDiff / 2,
|
||||
center.latitude - newLatDiff / 2,
|
||||
center.longitude - newLonDiff / 2
|
||||
center.longitude - newLonDiff / 2,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ class MainActivity : AppCompatActivity() {
|
|||
private var isMeshServiceBound = false
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
|
||||
@Suppress("TooGenericExceptionCaught", "LongMethod")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
this.enableEdgeToEdge()
|
||||
|
@ -109,6 +110,7 @@ class MainActivity : AppCompatActivity() {
|
|||
val meshtasticReceiver: BroadcastReceiver =
|
||||
object : BroadcastReceiver() {
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Suppress("ReturnCount")
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent == null) {
|
||||
Log.w(TAG, "Received null intent")
|
||||
|
@ -166,7 +168,7 @@ class MainActivity : AppCompatActivity() {
|
|||
Log.d(TAG, "Position App NodeInfo: $ni")
|
||||
mainTextView.text = "Position App NodeInfo: $ni"
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "onReceive: $e")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -189,7 +191,8 @@ class MainActivity : AppCompatActivity() {
|
|||
|
||||
while (!bindMeshService()) {
|
||||
try {
|
||||
Thread.sleep(1000)
|
||||
@Suppress("MagicNumber")
|
||||
Thread.sleep(1_000)
|
||||
} catch (e: InterruptedException) {
|
||||
Log.e(TAG, "Binding interrupted", e)
|
||||
break
|
||||
|
|
Ładowanie…
Reference in New Issue