Proper polygon contains check
This commit is contained in:
parent
ba962acf5c
commit
ea8c5571bb
@ -58,13 +58,15 @@ fun App() {
|
||||
mapTileProvider = mapTileProvider,
|
||||
config = ViewConfig(
|
||||
onViewChange = { centerCoordinates.value = focus },
|
||||
onClick = { _, viewPoint -> println(viewPoint) }
|
||||
onClick = { _, viewPoint ->
|
||||
println(viewPoint)
|
||||
}
|
||||
)
|
||||
) {
|
||||
|
||||
geoJson(URL("https://raw.githubusercontent.com/ggolikov/cities-comparison/master/src/moscow.geo.json"))
|
||||
.withAttribute(ColorAttribute, Color.Blue)
|
||||
.withAttribute(AlphaAttribute, 0.4f)
|
||||
.attribute(ColorAttribute, Color.Blue)
|
||||
.attribute(AlphaAttribute, 0.4f)
|
||||
|
||||
image(pointOne, Icons.Filled.Home)
|
||||
|
||||
@ -117,24 +119,19 @@ fun App() {
|
||||
}
|
||||
}.launchIn(scope)
|
||||
|
||||
visit { id, feature ->
|
||||
if (feature is PolygonFeature) {
|
||||
id as FeatureId<PolygonFeature<Gmc>>
|
||||
id.onClick {
|
||||
println("Click on $id")
|
||||
|
||||
forEachWithType<Gmc, PolygonFeature<Gmc>> { id, feature ->
|
||||
id.onClick {
|
||||
println("Click on $id")
|
||||
//draw in top-level scope
|
||||
with(this@MapView) {
|
||||
points(
|
||||
feature.points,
|
||||
stroke = 4f,
|
||||
pointMode = PointMode.Polygon,
|
||||
attributes = Attributes(ZAttribute, 10f),
|
||||
id = "selected",
|
||||
attributes = Attributes(ZAttribute, 10f)
|
||||
).color(Color.Blue)
|
||||
}
|
||||
id.onHover {
|
||||
println("Hover on $id")
|
||||
points(
|
||||
feature.points,
|
||||
id = "selected",
|
||||
attributes = Attributes(ZAttribute, 10f)
|
||||
).color(Color.Blue)
|
||||
).color(Color.Magenta)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -142,6 +139,7 @@ fun App() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun main() = application {
|
||||
Window(onCloseRequest = ::exitApplication) {
|
||||
App()
|
||||
|
@ -85,11 +85,19 @@ public object WebMercatorSpace : CoordinateSpace<Gmc> {
|
||||
}
|
||||
|
||||
override fun Gmc.isInsidePolygon(points: List<Gmc>): Boolean = points.zipWithNext().count { (left, right) ->
|
||||
val dist = right.latitude - left.latitude
|
||||
val intersection = left.latitude * abs((right.longitude - longitude) / dist) +
|
||||
right.latitude * abs((longitude - left.longitude) / dist)
|
||||
longitude in left.longitude..right.longitude && intersection >= latitude
|
||||
} % 2 == 0
|
||||
val longitudeRange = if(right.longitude >= left.longitude) {
|
||||
left.longitude..right.longitude
|
||||
} else {
|
||||
right.longitude..left.longitude
|
||||
}
|
||||
|
||||
if(longitude !in longitudeRange) return@count false
|
||||
|
||||
val longitudeDelta = right.longitude - left.longitude
|
||||
|
||||
left.latitude * abs((right.longitude - longitude) / longitudeDelta) +
|
||||
right.latitude * abs((longitude - left.longitude) / longitudeDelta) >= latitude
|
||||
} % 2 == 1
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -94,7 +94,7 @@ public actual fun MapView(
|
||||
}
|
||||
|
||||
|
||||
Canvas(modifier = modifier.mapControls(mapState, featuresState.features).fillMaxSize()) {
|
||||
Canvas(modifier = modifier.mapControls(mapState, featuresState).fillMaxSize()) {
|
||||
|
||||
if (canvasSize != size.toDpSize()) {
|
||||
logger.debug { "Recalculate canvas. Size: $size" }
|
||||
|
@ -49,12 +49,22 @@ public data class FeatureGroup<T : Any>(
|
||||
|
||||
public val features: Collection<Feature<T>> get() = featureMap.values.sortedByDescending { it.z }
|
||||
|
||||
public fun visit(visitor: FeatureGroup<T>.(id: FeatureId<Feature<T>>, feature: Feature<T>) -> Unit) {
|
||||
featureMap.forEach { (key, feature) ->
|
||||
public fun visit(visitor: FeatureGroup<T>.(id: String, feature: Feature<T>) -> Unit) {
|
||||
featureMap.entries.sortedByDescending { it.value.z }.forEach { (key, feature) ->
|
||||
if (feature is FeatureGroup<T>) {
|
||||
feature.visit(visitor)
|
||||
} else {
|
||||
visitor(this, FeatureId(key), feature)
|
||||
visitor(this, key, feature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun visitUntil(visitor: FeatureGroup<T>.(id: String, feature: Feature<T>) -> Boolean) {
|
||||
featureMap.entries.sortedByDescending { it.value.z }.forEach { (key, feature) ->
|
||||
if (feature is FeatureGroup<T>) {
|
||||
feature.visitUntil(visitor)
|
||||
} else {
|
||||
if (!visitor(this, key, feature)) return@forEach
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,30 +73,12 @@ public data class FeatureGroup<T : Any>(
|
||||
public fun <A> getAttribute(id: FeatureId<Feature<T>>, key: Attribute<A>): A? =
|
||||
get(id).attributes[key]
|
||||
|
||||
/**
|
||||
* Process all features with a given attribute from the one with highest [z] to lowest
|
||||
*/
|
||||
public inline fun <A> forEachWithAttribute(
|
||||
key: Attribute<A>,
|
||||
block: (id: FeatureId<*>, feature: Feature<T>, attributeValue: A) -> Unit,
|
||||
) {
|
||||
featureMap.entries.sortedByDescending { it.value.z }.forEach { (id, feature) ->
|
||||
feature.attributes[key]?.let {
|
||||
block(FeatureId<Feature<T>>(id), feature, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun <F : Feature<T>, V> FeatureId<F>.modifyAttributes(modify: Attributes.() -> Attributes) {
|
||||
feature(this, get(this).withAttributes(modify))
|
||||
}
|
||||
|
||||
public fun <F : Feature<T>> FeatureId<F>.withAttributes(modify: Attributes.() -> Attributes): FeatureId<F> {
|
||||
public fun <F : Feature<T>> FeatureId<F>.modifyAttributes(modify: Attributes.() -> Attributes): FeatureId<F> {
|
||||
feature(this, get(this).withAttributes(modify))
|
||||
return this
|
||||
}
|
||||
|
||||
public fun <F : Feature<T>, V> FeatureId<F>.withAttribute(key: Attribute<V>, value: V?): FeatureId<F> {
|
||||
public fun <F : Feature<T>, V> FeatureId<F>.attribute(key: Attribute<V>, value: V?): FeatureId<F> {
|
||||
feature(this, get(this).withAttributes { withAttribute(key, value) })
|
||||
return this
|
||||
}
|
||||
@ -120,7 +112,7 @@ public data class FeatureGroup<T : Any>(
|
||||
DragResult(end, true)
|
||||
}
|
||||
}
|
||||
this.withAttribute(DraggableAttribute, handle)
|
||||
this.attribute(DraggableAttribute, handle)
|
||||
}
|
||||
|
||||
//Apply callback
|
||||
@ -134,7 +126,7 @@ public data class FeatureGroup<T : Any>(
|
||||
public fun FeatureId<DraggableFeature<T>>.onDrag(
|
||||
listener: PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit,
|
||||
) {
|
||||
withAttribute(
|
||||
attribute(
|
||||
DragListenerAttribute,
|
||||
(getAttribute(this, DragListenerAttribute) ?: emptySet()) +
|
||||
DragListener { event, from, to ->
|
||||
@ -147,7 +139,7 @@ public data class FeatureGroup<T : Any>(
|
||||
public fun <F : DomainFeature<T>> FeatureId<F>.onClick(
|
||||
onClick: PointerEvent.(click: ViewPoint<T>) -> Unit,
|
||||
) {
|
||||
withAttribute(
|
||||
attribute(
|
||||
ClickListenerAttribute,
|
||||
(getAttribute(this, ClickListenerAttribute) ?: emptySet()) +
|
||||
MouseListener { event, point -> event.onClick(point as ViewPoint<T>) }
|
||||
@ -158,7 +150,7 @@ public data class FeatureGroup<T : Any>(
|
||||
public fun <F : DomainFeature<T>> FeatureId<F>.onHover(
|
||||
onClick: PointerEvent.(move: ViewPoint<T>) -> Unit,
|
||||
) {
|
||||
withAttribute(
|
||||
attribute(
|
||||
HoverListenerAttribute,
|
||||
(getAttribute(this, HoverListenerAttribute) ?: emptySet()) +
|
||||
MouseListener { event, point -> event.onClick(point as ViewPoint<T>) }
|
||||
@ -178,10 +170,10 @@ public data class FeatureGroup<T : Any>(
|
||||
// }
|
||||
|
||||
public fun <F : Feature<T>> FeatureId<F>.color(color: Color): FeatureId<F> =
|
||||
withAttribute(ColorAttribute, color)
|
||||
attribute(ColorAttribute, color)
|
||||
|
||||
public fun <F : Feature<T>> FeatureId<F>.zoomRange(range: FloatRange): FeatureId<F> =
|
||||
withAttribute(ZoomRangeAttribute, range)
|
||||
attribute(ZoomRangeAttribute, range)
|
||||
|
||||
override fun getBoundingBox(zoom: Float): Rectangle<T>? = with(space) {
|
||||
featureMap.values.mapNotNull { it.getBoundingBox(zoom) }.wrapRectangles()
|
||||
@ -213,6 +205,47 @@ public data class FeatureGroup<T : Any>(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process all features with a given attribute from the one with highest [z] to lowest
|
||||
*/
|
||||
public fun <T : Any, A> FeatureGroup<T>.forEachWithAttribute(
|
||||
key: Attribute<A>,
|
||||
block: FeatureGroup<T>.(id: String, feature: Feature<T>, attributeValue: A) -> Unit,
|
||||
) {
|
||||
visit { id, feature ->
|
||||
feature.attributes[key]?.let {
|
||||
block(id, feature, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T : Any, A> FeatureGroup<T>.forEachWithAttributeUntil(
|
||||
key: Attribute<A>,
|
||||
block: FeatureGroup<T>.(id: String, feature: Feature<T>, attributeValue: A) -> Boolean,
|
||||
) {
|
||||
visitUntil { id, feature ->
|
||||
feature.attributes[key]?.let {
|
||||
block(id, feature, it)
|
||||
} ?: true
|
||||
}
|
||||
}
|
||||
|
||||
public inline fun <T : Any, reified F : Feature<T>> FeatureGroup<T>.forEachWithType(
|
||||
crossinline block: FeatureGroup<T>.(FeatureId<F>, feature: F) -> Unit,
|
||||
) {
|
||||
visit { id, feature ->
|
||||
if (feature is F) block(FeatureId(id), feature)
|
||||
}
|
||||
}
|
||||
|
||||
public inline fun <T : Any, reified F : Feature<T>> FeatureGroup<T>.forEachWithTypeUntil(
|
||||
crossinline block: FeatureGroup<T>.(FeatureId<F>, feature: F) -> Boolean,
|
||||
) {
|
||||
visitUntil { id, feature ->
|
||||
if (feature is F) block(FeatureId(id), feature) else true
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T : Any> FeatureGroup<T>.circle(
|
||||
center: T,
|
||||
size: Dp = 5.dp,
|
||||
|
@ -8,12 +8,17 @@ public fun <T : Any> FeatureGroup<T>.draggableLine(
|
||||
): FeatureId<LineFeature<T>> {
|
||||
var lineId: FeatureId<LineFeature<T>>? = null
|
||||
|
||||
fun drawLine(): FeatureId<LineFeature<T>> = line(
|
||||
get(aId).center,
|
||||
get(bId).center,
|
||||
lineId?.id ?: id
|
||||
).also {
|
||||
lineId = it
|
||||
fun drawLine(): FeatureId<LineFeature<T>> {
|
||||
//save attributes before update
|
||||
val attributes: Attributes? = lineId?.let(::get)?.attributes
|
||||
val currentId = line(
|
||||
get(aId).center,
|
||||
get(bId).center,
|
||||
lineId?.id ?: id
|
||||
)
|
||||
if (attributes != null) currentId.modifyAttributes { attributes.withAttribute(ZAttribute, -10f) }
|
||||
lineId = currentId
|
||||
return currentId
|
||||
}
|
||||
|
||||
aId.draggable { _, _ ->
|
||||
|
@ -18,7 +18,7 @@ import kotlin.math.min
|
||||
*/
|
||||
public fun <T : Any> Modifier.mapControls(
|
||||
state: CoordinateViewScope<T>,
|
||||
features: Collection<Feature<T>>,
|
||||
features: FeatureGroup<T>,
|
||||
): Modifier = with(state) {
|
||||
pointerInput(Unit) {
|
||||
fun Offset.toDpOffset() = DpOffset(x.toDp(), y.toDp())
|
||||
@ -29,11 +29,10 @@ public fun <T : Any> Modifier.mapControls(
|
||||
val point = space.ViewPoint(coordinates, zoom)
|
||||
|
||||
if (event.type == PointerEventType.Move) {
|
||||
for (feature in features) {
|
||||
val listeners = (feature as? DomainFeature)?.attributes?.get(HoverListenerAttribute)
|
||||
if (listeners != null && point in feature) {
|
||||
features.forEachWithAttribute(HoverListenerAttribute) { _, feature, listeners ->
|
||||
if (point in feature as DomainFeature) {
|
||||
listeners.forEach { it.handle(event, point) }
|
||||
break
|
||||
return@forEachWithAttribute
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -42,11 +41,10 @@ public fun <T : Any> Modifier.mapControls(
|
||||
event,
|
||||
point
|
||||
)
|
||||
for (feature in features) {
|
||||
val listeners = (feature as? DomainFeature)?.attributes?.get(ClickListenerAttribute)
|
||||
if (listeners != null && point in feature) {
|
||||
features.forEachWithAttribute(ClickListenerAttribute) { _, feature, listeners ->
|
||||
if (point in feature as DomainFeature) {
|
||||
listeners.forEach { it.handle(event, point) }
|
||||
break
|
||||
return@forEachWithAttribute
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -96,13 +94,9 @@ public fun <T : Any> Modifier.mapControls(
|
||||
val dragResult = config.dragHandle?.handle(event, dragStart, dragEnd)
|
||||
if (dragResult?.handleNext == false) return@drag
|
||||
|
||||
features.asSequence()
|
||||
.filterIsInstance<DraggableFeature<T>>()
|
||||
.mapNotNull {
|
||||
it.attributes[DraggableAttribute]
|
||||
}.forEach { handler ->
|
||||
if (!handler.handle(event, dragStart, dragEnd).handleNext) return@drag
|
||||
}
|
||||
features.forEachWithAttributeUntil(DraggableAttribute) { _, _, handler ->
|
||||
handler.handle(event, dragStart, dragEnd).handleNext
|
||||
}
|
||||
}
|
||||
|
||||
if (event.buttons.isPrimaryPressed) {
|
||||
|
@ -73,9 +73,17 @@ object XYCoordinateSpace : CoordinateSpace<XY> {
|
||||
)
|
||||
|
||||
override fun XY.isInsidePolygon(points: List<XY>): Boolean = points.zipWithNext().count { (left, right) ->
|
||||
val dist = right.y - left.y
|
||||
val intersection = left.y * abs((right.x - x) / dist) +
|
||||
right.y * abs((x - left.x) / dist)
|
||||
x in left.x..right.x && intersection >= y
|
||||
} % 2 == 0
|
||||
val yRange = if(right.x >= left.x) {
|
||||
left.y..right.y
|
||||
} else {
|
||||
right.y..left.y
|
||||
}
|
||||
|
||||
if(y !in yRange) return@count false
|
||||
|
||||
val longitudeDelta = right.y - left.y
|
||||
|
||||
left.x * abs((right.y - y) / longitudeDelta) +
|
||||
right.x * abs((y - left.y) / longitudeDelta) >= x
|
||||
} % 2 == 1
|
||||
}
|
Loading…
Reference in New Issue
Block a user