feat: implement MSL altitude using `AltitudeConverterCompat` (#1094)

- Added `androidx.core:core-location-altitude:1.0.0-alpha02`;
- Implemented `AltitudeConverterCompat.addMslAltitudeToLocation(context, location)` to convert `altitude` (above the WGS84 reference ellipsoid) to Mean Sea Level (MSL) and add MSL altitude and accuracy to the location object.

Reference:
- https://issuetracker.google.com/issues/195660815
- Brian Julian and Michael Angermann. "Resource efficient and accurate altitude conversion to Mean Sea Level." [2023 IEEE/ION Position, Location and Navigation Symposium (PLANS)](https://www.ion.org/plans/abstracts.cfm?paperID=12011).
pull/1088/head
Andre K 2024-06-13 07:26:56 -03:00 zatwierdzone przez GitHub
rodzic 8c53908eb5
commit 001b18be95
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
3 zmienionych plików z 36 dodań i 36 usunięć

Wyświetl plik

@ -152,6 +152,7 @@ dependencies {
implementation "androidx.emoji2:emoji2-emojipicker:1.4.0" implementation "androidx.emoji2:emoji2-emojipicker:1.4.0"
implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.core:core-location-altitude:1.0.0-alpha02'
implementation 'androidx.fragment:fragment-ktx:1.7.1' implementation 'androidx.fragment:fragment-ktx:1.7.1'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2' implementation 'androidx.recyclerview:recyclerview:1.3.2'

Wyświetl plik

@ -3,9 +3,11 @@ package com.geeksville.mesh.repository.location
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Application import android.app.Application
import android.location.LocationManager import android.location.LocationManager
import androidx.core.location.LocationCompat
import androidx.core.location.LocationListenerCompat import androidx.core.location.LocationListenerCompat
import androidx.core.location.LocationManagerCompat import androidx.core.location.LocationManagerCompat
import androidx.core.location.LocationRequestCompat import androidx.core.location.LocationRequestCompat
import androidx.core.location.altitude.AltitudeConverterCompat
import com.geeksville.mesh.android.GeeksvilleApplication import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.Logging import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.hasBackgroundPermission import com.geeksville.mesh.android.hasBackgroundPermission
@ -43,6 +45,13 @@ class LocationRepository @Inject constructor(
.build() .build()
val locationListener = LocationListenerCompat { location -> val locationListener = LocationListenerCompat { location ->
if (location.hasAltitude() && !LocationCompat.hasMslAltitude(location)) {
try {
AltitudeConverterCompat.addMslAltitudeToLocation(context, location)
} catch (e: Exception) {
errormsg("addMslAltitudeToLocation() failed", e)
}
}
// info("New location: $location") // info("New location: $location")
trySend(location) trySend(location)
} }

Wyświetl plik

@ -7,6 +7,7 @@ import android.content.pm.ServiceInfo
import android.os.IBinder import android.os.IBinder
import android.os.RemoteException import android.os.RemoteException
import androidx.core.app.ServiceCompat import androidx.core.app.ServiceCompat
import androidx.core.location.LocationCompat
import com.geeksville.mesh.analytics.DataPair import com.geeksville.mesh.analytics.DataPair
import com.geeksville.mesh.android.GeeksvilleApplication import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.Logging import com.geeksville.mesh.android.Logging
@ -175,16 +176,19 @@ class MeshService : Service(), Logging {
if (locationFlow?.isActive == true) return if (locationFlow?.isActive == true) return
if (hasBackgroundPermission()) { if (hasBackgroundPermission()) {
locationFlow = locationRepository.getLocations() locationFlow = locationRepository.getLocations().onEach { location ->
.onEach { location ->
sendPosition( sendPosition(
location.latitude, position {
location.longitude, latitudeI = Position.degI(location.latitude)
location.altitude.toInt(), longitudeI = Position.degI(location.longitude)
(location.time / 1000).toInt(), altitude = LocationCompat.getMslAltitudeMeters(location).toInt()
) altitudeHae = location.altitude.toInt()
time = (location.time / 1000).toInt()
groundSpeed = location.speed.toInt()
groundTrack = location.bearing.toInt()
} }
.launchIn(serviceScope) )
}.launchIn(serviceScope)
} }
} }
@ -802,14 +806,15 @@ class MeshService : Service(), Logging {
// Nodes periodically send out position updates, but those updates might not contain a lat & lon (because no GPS lock) // Nodes periodically send out position updates, but those updates might not contain a lat & lon (because no GPS lock)
// We like to look at the local node to see if it has been sending out valid lat/lon, so for the LOCAL node (only) // We like to look at the local node to see if it has been sending out valid lat/lon, so for the LOCAL node (only)
// we don't record these nop position updates // we don't record these nop position updates
if (myNodeNum == fromNum && p.latitudeI == 0 && p.longitudeI == 0) if (myNodeNum == fromNum && p.latitudeI == 0 && p.longitudeI == 0) {
debug("Ignoring nop position update for the local node") debug("Ignoring nop position update for the local node")
else } else {
updateNodeInfo(fromNum) { updateNodeInfo(fromNum) {
debug("update position: ${it.user?.longName?.toPIIString()} with ${p.toPIIString()}") debug("update position: ${it.user?.longName?.toPIIString()} with ${p.toPIIString()}")
it.position = Position(p, (defaultTime / 1000L).toInt()) it.position = Position(p, (defaultTime / 1000L).toInt())
} }
} }
}
/// Update our DB of users based on someone sending out a Telemetry subpacket /// Update our DB of users based on someone sending out a Telemetry subpacket
private fun handleReceivedTelemetry( private fun handleReceivedTelemetry(
@ -1573,10 +1578,7 @@ class MeshService : Service(), Logging {
* Send a position (typically from our built in GPS) into the mesh. * Send a position (typically from our built in GPS) into the mesh.
*/ */
private fun sendPosition( private fun sendPosition(
lat: Double = 0.0, position: MeshProtos.Position,
lon: Double = 0.0,
alt: Int = 0,
time: Int = currentSecond(),
destNum: Int? = null, destNum: Int? = null,
wantResponse: Boolean = false wantResponse: Boolean = false
) { ) {
@ -1584,30 +1586,19 @@ class MeshService : Service(), Logging {
val mi = myNodeInfo val mi = myNodeInfo
if (mi != null) { if (mi != null) {
val idNum = destNum ?: mi.myNodeNum // when null we just send to the local node val idNum = destNum ?: mi.myNodeNum // when null we just send to the local node
debug("Sending our position/time to=$idNum lat=${lat.anonymize}, lon=${lon.anonymize}, alt=$alt, time=$time") debug("Sending our position/time to=$idNum ${Position(position)}")
val position = MeshProtos.Position.newBuilder().also {
it.longitudeI = Position.degI(lon)
it.latitudeI = Position.degI(lat)
it.altitude = alt
it.time = time
}.build()
// Also update our own map for our nodeNum, by handling the packet just like packets from other users // Also update our own map for our nodeNum, by handling the packet just like packets from other users
handleReceivedPosition(mi.myNodeNum, position) handleReceivedPosition(mi.myNodeNum, position)
val fullPacket = newMeshPacketTo(idNum).buildMeshPacket( sendToRadio(newMeshPacketTo(idNum).buildMeshPacket(
channel = if (destNum == null) 0 else nodeDBbyNodeNum[destNum]?.channel ?: 0, channel = if (destNum == null) 0 else nodeDBbyNodeNum[destNum]?.channel ?: 0,
priority = MeshPacket.Priority.BACKGROUND, priority = MeshPacket.Priority.BACKGROUND,
) { ) {
portnumValue = Portnums.PortNum.POSITION_APP_VALUE portnumValue = Portnums.PortNum.POSITION_APP_VALUE
payload = position.toByteString() payload = position.toByteString()
this.wantResponse = wantResponse this.wantResponse = wantResponse
} })
// send the packet into the mesh
sendToRadio(fullPacket)
} }
} catch (ex: BLEException) { } catch (ex: BLEException) {
warn("Ignoring disconnected radio during gps location update") warn("Ignoring disconnected radio during gps location update")
@ -1916,16 +1907,15 @@ class MeshService : Service(), Logging {
wantResponse = true wantResponse = true
}) })
} else { } else {
// send fixed position (local only/no remote method, so we force destNum to null) // send fixed position (local only/no remote method)
val (lat, lon, alt) = position
sendPosition(destNum = null, lat = lat, lon = lon, alt = alt)
sendToRadio(newMeshPacketTo(destNum).buildAdminPacket { sendToRadio(newMeshPacketTo(destNum).buildAdminPacket {
if (position != Position(0.0, 0.0, 0)) { if (position != Position(0.0, 0.0, 0)) {
setFixedPosition = position { setFixedPosition = position {
longitudeI = Position.degI(lon) latitudeI = Position.degI(position.latitude)
latitudeI = Position.degI(lat) longitudeI = Position.degI(position.longitude)
altitude = alt altitude = position.altitude
} }
.also { sendPosition(it) } // TODO remove after minDeviceVersion >= 2.3.3
} else { } else {
removeFixedPosition = true removeFixedPosition = true
} }