|
|
|
@ -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 Feature<T : Any> {
|
|
|
|
|
public interface Attribute<T>
|
|
|
|
|
|
|
|
|
|
public val space: CoordinateSpace<T>
|
|
|
|
|
|
|
|
|
|
public val zoomRange: ClosedFloatingPointRange<Double>
|
|
|
|
|
|
|
|
|
|
public var attributes: AttributeMap
|
|
|
|
@ -40,18 +27,22 @@ public interface Feature<T: Any> {
|
|
|
|
|
public fun getBoundingBox(zoom: Double): Rectangle<T>?
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public interface SelectableFeature<T: Any> : Feature<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 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())
|
|
|
|
@ -61,7 +52,8 @@ 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>(
|
|
|
|
|
public class FeatureSelector<T : Any>(
|
|
|
|
|
override val space: CoordinateSpace<T>,
|
|
|
|
|
override var attributes: AttributeMap = AttributeMap(),
|
|
|
|
|
public val selector: (zoom: Int) -> Feature<T>,
|
|
|
|
|
) : Feature<T> {
|
|
|
|
@ -70,22 +62,22 @@ public class FeatureSelector<T: Any>(
|
|
|
|
|
override fun getBoundingBox(zoom: Double): Rectangle<T>? = selector(floor(zoom).toInt()).getBoundingBox(zoom)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class DrawFeature<T: Any>(
|
|
|
|
|
public val position: T,
|
|
|
|
|
public class DrawFeature<T : Any>(
|
|
|
|
|
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>(
|
|
|
|
|
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>(
|
|
|
|
|
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>(
|
|
|
|
|
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>(
|
|
|
|
|
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>(
|
|
|
|
|
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>(
|
|
|
|
|
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,
|
|
|
|
|
public class BitmapImageFeature<T : Any>(
|
|
|
|
|
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,
|
|
|
|
|
public class VectorImageFeature<T : Any>(
|
|
|
|
|
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)
|
|
|
|
@ -208,22 +216,24 @@ public class VectorImageFeature<T: Any>(
|
|
|
|
|
/**
|
|
|
|
|
* A group of other features
|
|
|
|
|
*/
|
|
|
|
|
public class FeatureGroup<T: Any>(
|
|
|
|
|
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>(
|
|
|
|
|
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,
|
|
|
|
|
public val fontConfig: FeatureFont.() -> Unit,
|
|
|
|
|
) : DraggableFeature<T> {
|
|
|
|
|
override fun getBoundingBox(zoom: Double): Rectangle<T> = GmcRectangle(position, position)
|
|
|
|
|
|
|
|
|
|