State encapsulation #14

Open
ArystanK wants to merge 4 commits from state_encapsulation into main
5 changed files with 297 additions and 224 deletions
Showing only changes of commit 13b44a8091 - Show all commits

View File

@ -49,34 +49,33 @@ fun App() {
var centerCoordinates by remember { mutableStateOf<GeodeticMapCoordinates?>(null) } var centerCoordinates by remember { mutableStateOf<GeodeticMapCoordinates?>(null) }
val markers = (1..1_000_000).map { // val markers = (1..1_000_000).map {
val position = GeodeticMapCoordinates.ofDegrees( // val position = GeodeticMapCoordinates.ofDegrees(
latitude = Random.nextDouble(-90.0, 90.0), // latitude = Random.nextDouble(-90.0, 90.0),
longitude = Random.nextDouble(0.0, 180.0) // longitude = Random.nextDouble(0.0, 180.0)
) // )
MapDrawFeature( // MapDrawFeature(
position = position, // position = position,
getBoundingBox = { // computeBoundingBox = {
GmcBox.withCenter( // GmcBox.withCenter(
center = position, // center = position,
width = Distance(0.001), // width = Distance(0.001),
height = Distance(0.001) // height = Distance(0.001)
) // )
} // }
) { // ) {
drawRoundRect( // drawRoundRect(
color = Color.Yellow, // color = Color.Yellow,
size = Size(10f, 10f) // size = Size(1f, 1f)
) // )
} // }
} // }
val state = MapViewState(
MapView(
mapTileProvider = mapTileProvider, mapTileProvider = mapTileProvider,
initialViewPoint = viewPoint, computeViewPoint = { viewPoint },
config = MapViewConfig( config = MapViewConfig(
inferViewBoxFromFeatures = true, inferViewBoxFromFeatures = true,
onViewChange = { centerCoordinates = focus } onViewChange = { centerCoordinates = focus },
) )
) { ) {
val pointOne = 55.568548 to 37.568604 val pointOne = 55.568548 to 37.568604
@ -107,9 +106,11 @@ fun App() {
drawLine(start = Offset(-10f, 10f), end = Offset(10f, -10f), color = Color.Red) drawLine(start = Offset(-10f, 10f), end = Offset(10f, -10f), color = Color.Red)
} }
featureSelector { zoom -> // markers.forEach { feature ->
markers.groupBy { } // featureSelector {
} // feature
// }
// }
arc(pointOne, Distance(10.0), 0f, PI) arc(pointOne, Distance(10.0), 0f, PI)
@ -135,6 +136,9 @@ fun App() {
} }
} }
} }
MapView(
mapViewState = state
)
} }
} }

View File

