Is there any way to make the camera follow a target? #727
Unanswered
Danwolve98
asked this question in
Q&A
Replies: 2 comments
-
What kind of movement did you observe, and what's desired? |
Beta Was this translation helpful? Give feedback.
0 replies
-
|
Hi. class MapState(
val cameraState: CameraState,
val styleState: StyleState,
val locationPermissionState: LocationPermissionState,
val locationState: UserLocationState,
) {
var trackLocation by mutableStateOf(true)
var bearingUpdate by mutableStateOf(BearingUpdate.TRACK_LOCATION)
}
@Composable
fun rememberMapState(): MapState
{
val cameraState = rememberCameraState()
val styleState = rememberStyleState()
val locationPermissionState = rememberLocationPermissionState()
val locationProvider = rememberFusedLocationProvider()
val locationState = rememberUserLocationState(locationProvider = locationProvider)
return remember(cameraState, styleState, locationPermissionState) {
MapState(cameraState, styleState, locationPermissionState, locationState)
}
}@Composable
fun LocationPuck(
state: MapState
) {
@SuppressLint("MissingPermission")
if (state.locationPermissionState.hasPermission) {
if (state.locationState.location == null)
return
LaunchedEffect(state.cameraState.moveReason) {
if (state.cameraState.moveReason == CameraMoveReason.GESTURE)
state.trackLocation = false
}
val interpolatedLocation = animateLocationAsState(targetLocation = state.locationState.location!!)
val locationSource = rememberLocationSource(locationState = interpolatedLocation)
LocationTrackingEffect(
locationState = interpolatedLocation,
enabled = state.trackLocation,
trackBearing = state.bearingUpdate == BearingUpdate.TRACK_LOCATION
) {
state.cameraState.updateFromLocation(
updateBearing = state.bearingUpdate,
zoom = 18.0
)
}
LocationPuckLayers(
locationSource = locationSource,
cameraState = state.cameraState
)
} else {
state.locationPermissionState.requestPermission()
}
}
@Composable
private fun LocationPuckLayers(
locationSource: Source,
cameraState: CameraState,
oldLocationThreshold: Duration = 30.seconds
) {
val colors = LocationPuckColors()
val sizes = LocationPuckSizes()
CircleLayer(
id = "user-location-accuracy",
source = locationSource,
radius =
switch(
condition(
test =
feature["age"].asNumber() gt const(oldLocationThreshold.inWholeNanoseconds.toFloat()),
output = const(0.dp),
),
fallback =
(feature["accuracy"].asNumber() / const(cameraState.metersPerDpAtTarget.toFloat())).dp,
),
color = const(colors.accuracyFillColor),
strokeColor = const(colors.accuracyStrokeColor),
strokeWidth = const(sizes.accuracyStrokeWidth),
)
CircleLayer(
id = "user-location-shadow",
source = locationSource,
visible = sizes.shadowSize > 0.dp,
radius = const(sizes.dotRadius + sizes.dotStrokeWidth + sizes.shadowSize),
color = const(colors.shadowColor),
blur = const(sizes.shadowBlur),
translate = const(DpOffset(0.dp, 1.dp)),
)
CircleLayer(
id = "user-location-dot",
source = locationSource,
radius = const(sizes.dotRadius),
color =
switch(
condition(
test =
feature["age"].asNumber() gt const(oldLocationThreshold.inWholeNanoseconds.toFloat()),
output = const(colors.dotFillColorOldLocation),
),
fallback = const(colors.dotFillColorCurrentLocation),
),
strokeColor = const(colors.dotStrokeColor),
strokeWidth = const(sizes.dotStrokeWidth)
)
}
@Composable
private fun animateLocationAsState(
targetLocation: Location,
): State<Location> {
var prevLocation by remember { mutableStateOf(targetLocation) }
val animLocation = remember { mutableStateOf(targetLocation) }
var bearing = 0.0
LaunchedEffect(targetLocation) {
animate(
initialValue = 0F,
targetValue = 1F,
animationSpec = tween(
durationMillis = 1000,
easing = LinearEasing
)
) { fraction, _ ->
val latitude = lerp(prevLocation.position.latitude, targetLocation.position.latitude, fraction)
val longitude = lerp(prevLocation.position.longitude, targetLocation.position.longitude, fraction)
val accuracy = lerp(prevLocation.accuracy, targetLocation.accuracy, fraction)
targetLocation.bearing?.let { targetBearing ->
var origBearing = prevLocation.bearing!!
if (origBearing in 0.0..90.0 && targetBearing in 270.0..360.0) origBearing += 360.0
if (origBearing in 270.0..360.0 && targetBearing in 0.0..90.0) origBearing -= 360.0
bearing = lerp(origBearing, targetBearing, fraction)
if (bearing < 0.0) bearing += 360.0
if (bearing > 360.0) bearing -= 360.0
}
animLocation.value = Location(
position = Position(
latitude = latitude,
longitude = longitude
),
accuracy = accuracy,
bearing = bearing,
bearingAccuracy = prevLocation.bearingAccuracy,
speed = prevLocation.speed,
speedAccuracy = prevLocation.speedAccuracy,
timestamp = prevLocation.timestamp
)
}
}
DisposableEffect(targetLocation) {
onDispose {
prevLocation = animLocation.value
}
}
return animLocation
}
interface LocationChangeScope {
val previousLocation: Location?
val currentLocation: Location
suspend fun CameraState.updateFromLocation(
updateBearing: BearingUpdate = BearingUpdate.TRACK_LOCATION,
zoom: Double = 1.0
)
}
class LocationChangeCollector(private val onEmit: suspend LocationChangeScope.() -> Unit) :
FlowCollector<Location>, LocationChangeScope {
override var previousLocation: Location? = null
override lateinit var currentLocation: Location
override suspend fun emit(value: Location) {
currentLocation = value
onEmit()
previousLocation = value
}
override suspend fun CameraState.updateFromLocation(
updateBearing: BearingUpdate,
zoom: Double
) {
val cameraState = this
val newPosition =
when (updateBearing) {
BearingUpdate.IGNORE -> {
cameraState.position.copy(target = currentLocation.position)
}
BearingUpdate.ALWAYS_NORTH -> {
cameraState.position.copy(
target = currentLocation.position,
bearing = 0.0,
zoom = zoom
)
}
BearingUpdate.TRACK_LOCATION -> {
cameraState.position.copy(
target = currentLocation.position,
bearing = currentLocation.bearing ?: cameraState.position.bearing,
zoom = zoom
)
}
}
cameraState.position = newPosition
}
}
@Composable
fun LocationTrackingEffect(
locationState: State<Location>,
enabled: Boolean = true,
trackBearing: Boolean = true,
precision: Double = 0.00001,
onLocationChange: suspend LocationChangeScope.() -> Unit
) {
val changeCollector = remember(onLocationChange) { LocationChangeCollector(onLocationChange) }
LaunchedEffect(enabled, trackBearing, changeCollector) {
if (!enabled) return@LaunchedEffect
snapshotFlow { locationState.value }
.filterNotNull()
.distinctUntilChanged equal@{ old, new ->
if (trackBearing && (old.bearing != null || new.bearing != null)) {
if (old.bearing == null) return@equal false
if (new.bearing == null) return@equal false
if (abs(old.bearing!! - new.bearing!!) >= precision) return@equal false
}
if (abs(old.position.latitude - new.position.latitude) >= precision) return@equal false
if (abs(old.position.longitude - new.position.longitude) >= precision) return@equal false
true
}
.collect(changeCollector)
}
}
private fun lerp(start: Double, stop: Double, amount: Float): Double {
return start + (stop - start) * amount
}
@Composable
private fun rememberLocationSource(locationState: State<Location>): GeoJsonSource {
val features =
remember(locationState.value) {
val location = locationState.value
FeatureCollection(
Feature(
geometry = Point(location.position),
properties =
buildJsonObject {
put("accuracy", location.accuracy)
put("bearing", location.bearing)
put("bearingAccuracy", location.bearingAccuracy)
put("age", location.timestamp.elapsedNow().inWholeNanoseconds)
},
)
)
}
return rememberGeoJsonSource(GeoJsonData.Features(features))
}But after that there are two problems:
|
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment

Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I would like the camera to track a point on the map (user's location) in real time, but since the point is manually interpolated, I can't make the camera movement smooth using
cameraState.animateTo.Affected Platform(s)
Library Version
0.12.1
What have you tried?
I've tried updating the camera in the following way: the animation time is slightly longer than the position interpolation time, but nothing works; the movement is still not as desired.
Additional context
No response
Beta Was this translation helpful? Give feedback.
All reactions