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