Fix for imposible tile index crush.
This commit is contained in:
parent
005ef17f8e
commit
6868c1a5ca
@ -14,8 +14,6 @@ data class MapTile(
|
||||
val image: ImageBitmap,
|
||||
)
|
||||
|
||||
|
||||
|
||||
interface MapTileProvider {
|
||||
suspend fun loadTile(id: TileId): MapTile
|
||||
fun toIndex(d: Double): Int = floor(d / DEFAULT_TILE_SIZE).toInt()
|
||||
|
@ -13,32 +13,58 @@ import centre.sciprog.maps.MapViewPoint
|
||||
import centre.sciprog.maps.compose.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.cio.CIO
|
||||
import kotlinx.coroutines.delay
|
||||
import java.nio.file.Path
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* initial set of features
|
||||
*/
|
||||
@Composable
|
||||
private fun initialFeatures() = buildList {
|
||||
val pointOne = 55.568548 to 37.568604
|
||||
val pointTwo = 55.929444 to 37.518434
|
||||
add(MapVectorImageFeature(pointOne.toCoordinates(), Icons.Filled.Home))
|
||||
// add(MapCircleFeature(pointOne))
|
||||
add(MapCircleFeature(pointTwo))
|
||||
add(MapLineFeature(pointOne, pointTwo))
|
||||
add(MapTextFeature(pointOne.toCoordinates(), "Home"))
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun App() {
|
||||
MaterialTheme {
|
||||
val viewPoint = MapViewPoint(
|
||||
//create a view point
|
||||
val viewPoint = remember {
|
||||
MapViewPoint(
|
||||
GeodeticMapCoordinates.ofDegrees(55.7558, 37.6173),
|
||||
6.0
|
||||
)
|
||||
val pointOne = 55.568548 to 37.568604
|
||||
val pointTwo = 55.929444 to 37.518434
|
||||
val features = buildList<MapFeature> {
|
||||
// add(MapCircleFeature(pointOne))
|
||||
add(MapCircleFeature(pointTwo))
|
||||
add(MapLineFeature(pointOne, pointTwo))
|
||||
add(MapTextFeature(pointOne.toCoordinates(), "Home"))
|
||||
add(MapVectorImageFeature(pointOne.toCoordinates(), Icons.Filled.Home))
|
||||
}
|
||||
|
||||
// observable list of features
|
||||
val features = mutableStateListOf<MapFeature>().apply {
|
||||
addAll(initialFeatures())
|
||||
}
|
||||
|
||||
// // test dynamic rendering
|
||||
// LaunchedEffect(features) {
|
||||
// repeat(10000) {
|
||||
// delay(10)
|
||||
// val randomPoint = Random.nextDouble(55.568548, 55.929444) to Random.nextDouble(37.518434, 37.568604)
|
||||
// features.add(MapCircleFeature(randomPoint))
|
||||
// }
|
||||
// }
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val mapTileProvider = remember { OpenStreetMapTileProvider(scope, HttpClient(CIO), Path.of("mapCache")) }
|
||||
|
||||
var coordinates by remember { mutableStateOf<GeodeticMapCoordinates?>(null) }
|
||||
|
||||
Column {
|
||||
//display click coordinates
|
||||
Text(coordinates?.toString() ?: "")
|
||||
MapView(viewPoint, mapTileProvider, features = features) {
|
||||
coordinates = it
|
||||
|
@ -36,7 +36,6 @@ private val logger = KotlinLogging.logger("MapView")
|
||||
/**
|
||||
* A component that renders map and provides basic map manipulation capabilities
|
||||
*/
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
actual fun MapView(
|
||||
initialViewPoint: MapViewPoint,
|
||||
@ -58,8 +57,6 @@ actual fun MapView(
|
||||
|
||||
// Load tiles asynchronously
|
||||
LaunchedEffect(viewPoint, canvasSize) {
|
||||
//remember zoom state to avoid races
|
||||
val z = zoom
|
||||
val left = centerCoordinates.x - canvasSize.width / 2
|
||||
val right = centerCoordinates.x + canvasSize.width / 2
|
||||
val horizontalIndices = mapTileProvider.toIndex(left)..mapTileProvider.toIndex(right)
|
||||
@ -70,14 +67,18 @@ actual fun MapView(
|
||||
|
||||
mapTiles.clear()
|
||||
|
||||
val indexRange = 0 until 2.0.pow(z).toInt()
|
||||
val indexRange = 0 until 2.0.pow(zoom).toInt()
|
||||
|
||||
for (j in verticalIndices) {
|
||||
for (i in horizontalIndices) {
|
||||
if (z == zoom && i in indexRange && j in indexRange) {
|
||||
val tileId = TileId(z, i, j)
|
||||
if (i in indexRange && j in indexRange) {
|
||||
val tileId = TileId(zoom, i, j)
|
||||
try {
|
||||
val tile = mapTileProvider.loadTile(tileId)
|
||||
mapTiles.add(tile)
|
||||
} catch (ex: Exception) {
|
||||
logger.error(ex) { "Failed to load tile $tileId" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -99,6 +100,7 @@ actual fun MapView(
|
||||
|
||||
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) {
|
||||
|
@ -5,15 +5,13 @@ import androidx.compose.ui.graphics.toComposeImageBitmap
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.statement.readBytes
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.*
|
||||
import mu.KotlinLogging
|
||||
import org.jetbrains.skia.Image
|
||||
import java.net.URL
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
import kotlin.math.pow
|
||||
|
||||
/**
|
||||
* A [MapTileProvider] based on Open Street Map API. With in-memory and file cache
|
||||
@ -25,6 +23,9 @@ public class OpenStreetMapTileProvider(private val scope: CoroutineScope, privat
|
||||
|
||||
private fun TileId.cacheFilePath() = cacheDirectory.resolve("${zoom}/${i}/${j}.png")
|
||||
|
||||
/**
|
||||
* Download and cache the tile image
|
||||
*/
|
||||
private fun downloadImageAsync(id: TileId) = scope.async(Dispatchers.IO) {
|
||||
id.cacheFilePath()?.let { path ->
|
||||
if (path.exists()) {
|
||||
@ -53,6 +54,11 @@ public class OpenStreetMapTileProvider(private val scope: CoroutineScope, privat
|
||||
}
|
||||
|
||||
override suspend fun loadTile(id: TileId): MapTile {
|
||||
val indexRange = indexRange(id.zoom)
|
||||
if(id.i !in indexRange || id.j !in indexRange){
|
||||
error("Indices (${id.i}, ${id.j}) are not in index range $indexRange for zoom ${id.zoom}")
|
||||
}
|
||||
|
||||
val image = cache.getOrPut(id) {
|
||||
downloadImageAsync(id)
|
||||
}.await()
|
||||
@ -62,5 +68,6 @@ public class OpenStreetMapTileProvider(private val scope: CoroutineScope, privat
|
||||
|
||||
companion object{
|
||||
private val logger = KotlinLogging.logger("OpenStreetMapCache")
|
||||
private fun indexRange(zoom: Int): IntRange = 0 until 2.0.pow(zoom).toInt()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user