Refactor map arguments
This commit is contained in:
parent
2fdec494fb
commit
c7d1797617
@ -1,8 +1,6 @@
|
|||||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Home
|
import androidx.compose.material.icons.filled.Home
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
@ -14,14 +12,19 @@ import androidx.compose.ui.window.application
|
|||||||
import centre.sciprog.maps.GeodeticMapCoordinates
|
import centre.sciprog.maps.GeodeticMapCoordinates
|
||||||
import centre.sciprog.maps.MapViewPoint
|
import centre.sciprog.maps.MapViewPoint
|
||||||
import centre.sciprog.maps.compose.*
|
import centre.sciprog.maps.compose.*
|
||||||
import io.ktor.client.*
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.engine.cio.*
|
import io.ktor.client.engine.cio.CIO
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import kotlin.math.PI
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
private fun GeodeticMapCoordinates.toShortString(): String =
|
||||||
|
"${(latitude * 180.0 / PI).toString().take(6)}:${(longitude * 180.0 / PI).toString().take(6)}"
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun App() {
|
fun App() {
|
||||||
@ -42,16 +45,16 @@ fun App() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var coordinates by remember { mutableStateOf<GeodeticMapCoordinates?>(null) }
|
var centerCoordinates by remember { mutableStateOf<GeodeticMapCoordinates?>(null) }
|
||||||
|
|
||||||
|
|
||||||
Column {
|
|
||||||
//display click coordinates
|
|
||||||
Text(coordinates?.toString() ?: "")
|
|
||||||
MapView(
|
MapView(
|
||||||
mapTileProvider = mapTileProvider,
|
mapTileProvider = mapTileProvider,
|
||||||
initialViewPoint = viewPoint,
|
initialViewPoint = viewPoint,
|
||||||
onClick = { coordinates = focus },
|
config = MapViewConfig(
|
||||||
config = MapViewConfig(inferViewBoxFromFeatures = true)
|
inferViewBoxFromFeatures = true,
|
||||||
|
onViewChange = { centerCoordinates = focus }
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
val pointOne = 55.568548 to 37.568604
|
val pointOne = 55.568548 to 37.568604
|
||||||
val pointTwo = 55.929444 to 37.518434
|
val pointTwo = 55.929444 to 37.518434
|
||||||
@ -71,9 +74,17 @@ fun App() {
|
|||||||
size = Size(20f, 20f)
|
size = Size(20f, 20f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
line(pointOne, pointTwo)
|
line(pointOne, pointTwo)
|
||||||
text(pointOne, "Home")
|
text(pointOne, "Home")
|
||||||
|
|
||||||
|
centerCoordinates?.let {
|
||||||
|
group(id = "center") {
|
||||||
|
circle(center = it, color = Color.Blue, size = 1f)
|
||||||
|
text(position = it, it.toShortString(), color = Color.Blue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
delay(200)
|
delay(200)
|
||||||
@ -87,7 +98,6 @@ fun App() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
|
@ -8,6 +8,7 @@ import androidx.compose.ui.graphics.drawscope.DrawScope
|
|||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import centre.sciprog.maps.GeodeticMapCoordinates
|
||||||
|
|
||||||
typealias FeatureId = String
|
typealias FeatureId = String
|
||||||
|
|
||||||
@ -34,6 +35,16 @@ internal class MapFeatureBuilder(initialFeatures: Map<FeatureId, MapFeature>) :
|
|||||||
override fun build(): SnapshotStateMap<FeatureId, MapFeature> = content
|
override fun build(): SnapshotStateMap<FeatureId, MapFeature> = content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun FeatureBuilder.circle(
|
||||||
|
center: GeodeticMapCoordinates,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
size: Float = 5f,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
id: FeatureId? = null,
|
||||||
|
) = addFeature(
|
||||||
|
id, MapCircleFeature(center, zoomRange, size, color)
|
||||||
|
)
|
||||||
|
|
||||||
fun FeatureBuilder.circle(
|
fun FeatureBuilder.circle(
|
||||||
centerCoordinates: Pair<Double, Double>,
|
centerCoordinates: Pair<Double, Double>,
|
||||||
zoomRange: IntRange = defaultZoomRange,
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
@ -59,6 +70,14 @@ fun FeatureBuilder.line(
|
|||||||
id: FeatureId? = null,
|
id: FeatureId? = null,
|
||||||
) = addFeature(id, MapLineFeature(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), zoomRange, color))
|
) = addFeature(id, MapLineFeature(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), zoomRange, color))
|
||||||
|
|
||||||
|
fun FeatureBuilder.text(
|
||||||
|
position: GeodeticMapCoordinates,
|
||||||
|
text: String,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
id: FeatureId? = null,
|
||||||
|
) = addFeature(id, MapTextFeature(position, text, zoomRange, color))
|
||||||
|
|
||||||
fun FeatureBuilder.text(
|
fun FeatureBuilder.text(
|
||||||
position: Pair<Double, Double>,
|
position: Pair<Double, Double>,
|
||||||
text: String,
|
text: String,
|
||||||
|
@ -10,9 +10,14 @@ import kotlin.math.log2
|
|||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
|
||||||
|
//TODO consider replacing by modifier
|
||||||
data class MapViewConfig(
|
data class MapViewConfig(
|
||||||
val zoomSpeed: Double = 1.0 / 3.0,
|
val zoomSpeed: Double = 1.0 / 3.0,
|
||||||
val inferViewBoxFromFeatures: Boolean = false
|
val inferViewBoxFromFeatures: Boolean = false,
|
||||||
|
val onClick: MapViewPoint.() -> Unit = {},
|
||||||
|
val onViewChange: MapViewPoint.() -> Unit = {},
|
||||||
|
val onSelect: (GmcBox) -> Unit = {},
|
||||||
|
val zoomOnSelect: Boolean = true
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -20,8 +25,6 @@ expect fun MapView(
|
|||||||
mapTileProvider: MapTileProvider,
|
mapTileProvider: MapTileProvider,
|
||||||
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
|
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
|
||||||
features: Map<FeatureId, MapFeature>,
|
features: Map<FeatureId, MapFeature>,
|
||||||
onClick: MapViewPoint.() -> Unit = {},
|
|
||||||
//TODO consider replacing by modifier
|
|
||||||
config: MapViewConfig = MapViewConfig(),
|
config: MapViewConfig = MapViewConfig(),
|
||||||
modifier: Modifier = Modifier.fillMaxSize(),
|
modifier: Modifier = Modifier.fillMaxSize(),
|
||||||
)
|
)
|
||||||
@ -31,14 +34,29 @@ fun MapView(
|
|||||||
mapTileProvider: MapTileProvider,
|
mapTileProvider: MapTileProvider,
|
||||||
initialViewPoint: MapViewPoint,
|
initialViewPoint: MapViewPoint,
|
||||||
features: Map<FeatureId, MapFeature> = emptyMap(),
|
features: Map<FeatureId, MapFeature> = emptyMap(),
|
||||||
onClick: MapViewPoint.() -> Unit = {},
|
|
||||||
config: MapViewConfig = MapViewConfig(),
|
config: MapViewConfig = MapViewConfig(),
|
||||||
modifier: Modifier = Modifier.fillMaxSize(),
|
modifier: Modifier = Modifier.fillMaxSize(),
|
||||||
buildFeatures: @Composable (FeatureBuilder.() -> Unit) = {},
|
buildFeatures: @Composable (FeatureBuilder.() -> Unit) = {},
|
||||||
) {
|
) {
|
||||||
val featuresBuilder = MapFeatureBuilder(features)
|
val featuresBuilder = MapFeatureBuilder(features)
|
||||||
featuresBuilder.buildFeatures()
|
featuresBuilder.buildFeatures()
|
||||||
MapView(mapTileProvider, { initialViewPoint }, featuresBuilder.build(), onClick, config, modifier)
|
MapView(
|
||||||
|
mapTileProvider,
|
||||||
|
{ initialViewPoint },
|
||||||
|
featuresBuilder.build(),
|
||||||
|
config,
|
||||||
|
modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun GmcBox.getComputeViewPoint(mapTileProvider: MapTileProvider): (canvasSize: DpSize) -> MapViewPoint = { canvasSize ->
|
||||||
|
val zoom = log2(
|
||||||
|
min(
|
||||||
|
canvasSize.width.value / width,
|
||||||
|
canvasSize.height.value / height
|
||||||
|
) * PI / mapTileProvider.tileSize
|
||||||
|
)
|
||||||
|
MapViewPoint(center, zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -46,21 +64,17 @@ fun MapView(
|
|||||||
mapTileProvider: MapTileProvider,
|
mapTileProvider: MapTileProvider,
|
||||||
box: GmcBox,
|
box: GmcBox,
|
||||||
features: Map<FeatureId, MapFeature> = emptyMap(),
|
features: Map<FeatureId, MapFeature> = emptyMap(),
|
||||||
onClick: MapViewPoint.() -> Unit = {},
|
|
||||||
config: MapViewConfig = MapViewConfig(),
|
config: MapViewConfig = MapViewConfig(),
|
||||||
modifier: Modifier = Modifier.fillMaxSize(),
|
modifier: Modifier = Modifier.fillMaxSize(),
|
||||||
buildFeatures: @Composable (FeatureBuilder.() -> Unit) = {},
|
buildFeatures: @Composable (FeatureBuilder.() -> Unit) = {},
|
||||||
) {
|
) {
|
||||||
val featuresBuilder = MapFeatureBuilder(features)
|
val featuresBuilder = MapFeatureBuilder(features)
|
||||||
featuresBuilder.buildFeatures()
|
featuresBuilder.buildFeatures()
|
||||||
val computeViewPoint: (canvasSize: DpSize) -> MapViewPoint = { canvasSize ->
|
MapView(
|
||||||
val zoom = log2(
|
mapTileProvider,
|
||||||
min(
|
box.getComputeViewPoint(mapTileProvider),
|
||||||
canvasSize.width.value / box.width,
|
featuresBuilder.build(),
|
||||||
canvasSize.height.value / box.height
|
config,
|
||||||
) * PI / mapTileProvider.tileSize
|
modifier
|
||||||
)
|
)
|
||||||
MapViewPoint(box.center, zoom)
|
|
||||||
}
|
|
||||||
MapView(mapTileProvider, computeViewPoint, featuresBuilder.build(), onClick, config, modifier)
|
|
||||||
}
|
}
|
@ -9,7 +9,6 @@ import androidx.compose.ui.ExperimentalComposeUiApi
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.geometry.Rect
|
import androidx.compose.ui.geometry.Rect
|
||||||
import androidx.compose.ui.geometry.Size
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.PathEffect
|
import androidx.compose.ui.graphics.PathEffect
|
||||||
import androidx.compose.ui.graphics.drawscope.*
|
import androidx.compose.ui.graphics.drawscope.*
|
||||||
@ -55,7 +54,6 @@ actual fun MapView(
|
|||||||
mapTileProvider: MapTileProvider,
|
mapTileProvider: MapTileProvider,
|
||||||
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
|
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
|
||||||
features: Map<FeatureId, MapFeature>,
|
features: Map<FeatureId, MapFeature>,
|
||||||
onClick: MapViewPoint.() -> Unit,
|
|
||||||
config: MapViewConfig,
|
config: MapViewConfig,
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
) {
|
) {
|
||||||
@ -107,6 +105,8 @@ actual fun MapView(
|
|||||||
val canvasModifier = modifier.pointerInput(Unit) {
|
val canvasModifier = modifier.pointerInput(Unit) {
|
||||||
forEachGesture {
|
forEachGesture {
|
||||||
awaitPointerEventScope {
|
awaitPointerEventScope {
|
||||||
|
fun Offset.toDpOffset() = DpOffset(x.toDp(), y.toDp())
|
||||||
|
|
||||||
val event: PointerEvent = awaitPointerEvent()
|
val event: PointerEvent = awaitPointerEvent()
|
||||||
event.changes.forEach { change ->
|
event.changes.forEach { change ->
|
||||||
if (event.buttons.isPrimaryPressed) {
|
if (event.buttons.isPrimaryPressed) {
|
||||||
@ -125,29 +125,32 @@ actual fun MapView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
selectRect?.let { rect ->
|
selectRect?.let { rect ->
|
||||||
val (centerX, centerY) = rect.center
|
//Use selection override if it is defined
|
||||||
val centerGmc = DpOffset(centerX.toDp(), centerY.toDp()).toGeodetic()
|
val gmcBox = GmcBox(
|
||||||
|
rect.topLeft.toDpOffset().toGeodetic(),
|
||||||
val horizontalZoom: Float = log2(canvasSize.width.toPx() / rect.width)
|
rect.bottomRight.toDpOffset().toGeodetic()
|
||||||
val verticalZoom: Float = log2(canvasSize.height.toPx() / rect.height)
|
|
||||||
|
|
||||||
|
|
||||||
viewPointOverride = MapViewPoint(
|
|
||||||
centerGmc,
|
|
||||||
viewPoint.zoom + min(verticalZoom, horizontalZoom)
|
|
||||||
)
|
)
|
||||||
|
config.onSelect(gmcBox)
|
||||||
|
if(config.zoomOnSelect) {
|
||||||
|
val newViewPoint = gmcBox.getComputeViewPoint(mapTileProvider).invoke(canvasSize)
|
||||||
|
|
||||||
|
config.onViewChange(newViewPoint)
|
||||||
|
viewPointOverride = newViewPoint
|
||||||
|
}
|
||||||
selectRect = null
|
selectRect = null
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val dragStart = change.position
|
val dragStart = change.position
|
||||||
val dpPos = DpOffset(dragStart.x.toDp(), dragStart.y.toDp())
|
val dpPos = DpOffset(dragStart.x.toDp(), dragStart.y.toDp())
|
||||||
onClick(MapViewPoint(dpPos.toGeodetic(), viewPoint.zoom))
|
config.onClick(MapViewPoint(dpPos.toGeodetic(), viewPoint.zoom))
|
||||||
drag(change.id) { dragChange ->
|
drag(change.id) { dragChange ->
|
||||||
val dragAmount = dragChange.position - dragChange.previousPosition
|
val dragAmount = dragChange.position - dragChange.previousPosition
|
||||||
viewPointOverride = viewPoint.move(
|
val newViewPoint = viewPoint.move(
|
||||||
-dragAmount.x.toDp().value / tileScale,
|
-dragAmount.x.toDp().value / tileScale,
|
||||||
+dragAmount.y.toDp().value / tileScale
|
+dragAmount.y.toDp().value / tileScale
|
||||||
)
|
)
|
||||||
|
config.onViewChange(newViewPoint)
|
||||||
|
viewPointOverride = newViewPoint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,7 +162,9 @@ actual fun MapView(
|
|||||||
val (xPos, yPos) = change.position
|
val (xPos, yPos) = change.position
|
||||||
//compute invariant point of translation
|
//compute invariant point of translation
|
||||||
val invariant = DpOffset(xPos.toDp(), yPos.toDp()).toGeodetic()
|
val invariant = DpOffset(xPos.toDp(), yPos.toDp()).toGeodetic()
|
||||||
viewPointOverride = viewPoint.zoom(-change.scrollDelta.y.toDouble() * config.zoomSpeed, invariant)
|
val newViewPoint = viewPoint.zoom(-change.scrollDelta.y.toDouble() * config.zoomSpeed, invariant)
|
||||||
|
config.onViewChange(newViewPoint)
|
||||||
|
viewPointOverride = newViewPoint
|
||||||
}.fillMaxSize()
|
}.fillMaxSize()
|
||||||
|
|
||||||
|
|
||||||
@ -241,7 +246,7 @@ actual fun MapView(
|
|||||||
}
|
}
|
||||||
is MapDrawFeature -> {
|
is MapDrawFeature -> {
|
||||||
val offset = feature.position.toOffset()
|
val offset = feature.position.toOffset()
|
||||||
translate (offset.x, offset.y) {
|
translate(offset.x, offset.y) {
|
||||||
feature.drawFeature(this)
|
feature.drawFeature(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user