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