Implementation for TAVRIDA-T-7

This commit is contained in:
Alexander Nozik 2022-07-13 11:36:02 +03:00
parent 960c64d480
commit b764fdd95a
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
3 changed files with 38 additions and 21 deletions

View File

@ -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))
}

View File

@ -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)
}

View File

@ -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,