diff --git a/src/commonMain/kotlin/centre/sciprog/maps/compose/MapTileProvider.kt b/src/commonMain/kotlin/centre/sciprog/maps/compose/MapTileProvider.kt index 37e3ca3..6c91a99 100644 --- a/src/commonMain/kotlin/centre/sciprog/maps/compose/MapTileProvider.kt +++ b/src/commonMain/kotlin/centre/sciprog/maps/compose/MapTileProvider.kt @@ -2,7 +2,6 @@ package centre.sciprog.maps.compose import androidx.compose.ui.graphics.ImageBitmap import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred import kotlin.math.floor data class TileId( @@ -17,7 +16,7 @@ data class MapTile( ) interface MapTileProvider { - suspend fun loadTileAsync(id: TileId, scope: CoroutineScope): Deferred + suspend fun loadTileAsync(tileIds: List, scope: CoroutineScope, onTileLoad: (mapTile: MapTile) -> Unit) val tileSize: Int get() = DEFAULT_TILE_SIZE diff --git a/src/jvmMain/kotlin/centre/sciprog/maps/compose/MapViewJvm.kt b/src/jvmMain/kotlin/centre/sciprog/maps/compose/MapViewJvm.kt index 8e07084..1e254ca 100644 --- a/src/jvmMain/kotlin/centre/sciprog/maps/compose/MapViewJvm.kt +++ b/src/jvmMain/kotlin/centre/sciprog/maps/compose/MapViewJvm.kt @@ -17,7 +17,6 @@ import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.input.pointer.* import androidx.compose.ui.unit.* import centre.sciprog.maps.* -import kotlinx.coroutines.launch import mu.KotlinLogging import org.jetbrains.skia.Font import org.jetbrains.skia.Paint @@ -46,21 +45,23 @@ actual fun MapView( ) { var canvasSize by remember { mutableStateOf(DpSize(512.dp, 512.dp)) } - var viewPointOverride by remember { mutableStateOf( - 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) + var viewPointOverride by remember { + mutableStateOf( + 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) + } + } else { + null } - } else { - null - } - ) } + ) + } val viewPoint by derivedStateOf { viewPointOverride ?: computeViewPoint(canvasSize) } @@ -163,21 +164,17 @@ actual fun MapView( mapTiles.clear() - verticalIndices + val tileIds = verticalIndices .flatMap { j -> horizontalIndices .asSequence() .map { TileId(zoom, it, j) } } - .forEach { - try { - launch { - mapTiles += mapTileProvider.loadTileAsync(it, this).await() - } - } catch (ex: Exception) { - logger.error(ex) { "Failed to load tile $it" } - } - } + + mapTileProvider.loadTileAsync( + tileIds = tileIds, + scope = this + ) { mapTiles += it } } diff --git a/src/jvmMain/kotlin/centre/sciprog/maps/compose/OpenStreetMapTileProvider.kt b/src/jvmMain/kotlin/centre/sciprog/maps/compose/OpenStreetMapTileProvider.kt index 4590994..04daea8 100644 --- a/src/jvmMain/kotlin/centre/sciprog/maps/compose/OpenStreetMapTileProvider.kt +++ b/src/jvmMain/kotlin/centre/sciprog/maps/compose/OpenStreetMapTileProvider.kt @@ -6,10 +6,7 @@ import centre.sciprog.maps.LruCache import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.statement.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async +import kotlinx.coroutines.* import kotlinx.coroutines.sync.Semaphore import mu.KotlinLogging import org.jetbrains.skia.Image @@ -63,17 +60,31 @@ class OpenStreetMapTileProvider( Image.makeFromEncoded(byteArray).toComposeImageBitmap() } - override suspend fun loadTileAsync(id: TileId, scope: CoroutineScope) = scope.async { - semaphore.acquire() - try { - val image = cache.getOrPut(id) { downloadImageAsync(id) } - MapTile(id, image.await()) - } catch (e: Exception) { - cache.remove(id) - throw e - } finally { - semaphore.release() - } + override suspend fun loadTileAsync( + tileIds: List, + scope: CoroutineScope, + onTileLoad: (mapTile: MapTile) -> Unit, + ) { + tileIds + .forEach { id -> + try { + scope.launch { + semaphore.acquire() + try { + val image = cache.getOrPut(id) { downloadImageAsync(id) } + val result = MapTile(id, image.await()) + onTileLoad(result) + } catch (e: Exception) { + cache.remove(id) + throw e + } finally { + semaphore.release() + } + } + } catch (ex: Exception) { + logger.error(ex) { "Failed to load tile $id" } + } + } } companion object {