@ -40,10 +40,10 @@ public class MapFeatureSelector(
public class MapDrawFeature( public class MapDrawFeature(
public val position: GeodeticMapCoordinates, public val position: GeodeticMapCoordinates,
override val zoomRange: IntRange = defaultZoomRange, override val zoomRange: IntRange = defaultZoomRange,
private val getBoundingBox: (zoom: Int) -> GmcBox, private val computeBoundingBox: (zoom: Int) -> GmcBox,
public val drawFeature: DrawScope.() -> Unit, public val drawFeature: DrawScope.() -> Unit,
) : MapFeature { ) : MapFeature {
override fun getBoundingBox(zoom: Int): GmcBox = getBoundingBox(zoom) override fun getBoundingBox(zoom: Int): GmcBox = computeBoundingBox(zoom)
} }
public class MapCircleFeature( public class MapCircleFeature(

View File

@ -23,11 +23,8 @@ public data class MapViewConfig(
@Composable @Composable
public expect fun MapView( public expect fun MapView(
mapTileProvider: MapTileProvider, mapViewState: MapViewState,
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint, modifier: Modifier = Modifier,
features: Map<FeatureId, MapFeature>,
config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
) )
@Composable @Composable
@ -42,23 +39,26 @@ public fun MapView(
val featuresBuilder = MapFeatureBuilderImpl(features) val featuresBuilder = MapFeatureBuilderImpl(features)
featuresBuilder.buildFeatures() featuresBuilder.buildFeatures()
MapView( MapView(
mapTileProvider, mapViewState = MapViewState(
{ initialViewPoint }, mapTileProvider = mapTileProvider,
featuresBuilder.build(), computeViewPoint = { initialViewPoint },
config, features = featuresBuilder.build(),
modifier config = config,
),
modifier = modifier
) )
} }
internal fun GmcBox.computeViewPoint(mapTileProvider: MapTileProvider): (canvasSize: DpSize) -> MapViewPoint = { canvasSize -> internal fun GmcBox.computeViewPoint(mapTileProvider: MapTileProvider): (canvasSize: DpSize) -> MapViewPoint =
val zoom = log2( { canvasSize ->
min( val zoom = log2(
canvasSize.width.value / width, min(
canvasSize.height.value / height canvasSize.width.value / width,
) * PI / mapTileProvider.tileSize canvasSize.height.value / height
) ) * PI / mapTileProvider.tileSize
MapViewPoint(center, zoom) )
} MapViewPoint(center, zoom)
}
@Composable @Composable
public fun MapView( public fun MapView(
@ -72,10 +72,12 @@ public fun MapView(
val featuresBuilder = MapFeatureBuilderImpl(features) val featuresBuilder = MapFeatureBuilderImpl(features)
featuresBuilder.buildFeatures() featuresBuilder.buildFeatures()
MapView( MapView(
mapTileProvider, mapViewState = MapViewState(
box.computeViewPoint(mapTileProvider), config = config,
featuresBuilder.build(), mapTileProvider = mapTileProvider,
config, features = featuresBuilder.build(),
computeViewPoint = box.computeViewPoint(mapTileProvider),
),
modifier modifier
) )
} }

View File

@ -0,0 +1,179 @@
package center.sciprog.maps.compose
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import center.sciprog.maps.coordinates.*
import center.sciprog.maps.coordinates.MercatorProjection.Companion.toMercator
import kotlin.math.*
@Composable
public fun MapViewState(
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
mapTileProvider: MapTileProvider,
config: MapViewConfig = MapViewConfig(),
features: Map<FeatureId, MapFeature> = emptyMap(),
buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
): MapViewState {
val featuresBuilder = MapFeatureBuilderImpl(features)
featuresBuilder.buildFeatures()
return MapViewState(
computeViewPoint = computeViewPoint,
mapTileProvider = mapTileProvider,
config = config,
features = featuresBuilder.build()
)
}
public class MapViewState(
public val computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
public val mapTileProvider: MapTileProvider,
public val config: MapViewConfig = MapViewConfig(),
public val features: Map<FeatureId, MapFeature> = emptyMap(),
) {
public var canvasSize: DpSize by mutableStateOf(DpSize(512.dp, 512.dp))
public var viewPointInternal: MapViewPoint? by mutableStateOf(null)
public val viewPoint: MapViewPoint by derivedStateOf {
viewPointInternal ?: if (config.inferViewBoxFromFeatures) {
features.values.computeBoundingBox(1)?.let { box ->
val zoom = log2(
min(
canvasSize.width.value / box.width,
canvasSize.height.value / box.height
) * PI / mapTileProvider.tileSize
)
MapViewPoint(box.center, zoom)
} ?: computeViewPoint(canvasSize)
} else {
computeViewPoint(canvasSize)
}
}
public val zoom: Int by derivedStateOf { floor(viewPoint.zoom).toInt() }
public val tileScale: Double by derivedStateOf { 2.0.pow(viewPoint.zoom - zoom) }
public val mapTiles: SnapshotStateList<MapTile> = mutableStateListOf()
public val centerCoordinates: WebMercatorCoordinates by derivedStateOf {
WebMercatorProjection.toMercator(
viewPoint.focus,
zoom
)
}
public fun DpOffset.toMercator(): WebMercatorCoordinates = WebMercatorCoordinates(
zoom,
(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
*/
public fun DpOffset.toGeodetic(): GeodeticMapCoordinates =
with(this@MapViewState) { WebMercatorProjection.toGeodetic(toMercator()) }
// Selection rectangle. If null - no selection
public var selectRect: Rect? by mutableStateOf(null)
public fun WebMercatorCoordinates.toOffset(density: Density): Offset =
with(density) {
with(this@MapViewState) {
Offset(
(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
public fun GeodeticMapCoordinates.toOffset(density: Density): Offset =
WebMercatorProjection.toMercator(this, zoom).toOffset(density)
public 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(this@drawFeature)
)
is MapRectangleFeature -> drawRect(
feature.color,
topLeft = feature.center.toOffset(this@drawFeature) - Offset(
feature.size.width.toPx() / 2,
feature.size.height.toPx() / 2
),
size = feature.size.toSize()
)
is MapLineFeature -> drawLine(
feature.color,
feature.a.toOffset(this@drawFeature),
feature.b.toOffset(this@drawFeature)
)
is MapArcFeature -> {
val topLeft = feature.oval.topLeft.toOffset(this@drawFeature)
val bottomRight = feature.oval.bottomRight.toOffset(this@drawFeature)
val path = Path().apply {
addArcRad(Rect(topLeft, bottomRight), feature.startAngle, feature.endAngle - feature.startAngle)
}
drawPath(path, color = feature.color, style = Stroke())
}
is MapBitmapImageFeature -> drawImage(
image = feature.image,
topLeft = feature.position.toOffset(this@drawFeature)
)
is MapVectorImageFeature -> {
val offset = feature.position.toOffset(this@drawFeature)
val size = feature.size.toSize()
translate(offset.x - size.width / 2, offset.y - size.height / 2) {
with(feature.painter) {
draw(size)
}
}
}
is MapTextFeature -> drawIntoCanvas { canvas ->
val offset = toOffset(feature.position, mapViewState)
canvas.nativeCanvas.drawString(
feature.text,
offset.x + 5,
offset.y - 5,
Font().apply(feature.fontConfig),
feature.color.toPaint()
)
}
is MapDrawFeature -> {
val offset = toOffset(feature.position, mapViewState)
translate(offset.x, offset.y) {
feature.drawFeature(this)
}
}
is MapFeatureGroup -> {
feature.children.values.forEach {
drawFeature(zoom, it)
}
}
else -> {
logger.error { "Unrecognized feature type: ${feature::class}" }
}
}
}
}

View File

@ -48,60 +48,9 @@ private val logger = KotlinLogging.logger("MapView")
@Composable @Composable
public actual fun MapView( public actual fun MapView(
mapTileProvider: MapTileProvider, mapViewState: MapViewState,
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
features: Map<FeatureId, MapFeature>,
config: MapViewConfig,
modifier: Modifier, modifier: Modifier,
) { ) {
var canvasSize by remember { mutableStateOf(DpSize(512.dp, 512.dp)) }
var viewPointInternal: MapViewPoint? by remember {
mutableStateOf(null)
}
if (config.resetViewPoint) {
viewPointInternal = null
}
val viewPoint: MapViewPoint by derivedStateOf {
viewPointInternal ?: if (config.inferViewBoxFromFeatures) {
features.values.computeBoundingBox(1)?.let { box ->
val zoom = log2(
min(
canvasSize.width.value / box.width,
canvasSize.height.value / box.height
) * PI / mapTileProvider.tileSize
)
MapViewPoint(box.center, zoom)
} ?: computeViewPoint(canvasSize)
} else {
computeViewPoint(canvasSize)
}
}
val zoom: Int by derivedStateOf { floor(viewPoint.zoom).toInt() }
val tileScale: Double by derivedStateOf { 2.0.pow(viewPoint.zoom - zoom) }
val mapTiles = remember { mutableStateListOf<MapTile>() }
val centerCoordinates by derivedStateOf { WebMercatorProjection.toMercator(viewPoint.focus, zoom) }
fun DpOffset.toMercator(): WebMercatorCoordinates = WebMercatorCoordinates(
zoom,
(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())
// Selection rectangle. If null - no selection
var selectRect by remember { mutableStateOf<Rect?>(null) }
@OptIn(ExperimentalComposeUiApi::class) @OptIn(ExperimentalComposeUiApi::class)
val canvasModifier = modifier.pointerInput(Unit) { val canvasModifier = modifier.pointerInput(Unit) {
forEachGesture { forEachGesture {
@ -113,11 +62,11 @@ public actual fun MapView(
if (event.buttons.isPrimaryPressed) { if (event.buttons.isPrimaryPressed) {
//Evaluating selection frame //Evaluating selection frame
if (event.keyboardModifiers.isShiftPressed) { if (event.keyboardModifiers.isShiftPressed) {
selectRect = Rect(change.position, change.position) mapViewState.selectRect = Rect(change.position, change.position)
drag(change.id) { dragChange -> drag(change.id) { dragChange ->
selectRect?.let { rect -> mapViewState.selectRect?.let { rect ->
val offset = dragChange.position val offset = dragChange.position
selectRect = Rect( mapViewState.selectRect = Rect(
min(offset.x, rect.left), min(offset.x, rect.left),
min(offset.y, rect.top), min(offset.y, rect.top),
max(offset.x, rect.right), max(offset.x, rect.right),
@ -125,33 +74,41 @@ public actual fun MapView(
) )
} }
} }
selectRect?.let { rect -> mapViewState.selectRect?.let { rect ->
//Use selection override if it is defined //Use selection override if it is defined
val gmcBox = GmcBox( val gmcBox = with(mapViewState) {
rect.topLeft.toDpOffset().toGeodetic(), GmcBox(
rect.bottomRight.toDpOffset().toGeodetic() rect.topLeft.toDpOffset().toGeodetic(),
) rect.bottomRight.toDpOffset().toGeodetic()
config.onSelect(gmcBox) )
if (config.zoomOnSelect) {
val newViewPoint = gmcBox.computeViewPoint(mapTileProvider).invoke(canvasSize)
config.onViewChange(newViewPoint)
viewPointInternal = newViewPoint
} }
selectRect = null mapViewState.config.onSelect(gmcBox)
if (mapViewState.config.zoomOnSelect) {
val newViewPoint = gmcBox.computeViewPoint(mapViewState.mapTileProvider)
.invoke(mapViewState.canvasSize)
mapViewState.config.onViewChange(newViewPoint)
mapViewState.viewPointInternal = newViewPoint
}
mapViewState.selectRect = null
} }
} else { } else {
val dragStart = change.position val dragStart = change.position
val dpPos = DpOffset(dragStart.x.toDp(), dragStart.y.toDp()) val dpPos = DpOffset(dragStart.x.toDp(), dragStart.y.toDp())
config.onClick(MapViewPoint(dpPos.toGeodetic(), viewPoint.zoom)) mapViewState.config.onClick(
MapViewPoint(
with(mapViewState) { dpPos.toGeodetic() },
mapViewState.viewPoint.zoom
)
)
drag(change.id) { dragChange -> drag(change.id) { dragChange ->
val dragAmount = dragChange.position - dragChange.previousPosition val dragAmount = dragChange.position - dragChange.previousPosition
val newViewPoint = viewPoint.move( val newViewPoint = mapViewState.viewPoint.move(
-dragAmount.x.toDp().value / tileScale, -dragAmount.x.toDp().value / mapViewState.tileScale,
+dragAmount.y.toDp().value / tileScale +dragAmount.y.toDp().value / mapViewState.tileScale
) )
config.onViewChange(newViewPoint) mapViewState.config.onViewChange(newViewPoint)
viewPointInternal = newViewPoint mapViewState.viewPointInternal = newViewPoint
} }
} }
} }
@ -162,37 +119,42 @@ public 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 invariant = DpOffset(xPos.toDp(), yPos.toDp()).toGeodetic() val invariant = with(mapViewState) { DpOffset(xPos.toDp(), yPos.toDp()).toGeodetic() }
val newViewPoint = viewPoint.zoom(-change.scrollDelta.y.toDouble() * config.zoomSpeed, invariant) val newViewPoint =
config.onViewChange(newViewPoint) mapViewState.viewPoint.zoom(-change.scrollDelta.y.toDouble() * mapViewState.config.zoomSpeed, invariant)
viewPointInternal = newViewPoint mapViewState.config.onViewChange(newViewPoint)
mapViewState.viewPointInternal = newViewPoint
}.fillMaxSize() }.fillMaxSize()
// Load tiles asynchronously // Load tiles asynchronously
LaunchedEffect(viewPoint, canvasSize) { LaunchedEffect(mapViewState.viewPoint, mapViewState.canvasSize) {
with(mapTileProvider) { with(mapViewState.mapTileProvider) {
val indexRange = 0 until 2.0.pow(zoom).toInt() val indexRange = 0 until 2.0.pow(mapViewState.zoom).toInt()
val left = centerCoordinates.x - canvasSize.width.value / 2 / tileScale val left =
val right = centerCoordinates.x + canvasSize.width.value / 2 / tileScale mapViewState.centerCoordinates.x - mapViewState.canvasSize.width.value / 2 / mapViewState.tileScale
val right =
mapViewState.centerCoordinates.x + mapViewState.canvasSize.width.value / 2 / mapViewState.tileScale
val horizontalIndices: IntRange = (toIndex(left)..toIndex(right)).intersect(indexRange) val horizontalIndices: IntRange = (toIndex(left)..toIndex(right)).intersect(indexRange)
val top = (centerCoordinates.y + canvasSize.height.value / 2 / tileScale) val top =
val bottom = (centerCoordinates.y - canvasSize.height.value / 2 / tileScale) (mapViewState.centerCoordinates.y + mapViewState.canvasSize.height.value / 2 / mapViewState.tileScale)
val bottom =
(mapViewState.centerCoordinates.y - mapViewState.canvasSize.height.value / 2 / mapViewState.tileScale)
val verticalIndices: IntRange = (toIndex(bottom)..toIndex(top)).intersect(indexRange) val verticalIndices: IntRange = (toIndex(bottom)..toIndex(top)).intersect(indexRange)
mapTiles.clear() mapViewState.mapTiles.clear()
for (j in verticalIndices) { for (j in verticalIndices) {
for (i in horizontalIndices) { for (i in horizontalIndices) {
val id = TileId(zoom, i, j) val id = TileId(mapViewState.zoom, i, j)
//start all //start all
val deferred = loadTileAsync(id) val deferred = loadTileAsync(id)
//wait asynchronously for it to finish //wait asynchronously for it to finish
launch { launch {
try { try {
mapTiles += deferred.await() mapViewState.mapTiles += deferred.await()
} catch (ex: Exception) { } catch (ex: Exception) {
if (ex !is CancellationException) { if (ex !is CancellationException) {
//displaying the error is maps responsibility //displaying the error is maps responsibility
@ -207,94 +169,20 @@ public actual fun MapView(
Canvas(canvasModifier) { Canvas(canvasModifier) {
fun WebMercatorCoordinates.toOffset(): Offset = Offset( if (mapViewState.canvasSize != size.toDpSize()) {
(canvasSize.width / 2 + (x.dp - centerCoordinates.x.dp) * tileScale.toFloat()).toPx(), mapViewState.canvasSize = size.toDpSize()
(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 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 MapRectangleFeature -> drawRect(
feature.color,
topLeft = feature.center.toOffset() - Offset(
feature.size.width.toPx() / 2,
feature.size.height.toPx() / 2
),
size = feature.size.toSize()
)
is MapLineFeature -> drawLine(feature.color, feature.a.toOffset(), feature.b.toOffset())
is MapArcFeature -> {
val topLeft = feature.oval.topLeft.toOffset()
val bottomRight = feature.oval.bottomRight.toOffset()
val path = Path().apply {
addArcRad(Rect(topLeft, bottomRight), feature.startAngle, feature.endAngle - feature.startAngle)
}
drawPath(path, color = feature.color, style = Stroke())
}
is MapBitmapImageFeature -> drawImage(feature.image, feature.position.toOffset())
is MapVectorImageFeature -> {
val offset = feature.position.toOffset()
val size = feature.size.toSize()
translate(offset.x - size.width / 2, offset.y - size.height / 2) {
with(feature.painter) {
draw(size)
}
}
}
is MapTextFeature -> drawIntoCanvas { canvas ->
val offset = feature.position.toOffset()
canvas.nativeCanvas.drawString(
feature.text,
offset.x + 5,
offset.y - 5,
Font().apply(feature.fontConfig),
feature.color.toPaint()
)
}
is MapDrawFeature -> {
val offset = feature.position.toOffset()
translate(offset.x, offset.y) {
feature.drawFeature(this)
}
}
is MapFeatureGroup -> {
feature.children.values.forEach {
drawFeature(zoom, it)
}
}
else -> {
logger.error { "Unrecognized feature type: ${feature::class}" }
}
}
}
if (canvasSize != size.toDpSize()) {
canvasSize = size.toDpSize()
logger.debug { "Recalculate canvas. Size: $size" } logger.debug { "Recalculate canvas. Size: $size" }
} }
clipRect { clipRect {
val tileSize = IntSize( val tileSize = IntSize(
ceil((mapTileProvider.tileSize.dp * tileScale.toFloat()).toPx()).toInt(), ceil((mapViewState.mapTileProvider.tileSize.dp * mapViewState.tileScale.toFloat()).toPx()).toInt(),
ceil((mapTileProvider.tileSize.dp * tileScale.toFloat()).toPx()).toInt() ceil((mapViewState.mapTileProvider.tileSize.dp * mapViewState.tileScale.toFloat()).toPx()).toInt()
) )
mapTiles.forEach { (id, image) -> mapViewState.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 + (mapTileProvider.toCoordinate(id.i).dp - centerCoordinates.x.dp) * tileScale.toFloat()).roundToPx(), (mapViewState.canvasSize.width / 2 + (mapViewState.mapTileProvider.toCoordinate(id.i).dp - mapViewState.centerCoordinates.x.dp) * mapViewState.tileScale.toFloat()).roundToPx(),
(canvasSize.height / 2 + (mapTileProvider.toCoordinate(id.j).dp - centerCoordinates.y.dp) * tileScale.toFloat()).roundToPx() (mapViewState.canvasSize.height / 2 + (mapViewState.mapTileProvider.toCoordinate(id.j).dp - mapViewState.centerCoordinates.y.dp) * mapViewState.tileScale.toFloat()).roundToPx()
) )
drawImage( drawImage(
image = image, image = image,
@ -302,11 +190,11 @@ public actual fun MapView(
dstSize = tileSize dstSize = tileSize
) )
} }
features.values.filter { zoom in it.zoomRange }.forEach { feature -> mapViewState.features.values.filter { mapViewState.zoom in it.zoomRange }.forEach { feature ->
drawFeature(zoom, feature) drawFeature(mapViewState.zoom, feature)
} }
} }
selectRect?.let { rect -> mapViewState.selectRect?.let { rect ->
drawRect( drawRect(
color = Color.Blue, color = Color.Blue,
topLeft = rect.topLeft, topLeft = rect.topLeft,