Fix for TAVRIDA-T-2
This commit is contained in:
parent
792a755682
commit
2ac4bdfd4f
@ -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
|
||||
}
|
||||
}
|
@ -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 ->
|
||||
|
Loading…
Reference in New Issue
Block a user