Type safe angles
This commit is contained in:
parent
a23b9954cd
commit
50ccfeab70
@ -6,6 +6,8 @@ plugins {
|
|||||||
id("space.kscience.gradle.project")
|
id("space.kscience.gradle.project")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val kmathVersion: String by extra("0.3.1-dev-10")
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "center.sciprog"
|
group = "center.sciprog"
|
||||||
version = "0.2.2-dev-1"
|
version = "0.2.2-dev-1"
|
||||||
|
@ -59,7 +59,7 @@ fun App() {
|
|||||||
|
|
||||||
val pointOne = 55.568548 to 37.568604
|
val pointOne = 55.568548 to 37.568604
|
||||||
val pointTwo = 55.929444 to 37.518434
|
val pointTwo = 55.929444 to 37.518434
|
||||||
val pointThree = 60.929444 to 37.518434
|
// val pointThree = 60.929444 to 37.518434
|
||||||
|
|
||||||
MapView(
|
MapView(
|
||||||
mapTileProvider = mapTileProvider,
|
mapTileProvider = mapTileProvider,
|
||||||
@ -81,9 +81,15 @@ fun App() {
|
|||||||
val marker2 = rectangle(55.8 to 38.5, size = DpSize(10.dp, 10.dp)).color(Color.Magenta)
|
val marker2 = rectangle(55.8 to 38.5, size = DpSize(10.dp, 10.dp)).color(Color.Magenta)
|
||||||
val marker3 = rectangle(56.0 to 38.5, size = DpSize(10.dp, 10.dp)).color(Color.Magenta)
|
val marker3 = rectangle(56.0 to 38.5, size = DpSize(10.dp, 10.dp)).color(Color.Magenta)
|
||||||
|
|
||||||
draggableLine(marker1, marker2).color(Color.Blue)
|
draggableLine(marker1, marker2, id = "line 1").color(Color.Red).onClick {
|
||||||
draggableLine(marker2, marker3).color(Color.Blue)
|
println("line 1 clicked")
|
||||||
draggableLine(marker3, marker1).color(Color.Blue)
|
}
|
||||||
|
draggableLine(marker2, marker3, id = "line 2").color(Color.DarkGray).onClick {
|
||||||
|
println("line 2 clicked")
|
||||||
|
}
|
||||||
|
draggableLine(marker3, marker1, id = "line 3").color(Color.Blue).onClick {
|
||||||
|
println("line 3 clicked")
|
||||||
|
}
|
||||||
|
|
||||||
points(
|
points(
|
||||||
points = listOf(
|
points = listOf(
|
||||||
|
@ -19,9 +19,9 @@ import center.sciprog.maps.svg.snapshot
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import space.kscience.kmath.geometry.Angle
|
||||||
import java.awt.Desktop
|
import java.awt.Desktop
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import kotlin.math.PI
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
@ -35,7 +35,7 @@ fun App() {
|
|||||||
text(410.52737 to 868.7676, "Shire").color(Color.Blue)
|
text(410.52737 to 868.7676, "Shire").color(Color.Blue)
|
||||||
circle(1132.0881 to 394.99127).color(Color.Red)
|
circle(1132.0881 to 394.99127).color(Color.Red)
|
||||||
text(1132.0881 to 394.99127, "Ordruin").color(Color.Red)
|
text(1132.0881 to 394.99127, "Ordruin").color(Color.Red)
|
||||||
arc(center = 1132.0881 to 394.99127, radius = 20f, startAngle = 0f, 2 * PI.toFloat())
|
arc(center = 1132.0881 to 394.99127, radius = 20f, startAngle = Angle.zero, Angle.piTimes2)
|
||||||
|
|
||||||
//circle(410.52737 to 868.7676, id = "hobbit")
|
//circle(410.52737 to 868.7676, id = "hobbit")
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ import center.sciprog.maps.features.Rectangle
|
|||||||
import space.kscience.kmath.geometry.Angle
|
import space.kscience.kmath.geometry.Angle
|
||||||
import space.kscience.kmath.geometry.abs
|
import space.kscience.kmath.geometry.abs
|
||||||
|
|
||||||
|
internal fun Angle.isBetween(a: Angle, b: Angle) = this in a..b || this in b..a
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A section of the map between two parallels and two meridians. The figure represents a square in a Mercator projection.
|
* A section of the map between two parallels and two meridians. The figure represents a square in a Mercator projection.
|
||||||
* Params are two opposing "corners" of quasi-square.
|
* Params are two opposing "corners" of quasi-square.
|
||||||
@ -18,8 +20,7 @@ internal data class GmcRectangle(
|
|||||||
) : Rectangle<Gmc> {
|
) : Rectangle<Gmc> {
|
||||||
|
|
||||||
override fun contains(point: Gmc): Boolean =
|
override fun contains(point: Gmc): Boolean =
|
||||||
point.latitude in a.latitude..b.latitude
|
point.latitude.isBetween(a.latitude, b.latitude) && point.longitude.isBetween(a.longitude, b.longitude)
|
||||||
&& point.longitude in a.longitude..b.longitude
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public val Rectangle<Gmc>.center: GeodeticMapCoordinates
|
public val Rectangle<Gmc>.center: GeodeticMapCoordinates
|
||||||
|
@ -12,7 +12,6 @@ import center.sciprog.maps.coordinates.Gmc
|
|||||||
import center.sciprog.maps.coordinates.GmcCurve
|
import center.sciprog.maps.coordinates.GmcCurve
|
||||||
import center.sciprog.maps.features.*
|
import center.sciprog.maps.features.*
|
||||||
import space.kscience.kmath.geometry.Angle
|
import space.kscience.kmath.geometry.Angle
|
||||||
import space.kscience.kmath.geometry.radians
|
|
||||||
|
|
||||||
|
|
||||||
internal fun FeatureGroup<Gmc>.coordinatesOf(pair: Pair<Number, Number>) =
|
internal fun FeatureGroup<Gmc>.coordinatesOf(pair: Pair<Number, Number>) =
|
||||||
@ -77,8 +76,8 @@ public fun FeatureGroup<Gmc>.arc(
|
|||||||
ArcFeature(
|
ArcFeature(
|
||||||
space,
|
space,
|
||||||
oval = space.Rectangle(coordinatesOf(center), radius, radius),
|
oval = space.Rectangle(coordinatesOf(center), radius, radius),
|
||||||
startAngle = startAngle.radians.toFloat(),
|
startAngle = startAngle,
|
||||||
arcLength = arcLength.radians.toFloat(),
|
arcLength = arcLength,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ plugins {
|
|||||||
`maven-publish`
|
`maven-publish`
|
||||||
}
|
}
|
||||||
|
|
||||||
val kmathVersion: String by rootProject.extra("0.3.1-dev-10")
|
val kmathVersion: String by rootProject.extra
|
||||||
|
|
||||||
kscience{
|
kscience{
|
||||||
useSerialization()
|
useSerialization()
|
||||||
|
@ -4,10 +4,13 @@ plugins {
|
|||||||
`maven-publish`
|
`maven-publish`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val kmathVersion: String by rootProject.extra
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api("space.kscience:kmath-trajectory:$kmathVersion")
|
||||||
api(compose.foundation)
|
api(compose.foundation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import androidx.compose.ui.unit.DpSize
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import center.sciprog.attributes.Attributes
|
import center.sciprog.attributes.Attributes
|
||||||
import center.sciprog.attributes.NameAttribute
|
import center.sciprog.attributes.NameAttribute
|
||||||
|
import space.kscience.kmath.geometry.Angle
|
||||||
|
|
||||||
public typealias FloatRange = ClosedFloatingPointRange<Float>
|
public typealias FloatRange = ClosedFloatingPointRange<Float>
|
||||||
|
|
||||||
@ -185,7 +186,7 @@ public data class LineFeature<T : Any>(
|
|||||||
override fun getBoundingBox(zoom: Float): Rectangle<T> =
|
override fun getBoundingBox(zoom: Float): Rectangle<T> =
|
||||||
space.Rectangle(a, b)
|
space.Rectangle(a, b)
|
||||||
|
|
||||||
private val clickRadius get() = attributes[ClickRadius] ?: 20f
|
private val clickRadius get() = attributes[ClickRadius] ?: 10f
|
||||||
|
|
||||||
override fun contains(viewPoint: ViewPoint<T>): Boolean = with(space) {
|
override fun contains(viewPoint: ViewPoint<T>): Boolean = with(space) {
|
||||||
viewPoint.focus in getBoundingBox(viewPoint.zoom) && viewPoint.focus.distanceToLine(
|
viewPoint.focus in getBoundingBox(viewPoint.zoom) && viewPoint.focus.distanceToLine(
|
||||||
@ -206,8 +207,8 @@ public data class LineFeature<T : Any>(
|
|||||||
public data class ArcFeature<T : Any>(
|
public data class ArcFeature<T : Any>(
|
||||||
override val space: CoordinateSpace<T>,
|
override val space: CoordinateSpace<T>,
|
||||||
public val oval: Rectangle<T>,
|
public val oval: Rectangle<T>,
|
||||||
public val startAngle: Float,
|
public val startAngle: Angle,
|
||||||
public val arcLength: Float,
|
public val arcLength: Angle,
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
) : DraggableFeature<T> {
|
) : DraggableFeature<T> {
|
||||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = oval
|
override fun getBoundingBox(zoom: Float): Rectangle<T> = oval
|
||||||
|
@ -12,6 +12,7 @@ import androidx.compose.ui.unit.Dp
|
|||||||
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.attributes.*
|
import center.sciprog.attributes.*
|
||||||
|
import space.kscience.kmath.geometry.Angle
|
||||||
|
|
||||||
//@JvmInline
|
//@JvmInline
|
||||||
//public value class FeatureId<out F : Feature<*>>(public val id: String)
|
//public value class FeatureId<out F : Feature<*>>(public val id: String)
|
||||||
@ -188,8 +189,8 @@ public fun <T : Any> FeatureGroup<T>.line(
|
|||||||
|
|
||||||
public fun <T : Any> FeatureGroup<T>.arc(
|
public fun <T : Any> FeatureGroup<T>.arc(
|
||||||
oval: Rectangle<T>,
|
oval: Rectangle<T>,
|
||||||
startAngle: Float,
|
startAngle: Angle,
|
||||||
arcLength: Float,
|
arcLength: Angle,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureRef<T, ArcFeature<T>> = feature(
|
): FeatureRef<T, ArcFeature<T>> = feature(
|
||||||
id,
|
id,
|
||||||
|
@ -11,17 +11,18 @@ public fun <T : Any> FeatureGroup<T>.draggableLine(
|
|||||||
var lineId: FeatureRef<T, LineFeature<T>>? = null
|
var lineId: FeatureRef<T, LineFeature<T>>? = null
|
||||||
|
|
||||||
fun drawLine(): FeatureRef<T, LineFeature<T>> {
|
fun drawLine(): FeatureRef<T, LineFeature<T>> {
|
||||||
//save attributes before update
|
val currentId = feature(
|
||||||
val attributes: Attributes? = lineId?.attributes
|
lineId?.id ?: id,
|
||||||
val currentId = line(
|
LineFeature(
|
||||||
|
space,
|
||||||
aId.resolve().center,
|
aId.resolve().center,
|
||||||
bId.resolve().center,
|
bId.resolve().center,
|
||||||
lineId?.id ?: id
|
Attributes {
|
||||||
)
|
|
||||||
currentId.modifyAttributes {
|
|
||||||
ZAttribute(-10f)
|
ZAttribute(-10f)
|
||||||
if (attributes != null) from(attributes)
|
lineId?.attributes?.let { from(it) }
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
lineId = currentId
|
lineId = currentId
|
||||||
return currentId
|
return currentId
|
||||||
}
|
}
|
||||||
|
@ -57,8 +57,6 @@ public fun <T : Any, F : Feature<T>, V> FeatureRef<T, F>.modifyAttribute(key: A
|
|||||||
* Add drag to this feature
|
* Add drag to this feature
|
||||||
*
|
*
|
||||||
* @param constraint optional drag constraint
|
* @param constraint optional drag constraint
|
||||||
*
|
|
||||||
* TODO use context receiver for that
|
|
||||||
*/
|
*/
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
public fun <T: Any, F : DraggableFeature<T>> FeatureRef<T, F>.draggable(
|
public fun <T: Any, F : DraggableFeature<T>> FeatureRef<T, F>.draggable(
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package center.sciprog.maps.compose
|
package center.sciprog.maps.compose
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.gestures.drag
|
import androidx.compose.foundation.gestures.drag
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
@ -17,7 +16,6 @@ import kotlin.math.min
|
|||||||
* Create a modifier for Map/Scheme canvas controls on desktop
|
* Create a modifier for Map/Scheme canvas controls on desktop
|
||||||
* @param features a collection of features to be rendered in descending [ZAttribute] order
|
* @param features a collection of features to be rendered in descending [ZAttribute] order
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
public fun <T : Any> Modifier.mapControls(
|
public fun <T : Any> Modifier.mapControls(
|
||||||
state: CoordinateViewScope<T>,
|
state: CoordinateViewScope<T>,
|
||||||
features: FeatureGroup<T>,
|
features: FeatureGroup<T>,
|
||||||
@ -55,7 +53,7 @@ public fun <T : Any> Modifier.mapControls(
|
|||||||
point
|
point
|
||||||
)
|
)
|
||||||
features.forEachWithAttributeUntil(ClickListenerAttribute) { _, feature, listeners ->
|
features.forEachWithAttributeUntil(ClickListenerAttribute) { _, feature, listeners ->
|
||||||
if (point in feature as DomainFeature) {
|
if (point in (feature as DomainFeature)) {
|
||||||
listeners.forEach { it.handle(event, point) }
|
listeners.forEach { it.handle(event, point) }
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
|
@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.toArgb
|
|||||||
import center.sciprog.attributes.plus
|
import center.sciprog.attributes.plus
|
||||||
import org.jetbrains.skia.Font
|
import org.jetbrains.skia.Font
|
||||||
import org.jetbrains.skia.Paint
|
import org.jetbrains.skia.Paint
|
||||||
|
import space.kscience.kmath.geometry.degrees
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
|
|
||||||
|
|
||||||
@ -56,8 +57,8 @@ public fun <T : Any> DrawScope.drawFeature(
|
|||||||
|
|
||||||
drawArc(
|
drawArc(
|
||||||
color = color,
|
color = color,
|
||||||
startAngle = feature.startAngle / PI.toFloat() * 180f,
|
startAngle = (feature.startAngle.degrees / PI * 180).toFloat(),
|
||||||
sweepAngle = feature.arcLength / PI.toFloat() * 180f,
|
sweepAngle = (feature.arcLength.degrees / PI * 180).toFloat(),
|
||||||
useCenter = false,
|
useCenter = false,
|
||||||
topLeft = dpRect.topLeft,
|
topLeft = dpRect.topLeft,
|
||||||
size = size,
|
size = size,
|
||||||
|
@ -9,6 +9,7 @@ import androidx.compose.ui.unit.DpSize
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import center.sciprog.attributes.Attributes
|
import center.sciprog.attributes.Attributes
|
||||||
import center.sciprog.maps.features.*
|
import center.sciprog.maps.features.*
|
||||||
|
import space.kscience.kmath.geometry.Angle
|
||||||
|
|
||||||
internal fun Pair<Number, Number>.toCoordinates(): XY = XY(first.toFloat(), second.toFloat())
|
internal fun Pair<Number, Number>.toCoordinates(): XY = XY(first.toFloat(), second.toFloat())
|
||||||
|
|
||||||
@ -56,8 +57,8 @@ fun FeatureGroup<XY>.line(
|
|||||||
public fun FeatureGroup<XY>.arc(
|
public fun FeatureGroup<XY>.arc(
|
||||||
center: Pair<Double, Double>,
|
center: Pair<Double, Double>,
|
||||||
radius: Float,
|
radius: Float,
|
||||||
startAngle: Float,
|
startAngle: Angle,
|
||||||
arcLength: Float,
|
arcLength: Angle,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureRef<XY, ArcFeature<XY>> = arc(
|
): FeatureRef<XY, ArcFeature<XY>> = arc(
|
||||||
oval = XYCoordinateSpace.Rectangle(center.toCoordinates(), radius, radius),
|
oval = XYCoordinateSpace.Rectangle(center.toCoordinates(), radius, radius),
|
||||||
|
@ -12,8 +12,8 @@ import center.sciprog.maps.features.*
|
|||||||
import center.sciprog.maps.scheme.*
|
import center.sciprog.maps.scheme.*
|
||||||
import org.jfree.svg.SVGGraphics2D
|
import org.jfree.svg.SVGGraphics2D
|
||||||
import org.jfree.svg.SVGUtils
|
import org.jfree.svg.SVGUtils
|
||||||
|
import space.kscience.kmath.geometry.degrees
|
||||||
import java.awt.Font.PLAIN
|
import java.awt.Font.PLAIN
|
||||||
import kotlin.math.PI
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ class FeatureStateSnapshot<T : Any>(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T: Any> FeatureGroup<T>.snapshot(): FeatureStateSnapshot<T> = FeatureStateSnapshot(
|
fun <T : Any> FeatureGroup<T>.snapshot(): FeatureStateSnapshot<T> = FeatureStateSnapshot(
|
||||||
featureMap,
|
featureMap,
|
||||||
features.filterIsInstance<PainterFeature<T>>().associateWith { it.getPainter() }
|
features.filterIsInstance<PainterFeature<T>>().associateWith { it.getPainter() }
|
||||||
)
|
)
|
||||||
@ -44,7 +44,7 @@ fun FeatureStateSnapshot<XY>.generateSvg(
|
|||||||
fun SvgDrawScope.drawFeature(scale: Float, feature: Feature<XY>) {
|
fun SvgDrawScope.drawFeature(scale: Float, feature: Feature<XY>) {
|
||||||
|
|
||||||
val color = feature.color ?: Color.Red
|
val color = feature.color ?: Color.Red
|
||||||
val alpha = feature.attributes[AlphaAttribute]?:1f
|
val alpha = feature.attributes[AlphaAttribute] ?: 1f
|
||||||
|
|
||||||
when (feature) {
|
when (feature) {
|
||||||
is ScalableImageFeature -> {
|
is ScalableImageFeature -> {
|
||||||
@ -66,10 +66,16 @@ fun FeatureStateSnapshot<XY>.generateSvg(
|
|||||||
is CircleFeature -> drawCircle(
|
is CircleFeature -> drawCircle(
|
||||||
color,
|
color,
|
||||||
feature.size.toPx(),
|
feature.size.toPx(),
|
||||||
center = feature.center.toOffset()
|
center = feature.center.toOffset(),
|
||||||
|
alpha = alpha
|
||||||
)
|
)
|
||||||
|
|
||||||
is LineFeature -> drawLine(color, feature.a.toOffset(), feature.b.toOffset())
|
is LineFeature -> drawLine(
|
||||||
|
color,
|
||||||
|
feature.a.toOffset(),
|
||||||
|
feature.b.toOffset(),
|
||||||
|
alpha = alpha
|
||||||
|
)
|
||||||
|
|
||||||
is ArcFeature -> {
|
is ArcFeature -> {
|
||||||
val topLeft = feature.oval.leftTop.toOffset()
|
val topLeft = feature.oval.leftTop.toOffset()
|
||||||
@ -79,12 +85,13 @@ fun FeatureStateSnapshot<XY>.generateSvg(
|
|||||||
|
|
||||||
drawArc(
|
drawArc(
|
||||||
color = color,
|
color = color,
|
||||||
startAngle = (feature.startAngle * 180 / PI).toFloat(),
|
startAngle = feature.startAngle.degrees.toFloat(),
|
||||||
sweepAngle = (feature.arcLength * 180 / PI).toFloat(),
|
sweepAngle = feature.arcLength.degrees.toFloat(),
|
||||||
useCenter = false,
|
useCenter = false,
|
||||||
topLeft = topLeft,
|
topLeft = topLeft,
|
||||||
size = size,
|
size = size,
|
||||||
style = Stroke()
|
style = Stroke(),
|
||||||
|
alpha = alpha
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,12 +102,12 @@ fun FeatureStateSnapshot<XY>.generateSvg(
|
|||||||
val imageSize = feature.size.toSize()
|
val imageSize = feature.size.toSize()
|
||||||
translate(offset.x - imageSize.width / 2, offset.y - imageSize.height / 2) {
|
translate(offset.x - imageSize.width / 2, offset.y - imageSize.height / 2) {
|
||||||
with(painterCache[feature]!!) {
|
with(painterCache[feature]!!) {
|
||||||
draw(imageSize)
|
draw(imageSize, alpha = alpha)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is TextFeature -> drawIntoCanvas { canvas ->
|
is TextFeature -> drawIntoCanvas { _ ->
|
||||||
val offset = feature.position.toOffset()
|
val offset = feature.position.toOffset()
|
||||||
drawText(
|
drawText(
|
||||||
feature.text,
|
feature.text,
|
||||||
|
Loading…
Reference in New Issue
Block a user