Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ dependencies {
debugImplementation("androidx.compose.ui:ui-tooling:1.10.0")

// location
implementation("org.maplibre.compose:location:0.15.0")
implementation("org.maplibre.compose:location:0.16.0")

// Dependency Injection
implementation("io.insert-koin:koin-android:4.2.2")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import de.westnordost.streetcomplete.util.ktx.observe
import de.westnordost.streetcomplete.util.ktx.toLatLon
import de.westnordost.streetcomplete.util.ktx.toOffset
import de.westnordost.streetcomplete.util.ktx.toast
import de.westnordost.streetcomplete.util.ktx.updatesWithPermissionChanges
import de.westnordost.streetcomplete.util.math.area
import de.westnordost.streetcomplete.util.math.enclosingBoundingBox
import de.westnordost.streetcomplete.util.math.enlargedBy
Expand Down Expand Up @@ -348,15 +349,14 @@ class MainActivity :
mainBottomSheetViewModel.closeBottomSheet()
}
}
observe(locationProvider.updates(LocationRequest())) { locationEvent ->
observe(locationProvider.updatesWithPermissionChanges(LocationRequest())) { locationEvent ->
viewModel.locationState.value = when (locationEvent) {
is LocationEvent.Fix -> LocationState.UPDATING
is LocationEvent.Update -> LocationState.UPDATING
is LocationEvent.Unavailable -> when (locationEvent.reason) {
LocationUnavailableReason.ServicesDisabled -> LocationState.ALLOWED
LocationUnavailableReason.TemporarilyUnavailable -> LocationState.SEARCHING
LocationUnavailableReason.PermissionDenied -> LocationState.DENIED
LocationUnavailableReason.Unsupported,
LocationUnavailableReason.Misconfigured,
LocationUnavailableReason.UnexpectedFailure -> null
}
}
Expand Down Expand Up @@ -490,7 +490,7 @@ class MainActivity :

private fun getDisplayedPoint(): PointF? {
val mapFragment = mapFragment ?: return null
val displayedPosition = mapFragment.displayedLocation?.position?.value?.toLatLon() ?: return null
val displayedPosition = mapFragment.displayedLocation?.position?.toLatLon() ?: return null
return mapFragment.getPointOf(displayedPosition)
}

Expand Down Expand Up @@ -588,7 +588,7 @@ class MainActivity :
viewModel.isRecordingTracks.value = false
val mapFragment = mapFragment ?: return
mapFragment.stopPositionTrackRecording()
val pos = mapFragment.displayedLocation?.position?.value?.toLatLon() ?: return
val pos = mapFragment.displayedLocation?.position?.toLatLon() ?: return
composeNote(pos, mapFragment.recordedTracks.takeIf { it.isNotEmpty() })
}

Expand Down
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ kotlin {
implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.9.1")

// location
implementation("org.maplibre.compose:location:0.15.0")
implementation("org.maplibre.compose:location:0.16.0")

// SQLite
implementation("androidx.sqlite:sqlite:2.7.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,17 @@ import org.maplibre.android.maps.MapLibreMap
import org.maplibre.android.maps.Style
import org.maplibre.android.style.layers.Property
import org.maplibre.android.style.layers.PropertyFactory.visibility
import org.maplibre.compose.location.AndroidOrientationProvider
import org.maplibre.compose.location.Location
import org.maplibre.compose.location.AndroidHeadingProvider
import org.maplibre.compose.location.HeadingMeasurement
import org.maplibre.compose.location.HeadingProvider
import org.maplibre.compose.location.HeadingRequest
import org.maplibre.compose.location.LocationEvent
import org.maplibre.compose.location.Orientation
import org.maplibre.compose.location.OrientationProvider
import org.maplibre.compose.location.PositionWithAccuracy
import org.maplibre.compose.location.LocationMeasurement
import org.maplibre.spatialk.units.Bearing
import org.maplibre.spatialk.units.DMS
import org.maplibre.spatialk.units.International
import org.maplibre.spatialk.units.extensions.degrees
import org.maplibre.spatialk.units.extensions.inDegrees
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.TimeSource

/** This is the map shown in the main view. It manages a map that shows the quest pins, quest
* geometry, overlays, tracks, location... */
Expand All @@ -75,7 +75,7 @@ class MainMapFragment : MapFragment() {
private val surveyChecker: SurveyChecker by inject()
private val prefs: Preferences by inject()

private lateinit var orientationProvider: OrientationProvider
private lateinit var headingProvider: HeadingProvider

private var mapImages: MapImages? = null
private var geometryMarkersMapComponent: GeometryMarkersMapComponent? = null
Expand Down Expand Up @@ -106,7 +106,7 @@ class MainMapFragment : MapFragment() {
private var zoomedYet = false

/** The GPS position at which the user is displayed at */
var displayedLocation: Location? = null
var displayedLocation: LocationMeasurement? = null
private set

/** The GPS trackpoints the user has walked */
Expand Down Expand Up @@ -161,15 +161,11 @@ class MainMapFragment : MapFragment() {

override fun onAttach(context: Context) {
super.onAttach(context)
orientationProvider = AndroidOrientationProvider(
context = context,
updateInterval = 33.milliseconds,
coroutineScope = lifecycleScope
)
headingProvider = AndroidHeadingProvider(context)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
orientationProvider.orientation.collect { orientation ->
onCompassRotationChanged(orientation)
headingProvider.updates(HeadingRequest(33.milliseconds)).collect { heading ->
onCompassRotationChanged(heading)
}
}
}
Expand All @@ -178,9 +174,7 @@ class MainMapFragment : MapFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
val position: PositionWithAccuracy? =
savedInstanceState.getString(DISPLAYED_POSITION)?.let { Json.decodeFromString(it) }
displayedLocation = position?.let { Location(it, timestamp = TimeSource.Monotonic.markNow()) }
displayedLocation = savedInstanceState.getString(DISPLAYED_LOCATION)?.let { Json.decodeFromString(it) }
isRecordingTracks = savedInstanceState.getBoolean(TRACKS_IS_RECORDING)
tracks = Json.decodeFromString(savedInstanceState.getString(TRACKS)!!)
}
Expand Down Expand Up @@ -278,7 +272,7 @@ class MainMapFragment : MapFragment() {
onSelectedOverlayChanged()
selectedOverlaySource.addListener(overlayListener)

locationMapComponent?.targetPositionWithAccuracy = displayedLocation?.position
locationMapComponent?.targetLocation = displayedLocation

val positionsLists = tracks.map { track -> track.map { it.position } }
tracksMapComponent?.setTracks(positionsLists, isRecordingTracks)
Expand All @@ -291,7 +285,7 @@ class MainMapFragment : MapFragment() {

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(DISPLAYED_POSITION, Json.encodeToString(displayedLocation?.position))
outState.putString(DISPLAYED_LOCATION, Json.encodeToString(displayedLocation))
// the amount of data one can put into a bundle is limited, let's cut off at 1000 points
outState.putString(TRACKS, Json.encodeToString(tracks.takeLastNested(1000)))
outState.putBoolean(TRACKS_IS_RECORDING, isRecordingTracks)
Expand Down Expand Up @@ -333,27 +327,23 @@ class MainMapFragment : MapFragment() {
return true
}

private fun onCompassRotationChanged(orientation: Orientation?) {
val rotation = orientation
?.orientation
?.value
?.clockwiseRotationTo(Bearing.North)
?.toDouble(DMS.Degrees)
locationMapComponent?.targetRotation = rotation?.let { rotation - (map?.camera?.rotation ?: 0.0) }?.toFloat()
private fun onCompassRotationChanged(heading: HeadingMeasurement) {
val cameraBearing = Bearing.North + (map?.camera?.rotation ?: 0.0).degrees
locationMapComponent?.targetRotation = (heading.bearing - cameraBearing).inDegrees.toFloat()
}

fun onLocationEvent(locationEvent: LocationEvent) {
when (locationEvent) {
is LocationEvent.Fix -> {
val location = locationEvent.location
is LocationEvent.Update -> {
val location = locationEvent.measurement
displayedLocation = location
surveyChecker.addRecentLocation(location.toLocation())
locationMapComponent?.targetPositionWithAccuracy = location.position
surveyChecker.addRecentLocation(locationEvent.toLocation())
locationMapComponent?.targetLocation = location
addTrackLocation(location)
centerCurrentPositionIfFollowing()
}
is LocationEvent.Unavailable -> {
locationMapComponent?.targetPositionWithAccuracy = null
locationMapComponent?.targetLocation = null
displayedLocation = null
isNavigationMode = false

Expand Down Expand Up @@ -408,24 +398,24 @@ class MainMapFragment : MapFragment() {
previouslyHiddenLayers = new
}

private fun addTrackLocation(location: Location) {
private fun addTrackLocation(location: LocationMeasurement) {
// ignore if too imprecise
val accuracy = location.position.accuracy?.toFloat(International.Meters)
val accuracy = location.horizontalAccuracy?.toFloat(International.Meters)
if (accuracy != null && accuracy > MIN_TRACK_ACCURACY) return
val lastLocation = tracks.last().lastOrNull()

// create new track if last position too old
if (lastLocation != null && !isRecordingTracks) {
if ((displayedLocation?.timestamp?.elapsedNow()?.inWholeMilliseconds ?: 0) - lastLocation.time > MAX_TIME_BETWEEN_LOCATIONS) {
if (location.measuredAt.toEpochMilliseconds() - lastLocation.time > MAX_TIME_BETWEEN_LOCATIONS) {
tracks.add(ArrayList())
tracksMapComponent?.startNewTrack(false)
}
}
val trackpoint = Trackpoint(
position = location.position.value.toLatLon(),
time = location.timestamp.elapsedNow().inWholeMilliseconds,
position = location.position.toLatLon(),
time = location.measuredAt.toEpochMilliseconds(),
accuracy = accuracy ?: 0f,
elevation = location.position.value.altitude?.toFloat() ?: 0f
elevation = location.position.altitude?.toFloat() ?: 0f
)

tracks.last().add(trackpoint)
Expand Down Expand Up @@ -512,7 +502,7 @@ class MainMapFragment : MapFragment() {
}

private fun centerCurrentPosition() {
val displayedPosition = displayedLocation?.position?.value?.let { it.toLatLon() } ?: return
val displayedPosition = displayedLocation?.position?.let { it.toLatLon() } ?: return

updateCameraPosition(600) {
if (isNavigationMode) {
Expand Down Expand Up @@ -571,7 +561,7 @@ class MainMapFragment : MapFragment() {
//endregion

companion object {
private const val DISPLAYED_POSITION = "displayed_position"
private const val DISPLAYED_LOCATION = "displayed_location"
private const val TRACKS = "tracks"
private const val TRACKS_IS_RECORDING = "tracks_is_recording"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import org.maplibre.android.style.layers.Property
import org.maplibre.android.style.layers.PropertyFactory.*
import org.maplibre.android.style.layers.SymbolLayer
import org.maplibre.android.style.sources.GeoJsonSource
import org.maplibre.compose.location.PositionWithAccuracy
import org.maplibre.compose.location.LocationMeasurement
import org.maplibre.geojson.Feature
import org.maplibre.spatialk.geojson.Position
import org.maplibre.spatialk.units.International
Expand All @@ -38,38 +38,40 @@ class CurrentLocationMapComponent(context: Context, mapStyle: Style, private val
private val locationAnimation = ValueAnimator()

/** The location the GPS location dot on the map should be animated to */
var targetPositionWithAccuracy: PositionWithAccuracy? = null
var targetLocation: LocationMeasurement? = null
@UiThread set(value) {
if (field == value) return
field = value
val positionWithAccuracy = this.positionWithAccuracy
if (positionWithAccuracy == null || value == null) {
val location = this.location
if (location == null || value == null) {
locationAnimation.cancel()
this.positionWithAccuracy = value
this.location = value
update()
} else {
locationAnimation.setObjectValues(positionWithAccuracy, value)
locationAnimation.setObjectValues(location, value)
locationAnimation.setEvaluator(locationTypeEvaluator)
locationAnimation.start()
}
}

/** The location of the GPS location dot on the map (animated) */
var positionWithAccuracy: PositionWithAccuracy? = null
var location: LocationMeasurement? = null
private set

private val locationTypeEvaluator = object : TypeEvaluator<PositionWithAccuracy> {
override fun evaluate(fraction: Float, s: PositionWithAccuracy, e: PositionWithAccuracy): PositionWithAccuracy {
val sp = s.value
val ep = e.value
val sa = s.accuracy
val ea = e.accuracy
return PositionWithAccuracy(
value = Position(
private val locationTypeEvaluator = object : TypeEvaluator<LocationMeasurement> {
override fun evaluate(fraction: Float, s: LocationMeasurement, e: LocationMeasurement): LocationMeasurement {
val sp = s.position
val ep = e.position
val sa = s.horizontalAccuracy
val ea = e.horizontalAccuracy
// Preserve measuredAt from the target measurement while animating its position and accuracy.
return e.copy(
position = Position(
longitude = normalizeLongitude(sp.longitude + (ep.longitude - sp.longitude) * fraction),
latitude = sp.latitude + (ep.latitude - sp.latitude) * fraction,
altitude = ep.altitude,
),
accuracy = if (ea != null && sa != null) {
horizontalAccuracy = if (ea != null && sa != null) {
sa + (ea - sa) * fraction.toDouble()
} else { null }
)
Expand Down Expand Up @@ -148,7 +150,7 @@ class CurrentLocationMapComponent(context: Context, mapStyle: Style, private val
locationAnimation.duration = 600L
locationAnimation.interpolator = AccelerateDecelerateInterpolator()
locationAnimation.addUpdateListener {
positionWithAccuracy = locationAnimation.animatedValue as PositionWithAccuracy
location = locationAnimation.animatedValue as LocationMeasurement
update()
}

Expand Down Expand Up @@ -195,15 +197,15 @@ class CurrentLocationMapComponent(context: Context, mapStyle: Style, private val

/** Update the GPS position shown on the map */
private fun update() {
val positionWithAccuracy = this.positionWithAccuracy
if (positionWithAccuracy == null) {
val location = this.location
if (location == null) {
locationSource.clear()
return
}
val pos = positionWithAccuracy.value
val pos = location.position

val p = JsonObject()
p.addProperty("radius", positionWithAccuracy.accuracy?.toDouble(International.Meters))
p.addProperty("radius", location.horizontalAccuracy?.toDouble(International.Meters))
rotation?.let { p.addProperty("rotation", it) }
map.style?.getLayerAs<CircleLayer>("accuracy")?.setProperties(
circleRadius(inMeters(get("radius"), pos.latitude))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package de.westnordost.streetcomplete.ui.util

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import org.maplibre.compose.location.AndroidLocationProvider
import org.maplibre.compose.location.LocationProvider
import org.maplibre.compose.location.createDefaultLocationProvider

@Composable
public actual fun rememberDefaultLocationProvider(): LocationProvider {
val context = LocalContext.current
AndroidLocationProvider(context)
return remember(context) { createDefaultLocationProvider(context) }
val provider = remember(context) { createDefaultLocationProvider(context) }
DisposableEffect(provider) { onDispose { provider.close() } }
return provider
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ class RecentLocations(

fun add(location: Location): Unit = lock.withLock {
// only add newer locations
val firstDuration = locations.firstOrNull()?.elapsedDuration ?: Duration.ZERO
if (firstDuration >= location.elapsedDuration) return@withLock
val firstDuration = locations.firstOrNull()?.elapsedDuration
if (firstDuration != null && firstDuration >= location.elapsedDuration) return@withLock

// clear from deque all older than `maxAge` before inserting new
while (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import de.westnordost.streetcomplete.data.upload.UploadController
import de.westnordost.streetcomplete.data.user.UserLoginSource
import de.westnordost.streetcomplete.data.visiblequests.TeamModeQuestFilterSource
import de.westnordost.streetcomplete.util.ktx.format
import de.westnordost.streetcomplete.util.ktx.updatesWithPermissionChanges
import de.westnordost.streetcomplete.util.logs.Log
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -117,9 +118,9 @@ class AutoSyncer(
coroutineScope.launch {
owner.repeatOnLifecycle(Lifecycle.State.STARTED) {
val request = LocationRequest(LocationAccuracy.High, 30.seconds, 100.meters)
locationProvider.updates(request).collect { locationEvent ->
if (locationEvent is LocationEvent.Fix) {
val (position, accuracy) = locationEvent.location.position
locationProvider.updatesWithPermissionChanges(request).collect { locationEvent ->
if (locationEvent is LocationEvent.Update) {
val (position, accuracy) = locationEvent.measurement
if (accuracy == null || accuracy < 300.meters) {
pos = LatLon(position.latitude, position.longitude)
triggerAutoDownload()
Expand Down
Loading