package refactoring

This commit is contained in:
Alexander Nozik 2022-07-23 13:49:47 +03:00
parent 8867388e85
commit 4c3aefcfae
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
13 changed files with 115 additions and 122 deletions

View File

@ -15,11 +15,12 @@ import center.sciprog.maps.coordinates.GmcBox
import center.sciprog.maps.coordinates.wrapAll
//TODO replace zoom range with zoom-based representation change
sealed class MapFeature(val zoomRange: IntRange) {
abstract fun getBoundingBox(zoom: Int): GmcBox?
public sealed interface MapFeature {
public val zoomRange: IntRange
public fun getBoundingBox(zoom: Int): GmcBox?
}
fun Iterable<MapFeature>.computeBoundingBox(zoom: Int): GmcBox? =
public fun Iterable<MapFeature>.computeBoundingBox(zoom: Int): GmcBox? =
mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
internal fun Pair<Double, Double>.toCoordinates() = GeodeticMapCoordinates.ofDegrees(first, second)
@ -29,68 +30,72 @@ 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) {
public class MapFeatureSelector(
public val selector: (zoom: Int) -> MapFeature,
) : MapFeature {
override val zoomRange: IntRange get() = defaultZoomRange
override fun getBoundingBox(zoom: Int): GmcBox? = selector(zoom).getBoundingBox(zoom)
}
class MapDrawFeature(
val position: GeodeticMapCoordinates,
zoomRange: IntRange = defaultZoomRange,
val drawFeature: DrawScope.() -> Unit,
) : MapFeature(zoomRange) {
public class MapDrawFeature(
public val position: GeodeticMapCoordinates,
override val zoomRange: IntRange = defaultZoomRange,
public val drawFeature: DrawScope.() -> Unit,
) : MapFeature{
override fun getBoundingBox(zoom: Int): GmcBox {
//TODO add box computation
return GmcBox(position, position)
}
}
class MapCircleFeature(
val center: GeodeticMapCoordinates,
zoomRange: IntRange = defaultZoomRange,
val size: Float = 5f,
val color: Color = Color.Red,
) : MapFeature(zoomRange) {
public class MapCircleFeature(
public val center: GeodeticMapCoordinates,
override val zoomRange: IntRange = defaultZoomRange,
public val size: Float = 5f,
public val color: Color = Color.Red,
) : MapFeature {
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) {
public class MapLineFeature(
public val a: GeodeticMapCoordinates,
public val b: GeodeticMapCoordinates,
override val zoomRange: IntRange = defaultZoomRange,
public val color: Color = Color.Red,
) : MapFeature {
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) {
public class MapTextFeature(
public val position: GeodeticMapCoordinates,
public val text: String,
override val zoomRange: IntRange = defaultZoomRange,
public val color: Color = Color.Red,
) : MapFeature {
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) {
public class MapBitmapImageFeature(
public val position: GeodeticMapCoordinates,
public val image: ImageBitmap,
public val size: IntSize = IntSize(15, 15),
override val zoomRange: IntRange = defaultZoomRange,
) : MapFeature{
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position)
}
class MapVectorImageFeature(
val position: GeodeticMapCoordinates,
val painter: Painter,
val size: DpSize,
zoomRange: IntRange = defaultZoomRange,
) : MapFeature(zoomRange) {
public class MapVectorImageFeature(
public val position: GeodeticMapCoordinates,
public val painter: Painter,
public val size: DpSize,
override val zoomRange: IntRange = defaultZoomRange,
) : MapFeature {
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position)
}
@Composable
fun MapVectorImageFeature(
public fun MapVectorImageFeature(
position: GeodeticMapCoordinates,
image: ImageVector,
size: DpSize = DpSize(20.dp, 20.dp),
@ -100,9 +105,9 @@ fun MapVectorImageFeature(
/**
* A group of other features
*/
class MapFeatureGroup(
val children: Map<FeatureId, MapFeature>,
zoomRange: IntRange = defaultZoomRange,
) : MapFeature(zoomRange) {
public class MapFeatureGroup(
public val children: Map<FeatureId, MapFeature>,
override val zoomRange: IntRange = defaultZoomRange,
) : MapFeature{
override fun getBoundingBox(zoom: Int): GmcBox? = children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
}

View File

@ -10,15 +10,15 @@ import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
typealias FeatureId = String
public typealias FeatureId = String
interface FeatureBuilder {
fun addFeature(id: FeatureId?, feature: MapFeature): FeatureId
public interface MapFeatureBuilder {
public fun addFeature(id: FeatureId?, feature: MapFeature): FeatureId
fun build(): SnapshotStateMap<FeatureId, MapFeature>
public fun build(): SnapshotStateMap<FeatureId, MapFeature>
}
internal class MapFeatureBuilder(initialFeatures: Map<FeatureId, MapFeature>) : FeatureBuilder {
internal class MapFeatureBuilderImpl(initialFeatures: Map<FeatureId, MapFeature>) : MapFeatureBuilder {
private val content: SnapshotStateMap<FeatureId, MapFeature> = mutableStateMapOf<FeatureId, MapFeature>().apply {
putAll(initialFeatures)
@ -35,72 +35,72 @@ internal class MapFeatureBuilder(initialFeatures: Map<FeatureId, MapFeature>) :
override fun build(): SnapshotStateMap<FeatureId, MapFeature> = content
}
fun FeatureBuilder.circle(
public fun MapFeatureBuilder.circle(
center: GeodeticMapCoordinates,
zoomRange: IntRange = defaultZoomRange,
size: Float = 5f,
color: Color = Color.Red,
id: FeatureId? = null,
) = addFeature(
): FeatureId = addFeature(
id, MapCircleFeature(center, zoomRange, size, color)
)
fun FeatureBuilder.circle(
public fun MapFeatureBuilder.circle(
centerCoordinates: Pair<Double, Double>,
zoomRange: IntRange = defaultZoomRange,
size: Float = 5f,
color: Color = Color.Red,
id: FeatureId? = null,
) = addFeature(
): FeatureId = addFeature(
id, MapCircleFeature(centerCoordinates.toCoordinates(), zoomRange, size, color)
)
fun FeatureBuilder.custom(
public fun MapFeatureBuilder.custom(
position: Pair<Double, Double>,
zoomRange: IntRange = defaultZoomRange,
id: FeatureId? = null,
drawFeature: DrawScope.() -> Unit,
) = addFeature(id, MapDrawFeature(position.toCoordinates(), zoomRange, drawFeature))
): FeatureId = addFeature(id, MapDrawFeature(position.toCoordinates(), zoomRange, drawFeature))
fun FeatureBuilder.line(
public fun MapFeatureBuilder.line(
aCoordinates: Pair<Double, Double>,
bCoordinates: Pair<Double, Double>,
zoomRange: IntRange = defaultZoomRange,
color: Color = Color.Red,
id: FeatureId? = null,
) = addFeature(id, MapLineFeature(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), zoomRange, color))
): FeatureId = addFeature(id, MapLineFeature(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), zoomRange, color))
fun FeatureBuilder.text(
public fun MapFeatureBuilder.text(
position: GeodeticMapCoordinates,
text: String,
zoomRange: IntRange = defaultZoomRange,
color: Color = Color.Red,
id: FeatureId? = null,
) = addFeature(id, MapTextFeature(position, text, zoomRange, color))
): FeatureId = addFeature(id, MapTextFeature(position, text, zoomRange, color))
fun FeatureBuilder.text(
public fun MapFeatureBuilder.text(
position: Pair<Double, Double>,
text: String,
zoomRange: IntRange = defaultZoomRange,
color: Color = Color.Red,
id: FeatureId? = null,
) = addFeature(id, MapTextFeature(position.toCoordinates(), text, zoomRange, color))
): FeatureId = addFeature(id, MapTextFeature(position.toCoordinates(), text, zoomRange, color))
@Composable
fun FeatureBuilder.image(
public fun MapFeatureBuilder.image(
position: Pair<Double, Double>,
image: ImageVector,
size: DpSize = DpSize(20.dp, 20.dp),
zoomRange: IntRange = defaultZoomRange,
id: FeatureId? = null,
) = addFeature(id, MapVectorImageFeature(position.toCoordinates(), image, size, zoomRange))
): FeatureId = addFeature(id, MapVectorImageFeature(position.toCoordinates(), image, size, zoomRange))
fun FeatureBuilder.group(
public fun MapFeatureBuilder.group(
zoomRange: IntRange = defaultZoomRange,
id: FeatureId? = null,
builder: FeatureBuilder.() -> Unit,
builder: MapFeatureBuilder.() -> Unit,
): FeatureId {
val map = MapFeatureBuilder(emptyMap()).apply(builder).build()
val map = MapFeatureBuilderImpl(emptyMap()).apply(builder).build()
val feature = MapFeatureGroup(map, zoomRange)
return addFeature(id, feature)
}

View File

@ -5,27 +5,27 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlin.math.floor
data class TileId(
public data class TileId(
val zoom: Int,
val i: Int,
val j: Int,
)
data class MapTile(
public data class MapTile(
val id: TileId,
val image: ImageBitmap,
)
interface MapTileProvider {
fun CoroutineScope.loadTileAsync(tileId: TileId): Deferred<MapTile>
public interface MapTileProvider {
public fun CoroutineScope.loadTileAsync(tileId: TileId): Deferred<MapTile>
val tileSize: Int get() = DEFAULT_TILE_SIZE
public val tileSize: Int get() = DEFAULT_TILE_SIZE
fun toIndex(d: Double): Int = floor(d / tileSize).toInt()
public fun toIndex(d: Double): Int = floor(d / tileSize).toInt()
fun toCoordinate(i: Int): Double = (i * tileSize).toDouble()
public fun toCoordinate(i: Int): Double = (i * tileSize).toDouble()
companion object {
const val DEFAULT_TILE_SIZE = 256
public companion object {
public const val DEFAULT_TILE_SIZE: Int = 256
}
}

View File

@ -11,7 +11,7 @@ import kotlin.math.min
//TODO consider replacing by modifier
data class MapViewConfig(
public data class MapViewConfig(
val zoomSpeed: Double = 1.0 / 3.0,
val inferViewBoxFromFeatures: Boolean = false,
val onClick: MapViewPoint.() -> Unit = {},
@ -21,7 +21,7 @@ data class MapViewConfig(
)
@Composable
expect fun MapView(
public expect fun MapView(
mapTileProvider: MapTileProvider,
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
features: Map<FeatureId, MapFeature>,
@ -30,15 +30,15 @@ expect fun MapView(
)
@Composable
fun MapView(
public fun MapView(
mapTileProvider: MapTileProvider,
initialViewPoint: MapViewPoint,
features: Map<FeatureId, MapFeature> = emptyMap(),
config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
buildFeatures: @Composable (FeatureBuilder.() -> Unit) = {},
buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
) {
val featuresBuilder = MapFeatureBuilder(features)
val featuresBuilder = MapFeatureBuilderImpl(features)
featuresBuilder.buildFeatures()
MapView(
mapTileProvider,
@ -66,9 +66,9 @@ public fun MapView(
features: Map<FeatureId, MapFeature> = emptyMap(),
config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
buildFeatures: @Composable (FeatureBuilder.() -> Unit) = {},
buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
) {
val featuresBuilder = MapFeatureBuilder(features)
val featuresBuilder = MapFeatureBuilderImpl(features)
featuresBuilder.buildFeatures()
MapView(
mapTileProvider,

View File

@ -33,7 +33,7 @@ private fun Color.toPaint(): Paint = Paint().apply {
private fun IntRange.intersect(other: IntRange) = max(first, other.first)..min(last, other.last)
internal fun MapViewPoint.move(deltaX: Double, deltaY: Double): MapViewPoint {
val newCoordinates = center.sciprog.maps.coordinates.GeodeticMapCoordinates.ofRadians(
val newCoordinates = GeodeticMapCoordinates.ofRadians(
(focus.latitude + deltaY / scaleFactor).coerceIn(
-MercatorProjection.MAXIMUM_LATITUDE,
MercatorProjection.MAXIMUM_LATITUDE
@ -50,7 +50,7 @@ private val logger = KotlinLogging.logger("MapView")
*/
@Composable
actual fun MapView(
public actual fun MapView(
mapTileProvider: MapTileProvider,
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
features: Map<FeatureId, MapFeature>,

View File

@ -1,11 +0,0 @@
package center.sciprog.maps.coordinates
/**
* A marker interface for flat coordinates
*/
public interface Coordinates2D
public interface CoordinateBox<T: Coordinates2D>{
public val a: T
public val b :T
}

View File

@ -8,7 +8,7 @@ import kotlin.math.PI
public class GeodeticMapCoordinates private constructor(
public val latitude: Double,
public val longitude: Double,
): Coordinates2D {
){
override fun equals(other: Any?): Boolean {
if (this === other) return true

View File

@ -5,9 +5,9 @@ import kotlin.math.max
import kotlin.math.min
public data class GmcBox(
public override val a: GeodeticMapCoordinates,
public override val b: GeodeticMapCoordinates,
) : CoordinateBox<GeodeticMapCoordinates>
public val a: GeodeticMapCoordinates,
public val b: GeodeticMapCoordinates,
)
public fun GmcBox(
latitudes: ClosedFloatingPointRange<Double>,

View File

@ -25,7 +25,6 @@ kotlin {
sourceSets {
commonMain {
dependencies {
api(projects.mapsKtCore)
api("io.github.microutils:kotlin-logging:2.1.23")
api(compose.foundation)
}

View File

@ -1,17 +1,15 @@
package center.sciprog.maps.scheme
import center.sciprog.maps.coordinates.CoordinateBox
import center.sciprog.maps.coordinates.Coordinates2D
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
data class SchemeCoordinates(val x: Float, val y: Float) : Coordinates2D
data class SchemeCoordinates(val x: Float, val y: Float)
data class SchemeCoordinateBox(
override val a: SchemeCoordinates,
override val b: SchemeCoordinates,
) : CoordinateBox<SchemeCoordinates>
val a: SchemeCoordinates,
val b: SchemeCoordinates,
)
val SchemeCoordinateBox.top get() = max(a.y, b.y)
val SchemeCoordinateBox.bottom get() = min(a.y, b.y)

View File

@ -14,13 +14,15 @@ import center.sciprog.maps.scheme.SchemeFeature.Companion.defaultScaleRange
typealias FeatureId = String
interface FeatureBuilder {
interface SchemeFeatureBuilder {
fun addFeature(id: FeatureId?, feature: SchemeFeature): FeatureId
fun build(): SnapshotStateMap<FeatureId, SchemeFeature>
}
internal class SchemeFeatureBuilder(initialFeatures: Map<FeatureId, SchemeFeature>) : FeatureBuilder {
internal class SchemeFeatureBuilderImpl(
initialFeatures: Map<FeatureId, SchemeFeature>,
) : SchemeFeatureBuilder {
private val content: SnapshotStateMap<FeatureId, SchemeFeature> =
mutableStateMapOf<FeatureId, SchemeFeature>().apply {
@ -38,7 +40,7 @@ internal class SchemeFeatureBuilder(initialFeatures: Map<FeatureId, SchemeFeatur
override fun build(): SnapshotStateMap<FeatureId, SchemeFeature> = content
}
fun FeatureBuilder.background(
fun SchemeFeatureBuilder.background(
painter: Painter,
box: SchemeCoordinateBox,
id: FeatureId? = null,
@ -47,7 +49,7 @@ fun FeatureBuilder.background(
SchemeBackgroundFeature(box, painter)
)
fun FeatureBuilder.background(
fun SchemeFeatureBuilder.background(
painter: Painter,
size: Size = painter.intrinsicSize,
offset: SchemeCoordinates = SchemeCoordinates(0f, 0f),
@ -60,7 +62,7 @@ fun FeatureBuilder.background(
return background(painter, box, id)
}
fun FeatureBuilder.circle(
fun SchemeFeatureBuilder.circle(
center: SchemeCoordinates,
scaleRange: FloatRange = defaultScaleRange,
size: Float = 5f,
@ -70,7 +72,7 @@ fun FeatureBuilder.circle(
id, SchemeCircleFeature(center, scaleRange, size, color)
)
fun FeatureBuilder.circle(
fun SchemeFeatureBuilder.circle(
centerCoordinates: Pair<Number, Number>,
scaleRange: FloatRange = defaultScaleRange,
size: Float = 5f,
@ -80,14 +82,14 @@ fun FeatureBuilder.circle(
id, SchemeCircleFeature(centerCoordinates.toCoordinates(), scaleRange, size, color)
)
fun FeatureBuilder.custom(
fun SchemeFeatureBuilder.custom(
position: Pair<Number, Number>,
scaleRange: FloatRange = defaultScaleRange,
id: FeatureId? = null,
drawFeature: DrawScope.() -> Unit,
) = addFeature(id, SchemeDrawFeature(position.toCoordinates(), scaleRange, drawFeature))
fun FeatureBuilder.line(
fun SchemeFeatureBuilder.line(
aCoordinates: Pair<Number, Number>,
bCoordinates: Pair<Number, Number>,
scaleRange: FloatRange = defaultScaleRange,
@ -95,7 +97,7 @@ fun FeatureBuilder.line(
id: FeatureId? = null,
) = addFeature(id, SchemeLineFeature(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), scaleRange, color))
fun FeatureBuilder.text(
fun SchemeFeatureBuilder.text(
position: SchemeCoordinates,
text: String,
scaleRange: FloatRange = defaultScaleRange,
@ -103,7 +105,7 @@ fun FeatureBuilder.text(
id: FeatureId? = null,
) = addFeature(id, SchemeTextFeature(position, text, scaleRange, color))
fun FeatureBuilder.text(
fun SchemeFeatureBuilder.text(
position: Pair<Number, Number>,
text: String,
scaleRange: FloatRange = defaultScaleRange,
@ -112,7 +114,7 @@ fun FeatureBuilder.text(
) = addFeature(id, SchemeTextFeature(position.toCoordinates(), text, scaleRange, color))
@Composable
fun FeatureBuilder.image(
fun SchemeFeatureBuilder.image(
position: Pair<Number, Number>,
image: ImageVector,
size: DpSize = DpSize(20.dp, 20.dp),
@ -120,12 +122,12 @@ fun FeatureBuilder.image(
id: FeatureId? = null,
) = addFeature(id, SchemeVectorImageFeature(position.toCoordinates(), image, size, scaleRange))
fun FeatureBuilder.group(
fun SchemeFeatureBuilder.group(
scaleRange: FloatRange = defaultScaleRange,
id: FeatureId? = null,
builder: FeatureBuilder.() -> Unit,
builder: SchemeFeatureBuilder.() -> Unit,
): FeatureId {
val map = SchemeFeatureBuilder(emptyMap()).apply(builder).build()
val map = SchemeFeatureBuilderImpl(emptyMap()).apply(builder).build()
val feature = SchemeFeatureGroup(map, scaleRange)
return addFeature(id, feature)
}

View File

@ -249,9 +249,9 @@ fun SchemeView(
features: Map<FeatureId, SchemeFeature> = emptyMap(),
config: SchemeViewConfig = SchemeViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
buildFeatures: @Composable (FeatureBuilder.() -> Unit) = {},
buildFeatures: @Composable (SchemeFeatureBuilder.() -> Unit) = {},
) {
val featuresBuilder = SchemeFeatureBuilder(features)
val featuresBuilder = SchemeFeatureBuilderImpl(features)
featuresBuilder.buildFeatures()
SchemeView(
{ initialViewPoint },