Skip to content

Commit

Permalink
fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
sargunv committed Jan 8, 2025
1 parent 7d055ce commit cb846dc
Show file tree
Hide file tree
Showing 13 changed files with 91 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ fun DemoMapControls(
if (Platform.supportsBlending) {
Box(modifier = modifier.fillMaxSize().padding(8.dp)) {
DisappearingScaleBar(
metersPerDp = cameraState.metersPerDpAtTarget,
lengthPerDp = cameraState.lengthPerDpAtTarget,
zoom = cameraState.position.zoom,
modifier = Modifier.align(Alignment.TopStart),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import dev.sargunv.maplibrecompose.demoapp.DemoOrnamentSettings
import dev.sargunv.maplibrecompose.demoapp.DemoScaffold
import dev.sargunv.maplibrecompose.demoapp.format
import io.github.dellisd.spatialk.geojson.Position
import kotlin.math.roundToInt
import io.github.kevincianfarini.alchemist.unit.LengthUnit.International.Meter

private val CHICAGO = Position(latitude = 41.878, longitude = -87.626)

Expand Down Expand Up @@ -53,14 +53,14 @@ object CameraStateDemo : Demo {

Row(modifier = Modifier.safeDrawingPadding().wrapContentSize(Alignment.Center)) {
val pos = cameraState.position
val scale = cameraState.metersPerDpAtTarget
val scale = cameraState.lengthPerDpAtTarget

Cell("Latitude", pos.target.latitude.format(3), Modifier.weight(1.4f))
Cell("Longitude", pos.target.longitude.format(3), Modifier.weight(1.4f))
Cell("Zoom", pos.zoom.format(2), Modifier.weight(1f))
Cell("Bearing", pos.bearing.format(2), Modifier.weight(1f))
Cell("Tilt", pos.tilt.format(2), Modifier.weight(1f))
Cell("Scale", "${scale.roundToInt()}m", Modifier.weight(1f))
Cell("Scale", "${scale.toLong(Meter)}m", Modifier.weight(1f))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fun Material3() {
)

Box(modifier = Modifier.fillMaxSize().padding(8.dp)) {
ScaleBar(cameraState.metersPerDpAtTarget, modifier = Modifier.align(Alignment.TopStart))
ScaleBar(cameraState.lengthPerDpAtTarget, modifier = Modifier.align(Alignment.TopStart))
CompassButton(cameraState, modifier = Modifier.align(Alignment.TopEnd))
AttributionButton(styleState, modifier = Modifier.align(Alignment.BottomEnd))
}
Expand All @@ -50,7 +50,7 @@ fun Material3() {

Box(modifier = Modifier.fillMaxSize().padding(8.dp)) {
DisappearingScaleBar(
metersPerDp = cameraState.metersPerDpAtTarget,
lengthPerDp = cameraState.lengthPerDpAtTarget,
zoom = cameraState.position.zoom,
modifier = Modifier.align(Alignment.TopStart),
) // (1)!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import dev.sargunv.maplibrecompose.material3.defaultScaleBarMeasures
import io.github.kevincianfarini.alchemist.type.Length
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.delay
Expand All @@ -24,9 +25,9 @@ import kotlinx.coroutines.delay
* An animated scale bar that appears when the [zoom] level of the map changes, and then disappears
* after [visibilityDuration]. This composable wraps [ScaleBar] with visibility animations.
*
* @param metersPerDp how many meters are displayed in one device independent pixel (dp), i.e. the
* scale. See
* [CameraState.metersPerDpAtTarget][dev.sargunv.maplibrecompose.compose.CameraState.metersPerDpAtTarget]
* @param lengthPerDp the real world distance in one device independent pixel (dp), i.e. the scale.
* See
* [CameraState.lengthPerDpAtTarget][dev.sargunv.maplibrecompose.compose.CameraState.lengthPerDpAtTarget]
* @param zoom zoom level of the map
* @param modifier the [Modifier] to be applied to this layout node
* @param measures which measures to show on the scale bar. If `null`, measures will be selected
Expand All @@ -42,7 +43,7 @@ import kotlinx.coroutines.delay
*/
@Composable
public fun DisappearingScaleBar(
metersPerDp: Double,
lengthPerDp: Length,
zoom: Double,
modifier: Modifier = Modifier,
measures: ScaleBarMeasures = defaultScaleBarMeasures(),
Expand Down Expand Up @@ -71,7 +72,7 @@ public fun DisappearingScaleBar(
exit = exitTransition,
) {
ScaleBar(
metersPerDp = metersPerDp,
lengthPerDp = lengthPerDp,
measures = measures,
haloColor = haloColor,
color = color,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import androidx.compose.ui.unit.toSize
import dev.sargunv.maplibrecompose.material3.defaultScaleBarMeasures
import dev.sargunv.maplibrecompose.material3.drawPathsWithHalo
import dev.sargunv.maplibrecompose.material3.drawTextWithHalo
import io.github.kevincianfarini.alchemist.scalar.toLength
import io.github.kevincianfarini.alchemist.type.Length
import io.github.kevincianfarini.alchemist.unit.LengthUnit.International.Nanometer
import kotlin.math.roundToLong

/** Which measures to show on the scale bar. */
public data class ScaleBarMeasures(
Expand All @@ -31,12 +35,12 @@ public data class ScaleBarMeasures(
)

/**
* A scale bar composable that shows the current scale of the map in feet, meters or feet and meters
* when zoomed in to the map, changing to miles and kilometers, respectively, when zooming out.
* A scale bar composable that shows the current scale of the map in feet, yards, or meters when
* zoomed in to the map, changing to miles and kilometers when zooming out.
*
* @param metersPerDp how many meters are displayed in one device independent pixel (dp), i.e. the
* scale. See
* [CameraState.metersPerDpAtTarget][dev.sargunv.maplibrecompose.compose.CameraState.metersPerDpAtTarget]
* @param lengthPerDp the real world distance in one device independent pixel (dp), i.e. the scale.
* See
* [CameraState.lengthPerDpAtTarget][dev.sargunv.maplibrecompose.compose.CameraState.lengthPerDpAtTarget]
* @param modifier the [Modifier] to be applied to this layout node
* @param measures which measures to show on the scale bar. If `null`, measures will be selected
* based on the system settings or otherwise the user's locale.
Expand All @@ -48,7 +52,7 @@ public data class ScaleBarMeasures(
*/
@Composable
public fun ScaleBar(
metersPerDp: Double,
lengthPerDp: Length,
modifier: Modifier = Modifier,
measures: ScaleBarMeasures = defaultScaleBarMeasures(),
haloColor: Color = MaterialTheme.colorScheme.surface,
Expand Down Expand Up @@ -83,8 +87,8 @@ public fun ScaleBar(
// scale bar start/end should not overlap horizontally with canvas bounds
val maxBarLength = maxWidth - fullStrokeWidth

val params1 = scaleBarParameters(measures.primary, metersPerDp, maxBarLength)
val params2 = measures.secondary?.let { scaleBarParameters(it, metersPerDp, maxBarLength) }
val params1 = scaleBarParameters(measures.primary, lengthPerDp, maxBarLength)
val params2 = measures.secondary?.let { scaleBarParameters(it, lengthPerDp, maxBarLength) }

Canvas(modifier.fillMaxSize()) {
val fullStrokeWidthPx = fullStrokeWidth.toPx()
Expand All @@ -102,15 +106,15 @@ public fun ScaleBar(
if (true) { // just want a scope here
val offsetX =
alignment.align(
size = params1.barLength.toPx().toInt(),
size = params1.barWidth.toPx().toInt(),
space = (size.width - fullStrokeWidthPx).toInt(),
layoutDirection = layoutDirection,
)
paths.add(
listOf(
Offset(offsetX + fullStrokeWidthPx / 2f, 0f + textHeightPx / 2f),
Offset(0f, barEndsHeightPx),
Offset(params1.barLength.toPx(), 0f),
Offset(params1.barWidth.toPx(), 0f),
Offset(0f, -barEndsHeightPx),
)
)
Expand All @@ -127,15 +131,15 @@ public fun ScaleBar(
if (params2 != null) {
val offsetX =
alignment.align(
size = params2.barLength.toPx().toInt(),
size = params2.barWidth.toPx().toInt(),
space = (size.width - fullStrokeWidthPx).toInt(),
layoutDirection = layoutDirection,
)
paths.add(
listOf(
Offset(offsetX + fullStrokeWidthPx / 2f, y + fullStrokeWidthPx / 2f + barEndsHeightPx),
Offset(0f, -barEndsHeightPx),
Offset(params2.barLength.toPx(), 0f),
Offset(params2.barWidth.toPx(), 0f),
Offset(0f, +barEndsHeightPx),
)
)
Expand Down Expand Up @@ -178,24 +182,29 @@ public fun ScaleBar(
}
}

private data class ScaleBarParams(val barLength: Dp, val text: String)
private data class ScaleBarParams(val barWidth: Dp, val text: String)

@Composable
private fun scaleBarParameters(
measure: ScaleBarMeasure,
metersPerDp: Double,
lengthPerDp: Length,
maxBarLength: Dp,
): ScaleBarParams {
val max = maxBarLength.value * metersPerDp / measure.unitInMeters
val max = lengthPerDp * maxBarLength.value.toDouble()
val stop = findStop(max, measure.stops)
return ScaleBarParams((stop * measure.unitInMeters / metersPerDp).dp, measure.getText(stop))
return ScaleBarParams(barWidth = (stop / lengthPerDp).dp, text = measure.getText(stop))
}

/**
* find the largest stop in the list of stops (sorted in ascending order) that is below or equal
* [max].
*/
private fun findStop(max: Double, stops: List<Double>): Double {
private fun findStop(max: Length, stops: List<Length>): Length {
val i = stops.binarySearch { it.compareTo(max) }
return if (i >= 0) stops[i] else stops[(-i - 2).coerceAtLeast(0)]
}

// https://github.com/kevincianfarini/alchemist/issues/53
// https://github.com/kevincianfarini/alchemist/issues/54
private operator fun Length.times(other: Double) =
(this.toLong(Nanometer) * other).roundToLong().toLength(Nanometer)
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,17 @@ import dev.sargunv.maplibrecompose.material3.generated.kilometers_symbol
import dev.sargunv.maplibrecompose.material3.generated.meters_symbol
import dev.sargunv.maplibrecompose.material3.generated.miles_symbol
import dev.sargunv.maplibrecompose.material3.generated.yards_symbol
import io.github.kevincianfarini.alchemist.scalar.feet
import io.github.kevincianfarini.alchemist.scalar.kilometers
import io.github.kevincianfarini.alchemist.scalar.meters
import io.github.kevincianfarini.alchemist.scalar.miles
import io.github.kevincianfarini.alchemist.scalar.nanometers
import io.github.kevincianfarini.alchemist.scalar.yards
import io.github.kevincianfarini.alchemist.scalar.toLength
import io.github.kevincianfarini.alchemist.type.Length
import io.github.kevincianfarini.alchemist.unit.LengthUnit
import io.github.kevincianfarini.alchemist.unit.LengthUnit.International.Kilometer
import io.github.kevincianfarini.alchemist.unit.LengthUnit.International.Meter
import io.github.kevincianfarini.alchemist.unit.LengthUnit.International.Nanometer
import io.github.kevincianfarini.alchemist.unit.LengthUnit.UnitedStatesCustomary.Foot
import io.github.kevincianfarini.alchemist.unit.LengthUnit.UnitedStatesCustomary.Mile
import io.github.kevincianfarini.alchemist.unit.LengthUnit.UnitedStatesCustomary.Yard
import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.math.roundToLong
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.stringResource

Expand All @@ -36,11 +32,12 @@ public interface ScaleBarMeasure {
public data object Metric : ScaleBarMeasure {

override val stops: List<Length> =
buildStops(mantissas = listOf(1, 2, 5).map { it.meters }, exponents = -1..7)
buildStops(mantissas = listOf(1, 2, 5).map { it.toLength(Meter) }, exponents = -1..7)

@Composable
override fun getText(stop: Length): String =
if (stop >= 1.kilometers) stop.toDisplayString(Kilometer, Res.string.kilometers_symbol)
if (stop >= 1.toLength(Kilometer))
stop.toDisplayString(Kilometer, Res.string.kilometers_symbol)
else stop.toDisplayString(Meter, Res.string.meters_symbol)
}

Expand All @@ -49,14 +46,15 @@ public interface ScaleBarMeasure {

override val stops: List<Length> =
listOf(
buildStops(mantissas = listOf(1, 2, 5).map { it.feet }, exponents = -1..3).dropLast(1),
buildStops(mantissas = listOf(1, 2, 5).map { it.miles }, exponents = 0..4),
buildStops(mantissas = listOf(1, 2, 5).map { it.toLength(Foot) }, exponents = -1..3)
.dropLast(1),
buildStops(mantissas = listOf(1, 2, 5).map { it.toLength(Mile) }, exponents = 0..4),
)
.flatten()

@Composable
override fun getText(stop: Length): String =
if (stop >= 1.miles) stop.toDisplayString(Mile, Res.string.miles_symbol)
if (stop >= 1.toLength(Mile)) stop.toDisplayString(Mile, Res.string.miles_symbol)
else stop.toDisplayString(Foot, Res.string.feet_symbol)
}

Expand All @@ -65,14 +63,15 @@ public interface ScaleBarMeasure {

override val stops: List<Length> =
listOf(
buildStops(mantissas = listOf(1, 2, 5).map { it.yards }, exponents = -1..3).dropLast(2),
buildStops(mantissas = listOf(1, 2, 5).map { it.miles }, exponents = 0..4),
buildStops(mantissas = listOf(1, 2, 5).map { it.toLength(Yard) }, exponents = -1..3)
.dropLast(2),
buildStops(mantissas = listOf(1, 2, 5).map { it.toLength(Mile) }, exponents = 0..4),
)
.flatten()

@Composable
override fun getText(stop: Length): String =
if (stop >= 1.miles) stop.toDisplayString(Mile, Res.string.miles_symbol)
if (stop >= 1.toLength(Mile)) stop.toDisplayString(Mile, Res.string.miles_symbol)
else stop.toDisplayString(Yard, Res.string.yards_symbol)
}
}
Expand All @@ -92,5 +91,6 @@ private fun buildStops(mantissas: List<Length>, exponents: IntRange) = buildList
}

// https://github.com/kevincianfarini/alchemist/issues/53
// https://github.com/kevincianfarini/alchemist/issues/54
private operator fun Length.times(other: Double) =
(this.toDouble(LengthUnit.International.Nanometer) * other).roundToInt().nanometers
(this.toLong(Nanometer) * other).roundToLong().toLength(Nanometer)
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import dev.sargunv.maplibrecompose.expressions.value.BooleanValue
import io.github.dellisd.spatialk.geojson.BoundingBox
import io.github.dellisd.spatialk.geojson.Feature
import io.github.dellisd.spatialk.geojson.Position
import io.github.kevincianfarini.alchemist.scalar.toLength
import io.github.kevincianfarini.alchemist.type.Length
import io.github.kevincianfarini.alchemist.unit.LengthUnit.International.Meter
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlin.time.Duration
Expand Down Expand Up @@ -335,8 +338,10 @@ internal class AndroidMap(
.map { Feature.fromJson(it.toJson()) }
}

override fun metersPerDpAtLatitude(latitude: Double) =
map.projection.getMetersPerPixelAtLatitude(latitude)
override fun lengthPerDpAtLatitude(latitude: Double): Length {
// https://github.com/kevincianfarini/alchemist/issues/54
return map.projection.getMetersPerPixelAtLatitude(latitude).toLength(Meter)
}
}

private fun MLNVisibleRegion.toVisibleRegion() =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import dev.sargunv.maplibrecompose.expressions.value.BooleanValue
import io.github.dellisd.spatialk.geojson.BoundingBox
import io.github.dellisd.spatialk.geojson.Feature
import io.github.dellisd.spatialk.geojson.Position
import io.github.kevincianfarini.alchemist.scalar.toLength
import io.github.kevincianfarini.alchemist.type.Length
import io.github.kevincianfarini.alchemist.unit.LengthUnit.International.Meter
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.channels.Channel
Expand All @@ -42,7 +45,8 @@ public class CameraState internal constructor(firstPosition: CameraPosition) {

internal val positionState = mutableStateOf(firstPosition)
internal val moveReasonState = mutableStateOf(CameraMoveReason.NONE)
internal val metersPerDpAtTargetState = mutableStateOf(0.0)
// https://github.com/kevincianfarini/alchemist/issues/54
internal val lengthPerDpAtTargetState = mutableStateOf(0.toLength(Meter))

/** how the camera is oriented towards the map */
// if the map is not yet initialized, we store the value to apply it later
Expand All @@ -57,9 +61,9 @@ public class CameraState internal constructor(firstPosition: CameraPosition) {
public val moveReason: CameraMoveReason
get() = moveReasonState.value

/** meters per dp at the target position */
public val metersPerDpAtTarget: Double
get() = metersPerDpAtTargetState.value
/** real world distance per dp at the target position */
public val lengthPerDpAtTarget: Length
get() = lengthPerDpAtTargetState.value

/** suspends until the map has been initialized */
public suspend fun awaitInitialized() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ public fun MaplibreMap(
map as StandardMaplibreMap
styleState.attach(style)
rememberedStyle = style
cameraState.metersPerDpAtTargetState.value =
map.metersPerDpAtLatitude(map.getCameraPosition().target.latitude)
cameraState.lengthPerDpAtTargetState.value =
map.lengthPerDpAtLatitude(map.getCameraPosition().target.latitude)
}

override fun onCameraMoveStarted(map: MaplibreMap, reason: CameraMoveReason) {
Expand All @@ -126,8 +126,8 @@ public fun MaplibreMap(
override fun onCameraMoved(map: MaplibreMap) {
map as StandardMaplibreMap
cameraState.positionState.value = map.getCameraPosition()
cameraState.metersPerDpAtTargetState.value =
map.metersPerDpAtLatitude(map.getCameraPosition().target.latitude)
cameraState.lengthPerDpAtTargetState.value =
map.lengthPerDpAtLatitude(map.getCameraPosition().target.latitude)
}

override fun onCameraMoveEnded(map: MaplibreMap) {}
Expand Down
Loading

0 comments on commit cb846dc

Please sign in to comment.