[WIP] Generic features
This commit is contained in:
parent
0d9efadcb8
commit
7735d667bc
@ -1,6 +1,6 @@
|
|||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
|
|
||||||
compose.version=1.2.1
|
compose.version=1.2.2
|
||||||
|
|
||||||
agp.version=4.2.2
|
agp.version=4.2.2
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import org.jetbrains.compose.compose
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("multiplatform")
|
kotlin("multiplatform")
|
||||||
id("org.jetbrains.compose")
|
id("org.jetbrains.compose")
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
package center.sciprog.maps.compose
|
||||||
|
|
||||||
|
import androidx.compose.ui.input.pointer.PointerEvent
|
||||||
|
import androidx.compose.ui.input.pointer.isPrimaryPressed
|
||||||
|
import center.sciprog.maps.coordinates.MapViewPoint
|
||||||
|
|
||||||
|
public fun interface DragHandle {
|
||||||
|
/**
|
||||||
|
* @param event - qualifiers of the event used for drag
|
||||||
|
* @param start - is a point where drag begins, end is a point where drag ends
|
||||||
|
* @param end - end point of the drag
|
||||||
|
*
|
||||||
|
* @return true if default event processors should be used after this one
|
||||||
|
*/
|
||||||
|
public fun handle(event: PointerEvent, start: MapViewPoint, end: MapViewPoint): Boolean
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
public val BYPASS: DragHandle = DragHandle { _, _, _ -> true }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process only events with primary button pressed
|
||||||
|
*/
|
||||||
|
public fun withPrimaryButton(
|
||||||
|
block: (event: PointerEvent, start: MapViewPoint, end: MapViewPoint) -> Boolean,
|
||||||
|
): DragHandle = DragHandle { event, start, end ->
|
||||||
|
if (event.buttons.isPrimaryPressed) {
|
||||||
|
block(event, start, end)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine several handles into one
|
||||||
|
*/
|
||||||
|
public fun combine(vararg handles: DragHandle): DragHandle = DragHandle { event, start, end ->
|
||||||
|
handles.forEach {
|
||||||
|
if (!it.handle(event, start, end)) return@DragHandle false
|
||||||
|
}
|
||||||
|
return@DragHandle true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,7 @@ import kotlin.math.floor
|
|||||||
public interface MapFeature {
|
public interface MapFeature {
|
||||||
public interface Attribute<T>
|
public interface Attribute<T>
|
||||||
|
|
||||||
public val zoomRange: IntRange
|
public val zoomRange: ClosedFloatingPointRange<Double>
|
||||||
|
|
||||||
public var attributes: AttributeMap
|
public var attributes: AttributeMap
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ public fun Iterable<MapFeature>.computeBoundingBox(zoom: Double): GmcRectangle?
|
|||||||
public fun Pair<Number, Number>.toCoordinates(): GeodeticMapCoordinates =
|
public fun Pair<Number, Number>.toCoordinates(): GeodeticMapCoordinates =
|
||||||
GeodeticMapCoordinates.ofDegrees(first.toDouble(), second.toDouble())
|
GeodeticMapCoordinates.ofDegrees(first.toDouble(), second.toDouble())
|
||||||
|
|
||||||
internal val defaultZoomRange = 1..18
|
internal val defaultZoomRange = 1.0..Double.POSITIVE_INFINITY
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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)
|
||||||
@ -50,14 +50,14 @@ public class MapFeatureSelector(
|
|||||||
override var 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: ClosedFloatingPointRange<Double> get() = defaultZoomRange
|
||||||
|
|
||||||
override fun getBoundingBox(zoom: Double): GmcRectangle? = selector(floor(zoom).toInt()).getBoundingBox(zoom)
|
override fun getBoundingBox(zoom: Double): GmcRectangle? = selector(floor(zoom).toInt()).getBoundingBox(zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MapDrawFeature(
|
public class MapDrawFeature(
|
||||||
public val position: GeodeticMapCoordinates,
|
public val position: GeodeticMapCoordinates,
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
override var attributes: AttributeMap = AttributeMap(),
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
public val drawFeature: DrawScope.() -> Unit,
|
public val drawFeature: DrawScope.() -> Unit,
|
||||||
) : DraggableMapFeature {
|
) : DraggableMapFeature {
|
||||||
@ -76,7 +76,7 @@ public class MapPathFeature(
|
|||||||
public val brush: Brush,
|
public val brush: Brush,
|
||||||
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: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
override var attributes: AttributeMap = AttributeMap(),
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
) : DraggableMapFeature {
|
) : DraggableMapFeature {
|
||||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
||||||
@ -88,7 +88,7 @@ public class MapPathFeature(
|
|||||||
|
|
||||||
public class MapPointsFeature(
|
public class MapPointsFeature(
|
||||||
public val points: List<GeodeticMapCoordinates>,
|
public val points: List<GeodeticMapCoordinates>,
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
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,
|
||||||
@ -99,7 +99,7 @@ public class MapPointsFeature(
|
|||||||
|
|
||||||
public data class MapCircleFeature(
|
public data class MapCircleFeature(
|
||||||
public val center: GeodeticMapCoordinates,
|
public val center: GeodeticMapCoordinates,
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
public val size: Float = 5f,
|
public val size: Float = 5f,
|
||||||
public val color: Color = Color.Red,
|
public val color: Color = Color.Red,
|
||||||
override var attributes: AttributeMap = AttributeMap(),
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
@ -115,7 +115,7 @@ public data class MapCircleFeature(
|
|||||||
|
|
||||||
public class MapRectangleFeature(
|
public class MapRectangleFeature(
|
||||||
public val center: GeodeticMapCoordinates,
|
public val center: GeodeticMapCoordinates,
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
override val zoomRange: ClosedFloatingPointRange<Double> = 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 var attributes: AttributeMap = AttributeMap(),
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
@ -132,7 +132,7 @@ public class MapRectangleFeature(
|
|||||||
public class MapLineFeature(
|
public class MapLineFeature(
|
||||||
public val a: GeodeticMapCoordinates,
|
public val a: GeodeticMapCoordinates,
|
||||||
public val b: GeodeticMapCoordinates,
|
public val b: GeodeticMapCoordinates,
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
public val color: Color = Color.Red,
|
public val color: Color = Color.Red,
|
||||||
override var attributes: AttributeMap = AttributeMap(),
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
) : SelectableMapFeature {
|
) : SelectableMapFeature {
|
||||||
@ -151,7 +151,7 @@ public class MapArcFeature(
|
|||||||
public val oval: GmcRectangle,
|
public val oval: GmcRectangle,
|
||||||
public val startAngle: Angle,
|
public val startAngle: Angle,
|
||||||
public val arcLength: Angle,
|
public val arcLength: Angle,
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
public val color: Color = Color.Red,
|
public val color: Color = Color.Red,
|
||||||
override var attributes: AttributeMap = AttributeMap(),
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
) : DraggableMapFeature {
|
) : DraggableMapFeature {
|
||||||
@ -165,7 +165,7 @@ public class MapBitmapImageFeature(
|
|||||||
public val position: GeodeticMapCoordinates,
|
public val position: GeodeticMapCoordinates,
|
||||||
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: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
override var 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)
|
||||||
@ -178,7 +178,7 @@ public class MapVectorImageFeature(
|
|||||||
public val position: GeodeticMapCoordinates,
|
public val position: GeodeticMapCoordinates,
|
||||||
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: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
override var 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)
|
||||||
@ -195,7 +195,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: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
override var attributes: AttributeMap = AttributeMap(),
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
) : MapFeature {
|
) : MapFeature {
|
||||||
override fun getBoundingBox(zoom: Double): GmcRectangle? =
|
override fun getBoundingBox(zoom: Double): GmcRectangle? =
|
||||||
@ -205,7 +205,7 @@ public class MapFeatureGroup(
|
|||||||
public class MapTextFeature(
|
public class MapTextFeature(
|
||||||
public val position: GeodeticMapCoordinates,
|
public val position: GeodeticMapCoordinates,
|
||||||
public val text: String,
|
public val text: String,
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
public val color: Color = Color.Black,
|
public val color: Color = Color.Black,
|
||||||
override var attributes: AttributeMap = AttributeMap(),
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
public val fontConfig: MapTextFeatureFont.() -> Unit,
|
public val fontConfig: MapTextFeatureFont.() -> Unit,
|
||||||
|
@ -6,7 +6,6 @@ import androidx.compose.runtime.key
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.pointer.PointerEvent
|
import androidx.compose.ui.input.pointer.PointerEvent
|
||||||
import androidx.compose.ui.input.pointer.isPrimaryPressed
|
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import center.sciprog.maps.coordinates.*
|
import center.sciprog.maps.coordinates.*
|
||||||
@ -15,44 +14,6 @@ import kotlin.math.log2
|
|||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
|
||||||
public fun interface DragHandle {
|
|
||||||
/**
|
|
||||||
* @param event - qualifiers of the event used for drag
|
|
||||||
* @param start - is a point where drag begins, end is a point where drag ends
|
|
||||||
* @param end - end point of the drag
|
|
||||||
*
|
|
||||||
* @return true if default event processors should be used after this one
|
|
||||||
*/
|
|
||||||
public fun handle(event: PointerEvent, start: MapViewPoint, end: MapViewPoint): Boolean
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
public val BYPASS: DragHandle = DragHandle { _, _, _ -> true }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process only events with primary button pressed
|
|
||||||
*/
|
|
||||||
public fun withPrimaryButton(
|
|
||||||
block: (event: PointerEvent, start: MapViewPoint, end: MapViewPoint) -> Boolean,
|
|
||||||
): DragHandle = DragHandle { event, start, end ->
|
|
||||||
if (event.buttons.isPrimaryPressed) {
|
|
||||||
block(event, start, end)
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Combine several handles into one
|
|
||||||
*/
|
|
||||||
public fun combine(vararg handles: DragHandle): DragHandle = DragHandle { event, start, end ->
|
|
||||||
handles.forEach {
|
|
||||||
if (!it.handle(event, start, end)) return@DragHandle false
|
|
||||||
}
|
|
||||||
return@DragHandle true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO consider replacing by modifier
|
//TODO consider replacing by modifier
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
|
36
maps-kt-features/build.gradle.kts
Normal file
36
maps-kt-features/build.gradle.kts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("multiplatform")
|
||||||
|
id("org.jetbrains.compose")
|
||||||
|
`maven-publish`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
explicitApi = org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode.Warning
|
||||||
|
jvm {
|
||||||
|
compilations.all {
|
||||||
|
kotlinOptions.jvmTarget = space.kscience.gradle.KScienceVersions.JVM_TARGET.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
api(compose.foundation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val jvmTest by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(kotlin("test-junit5"))
|
||||||
|
implementation("org.junit.jupiter:junit-jupiter:5.8.2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
targetCompatibility = space.kscience.gradle.KScienceVersions.JVM_TARGET
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<Test> {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018-2021 KMath contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package center.sciprog.maps.features
|
||||||
|
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.floor
|
||||||
|
|
||||||
|
// Taken from KMath dev version, to be used directly in the future
|
||||||
|
|
||||||
|
|
||||||
|
public sealed interface Angle : Comparable<Angle> {
|
||||||
|
public val radians: Radians
|
||||||
|
public val degrees: Degrees
|
||||||
|
|
||||||
|
public operator fun plus(other: Angle): Angle
|
||||||
|
public operator fun minus(other: Angle): Angle
|
||||||
|
|
||||||
|
public operator fun times(other: Number): Angle
|
||||||
|
public operator fun div(other: Number): Angle
|
||||||
|
public operator fun div(other: Angle): Double
|
||||||
|
public operator fun unaryMinus(): Angle
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
public val zero: Angle = 0.radians
|
||||||
|
public val pi: Angle = PI.radians
|
||||||
|
public val piTimes2: Angle = (2 * PI).radians
|
||||||
|
public val piDiv2: Angle = (PI / 2).radians
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type safe radians
|
||||||
|
*/
|
||||||
|
@JvmInline
|
||||||
|
public value class Radians(public val value: Double) : Angle {
|
||||||
|
override val radians: Radians
|
||||||
|
get() = this
|
||||||
|
override val degrees: Degrees
|
||||||
|
get() = Degrees(value * 180 / PI)
|
||||||
|
|
||||||
|
public override fun plus(other: Angle): Radians = Radians(value + other.radians.value)
|
||||||
|
public override fun minus(other: Angle): Radians = Radians(value - other.radians.value)
|
||||||
|
|
||||||
|
public override fun times(other: Number): Radians = Radians(value * other.toDouble())
|
||||||
|
public override fun div(other: Number): Radians = Radians(value / other.toDouble())
|
||||||
|
override fun div(other: Angle): Double = value / other.radians.value
|
||||||
|
public override fun unaryMinus(): Radians = Radians(-value)
|
||||||
|
|
||||||
|
override fun compareTo(other: Angle): Int = value.compareTo(other.radians.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun sin(angle: Angle): Double = kotlin.math.sin(angle.radians.value)
|
||||||
|
public fun cos(angle: Angle): Double = kotlin.math.cos(angle.radians.value)
|
||||||
|
public fun tan(angle: Angle): Double = kotlin.math.tan(angle.radians.value)
|
||||||
|
|
||||||
|
public val Number.radians: Radians get() = Radians(toDouble())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type safe degrees
|
||||||
|
*/
|
||||||
|
@JvmInline
|
||||||
|
public value class Degrees(public val value: Double) : Angle {
|
||||||
|
override val radians: Radians
|
||||||
|
get() = Radians(value * PI / 180)
|
||||||
|
override val degrees: Degrees
|
||||||
|
get() = this
|
||||||
|
|
||||||
|
public override fun plus(other: Angle): Degrees = Degrees(value + other.degrees.value)
|
||||||
|
public override fun minus(other: Angle): Degrees = Degrees(value - other.degrees.value)
|
||||||
|
|
||||||
|
public override fun times(other: Number): Degrees = Degrees(value * other.toDouble())
|
||||||
|
public override fun div(other: Number): Degrees = Degrees(value / other.toDouble())
|
||||||
|
override fun div(other: Angle): Double = value / other.degrees.value
|
||||||
|
public override fun unaryMinus(): Degrees = Degrees(-value)
|
||||||
|
|
||||||
|
override fun compareTo(other: Angle): Int = value.compareTo(other.degrees.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public val Number.degrees: Degrees get() = Degrees(toDouble())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalized angle 2 PI range symmetric around [center]. By default, uses (0, 2PI) range.
|
||||||
|
*/
|
||||||
|
public fun Angle.normalized(center: Angle = Angle.pi): Angle =
|
||||||
|
this - Angle.piTimes2 * floor((radians.value + PI - center.radians.value) / PI/2)
|
||||||
|
|
||||||
|
public fun abs(angle: Angle): Angle = if (angle < Angle.zero) -angle else angle
|
||||||
|
|
||||||
|
public fun Radians.toFloat(): Float = value.toFloat()
|
||||||
|
|
||||||
|
public fun Degrees.toFloat(): Float = value.toFloat()
|
@ -0,0 +1,43 @@
|
|||||||
|
package center.sciprog.maps.features
|
||||||
|
|
||||||
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
public object DraggableAttribute : Feature.Attribute<DragHandle<*>>
|
||||||
|
public object SelectableAttribute : Feature.Attribute<(FeatureId<*>, SelectableFeature<*>) -> Unit>
|
||||||
|
public object VisibleAttribute : Feature.Attribute<Boolean>
|
||||||
|
|
||||||
|
public object ColorAttribute : Feature.Attribute<Color>
|
||||||
|
|
||||||
|
public class AttributeMap {
|
||||||
|
public val map: MutableMap<Feature.Attribute<*>, Any> = mutableStateMapOf()
|
||||||
|
|
||||||
|
public fun <T, A : Feature.Attribute<T>> setAttribute(
|
||||||
|
attribute: A,
|
||||||
|
attrValue: T?,
|
||||||
|
) {
|
||||||
|
if (attrValue == null) {
|
||||||
|
map.remove(attribute)
|
||||||
|
} else {
|
||||||
|
map[attribute] = attrValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
public operator fun <T> get(attribute: Feature.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
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int = map.hashCode()
|
||||||
|
|
||||||
|
override fun toString(): String = "AttributeMap(value=${map.entries})"
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package center.sciprog.maps.features
|
||||||
|
|
||||||
|
import androidx.compose.ui.input.pointer.PointerEvent
|
||||||
|
import androidx.compose.ui.input.pointer.isPrimaryPressed
|
||||||
|
|
||||||
|
public fun interface DragHandle<in V: ViewPoint<*>> {
|
||||||
|
/**
|
||||||
|
* @param event - qualifiers of the event used for drag
|
||||||
|
* @param start - is a point where drag begins, end is a point where drag ends
|
||||||
|
* @param end - end point of the drag
|
||||||
|
*
|
||||||
|
* @return true if default event processors should be used after this one
|
||||||
|
*/
|
||||||
|
public fun handle(event: PointerEvent, start: V, end: V): Boolean
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
public val BYPASS: DragHandle<*> = DragHandle<ViewPoint<*>> { _, _, _ -> true }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process only events with primary button pressed
|
||||||
|
*/
|
||||||
|
public fun <V> withPrimaryButton(
|
||||||
|
block: (event: PointerEvent, start: V, end: V) -> Boolean,
|
||||||
|
): DragHandle<V> = DragHandle { event, start, end ->
|
||||||
|
if (event.buttons.isPrimaryPressed) {
|
||||||
|
block(event, start, end)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine several handles into one
|
||||||
|
*/
|
||||||
|
public fun <V> combine(vararg handles: DragHandle<V>): DragHandle<V> = DragHandle { event, start, end ->
|
||||||
|
handles.forEach {
|
||||||
|
if (!it.handle(event, start, end)) return@DragHandle false
|
||||||
|
}
|
||||||
|
return@DragHandle true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,232 @@
|
|||||||
|
package center.sciprog.maps.features
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.geometry.Rect
|
||||||
|
import androidx.compose.ui.graphics.*
|
||||||
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
|
import androidx.compose.ui.graphics.drawscope.DrawStyle
|
||||||
|
import androidx.compose.ui.graphics.drawscope.Fill
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.graphics.vector.VectorPainter
|
||||||
|
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.IntSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlin.math.floor
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param T type of coordinates used for the view point
|
||||||
|
*/
|
||||||
|
public interface ViewPoint<T: Any> {
|
||||||
|
public val focus: T
|
||||||
|
public val zoom: Double
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Rectangle<T: Any>{
|
||||||
|
public val topLeft: T
|
||||||
|
public val bottomRight: T
|
||||||
|
|
||||||
|
public operator fun contains(point: T): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Feature<T: Any> {
|
||||||
|
public interface Attribute<T>
|
||||||
|
|
||||||
|
public val zoomRange: ClosedFloatingPointRange<Double>
|
||||||
|
|
||||||
|
public var attributes: AttributeMap
|
||||||
|
|
||||||
|
public fun getBoundingBox(zoom: Double): Rectangle<T>?
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface SelectableFeature<T: Any> : Feature<T> {
|
||||||
|
public operator fun contains(point: ViewPoint<T>): Boolean = getBoundingBox(point.zoom)?.let {
|
||||||
|
point.focus in it
|
||||||
|
} ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface DraggableFeature<T: Any> : SelectableFeature<T> {
|
||||||
|
public fun withCoordinates(newCoordinates: T): Feature<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <T: Any> Iterable<Feature<T>>.computeBoundingBox(zoom: Double): Rectangle<T>? =
|
||||||
|
mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
|
||||||
|
|
||||||
|
//public fun Pair<Number, Number>.toCoordinates(): GeodeticMapCoordinates =
|
||||||
|
// GeodeticMapCoordinates.ofDegrees(first.toDouble(), second.toDouble())
|
||||||
|
|
||||||
|
internal val defaultZoomRange = 1.0..Double.POSITIVE_INFINITY
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A feature that decides what to show depending on the zoom value (it could change size of shape)
|
||||||
|
*/
|
||||||
|
public class FeatureSelector<T: Any>(
|
||||||
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
|
public val selector: (zoom: Int) -> Feature<T>,
|
||||||
|
) : Feature<T> {
|
||||||
|
override val zoomRange: ClosedFloatingPointRange<Double> get() = defaultZoomRange
|
||||||
|
|
||||||
|
override fun getBoundingBox(zoom: Double): Rectangle<T>? = selector(floor(zoom).toInt()).getBoundingBox(zoom)
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DrawFeature<T: Any>(
|
||||||
|
public val position: T,
|
||||||
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
|
public val drawFeature: DrawScope.() -> Unit,
|
||||||
|
) : DraggableFeature<T> {
|
||||||
|
override fun getBoundingBox(zoom: Double): Rectangle<T> {
|
||||||
|
//TODO add box computation
|
||||||
|
return GmcRectangle(position, position)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||||
|
DrawFeature(newCoordinates, zoomRange, attributes, drawFeature)
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PathFeature<T: Any>(
|
||||||
|
public val rectangle: Rectangle<T>,
|
||||||
|
public val path: Path,
|
||||||
|
public val brush: Brush,
|
||||||
|
public val style: DrawStyle = Fill,
|
||||||
|
public val targetRect: Rect = path.getBounds(),
|
||||||
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
|
) : DraggableFeature<T> {
|
||||||
|
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||||
|
PathFeature(rectangle.moveTo(newCoordinates), path, brush, style, targetRect, zoomRange)
|
||||||
|
|
||||||
|
override fun getBoundingBox(zoom: Double): Rectangle<T> = rectangle
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PointsFeature<T: Any>(
|
||||||
|
public val points: List<T>,
|
||||||
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
|
public val stroke: Float = 2f,
|
||||||
|
public val color: Color = Color.Red,
|
||||||
|
public val pointMode: PointMode = PointMode.Points,
|
||||||
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
|
) : Feature<T> {
|
||||||
|
override fun getBoundingBox(zoom: Double): Rectangle<T> = GmcRectangle(points.first(), points.last())
|
||||||
|
}
|
||||||
|
|
||||||
|
public data class CircleFeature<T: Any>(
|
||||||
|
public val center: T,
|
||||||
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
|
public val size: Float = 5f,
|
||||||
|
public val color: Color = Color.Red,
|
||||||
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
|
) : DraggableFeature<T> {
|
||||||
|
override fun getBoundingBox(zoom: Double): Rectangle<T> {
|
||||||
|
val scale = WebMercatorProjection.scaleFactor(zoom)
|
||||||
|
return GmcRectangle.square(center, (size / scale).radians, (size / scale).radians)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||||
|
CircleFeature(newCoordinates, zoomRange, size, color, attributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RectangleFeature<T: Any>(
|
||||||
|
public val center: T,
|
||||||
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
|
public val size: DpSize = DpSize(5.dp, 5.dp),
|
||||||
|
public val color: Color = Color.Red,
|
||||||
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
|
) : DraggableFeature<T> {
|
||||||
|
override fun getBoundingBox(zoom: Double): Rectangle<T> {
|
||||||
|
val scale = WebMercatorProjection.scaleFactor(zoom)
|
||||||
|
return GmcRectangle.square(center, (size.height.value / scale).radians, (size.width.value / scale).radians)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||||
|
RectangleFeature(newCoordinates, zoomRange, size, color, attributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LineFeature<T: Any>(
|
||||||
|
public val a: T,
|
||||||
|
public val b: T,
|
||||||
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
|
public val color: Color = Color.Red,
|
||||||
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
|
) : SelectableFeature<T> {
|
||||||
|
override fun getBoundingBox(zoom: Double): Rectangle<T> = GmcRectangle(a, b)
|
||||||
|
|
||||||
|
override fun contains(point: ViewPoint<T>): Boolean {
|
||||||
|
return super.contains(point)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param startAngle the angle from parallel downwards for the start of the arc
|
||||||
|
* @param arcLength arc length
|
||||||
|
*/
|
||||||
|
public class ArcFeature<T:Any>(
|
||||||
|
public val oval: Rectangle<T>,
|
||||||
|
public val startAngle: Angle,
|
||||||
|
public val arcLength: Angle,
|
||||||
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
|
public val color: Color = Color.Red,
|
||||||
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
|
) : DraggableFeature<T> {
|
||||||
|
override fun getBoundingBox(zoom: Double): Rectangle<T> = oval
|
||||||
|
|
||||||
|
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||||
|
ArcFeature(oval.moveTo(newCoordinates), startAngle, arcLength, zoomRange, color, attributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BitmapImageFeature<T: Any>(
|
||||||
|
public val position: T,
|
||||||
|
public val image: ImageBitmap,
|
||||||
|
public val size: IntSize = IntSize(15, 15),
|
||||||
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
|
) : DraggableFeature<T> {
|
||||||
|
override fun getBoundingBox(zoom: Double): Rectangle<T> = GmcRectangle(position, position)
|
||||||
|
|
||||||
|
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||||
|
BitmapImageFeature(newCoordinates, image, size, zoomRange, attributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VectorImageFeature<T: Any>(
|
||||||
|
public val position: T,
|
||||||
|
public val image: ImageVector,
|
||||||
|
public val size: DpSize = DpSize(20.dp, 20.dp),
|
||||||
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
|
) : DraggableFeature<T> {
|
||||||
|
override fun getBoundingBox(zoom: Double): Rectangle<T> = GmcRectangle(position, position)
|
||||||
|
|
||||||
|
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||||
|
VectorImageFeature(newCoordinates, image, size, zoomRange, attributes)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun painter(): VectorPainter = rememberVectorPainter(image)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A group of other features
|
||||||
|
*/
|
||||||
|
public class FeatureGroup<T: Any>(
|
||||||
|
public val children: Map<FeatureId<*>, Feature<T>>,
|
||||||
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
|
) : Feature<T> {
|
||||||
|
override fun getBoundingBox(zoom: Double): Rectangle<T>? =
|
||||||
|
children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TextFeature<T: Any>(
|
||||||
|
public val position: T,
|
||||||
|
public val text: String,
|
||||||
|
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||||
|
public val color: Color = Color.Black,
|
||||||
|
override var attributes: AttributeMap = AttributeMap(),
|
||||||
|
public val fontConfig: MapTextFeatureFont.() -> Unit,
|
||||||
|
) : DraggableFeature<T> {
|
||||||
|
override fun getBoundingBox(zoom: Double): Rectangle<T> = GmcRectangle(position, position)
|
||||||
|
|
||||||
|
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||||
|
TextFeature(newCoordinates, text, zoomRange, color, attributes, fontConfig)
|
||||||
|
}
|
@ -0,0 +1,302 @@
|
|||||||
|
package center.sciprog.maps.features
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
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
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import center.sciprog.maps.coordinates.*
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
public value class FeatureId<out MapFeature>(public val id: String)
|
||||||
|
|
||||||
|
public class MapFeaturesState {
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal val featureMap: MutableMap<String, Feature> = mutableStateMapOf()
|
||||||
|
|
||||||
|
//TODO use context receiver for that
|
||||||
|
public fun FeatureId<DraggableFeature>.draggable(
|
||||||
|
//TODO add constraints
|
||||||
|
callback: DragHandle = DragHandle.BYPASS,
|
||||||
|
) {
|
||||||
|
val handle = DragHandle.withPrimaryButton { event, start, end ->
|
||||||
|
val feature = featureMap[id] as? DraggableFeature ?: return@withPrimaryButton true
|
||||||
|
val boundingBox = feature.getBoundingBox(start.zoom) ?: return@withPrimaryButton true
|
||||||
|
if (start.focus in boundingBox) {
|
||||||
|
feature(id, feature.withCoordinates(end.focus))
|
||||||
|
callback.handle(event, start, end)
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setAttribute(this, DraggableAttribute, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cyclic update of a feature. Called infinitely until canceled.
|
||||||
|
*/
|
||||||
|
public fun <T : Feature> FeatureId<T>.updated(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
update: suspend (T) -> T,
|
||||||
|
): Job = scope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
feature(this@updated, update(getFeature(this@updated)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
public fun <T : SelectableFeature> FeatureId<T>.selectable(
|
||||||
|
onSelect: (FeatureId<T>, T) -> Unit,
|
||||||
|
) {
|
||||||
|
setAttribute(this, SelectableAttribute) { id, feature -> onSelect(id as FeatureId<T>, feature as T) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public val features: Map<FeatureId<*>, Feature>
|
||||||
|
get() = featureMap.mapKeys { FeatureId<Feature>(it.key) }
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
public fun <T : Feature> getFeature(id: FeatureId<T>): T = featureMap[id.id] as T
|
||||||
|
|
||||||
|
|
||||||
|
private fun generateID(feature: Feature): String = "@feature[${feature.hashCode().toUInt()}]"
|
||||||
|
|
||||||
|
public fun <T : Feature> feature(id: String?, feature: T): FeatureId<T> {
|
||||||
|
val safeId = id ?: generateID(feature)
|
||||||
|
featureMap[safeId] = feature
|
||||||
|
return FeatureId(safeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <T : Feature> feature(id: FeatureId<T>?, feature: T): FeatureId<T> = feature(id?.id, feature)
|
||||||
|
|
||||||
|
public fun <T> setAttribute(id: FeatureId<Feature>, key: Feature.Attribute<T>, value: T?) {
|
||||||
|
getFeature(id).attributes.setAttribute(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
public fun <T> getAttribute(id: FeatureId<Feature>, key: Feature.Attribute<T>): T? =
|
||||||
|
getFeature(id).attributes[key]
|
||||||
|
|
||||||
|
|
||||||
|
// @Suppress("UNCHECKED_CAST")
|
||||||
|
// public fun <T> findAllWithAttribute(key: Attribute<T>, condition: (T) -> Boolean): Set<FeatureId> {
|
||||||
|
// return attributes.filterValues {
|
||||||
|
// condition(it[key] as T)
|
||||||
|
// }.keys
|
||||||
|
// }
|
||||||
|
|
||||||
|
public inline fun <T> forEachWithAttribute(
|
||||||
|
key: Feature.Attribute<T>,
|
||||||
|
block: (id: FeatureId<*>, attributeValue: T) -> Unit,
|
||||||
|
) {
|
||||||
|
featureMap.forEach { (id, feature) ->
|
||||||
|
feature.attributes[key]?.let {
|
||||||
|
block(FeatureId<Feature>(id), it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build, but do not remember map feature state
|
||||||
|
*/
|
||||||
|
public fun build(
|
||||||
|
builder: MapFeaturesState.() -> Unit = {},
|
||||||
|
): MapFeaturesState = MapFeaturesState().apply(builder)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build and remember map feature state
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
public fun remember(
|
||||||
|
builder: MapFeaturesState.() -> Unit = {},
|
||||||
|
): MapFeaturesState = remember(builder) {
|
||||||
|
build(builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun MapFeaturesState.circle(
|
||||||
|
center: GeodeticMapCoordinates,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
size: Float = 5f,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
id: String? = null,
|
||||||
|
): FeatureId<CircleFeature> = feature(
|
||||||
|
id, CircleFeature(center, zoomRange, size, color)
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun MapFeaturesState.circle(
|
||||||
|
centerCoordinates: Pair<Double, Double>,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
size: Float = 5f,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
id: String? = null,
|
||||||
|
): FeatureId<CircleFeature> = feature(
|
||||||
|
id, CircleFeature(centerCoordinates.toCoordinates(), zoomRange, size, color)
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun MapFeaturesState.rectangle(
|
||||||
|
centerCoordinates: Gmc,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
size: DpSize = DpSize(5.dp, 5.dp),
|
||||||
|
color: Color = Color.Red,
|
||||||
|
id: String? = null,
|
||||||
|
): FeatureId<RectangleFeature> = feature(
|
||||||
|
id, RectangleFeature(centerCoordinates, zoomRange, size, color)
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun MapFeaturesState.rectangle(
|
||||||
|
centerCoordinates: Pair<Double, Double>,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
size: DpSize = DpSize(5.dp, 5.dp),
|
||||||
|
color: Color = Color.Red,
|
||||||
|
id: String? = null,
|
||||||
|
): FeatureId<RectangleFeature> = feature(
|
||||||
|
id, RectangleFeature(centerCoordinates.toCoordinates(), zoomRange, size, color)
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun MapFeaturesState.draw(
|
||||||
|
position: Pair<Double, Double>,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
id: String? = null,
|
||||||
|
draw: DrawScope.() -> Unit,
|
||||||
|
): FeatureId<DrawFeature> = feature(id, DrawFeature(position.toCoordinates(), zoomRange, drawFeature = draw))
|
||||||
|
|
||||||
|
public fun MapFeaturesState.line(
|
||||||
|
aCoordinates: Gmc,
|
||||||
|
bCoordinates: Gmc,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
id: String? = null,
|
||||||
|
): FeatureId<LineFeature> = feature(
|
||||||
|
id,
|
||||||
|
LineFeature(aCoordinates, bCoordinates, zoomRange, color)
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun MapFeaturesState.line(
|
||||||
|
curve: GmcCurve,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
id: String? = null,
|
||||||
|
): FeatureId<LineFeature> = feature(
|
||||||
|
id,
|
||||||
|
LineFeature(curve.forward.coordinates, curve.backward.coordinates, zoomRange, color)
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun MapFeaturesState.line(
|
||||||
|
aCoordinates: Pair<Double, Double>,
|
||||||
|
bCoordinates: Pair<Double, Double>,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
id: String? = null,
|
||||||
|
): FeatureId<LineFeature> = feature(
|
||||||
|
id,
|
||||||
|
LineFeature(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), zoomRange, color)
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun MapFeaturesState.arc(
|
||||||
|
oval: GmcRectangle,
|
||||||
|
startAngle: Angle,
|
||||||
|
arcLength: Angle,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
id: String? = null,
|
||||||
|
): FeatureId<ArcFeature> = feature(
|
||||||
|
id,
|
||||||
|
ArcFeature(oval, startAngle, arcLength, zoomRange, color)
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun MapFeaturesState.arc(
|
||||||
|
center: Pair<Double, Double>,
|
||||||
|
radius: Distance,
|
||||||
|
startAngle: Angle,
|
||||||
|
arcLength: Angle,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
id: String? = null,
|
||||||
|
): FeatureId<ArcFeature> = feature(
|
||||||
|
id,
|
||||||
|
ArcFeature(
|
||||||
|
oval = GmcRectangle.square(center.toCoordinates(), radius, radius),
|
||||||
|
startAngle = startAngle,
|
||||||
|
arcLength = arcLength,
|
||||||
|
zoomRange = zoomRange,
|
||||||
|
color = color
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun MapFeaturesState.points(
|
||||||
|
points: List<Gmc>,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
stroke: Float = 2f,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
pointMode: PointMode = PointMode.Points,
|
||||||
|
id: String? = null,
|
||||||
|
): FeatureId<PointsFeature> = feature(id, PointsFeature(points, zoomRange, stroke, color, pointMode))
|
||||||
|
|
||||||
|
@JvmName("pointsFromPairs")
|
||||||
|
public fun MapFeaturesState.points(
|
||||||
|
points: List<Pair<Double, Double>>,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
stroke: Float = 2f,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
pointMode: PointMode = PointMode.Points,
|
||||||
|
id: String? = null,
|
||||||
|
): FeatureId<PointsFeature> =
|
||||||
|
feature(id, PointsFeature(points.map { it.toCoordinates() }, zoomRange, stroke, color, pointMode))
|
||||||
|
|
||||||
|
public fun MapFeaturesState.image(
|
||||||
|
position: Pair<Double, Double>,
|
||||||
|
image: ImageVector,
|
||||||
|
size: DpSize = DpSize(20.dp, 20.dp),
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
id: String? = null,
|
||||||
|
): FeatureId<VectorImageFeature> =
|
||||||
|
feature(id, VectorImageFeature(position.toCoordinates(), image, size, zoomRange))
|
||||||
|
|
||||||
|
public fun MapFeaturesState.group(
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
id: String? = null,
|
||||||
|
builder: MapFeaturesState.() -> Unit,
|
||||||
|
): FeatureId<FeatureGroup> {
|
||||||
|
val map = MapFeaturesState().apply(builder).features
|
||||||
|
val feature = FeatureGroup(map, zoomRange)
|
||||||
|
return feature(id, feature)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun MapFeaturesState.text(
|
||||||
|
position: GeodeticMapCoordinates,
|
||||||
|
text: String,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
font: MapTextFeatureFont.() -> Unit = { size = 16f },
|
||||||
|
id: String? = null,
|
||||||
|
): FeatureId<TextFeature> = feature(
|
||||||
|
id,
|
||||||
|
TextFeature(position, text, zoomRange, color, fontConfig = font)
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun MapFeaturesState.text(
|
||||||
|
position: Pair<Double, Double>,
|
||||||
|
text: String,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
font: MapTextFeatureFont.() -> Unit = { size = 16f },
|
||||||
|
id: String? = null,
|
||||||
|
): FeatureId<TextFeature> = feature(
|
||||||
|
id,
|
||||||
|
TextFeature(position.toCoordinates(), text, zoomRange, color, fontConfig = font)
|
||||||
|
)
|
@ -46,6 +46,7 @@ dependencyResolutionManagement {
|
|||||||
|
|
||||||
include(
|
include(
|
||||||
":maps-kt-core",
|
":maps-kt-core",
|
||||||
|
":maps-kt-features",
|
||||||
":maps-kt-compose",
|
":maps-kt-compose",
|
||||||
":demo:maps",
|
":demo:maps",
|
||||||
":maps-kt-scheme",
|
":maps-kt-scheme",
|
||||||
|
Loading…
Reference in New Issue
Block a user