Change state propagation and rectangle selection
This commit is contained in:
parent
4cabfcf16f
commit
6b0a2566bd
@ -11,9 +11,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import center.sciprog.maps.compose.*
|
||||
import center.sciprog.maps.coordinates.Distance
|
||||
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
|
||||
import center.sciprog.maps.coordinates.MapViewPoint
|
||||
import center.sciprog.maps.coordinates.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.cio.CIO
|
||||
import kotlinx.coroutines.delay
|
||||
@ -22,9 +20,6 @@ import kotlinx.coroutines.launch
|
||||
import java.nio.file.Path
|
||||
import kotlin.math.PI
|
||||
import kotlin.random.Random
|
||||
import center.sciprog.maps.coordinates.kilometers
|
||||
import center.sciprog.maps.coordinates.radians
|
||||
import center.sciprog.maps.coordinates.Angle
|
||||
|
||||
private fun GeodeticMapCoordinates.toShortString(): String =
|
||||
"${(latitude.degrees.value).toString().take(6)}:${(longitude.degrees.value).toString().take(6)}"
|
||||
@ -34,13 +29,6 @@ private fun GeodeticMapCoordinates.toShortString(): String =
|
||||
@Preview
|
||||
fun App() {
|
||||
MaterialTheme {
|
||||
//create a view point
|
||||
// val viewPoint = remember {
|
||||
// MapViewPoint(
|
||||
// GeodeticMapCoordinates.ofDegrees(55.7558, 37.6173),
|
||||
// 8.0
|
||||
// )
|
||||
// }
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val mapTileProvider = remember {
|
||||
@ -50,7 +38,7 @@ fun App() {
|
||||
)
|
||||
}
|
||||
|
||||
var centerCoordinates by remember { mutableStateOf<GeodeticMapCoordinates?>(null) }
|
||||
var centerCoordinates by remember { mutableStateOf<Gmc?>(null) }
|
||||
|
||||
|
||||
val pointOne = 55.568548 to 37.568604
|
||||
@ -61,7 +49,15 @@ fun App() {
|
||||
|
||||
MapView(
|
||||
mapTileProvider = mapTileProvider,
|
||||
initialViewPoint = null,// use null to infer view point from features
|
||||
// initialViewPoint = MapViewPoint(
|
||||
// GeodeticMapCoordinates.ofDegrees(55.7558, 37.6173),
|
||||
// 8.0
|
||||
// ),
|
||||
// initialRectangle = GmcRectangle.square(
|
||||
// GeodeticMapCoordinates.ofDegrees(55.7558, 37.6173),
|
||||
// 50.kilometers,
|
||||
// 50.kilometers
|
||||
// ),
|
||||
config = MapViewConfig(
|
||||
onViewChange = { centerCoordinates = focus },
|
||||
)
|
||||
@ -93,7 +89,7 @@ fun App() {
|
||||
drawLine(start = Offset(-10f, 10f), end = Offset(10f, -10f), color = Color.Red)
|
||||
}
|
||||
|
||||
arc(pointOne, 10.0.kilometers, (PI/4).radians, -Angle.pi/2)
|
||||
arc(pointOne, 10.0.kilometers, (PI / 4).radians, -Angle.pi / 2)
|
||||
|
||||
line(pointOne, pointTwo, id = "line")
|
||||
text(pointOne, "Home", font = { size = 32f })
|
||||
|
@ -1,9 +1,8 @@
|
||||
package center.sciprog.maps.compose
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.PointerEvent
|
||||
import androidx.compose.ui.input.pointer.isPrimaryPressed
|
||||
@ -62,42 +61,18 @@ public data class MapViewConfig(
|
||||
val onViewChange: MapViewPoint.() -> Unit = {},
|
||||
val onSelect: (GmcRectangle) -> Unit = {},
|
||||
val zoomOnSelect: Boolean = true,
|
||||
val onCanvasSizeChange: (DpSize) -> Unit = {},
|
||||
)
|
||||
|
||||
@Composable
|
||||
public expect fun MapView(
|
||||
mapTileProvider: MapTileProvider,
|
||||
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
|
||||
initialViewPoint: MapViewPoint,
|
||||
features: Map<FeatureId, MapFeature>,
|
||||
config: MapViewConfig = MapViewConfig(),
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
|
||||
private fun prepareConfig(initialConfig: MapViewConfig, featureBuilder: MapFeatureBuilder): MapViewConfig {
|
||||
val draggableFeatureIds: Set<FeatureId> = featureBuilder.attributes().filterValues {
|
||||
it[DraggableAttribute] ?: false
|
||||
}.keys
|
||||
|
||||
val features = featureBuilder.features
|
||||
|
||||
val featureDrag = DragHandle.withPrimaryButton { _, start, end ->
|
||||
val zoom = start.zoom
|
||||
draggableFeatureIds.forEach { id ->
|
||||
val feature = features[id] as? DraggableMapFeature ?: return@forEach
|
||||
//val border = WebMercatorProjection.scaleFactor(zoom)
|
||||
val boundingBox = feature.getBoundingBox(zoom) ?: return@forEach
|
||||
if (start.focus in boundingBox) {
|
||||
features[id] = feature.withCoordinates(end.focus)
|
||||
return@withPrimaryButton false
|
||||
}
|
||||
}
|
||||
return@withPrimaryButton true
|
||||
}
|
||||
return initialConfig.copy(
|
||||
dragHandle = DragHandle.combine(featureDrag, initialConfig.dragHandle),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
internal fun GmcRectangle.computeViewPoint(
|
||||
mapTileProvider: MapTileProvider,
|
||||
@ -128,44 +103,50 @@ public fun MapView(
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
|
||||
) {
|
||||
val featuresBuilder = MapFeatureBuilderImpl(mutableStateMapOf())
|
||||
featuresBuilder.buildFeatures()
|
||||
val features = remember { featuresBuilder.features }
|
||||
|
||||
val newConfig = remember(features) {
|
||||
prepareConfig(config, featuresBuilder)
|
||||
var viewPointOverride by remember { mutableStateOf(initialViewPoint ?: MapViewPoint.globe) }
|
||||
|
||||
val featuresBuilder = MapFeatureBuilderImpl(mutableStateMapOf()).apply { buildFeatures() }
|
||||
|
||||
val features: SnapshotStateMap<FeatureId, MapFeature> = remember { featuresBuilder.features }
|
||||
|
||||
val attributes = remember { featuresBuilder.attributes() }
|
||||
|
||||
val featureDrag = remember {
|
||||
DragHandle.withPrimaryButton { _, start, end ->
|
||||
val zoom = start.zoom
|
||||
attributes.filterValues {
|
||||
it[DraggableAttribute] ?: false
|
||||
}.keys.forEach { id ->
|
||||
val feature = features[id] as? DraggableMapFeature ?: return@forEach
|
||||
//val border = WebMercatorProjection.scaleFactor(zoom)
|
||||
val boundingBox = feature.getBoundingBox(zoom) ?: return@forEach
|
||||
if (start.focus in boundingBox) {
|
||||
features[id] = feature.withCoordinates(end.focus)
|
||||
return@withPrimaryButton false
|
||||
}
|
||||
}
|
||||
return@withPrimaryButton true
|
||||
}
|
||||
}
|
||||
|
||||
MapView(
|
||||
mapTileProvider,
|
||||
{ canvasSize ->
|
||||
initialViewPoint
|
||||
val newConfig = config.copy(
|
||||
dragHandle = DragHandle.combine(featureDrag, config.dragHandle),
|
||||
onCanvasSizeChange = { canvasSize ->
|
||||
viewPointOverride = initialViewPoint
|
||||
?: initialRectangle?.computeViewPoint(mapTileProvider, canvasSize)
|
||||
?: features.values.computeBoundingBox(1.0)?.computeViewPoint(mapTileProvider, canvasSize)
|
||||
?: MapViewPoint(GeodeticMapCoordinates(0.0.radians, 0.0.radians), 1.0)
|
||||
},
|
||||
features,
|
||||
newConfig,
|
||||
modifier
|
||||
)
|
||||
}
|
||||
?: features.values.computeBoundingBox(1.0)?.computeViewPoint(mapTileProvider, canvasSize)
|
||||
?: MapViewPoint.globe
|
||||
|
||||
//
|
||||
//@Composable
|
||||
//public fun MapView(
|
||||
// mapTileProvider: MapTileProvider,
|
||||
// box: GmcRectangle,
|
||||
// config: MapViewConfig = MapViewConfig(),
|
||||
// modifier: Modifier = Modifier.fillMaxSize(),
|
||||
// buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
|
||||
//) {
|
||||
// val builder by derivedStateOf { MapFeatureBuilderImpl().apply(buildFeatures) }
|
||||
//
|
||||
// MapView(
|
||||
// mapTileProvider,
|
||||
// box.computeViewPoint(mapTileProvider),
|
||||
// builder.features,
|
||||
// prepareConfig(config, builder),
|
||||
// modifier
|
||||
// )
|
||||
//}
|
||||
config.onCanvasSizeChange(canvasSize)
|
||||
}
|
||||
)
|
||||
|
||||
MapView(
|
||||
mapTileProvider = mapTileProvider,
|
||||
initialViewPoint = viewPointOverride,
|
||||
features = features,
|
||||
config = newConfig,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
@ -49,15 +49,24 @@ private val logger = KotlinLogging.logger("MapView")
|
||||
@Composable
|
||||
public actual fun MapView(
|
||||
mapTileProvider: MapTileProvider,
|
||||
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
|
||||
initialViewPoint: MapViewPoint,
|
||||
features: Map<FeatureId, MapFeature>,
|
||||
config: MapViewConfig,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
var canvasSize by remember { mutableStateOf(DpSize(512.dp, 512.dp)) }
|
||||
|
||||
var viewPoint: MapViewPoint by remember {
|
||||
mutableStateOf(computeViewPoint(canvasSize))
|
||||
var viewPointOverride by remember { mutableStateOf(initialViewPoint) }
|
||||
var viewPoint by remember { mutableStateOf(initialViewPoint) }
|
||||
|
||||
if (viewPointOverride != initialViewPoint) {
|
||||
viewPoint = initialViewPoint
|
||||
viewPointOverride = initialViewPoint
|
||||
}
|
||||
|
||||
fun setViewPoint(newViewPoint: MapViewPoint) {
|
||||
config.onViewChange(newViewPoint)
|
||||
viewPoint = newViewPoint
|
||||
}
|
||||
|
||||
val zoom: Int by derivedStateOf {
|
||||
@ -140,12 +149,12 @@ public actual fun MapView(
|
||||
|
||||
config.onClick(MapViewPoint(dpPos.toGeodetic(), viewPoint.zoom), event)
|
||||
|
||||
val newViewPoint = viewPoint.move(
|
||||
-dragAmount.x.toDp().value / tileScale,
|
||||
+dragAmount.y.toDp().value / tileScale
|
||||
setViewPoint(
|
||||
viewPoint.move(
|
||||
-dragAmount.x.toDp().value / tileScale,
|
||||
+dragAmount.y.toDp().value / tileScale
|
||||
)
|
||||
)
|
||||
config.onViewChange(newViewPoint)
|
||||
viewPoint = newViewPoint
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,10 +167,7 @@ public actual fun MapView(
|
||||
)
|
||||
config.onSelect(gmcBox)
|
||||
if (config.zoomOnSelect) {
|
||||
val newViewPoint = gmcBox.computeViewPoint(mapTileProvider, canvasSize)
|
||||
|
||||
config.onViewChange(newViewPoint)
|
||||
viewPoint = newViewPoint
|
||||
setViewPoint(gmcBox.computeViewPoint(mapTileProvider, canvasSize))
|
||||
}
|
||||
selectRect = null
|
||||
}
|
||||
@ -173,9 +179,7 @@ public actual fun MapView(
|
||||
val (xPos, yPos) = change.position
|
||||
//compute invariant point of translation
|
||||
val invariant = DpOffset(xPos.toDp(), yPos.toDp()).toGeodetic()
|
||||
val newViewPoint = viewPoint.zoom(-change.scrollDelta.y.toDouble() * config.zoomSpeed, invariant)
|
||||
config.onViewChange(newViewPoint)
|
||||
viewPoint = newViewPoint
|
||||
setViewPoint(viewPoint.zoom(-change.scrollDelta.y.toDouble() * config.zoomSpeed, invariant))
|
||||
}.fillMaxSize()
|
||||
|
||||
|
||||
@ -314,9 +318,11 @@ public actual fun MapView(
|
||||
}
|
||||
|
||||
if (canvasSize != size.toDpSize()) {
|
||||
canvasSize = size.toDpSize()
|
||||
logger.debug { "Recalculate canvas. Size: $size" }
|
||||
config.onCanvasSizeChange(canvasSize)
|
||||
canvasSize = size.toDpSize()
|
||||
}
|
||||
|
||||
clipRect {
|
||||
val tileSize = IntSize(
|
||||
ceil((mapTileProvider.tileSize.dp * tileScale.toFloat()).toPx()).toInt(),
|
||||
|
@ -10,6 +10,10 @@ public data class MapViewPoint(
|
||||
val zoom: Double,
|
||||
) {
|
||||
val scaleFactor: Double by lazy { WebMercatorProjection.scaleFactor(zoom) }
|
||||
|
||||
public companion object{
|
||||
public val globe: MapViewPoint = MapViewPoint(GeodeticMapCoordinates(0.0.radians, 0.0.radians), 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
public fun MapViewPoint.move(delta: GeodeticMapCoordinates): MapViewPoint {
|
||||
|
Loading…
Reference in New Issue
Block a user