Fix for TAVRIDA-T-2

This commit is contained in:
Alexander Nozik 2022-07-11 20:29:07 +03:00
parent 792a755682
commit 2ac4bdfd4f
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
2 changed files with 85 additions and 75 deletions

View File

@ -16,10 +16,14 @@ data class MapTile(
interface MapTileProvider {
suspend fun loadTile(id: TileId): MapTile
fun toIndex(d: Double): Int = floor(d / DEFAULT_TILE_SIZE).toInt()
fun toCoordinate(i: Int): Double = (i * DEFAULT_TILE_SIZE).toDouble()
companion object{
val tileSize: Int get() = DEFAULT_TILE_SIZE
fun toIndex(d: Double): Int = floor(d / tileSize).toInt()
fun toCoordinate(i: Int): Double = (i * tileSize).toDouble()
companion object {
const val DEFAULT_TILE_SIZE = 256
}
}

View File

@ -4,11 +4,9 @@ import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.clipRect
@ -20,6 +18,7 @@ import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.*
import centre.sciprog.maps.*
import mu.KotlinLogging
import org.jetbrains.skia.Font
@ -46,6 +45,7 @@ actual fun MapView(
onClick: (GeodeticMapCoordinates) -> Unit,
modifier: Modifier,
) {
var viewPoint by remember { mutableStateOf(initialViewPoint) }
val zoom: Int by derivedStateOf { viewPoint.zoom.roundToInt() }
@ -53,18 +53,39 @@ actual fun MapView(
val mapTiles = remember { mutableStateListOf<MapTile>() }
//var mapRectangle by remember { mutableStateOf(initialRectangle) }
var canvasSize by remember { mutableStateOf(Size(512f, 512f)) }
var canvasSize by remember { mutableStateOf(DpSize(512.dp, 512.dp)) }
val centerCoordinates by derivedStateOf { WebMercatorProjection.toMercator(viewPoint.focus, zoom) }
fun DpOffset.toMercator(): WebMercatorCoordinates = WebMercatorCoordinates(
zoom,
(x - canvasSize.width / 2).value + centerCoordinates.x,
(y - canvasSize.height / 2).value + centerCoordinates.y,
)
fun DpOffset.toGeodetic() = WebMercatorProjection.toGeodetic(toMercator())
@OptIn(ExperimentalComposeUiApi::class)
val canvasModifier = modifier.onPointerEvent(PointerEventType.Press) {
val (xPos, yPos) = it.changes.first().position
onClick(DpOffset(xPos.toDp(), yPos.toDp()).toGeodetic())
}.onPointerEvent(PointerEventType.Scroll) {
viewPoint = viewPoint.zoom(-it.changes.first().scrollDelta.y.toDouble())
}.pointerInput(Unit) {
detectDragGestures { _: PointerInputChange, dragAmount: Offset ->
viewPoint = viewPoint.move(-dragAmount.x, +dragAmount.y)
}
}.fillMaxSize()
// Load tiles asynchronously
LaunchedEffect(viewPoint, canvasSize) {
val left = centerCoordinates.x - canvasSize.width / 2
val right = centerCoordinates.x + canvasSize.width / 2
val left = centerCoordinates.x - canvasSize.width.value / 2
val right = centerCoordinates.x + canvasSize.width.value / 2
val horizontalIndices = mapTileProvider.toIndex(left)..mapTileProvider.toIndex(right)
val top = (centerCoordinates.y + canvasSize.height / 2)
val bottom = (centerCoordinates.y - canvasSize.height / 2)
val top = (centerCoordinates.y + canvasSize.height.value / 2)
val bottom = (centerCoordinates.y - canvasSize.height.value / 2)
val verticalIndices = mapTileProvider.toIndex(bottom)..mapTileProvider.toIndex(top)
mapTiles.clear()
@ -87,80 +108,65 @@ actual fun MapView(
}
fun Offset.toMercator(): WebMercatorCoordinates = WebMercatorCoordinates(
zoom,
x + centerCoordinates.x - canvasSize.width / 2,
y + centerCoordinates.y - canvasSize.height / 2,
)
fun Offset.toGeodetic() = WebMercatorProjection.toGeodetic(toMercator())
fun WebMercatorCoordinates.toOffset(): Offset = Offset(
(canvasSize.width / 2 - centerCoordinates.x + x).toFloat(),
(canvasSize.height / 2 - centerCoordinates.y + y).toFloat()
)
fun GeodeticMapCoordinates.toOffset(): Offset = WebMercatorProjection.toMercator(this, zoom).toOffset()
@OptIn(ExperimentalComposeUiApi::class)
val canvasModifier = modifier.onPointerEvent(PointerEventType.Press) {
onClick(it.changes.first().position.toGeodetic())
}.onPointerEvent(PointerEventType.Scroll) {
viewPoint = viewPoint.zoom(-it.changes.first().scrollDelta.y.toDouble())
}.pointerInput(Unit) {
detectDragGestures { _: PointerInputChange, dragAmount: Offset ->
viewPoint = viewPoint.move(-dragAmount.x, +dragAmount.y)
}
}.fillMaxSize()
fun DrawScope.drawFeature(zoom: Int, feature: MapFeature) {
when (feature) {
is MapFeatureSelector -> drawFeature(zoom, feature.selector(zoom))
is MapCircleFeature -> drawCircle(
feature.color,
feature.size,
center = feature.center.toOffset()
)
is MapLineFeature -> drawLine(feature.color, feature.a.toOffset(), feature.b.toOffset())
is MapBitmapImageFeature -> drawImage(feature.image, feature.position.toOffset())
is MapVectorImageFeature -> {
val offset = feature.position.toOffset()
translate(offset.x - feature.size.width / 2, offset.y - feature.size.height / 2) {
with(feature.painter) {
draw(feature.size)
}
}
}
is MapTextFeature -> drawIntoCanvas { canvas ->
val offset = feature.position.toOffset()
canvas.nativeCanvas.drawString(
feature.text,
offset.x + 5,
offset.y - 5,
Font().apply { size = 16f },
feature.color.toPaint()
)
}
}
}
Canvas(canvasModifier) {
if (canvasSize != size) {
canvasSize = size
logger.debug { "Redraw canvas. Size: $size" }
fun WebMercatorCoordinates.toOffset(): Offset = Offset(
(canvasSize.width / 2 - centerCoordinates.x.dp + x.dp).toPx(),
(canvasSize.height / 2 - centerCoordinates.y.dp + y.dp).toPx()
)
fun GeodeticMapCoordinates.toOffset(): Offset = WebMercatorProjection.toMercator(this, zoom).toOffset()
fun DrawScope.drawFeature(zoom: Int, feature: MapFeature) {
when (feature) {
is MapFeatureSelector -> drawFeature(zoom, feature.selector(zoom))
is MapCircleFeature -> drawCircle(
feature.color,
feature.size,
center = feature.center.toOffset()
)
is MapLineFeature -> drawLine(feature.color, feature.a.toOffset(), feature.b.toOffset())
is MapBitmapImageFeature -> drawImage(feature.image, feature.position.toOffset())
is MapVectorImageFeature -> {
val offset = feature.position.toOffset()
translate(offset.x - feature.size.width / 2, offset.y - feature.size.height / 2) {
with(feature.painter) {
draw(feature.size)
}
}
}
is MapTextFeature -> drawIntoCanvas { canvas ->
val offset = feature.position.toOffset()
canvas.nativeCanvas.drawString(
feature.text,
offset.x + 5,
offset.y - 5,
Font().apply { size = 16f },
feature.color.toPaint()
)
}
}
}
if (canvasSize != size.toDpSize()) {
canvasSize = size.toDpSize()
logger.debug { "Recalculate canvas. Size: $size" }
}
clipRect {
val tileSize = IntSize(mapTileProvider.tileSize.dp.roundToPx(), mapTileProvider.tileSize.dp.roundToPx())
mapTiles.forEach { (id, image) ->
//converting back from tile index to screen offset
val offset = Offset(
(canvasSize.width / 2 - centerCoordinates.x + mapTileProvider.toCoordinate(id.i)).toFloat(),
(canvasSize.height / 2 - centerCoordinates.y + mapTileProvider.toCoordinate(id.j)).toFloat()
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()
)
drawImage(
image = image,
topLeft = offset
dstOffset = offset,
dstSize = tileSize
)
}
features.values.filter { zoom in it.zoomRange }.forEach { feature ->