Fixes and refactoring for LocationSource

pull/122/head
Arty Bishop 2023-03-18 15:29:49 +00:00
rodzic 8f66cd379b
commit e47923f693
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 5C71CFDC37AD73CC
19 zmienionych plików z 125 dodań i 95 usunięć

Wyświetl plik

@ -18,73 +18,67 @@
package com.rtbishop.look4sat.framework
import android.location.Criteria
import android.location.Location
import android.location.LocationManager
import androidx.core.location.LocationListenerCompat
import androidx.core.location.LocationManagerCompat
import androidx.core.os.CancellationSignal
import com.rtbishop.look4sat.domain.ILocationSource
import com.rtbishop.look4sat.domain.ISettingsSource
import com.rtbishop.look4sat.model.StationPos
import com.rtbishop.look4sat.model.GeoPos
import com.rtbishop.look4sat.utility.QthConverter
import com.rtbishop.look4sat.utility.round
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import timber.log.Timber
import java.util.concurrent.Executors
class LocationSource(
private val manager: LocationManager,
private val settings: ISettingsSource,
) : ILocationSource, LocationListenerCompat {
) : ILocationSource {
private val defaultProvider = LocationManager.PASSIVE_PROVIDER
private val _stationPosition = MutableStateFlow(settings.loadStationPosition())
override val stationPosition: StateFlow<StationPos> = _stationPosition
private val defaultProvider = LocationManager.PASSIVE_PROVIDER
private val executor = Executors.newSingleThreadExecutor()
private val timeoutSignal = CancellationSignal().apply {
setOnCancelListener { _stationPosition.value = _stationPosition.value }
}
override val stationPosition: StateFlow<GeoPos> = _stationPosition
override fun setGpsPosition(): Boolean {
if (!LocationManagerCompat.isLocationEnabled(manager)) return false
val criteria = Criteria().apply { isCostAllowed = true }
val bestProvider = manager.getBestProvider(criteria, true) ?: defaultProvider
forceLocationUpdate(bestProvider)
try {
val criteria = Criteria().apply { isCostAllowed = true }
val provider = manager.getBestProvider(criteria, true) ?: defaultProvider
Timber.d("Requesting location for $provider provider")
LocationManagerCompat.getCurrentLocation(manager, provider, timeoutSignal, executor) {
it?.let { setGeoPosition(it.latitude, it.longitude, it.altitude) }
}
} catch (exception: SecurityException) {
Timber.d("No permissions were given")
}
return true
}
override fun setGeoPosition(latitude: Double, longitude: Double): Boolean {
manager.removeUpdates(this)
override fun setGeoPosition(latitude: Double, longitude: Double, altitude: Double): Boolean {
val newLongitude = if (longitude > 180.0) longitude - 180 else longitude
val locator = QthConverter.positionToQth(latitude, newLongitude) ?: return false
saveStationPosition(latitude, newLongitude, locator)
saveGeoPos(latitude, newLongitude, altitude, locator)
return true
}
override fun setQthPosition(locator: String): Boolean {
val position = QthConverter.qthToPosition(locator) ?: return false
saveStationPosition(position.lat, position.lon, locator)
saveGeoPos(position.latitude, position.longitude, 0.0, locator)
return true
}
override fun onLocationChanged(location: Location) {
setGeoPosition(location.latitude, location.longitude)
}
private fun forceLocationUpdate(provider: String) {
try {
val location = manager.getLastKnownLocation(defaultProvider)
if (location == null || System.currentTimeMillis() - location.time > 600000) {
Timber.d("Requesting $provider location update")
manager.requestLocationUpdates(provider, 0L, 0f, this)
} else {
setGeoPosition(location.latitude, location.longitude)
}
} catch (exception: SecurityException) {
Timber.d("No permissions were given")
}
}
private fun saveStationPosition(latitude: Double, longitude: Double, locator: String) {
private fun saveGeoPos(latitude: Double, longitude: Double, altitude: Double, locator: String) {
val newLat = latitude.round(4)
val newLon = longitude.round(4)
Timber.d("Received new Position($newLat, $newLon) & Locator $locator")
_stationPosition.value = StationPos(newLat, newLon, locator, System.currentTimeMillis())
val newAlt = altitude.round(1)
val timestamp = System.currentTimeMillis()
Timber.d("Received new Position($newLat, $newLon, $newAlt) & Locator $locator")
_stationPosition.value = GeoPos(newLat, newLon, newAlt, locator, timestamp)
settings.saveStationPosition(stationPosition.value)
}
}

Wyświetl plik

@ -20,7 +20,10 @@ package com.rtbishop.look4sat.framework
import android.content.SharedPreferences
import androidx.core.content.edit
import com.rtbishop.look4sat.domain.ISettingsSource
import com.rtbishop.look4sat.model.StationPos
import com.rtbishop.look4sat.model.GeoPos
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.onCompletion
class SettingsSource(private val prefs: SharedPreferences) : ISettingsSource {
@ -42,22 +45,25 @@ class SettingsSource(private val prefs: SharedPreferences) : ISettingsSource {
const val keyBTFormat = "BTFormat"
const val keyLatitude = "stationLat"
const val keyLongitude = "stationLon"
const val keyAltitude = "stationAlt"
const val keyLocator = "stationQTH"
const val keyLocTimestamp = "locTimestamp"
const val keySelection = "selection"
}
override fun loadStationPosition(): StationPos {
val latitude = prefs.getString(keyLatitude, null) ?: "0.0"
val longitude = prefs.getString(keyLongitude, null) ?: "0.0"
override fun loadStationPosition(): GeoPos {
val latitude = (prefs.getString(keyLatitude, null) ?: "0.0").toDouble()
val longitude = (prefs.getString(keyLongitude, null) ?: "0.0").toDouble()
val altitude = (prefs.getString(keyAltitude, null) ?: "0.0").toDouble()
val qthLocator = prefs.getString(keyLocator, null) ?: "null"
val timestamp = prefs.getLong(keyLocTimestamp, 0L)
return StationPos(latitude.toDouble(), longitude.toDouble(), qthLocator, timestamp)
return GeoPos(latitude, longitude, altitude, qthLocator, timestamp)
}
override fun saveStationPosition(stationPos: StationPos) = prefs.edit {
override fun saveStationPosition(stationPos: GeoPos) = prefs.edit {
putString(keyLatitude, stationPos.latitude.toString())
putString(keyLongitude, stationPos.longitude.toString())
putString(keyAltitude, stationPos.altitude.toString())
putString(keyLocator, stationPos.qthLocator)
putLong(keyLocTimestamp, stationPos.timestamp)
}
@ -209,4 +215,24 @@ class SettingsSource(private val prefs: SharedPreferences) : ISettingsSource {
private fun SharedPreferences.Editor.putDouble(key: String, double: Double) {
putLong(key, double.toRawBits())
}
inline fun <reified T> SharedPreferences.observeKey(key: String, default: T): Flow<T> {
val flow = MutableStateFlow(getItem(key, default))
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, k ->
if (key == k) flow.value = getItem(key, default)
}
registerOnSharedPreferenceChangeListener(listener)
return flow.onCompletion { unregisterOnSharedPreferenceChangeListener(listener) }
}
inline fun <reified T> SharedPreferences.getItem(key: String, default: T): T {
return when (default) {
is Boolean -> getBoolean(key, default) as T
is Int -> getInt(key, default) as T
is Long -> getLong(key, default) as T
is Float -> getFloat(key, default) as T
is String -> getString(key, default) as T
else -> throw IllegalArgumentException("Could not handle ${T::class.java.name}")
}
}
}

Wyświetl plik

@ -6,10 +6,6 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.PlayArrow
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@ -96,7 +92,8 @@ private fun TopBar(setQuery: (String) -> Unit, saveSelection: () -> Unit) {
@Composable
private fun SearchBar(setQuery: (String) -> Unit, modifier: Modifier = Modifier) {
val currentQuery = rememberSaveable { mutableStateOf("") }
OutlinedTextField(value = currentQuery.value,
OutlinedTextField(
value = currentQuery.value,
onValueChange = { newValue ->
currentQuery.value = newValue
setQuery(newValue)
@ -104,9 +101,14 @@ private fun SearchBar(setQuery: (String) -> Unit, modifier: Modifier = Modifier)
maxLines = 1,
// label = { Text(text = stringResource(id = R.string.entries_search_hint)) },
placeholder = { Text(text = stringResource(id = R.string.entries_search_hint)) },
leadingIcon = { Icon(imageVector = Icons.Outlined.Search, contentDescription = null) },
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.ic_search),
contentDescription = null
)
},
trailingIcon = {
Icon(imageVector = Icons.Outlined.Close,
Icon(painter = painterResource(id = R.drawable.ic_close),
contentDescription = null,
modifier = Modifier.onClick {
currentQuery.value = ""
@ -150,7 +152,7 @@ private fun EntryTypeCard(type: String, onClick: () -> Unit, modifier: Modifier
.height(48.dp)
.clickable { onClick() }) {
Icon(
imageVector = Icons.Outlined.PlayArrow,
painter = painterResource(id = R.drawable.ic_next),
contentDescription = null,
modifier = Modifier
.size(36.dp)

Wyświetl plik

@ -89,7 +89,7 @@ private fun setStationPosition(stationPos: GeoPos, mapView: MapView) {
setInfoWindow(null)
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
icon = ContextCompat.getDrawable(mapView.context, R.drawable.ic_position)
position = GeoPoint(stationPos.lat, stationPos.lon)
position = GeoPoint(stationPos.latitude, stationPos.longitude)
mapView.overlays[0] = this
mapView.invalidate()
}
@ -109,7 +109,7 @@ private fun setPositions(
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
icon = getCustomTextIcon(it.key.data.name, mapView)
try {
position = GeoPoint(it.value.lat, it.value.lon)
position = GeoPoint(it.value.latitude, it.value.longitude)
} catch (exception: IllegalArgumentException) {
println(exception.stackTraceToString())
}
@ -145,7 +145,7 @@ private fun setSatelliteTrack(satTrack: List<List<GeoPos>>, mapView: MapView) {
val trackOverlay = FolderOverlay()
try {
satTrack.forEach { track ->
val trackPoints = track.map { GeoPoint(it.lat, it.lon) }
val trackPoints = track.map { GeoPoint(it.latitude, it.longitude) }
Polyline().apply {
setPoints(trackPoints)
outlinePaint.set(trackPaint)
@ -160,7 +160,7 @@ private fun setSatelliteTrack(satTrack: List<List<GeoPos>>, mapView: MapView) {
}
private fun setFootprint(satPos: SatPos, mapView: MapView) {
val footprintPoints = satPos.getRangeCircle().map { GeoPoint(it.lat, it.lon) }
val footprintPoints = satPos.getRangeCircle().map { GeoPoint(it.latitude, it.longitude) }
try {
val footprintOverlay = Polyline().apply {
outlinePaint.set(footprintPaint)

Wyświetl plik

@ -53,8 +53,8 @@ class MapViewModel @Inject constructor(
private var selectedSatellite: Satellite? = null
val stationPos = flow {
val osmLat = clipLat(stationPosition.lat)
val osmLon = clipLon(stationPosition.lon)
val osmLat = clipLat(stationPosition.latitude)
val osmLon = clipLon(stationPosition.longitude)
emit(GeoPos(osmLat, osmLon))
}
@ -127,20 +127,20 @@ class MapViewModel @Inject constructor(
val osmLat = clipLat(satPos.latitude.toDegrees())
val osmLon = clipLon(satPos.longitude.toDegrees())
val currentPosition = GeoPos(osmLat, osmLon)
if (oldLongitude < -170.0 && currentPosition.lon > 170.0) {
if (oldLongitude < -170.0 && currentPosition.longitude > 170.0) {
// adding left terminal position
currentTrack.add(GeoPos(osmLat, -180.0))
val finishedTrack = mutableListOf<GeoPos>().apply { addAll(currentTrack) }
satTracks.add(finishedTrack)
currentTrack.clear()
} else if (oldLongitude > 170.0 && currentPosition.lon < -170.0) {
} else if (oldLongitude > 170.0 && currentPosition.longitude < -170.0) {
// adding right terminal position
currentTrack.add(GeoPos(osmLat, 180.0))
val finishedTrack = mutableListOf<GeoPos>().apply { addAll(currentTrack) }
satTracks.add(finishedTrack)
currentTrack.clear()
}
oldLongitude = currentPosition.lon
oldLongitude = currentPosition.longitude
currentTrack.add(currentPosition)
}
satTracks.add(currentTrack)
@ -183,7 +183,7 @@ class MapViewModel @Inject constructor(
val osmLat = clipLat(satPos.latitude.toDegrees())
val osmLon = clipLon(satPos.longitude.toDegrees())
val osmPos = GeoPos(osmLat, osmLon)
val qthLoc = QthConverter.positionToQth(osmPos.lat, osmPos.lon) ?: "-- --"
val qthLoc = QthConverter.positionToQth(osmPos.latitude, osmPos.longitude) ?: "-- --"
val velocity = satPos.getOrbitalVelocity()
val phase = satPos.phase.toDegrees()
val visibility = satPos.eclipsed

Wyświetl plik

@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
@ -94,8 +95,8 @@ private fun TransmitterItem(radio: SatRadio) {
) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
Icon(
painter = painterResource(id = R.drawable.ic_prev),
contentDescription = null
painter = painterResource(id = R.drawable.ic_next),
contentDescription = null, modifier = Modifier.rotate(180f)
)
Text(
text = radio.info,

Wyświetl plik

@ -52,7 +52,7 @@ class RadarViewModel @Inject constructor(
private val locationSource: ILocationSource
) : ViewModel() {
private val stationPos = locationSource.stationPosition.value.toGeoPos()
private val stationPos = locationSource.stationPosition.value
private val magDeclination = getMagDeclination(stationPos)
val transmitters = mutableStateOf<List<SatRadio>>(emptyList())
val radarData = mutableStateOf<RadarData?>(null)
@ -102,8 +102,8 @@ class RadarViewModel @Inject constructor(
fun getShowSweep(): Boolean = settings.isSweepEnabled()
private fun getMagDeclination(geoPos: GeoPos, time: Long = System.currentTimeMillis()): Float {
val latitude = geoPos.lat.toFloat()
val longitude = geoPos.lon.toFloat()
val latitude = geoPos.latitude.toFloat()
val longitude = geoPos.longitude.toFloat()
return GeomagneticField(latitude, longitude, 0f, time).declination
}

Wyświetl plik

@ -23,7 +23,7 @@ import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import com.rtbishop.look4sat.BuildConfig
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.model.StationPos
import com.rtbishop.look4sat.model.GeoPos
import com.rtbishop.look4sat.presentation.CardButton
import com.rtbishop.look4sat.presentation.MainTheme
import com.rtbishop.look4sat.presentation.dialogs.LocatorDialog
@ -164,7 +164,7 @@ private fun CardAbout(version: String, modifier: Modifier = Modifier) {
@Preview(showBackground = true)
@Composable
private fun LocationCardPreview() = MainTheme {
val stationPos = StationPos(0.0, 0.0, "IO91vl", 0L)
val stationPos = GeoPos(0.0, 0.0, 0.0, "IO91vl", 0L)
LocationCard(
settings = LocationSettings(true, stationPos, {}, { _, _ -> }, { }),
setGpsLoc = {}, togglePosDialog = {}) {}

Wyświetl plik

@ -1,10 +1,10 @@
package com.rtbishop.look4sat.presentation.settings
import com.rtbishop.look4sat.model.StationPos
import com.rtbishop.look4sat.model.GeoPos
data class LocationSettings(
val isUpdating: Boolean,
val stationPos: StationPos,
val stationPos: GeoPos,
val setGpsLoc: () -> Unit,
val setManualLoc: (Double, Double) -> Unit,
val setQthLoc: (String) -> Unit

Wyświetl plik

@ -25,7 +25,6 @@ import com.rtbishop.look4sat.domain.ILocationSource
import com.rtbishop.look4sat.domain.ISettingsSource
import com.rtbishop.look4sat.model.DataState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -66,7 +65,6 @@ class SettingsViewModel @Inject constructor(
init {
viewModelScope.launch {
locationSource.stationPosition.collect { stationPos ->
delay(125)
locationSettings.value = locationSettings.value.copy(
isUpdating = false, stationPos = stationPos
)

Wyświetl plik

@ -6,5 +6,5 @@
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M16,6v14l-11,-7z" />
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
</vector>

Wyświetl plik

@ -6,5 +6,5 @@
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M8,6v14l11,-7z" />
android:pathData="M10,8.64L15.27,12 10,15.36V8.64M8,5v14l11,-7L8,5z" />
</vector>

Wyświetl plik

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
</vector>

Wyświetl plik

@ -17,16 +17,16 @@
*/
package com.rtbishop.look4sat.domain
import com.rtbishop.look4sat.model.StationPos
import com.rtbishop.look4sat.model.GeoPos
import kotlinx.coroutines.flow.StateFlow
interface ILocationSource {
val stationPosition: StateFlow<StationPos>
val stationPosition: StateFlow<GeoPos>
fun setGpsPosition(): Boolean
fun setGeoPosition(latitude: Double, longitude: Double): Boolean
fun setGeoPosition(latitude: Double, longitude: Double, altitude: Double = 0.0): Boolean
fun setQthPosition(locator: String): Boolean
}

Wyświetl plik

@ -17,7 +17,7 @@
*/
package com.rtbishop.look4sat.domain
import com.rtbishop.look4sat.model.StationPos
import com.rtbishop.look4sat.model.GeoPos
interface ISettingsSource {
@ -51,9 +51,9 @@ interface ISettingsSource {
"X-Comm" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=x-comm&FORMAT=csv"
)
fun loadStationPosition(): StationPos
fun loadStationPosition(): GeoPos
fun saveStationPosition(stationPos: StationPos)
fun saveStationPosition(stationPos: GeoPos)
fun getHoursAhead(): Int

Wyświetl plik

@ -60,7 +60,7 @@ abstract class Satellite(val data: OrbitalData) {
val apogee = sma * (1.0 + data.eccn) - EARTH_RADIUS
var lin = data.incl
if (lin >= 90.0) lin = 180.0 - lin
acos(EARTH_RADIUS / (apogee + EARTH_RADIUS)) + lin * DEG2RAD > abs(pos.lat * DEG2RAD)
acos(EARTH_RADIUS / (apogee + EARTH_RADIUS)) + lin * DEG2RAD > abs(pos.latitude * DEG2RAD)
}
}
@ -146,8 +146,8 @@ abstract class Satellite(val data: OrbitalData) {
velocityVector.z - obsVel.z
)
magnitude(range)
val sinLat = sin(DEG2RAD * gsPos.lat)
val cosLat = cos(DEG2RAD * gsPos.lat)
val sinLat = sin(DEG2RAD * gsPos.latitude)
val cosLat = cos(DEG2RAD * gsPos.latitude)
val sinTheta = sin(gsPosTheta)
val cosTheta = cos(gsPosTheta)
val topS = sinLat * cosTheta * range.x + sinLat * sinTheta * range.y - cosLat * range.z
@ -173,13 +173,13 @@ abstract class Satellite(val data: OrbitalData) {
obsVel: Vector4
) {
val mFactor = 7.292115E-5
gsPosTheta = mod2PI(thetaGJD(time) + DEG2RAD * gsPos.lon)
val c = invert(sqrt(1.0 + FLAT_FACT * (FLAT_FACT - 2) * sqr(sin(DEG2RAD * gsPos.lat))))
gsPosTheta = mod2PI(thetaGJD(time) + DEG2RAD * gsPos.longitude)
val c = invert(sqrt(1.0 + FLAT_FACT * (FLAT_FACT - 2) * sqr(sin(DEG2RAD * gsPos.latitude))))
val sq = sqr(1.0 - FLAT_FACT) * c
val achcp = (EARTH_RADIUS * c + gsPos.alt / 1000.0) * cos(DEG2RAD * gsPos.lat)
val achcp = (EARTH_RADIUS * c + gsPos.altitude / 1000.0) * cos(DEG2RAD * gsPos.latitude)
obsPos.setXYZ(
achcp * cos(gsPosTheta), achcp * sin(gsPosTheta),
(EARTH_RADIUS * sq + gsPos.alt / 1000.0) * sin(DEG2RAD * gsPos.lat)
(EARTH_RADIUS * sq + gsPos.altitude / 1000.0) * sin(DEG2RAD * gsPos.latitude)
)
obsVel.setXYZ(-mFactor * obsPos.y, mFactor * obsPos.x, 0.0)
magnitude(obsPos)

Wyświetl plik

@ -17,4 +17,10 @@
*/
package com.rtbishop.look4sat.model
data class GeoPos(val lat: Double, val lon: Double, val alt: Double = 0.0, val name: String? = null)
data class GeoPos(
val latitude: Double,
val longitude: Double,
val altitude: Double = 0.0,
val qthLocator: String = "null",
val timestamp: Long = 0L
)

Wyświetl plik

@ -1,7 +0,0 @@
package com.rtbishop.look4sat.model
data class StationPos(
val latitude: Double, val longitude: Double, val qthLocator: String, val timestamp: Long
) {
fun toGeoPos(): GeoPos = GeoPos(latitude, longitude)
}

Wyświetl plik

@ -25,11 +25,11 @@ class QthConverterTest {
@Test
fun `Given valid QTH returns correct POS`() {
var result = QthConverter.qthToPosition("io91VL39FX")
assert(result?.lat == 51.4792 && result.lon == -0.2083)
assert(result?.latitude == 51.4792 && result.longitude == -0.2083)
result = QthConverter.qthToPosition("JN58TD")
assert(result?.lat == 48.1458 && result.lon == 11.6250)
assert(result?.latitude == 48.1458 && result.longitude == 11.6250)
result = QthConverter.qthToPosition("gf15vc")
assert(result?.lat == -34.8958 && result.lon == -56.2083)
assert(result?.latitude == -34.8958 && result.longitude == -56.2083)
}
@Test