From 1541fb4f3959c1327a6f380bd35bdaa1651a3cc4 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 14 Jul 2022 10:36:16 +0300 Subject: [PATCH] [WIP] bounding boxes --- .../sciprog/maps/GeodeticMapCoordinates.kt | 2 ++ .../kotlin/centre/sciprog/maps/GmcBox.kt | 35 +++++++++++++++++++ .../centre/sciprog/maps/compose/MapFeature.kt | 33 +++++++++++++---- 3 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 src/commonMain/kotlin/centre/sciprog/maps/GmcBox.kt diff --git a/src/commonMain/kotlin/centre/sciprog/maps/GeodeticMapCoordinates.kt b/src/commonMain/kotlin/centre/sciprog/maps/GeodeticMapCoordinates.kt index 9f73bc2..d569e3e 100644 --- a/src/commonMain/kotlin/centre/sciprog/maps/GeodeticMapCoordinates.kt +++ b/src/commonMain/kotlin/centre/sciprog/maps/GeodeticMapCoordinates.kt @@ -43,6 +43,8 @@ public class GeodeticMapCoordinates private constructor(public val latitude: Dou } } +internal typealias Gmc = GeodeticMapCoordinates + //public interface GeoToScreenConversion { // public fun getScreenX(gmc: GeodeticMapCoordinates): Double diff --git a/src/commonMain/kotlin/centre/sciprog/maps/GmcBox.kt b/src/commonMain/kotlin/centre/sciprog/maps/GmcBox.kt new file mode 100644 index 0000000..501d600 --- /dev/null +++ b/src/commonMain/kotlin/centre/sciprog/maps/GmcBox.kt @@ -0,0 +1,35 @@ +package centre.sciprog.maps + +import kotlin.math.max +import kotlin.math.min + +class GmcBox(val a: GeodeticMapCoordinates, val b: GeodeticMapCoordinates) + +fun GmcBox(latitudes: ClosedFloatingPointRange, longitudes: ClosedFloatingPointRange) = GmcBox( + Gmc.ofRadians(latitudes.start, longitudes.start), + Gmc.ofRadians(latitudes.endInclusive, longitudes.endInclusive) +) + +val GmcBox.center + get() = GeodeticMapCoordinates.ofRadians( + (a.latitude + b.latitude) / 2, + (a.longitude + b.longitude) / 2 + ) + +val GmcBox.left get() = min(a.longitude, b.longitude) +val GmcBox.right get() = max(a.longitude, b.longitude) + +val GmcBox.top get() = max(a.latitude, b.latitude) +val GmcBox.bottom get() = min(a.latitude, b.latitude) + +/** + * Compute a minimal bounding box including all given boxes + */ +fun Iterable.wrapAll(): GmcBox { + //TODO optimize computation + val minLat = minOf { it.bottom } + val maxLat = maxOf { it.top } + val minLong = minOf { it.left } + val maxLong = maxOf { it.right } + return GmcBox(maxLat..maxLat, minLong..maxLong) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/centre/sciprog/maps/compose/MapFeature.kt b/src/commonMain/kotlin/centre/sciprog/maps/compose/MapFeature.kt index 3ec5b0f..1dcc333 100644 --- a/src/commonMain/kotlin/centre/sciprog/maps/compose/MapFeature.kt +++ b/src/commonMain/kotlin/centre/sciprog/maps/compose/MapFeature.kt @@ -9,9 +9,15 @@ import androidx.compose.ui.graphics.vector.VectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.unit.IntSize import centre.sciprog.maps.GeodeticMapCoordinates +import centre.sciprog.maps.GmcBox +import centre.sciprog.maps.wrapAll //TODO replace zoom range with zoom-based representation change -sealed class MapFeature(val zoomRange: IntRange) +sealed class MapFeature(val zoomRange: IntRange) { + abstract fun getBoundingBox(zoom: Int): GmcBox +} + +fun Iterable.computeBoundingBox(zoom: Int): GmcBox = map { it.getBoundingBox(zoom) }.wrapAll() internal fun Pair.toCoordinates() = GeodeticMapCoordinates.ofDegrees(first, second) @@ -20,35 +26,46 @@ internal val defaultZoomRange = 1..18 /** * A feature that decides what to show depending on the zoom value (it could change size of shape) */ -class MapFeatureSelector(val selector: (zoom: Int) -> MapFeature) : MapFeature(defaultZoomRange) +class MapFeatureSelector(val selector: (zoom: Int) -> MapFeature) : MapFeature(defaultZoomRange) { + override fun getBoundingBox(zoom: Int): GmcBox = selector(zoom).getBoundingBox(zoom) + +} class MapCircleFeature( val center: GeodeticMapCoordinates, zoomRange: IntRange = defaultZoomRange, val size: Float = 5f, val color: Color = Color.Red, -) : MapFeature(zoomRange) +) : MapFeature(zoomRange) { + override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(center, center) +} class MapLineFeature( val a: GeodeticMapCoordinates, val b: GeodeticMapCoordinates, zoomRange: IntRange = defaultZoomRange, val color: Color = Color.Red, -) : MapFeature(zoomRange) +) : MapFeature(zoomRange) { + override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(a, b) +} class MapTextFeature( val position: GeodeticMapCoordinates, val text: String, zoomRange: IntRange = defaultZoomRange, val color: Color = Color.Red, -) : MapFeature(zoomRange) +) : MapFeature(zoomRange) { + override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position) +} class MapBitmapImageFeature( val position: GeodeticMapCoordinates, val image: ImageBitmap, val size: IntSize = IntSize(15, 15), zoomRange: IntRange = defaultZoomRange, -) : MapFeature(zoomRange) +) : MapFeature(zoomRange) { + override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position) +} class MapVectorImageFeature internal constructor( @@ -56,7 +73,9 @@ class MapVectorImageFeature internal constructor( val painter: VectorPainter, val size: Size, zoomRange: IntRange = defaultZoomRange, -) : MapFeature(zoomRange) +) : MapFeature(zoomRange) { + override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position) +} @Composable fun MapVectorImageFeature(