[WIP] generic features
This commit is contained in:
parent
7735d667bc
commit
c2157c8351
@ -1,94 +0,0 @@
|
||||
/*
|
||||
* 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,34 @@
|
||||
package center.sciprog.maps.features
|
||||
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
|
||||
|
||||
public interface Rectangle<T : Any> {
|
||||
public val topLeft: T
|
||||
public val bottomRight: T
|
||||
|
||||
public operator fun contains(point: T): Boolean
|
||||
}
|
||||
|
||||
public interface CoordinateSpace<T : Any> {
|
||||
|
||||
/**
|
||||
* Build a rectangle by two opposing corners
|
||||
*/
|
||||
public fun buildRectangle(first: T, second: T): Rectangle<T>
|
||||
|
||||
/**
|
||||
* Build a rectangle of visual size [size]
|
||||
*/
|
||||
public fun buildRectangle(center: T, zoom: Double, size: DpSize): Rectangle<T>
|
||||
//GmcRectangle.square(center, (size.height.value / scale).radians, (size.width.value / scale).radians)
|
||||
|
||||
/**
|
||||
* Move given rectangle to be centered at [center]
|
||||
*/
|
||||
public fun Rectangle<T>.withCenter(center: T): Rectangle<T>
|
||||
|
||||
public fun Iterable<Rectangle<T>>.computeRectangle(): Rectangle<T>?
|
||||
|
||||
public fun Iterable<T>.computeRectangle(): Rectangle<T>?
|
||||
}
|
@ -9,30 +9,17 @@ 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.Dp
|
||||
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 space: CoordinateSpace<T>
|
||||
|
||||
public val zoomRange: ClosedFloatingPointRange<Double>
|
||||
|
||||
public var attributes: AttributeMap
|
||||
@ -50,8 +37,12 @@ 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 <T : Any> Iterable<Feature<T>>.computeBoundingBox(
|
||||
space: CoordinateSpace<T>,
|
||||
zoom: Double,
|
||||
): Rectangle<T>? = with(space) {
|
||||
mapNotNull { it.getBoundingBox(zoom) }.computeRectangle()
|
||||
}
|
||||
|
||||
//public fun Pair<Number, Number>.toCoordinates(): GeodeticMapCoordinates =
|
||||
// GeodeticMapCoordinates.ofDegrees(first.toDouble(), second.toDouble())
|
||||
@ -62,6 +53,7 @@ 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 val space: CoordinateSpace<T>,
|
||||
override var attributes: AttributeMap = AttributeMap(),
|
||||
public val selector: (zoom: Int) -> Feature<T>,
|
||||
) : Feature<T> {
|
||||
@ -71,21 +63,21 @@ public class FeatureSelector<T: Any>(
|
||||
}
|
||||
|
||||
public class DrawFeature<T : Any>(
|
||||
public val position: T,
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val rectangle: Rectangle<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 getBoundingBox(zoom: Double): Rectangle<T> = rectangle
|
||||
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||
DrawFeature(newCoordinates, zoomRange, attributes, drawFeature)
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> = with(space) {
|
||||
DrawFeature(space, rectangle.withCenter(newCoordinates), zoomRange, attributes, drawFeature)
|
||||
}
|
||||
}
|
||||
|
||||
public class PathFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val rectangle: Rectangle<T>,
|
||||
public val path: Path,
|
||||
public val brush: Brush,
|
||||
@ -94,14 +86,24 @@ public class PathFeature<T: Any>(
|
||||
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 withCoordinates(newCoordinates: T): Feature<T> = with(space) {
|
||||
PathFeature(
|
||||
space = space,
|
||||
rectangle = rectangle.withCenter(newCoordinates),
|
||||
path = path,
|
||||
brush = brush,
|
||||
style = style,
|
||||
targetRect = targetRect,
|
||||
zoomRange = zoomRange
|
||||
)
|
||||
}
|
||||
|
||||
override fun getBoundingBox(zoom: Double): Rectangle<T> = rectangle
|
||||
|
||||
}
|
||||
|
||||
public class PointsFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val points: List<T>,
|
||||
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||
public val stroke: Float = 2f,
|
||||
@ -109,49 +111,51 @@ public class PointsFeature<T: Any>(
|
||||
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())
|
||||
override fun getBoundingBox(zoom: Double): Rectangle<T>? = with(space) {
|
||||
points.computeRectangle()
|
||||
}
|
||||
}
|
||||
|
||||
public data class CircleFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val center: T,
|
||||
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||
public val size: Float = 5f,
|
||||
public val size: 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 / scale).radians, (size / scale).radians)
|
||||
}
|
||||
override fun getBoundingBox(zoom: Double): Rectangle<T> =
|
||||
space.buildRectangle(center, zoom, DpSize(size, size))
|
||||
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||
CircleFeature(newCoordinates, zoomRange, size, color, attributes)
|
||||
CircleFeature(space, newCoordinates, zoomRange, size, color, attributes)
|
||||
}
|
||||
|
||||
public class RectangleFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
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 getBoundingBox(zoom: Double): Rectangle<T> =
|
||||
space.buildRectangle(center, zoom, size)
|
||||
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||
RectangleFeature(newCoordinates, zoomRange, size, color, attributes)
|
||||
RectangleFeature(space, newCoordinates, zoomRange, size, color, attributes)
|
||||
}
|
||||
|
||||
public class LineFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
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 getBoundingBox(zoom: Double): Rectangle<T> =
|
||||
space.buildRectangle(a, b)
|
||||
|
||||
override fun contains(point: ViewPoint<T>): Boolean {
|
||||
return super.contains(point)
|
||||
@ -159,47 +163,51 @@ public class LineFeature<T: Any>(
|
||||
}
|
||||
|
||||
/**
|
||||
* @param startAngle the angle from parallel downwards for the start of the arc
|
||||
* @param arcLength arc length
|
||||
* @param startAngle the angle from 3 o'clock downwards for the start of the arc in radians
|
||||
* @param arcLength arc length in radians
|
||||
*/
|
||||
public class ArcFeature<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val oval: Rectangle<T>,
|
||||
public val startAngle: Angle,
|
||||
public val arcLength: Angle,
|
||||
public val startAngle: Float,
|
||||
public val arcLength: Float,
|
||||
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)
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> = with(space) {
|
||||
ArcFeature(space, oval.withCenter(newCoordinates), startAngle, arcLength, zoomRange, color, attributes)
|
||||
}
|
||||
}
|
||||
|
||||
public class BitmapImageFeature<T : Any>(
|
||||
public val position: T,
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val rectangle: Rectangle<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 getBoundingBox(zoom: Double): Rectangle<T> = rectangle
|
||||
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||
BitmapImageFeature(newCoordinates, image, size, zoomRange, attributes)
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> = with(space) {
|
||||
BitmapImageFeature(space, rectangle.withCenter(newCoordinates), image, zoomRange, attributes)
|
||||
}
|
||||
}
|
||||
|
||||
public class VectorImageFeature<T : Any>(
|
||||
public val position: T,
|
||||
override val space: CoordinateSpace<T>,
|
||||
public val rectangle: Rectangle<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 getBoundingBox(zoom: Double): Rectangle<T> = rectangle
|
||||
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> =
|
||||
VectorImageFeature(newCoordinates, image, size, zoomRange, attributes)
|
||||
override fun withCoordinates(newCoordinates: T): Feature<T> = with(space) {
|
||||
VectorImageFeature(space, rectangle.withCenter(newCoordinates), image, zoomRange, attributes)
|
||||
}
|
||||
|
||||
@Composable
|
||||
public fun painter(): VectorPainter = rememberVectorPainter(image)
|
||||
@ -209,12 +217,14 @@ public class VectorImageFeature<T: Any>(
|
||||
* A group of other features
|
||||
*/
|
||||
public class FeatureGroup<T : Any>(
|
||||
override val space: CoordinateSpace<T>,
|
||||
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()
|
||||
override fun getBoundingBox(zoom: Double): Rectangle<T>? = with(space) {
|
||||
children.values.mapNotNull { it.getBoundingBox(zoom) }.computeRectangle()
|
||||
}
|
||||
}
|
||||
|
||||
public class TextFeature<T : Any>(
|
||||
@ -223,7 +233,7 @@ public class TextFeature<T: Any>(
|
||||
override val zoomRange: ClosedFloatingPointRange<Double> = defaultZoomRange,
|
||||
public val color: Color = Color.Black,
|
||||
override var attributes: AttributeMap = AttributeMap(),
|
||||
public val fontConfig: MapTextFeatureFont.() -> Unit,
|
||||
public val fontConfig: FeatureFont.() -> Unit,
|
||||
) : DraggableFeature<T> {
|
||||
override fun getBoundingBox(zoom: Double): Rectangle<T> = GmcRectangle(position, position)
|
||||
|
||||
|
@ -0,0 +1,5 @@
|
||||
package center.sciprog.maps.features
|
||||
|
||||
public expect class FeatureFont {
|
||||
public var size: Float
|
||||
}
|
@ -282,7 +282,7 @@ public fun MapFeaturesState.text(
|
||||
text: String,
|
||||
zoomRange: IntRange = defaultZoomRange,
|
||||
color: Color = Color.Red,
|
||||
font: MapTextFeatureFont.() -> Unit = { size = 16f },
|
||||
font: FeatureFont.() -> Unit = { size = 16f },
|
||||
id: String? = null,
|
||||
): FeatureId<TextFeature> = feature(
|
||||
id,
|
||||
@ -294,7 +294,7 @@ public fun MapFeaturesState.text(
|
||||
text: String,
|
||||
zoomRange: IntRange = defaultZoomRange,
|
||||
color: Color = Color.Red,
|
||||
font: MapTextFeatureFont.() -> Unit = { size = 16f },
|
||||
font: FeatureFont.() -> Unit = { size = 16f },
|
||||
id: String? = null,
|
||||
): FeatureId<TextFeature> = feature(
|
||||
id,
|
||||
|
@ -0,0 +1,9 @@
|
||||
package center.sciprog.maps.features
|
||||
|
||||
/**
|
||||
* @param T type of coordinates used for the view point
|
||||
*/
|
||||
public interface ViewPoint<T: Any> {
|
||||
public val focus: T
|
||||
public val zoom: Double
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package center.sciprog.maps.features
|
||||
|
||||
import org.jetbrains.skia.Font
|
||||
|
||||
public actual typealias FeatureFont = Font
|
Loading…
Reference in New Issue
Block a user