Implementation for TAVRIDA-T-7
This commit is contained in:
parent
960c64d480
commit
b764fdd95a
@ -2,7 +2,6 @@ package centre.sciprog.maps
|
||||
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.math.sign
|
||||
|
||||
/**
|
||||
* Observable position on the map. Includes observation coordinate and [zoom] factor
|
||||
@ -14,7 +13,7 @@ data class MapViewPoint(
|
||||
val scaleFactor by lazy { WebMercatorProjection.scaleFactor(zoom.roundToInt()) }
|
||||
}
|
||||
|
||||
fun MapViewPoint.move(deltaX: Float, deltaY: Float): MapViewPoint {
|
||||
fun MapViewPoint.move(deltaX: Double, deltaY: Double): MapViewPoint {
|
||||
val newCoordinates = GeodeticMapCoordinates.ofRadians(
|
||||
(focus.latitude + deltaY / scaleFactor).coerceIn(
|
||||
-MercatorProjection.MAXIMUM_LATITUDE,
|
||||
@ -38,11 +37,11 @@ fun MapViewPoint.move(delta: GeodeticMapCoordinates): MapViewPoint {
|
||||
|
||||
fun MapViewPoint.zoom(zoomDelta: Double): MapViewPoint = copy(zoom = (zoom + zoomDelta).coerceIn(2.0, 18.0))
|
||||
|
||||
fun MapViewPoint.zoom(zoomDelta: Double, at: GeodeticMapCoordinates): MapViewPoint {
|
||||
fun MapViewPoint.zoom(zoomDelta: Double, invariant: GeodeticMapCoordinates): MapViewPoint {
|
||||
val difScale = 2.0.pow(-zoomDelta)
|
||||
val newCenter = GeodeticMapCoordinates.ofRadians(
|
||||
focus.latitude + (at.latitude - focus.latitude) * difScale,
|
||||
focus.longitude + (at.longitude - focus.longitude) * difScale
|
||||
focus.latitude + (invariant.latitude - focus.latitude) * difScale,
|
||||
focus.longitude + (invariant.longitude - focus.longitude) * difScale
|
||||
)
|
||||
return MapViewPoint(newCenter, (zoom + zoomDelta).coerceIn(2.0, 18.0))
|
||||
}
|
@ -6,12 +6,19 @@ import androidx.compose.ui.Modifier
|
||||
import centre.sciprog.maps.GeodeticMapCoordinates
|
||||
import centre.sciprog.maps.MapViewPoint
|
||||
|
||||
|
||||
data class MapViewConfig(
|
||||
val zoomSpeed: Double = 1.0 / 3.0,
|
||||
)
|
||||
|
||||
@Composable
|
||||
expect fun MapView(
|
||||
initialViewPoint: MapViewPoint,
|
||||
mapTileProvider: MapTileProvider,
|
||||
features: Map<FeatureId, MapFeature>,
|
||||
onClick: (GeodeticMapCoordinates) -> Unit = {},
|
||||
//TODO consider replacing by modifier
|
||||
config: MapViewConfig = MapViewConfig(),
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
|
||||
@ -20,10 +27,11 @@ fun MapView(
|
||||
initialViewPoint: MapViewPoint,
|
||||
mapTileProvider: MapTileProvider,
|
||||
onClick: (GeodeticMapCoordinates) -> Unit = {},
|
||||
config: MapViewConfig = MapViewConfig(),
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
addFeatures: @Composable() (FeatureBuilder.() -> Unit) = {},
|
||||
) {
|
||||
val featuresBuilder = MapFeatureBuilder()
|
||||
featuresBuilder.addFeatures()
|
||||
MapView(initialViewPoint, mapTileProvider, featuresBuilder.build(), onClick, modifier)
|
||||
MapView(initialViewPoint, mapTileProvider, featuresBuilder.build(), onClick, config, modifier)
|
||||
}
|
@ -43,6 +43,7 @@ actual fun MapView(
|
||||
mapTileProvider: MapTileProvider,
|
||||
features: Map<FeatureId, MapFeature>,
|
||||
onClick: (GeodeticMapCoordinates) -> Unit,
|
||||
config: MapViewConfig,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
|
||||
@ -50,6 +51,8 @@ actual fun MapView(
|
||||
|
||||
val zoom: Int by derivedStateOf { viewPoint.zoom.roundToInt() }
|
||||
|
||||
val tileScale: Double by derivedStateOf { 2.0.pow(viewPoint.zoom - zoom) }
|
||||
|
||||
val mapTiles = remember { mutableStateListOf<MapTile>() }
|
||||
|
||||
//var mapRectangle by remember { mutableStateOf(initialRectangle) }
|
||||
@ -59,10 +62,13 @@ actual fun MapView(
|
||||
|
||||
fun DpOffset.toMercator(): WebMercatorCoordinates = WebMercatorCoordinates(
|
||||
zoom,
|
||||
(x - canvasSize.width / 2).value + centerCoordinates.x,
|
||||
(y - canvasSize.height / 2).value + centerCoordinates.y,
|
||||
(x - canvasSize.width / 2).value / tileScale + centerCoordinates.x,
|
||||
(y - canvasSize.height / 2).value / tileScale + centerCoordinates.y,
|
||||
)
|
||||
|
||||
/*
|
||||
* Convert screen independent offset to GMC, adjusting for fractional zoom
|
||||
*/
|
||||
fun DpOffset.toGeodetic() = WebMercatorProjection.toGeodetic(toMercator())
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@ -73,23 +79,23 @@ actual fun MapView(
|
||||
val change = it.changes.first()
|
||||
val (xPos, yPos) = change.position
|
||||
//compute invariant point of translation
|
||||
val at = DpOffset(xPos.toDp(), yPos.toDp()).toGeodetic()
|
||||
viewPoint = viewPoint.zoom(-change.scrollDelta.y.toDouble(), at)
|
||||
val invariant = DpOffset(xPos.toDp(), yPos.toDp()).toGeodetic()
|
||||
viewPoint = viewPoint.zoom(-change.scrollDelta.y.toDouble() * config.zoomSpeed, invariant)
|
||||
}.pointerInput(Unit) {
|
||||
detectDragGestures { _: PointerInputChange, dragAmount: Offset ->
|
||||
viewPoint = viewPoint.move(-dragAmount.x, +dragAmount.y)
|
||||
viewPoint = viewPoint.move(-dragAmount.x.toDp().value / tileScale, +dragAmount.y.toDp().value / tileScale)
|
||||
}
|
||||
}.fillMaxSize()
|
||||
|
||||
|
||||
// Load tiles asynchronously
|
||||
LaunchedEffect(viewPoint, canvasSize) {
|
||||
val left = centerCoordinates.x - canvasSize.width.value / 2
|
||||
val right = centerCoordinates.x + canvasSize.width.value / 2
|
||||
val left = centerCoordinates.x - canvasSize.width.value / 2 / tileScale
|
||||
val right = centerCoordinates.x + canvasSize.width.value / 2 / tileScale
|
||||
val horizontalIndices = mapTileProvider.toIndex(left)..mapTileProvider.toIndex(right)
|
||||
|
||||
val top = (centerCoordinates.y + canvasSize.height.value / 2)
|
||||
val bottom = (centerCoordinates.y - canvasSize.height.value / 2)
|
||||
val top = (centerCoordinates.y + canvasSize.height.value / 2 / tileScale)
|
||||
val bottom = (centerCoordinates.y - canvasSize.height.value / 2 / tileScale)
|
||||
val verticalIndices = mapTileProvider.toIndex(bottom)..mapTileProvider.toIndex(top)
|
||||
|
||||
mapTiles.clear()
|
||||
@ -112,14 +118,15 @@ actual fun MapView(
|
||||
|
||||
}
|
||||
|
||||
|
||||
// d
|
||||
Canvas(canvasModifier) {
|
||||
|
||||
fun WebMercatorCoordinates.toOffset(): Offset = Offset(
|
||||
(canvasSize.width / 2 - centerCoordinates.x.dp + x.dp).toPx(),
|
||||
(canvasSize.height / 2 - centerCoordinates.y.dp + y.dp).toPx()
|
||||
(canvasSize.width / 2 + (x.dp - centerCoordinates.x.dp) * tileScale.toFloat()).toPx(),
|
||||
(canvasSize.height / 2 + (y.dp - centerCoordinates.y.dp) * tileScale.toFloat()).toPx()
|
||||
)
|
||||
|
||||
//Convert GMC to offset in pixels (not DP), adjusting for zoom
|
||||
fun GeodeticMapCoordinates.toOffset(): Offset = WebMercatorProjection.toMercator(this, zoom).toOffset()
|
||||
|
||||
|
||||
@ -160,12 +167,15 @@ actual fun MapView(
|
||||
logger.debug { "Recalculate canvas. Size: $size" }
|
||||
}
|
||||
clipRect {
|
||||
val tileSize = IntSize(mapTileProvider.tileSize.dp.roundToPx(), mapTileProvider.tileSize.dp.roundToPx())
|
||||
val tileSize = IntSize(
|
||||
(mapTileProvider.tileSize.dp * tileScale.toFloat()).roundToPx(),
|
||||
(mapTileProvider.tileSize.dp * tileScale.toFloat()).roundToPx()
|
||||
)
|
||||
mapTiles.forEach { (id, image) ->
|
||||
//converting back from tile index to screen offset
|
||||
val offset = IntOffset(
|
||||
(canvasSize.width / 2 - centerCoordinates.x.dp + mapTileProvider.toCoordinate(id.i).dp).roundToPx(),
|
||||
(canvasSize.height / 2 - centerCoordinates.y.dp + mapTileProvider.toCoordinate(id.j).dp).roundToPx()
|
||||
(canvasSize.width / 2 + (mapTileProvider.toCoordinate(id.i).dp - centerCoordinates.x.dp) * tileScale.toFloat()).roundToPx(),
|
||||
(canvasSize.height / 2 + (mapTileProvider.toCoordinate(id.j).dp - centerCoordinates.y.dp) * tileScale.toFloat()).roundToPx()
|
||||
)
|
||||
drawImage(
|
||||
image = image,
|
||||
|
Loading…
Reference in New Issue
Block a user