Fix feature update
This commit is contained in:
parent
62196fc6f5
commit
29074a9624
@ -168,7 +168,7 @@ fun App() {
|
|||||||
//Add click listeners for all polygons
|
//Add click listeners for all polygons
|
||||||
forEachWithType<Gmc, PolygonFeature<Gmc>> { ref, polygon: PolygonFeature<Gmc> ->
|
forEachWithType<Gmc, PolygonFeature<Gmc>> { ref, polygon: PolygonFeature<Gmc> ->
|
||||||
ref.onClick(PointerMatcher.Primary) {
|
ref.onClick(PointerMatcher.Primary) {
|
||||||
println("Click on ${ref.id}")
|
println("Click on $ref")
|
||||||
//draw in top-level scope
|
//draw in top-level scope
|
||||||
with(this@MapView) {
|
with(this@MapView) {
|
||||||
multiLine(
|
multiLine(
|
||||||
|
@ -55,6 +55,7 @@ fun App() {
|
|||||||
}
|
}
|
||||||
draggableMultiLine(
|
draggableMultiLine(
|
||||||
pointRefs + pointRefs.first(),
|
pointRefs + pointRefs.first(),
|
||||||
|
"line"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import androidx.compose.ui.unit.DpRect
|
|||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import space.kscience.attributes.Attributes
|
import space.kscience.attributes.Attributes
|
||||||
|
import space.kscience.attributes.plus
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An extension of [DrawScope] to include map-specific features
|
* An extension of [DrawScope] to include map-specific features
|
||||||
@ -96,12 +97,25 @@ public fun <T : Any> FeatureCanvas(
|
|||||||
if (state.canvasSize != size.toDpSize()) {
|
if (state.canvasSize != size.toDpSize()) {
|
||||||
state.canvasSize = size.toDpSize()
|
state.canvasSize = size.toDpSize()
|
||||||
}
|
}
|
||||||
ComposeFeatureDrawScope(this, state, painterCache, textMeasurer).apply(draw).apply {
|
|
||||||
clipRect {
|
clipRect {
|
||||||
features.values.sortedBy { it.z }
|
ComposeFeatureDrawScope(this, state, painterCache, textMeasurer).apply(draw).apply {
|
||||||
.filter { state.viewPoint.zoom in it.zoomRange }
|
|
||||||
.forEach { feature ->
|
val attributesCache = mutableMapOf<List<String>, Attributes>()
|
||||||
this@apply.drawFeature(feature)
|
|
||||||
|
fun computeGroupAttributes(path: List<String>): Attributes = attributesCache.getOrPut(path){
|
||||||
|
if (path.isEmpty()) return Attributes.EMPTY
|
||||||
|
else if (path.size == 1) {
|
||||||
|
features[path.first()]?.attributes ?: Attributes.EMPTY
|
||||||
|
} else {
|
||||||
|
computeGroupAttributes(path.dropLast(1)) + (features[path.first()]?.attributes ?: Attributes.EMPTY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
features.entries.sortedBy { it.value.z }
|
||||||
|
.filter { state.viewPoint.zoom in it.value.zoomRange }
|
||||||
|
.forEach { (id, feature) ->
|
||||||
|
val path = id.split("/")
|
||||||
|
drawFeature(feature, computeGroupAttributes(path.dropLast(1)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,16 +19,24 @@ import space.kscience.attributes.Attributes
|
|||||||
import space.kscience.kmath.geometry.Angle
|
import space.kscience.kmath.geometry.Angle
|
||||||
import space.kscience.kmath.nd.*
|
import space.kscience.kmath.nd.*
|
||||||
import space.kscience.kmath.structures.Buffer
|
import space.kscience.kmath.structures.Buffer
|
||||||
import space.kscience.maps.features.FeatureStore.Companion.generateId
|
import space.kscience.maps.features.FeatureStore.Companion.generateFeatureId
|
||||||
|
|
||||||
//@JvmInline
|
//@JvmInline
|
||||||
//public value class FeatureId<out F : Feature<*>>(public val id: String)
|
//public value class FeatureId<out F : Feature<*>>(public val id: String)
|
||||||
|
|
||||||
public class FeatureRef<T : Any, out F : Feature<T>>(public val store: FeatureStore<T>, public val id: String)
|
/**
|
||||||
|
* A reference to a feature inside a [FeatureStore]
|
||||||
|
*/
|
||||||
|
public class FeatureRef<T : Any, out F : Feature<T>> internal constructor(
|
||||||
|
internal val store: FeatureStore<T>,
|
||||||
|
internal val id: String,
|
||||||
|
) {
|
||||||
|
override fun toString(): String = "FeatureRef($id)"
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.resolve(): F =
|
public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.resolve(): F =
|
||||||
store.features[id]?.let { it as F } ?: error("Feature with id=$id not found")
|
store.features[id]?.let { it as F } ?: error("Feature with ref $this not found")
|
||||||
|
|
||||||
public val <T : Any, F : Feature<T>> FeatureRef<T, F>.attributes: Attributes get() = resolve().attributes
|
public val <T : Any, F : Feature<T>> FeatureRef<T, F>.attributes: Attributes get() = resolve().attributes
|
||||||
|
|
||||||
@ -36,8 +44,17 @@ public fun Uuid.toIndex(): String = leastSignificantBits.toString(16)
|
|||||||
|
|
||||||
public interface FeatureBuilder<T : Any> {
|
public interface FeatureBuilder<T : Any> {
|
||||||
public val space: CoordinateSpace<T>
|
public val space: CoordinateSpace<T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add or replace feature. If [id] is null, then a unique id is genertated
|
||||||
|
*/
|
||||||
public fun <F : Feature<T>> feature(id: String?, feature: F): FeatureRef<T, F>
|
public fun <F : Feature<T>> feature(id: String?, feature: F): FeatureRef<T, F>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update existing feature if it is present and is of type [F]
|
||||||
|
*/
|
||||||
|
public fun <F : Feature<T>> updateFeature(id: String, block: (F?) -> F): FeatureRef<T, F>
|
||||||
|
|
||||||
public fun group(
|
public fun group(
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
attributes: Attributes = Attributes.EMPTY,
|
attributes: Attributes = Attributes.EMPTY,
|
||||||
@ -67,17 +84,21 @@ public class FeatureStore<T : Any>(
|
|||||||
override val features: Map<String, Feature<T>> get() = featureFlow.value
|
override val features: Map<String, Feature<T>> get() = featureFlow.value
|
||||||
|
|
||||||
override fun <F : Feature<T>> feature(id: String?, feature: F): FeatureRef<T, F> {
|
override fun <F : Feature<T>> feature(id: String?, feature: F): FeatureRef<T, F> {
|
||||||
val safeId = id ?: generateId(feature)
|
val safeId = id ?: generateFeatureId(feature)
|
||||||
_featureFlow.value += (safeId to feature)
|
_featureFlow.value += (safeId to feature)
|
||||||
return FeatureRef(this, safeId)
|
return FeatureRef(this, safeId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <F : Feature<T>> updateFeature(id: String, block: (F?) -> F): FeatureRef<T, F> =
|
||||||
|
feature(id, block(features[id] as? F))
|
||||||
|
|
||||||
override fun group(
|
override fun group(
|
||||||
id: String?,
|
id: String?,
|
||||||
attributes: Attributes,
|
attributes: Attributes,
|
||||||
builder: FeatureGroup<T>.() -> Unit,
|
builder: FeatureGroup<T>.() -> Unit,
|
||||||
): FeatureRef<T, FeatureGroup<T>> {
|
): FeatureRef<T, FeatureGroup<T>> {
|
||||||
val safeId = id ?: generateId(null)
|
val safeId: String = id ?: generateFeatureId<FeatureGroup<*>>()
|
||||||
return feature(safeId, FeatureGroup(this, safeId, attributes).apply(builder))
|
return feature(safeId, FeatureGroup(this, safeId, attributes).apply(builder))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,11 +114,15 @@ public class FeatureStore<T : Any>(
|
|||||||
|
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
internal fun generateId(feature: Feature<*>?): String = if (feature == null) {
|
|
||||||
"@group[${uuid4().toIndex()}]"
|
internal fun generateFeatureId(prefix: String): String =
|
||||||
} else {
|
"$prefix[${uuid4().toIndex()}]"
|
||||||
"${feature::class.simpleName}[${uuid4().toIndex()}]"
|
|
||||||
}
|
internal fun generateFeatureId(feature: Feature<*>): String =
|
||||||
|
generateFeatureId(feature::class.simpleName ?: "undefined")
|
||||||
|
|
||||||
|
internal inline fun <reified F : Feature<*>> generateFeatureId(): String =
|
||||||
|
generateFeatureId(F::class.simpleName ?: "undefined")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build, but do not remember map feature state
|
* Build, but do not remember map feature state
|
||||||
@ -136,14 +161,18 @@ public data class FeatureGroup<T : Any> internal constructor(
|
|||||||
|
|
||||||
|
|
||||||
override fun <F : Feature<T>> feature(id: String?, feature: F): FeatureRef<T, F> =
|
override fun <F : Feature<T>> feature(id: String?, feature: F): FeatureRef<T, F> =
|
||||||
store.feature("$groupId/${id ?: generateId(feature)}", feature)
|
store.feature("$groupId/${id ?: generateFeatureId(feature)}", feature)
|
||||||
|
|
||||||
|
override fun <F : Feature<T>> updateFeature(id: String, block: (F?) -> F): FeatureRef<T, F> =
|
||||||
|
store.updateFeature("$groupId/$id", block)
|
||||||
|
|
||||||
|
|
||||||
override fun group(
|
override fun group(
|
||||||
id: String?,
|
id: String?,
|
||||||
attributes: Attributes,
|
attributes: Attributes,
|
||||||
builder: FeatureGroup<T>.() -> Unit,
|
builder: FeatureGroup<T>.() -> Unit,
|
||||||
): FeatureRef<T, FeatureGroup<T>> {
|
): FeatureRef<T, FeatureGroup<T>> {
|
||||||
val safeId = id ?: generateId(null)
|
val safeId = id ?: generateFeatureId<FeatureGroup<*>>()
|
||||||
return feature(safeId, FeatureGroup(store, "$groupId/$safeId", attributes).apply(builder))
|
return feature(safeId, FeatureGroup(store, "$groupId/$safeId", attributes).apply(builder))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,23 +9,15 @@ public fun <T : Any> FeatureBuilder<T>.draggableLine(
|
|||||||
bId: FeatureRef<T, MarkerFeature<T>>,
|
bId: FeatureRef<T, MarkerFeature<T>>,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureRef<T, LineFeature<T>> {
|
): FeatureRef<T, LineFeature<T>> {
|
||||||
var lineId: FeatureRef<T, LineFeature<T>>? = null
|
val lineId = id ?: FeatureStore.generateFeatureId<LineFeature<*>>()
|
||||||
|
|
||||||
fun drawLine(): FeatureRef<T, LineFeature<T>> {
|
fun drawLine(): FeatureRef<T, LineFeature<T>> = updateFeature(lineId) { old ->
|
||||||
val currentId = feature(
|
|
||||||
lineId?.id ?: id,
|
|
||||||
LineFeature(
|
LineFeature(
|
||||||
space,
|
space,
|
||||||
aId.resolve().center,
|
aId.resolve().center,
|
||||||
bId.resolve().center,
|
bId.resolve().center,
|
||||||
Attributes<FeatureGroup<T>> {
|
old?.attributes ?: Attributes(ZAttribute, -10f)
|
||||||
ZAttribute(-10f)
|
|
||||||
lineId?.attributes?.let { putAll(it) }
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
lineId = currentId
|
|
||||||
return currentId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
aId.draggable { _, _ ->
|
aId.draggable { _, _ ->
|
||||||
@ -43,22 +35,14 @@ public fun <T : Any> FeatureBuilder<T>.draggableMultiLine(
|
|||||||
points: List<FeatureRef<T, MarkerFeature<T>>>,
|
points: List<FeatureRef<T, MarkerFeature<T>>>,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureRef<T, MultiLineFeature<T>> {
|
): FeatureRef<T, MultiLineFeature<T>> {
|
||||||
var polygonId: FeatureRef<T, MultiLineFeature<T>>? = null
|
val polygonId = id ?: FeatureStore.generateFeatureId("multiline")
|
||||||
|
|
||||||
fun drawLines(): FeatureRef<T, MultiLineFeature<T>> {
|
fun drawLines(): FeatureRef<T, MultiLineFeature<T>> = updateFeature(polygonId) { old ->
|
||||||
val currentId = feature(
|
|
||||||
polygonId?.id ?: id,
|
|
||||||
MultiLineFeature(
|
MultiLineFeature(
|
||||||
space,
|
space,
|
||||||
points.map { it.resolve().center },
|
points.map { it.resolve().center },
|
||||||
Attributes<FeatureGroup<T>>{
|
old?.attributes ?: Attributes(ZAttribute, -10f)
|
||||||
ZAttribute(-10f)
|
|
||||||
polygonId?.attributes?.let { putAll(it) }
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
polygonId = currentId
|
|
||||||
return currentId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
points.forEach {
|
points.forEach {
|
||||||
|
@ -8,6 +8,7 @@ import androidx.compose.ui.graphics.Path
|
|||||||
import androidx.compose.ui.graphics.PointMode
|
import androidx.compose.ui.graphics.PointMode
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.graphics.drawscope.translate
|
import androidx.compose.ui.graphics.drawscope.translate
|
||||||
|
import space.kscience.attributes.Attributes
|
||||||
import space.kscience.attributes.plus
|
import space.kscience.attributes.plus
|
||||||
import space.kscience.kmath.PerformancePitfall
|
import space.kscience.kmath.PerformancePitfall
|
||||||
|
|
||||||
@ -20,14 +21,16 @@ import space.kscience.kmath.PerformancePitfall
|
|||||||
|
|
||||||
public fun <T : Any> FeatureDrawScope<T>.drawFeature(
|
public fun <T : Any> FeatureDrawScope<T>.drawFeature(
|
||||||
feature: Feature<T>,
|
feature: Feature<T>,
|
||||||
|
baseAttributes: Attributes,
|
||||||
): Unit {
|
): Unit {
|
||||||
val color = feature.color ?: Color.Red
|
val attributes = baseAttributes + feature.attributes
|
||||||
val alpha = feature.attributes[AlphaAttribute] ?: 1f
|
val color = attributes[ColorAttribute] ?: Color.Red
|
||||||
|
val alpha = attributes[AlphaAttribute] ?: 1f
|
||||||
//avoid drawing invisible features
|
//avoid drawing invisible features
|
||||||
if(feature.attributes[VisibleAttribute] == false) return
|
if(attributes[VisibleAttribute] == false) return
|
||||||
|
|
||||||
when (feature) {
|
when (feature) {
|
||||||
is FeatureSelector -> drawFeature(feature.selector(state.zoom))
|
is FeatureSelector -> drawFeature(feature.selector(state.zoom), attributes)
|
||||||
is CircleFeature -> drawCircle(
|
is CircleFeature -> drawCircle(
|
||||||
color,
|
color,
|
||||||
feature.radius.toPx(),
|
feature.radius.toPx(),
|
||||||
@ -49,8 +52,8 @@ public fun <T : Any> FeatureDrawScope<T>.drawFeature(
|
|||||||
color,
|
color,
|
||||||
feature.a.toOffset(),
|
feature.a.toOffset(),
|
||||||
feature.b.toOffset(),
|
feature.b.toOffset(),
|
||||||
strokeWidth = feature.attributes[StrokeAttribute] ?: Stroke.HairlineWidth,
|
strokeWidth = attributes[StrokeAttribute] ?: Stroke.HairlineWidth,
|
||||||
pathEffect = feature.attributes[PathEffectAttribute],
|
pathEffect = attributes[PathEffectAttribute],
|
||||||
alpha = alpha
|
alpha = alpha
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -84,7 +87,7 @@ public fun <T : Any> FeatureDrawScope<T>.drawFeature(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is TextFeature -> drawText(feature.text, feature.position.toOffset(), feature.attributes)
|
is TextFeature -> drawText(feature.text, feature.position.toOffset(), attributes)
|
||||||
|
|
||||||
is DrawFeature -> {
|
is DrawFeature -> {
|
||||||
val offset = feature.position.toOffset()
|
val offset = feature.position.toOffset()
|
||||||
@ -94,13 +97,14 @@ public fun <T : Any> FeatureDrawScope<T>.drawFeature(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is FeatureGroup -> {
|
is FeatureGroup -> {
|
||||||
feature.features.values.forEach {
|
//ignore groups
|
||||||
drawFeature(
|
// feature.features.values.forEach {
|
||||||
it.withAttributes {
|
// drawFeature(
|
||||||
feature.attributes + this
|
// it.withAttributes {
|
||||||
}
|
// feature.attributes + this
|
||||||
)
|
// }
|
||||||
}
|
// )
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
is PathFeature -> {
|
is PathFeature -> {
|
||||||
@ -117,9 +121,9 @@ public fun <T : Any> FeatureDrawScope<T>.drawFeature(
|
|||||||
drawPoints(
|
drawPoints(
|
||||||
points = points,
|
points = points,
|
||||||
color = color,
|
color = color,
|
||||||
strokeWidth = feature.attributes[StrokeAttribute] ?: 5f,
|
strokeWidth = attributes[StrokeAttribute] ?: 5f,
|
||||||
pointMode = PointMode.Points,
|
pointMode = PointMode.Points,
|
||||||
pathEffect = feature.attributes[PathEffectAttribute],
|
pathEffect = attributes[PathEffectAttribute],
|
||||||
alpha = alpha
|
alpha = alpha
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -129,9 +133,9 @@ public fun <T : Any> FeatureDrawScope<T>.drawFeature(
|
|||||||
drawPoints(
|
drawPoints(
|
||||||
points = points,
|
points = points,
|
||||||
color = color,
|
color = color,
|
||||||
strokeWidth = feature.attributes[StrokeAttribute] ?: Stroke.HairlineWidth,
|
strokeWidth = attributes[StrokeAttribute] ?: Stroke.HairlineWidth,
|
||||||
pointMode = PointMode.Polygon,
|
pointMode = PointMode.Polygon,
|
||||||
pathEffect = feature.attributes[PathEffectAttribute],
|
pathEffect = attributes[PathEffectAttribute],
|
||||||
alpha = alpha
|
alpha = alpha
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import androidx.compose.ui.unit.DpSize
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import org.jfree.svg.SVGGraphics2D
|
import org.jfree.svg.SVGGraphics2D
|
||||||
import org.jfree.svg.SVGUtils
|
import org.jfree.svg.SVGUtils
|
||||||
|
import space.kscience.attributes.Attributes
|
||||||
|
import space.kscience.attributes.plus
|
||||||
import space.kscience.maps.features.*
|
import space.kscience.maps.features.*
|
||||||
import space.kscience.maps.scheme.XY
|
import space.kscience.maps.scheme.XY
|
||||||
import space.kscience.maps.scheme.XYCanvasState
|
import space.kscience.maps.scheme.XYCanvasState
|
||||||
@ -160,10 +162,22 @@ public fun FeatureStateSnapshot<XY>.generateSvg(
|
|||||||
val svgScope = SvgDrawScope(svgCanvasState, svgGraphics2D, painterCache)
|
val svgScope = SvgDrawScope(svgCanvasState, svgGraphics2D, painterCache)
|
||||||
|
|
||||||
svgScope.apply {
|
svgScope.apply {
|
||||||
features.values.sortedBy { it.z }
|
features.entries.sortedBy { it.value.z }
|
||||||
.filter { state.viewPoint.zoom in it.zoomRange }
|
.filter { state.viewPoint.zoom in it.value.zoomRange }
|
||||||
.forEach { feature ->
|
.forEach { (id, feature) ->
|
||||||
this@apply.drawFeature(feature)
|
val attributesCache = mutableMapOf<List<String>, Attributes>()
|
||||||
|
|
||||||
|
fun computeGroupAttributes(path: List<String>): Attributes = attributesCache.getOrPut(path){
|
||||||
|
if (path.isEmpty()) return Attributes.EMPTY
|
||||||
|
else if (path.size == 1) {
|
||||||
|
features[path.first()]?.attributes ?: Attributes.EMPTY
|
||||||
|
} else {
|
||||||
|
computeGroupAttributes(path.dropLast(1)) + (features[path.first()]?.attributes ?: Attributes.EMPTY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val path = id.split("/")
|
||||||
|
drawFeature(feature, computeGroupAttributes(path.dropLast(1)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return svgGraphics2D.getSVGElement(id)
|
return svgGraphics2D.getSVGElement(id)
|
||||||
|
Loading…
Reference in New Issue
Block a user