diff --git a/demo/maps/src/jvmMain/kotlin/Main.kt b/demo/maps/src/jvmMain/kotlin/Main.kt index ff157a5..d1a72ee 100644 --- a/demo/maps/src/jvmMain/kotlin/Main.kt +++ b/demo/maps/src/jvmMain/kotlin/Main.kt @@ -22,6 +22,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import java.net.URL import java.nio.file.Path import kotlin.math.PI @@ -64,13 +66,13 @@ fun App() { image(pointOne, Icons.Filled.Home) - val marker1 = rectangle(55.744 to 38.614, size = DpSize(10.dp, 10.dp), color = Color.Magenta) - val marker2 = rectangle(55.8 to 38.5, size = DpSize(10.dp, 10.dp), color = Color.Magenta) - val marker3 = rectangle(56.0 to 38.5, size = DpSize(10.dp, 10.dp), color = Color.Magenta) + val marker1 = rectangle(55.744 to 38.614, size = DpSize(10.dp, 10.dp)).color(Color.Magenta) + val marker2 = rectangle(55.8 to 38.5, size = DpSize(10.dp, 10.dp)).color(Color.Magenta) + val marker3 = rectangle(56.0 to 38.5, size = DpSize(10.dp, 10.dp)).color(Color.Magenta) - draggableLine(marker1, marker2, color = Color.Blue) - draggableLine(marker2, marker3, color = Color.Blue) - draggableLine(marker3, marker1, color = Color.Blue) + draggableLine(marker1, marker2).color(Color.Blue) + draggableLine(marker2, marker3).color(Color.Blue) + draggableLine(marker3, marker1).color(Color.Blue) points( points = listOf( @@ -85,14 +87,17 @@ fun App() { ) //remember feature ID - circle( + val circleId = circle( centerCoordinates = pointTwo, - ).updated(scope) { - delay(200) - //Overwrite a feature with new color - it.copy(color = Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat())) + ) + scope.launch { + while (isActive) { + delay(200) + circleId.color(Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat())) + } } + // draw(position = pointThree) { // drawLine(start = Offset(-10f, -10f), end = Offset(10f, 10f), color = Color.Red) // drawLine(start = Offset(-10f, 10f), end = Offset(10f, -10f), color = Color.Red) @@ -105,16 +110,20 @@ fun App() { centerCoordinates.filterNotNull().onEach { group(id = "center") { - circle(center = it, color = Color.Blue, id = "circle", size = 1.dp) - text(position = it, it.toShortString(), id = "text", color = Color.Blue) + circle(center = it, id = "circle", size = 1.dp).color(Color.Blue) + text(position = it, it.toShortString(), id = "text").color(Color.Blue) } }.launchIn(scope) - features.forEach { (id, feature) -> + visit { id, feature -> if (feature is PolygonFeature) { (id as FeatureId>).onHover { println("Hover on $id") - points(feature.points, color = Color.Blue, id = "selected", attributes = Attributes(ZAttribute, 10f)) + points( + feature.points, + id = "selected", + attributes = Attributes(ZAttribute, 10f) + ).color(Color.Blue) } } } diff --git a/demo/scheme/src/jvmMain/kotlin/Main.kt b/demo/scheme/src/jvmMain/kotlin/Main.kt index 8aa286d..45c7360 100644 --- a/demo/scheme/src/jvmMain/kotlin/Main.kt +++ b/demo/scheme/src/jvmMain/kotlin/Main.kt @@ -8,7 +8,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.window.Window import androidx.compose.ui.window.application -import center.sciprog.maps.features.FeatureCollection +import center.sciprog.maps.features.FeatureGroup import center.sciprog.maps.features.ViewConfig import center.sciprog.maps.features.ViewPoint import center.sciprog.maps.features.computeBoundingBox @@ -29,7 +29,7 @@ fun App() { MaterialTheme { val scope = rememberCoroutineScope() - val schemeFeaturesState = FeatureCollection.remember(XYCoordinateSpace) { + val schemeFeaturesState = FeatureGroup.remember(XYCoordinateSpace) { background(1600f, 1200f) { painterResource("middle-earth.jpg") } circle(410.52737 to 868.7676, color = Color.Blue) text(410.52737 to 868.7676, "Shire", color = Color.Blue) @@ -53,7 +53,7 @@ fun App() { } val initialViewPoint: ViewPoint = remember { - schemeFeaturesState.features.values.computeBoundingBox(XYCoordinateSpace, 1f)?.computeViewPoint() + schemeFeaturesState.features.computeBoundingBox(XYCoordinateSpace, 1f)?.computeViewPoint() ?: XYViewPoint(XY(0f, 0f)) } diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapView.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapView.kt index f1a4608..a8ad123 100644 --- a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapView.kt +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapView.kt @@ -11,7 +11,7 @@ import center.sciprog.maps.features.* @Composable public expect fun MapView( mapState: MapViewScope, - featuresState: FeatureCollection, + featuresState: FeatureGroup, modifier: Modifier = Modifier.fillMaxSize(), ) @@ -29,7 +29,7 @@ public fun MapView( ) { val featureState = remember(featureMap) { - FeatureCollection.build(WebMercatorSpace) { + FeatureGroup.build(WebMercatorSpace) { featureMap.forEach { feature(it.key.id, it.value) } } } @@ -38,7 +38,7 @@ public fun MapView( mapTileProvider, config, initialViewPoint = initialViewPoint, - initialRectangle = initialRectangle ?: featureState.features.values.computeBoundingBox(WebMercatorSpace, Float.MAX_VALUE), + initialRectangle = initialRectangle ?: featureState.features.computeBoundingBox(WebMercatorSpace, Float.MAX_VALUE), ) MapView(mapState, featureState, modifier) @@ -58,16 +58,16 @@ public fun MapView( initialRectangle: Rectangle? = null, config: ViewConfig = ViewConfig(), modifier: Modifier = Modifier.fillMaxSize(), - buildFeatures: FeatureCollection.() -> Unit = {}, + buildFeatures: FeatureGroup.() -> Unit = {}, ) { - val featureState = FeatureCollection.remember(WebMercatorSpace, buildFeatures) + val featureState = FeatureGroup.remember(WebMercatorSpace, buildFeatures) val mapState: MapViewScope = rememberMapState( mapTileProvider, config, initialViewPoint = initialViewPoint, - initialRectangle = initialRectangle ?: featureState.features.values.computeBoundingBox(WebMercatorSpace, Float.MAX_VALUE), + initialRectangle = initialRectangle ?: featureState.features.computeBoundingBox(WebMercatorSpace, Float.MAX_VALUE), ) MapView(mapState, featureState, modifier) diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/mapFeatures.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/mapFeatures.kt index 80fe0eb..36e0986 100644 --- a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/mapFeatures.kt +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/mapFeatures.kt @@ -1,6 +1,5 @@ package center.sciprog.maps.compose -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PointMode import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.vector.ImageVector @@ -11,73 +10,62 @@ import center.sciprog.maps.coordinates.* import center.sciprog.maps.features.* -internal fun FeatureBuilder.coordinatesOf(pair: Pair) = +internal fun FeatureGroup.coordinatesOf(pair: Pair) = GeodeticMapCoordinates.ofDegrees(pair.first.toDouble(), pair.second.toDouble()) public typealias MapFeature = Feature -public fun FeatureBuilder.circle( +public fun FeatureGroup.circle( centerCoordinates: Pair, - zoomRange: FloatRange = defaultZoomRange, size: Dp = 5.dp, - color: Color = defaultColor, id: String? = null, ): FeatureId> = feature( - id, CircleFeature(space, coordinatesOf(centerCoordinates), zoomRange, size, color) + id, CircleFeature(space, coordinatesOf(centerCoordinates), size) ) -public fun FeatureBuilder.rectangle( +public fun FeatureGroup.rectangle( centerCoordinates: Pair, - zoomRange: FloatRange = defaultZoomRange, size: DpSize = DpSize(5.dp, 5.dp), - color: Color = defaultColor, id: String? = null, ): FeatureId> = feature( - id, RectangleFeature(space, coordinatesOf(centerCoordinates), zoomRange, size, color) + id, RectangleFeature(space, coordinatesOf(centerCoordinates), size) ) -public fun FeatureBuilder.draw( +public fun FeatureGroup.draw( position: Pair, - zoomRange: FloatRange = defaultZoomRange, id: String? = null, draw: DrawScope.() -> Unit, ): FeatureId> = feature( id, - DrawFeature(space, coordinatesOf(position), zoomRange, drawFeature = draw) + DrawFeature(space, coordinatesOf(position), drawFeature = draw) ) -public fun FeatureBuilder.line( +public fun FeatureGroup.line( curve: GmcCurve, - zoomRange: FloatRange = defaultZoomRange, - color: Color = defaultColor, id: String? = null, ): FeatureId> = feature( id, - LineFeature(space, curve.forward.coordinates, curve.backward.coordinates, zoomRange, color) + LineFeature(space, curve.forward.coordinates, curve.backward.coordinates) ) -public fun FeatureBuilder.line( +public fun FeatureGroup.line( aCoordinates: Pair, bCoordinates: Pair, - zoomRange: FloatRange = defaultZoomRange, - color: Color = defaultColor, id: String? = null, ): FeatureId> = feature( id, - LineFeature(space, coordinatesOf(aCoordinates), coordinatesOf(bCoordinates), zoomRange, color) + LineFeature(space, coordinatesOf(aCoordinates), coordinatesOf(bCoordinates)) ) -public fun FeatureBuilder.arc( +public fun FeatureGroup.arc( center: Pair, radius: Distance, startAngle: Angle, arcLength: Angle, - zoomRange: FloatRange = defaultZoomRange, - color: Color = defaultColor, id: String? = null, ): FeatureId> = feature( id, @@ -86,26 +74,21 @@ public fun FeatureBuilder.arc( oval = space.Rectangle(coordinatesOf(center), radius, radius), startAngle = startAngle.radians.toFloat(), arcLength = arcLength.radians.toFloat(), - zoomRange = zoomRange, - color = color ) ) -public fun FeatureBuilder.points( +public fun FeatureGroup.points( points: List>, - zoomRange: FloatRange = defaultZoomRange, stroke: Float = 2f, - color: Color = defaultColor, pointMode: PointMode = PointMode.Points, id: String? = null, ): FeatureId> = - feature(id, PointsFeature(space, points.map(::coordinatesOf), zoomRange, stroke, color, pointMode)) + feature(id, PointsFeature(space, points.map(::coordinatesOf), stroke, pointMode)) -public fun FeatureBuilder.image( +public fun FeatureGroup.image( position: Pair, image: ImageVector, size: DpSize = DpSize(20.dp, 20.dp), - zoomRange: FloatRange = defaultZoomRange, id: String? = null, ): FeatureId> = feature( id, @@ -114,18 +97,15 @@ public fun FeatureBuilder.image( coordinatesOf(position), size, image, - zoomRange ) ) -public fun FeatureBuilder.text( +public fun FeatureGroup.text( position: Pair, text: String, - zoomRange: FloatRange = defaultZoomRange, - color: Color = defaultColor, font: FeatureFont.() -> Unit = { size = 16f }, id: String? = null, ): FeatureId> = feature( id, - TextFeature(space, coordinatesOf(position), text, zoomRange, color, fontConfig = font) + TextFeature(space, coordinatesOf(position), text, fontConfig = font) ) 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 18d1aba..82532d7 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 @@ -15,10 +15,10 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import center.sciprog.maps.coordinates.Gmc -import center.sciprog.maps.features.FeatureCollection +import center.sciprog.maps.features.FeatureGroup import center.sciprog.maps.features.PainterFeature import center.sciprog.maps.features.drawFeature -import center.sciprog.maps.features.z +import center.sciprog.maps.features.zoomRange import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import mu.KotlinLogging @@ -44,7 +44,7 @@ private val logger = KotlinLogging.logger("MapView") @Composable public actual fun MapView( mapState: MapViewScope, - featuresState: FeatureCollection, + featuresState: FeatureGroup, modifier: Modifier, ): Unit = with(mapState) { @@ -90,9 +90,10 @@ public actual fun MapView( } val painterCache: Map, Painter> = key(featuresState) { - featuresState.features.values.filterIsInstance>().associateWith { it.getPainter() } + featuresState.features.filterIsInstance>().associateWith { it.getPainter() } } + Canvas(modifier = modifier.mapControls(mapState, featuresState.features).fillMaxSize()) { if (canvasSize != size.toDpSize()) { @@ -119,7 +120,7 @@ public actual fun MapView( ) } - featuresState.features.values.filter { viewPoint.zoom in it.zoomRange }.sortedBy { it.z } + featuresState.features.filter { viewPoint.zoom in it.zoomRange } .forEach { feature -> drawFeature(mapState, painterCache, feature) } diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/Attribute.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/Attribute.kt index 2c10878..73f6bdd 100644 --- a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/Attribute.kt +++ b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/Attribute.kt @@ -18,4 +18,6 @@ public object VisibleAttribute : Attribute public object ColorAttribute : Attribute +public object ZoomRangeAttribute : Attribute + public object AlphaAttribute : Attribute diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/Feature.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/Feature.kt index 8cda080..6469279 100644 --- a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/Feature.kt +++ b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/Feature.kt @@ -21,8 +21,6 @@ public interface Feature { public val space: CoordinateSpace - public val zoomRange: FloatRange - public val attributes: Attributes public fun getBoundingBox(zoom: Float): Rectangle? @@ -30,6 +28,11 @@ public interface Feature { public fun withAttributes(modify: Attributes.() -> Attributes): Feature } +public val Feature<*>.color: Color? get() = attributes[ColorAttribute] + +public val Feature<*>.zoomRange: FloatRange + get() = attributes[ZoomRangeAttribute] ?: Float.NEGATIVE_INFINITY..Float.POSITIVE_INFINITY + public interface PainterFeature : Feature { @Composable public fun getPainter(): Painter @@ -65,7 +68,6 @@ public fun Iterable>.computeBoundingBox( @Stable public data class FeatureSelector( override val space: CoordinateSpace, - override val zoomRange: FloatRange, override val attributes: Attributes = Attributes.EMPTY, public val selector: (zoom: Float) -> Feature, ) : Feature { @@ -81,7 +83,6 @@ public data class PathFeature( public val rectangle: Rectangle, public val path: Path, public val brush: Brush, - override val zoomRange: FloatRange, public val style: DrawStyle = Fill, public val targetRect: Rect = path.getBounds(), override val attributes: Attributes = Attributes.EMPTY, @@ -94,7 +95,6 @@ public data class PathFeature( brush = brush, style = style, targetRect = targetRect, - zoomRange = zoomRange ) } @@ -106,9 +106,7 @@ public data class PathFeature( public data class PointsFeature( override val space: CoordinateSpace, public val points: List, - override val zoomRange: FloatRange, public val stroke: Float = 2f, - public val color: Color = Color.Red, public val pointMode: PointMode = PointMode.Points, override val attributes: Attributes = Attributes.EMPTY, ) : Feature { @@ -125,8 +123,6 @@ public data class PointsFeature( public data class PolygonFeature( override val space: CoordinateSpace, public val points: List, - override val zoomRange: FloatRange, - public val color: Color = Color.Red, override val attributes: Attributes = Attributes.EMPTY, ) : DomainFeature { @@ -136,7 +132,8 @@ public data class PolygonFeature( override fun getBoundingBox(zoom: Float): Rectangle? = boundingBox - override fun contains(viewPoint: ViewPoint): Boolean = viewPoint.focus in boundingBox!!//with(space) { viewPoint.focus.isInsidePolygon(points) } + override fun contains(viewPoint: ViewPoint): Boolean = + viewPoint.focus in boundingBox!!//with(space) { viewPoint.focus.isInsidePolygon(points) } override fun withAttributes(modify: (Attributes) -> Attributes): Feature = copy(attributes = modify(attributes)) } @@ -145,9 +142,7 @@ public data class PolygonFeature( public data class CircleFeature( override val space: CoordinateSpace, override val center: T, - override val zoomRange: FloatRange, public val size: Dp = 5.dp, - public val color: Color = Color.Red, override val attributes: Attributes = Attributes.EMPTY, ) : MarkerFeature { override fun getBoundingBox(zoom: Float): Rectangle = @@ -162,9 +157,7 @@ public data class CircleFeature( public data class RectangleFeature( override val space: CoordinateSpace, override val center: T, - override val zoomRange: FloatRange, public val size: DpSize = DpSize(5.dp, 5.dp), - public val color: Color = Color.Red, override val attributes: Attributes = Attributes.EMPTY, ) : MarkerFeature { override fun getBoundingBox(zoom: Float): Rectangle = @@ -180,8 +173,6 @@ public data class LineFeature( override val space: CoordinateSpace, public val a: T, public val b: T, - override val zoomRange: FloatRange, - public val color: Color = Color.Red, override val attributes: Attributes = Attributes.EMPTY, ) : DomainFeature { override fun getBoundingBox(zoom: Float): Rectangle = @@ -208,8 +199,6 @@ public data class ArcFeature( public val oval: Rectangle, public val startAngle: Float, public val arcLength: Float, - override val zoomRange: FloatRange, - public val color: Color = Color.Red, override val attributes: Attributes = Attributes.EMPTY, ) : DraggableFeature { override fun getBoundingBox(zoom: Float): Rectangle = oval @@ -223,7 +212,6 @@ public data class ArcFeature( public data class DrawFeature( override val space: CoordinateSpace, public val position: T, - override val zoomRange: FloatRange, override val attributes: Attributes = Attributes.EMPTY, public val drawFeature: DrawScope.() -> Unit, ) : DraggableFeature { @@ -240,7 +228,6 @@ public data class BitmapImageFeature( override val center: T, public val size: DpSize, public val image: ImageBitmap, - override val zoomRange: FloatRange, override val attributes: Attributes = Attributes.EMPTY, ) : MarkerFeature { override fun getBoundingBox(zoom: Float): Rectangle = space.Rectangle(center, zoom, size) @@ -256,7 +243,6 @@ public data class VectorImageFeature( override val center: T, public val size: DpSize, public val image: ImageVector, - override val zoomRange: FloatRange, override val attributes: Attributes = Attributes.EMPTY, ) : MarkerFeature, PainterFeature { override fun getBoundingBox(zoom: Float): Rectangle = space.Rectangle(center, zoom, size) @@ -277,7 +263,6 @@ public data class VectorImageFeature( public data class ScalableImageFeature( override val space: CoordinateSpace, public val rectangle: Rectangle, - override val zoomRange: FloatRange, override val attributes: Attributes = Attributes.EMPTY, public val painter: @Composable () -> Painter, ) : Feature, PainterFeature { @@ -290,28 +275,10 @@ public data class ScalableImageFeature( } -/** - * A group of other features - */ -public data class FeatureGroup( - override val space: CoordinateSpace, - public val children: Map, Feature>, - override val zoomRange: FloatRange, - override val attributes: Attributes = Attributes.EMPTY, -) : Feature { - override fun getBoundingBox(zoom: Float): Rectangle? = with(space) { - children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapRectangles() - } - - override fun withAttributes(modify: Attributes.() -> Attributes): Feature = copy(attributes = attributes) -} - public data class TextFeature( override val space: CoordinateSpace, public val position: T, public val text: String, - override val zoomRange: FloatRange, - public val color: Color = Color.Black, override val attributes: Attributes = Attributes.EMPTY, public val fontConfig: FeatureFont.() -> Unit, ) : DraggableFeature { diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureCollection.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureCollection.kt deleted file mode 100644 index 22463ce..0000000 --- a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureCollection.kt +++ /dev/null @@ -1,335 +0,0 @@ -package center.sciprog.maps.features - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateMapOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.snapshots.SnapshotStateMap -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.PointMode -import androidx.compose.ui.graphics.drawscope.DrawScope -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.input.pointer.PointerEvent -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.unit.dp -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlin.jvm.JvmInline - -@JvmInline -public value class FeatureId>(public val id: String) - -public interface FeatureBuilder { - - public val space: CoordinateSpace - - public fun generateUID(feature: Feature?): String - - public fun > feature(id: String?, feature: F): FeatureId - - public fun , V> FeatureId.withAttribute(key: Attribute, value: V?): FeatureId - - public val defaultColor: Color get() = Color.Red - - public val defaultZoomRange: FloatRange get() = 0f..Float.POSITIVE_INFINITY -} - -public fun > FeatureBuilder.feature(id: FeatureId, feature: F): FeatureId = - feature(id.id, feature) - -public class FeatureCollection( - override val space: CoordinateSpace, -) : CoordinateSpace by space, FeatureBuilder { - - @PublishedApi - internal val featureMap: SnapshotStateMap> = mutableStateMapOf() - - public val features: Map, Feature> - get() = featureMap.mapKeys { FeatureId>(it.key) } - - @Suppress("UNCHECKED_CAST") - public operator fun > get(id: FeatureId): F = - featureMap[id.id]?.let { it as F } ?: error("Feature with id=$id not found") - - private var uidCounter = 0 - - override fun generateUID(feature: Feature?): String = if(feature == null){ - "@group[${uidCounter++}]" - } else { - "@${feature::class.simpleName}[${uidCounter++}]" - } - - override fun > feature(id: String?, feature: F): FeatureId { - val safeId = id ?: generateUID(feature) - featureMap[safeId] = feature - return FeatureId(safeId) - } - - public fun > feature(id: FeatureId, feature: F): FeatureId = feature(id.id, feature) - - @Suppress("UNCHECKED_CAST") - public fun getAttribute(id: FeatureId>, key: Attribute): A? = - get(id).attributes[key] - - /** - * Process all features with a given attribute from the one with highest [z] to lowest - */ - public inline fun forEachWithAttribute( - key: Attribute, - block: (id: FeatureId<*>, feature: Feature, attributeValue: A) -> Unit, - ) { - featureMap.entries.sortedByDescending { it.value.z }.forEach { (id, feature) -> - feature.attributes[key]?.let { - block(FeatureId>(id), feature, it) - } - } - } - - public fun , V> FeatureId.modifyAttributes(modify: Attributes.() -> Attributes) { - feature(this, get(this).withAttributes(modify)) - } - - override fun , V> FeatureId.withAttribute(key: Attribute, value: V?): FeatureId { - feature(this, get(this).withAttributes { withAttribute(key, value) }) - return this - } - - - /** - * Add drag to this feature - * - * @param constraint optional drag constraint - * - * TODO use context receiver for that - */ - @Suppress("UNCHECKED_CAST") - public fun FeatureId>.draggable( - constraint: ((T) -> T)? = null, - listener: (PointerEvent.(from: ViewPoint, to: ViewPoint) -> Unit)? = null, - ) { - if (getAttribute(this, DraggableAttribute) == null) { - val handle = DragHandle.withPrimaryButton { event, start, end -> - val feature = featureMap[id] as? DraggableFeature ?: return@withPrimaryButton DragResult(end) - start as ViewPoint - end as ViewPoint - if (start in feature) { - val finalPosition = constraint?.invoke(end.focus) ?: end.focus - feature(id, feature.withCoordinates(finalPosition)) - feature.attributes[DragListenerAttribute]?.forEach { - it.handle(event, start, ViewPoint(finalPosition, end.zoom)) - } - DragResult(ViewPoint(finalPosition, end.zoom), false) - } else { - DragResult(end, true) - } - } - this.withAttribute(DraggableAttribute, handle) - } - - //Apply callback - if (listener != null) { - onDrag(listener) - } - } - - - @Suppress("UNCHECKED_CAST") - public fun FeatureId>.onDrag( - listener: PointerEvent.(from: ViewPoint, to: ViewPoint) -> Unit, - ) { - withAttribute( - DragListenerAttribute, - (getAttribute(this, DragListenerAttribute) ?: emptySet()) + - DragListener { event, from, to -> - event.listener(from as ViewPoint, to as ViewPoint) - } - ) - } - - @Suppress("UNCHECKED_CAST") - public fun > FeatureId.onClick( - onClick: PointerEvent.(click: ViewPoint) -> Unit, - ) { - withAttribute( - ClickListenerAttribute, - (getAttribute(this, ClickListenerAttribute) ?: emptySet()) + - MouseListener { event, point -> event.onClick(point as ViewPoint) } - ) - } - - @Suppress("UNCHECKED_CAST") - public fun > FeatureId.onHover( - onClick: PointerEvent.(move: ViewPoint) -> Unit, - ) { - withAttribute( - HoverListenerAttribute, - (getAttribute(this, HoverListenerAttribute) ?: emptySet()) + - MouseListener { event, point -> event.onClick(point as ViewPoint) } - ) - } - - /** - * Cyclic update of a feature. Called infinitely until canceled. - */ - public fun > FeatureId.updated( - scope: CoroutineScope, - update: suspend (F) -> F, - ): Job = scope.launch { - while (isActive) { - feature(this@updated, update(get(this@updated))) - } - } - - - public companion object { - - /** - * Build, but do not remember map feature state - */ - public fun build( - coordinateSpace: CoordinateSpace, - builder: FeatureCollection.() -> Unit = {}, - ): FeatureCollection = FeatureCollection(coordinateSpace).apply(builder) - - /** - * Build and remember map feature state - */ - @Composable - public fun remember( - coordinateSpace: CoordinateSpace, - builder: FeatureCollection.() -> Unit = {}, - ): FeatureCollection = remember(builder) { - build(coordinateSpace, builder) - } - - } -} - -public fun FeatureBuilder.circle( - center: T, - zoomRange: FloatRange = defaultZoomRange, - size: Dp = 5.dp, - color: Color = defaultColor, - id: String? = null, -): FeatureId> = feature( - id, CircleFeature(space, center, zoomRange, size, color) -) - -public fun FeatureBuilder.rectangle( - centerCoordinates: T, - zoomRange: FloatRange = defaultZoomRange, - size: DpSize = DpSize(5.dp, 5.dp), - color: Color = defaultColor, - id: String? = null, -): FeatureId> = feature( - id, RectangleFeature(space, centerCoordinates, zoomRange, size, color) -) - -public fun FeatureBuilder.draw( - position: T, - zoomRange: FloatRange = defaultZoomRange, - id: String? = null, - draw: DrawScope.() -> Unit, -): FeatureId> = feature( - id, - DrawFeature(space, position, zoomRange, drawFeature = draw) -) - -public fun FeatureBuilder.line( - aCoordinates: T, - bCoordinates: T, - zoomRange: FloatRange = defaultZoomRange, - color: Color = defaultColor, - id: String? = null, -): FeatureId> = feature( - id, - LineFeature(space, aCoordinates, bCoordinates, zoomRange, color) -) - -public fun FeatureBuilder.arc( - oval: Rectangle, - startAngle: Float, - arcLength: Float, - zoomRange: FloatRange = defaultZoomRange, - color: Color = defaultColor, - id: String? = null, -): FeatureId> = feature( - id, - ArcFeature(space, oval, startAngle, arcLength, zoomRange, color) -) - -public fun FeatureBuilder.points( - points: List, - zoomRange: FloatRange = defaultZoomRange, - stroke: Float = 2f, - color: Color = defaultColor, - pointMode: PointMode = PointMode.Points, - attributes: Attributes = Attributes.EMPTY, - id: String? = null, -): FeatureId> = feature( - id, - PointsFeature(space, points, zoomRange, stroke, color, pointMode, attributes) -) - -public fun FeatureBuilder.polygon( - points: List, - zoomRange: FloatRange = defaultZoomRange, - color: Color = defaultColor, - id: String? = null, -): FeatureId> = feature( - id, - PolygonFeature(space, points, zoomRange, color) -) - -public fun FeatureBuilder.image( - position: T, - image: ImageVector, - zoomRange: FloatRange = defaultZoomRange, - size: DpSize = DpSize(image.defaultWidth, image.defaultHeight), - id: String? = null, -): FeatureId> = - feature( - id, - VectorImageFeature( - space, - position, - size, - image, - zoomRange - ) - ) - -public fun FeatureBuilder.group( - zoomRange: FloatRange = defaultZoomRange, - id: String? = null, - builder: FeatureCollection.() -> Unit, -): FeatureId> { - val map = FeatureCollection(space).apply(builder).features - val feature = FeatureGroup(space, map, zoomRange) - return feature(id, feature) -} - -public fun FeatureBuilder.scalableImage( - box: Rectangle, - zoomRange: FloatRange = defaultZoomRange, - id: String? = null, - painter: @Composable () -> Painter, -): FeatureId> = feature( - id, - ScalableImageFeature(space, box, zoomRange, painter = painter) -) - -public fun FeatureBuilder.text( - position: T, - text: String, - zoomRange: FloatRange = defaultZoomRange, - color: Color = defaultColor, - font: FeatureFont.() -> Unit = { size = 16f }, - id: String? = null, -): FeatureId> = feature( - id, - TextFeature(space, position, text, zoomRange, color, fontConfig = font) -) diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureGroup.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureGroup.kt index 623d3b4..73e86d2 100644 --- a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureGroup.kt +++ b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureGroup.kt @@ -1,37 +1,315 @@ package center.sciprog.maps.features -///** -// * A group of other features -// */ -//public data class FeatureGroup( -// val parentBuilder: FeatureBuilder, -// private val groupId: String, -// public override val zoomRange: FloatRange, -// override val attributes: Attributes = Attributes.EMPTY, -//) : FeatureBuilder, Feature { -// -// override val space: CoordinateSpace get() = parentBuilder.space -// -// override fun generateUID(feature: Feature?): String = parentBuilder.generateUID(feature) -// -// override fun > feature(id: String?, feature: F): FeatureId = -// parentBuilder.feature("${groupId}.${id ?: parentBuilder.generateUID(feature)}", feature) -// -// override fun , V> FeatureId.withAttribute(key: Attribute, value: V?): FeatureId = -// with(parentBuilder) { -// FeatureId("${groupId}.${this@withAttribute.id}").withAttribute(key, value) +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PointMode +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.PointerEvent +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import kotlin.jvm.JvmInline + +@JvmInline +public value class FeatureId>(public val id: String) + +/** + * A group of other features + */ +public data class FeatureGroup( + override val space: CoordinateSpace, + public val featureMap: SnapshotStateMap> = mutableStateMapOf(), + override val attributes: Attributes = Attributes.EMPTY, +) : CoordinateSpace by space, Feature { + + @Suppress("UNCHECKED_CAST") + public operator fun > get(id: FeatureId): F = + featureMap[id.id]?.let { it as F } ?: error("Feature with id=$id not found") + + private var uidCounter = 0 + + private fun generateUID(feature: Feature?): String = if (feature == null) { + "@group[${uidCounter++}]" + } else { + "@${feature::class.simpleName}[${uidCounter++}]" + } + + public fun > feature(id: String?, feature: F): FeatureId { + val safeId = id ?: generateUID(feature) + featureMap[safeId] = feature + return FeatureId(safeId) + } + + public fun > feature(id: FeatureId, feature: F): FeatureId = feature(id.id, feature) + + public val features: Collection> get() = featureMap.values.sortedByDescending { it.z } + + public fun visit(visitor: FeatureGroup.(id: FeatureId>, feature: Feature) -> Unit) { + featureMap.forEach { (key, feature) -> + if (feature is FeatureGroup) { + feature.visit(visitor) + } else { + visitor(this, FeatureId(key), feature) + } + } + } + + @Suppress("UNCHECKED_CAST") + public fun getAttribute(id: FeatureId>, key: Attribute): A? = + get(id).attributes[key] + + /** + * Process all features with a given attribute from the one with highest [z] to lowest + */ + public inline fun forEachWithAttribute( + key: Attribute, + block: (id: FeatureId<*>, feature: Feature, attributeValue: A) -> Unit, + ) { + featureMap.entries.sortedByDescending { it.value.z }.forEach { (id, feature) -> + feature.attributes[key]?.let { + block(FeatureId>(id), feature, it) + } + } + } + + public fun , V> FeatureId.modifyAttributes(modify: Attributes.() -> Attributes) { + feature(this, get(this).withAttributes(modify)) + } + + public fun , V> FeatureId.withAttribute(key: Attribute, value: V?): FeatureId { + feature(this, get(this).withAttributes { withAttribute(key, value) }) + return this + } + + + /** + * Add drag to this feature + * + * @param constraint optional drag constraint + * + * TODO use context receiver for that + */ + @Suppress("UNCHECKED_CAST") + public fun FeatureId>.draggable( + constraint: ((T) -> T)? = null, + listener: (PointerEvent.(from: ViewPoint, to: ViewPoint) -> Unit)? = null, + ) { + if (getAttribute(this, DraggableAttribute) == null) { + val handle = DragHandle.withPrimaryButton { event, start, end -> + val feature = featureMap[id] as? DraggableFeature ?: return@withPrimaryButton DragResult(end) + start as ViewPoint + end as ViewPoint + if (start in feature) { + val finalPosition = constraint?.invoke(end.focus) ?: end.focus + feature(id, feature.withCoordinates(finalPosition)) + feature.attributes[DragListenerAttribute]?.forEach { + it.handle(event, start, ViewPoint(finalPosition, end.zoom)) + } + DragResult(ViewPoint(finalPosition, end.zoom), false) + } else { + DragResult(end, true) + } + } + this.withAttribute(DraggableAttribute, handle) + } + + //Apply callback + if (listener != null) { + onDrag(listener) + } + } + + + @Suppress("UNCHECKED_CAST") + public fun FeatureId>.onDrag( + listener: PointerEvent.(from: ViewPoint, to: ViewPoint) -> Unit, + ) { + withAttribute( + DragListenerAttribute, + (getAttribute(this, DragListenerAttribute) ?: emptySet()) + + DragListener { event, from, to -> + event.listener(from as ViewPoint, to as ViewPoint) + } + ) + } + + @Suppress("UNCHECKED_CAST") + public fun > FeatureId.onClick( + onClick: PointerEvent.(click: ViewPoint) -> Unit, + ) { + withAttribute( + ClickListenerAttribute, + (getAttribute(this, ClickListenerAttribute) ?: emptySet()) + + MouseListener { event, point -> event.onClick(point as ViewPoint) } + ) + } + + @Suppress("UNCHECKED_CAST") + public fun > FeatureId.onHover( + onClick: PointerEvent.(move: ViewPoint) -> Unit, + ) { + withAttribute( + HoverListenerAttribute, + (getAttribute(this, HoverListenerAttribute) ?: emptySet()) + + MouseListener { event, point -> event.onClick(point as ViewPoint) } + ) + } + +// /** +// * Cyclic update of a feature. Called infinitely until canceled. +// */ +// public fun > FeatureId.updated( +// scope: CoroutineScope, +// update: suspend (F) -> F, +// ): Job = scope.launch { +// while (isActive) { +// feature(this@updated, update(get(this@updated))) // } -// -// override fun getBoundingBox(zoom: Float): Rectangle? { -// TODO("Not yet implemented") // } -// -// override fun withAttributes(modify: Attributes.() -> Attributes): Feature = -// copy(attributes = attributes.modify()) -//} -// -//public fun FeatureBuilder.group( -// zoomRange: FloatRange = defaultZoomRange, -// id: String? = null, -// builder: FeatureBuilder.() -> Unit, -//): FeatureId> = feature(id, FeatureGroup(this, id ?: generateUID(null), zoomRange).apply(builder)) + + public fun > FeatureId.color(color: Color): FeatureId = + withAttribute(ColorAttribute, color) + public fun > FeatureId.zoomRange(range: FloatRange): FeatureId = + withAttribute(ZoomRangeAttribute, range) + + override fun getBoundingBox(zoom: Float): Rectangle? = with(space) { + featureMap.values.mapNotNull { it.getBoundingBox(zoom) }.wrapRectangles() + } + + override fun withAttributes(modify: Attributes.() -> Attributes): Feature = copy(attributes = attributes) + + public companion object { + + /** + * Build, but do not remember map feature state + */ + public fun build( + coordinateSpace: CoordinateSpace, + builder: FeatureGroup.() -> Unit = {}, + ): FeatureGroup = FeatureGroup(coordinateSpace).apply(builder) + + /** + * Build and remember map feature state + */ + @Composable + public fun remember( + coordinateSpace: CoordinateSpace, + builder: FeatureGroup.() -> Unit = {}, + ): FeatureGroup = remember(builder) { + build(coordinateSpace, builder) + } + + } +} + +public fun FeatureGroup.circle( + center: T, + size: Dp = 5.dp, + id: String? = null, +): FeatureId> = feature( + id, CircleFeature(space, center, size) +) + +public fun FeatureGroup.rectangle( + centerCoordinates: T, + size: DpSize = DpSize(5.dp, 5.dp), + id: String? = null, +): FeatureId> = feature( + id, RectangleFeature(space, centerCoordinates, size) +) + +public fun FeatureGroup.draw( + position: T, + id: String? = null, + draw: DrawScope.() -> Unit, +): FeatureId> = feature( + id, + DrawFeature(space, position, drawFeature = draw) +) + +public fun FeatureGroup.line( + aCoordinates: T, + bCoordinates: T, + id: String? = null, +): FeatureId> = feature( + id, + LineFeature(space, aCoordinates, bCoordinates) +) + +public fun FeatureGroup.arc( + oval: Rectangle, + startAngle: Float, + arcLength: Float, + id: String? = null, +): FeatureId> = feature( + id, + ArcFeature(space, oval, startAngle, arcLength) +) + +public fun FeatureGroup.points( + points: List, + stroke: Float = 2f, + pointMode: PointMode = PointMode.Points, + attributes: Attributes = Attributes.EMPTY, + id: String? = null, +): FeatureId> = feature( + id, + PointsFeature(space, points, stroke, pointMode, attributes) +) + +public fun FeatureGroup.polygon( + points: List, + id: String? = null, +): FeatureId> = feature( + id, + PolygonFeature(space, points) +) + +public fun FeatureGroup.image( + position: T, + image: ImageVector, + size: DpSize = DpSize(image.defaultWidth, image.defaultHeight), + id: String? = null, +): FeatureId> = + feature( + id, + VectorImageFeature( + space, + position, + size, + image, + ) + ) + +public fun FeatureGroup.group( + id: String? = null, + builder: FeatureGroup.() -> Unit, +): FeatureId> { + val collection = FeatureGroup(space).apply(builder) + val feature = FeatureGroup(space, collection.featureMap) + return feature(id, feature) +} + +public fun FeatureGroup.scalableImage( + box: Rectangle, + id: String? = null, + painter: @Composable () -> Painter, +): FeatureId> = feature( + id, + ScalableImageFeature(space, box, painter = painter) +) + +public fun FeatureGroup.text( + position: T, + text: String, + font: FeatureFont.() -> Unit = { size = 16f }, + id: String? = null, +): FeatureId> = feature( + id, + TextFeature(space, position, text, fontConfig = font) +) diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/compositeFeatures.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/compositeFeatures.kt index d87cfa9..ab78dc7 100644 --- a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/compositeFeatures.kt +++ b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/compositeFeatures.kt @@ -1,13 +1,9 @@ package center.sciprog.maps.features -import androidx.compose.ui.graphics.Color - -public fun FeatureCollection.draggableLine( +public fun FeatureGroup.draggableLine( aId: FeatureId>, bId: FeatureId>, - zoomRange: FloatRange = defaultZoomRange, - color: Color = Color.Red, id: String? = null, ): FeatureId> { var lineId: FeatureId>? = null @@ -15,8 +11,6 @@ public fun FeatureCollection.draggableLine( fun drawLine(): FeatureId> = line( get(aId).center, get(bId).center, - zoomRange, - color, lineId?.id ?: id ).also { lineId = it diff --git a/maps-kt-features/src/jvmMain/kotlin/center/sciprog/maps/compose/mapControls.kt b/maps-kt-features/src/jvmMain/kotlin/center/sciprog/maps/compose/mapControls.kt index 9a8e211..c525360 100644 --- a/maps-kt-features/src/jvmMain/kotlin/center/sciprog/maps/compose/mapControls.kt +++ b/maps-kt-features/src/jvmMain/kotlin/center/sciprog/maps/compose/mapControls.kt @@ -14,10 +14,11 @@ import kotlin.math.min /** * Create a modifier for Map/Scheme canvas controls on desktop + * @param features a collection of features to be rendered in descending [ZAttribute] order */ public fun Modifier.mapControls( state: CoordinateViewScope, - features: Map, Feature>, + features: Collection>, ): Modifier = with(state) { pointerInput(Unit) { fun Offset.toDpOffset() = DpOffset(x.toDp(), y.toDp()) @@ -27,10 +28,8 @@ public fun Modifier.mapControls( val coordinates = event.changes.first().position.toDpOffset().toCoordinates() val point = space.ViewPoint(coordinates, zoom) - val sortedFeatures =features.values.sortedByDescending { it.z } - if (event.type == PointerEventType.Move) { - for (feature in sortedFeatures) { + for (feature in features) { val listeners = (feature as? DomainFeature)?.attributes?.get(HoverListenerAttribute) if (listeners != null && point in feature) { listeners.forEach { it.handle(event, point) } @@ -43,7 +42,7 @@ public fun Modifier.mapControls( event, point ) - for (feature in sortedFeatures) { + for (feature in features) { val listeners = (feature as? DomainFeature)?.attributes?.get(ClickListenerAttribute) if (listeners != null && point in feature) { listeners.forEach { it.handle(event, point) } @@ -97,9 +96,8 @@ public fun Modifier.mapControls( val dragResult = config.dragHandle?.handle(event, dragStart, dragEnd) if (dragResult?.handleNext == false) return@drag - features.values.asSequence() + features.asSequence() .filterIsInstance>() - .sortedByDescending { it.z } .mapNotNull { it.attributes[DraggableAttribute] }.forEach { handler -> diff --git a/maps-kt-features/src/jvmMain/kotlin/center/sciprog/maps/features/drawFeature.kt b/maps-kt-features/src/jvmMain/kotlin/center/sciprog/maps/features/drawFeature.kt index 71d1706..69614f8 100644 --- a/maps-kt-features/src/jvmMain/kotlin/center/sciprog/maps/features/drawFeature.kt +++ b/maps-kt-features/src/jvmMain/kotlin/center/sciprog/maps/features/drawFeature.kt @@ -25,21 +25,21 @@ public fun DrawScope.drawFeature( state: CoordinateViewScope, painterCache: Map, Painter>, feature: Feature, - ): Unit = with(state) { + val color = feature.color ?: Color.Red val alpha = feature.attributes[AlphaAttribute]?:1f fun T.toOffset(): Offset = toOffset(this@drawFeature) when (feature) { is FeatureSelector -> drawFeature(state, painterCache, feature.selector(state.zoom)) is CircleFeature -> drawCircle( - feature.color, + color, feature.size.toPx(), center = feature.center.toOffset() ) is RectangleFeature -> drawRect( - feature.color, + color, topLeft = feature.center.toOffset() - Offset( feature.size.width.toPx() / 2, feature.size.height.toPx() / 2 @@ -47,14 +47,14 @@ public fun DrawScope.drawFeature( size = feature.size.toSize() ) - is LineFeature -> drawLine(feature.color, feature.a.toOffset(), feature.b.toOffset()) + is LineFeature -> drawLine(color, feature.a.toOffset(), feature.b.toOffset()) is ArcFeature -> { val dpRect = feature.oval.toDpRect().toRect() val size = Size(dpRect.width, dpRect.height) drawArc( - color = feature.color, + color = color, startAngle = feature.startAngle / PI.toFloat() * 180f, sweepAngle = feature.arcLength / PI.toFloat() * 180f, useCenter = false, @@ -85,7 +85,7 @@ public fun DrawScope.drawFeature( offset.x + 5, offset.y - 5, Font().apply(feature.fontConfig), - feature.color.toPaint() + (feature.color ?: Color.Black).toPaint() ) } @@ -97,8 +97,7 @@ public fun DrawScope.drawFeature( } is FeatureGroup -> { - //do nothing - feature.children.values.forEach { + feature.featureMap.values.forEach { drawFeature(state, painterCache, it) } } @@ -116,7 +115,7 @@ public fun DrawScope.drawFeature( val points = feature.points.map { it.toOffset() } drawPoints( points = points, - color = feature.color, + color = color, strokeWidth = feature.stroke, pointMode = feature.pointMode, alpha = alpha @@ -133,7 +132,7 @@ public fun DrawScope.drawFeature( } drawPath( path = polygonPath, - color = feature.color, + color = color, alpha = alpha ) } diff --git a/maps-kt-geojson/src/commonMain/kotlin/center/sciprog/maps/geojson/geoJsonFeature.kt b/maps-kt-geojson/src/commonMain/kotlin/center/sciprog/maps/geojson/geoJsonFeature.kt index 6a85432..bb7c3e4 100644 --- a/maps-kt-geojson/src/commonMain/kotlin/center/sciprog/maps/geojson/geoJsonFeature.kt +++ b/maps-kt-geojson/src/commonMain/kotlin/center/sciprog/maps/geojson/geoJsonFeature.kt @@ -12,14 +12,12 @@ import kotlinx.serialization.json.jsonPrimitive /** * Add a single Json geometry to a feature builder */ -public fun FeatureBuilder.geoJsonGeometry( +public fun FeatureGroup.geoJsonGeometry( geometry: GeoJsonGeometry, - color: Color = defaultColor, id: String? = null, ): FeatureId> = when (geometry) { is GeoJsonLineString -> points( geometry.coordinates, - color = color, pointMode = PointMode.Lines ) @@ -27,7 +25,6 @@ public fun FeatureBuilder.geoJsonGeometry( geometry.coordinates.forEach { points( it, - color = color, pointMode = PointMode.Lines ) } @@ -35,7 +32,6 @@ public fun FeatureBuilder.geoJsonGeometry( is GeoJsonMultiPoint -> points( geometry.coordinates, - color = color, pointMode = PointMode.Points ) @@ -43,15 +39,13 @@ public fun FeatureBuilder.geoJsonGeometry( geometry.coordinates.forEach { polygon( it.first(), - color = color, ) } } - is GeoJsonPoint -> circle(geometry.coordinates, color = color, id = id) + is GeoJsonPoint -> circle(geometry.coordinates, id = id) is GeoJsonPolygon -> polygon( geometry.coordinates.first(), - color = color, ) is GeoJsonGeometryCollection -> group(id = id) { @@ -63,18 +57,22 @@ public fun FeatureBuilder.geoJsonGeometry( withAttribute(AlphaAttribute, 0.5f) } -public fun FeatureBuilder.geoJsonFeature( +public fun FeatureGroup.geoJsonFeature( geoJson: GeoJsonFeature, - color: Color = defaultColor, id: String? = null, ): FeatureId>? { val geometry = geoJson.geometry ?: return null val idOverride = geoJson.properties?.get("id")?.jsonPrimitive?.contentOrNull ?: id - val colorOverride = geoJson.properties?.get("color")?.jsonPrimitive?.intOrNull?.let { Color(it) } ?: color - return geoJsonGeometry(geometry, colorOverride, idOverride) + val colorOverride = geoJson.properties?.get("color")?.jsonPrimitive?.intOrNull?.let { Color(it) } + val jsonGeometry = geoJsonGeometry(geometry, idOverride) + return if( colorOverride!= null){ + jsonGeometry.color(colorOverride) + } else{ + jsonGeometry + } } -public fun FeatureBuilder.geoJson( +public fun FeatureGroup.geoJson( geoJson: GeoJson, id: String? = null, ): FeatureId>? = when (geoJson) { diff --git a/maps-kt-geojson/src/jvmMain/kotlin/center/sciprog/maps/geojson/geoJsonFeatureJvm.kt b/maps-kt-geojson/src/jvmMain/kotlin/center/sciprog/maps/geojson/geoJsonFeatureJvm.kt index 9436911..69bab3a 100644 --- a/maps-kt-geojson/src/jvmMain/kotlin/center/sciprog/maps/geojson/geoJsonFeatureJvm.kt +++ b/maps-kt-geojson/src/jvmMain/kotlin/center/sciprog/maps/geojson/geoJsonFeatureJvm.kt @@ -1,7 +1,7 @@ package center.sciprog.maps.geojson import center.sciprog.maps.coordinates.Gmc -import center.sciprog.maps.features.FeatureBuilder +import center.sciprog.maps.features.FeatureGroup import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonObject import java.net.URL @@ -9,7 +9,7 @@ import java.net.URL /** * Add geojson features from url */ -public fun FeatureBuilder.geoJson( +public fun FeatureGroup.geoJson( geoJsonUrl: URL, id: String? = null, ) { diff --git a/maps-kt-scheme/src/commonMain/kotlin/center/sciprog/maps/scheme/schemeFeatures.kt b/maps-kt-scheme/src/commonMain/kotlin/center/sciprog/maps/scheme/schemeFeatures.kt index 2826827..5f6ae37 100644 --- a/maps-kt-scheme/src/commonMain/kotlin/center/sciprog/maps/scheme/schemeFeatures.kt +++ b/maps-kt-scheme/src/commonMain/kotlin/center/sciprog/maps/scheme/schemeFeatures.kt @@ -12,7 +12,7 @@ import center.sciprog.maps.features.* internal fun Pair.toCoordinates(): XY = XY(first.toFloat(), second.toFloat()) -fun FeatureBuilder.background( +fun FeatureGroup.background( width: Float, height: Float, offset: XY = XY(0f, 0f), @@ -35,7 +35,7 @@ fun FeatureBuilder.background( ) } -fun FeatureBuilder.circle( +fun FeatureGroup.circle( centerCoordinates: Pair, zoomRange: FloatRange = defaultZoomRange, size: Dp = 5.dp, @@ -43,14 +43,14 @@ fun FeatureBuilder.circle( id: String? = null, ): FeatureId> = circle(centerCoordinates.toCoordinates(), zoomRange, size, color, id = id) -fun FeatureBuilder.draw( +fun FeatureGroup.draw( position: Pair, zoomRange: FloatRange = defaultZoomRange, id: String? = null, draw: DrawScope.() -> Unit, ): FeatureId> = draw(position.toCoordinates(), zoomRange = zoomRange, id = id, draw = draw) -fun FeatureBuilder.line( +fun FeatureGroup.line( aCoordinates: Pair, bCoordinates: Pair, scaleRange: FloatRange = defaultZoomRange, @@ -59,7 +59,7 @@ fun FeatureBuilder.line( ): FeatureId> = line(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), scaleRange, color, id) -public fun FeatureBuilder.arc( +public fun FeatureGroup.arc( center: Pair, radius: Float, startAngle: Float, @@ -76,7 +76,7 @@ public fun FeatureBuilder.arc( id = id ) -fun FeatureBuilder.image( +fun FeatureGroup.image( position: Pair, image: ImageVector, size: DpSize = DpSize(image.defaultWidth, image.defaultHeight), @@ -85,7 +85,7 @@ fun FeatureBuilder.image( ): FeatureId> = image(position.toCoordinates(), image, size = size, zoomRange = zoomRange, id = id) -fun FeatureBuilder.text( +fun FeatureGroup.text( position: Pair, text: String, zoomRange: FloatRange = defaultZoomRange, diff --git a/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/scheme/SchemeView.kt b/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/scheme/SchemeView.kt index 56f5c7f..f333949 100644 --- a/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/scheme/SchemeView.kt +++ b/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/scheme/SchemeView.kt @@ -22,12 +22,12 @@ private val logger = KotlinLogging.logger("SchemeView") @Composable public fun SchemeView( state: XYViewScope, - featuresState: FeatureCollection, + featuresState: FeatureGroup, modifier: Modifier = Modifier.fillMaxSize(), ) { with(state) { val painterCache: Map, Painter> = key(featuresState) { - featuresState.features.values.filterIsInstance>().associateWith { it.getPainter() } + featuresState.features.filterIsInstance>().associateWith { it.getPainter() } } Canvas(modifier = modifier.mapControls(state, featuresState.features).fillMaxSize()) { @@ -38,9 +38,8 @@ public fun SchemeView( } clipRect { - featuresState.features.values + featuresState.features .filter { viewPoint.zoom in it.zoomRange } - .sortedBy { it.z } .forEach { background -> drawFeature(state, painterCache, background) } @@ -88,7 +87,7 @@ public fun SchemeView( val featureState = key(featureMap) { - FeatureCollection.build(XYCoordinateSpace) { + FeatureGroup.build(XYCoordinateSpace) { featureMap.forEach { feature(it.key.id, it.value) } } } @@ -96,7 +95,7 @@ public fun SchemeView( val state = rememberMapState( config, initialViewPoint = initialViewPoint, - initialRectangle = initialRectangle ?: featureState.features.values.computeBoundingBox(XYCoordinateSpace, Float.MAX_VALUE), + initialRectangle = initialRectangle ?: featureState.features.computeBoundingBox(XYCoordinateSpace, Float.MAX_VALUE), ) SchemeView(state, featureState, modifier) @@ -115,13 +114,13 @@ public fun SchemeView( initialRectangle: Rectangle? = null, config: ViewConfig = ViewConfig(), modifier: Modifier = Modifier.fillMaxSize(), - buildFeatures: FeatureCollection.() -> Unit = {}, + buildFeatures: FeatureGroup.() -> Unit = {}, ) { - val featureState = FeatureCollection.remember(XYCoordinateSpace, buildFeatures) + val featureState = FeatureGroup.remember(XYCoordinateSpace, buildFeatures) val mapState: XYViewScope = rememberMapState( config, initialViewPoint = initialViewPoint, - initialRectangle = initialRectangle ?: featureState.features.values.computeBoundingBox(XYCoordinateSpace, Float.MAX_VALUE), + initialRectangle = initialRectangle ?: featureState.features.computeBoundingBox(XYCoordinateSpace, Float.MAX_VALUE), ) SchemeView(mapState, featureState, modifier) diff --git a/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/exportToSvg.kt b/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/exportToSvg.kt index 72d2b8c..03a6fef 100644 --- a/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/exportToSvg.kt +++ b/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/exportToSvg.kt @@ -17,14 +17,14 @@ import kotlin.math.abs class FeatureStateSnapshot( - val features: Map, Feature>, + val features: Map>, val painterCache: Map, Painter>, ) @Composable -fun FeatureCollection.snapshot(): FeatureStateSnapshot = FeatureStateSnapshot( - features, - features.values.filterIsInstance>().associateWith { it.getPainter() } +fun FeatureGroup.snapshot(): FeatureStateSnapshot = FeatureStateSnapshot( + featureMap, + features.filterIsInstance>().associateWith { it.getPainter() } ) fun FeatureStateSnapshot.generateSvg( @@ -114,7 +114,7 @@ fun FeatureStateSnapshot.generateSvg( } is FeatureGroup -> { - feature.children.values.forEach { + feature.featureMap.values.forEach { drawFeature(scale, it) } }