diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeature.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeature.kt index 202e30b..f235db8 100644 --- a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeature.kt +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeature.kt @@ -132,4 +132,14 @@ public class MapFeatureGroup( override val zoomRange: IntRange = defaultZoomRange, ) : MapFeature { override fun getBoundingBox(zoom: Int): GmcBox? = children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapAll() -} \ No newline at end of file +} + +public class MapTextFeature( + public val position: GeodeticMapCoordinates, + public val text: String, + override val zoomRange: IntRange = defaultZoomRange, + public val color: Color, + public val fontConfig: MapTextFeatureFont.() -> Unit, +) : MapFeature { + override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position) +} diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeatureBuilder.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeatureBuilder.kt index e916f12..c11a0b6 100644 --- a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeatureBuilder.kt +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeatureBuilder.kt @@ -143,4 +143,22 @@ public fun MapFeatureBuilder.group( val map = MapFeatureBuilderImpl(emptyMap()).apply(builder).build() val feature = MapFeatureGroup(map, zoomRange) return addFeature(id, feature) -} \ No newline at end of file +} + +public fun MapFeatureBuilder.text( + position: GeodeticMapCoordinates, + text: String, + zoomRange: IntRange = defaultZoomRange, + color: Color = Color.Red, + font: MapTextFeatureFont.() -> Unit = { size = 16f }, + id: FeatureId? = null, +): FeatureId = addFeature(id, MapTextFeature(position, text, zoomRange, color, font)) + +public fun MapFeatureBuilder.text( + position: Pair, + text: String, + zoomRange: IntRange = defaultZoomRange, + color: Color = Color.Red, + font: MapTextFeatureFont.() -> Unit = { size = 16f }, + id: FeatureId? = null, +): FeatureId = addFeature(id, MapTextFeature(position.toCoordinates(), text, zoomRange, color, font)) diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapTextFeatureFont.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapTextFeatureFont.kt new file mode 100644 index 0000000..defe478 --- /dev/null +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapTextFeatureFont.kt @@ -0,0 +1,5 @@ +package center.sciprog.maps.compose + +public expect class MapTextFeatureFont { + public var size: Float +} \ No newline at end of file diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapTileProvider.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapTileProvider.kt index 6a71511..f205993 100644 --- a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapTileProvider.kt +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapTileProvider.kt @@ -13,7 +13,7 @@ public data class TileId( public data class MapTile( val id: TileId, - val image: ImageBitmap, + val image: ImageBitmap?, ) public interface MapTileProvider { diff --git a/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapTextFeature.kt b/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapTextFeature.kt deleted file mode 100644 index fef40fb..0000000 --- a/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapTextFeature.kt +++ /dev/null @@ -1,35 +0,0 @@ -package center.sciprog.maps.compose - -import androidx.compose.ui.graphics.Color -import center.sciprog.maps.coordinates.GeodeticMapCoordinates -import center.sciprog.maps.coordinates.GmcBox -import org.jetbrains.skia.Font - - -public class MapTextFeature( - public val position: GeodeticMapCoordinates, - public val text: String, - override val zoomRange: IntRange = defaultZoomRange, - public val color: Color, - public val fontConfig: Font.() -> Unit, -) : MapFeature { - override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position) -} - -public fun MapFeatureBuilder.text( - position: GeodeticMapCoordinates, - text: String, - zoomRange: IntRange = defaultZoomRange, - color: Color = Color.Red, - font: Font.() -> Unit = { size = 16f }, - id: FeatureId? = null, -): FeatureId = addFeature(id, MapTextFeature(position, text, zoomRange, color, font)) - -public fun MapFeatureBuilder.text( - position: Pair, - text: String, - zoomRange: IntRange = defaultZoomRange, - color: Color = Color.Red, - font: Font.() -> Unit = { size = 16f }, - id: FeatureId? = null, -): FeatureId = addFeature(id, MapTextFeature(position.toCoordinates(), text, zoomRange, color, font)) diff --git a/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapTextFeatureFontJvm.kt b/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapTextFeatureFontJvm.kt new file mode 100644 index 0000000..1a2023a --- /dev/null +++ b/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapTextFeatureFontJvm.kt @@ -0,0 +1,5 @@ +package center.sciprog.maps.compose + +import org.jetbrains.skia.Font + +public actual typealias MapTextFeatureFont = Font \ No newline at end of file diff --git a/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapViewJvm.kt b/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapViewJvm.kt index 27f9496..f080b3b 100644 --- a/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapViewJvm.kt +++ b/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapViewJvm.kt @@ -313,11 +313,13 @@ public actual fun MapView( (canvasSize.width / 2 + (mapTileProvider.toCoordinate(id.i).dp - centerCoordinates.x.dp) * tileScale.toFloat()).roundToPx(), (canvasSize.height / 2 + (mapTileProvider.toCoordinate(id.j).dp - centerCoordinates.y.dp) * tileScale.toFloat()).roundToPx() ) - drawImage( - image = image, - dstOffset = offset, - dstSize = tileSize - ) + image?.let { + drawImage( + image = it, + dstOffset = offset, + dstSize = tileSize + ) + } } features.values.filter { zoom in it.zoomRange }.forEach { feature -> drawFeature(zoom, feature) diff --git a/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/OpenStreetMapTileProvider.kt b/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/OpenStreetMapTileProvider.kt index 4929254..f350279 100644 --- a/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/OpenStreetMapTileProvider.kt +++ b/maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/OpenStreetMapTileProvider.kt @@ -2,10 +2,11 @@ package center.sciprog.maps.compose import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.toComposeImageBitmap -import io.ktor.client.HttpClient -import io.ktor.client.request.get -import io.ktor.client.statement.readBytes -import io.ktor.utils.io.CancellationException +import io.ktor.client.* +import io.ktor.client.network.sockets.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.utils.io.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers @@ -28,7 +29,7 @@ public class OpenStreetMapTileProvider( cacheCapacity: Int = 200, ) : MapTileProvider { private val semaphore = Semaphore(parallelism) - private val cache = LruCache>(cacheCapacity) + private val cache = LruCache>(cacheCapacity) private fun TileId.osmUrl() = URL("https://tile.openstreetmap.org/${zoom}/${i}/${j}.png") @@ -37,7 +38,7 @@ public class OpenStreetMapTileProvider( /** * Download and cache the tile image */ - private fun CoroutineScope.downloadImageAsync(id: TileId) = async(Dispatchers.IO) { + private fun CoroutineScope.downloadImageAsync(id: TileId): Deferred = async(Dispatchers.IO) { id.cacheFilePath()?.let { path -> if (path.exists()) { @@ -52,19 +53,22 @@ public class OpenStreetMapTileProvider( //semaphore works only for actual download semaphore.withPermit { - val url = id.osmUrl() - val byteArray = client.get(url).readBytes() + try { + val url = id.osmUrl() + val byteArray = client.get(url).readBytes() + logger.debug { "Finished downloading map tile with id $id from $url" } + id.cacheFilePath()?.let { path -> + logger.debug { "Caching map tile $id to $path" } - logger.debug { "Finished downloading map tile with id $id from $url" } + path.parent.createDirectories() + path.writeBytes(byteArray) + } - id.cacheFilePath()?.let { path -> - logger.debug { "Caching map tile $id to $path" } - - path.parent.createDirectories() - path.writeBytes(byteArray) + Image.makeFromEncoded(byteArray).toComposeImageBitmap() + } catch (e: ConnectTimeoutException) { + logger.error(e) { e.localizedMessage } + null } - - Image.makeFromEncoded(byteArray).toComposeImageBitmap() } } @@ -83,7 +87,7 @@ public class OpenStreetMapTileProvider( imageDeferred.await() } catch (ex: Exception) { cache.remove(tileId) - if(ex !is CancellationException) { + if (ex !is CancellationException) { logger.error(ex) { "Failed to load tile image with id=$tileId" } } throw ex @@ -96,4 +100,4 @@ public class OpenStreetMapTileProvider( public companion object { private val logger = KotlinLogging.logger("OpenStreetMapCache") } -} +} \ No newline at end of file