Connect timeout exception handling #16

Merged
ArystanK merged 3 commits from connect_timeout_exception_handling into main 2022-08-26 14:23:51 +03:00
8 changed files with 70 additions and 61 deletions
Showing only changes of commit 4b512d0967 - Show all commits

View File

@ -132,4 +132,14 @@ public class MapFeatureGroup(
override val zoomRange: IntRange = defaultZoomRange, override val zoomRange: IntRange = defaultZoomRange,
) : 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)
}

View File

@ -143,4 +143,22 @@ public fun MapFeatureBuilder.group(
val map = MapFeatureBuilderImpl(emptyMap()).apply(builder).build() val map = MapFeatureBuilderImpl(emptyMap()).apply(builder).build()
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))

View File

@ -0,0 +1,5 @@
package center.sciprog.maps.compose
public expect class MapTextFeatureFont {
public var size: Float
}

View File

@ -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 {

View File

@ -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))

View File

@ -0,0 +1,5 @@
package center.sciprog.maps.compose
import org.jetbrains.skia.Font
public actual typealias MapTextFeatureFont = Font

View File

@ -313,11 +313,13 @@ 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()
) )
drawImage( image?.let {
image = image, drawImage(
dstOffset = offset, image = it,
dstSize = tileSize dstOffset = offset,
) 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)

View File

@ -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,19 +53,22 @@ public class OpenStreetMapTileProvider(
//semaphore works only for actual download //semaphore works only for actual download
semaphore.withPermit { semaphore.withPermit {
val url = id.osmUrl() try {
val byteArray = client.get(url).readBytes() 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 -> Image.makeFromEncoded(byteArray).toComposeImageBitmap()
logger.debug { "Caching map tile $id to $path" } } catch (e: ConnectTimeoutException) {
logger.error(e) { e.localizedMessage }
path.parent.createDirectories() null
path.writeBytes(byteArray)
} }
Image.makeFromEncoded(byteArray).toComposeImageBitmap()
} }
} }
@ -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
@ -96,4 +100,4 @@ public class OpenStreetMapTileProvider(
public companion object { public companion object {
private val logger = KotlinLogging.logger("OpenStreetMapCache") private val logger = KotlinLogging.logger("OpenStreetMapCache")
} }
} }