New attributes

This commit is contained in:
Alexander Nozik 2022-12-17 21:36:05 +03:00
parent 8451cc3aa1
commit 0d9efadcb8
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
5 changed files with 71 additions and 56 deletions

View File

@ -20,7 +20,7 @@ public interface MapFeature {
public val zoomRange: IntRange public val zoomRange: IntRange
public val attributes: AttributeMap public var attributes: AttributeMap
public fun getBoundingBox(zoom: Double): GmcRectangle? public fun getBoundingBox(zoom: Double): GmcRectangle?
} }
@ -47,7 +47,7 @@ internal val defaultZoomRange = 1..18
* A feature that decides what to show depending on the zoom value (it could change size of shape) * A feature that decides what to show depending on the zoom value (it could change size of shape)
*/ */
public class MapFeatureSelector( public class MapFeatureSelector(
override val attributes: AttributeMap = AttributeMap(), override var attributes: AttributeMap = AttributeMap(),
public val selector: (zoom: Int) -> MapFeature, public val selector: (zoom: Int) -> MapFeature,
) : MapFeature { ) : MapFeature {
override val zoomRange: IntRange get() = defaultZoomRange override val zoomRange: IntRange get() = defaultZoomRange
@ -58,7 +58,7 @@ public class MapFeatureSelector(
public class MapDrawFeature( public class MapDrawFeature(
public val position: GeodeticMapCoordinates, public val position: GeodeticMapCoordinates,
override val zoomRange: IntRange = defaultZoomRange, override val zoomRange: IntRange = defaultZoomRange,
override val attributes: AttributeMap = AttributeMap(), override var attributes: AttributeMap = AttributeMap(),
public val drawFeature: DrawScope.() -> Unit, public val drawFeature: DrawScope.() -> Unit,
) : DraggableMapFeature { ) : DraggableMapFeature {
override fun getBoundingBox(zoom: Double): GmcRectangle { override fun getBoundingBox(zoom: Double): GmcRectangle {
@ -77,7 +77,7 @@ public class MapPathFeature(
public val style: DrawStyle = Fill, public val style: DrawStyle = Fill,
public val targetRect: Rect = path.getBounds(), public val targetRect: Rect = path.getBounds(),
override val zoomRange: IntRange = defaultZoomRange, override val zoomRange: IntRange = defaultZoomRange,
override val attributes: AttributeMap = AttributeMap(), override var attributes: AttributeMap = AttributeMap(),
) : DraggableMapFeature { ) : DraggableMapFeature {
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature = override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
MapPathFeature(rectangle.moveTo(newCoordinates), path, brush, style, targetRect, zoomRange) MapPathFeature(rectangle.moveTo(newCoordinates), path, brush, style, targetRect, zoomRange)
@ -92,7 +92,7 @@ public class MapPointsFeature(
public val stroke: Float = 2f, public val stroke: Float = 2f,
public val color: Color = Color.Red, public val color: Color = Color.Red,
public val pointMode: PointMode = PointMode.Points, public val pointMode: PointMode = PointMode.Points,
override val attributes: AttributeMap = AttributeMap(), override var attributes: AttributeMap = AttributeMap(),
) : MapFeature { ) : MapFeature {
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(points.first(), points.last()) override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(points.first(), points.last())
} }
@ -102,7 +102,7 @@ public data class MapCircleFeature(
override val zoomRange: IntRange = defaultZoomRange, override val zoomRange: IntRange = defaultZoomRange,
public val size: Float = 5f, public val size: Float = 5f,
public val color: Color = Color.Red, public val color: Color = Color.Red,
override val attributes: AttributeMap = AttributeMap(), override var attributes: AttributeMap = AttributeMap(),
) : DraggableMapFeature { ) : DraggableMapFeature {
override fun getBoundingBox(zoom: Double): GmcRectangle { override fun getBoundingBox(zoom: Double): GmcRectangle {
val scale = WebMercatorProjection.scaleFactor(zoom) val scale = WebMercatorProjection.scaleFactor(zoom)
@ -118,7 +118,7 @@ public class MapRectangleFeature(
override val zoomRange: IntRange = defaultZoomRange, override val zoomRange: IntRange = defaultZoomRange,
public val size: DpSize = DpSize(5.dp, 5.dp), public val size: DpSize = DpSize(5.dp, 5.dp),
public val color: Color = Color.Red, public val color: Color = Color.Red,
override val attributes: AttributeMap = AttributeMap(), override var attributes: AttributeMap = AttributeMap(),
) : DraggableMapFeature { ) : DraggableMapFeature {
override fun getBoundingBox(zoom: Double): GmcRectangle { override fun getBoundingBox(zoom: Double): GmcRectangle {
val scale = WebMercatorProjection.scaleFactor(zoom) val scale = WebMercatorProjection.scaleFactor(zoom)
@ -134,7 +134,7 @@ public class MapLineFeature(
public val b: GeodeticMapCoordinates, public val b: GeodeticMapCoordinates,
override val zoomRange: IntRange = defaultZoomRange, override val zoomRange: IntRange = defaultZoomRange,
public val color: Color = Color.Red, public val color: Color = Color.Red,
override val attributes: AttributeMap = AttributeMap(), override var attributes: AttributeMap = AttributeMap(),
) : SelectableMapFeature { ) : SelectableMapFeature {
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(a, b) override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(a, b)
@ -153,7 +153,7 @@ public class MapArcFeature(
public val arcLength: Angle, public val arcLength: Angle,
override val zoomRange: IntRange = defaultZoomRange, override val zoomRange: IntRange = defaultZoomRange,
public val color: Color = Color.Red, public val color: Color = Color.Red,
override val attributes: AttributeMap = AttributeMap(), override var attributes: AttributeMap = AttributeMap(),
) : DraggableMapFeature { ) : DraggableMapFeature {
override fun getBoundingBox(zoom: Double): GmcRectangle = oval override fun getBoundingBox(zoom: Double): GmcRectangle = oval
@ -166,7 +166,7 @@ public class MapBitmapImageFeature(
public val image: ImageBitmap, public val image: ImageBitmap,
public val size: IntSize = IntSize(15, 15), public val size: IntSize = IntSize(15, 15),
override val zoomRange: IntRange = defaultZoomRange, override val zoomRange: IntRange = defaultZoomRange,
override val attributes: AttributeMap = AttributeMap(), override var attributes: AttributeMap = AttributeMap(),
) : DraggableMapFeature { ) : DraggableMapFeature {
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position) override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position)
@ -179,7 +179,7 @@ public class MapVectorImageFeature(
public val image: ImageVector, public val image: ImageVector,
public val size: DpSize = DpSize(20.dp, 20.dp), public val size: DpSize = DpSize(20.dp, 20.dp),
override val zoomRange: IntRange = defaultZoomRange, override val zoomRange: IntRange = defaultZoomRange,
override val attributes: AttributeMap = AttributeMap(), override var attributes: AttributeMap = AttributeMap(),
) : DraggableMapFeature { ) : DraggableMapFeature {
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position) override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position)
@ -196,7 +196,7 @@ public class MapVectorImageFeature(
public class MapFeatureGroup( public class MapFeatureGroup(
public val children: Map<FeatureId<*>, MapFeature>, public val children: Map<FeatureId<*>, MapFeature>,
override val zoomRange: IntRange = defaultZoomRange, override val zoomRange: IntRange = defaultZoomRange,
override val attributes: AttributeMap = AttributeMap(), override var attributes: AttributeMap = AttributeMap(),
) : MapFeature { ) : MapFeature {
override fun getBoundingBox(zoom: Double): GmcRectangle? = override fun getBoundingBox(zoom: Double): GmcRectangle? =
children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapAll() children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
@ -207,7 +207,7 @@ public class MapTextFeature(
public val text: String, public val text: String,
override val zoomRange: IntRange = defaultZoomRange, override val zoomRange: IntRange = defaultZoomRange,
public val color: Color = Color.Black, public val color: Color = Color.Black,
override val attributes: AttributeMap = AttributeMap(), override var attributes: AttributeMap = AttributeMap(),
public val fontConfig: MapTextFeatureFont.() -> Unit, public val fontConfig: MapTextFeatureFont.() -> Unit,
) : DraggableMapFeature { ) : DraggableMapFeature {
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position) override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position)

View File

@ -18,10 +18,11 @@ import kotlinx.coroutines.launch
@JvmInline @JvmInline
public value class FeatureId<out MapFeature>(public val id: String) public value class FeatureId<out MapFeature>(public val id: String)
public class MapFeaturesState internal constructor( public class MapFeaturesState {
private val featureMap: MutableMap<String, MapFeature>,
//@PublishedApi internal val attributeMap: MutableMap<String, SnapshotStateMap<Attribute<out Any?>, in Any?>>, @PublishedApi
) { internal val featureMap: MutableMap<String, MapFeature> = mutableStateMapOf()
//TODO use context receiver for that //TODO use context receiver for that
public fun FeatureId<DraggableMapFeature>.draggable( public fun FeatureId<DraggableMapFeature>.draggable(
//TODO add constraints //TODO add constraints
@ -61,7 +62,8 @@ public class MapFeaturesState internal constructor(
} }
public fun features(): Map<FeatureId<*>, MapFeature> = featureMap.mapKeys { FeatureId<MapFeature>(it.key) } public val features: Map<FeatureId<*>, MapFeature>
get() = featureMap.mapKeys { FeatureId<MapFeature>(it.key) }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
public fun <T : MapFeature> getFeature(id: FeatureId<T>): T = featureMap[id.id] as T public fun <T : MapFeature> getFeature(id: FeatureId<T>): T = featureMap[id.id] as T
@ -77,18 +79,14 @@ public class MapFeaturesState internal constructor(
public fun <T : MapFeature> feature(id: FeatureId<T>?, feature: T): FeatureId<T> = feature(id?.id, feature) public fun <T : MapFeature> feature(id: FeatureId<T>?, feature: T): FeatureId<T> = feature(id?.id, feature)
public fun <T> setAttribute(id: FeatureId<*>, key: MapFeature.Attribute<T>, value: T) { public fun <T> setAttribute(id: FeatureId<MapFeature>, key: MapFeature.Attribute<T>, value: T?) {
feature(id,getFeature(id).at) getFeature(id).attributes.setAttribute(key, value)
attributeMap.getOrPut(id.id) { mutableStateMapOf() }[key] = value
}
public fun removeAttribute(id: FeatureId<*>, key: MapFeature.Attribute<*>) {
attributeMap[id.id]?.remove(key)
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
public fun <T> getAttribute(id: FeatureId<*>, key: MapFeature.Attribute<T>): T? = public fun <T> getAttribute(id: FeatureId<MapFeature>, key: MapFeature.Attribute<T>): T? =
attributeMap[id.id]?.get(key)?.let { it as T } getFeature(id).attributes[key]
// @Suppress("UNCHECKED_CAST") // @Suppress("UNCHECKED_CAST")
// public fun <T> findAllWithAttribute(key: Attribute<T>, condition: (T) -> Boolean): Set<FeatureId> { // public fun <T> findAllWithAttribute(key: Attribute<T>, condition: (T) -> Boolean): Set<FeatureId> {
@ -101,10 +99,9 @@ public class MapFeaturesState internal constructor(
key: MapFeature.Attribute<T>, key: MapFeature.Attribute<T>,
block: (id: FeatureId<*>, attributeValue: T) -> Unit, block: (id: FeatureId<*>, attributeValue: T) -> Unit,
) { ) {
attributeMap.forEach { (id, attributeMap) -> featureMap.forEach { (id, feature) ->
attributeMap[key]?.let { feature.attributes[key]?.let {
@Suppress("UNCHECKED_CAST") block(FeatureId<MapFeature>(id), it)
block(FeatureId<MapFeature>(id), it as T)
} }
} }
} }
@ -116,10 +113,7 @@ public class MapFeaturesState internal constructor(
*/ */
public fun build( public fun build(
builder: MapFeaturesState.() -> Unit = {}, builder: MapFeaturesState.() -> Unit = {},
): MapFeaturesState = MapFeaturesState( ): MapFeaturesState = MapFeaturesState().apply(builder)
mutableStateMapOf(),
mutableStateMapOf()
).apply(builder)
/** /**
* Build and remember map feature state * Build and remember map feature state
@ -178,8 +172,8 @@ public fun MapFeaturesState.draw(
position: Pair<Double, Double>, position: Pair<Double, Double>,
zoomRange: IntRange = defaultZoomRange, zoomRange: IntRange = defaultZoomRange,
id: String? = null, id: String? = null,
drawFeature: DrawScope.() -> Unit, draw: DrawScope.() -> Unit,
): FeatureId<MapDrawFeature> = feature(id, MapDrawFeature(position.toCoordinates(), zoomRange, drawFeature)) ): FeatureId<MapDrawFeature> = feature(id, MapDrawFeature(position.toCoordinates(), zoomRange, drawFeature = draw))
public fun MapFeaturesState.line( public fun MapFeaturesState.line(
aCoordinates: Gmc, aCoordinates: Gmc,
@ -278,10 +272,7 @@ public fun MapFeaturesState.group(
id: String? = null, id: String? = null,
builder: MapFeaturesState.() -> Unit, builder: MapFeaturesState.() -> Unit,
): FeatureId<MapFeatureGroup> { ): FeatureId<MapFeatureGroup> {
val map = MapFeaturesState( val map = MapFeaturesState().apply(builder).features
mutableStateMapOf(),
mutableStateMapOf()
).apply(builder).features()
val feature = MapFeatureGroup(map, zoomRange) val feature = MapFeatureGroup(map, zoomRange)
return feature(id, feature) return feature(id, feature)
} }
@ -293,7 +284,10 @@ public fun MapFeaturesState.text(
color: Color = Color.Red, color: Color = Color.Red,
font: MapTextFeatureFont.() -> Unit = { size = 16f }, font: MapTextFeatureFont.() -> Unit = { size = 16f },
id: String? = null, id: String? = null,
): FeatureId<MapTextFeature> = feature(id, MapTextFeature(position, text, zoomRange, color, font)) ): FeatureId<MapTextFeature> = feature(
id,
MapTextFeature(position, text, zoomRange, color, fontConfig = font)
)
public fun MapFeaturesState.text( public fun MapFeaturesState.text(
position: Pair<Double, Double>, position: Pair<Double, Double>,
@ -302,4 +296,7 @@ public fun MapFeaturesState.text(
color: Color = Color.Red, color: Color = Color.Red,
font: MapTextFeatureFont.() -> Unit = { size = 16f }, font: MapTextFeatureFont.() -> Unit = { size = 16f },
id: String? = null, id: String? = null,
): FeatureId<MapTextFeature> = feature(id, MapTextFeature(position.toCoordinates(), text, zoomRange, color, font)) ): FeatureId<MapTextFeature> = feature(
id,
MapTextFeature(position.toCoordinates(), text, zoomRange, color, fontConfig = font)
)

View File

@ -136,12 +136,10 @@ public fun MapView(
) { ) {
val featureState = MapFeaturesState.remember(buildFeatures) val featureState = MapFeaturesState.remember(buildFeatures)
val features = featureState.features()
val viewPointOverride: MapViewPoint = remember(initialViewPoint, initialRectangle) { val viewPointOverride: MapViewPoint = remember(initialViewPoint, initialRectangle) {
initialViewPoint initialViewPoint
?: initialRectangle?.computeViewPoint(mapTileProvider) ?: initialRectangle?.computeViewPoint(mapTileProvider)
?: features.values.computeBoundingBox(1.0)?.computeViewPoint(mapTileProvider) ?: featureState.features.values.computeBoundingBox(1.0)?.computeViewPoint(mapTileProvider)
?: MapViewPoint.globe ?: MapViewPoint.globe
} }

View File

@ -1,5 +1,6 @@
package center.sciprog.maps.compose package center.sciprog.maps.compose
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
public object DraggableAttribute : MapFeature.Attribute<DragHandle> public object DraggableAttribute : MapFeature.Attribute<DragHandle>
@ -8,16 +9,35 @@ public object VisibleAttribute : MapFeature.Attribute<Boolean>
public object ColorAttribute : MapFeature.Attribute<Color> public object ColorAttribute : MapFeature.Attribute<Color>
@JvmInline public class AttributeMap {
public value class AttributeMap internal constructor(private val map: Map<MapFeature.Attribute<*>, *>) { public val map: MutableMap<MapFeature.Attribute<*>, Any> = mutableStateMapOf()
public fun <T, A : MapFeature.Attribute<T>> withAttribute( public fun <T, A : MapFeature.Attribute<T>> setAttribute(
attribute: A, attribute: A,
value: T, attrValue: T?,
): AttributeMap = AttributeMap(map + (attribute to value)) ) {
if (attrValue == null) {
map.remove(attribute)
} else {
map[attribute] = attrValue
}
}
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
public operator fun <T> get(attribute: MapFeature.Attribute<T>): T? = map[attribute] as? T public operator fun <T> get(attribute: MapFeature.Attribute<T>): T? = map[attribute] as? T
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AttributeMap
if (map != other.map) return false
return true
} }
public fun AttributeMap(): AttributeMap = AttributeMap(emptyMap<MapFeature.Attribute<*>, Any?>()) override fun hashCode(): Int = map.hashCode()
override fun toString(): String = "AttributeMap(value=${map.entries})"
}

View File

@ -214,7 +214,7 @@ public actual fun MapView(
} }
val painterCache = key(featuresState) { val painterCache = key(featuresState) {
featuresState.features().values.filterIsInstance<MapVectorImageFeature>().associateWith { it.painter() } featuresState.features.values.filterIsInstance<MapVectorImageFeature>().associateWith { it.painter() }
} }
Canvas(canvasModifier) { Canvas(canvasModifier) {
@ -349,7 +349,7 @@ public actual fun MapView(
) )
} }
featuresState.features().values.filter { zoom in it.zoomRange }.forEach { feature -> featuresState.features.values.filter { zoom in it.zoomRange }.forEach { feature ->
drawFeature(zoom, feature) drawFeature(zoom, feature)
} }
} }