kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
add osm map features
rodzic
4bb8e2f0e4
commit
5d95017efa
|
@ -141,6 +141,7 @@ dependencies {
|
|||
|
||||
//OSMAND
|
||||
implementation 'org.osmdroid:osmdroid-android:6.1.14'
|
||||
implementation 'mil.nga:mgrs:2.0.0'
|
||||
|
||||
// optional - Kotlin Extensions and Coroutines support for Room
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
|
|
|
@ -120,7 +120,9 @@ data class NodeInfo(
|
|||
var deviceMetrics: DeviceMetrics? = null
|
||||
) : Parcelable {
|
||||
|
||||
val batteryPctLevel get() = deviceMetrics?.batteryLevel
|
||||
val batteryLevel get() = deviceMetrics?.batteryLevel
|
||||
val voltage get() = deviceMetrics?.voltage
|
||||
val batteryStr get() = String.format("%d%% %.2fV", batteryLevel, voltage ?: 0)
|
||||
|
||||
/**
|
||||
* true if the device was heard from recently
|
||||
|
@ -150,6 +152,13 @@ data class NodeInfo(
|
|||
return if (p != null && op != null) p.distance(op).toInt() else null
|
||||
}
|
||||
|
||||
/// @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
|
||||
fun distanceStr(o: NodeInfo?) = distance(o)?.let { dist ->
|
||||
when {
|
||||
|
|
|
@ -19,9 +19,9 @@ import com.geeksville.mesh.database.entity.Packet
|
|||
import com.geeksville.mesh.database.entity.QuickChatAction
|
||||
import com.geeksville.mesh.repository.datastore.LocalConfigRepository
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.util.GPSFormat
|
||||
import com.geeksville.mesh.util.positionToMeter
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
@ -215,6 +215,16 @@ class UIViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun gpsString(pos: Position): String {
|
||||
return when (_localConfig.value?.display?.gpsFormat) {
|
||||
ConfigProtos.Config.DisplayConfig.GpsCoordinateFormat.GpsFormatDec -> GPSFormat.dec(pos)
|
||||
ConfigProtos.Config.DisplayConfig.GpsCoordinateFormat.GpsFormatDMS -> GPSFormat.toDMS(pos)
|
||||
ConfigProtos.Config.DisplayConfig.GpsCoordinateFormat.GpsFormatUTM -> GPSFormat.toUTM(pos)
|
||||
ConfigProtos.Config.DisplayConfig.GpsCoordinateFormat.GpsFormatMGRS -> GPSFormat.toMGRS(pos)
|
||||
else -> GPSFormat.dec(pos)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
val isRouter: Boolean =
|
||||
localConfig.value?.device?.role == ConfigProtos.Config.DeviceConfig.Role.Router
|
||||
|
|
|
@ -70,7 +70,7 @@ class MapFragment : ScreenFragment("Map"), Logging {
|
|||
map.let {
|
||||
if (view != null) {
|
||||
mapController = map.controller
|
||||
binding.fabStyleToggle.setOnClickListener {
|
||||
binding.mapStyleButton.setOnClickListener {
|
||||
chooseMapStyle()
|
||||
}
|
||||
model.nodeDB.nodes.value?.let { nodes ->
|
||||
|
@ -121,17 +121,22 @@ class MapFragment : ScreenFragment("Map"), Logging {
|
|||
val mrkr = nodesWithPosition.map { node ->
|
||||
val p = node.position!!
|
||||
debug("Showing on map: $node")
|
||||
val f = GeoPoint(p.latitude, p.longitude)
|
||||
lateinit var marker: MarkerWithLabel
|
||||
node.user?.let {
|
||||
val label = it.longName + " " + formatAgo(p.time)
|
||||
marker = MarkerWithLabel(map, label)
|
||||
marker.title = label
|
||||
marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
|
||||
marker.position = f
|
||||
marker.title = buildString {
|
||||
append("$label ${node.batteryStr}\n${model.gpsString(p)}")
|
||||
model.nodeDB.ourNodeInfo?.let { our ->
|
||||
val dist = our.distanceStr(node)
|
||||
if (dist != null) append(" (${our.bearing(node)}° $dist)")
|
||||
}
|
||||
}
|
||||
marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
||||
marker.position = GeoPoint(p.latitude, p.longitude)
|
||||
marker.icon = ContextCompat.getDrawable(
|
||||
requireActivity(),
|
||||
R.drawable.ic_twotone_person_pin_24
|
||||
R.drawable.ic_twotone_location_on_24
|
||||
)
|
||||
}
|
||||
marker
|
||||
|
@ -238,12 +243,12 @@ class MapFragment : ScreenFragment("Map"), Logging {
|
|||
val p = mPositionPixels
|
||||
|
||||
val textPaint = Paint()
|
||||
textPaint.textSize = 50f
|
||||
textPaint.textSize = 40f
|
||||
textPaint.color = Color.RED
|
||||
textPaint.isAntiAlias = true
|
||||
textPaint.textAlign = Paint.Align.CENTER
|
||||
|
||||
c.drawText(mLabel, (p.x - 0).toFloat(), (p.y - 60).toFloat(), textPaint)
|
||||
c.drawText(mLabel, (p.x - 0f), (p.y - 80f), textPaint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,15 +114,9 @@ class UsersFragment : ScreenFragment("Users"), Logging {
|
|||
|
||||
val pos = n.validPosition
|
||||
if (pos != null) {
|
||||
val coords =
|
||||
String.format("%.5f %.5f", pos.latitude, pos.longitude).replace(",", ".")
|
||||
val html =
|
||||
"<a href='geo:${pos.latitude},${pos.longitude}?z=17&label=${
|
||||
URLEncoder.encode(
|
||||
name,
|
||||
"utf-8"
|
||||
)
|
||||
}'>${coords}</a>"
|
||||
val html = "<a href='geo:${pos.latitude},${pos.longitude}?z=17&label=${
|
||||
URLEncoder.encode(name, "utf-8")
|
||||
}'>${model.gpsString(pos)}</a>"
|
||||
holder.coordsView.text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||
holder.coordsView.movementMethod = LinkMovementMethod.getInstance()
|
||||
holder.coordsView.visibility = View.VISIBLE
|
||||
|
@ -138,7 +132,7 @@ class UsersFragment : ScreenFragment("Users"), Logging {
|
|||
} else {
|
||||
holder.distanceView.visibility = View.INVISIBLE
|
||||
}
|
||||
renderBattery(n.batteryPctLevel, n.deviceMetrics?.voltage, holder)
|
||||
renderBattery(n.batteryLevel, n.voltage, holder)
|
||||
|
||||
holder.lastTime.text = formatAgo(n.lastHeard)
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package com.geeksville.mesh.util
|
||||
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.Position
|
||||
import mil.nga.grid.features.Point
|
||||
import mil.nga.mgrs.MGRS
|
||||
import mil.nga.mgrs.utm.UTM
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.acos
|
||||
import kotlin.math.atan2
|
||||
|
@ -15,6 +19,42 @@ import kotlin.math.sin
|
|||
* 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 toDMS(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])
|
||||
return string(lat) + " " + string(lon)
|
||||
}
|
||||
|
||||
fun toUTM(p: Position): String {
|
||||
val UTM = UTM.from(Point.point(p.longitude, p.latitude))
|
||||
return String.format(
|
||||
"%s%s %.6s %.7s",
|
||||
UTM.zone,
|
||||
UTM.toMGRS().band,
|
||||
UTM.easting,
|
||||
UTM.northing
|
||||
)
|
||||
}
|
||||
|
||||
fun toMGRS(p: Position): String {
|
||||
val MGRS = MGRS.from(Point.point(p.longitude, p.latitude))
|
||||
return String.format(
|
||||
"%s%s %s%s %05d %05d",
|
||||
MGRS.zone,
|
||||
MGRS.band,
|
||||
MGRS.column,
|
||||
MGRS.row,
|
||||
MGRS.easting,
|
||||
MGRS.northing
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format as degrees, minutes, secs
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M11.99,18.54l-7.37,-5.73L3,14.07l9,7 9,-7 -1.63,-1.27 -7.38,5.74zM12,16l7.36,-5.73L21,9l-9,-7 -9,7 1.63,1.27L12,16z"/>
|
||||
|
||||
</vector>
|
|
@ -0,0 +1,8 @@
|
|||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillAlpha="0.3"
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6.26,9L12,13.47 17.74,9 12,4.53z" android:strokeAlpha="0.3"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19.37,12.8l-7.38,5.74 -7.37,-5.73L3,14.07l9,7 9,-7zM12,2L3,9l1.63,1.27L12,16l7.36,-5.73L21,9l-9,-7zM12,13.47L6.26,9 12,4.53 17.74,9 12,13.47z"/>
|
||||
</vector>
|
|
@ -0,0 +1,18 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#3388ff"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillAlpha="0.3"
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,4C9.24,4 7,6.24 7,9c0,2.85 2.92,7.21 5,9.88 2.11,-2.69 5,-7 5,-9.88 0,-2.76 -2.24,-5 -5,-5zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"
|
||||
android:strokeAlpha="0.3" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM7,9c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,2.88 -2.88,7.19 -5,9.88C9.92,16.21 7,11.85 7,9z" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,9m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0" />
|
||||
</vector>
|
|
@ -1,15 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M9.83,18l0.59,0.59L12,20.17l1.59,-1.59 0.58,-0.58H19V4H5v14h4.83zM12,5c1.65,0 3,1.35 3,3s-1.35,3 -3,3 -3,-1.35 -3,-3 1.35,-3 3,-3zM6,15.58C6,13.08 9.97,12 12,12s6,1.08 6,3.58V17H6v-1.42z"
|
||||
android:strokeAlpha="0.3"
|
||||
android:fillAlpha="0.3"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M9,20l3,3 3,-3h4c1.1,0 2,-0.9 2,-2L21,4c0,-1.1 -0.9,-2 -2,-2L5,2c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h4zM5,4h14v14h-4.83l-0.59,0.59L12,20.17l-1.59,-1.59 -0.58,-0.58L5,18L5,4zM12,11c1.65,0 3,-1.35 3,-3s-1.35,-3 -3,-3 -3,1.35 -3,3 1.35,3 3,3zM12,7c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM18,15.58c0,-2.5 -3.97,-3.58 -6,-3.58s-6,1.08 -6,3.58L6,17h12v-1.42zM8.48,15c0.74,-0.51 2.23,-1 3.52,-1s2.78,0.49 3.52,1L8.48,15z"/>
|
||||
</vector>
|
|
@ -10,6 +10,20 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/mapStyleButton"
|
||||
android:layout_width="48sp"
|
||||
android:layout_height="56sp"
|
||||
android:layout_margin="8dp"
|
||||
android:backgroundTint="@color/colorAdvancedBackground"
|
||||
android:contentDescription="@string/style_selection"
|
||||
app:icon="@drawable/ic_twotone_layers_24"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="0dp"
|
||||
app:iconTint="@color/colorIconTint"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_style_toggle"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -18,9 +32,8 @@
|
|||
android:backgroundTint="@color/design_default_color_secondary"
|
||||
android:contentDescription="@string/style_selection"
|
||||
android:orientation="vertical"
|
||||
android:src="@drawable/baseline_layers_white_24dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:visibility="visible"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:background="@color/design_default_color_secondary" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
Ładowanie…
Reference in New Issue