Connect timeout exception handling #16
@ -133,3 +133,13 @@ public class MapFeatureGroup(
|
|||||||
) : MapFeature {
|
) : MapFeature {
|
||||||
override fun getBoundingBox(zoom: Int): GmcBox? = children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
|
override fun getBoundingBox(zoom: Int): GmcBox? = children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
@ -144,3 +144,21 @@ public fun MapFeatureBuilder.group(
|
|||||||
val feature = MapFeatureGroup(map, zoomRange)
|
val feature = MapFeatureGroup(map, zoomRange)
|
||||||
return addFeature(id, feature)
|
return addFeature(id, feature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<Double, Double>,
|
||||||
|
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))
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package center.sciprog.maps.compose
|
||||||
|
|
||||||
|
public expect class MapTextFeatureFont {
|
||||||
|
public var size: Float
|
||||||
|
}
|
@ -13,7 +13,7 @@ public data class TileId(
|
|||||||
|
|
||||||
public data class MapTile(
|
public data class MapTile(
|
||||||
val id: TileId,
|
val id: TileId,
|
||||||
val image: ImageBitmap,
|
val image: ImageBitmap?,
|
||||||
)
|
)
|
||||||
|
|
||||||
public interface MapTileProvider {
|
public interface MapTileProvider {
|
||||||
|
@ -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<Double, Double>,
|
|
||||||
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))
|
|
@ -0,0 +1,5 @@
|
|||||||
|
package center.sciprog.maps.compose
|
||||||
|
|
||||||
|
import org.jetbrains.skia.Font
|
||||||
|
|
||||||
|
public actual typealias MapTextFeatureFont = Font
|
@ -313,12 +313,14 @@ public actual fun MapView(
|
|||||||
(canvasSize.width / 2 + (mapTileProvider.toCoordinate(id.i).dp - centerCoordinates.x.dp) * tileScale.toFloat()).roundToPx(),
|
(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()
|
(canvasSize.height / 2 + (mapTileProvider.toCoordinate(id.j).dp - centerCoordinates.y.dp) * tileScale.toFloat()).roundToPx()
|
||||||
)
|
)
|
||||||
|
image?.let {
|
||||||
drawImage(
|
drawImage(
|
||||||
image = image,
|
image = it,
|
||||||
dstOffset = offset,
|
dstOffset = offset,
|
||||||
dstSize = tileSize
|
dstSize = tileSize
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
features.values.filter { zoom in it.zoomRange }.forEach { feature ->
|
features.values.filter { zoom in it.zoomRange }.forEach { feature ->
|
||||||
drawFeature(zoom, feature)
|
drawFeature(zoom, feature)
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,11 @@ package center.sciprog.maps.compose
|
|||||||
|
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
import androidx.compose.ui.graphics.toComposeImageBitmap
|
import androidx.compose.ui.graphics.toComposeImageBitmap
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.*
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.network.sockets.*
|
||||||
import io.ktor.client.statement.readBytes
|
import io.ktor.client.request.*
|
||||||
import io.ktor.utils.io.CancellationException
|
import io.ktor.client.statement.*
|
||||||
|
import io.ktor.utils.io.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.Deferred
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -28,7 +29,7 @@ public class OpenStreetMapTileProvider(
|
|||||||
cacheCapacity: Int = 200,
|
cacheCapacity: Int = 200,
|
||||||
) : MapTileProvider {
|
) : MapTileProvider {
|
||||||
private val semaphore = Semaphore(parallelism)
|
private val semaphore = Semaphore(parallelism)
|
||||||
private val cache = LruCache<TileId, Deferred<ImageBitmap>>(cacheCapacity)
|
private val cache = LruCache<TileId, Deferred<ImageBitmap?>>(cacheCapacity)
|
||||||
|
|
||||||
private fun TileId.osmUrl() = URL("https://tile.openstreetmap.org/${zoom}/${i}/${j}.png")
|
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
|
* Download and cache the tile image
|
||||||
*/
|
*/
|
||||||
private fun CoroutineScope.downloadImageAsync(id: TileId) = async(Dispatchers.IO) {
|
private fun CoroutineScope.downloadImageAsync(id: TileId): Deferred<ImageBitmap?> = async(Dispatchers.IO) {
|
||||||
|
|
||||||
id.cacheFilePath()?.let { path ->
|
id.cacheFilePath()?.let { path ->
|
||||||
if (path.exists()) {
|
if (path.exists()) {
|
||||||
@ -52,11 +53,10 @@ public class OpenStreetMapTileProvider(
|
|||||||
|
|
||||||
//semaphore works only for actual download
|
//semaphore works only for actual download
|
||||||
semaphore.withPermit {
|
semaphore.withPermit {
|
||||||
|
try {
|
||||||
val url = id.osmUrl()
|
val url = id.osmUrl()
|
||||||
val byteArray = client.get(url).readBytes()
|
val byteArray = client.get(url).readBytes()
|
||||||
|
|
||||||
logger.debug { "Finished downloading map tile with id $id from $url" }
|
logger.debug { "Finished downloading map tile with id $id from $url" }
|
||||||
|
|
||||||
id.cacheFilePath()?.let { path ->
|
id.cacheFilePath()?.let { path ->
|
||||||
logger.debug { "Caching map tile $id to $path" }
|
logger.debug { "Caching map tile $id to $path" }
|
||||||
|
|
||||||
@ -65,6 +65,10 @@ public class OpenStreetMapTileProvider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Image.makeFromEncoded(byteArray).toComposeImageBitmap()
|
Image.makeFromEncoded(byteArray).toComposeImageBitmap()
|
||||||
|
} catch (e: ConnectTimeoutException) {
|
||||||
|
logger.error(e) { e.localizedMessage }
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +87,7 @@ public class OpenStreetMapTileProvider(
|
|||||||
imageDeferred.await()
|
imageDeferred.await()
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
cache.remove(tileId)
|
cache.remove(tileId)
|
||||||
if(ex !is CancellationException) {
|
if (ex !is CancellationException) {
|
||||||
logger.error(ex) { "Failed to load tile image with id=$tileId" }
|
logger.error(ex) { "Failed to load tile image with id=$tileId" }
|
||||||
}
|
}
|
||||||
throw ex
|
throw ex
|
||||||
|
Loading…
Reference in New Issue
Block a user