From 5d95017efad56b895fb2d6f66b0d404c910a5908 Mon Sep 17 00:00:00 2001 From: andrekir Date: Tue, 30 Aug 2022 17:25:11 -0300 Subject: [PATCH] add osm map features --- app/build.gradle | 1 + .../main/java/com/geeksville/mesh/NodeInfo.kt | 11 ++++- .../java/com/geeksville/mesh/model/UIState.kt | 12 +++++- .../com/geeksville/mesh/ui/MapFragment.kt | 21 ++++++---- .../com/geeksville/mesh/ui/UsersFragment.kt | 14 ++----- .../com/geeksville/mesh/util/LocationUtils.kt | 40 +++++++++++++++++++ .../drawable/baseline_layers_white_24dp.xml | 6 --- .../res/drawable/ic_twotone_layers_24.xml | 8 ++++ .../drawable/ic_twotone_location_on_24.xml | 18 +++++++++ .../res/drawable/ic_twotone_person_pin_24.xml | 15 ------- app/src/main/res/layout/map_view.xml | 17 +++++++- 11 files changed, 120 insertions(+), 43 deletions(-) delete mode 100644 app/src/main/res/drawable/baseline_layers_white_24dp.xml create mode 100644 app/src/main/res/drawable/ic_twotone_layers_24.xml create mode 100644 app/src/main/res/drawable/ic_twotone_location_on_24.xml delete mode 100644 app/src/main/res/drawable/ic_twotone_person_pin_24.xml diff --git a/app/build.gradle b/app/build.gradle index d9fa7629..f22a408f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" diff --git a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt index 6500376e..e533c197 100644 --- a/app/src/main/java/com/geeksville/mesh/NodeInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/NodeInfo.kt @@ -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 { diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index 3493df44..741876ea 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt index fcfc5f9d..95e4855f 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt @@ -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) } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index c35ba338..8e20da53 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -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 = - "${coords}" + val html = "${model.gpsString(pos)}" 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) diff --git a/app/src/main/java/com/geeksville/mesh/util/LocationUtils.kt b/app/src/main/java/com/geeksville/mesh/util/LocationUtils.kt index bcc5070c..e1f09c7b 100644 --- a/app/src/main/java/com/geeksville/mesh/util/LocationUtils.kt +++ b/app/src/main/java/com/geeksville/mesh/util/LocationUtils.kt @@ -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.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 diff --git a/app/src/main/res/drawable/baseline_layers_white_24dp.xml b/app/src/main/res/drawable/baseline_layers_white_24dp.xml deleted file mode 100644 index 02fd4892..00000000 --- a/app/src/main/res/drawable/baseline_layers_white_24dp.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/ic_twotone_layers_24.xml b/app/src/main/res/drawable/ic_twotone_layers_24.xml new file mode 100644 index 00000000..489b8775 --- /dev/null +++ b/app/src/main/res/drawable/ic_twotone_layers_24.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/drawable/ic_twotone_location_on_24.xml b/app/src/main/res/drawable/ic_twotone_location_on_24.xml new file mode 100644 index 00000000..0cdba5ab --- /dev/null +++ b/app/src/main/res/drawable/ic_twotone_location_on_24.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_twotone_person_pin_24.xml b/app/src/main/res/drawable/ic_twotone_person_pin_24.xml deleted file mode 100644 index 407fb4f9..00000000 --- a/app/src/main/res/drawable/ic_twotone_person_pin_24.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - diff --git a/app/src/main/res/layout/map_view.xml b/app/src/main/res/layout/map_view.xml index 7ed1b0c4..970f2aea 100644 --- a/app/src/main/res/layout/map_view.xml +++ b/app/src/main/res/layout/map_view.xml @@ -10,6 +10,20 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> + + \ No newline at end of file