0.3.0 #23
@ -5,16 +5,15 @@ plugins {
|
|||||||
id("space.kscience.gradle.project")
|
id("space.kscience.gradle.project")
|
||||||
}
|
}
|
||||||
|
|
||||||
val kmathVersion: String by extra("0.3.1")
|
val kmathVersion: String by extra("0.4.0-RC2")
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "center.sciprog"
|
group = "center.sciprog"
|
||||||
version = "0.3.0-dev-1"
|
version = "0.3.0-dev-2"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
maven("https://repo.kotlin.link")
|
maven("https://repo.kotlin.link")
|
||||||
maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
plugins {
|
|
||||||
kotlin("multiplatform")
|
|
||||||
id("org.jetbrains.compose")
|
|
||||||
}
|
|
||||||
|
|
||||||
val ktorVersion: String by rootProject.extra
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
js {
|
|
||||||
browser()
|
|
||||||
binaries.executable()
|
|
||||||
}
|
|
||||||
sourceSets {
|
|
||||||
val jsMain by getting {
|
|
||||||
dependencies {
|
|
||||||
implementation(projects.mapsKtCompose)
|
|
||||||
implementation(compose.runtime)
|
|
||||||
implementation(compose.html.core)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compose {
|
|
||||||
web {}
|
|
||||||
}
|
|
@ -1,172 +0,0 @@
|
|||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.PointerMatcher
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.platform.LocalFontFamilyResolver
|
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
|
||||||
import androidx.compose.ui.text.font.createFontFamilyResolver
|
|
||||||
import androidx.compose.ui.unit.Density
|
|
||||||
import androidx.compose.ui.unit.DpSize
|
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import center.sciprog.attributes.Attributes
|
|
||||||
import center.sciprog.maps.compose.*
|
|
||||||
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
|
|
||||||
import center.sciprog.maps.coordinates.Gmc
|
|
||||||
import center.sciprog.maps.coordinates.kilometers
|
|
||||||
import center.sciprog.maps.features.*
|
|
||||||
import io.ktor.client.HttpClient
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.jetbrains.compose.web.renderComposable
|
|
||||||
import space.kscience.kmath.geometry.Angle
|
|
||||||
import space.kscience.kmath.geometry.degrees
|
|
||||||
import space.kscience.kmath.geometry.radians
|
|
||||||
import kotlin.math.PI
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
public fun GeodeticMapCoordinates.toShortString(): String =
|
|
||||||
"${(latitude.degrees).toString().take(6)}:${(longitude.degrees).toString().take(6)}"
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
@Composable
|
|
||||||
fun App() {
|
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
val mapTileProvider = remember {
|
|
||||||
OpenStreetMapTileProvider(
|
|
||||||
client = HttpClient(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val centerCoordinates = MutableStateFlow<Gmc?>(null)
|
|
||||||
|
|
||||||
val pointOne = 55.568548 to 37.568604
|
|
||||||
val pointTwo = 55.929444 to 37.518434
|
|
||||||
// val pointThree = 60.929444 to 37.518434
|
|
||||||
|
|
||||||
MapView(
|
|
||||||
mapTileProvider = mapTileProvider,
|
|
||||||
config = ViewConfig(
|
|
||||||
onViewChange = { centerCoordinates.value = focus },
|
|
||||||
onClick = { _, viewPoint ->
|
|
||||||
println(viewPoint)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
|
|
||||||
// icon(pointOne, Icons.Filled.Home)
|
|
||||||
|
|
||||||
val marker1 = rectangle(55.744 to 38.614, 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)
|
|
||||||
|
|
||||||
draggableLine(marker1, marker2, id = "line 1").color(Color.Red).onClick {
|
|
||||||
println("line 1 clicked")
|
|
||||||
}
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
multiLine(
|
|
||||||
points = listOf(
|
|
||||||
55.742465 to 37.615812,
|
|
||||||
55.742713 to 37.616370,
|
|
||||||
55.742815 to 37.616659,
|
|
||||||
55.742320 to 37.617132,
|
|
||||||
55.742086 to 37.616566,
|
|
||||||
55.741715 to 37.616716
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
//remember feature ref
|
|
||||||
val circleId = circle(
|
|
||||||
centerCoordinates = pointTwo,
|
|
||||||
)
|
|
||||||
scope.launch {
|
|
||||||
while (isActive) {
|
|
||||||
delay(200)
|
|
||||||
circleId.color(Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
arc(pointOne, 10.0.kilometers, (PI / 4).radians, -Angle.pi / 2)
|
|
||||||
|
|
||||||
|
|
||||||
line(pointOne, pointTwo, id = "line")
|
|
||||||
text(pointOne, "Home", font = { size = 32f })
|
|
||||||
|
|
||||||
|
|
||||||
pixelMap(
|
|
||||||
space.Rectangle(
|
|
||||||
Gmc(latitude = 55.58461879539754.degrees, longitude = 37.8746197303493.degrees),
|
|
||||||
Gmc(latitude = 55.442792937592415.degrees, longitude = 38.132240805463844.degrees)
|
|
||||||
),
|
|
||||||
0.005.degrees,
|
|
||||||
0.005.degrees
|
|
||||||
) { gmc ->
|
|
||||||
Color(
|
|
||||||
red = ((gmc.latitude + Angle.piDiv2).degrees * 10 % 1f).toFloat(),
|
|
||||||
green = ((gmc.longitude + Angle.pi).degrees * 10 % 1f).toFloat(),
|
|
||||||
blue = 0f,
|
|
||||||
alpha = 0.3f
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
centerCoordinates.filterNotNull().onEach {
|
|
||||||
group(id = "center") {
|
|
||||||
circle(center = it, id = "circle", size = 1.dp).color(Color.Blue)
|
|
||||||
text(position = it, it.toShortString(), id = "text").color(Color.Blue)
|
|
||||||
}
|
|
||||||
}.launchIn(scope)
|
|
||||||
|
|
||||||
//Add click listeners for all polygons
|
|
||||||
forEachWithType<Gmc, PolygonFeature<Gmc>> { ref ->
|
|
||||||
ref.onClick(PointerMatcher.Primary) {
|
|
||||||
println("Click on ${ref.id}")
|
|
||||||
//draw in top-level scope
|
|
||||||
with(this@MapView) {
|
|
||||||
multiLine(
|
|
||||||
ref.resolve().points,
|
|
||||||
attributes = Attributes(ZAttribute, 10f),
|
|
||||||
id = "selected",
|
|
||||||
).modifyAttribute(StrokeAttribute, 4f).color(Color.Magenta)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
renderComposable(rootElementId = "root") {
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalDensity provides Density(1.0f),
|
|
||||||
LocalLayoutDirection provides LayoutDirection.Ltr,
|
|
||||||
// LocalViewConfiguration provides DefaultViewConfiguration(Density(1.0f)),
|
|
||||||
// LocalInputModeManager provides InputModeManagerObject,
|
|
||||||
LocalFontFamilyResolver provides createFontFamilyResolver()
|
|
||||||
) {
|
|
||||||
App()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Maps-kt demo</title>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"/>
|
|
||||||
</body>
|
|
||||||
<script src="maps-js.js"></script>
|
|
||||||
</html>
|
|
30
demo/maps-wasm/build.gradle.kts
Normal file
30
demo/maps-wasm/build.gradle.kts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
kotlin("multiplatform")
|
||||||
|
id("org.jetbrains.compose")
|
||||||
|
}
|
||||||
|
|
||||||
|
//val ktorVersion: String by rootProject.extra
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
@OptIn(ExperimentalWasmDsl::class)
|
||||||
|
wasmJs {
|
||||||
|
browser()
|
||||||
|
binaries.executable()
|
||||||
|
}
|
||||||
|
sourceSets {
|
||||||
|
val wasmJsMain by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(projects.mapsKtScheme)
|
||||||
|
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) api(compose.components.resources)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compose {
|
||||||
|
experimental.web{
|
||||||
|
application{}
|
||||||
|
}
|
||||||
|
}
|
79
demo/maps-wasm/src/wasmJsMain/kotlin/Main.kt
Normal file
79
demo/maps-wasm/src/wasmJsMain/kotlin/Main.kt
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
@file:OptIn(ExperimentalComposeUiApi::class, ExperimentalResourceApi::class)
|
||||||
|
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.window.CanvasBasedWindow
|
||||||
|
import center.sciprog.maps.features.FeatureGroup
|
||||||
|
import center.sciprog.maps.features.ViewConfig
|
||||||
|
import center.sciprog.maps.features.ViewPoint
|
||||||
|
import center.sciprog.maps.features.color
|
||||||
|
import center.sciprog.maps.scheme.*
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.jetbrains.compose.resources.DrawableResource
|
||||||
|
import org.jetbrains.compose.resources.ExperimentalResourceApi
|
||||||
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
import space.kscience.kmath.geometry.Angle
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun App() {
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
|
||||||
|
val features: FeatureGroup<XY> = FeatureGroup.remember(XYCoordinateSpace) {
|
||||||
|
background(1600f, 1200f) { painterResource(DrawableResource("middle-earth.jpg")) }
|
||||||
|
circle(410.52737 to 868.7676).color(Color.Blue)
|
||||||
|
text(410.52737 to 868.7676, "Shire").color(Color.Blue)
|
||||||
|
circle(1132.0881 to 394.99127).color(Color.Red)
|
||||||
|
text(1132.0881 to 394.99127, "Ordruin").color(Color.Red)
|
||||||
|
arc(center = 1132.0881 to 394.99127, radius = 20f, startAngle = Angle.zero, Angle.piTimes2)
|
||||||
|
|
||||||
|
//circle(410.52737 to 868.7676, id = "hobbit")
|
||||||
|
|
||||||
|
scope.launch {
|
||||||
|
var t = 0.0
|
||||||
|
while (isActive) {
|
||||||
|
val x = 410.52737 + t * (1132.0881 - 410.52737)
|
||||||
|
val y = 868.7676 + t * (394.99127 - 868.7676)
|
||||||
|
circle(x to y, id = "hobbit").color(Color.Green)
|
||||||
|
delay(100)
|
||||||
|
t += 0.005
|
||||||
|
if (t >= 1.0) t = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val initialViewPoint: ViewPoint<XY> = remember {
|
||||||
|
features.getBoundingBox(1f)?.computeViewPoint() ?: XYViewPoint(XY(0f, 0f))
|
||||||
|
}
|
||||||
|
|
||||||
|
var viewPoint: ViewPoint<XY> by remember { mutableStateOf(initialViewPoint) }
|
||||||
|
|
||||||
|
val mapState: XYCanvasState = XYCanvasState.remember(
|
||||||
|
ViewConfig(
|
||||||
|
onClick = { _, click ->
|
||||||
|
println("${click.focus.x}, ${click.focus.y}")
|
||||||
|
},
|
||||||
|
onViewChange = { viewPoint = this }
|
||||||
|
),
|
||||||
|
initialViewPoint = initialViewPoint,
|
||||||
|
)
|
||||||
|
|
||||||
|
SchemeView(
|
||||||
|
mapState,
|
||||||
|
features,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
// renderComposable(rootElementId = "root") {
|
||||||
|
CanvasBasedWindow("Maps demo", canvasElementId = "ComposeTarget") {
|
||||||
|
App()
|
||||||
|
}
|
||||||
|
}
|
12
demo/maps-wasm/src/wasmJsMain/resources/index.html
Normal file
12
demo/maps-wasm/src/wasmJsMain/resources/index.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Compose App</title>
|
||||||
|
<script type="application/javascript" src="skiko.js"></script>
|
||||||
|
<script type="application/javascript" src="maps-wasm.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="ComposeTarget"></canvas>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
demo/maps-wasm/src/wasmJsMain/resources/middle-earth.jpg
Normal file
BIN
demo/maps-wasm/src/wasmJsMain/resources/middle-earth.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 469 KiB |
@ -13,7 +13,6 @@ import androidx.compose.ui.unit.DpSize
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Window
|
import androidx.compose.ui.window.Window
|
||||||
import androidx.compose.ui.window.application
|
import androidx.compose.ui.window.application
|
||||||
import center.sciprog.attributes.Attributes
|
|
||||||
import center.sciprog.maps.compose.*
|
import center.sciprog.maps.compose.*
|
||||||
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
|
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
|
||||||
import center.sciprog.maps.coordinates.Gmc
|
import center.sciprog.maps.coordinates.Gmc
|
||||||
@ -29,6 +28,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import space.kscience.attributes.Attributes
|
||||||
import space.kscience.kmath.geometry.Angle
|
import space.kscience.kmath.geometry.Angle
|
||||||
import space.kscience.kmath.geometry.degrees
|
import space.kscience.kmath.geometry.degrees
|
||||||
import space.kscience.kmath.geometry.radians
|
import space.kscience.kmath.geometry.radians
|
||||||
@ -37,7 +37,7 @@ import kotlin.math.PI
|
|||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
public fun GeodeticMapCoordinates.toShortString(): String =
|
public fun GeodeticMapCoordinates.toShortString(): String =
|
||||||
"${(latitude.degrees).toString().take(6)}:${(longitude.degrees).toString().take(6)}"
|
"${(latitude.toDegrees().value).toString().take(6)}:${(longitude.toDegrees().value).toString().take(6)}"
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@ -151,8 +151,8 @@ fun App() {
|
|||||||
0.005.degrees
|
0.005.degrees
|
||||||
) { gmc ->
|
) { gmc ->
|
||||||
Color(
|
Color(
|
||||||
red = ((gmc.latitude + Angle.piDiv2).degrees * 10 % 1f).toFloat(),
|
red = ((gmc.latitude + Angle.piDiv2).toDegrees().value * 10 % 1f).toFloat(),
|
||||||
green = ((gmc.longitude + Angle.pi).degrees * 10 % 1f).toFloat(),
|
green = ((gmc.longitude + Angle.pi).toDegrees().value * 10 % 1f).toFloat(),
|
||||||
blue = 0f,
|
blue = 0f,
|
||||||
alpha = 0.3f
|
alpha = 0.3f
|
||||||
)
|
)
|
||||||
|
BIN
demo/scheme/src/jvmMain/resources/joker2023.png
Normal file
BIN
demo/scheme/src/jvmMain/resources/joker2023.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 968 KiB |
@ -12,13 +12,13 @@ import center.sciprog.maps.features.*
|
|||||||
import center.sciprog.maps.scheme.SchemeView
|
import center.sciprog.maps.scheme.SchemeView
|
||||||
import center.sciprog.maps.scheme.XY
|
import center.sciprog.maps.scheme.XY
|
||||||
import space.kscience.kmath.geometry.Angle
|
import space.kscience.kmath.geometry.Angle
|
||||||
import space.kscience.kmath.geometry.Circle2D
|
import space.kscience.kmath.geometry.Vector2D
|
||||||
import space.kscience.kmath.geometry.DoubleVector2D
|
import space.kscience.kmath.geometry.euclidean2d.Circle2D
|
||||||
import space.kscience.kmath.geometry.Euclidean2DSpace
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
|
||||||
import space.kscience.trajectory.*
|
import space.kscience.trajectory.*
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
private fun DoubleVector2D.toXY() = XY(x.toFloat(), y.toFloat())
|
private fun Vector2D<out Number>.toXY() = XY(x.toFloat(), y.toFloat())
|
||||||
|
|
||||||
private val random = Random(123)
|
private val random = Random(123)
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ fun FeatureGroup<XY>.trajectory(
|
|||||||
bCoordinates = trajectory.end.toXY(),
|
bCoordinates = trajectory.end.toXY(),
|
||||||
).color(colorPicker(trajectory))
|
).color(colorPicker(trajectory))
|
||||||
|
|
||||||
is CircleTrajectory2D -> with(Euclidean2DSpace) {
|
is CircleTrajectory2D -> with(Float64Space2D) {
|
||||||
val topLeft = trajectory.circle.center + vector(-trajectory.circle.radius, trajectory.circle.radius)
|
val topLeft = trajectory.circle.center + vector(-trajectory.circle.radius, trajectory.circle.radius)
|
||||||
val bottomRight = trajectory.circle.center + vector(trajectory.circle.radius, -trajectory.circle.radius)
|
val bottomRight = trajectory.circle.center + vector(trajectory.circle.radius, -trajectory.circle.radius)
|
||||||
|
|
||||||
@ -59,20 +59,20 @@ fun FeatureGroup<XY>.obstacle(obstacle: Obstacle, colorPicker: (Trajectory2D) ->
|
|||||||
polygon(obstacle.arcs.map { it.center.toXY() }).color(Color.Gray)
|
polygon(obstacle.arcs.map { it.center.toXY() }).color(Color.Gray)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun FeatureGroup<XY>.pose(pose2D: Pose2D) = with(Euclidean2DSpace) {
|
fun FeatureGroup<XY>.pose(pose2D: Pose2D) = with(Float64Space2D) {
|
||||||
line(pose2D.toXY(), (pose2D + Pose2D.bearingToVector(pose2D.bearing)).toXY())
|
line(pose2D.toXY(), (pose2D + Pose2D.bearingToVector(pose2D.bearing)).toXY())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun closePoints() {
|
fun closePoints() = with(Float64Space2D){
|
||||||
SchemeView {
|
SchemeView {
|
||||||
|
|
||||||
val obstacle = Obstacle(
|
val obstacle = Obstacle(
|
||||||
Circle2D(Euclidean2DSpace.vector(0.0, 0.0), 1.0),
|
Circle2D(vector(0.0, 0.0), 1.0),
|
||||||
Circle2D(Euclidean2DSpace.vector(0.0, 1.0), 1.0),
|
Circle2D(vector(0.0, 1.0), 1.0),
|
||||||
Circle2D(Euclidean2DSpace.vector(1.0, 1.0), 1.0),
|
Circle2D(vector(1.0, 1.0), 1.0),
|
||||||
Circle2D(Euclidean2DSpace.vector(1.0, 0.0), 1.0)
|
Circle2D(vector(1.0, 0.0), 1.0)
|
||||||
)
|
)
|
||||||
val enter = Pose2D(-0.8, -0.8, Angle.pi)
|
val enter = Pose2D(-0.8, -0.8, Angle.pi)
|
||||||
val exit = Pose2D(-0.8, -0.8, Angle.piDiv2)
|
val exit = Pose2D(-0.8, -0.8, Angle.piDiv2)
|
||||||
@ -101,7 +101,7 @@ fun closePoints() {
|
|||||||
@Preview
|
@Preview
|
||||||
fun singleObstacle() {
|
fun singleObstacle() {
|
||||||
SchemeView {
|
SchemeView {
|
||||||
val obstacle = Obstacle(Circle2D(Euclidean2DSpace.vector(7.0, 1.0), 5.0))
|
val obstacle = Obstacle(Circle2D(Float64Space2D.vector(7.0, 1.0), 5.0))
|
||||||
val enter = Pose2D(-5, -1, Angle.pi / 4)
|
val enter = Pose2D(-5, -1, Angle.pi / 4)
|
||||||
val exit = Pose2D(20, 4, Angle.pi * 3 / 4)
|
val exit = Pose2D(20, 4, Angle.pi * 3 / 4)
|
||||||
|
|
||||||
@ -123,19 +123,19 @@ fun singleObstacle() {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun doubleObstacle() {
|
fun doubleObstacle() = with(Float64Space2D){
|
||||||
SchemeView {
|
SchemeView {
|
||||||
val obstacles = arrayOf(
|
val obstacles = arrayOf(
|
||||||
Obstacle(
|
Obstacle(
|
||||||
Circle2D(Euclidean2DSpace.vector(1.0, 6.5), 0.5),
|
Circle2D(vector(1.0, 6.5), 0.5),
|
||||||
Circle2D(Euclidean2DSpace.vector(2.0, 1.0), 0.5),
|
Circle2D(vector(2.0, 1.0), 0.5),
|
||||||
Circle2D(Euclidean2DSpace.vector(6.0, 0.0), 0.5),
|
Circle2D(vector(6.0, 0.0), 0.5),
|
||||||
Circle2D(Euclidean2DSpace.vector(5.0, 5.0), 0.5)
|
Circle2D(vector(5.0, 5.0), 0.5)
|
||||||
), Obstacle(
|
), Obstacle(
|
||||||
Circle2D(Euclidean2DSpace.vector(10.0, 1.0), 0.5),
|
Circle2D(vector(10.0, 1.0), 0.5),
|
||||||
Circle2D(Euclidean2DSpace.vector(16.0, 0.0), 0.5),
|
Circle2D(vector(16.0, 0.0), 0.5),
|
||||||
Circle2D(Euclidean2DSpace.vector(14.0, 6.0), 0.5),
|
Circle2D(vector(14.0, 6.0), 0.5),
|
||||||
Circle2D(Euclidean2DSpace.vector(9.0, 4.0), 0.5)
|
Circle2D(vector(9.0, 4.0), 0.5)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
|
|
||||||
compose.version=1.5.10
|
compose.version=1.6.0-rc02
|
||||||
|
org.jetbrains.compose.experimental.wasm.enabled=true
|
||||||
org.jetbrains.compose.experimental.jscanvas.enabled=true
|
org.jetbrains.compose.experimental.jscanvas.enabled=true
|
||||||
|
|
||||||
agp.version=8.1.0
|
agp.version=8.1.0
|
||||||
@ -9,4 +10,4 @@ android.enableJetifier=true
|
|||||||
|
|
||||||
org.gradle.jvmargs=-Xmx4096m
|
org.gradle.jvmargs=-Xmx4096m
|
||||||
|
|
||||||
toolsVersion=0.15.0-kotlin-1.9.20
|
toolsVersion=0.15.2-kotlin-1.9.22
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@ -7,7 +7,8 @@ plugins {
|
|||||||
|
|
||||||
kscience {
|
kscience {
|
||||||
jvm()
|
jvm()
|
||||||
js()
|
wasm()
|
||||||
|
|
||||||
useCoroutines()
|
useCoroutines()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,12 +28,12 @@ kotlin {
|
|||||||
api("io.ktor:ktor-client-cio")
|
api("io.ktor:ktor-client-cio")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//
|
||||||
getByName("jsMain"){
|
// getByName("jsMain"){
|
||||||
dependencies {
|
// dependencies {
|
||||||
api("io.ktor:ktor-client-js")
|
// api("io.ktor:ktor-client-js")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
getByName("jvmTest") {
|
getByName("jvmTest") {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -61,8 +61,8 @@ public class MapCanvasState private constructor(
|
|||||||
override fun computeViewPoint(rectangle: Rectangle<Gmc>): ViewPoint<Gmc> {
|
override fun computeViewPoint(rectangle: Rectangle<Gmc>): ViewPoint<Gmc> {
|
||||||
val zoom = log2(
|
val zoom = log2(
|
||||||
min(
|
min(
|
||||||
canvasSize.width.value / rectangle.longitudeDelta.radians,
|
canvasSize.width.value / rectangle.longitudeDelta.toRadians().value,
|
||||||
canvasSize.height.value / rectangle.latitudeDelta.radians
|
canvasSize.height.value / rectangle.latitudeDelta.toRadians().value
|
||||||
) * 2 * PI / mapTileProvider.tileSize
|
) * 2 * PI / mapTileProvider.tileSize
|
||||||
).coerceIn(0.0..22.0)
|
).coerceIn(0.0..22.0)
|
||||||
return space.ViewPoint(rectangle.center, zoom.toFloat())
|
return space.ViewPoint(rectangle.center, zoom.toFloat())
|
||||||
|
@ -8,6 +8,7 @@ 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.*
|
||||||
import center.sciprog.maps.features.*
|
import center.sciprog.maps.features.*
|
||||||
|
import org.jetbrains.skia.Font
|
||||||
import space.kscience.kmath.geometry.Angle
|
import space.kscience.kmath.geometry.Angle
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
|
|
||||||
@ -139,7 +140,7 @@ public fun FeatureGroup<Gmc>.icon(
|
|||||||
public fun FeatureGroup<Gmc>.text(
|
public fun FeatureGroup<Gmc>.text(
|
||||||
position: Pair<Double, Double>,
|
position: Pair<Double, Double>,
|
||||||
text: String,
|
text: String,
|
||||||
font: FeatureFont.() -> Unit = { size = 16f },
|
font: Font.() -> Unit = { size = 16f },
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureRef<Gmc, TextFeature<Gmc>> = feature(
|
): FeatureRef<Gmc, TextFeature<Gmc>> = feature(
|
||||||
id,
|
id,
|
||||||
|
@ -9,6 +9,8 @@ kscience{
|
|||||||
jvm()
|
jvm()
|
||||||
js()
|
js()
|
||||||
native()
|
native()
|
||||||
|
wasm()
|
||||||
|
|
||||||
useSerialization()
|
useSerialization()
|
||||||
|
|
||||||
dependencies{
|
dependencies{
|
||||||
|
@ -22,7 +22,6 @@ public class GeodeticMapCoordinates(
|
|||||||
"Longitude $longitude is not in (-PI..PI) range"
|
"Longitude $longitude is not in (-PI..PI) range"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val x: Angle get() = longitude
|
override val x: Angle get() = longitude
|
||||||
|
|
||||||
override val y: Angle get() = latitude
|
override val y: Angle get() = latitude
|
||||||
@ -43,7 +42,7 @@ public class GeodeticMapCoordinates(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "GMC(latitude=${latitude.degrees} deg, longitude=${longitude.degrees} deg)"
|
return "GMC(latitude=${latitude.toDegrees().value} deg, longitude=${longitude.toDegrees().value} deg)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ public fun GeoEllipsoid.parallelCurve(latitude: Angle, fromLongitude: Angle, toL
|
|||||||
return GmcCurve(
|
return GmcCurve(
|
||||||
forward = GmcPose(Gmc.normalized(latitude, fromLongitude), if (right) Angle.piDiv2 else -Angle.piDiv2),
|
forward = GmcPose(Gmc.normalized(latitude, fromLongitude), if (right) Angle.piDiv2 else -Angle.piDiv2),
|
||||||
backward = GmcPose(Gmc.normalized(latitude, toLongitude), if (right) -Angle.piDiv2 else Angle.piDiv2),
|
backward = GmcPose(Gmc.normalized(latitude, toLongitude), if (right) -Angle.piDiv2 else Angle.piDiv2),
|
||||||
distance = reducedRadius(latitude) * abs((fromLongitude - toLongitude).radians)
|
distance = reducedRadius(latitude) * abs((fromLongitude - toLongitude).toRadians().value)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,12 +57,12 @@ public open class MercatorProjection(
|
|||||||
return if (ellipsoid === GeoEllipsoid.sphere) {
|
return if (ellipsoid === GeoEllipsoid.sphere) {
|
||||||
GeodeticMapCoordinates.ofRadians(
|
GeodeticMapCoordinates.ofRadians(
|
||||||
atan(sinh(pc.y / ellipsoid.equatorRadius)),
|
atan(sinh(pc.y / ellipsoid.equatorRadius)),
|
||||||
baseLongitude.radians + (pc.x / ellipsoid.equatorRadius),
|
baseLongitude.toRadians().value + (pc.x / ellipsoid.equatorRadius),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
GeodeticMapCoordinates.ofRadians(
|
GeodeticMapCoordinates.ofRadians(
|
||||||
cphi2(exp(-(pc.y / ellipsoid.equatorRadius))),
|
cphi2(exp(-(pc.y / ellipsoid.equatorRadius))),
|
||||||
baseLongitude.radians + (pc.x / ellipsoid.equatorRadius)
|
baseLongitude.toRadians().value + (pc.x / ellipsoid.equatorRadius)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,13 +76,13 @@ public open class MercatorProjection(
|
|||||||
|
|
||||||
return if (ellipsoid === GeoEllipsoid.sphere) {
|
return if (ellipsoid === GeoEllipsoid.sphere) {
|
||||||
ProjectionCoordinates(
|
ProjectionCoordinates(
|
||||||
x = ellipsoid.equatorRadius * (gmc.longitude - baseLongitude).radians,
|
x = ellipsoid.equatorRadius * (gmc.longitude - baseLongitude).toRadians().value,
|
||||||
y = ellipsoid.equatorRadius * ln(tan(Angle.pi / 4 + gmc.latitude / 2))
|
y = ellipsoid.equatorRadius * ln(tan(Angle.pi / 4 + gmc.latitude / 2))
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val sinPhi = sin(gmc.latitude)
|
val sinPhi = sin(gmc.latitude)
|
||||||
ProjectionCoordinates(
|
ProjectionCoordinates(
|
||||||
x = ellipsoid.equatorRadius * (gmc.longitude - baseLongitude).radians,
|
x = ellipsoid.equatorRadius * (gmc.longitude - baseLongitude).toRadians().value,
|
||||||
y = ellipsoid.equatorRadius * ln(
|
y = ellipsoid.equatorRadius * ln(
|
||||||
tan(Angle.pi / 4 + gmc.latitude / 2) * ((1 - e * sinPhi) / (1 + e * sinPhi)).pow(e / 2)
|
tan(Angle.pi / 4 + gmc.latitude / 2) * ((1 - e * sinPhi) / (1 + e * sinPhi)).pow(e / 2)
|
||||||
)
|
)
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
package center.sciprog.maps.coordinates
|
package center.sciprog.maps.coordinates
|
||||||
|
|
||||||
import space.kscience.kmath.geometry.abs
|
import space.kscience.kmath.geometry.abs
|
||||||
import space.kscience.kmath.geometry.radians
|
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
public data class WebMercatorCoordinates(val zoom: Int, val x: Float, val y: Float)
|
public data class WebMercatorCoordinates(val zoom: Int, val x: Float, val y: Float)
|
||||||
@ -36,8 +35,8 @@ public object WebMercatorProjection {
|
|||||||
val scaleFactor = scaleFactor(zoom.toFloat())
|
val scaleFactor = scaleFactor(zoom.toFloat())
|
||||||
return WebMercatorCoordinates(
|
return WebMercatorCoordinates(
|
||||||
zoom = zoom,
|
zoom = zoom,
|
||||||
x = scaleFactor * (gmc.longitude.radians + PI).toFloat(),
|
x = scaleFactor * (gmc.longitude.toRadians().value + PI).toFloat(),
|
||||||
y = scaleFactor * (PI - ln(tan(PI / 4 + gmc.latitude.radians / 2))).toFloat()
|
y = scaleFactor * (PI - ln(tan(PI / 4 + gmc.latitude.toRadians().value / 2))).toFloat()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ internal class DistanceTest {
|
|||||||
val distance = curve.distance
|
val distance = curve.distance
|
||||||
|
|
||||||
assertEquals(632.035426877, distance.kilometers, 0.0001)
|
assertEquals(632.035426877, distance.kilometers, 0.0001)
|
||||||
assertEquals(-0.6947937116552751, curve.forward.bearing.radians, 0.0001)
|
assertEquals(-0.6947937116552751, curve.forward.bearing.toRadians().value, 0.0001)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -30,7 +30,7 @@ internal class DistanceTest {
|
|||||||
GmcPose(moscow, (-0.6947937116552751).radians), Distance(632.035426877)
|
GmcPose(moscow, (-0.6947937116552751).radians), Distance(632.035426877)
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(spb.latitude.radians, curve.backward.latitude.radians, 0.0001)
|
assertEquals(spb.latitude.toRadians().value, curve.backward.latitude.toRadians().value, 0.0001)
|
||||||
assertEquals(spb.longitude.radians, curve.backward.longitude.radians, 0.0001)
|
assertEquals(spb.longitude.toRadians().value, curve.backward.longitude.toRadians().value, 0.0001)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package center.sciprog.maps.coordinates
|
package center.sciprog.maps.coordinates
|
||||||
|
|
||||||
import space.kscience.kmath.geometry.degrees
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@ -13,8 +12,8 @@ class MercatorTest {
|
|||||||
assertEquals(4186.0120709, mercator.x.kilometers, 1e-4)
|
assertEquals(4186.0120709, mercator.x.kilometers, 1e-4)
|
||||||
assertEquals(7510.9013658, mercator.y.kilometers, 1e-4)
|
assertEquals(7510.9013658, mercator.y.kilometers, 1e-4)
|
||||||
val backwards = MapProjection.epsg3857.toGeodetic(mercator)
|
val backwards = MapProjection.epsg3857.toGeodetic(mercator)
|
||||||
assertEquals(moscow.latitude.degrees, backwards.latitude.degrees, 1e-6)
|
assertEquals(moscow.latitude.toDegrees().value, backwards.latitude.toDegrees().value, 1e-6)
|
||||||
assertEquals(moscow.longitude.degrees, backwards.longitude.degrees, 1e-6)
|
assertEquals(moscow.longitude.toDegrees().value, backwards.longitude.toDegrees().value, 1e-6)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -23,7 +22,7 @@ class MercatorTest {
|
|||||||
val projection = MercatorProjection(ellipsoid = GeoEllipsoid.WGS84)
|
val projection = MercatorProjection(ellipsoid = GeoEllipsoid.WGS84)
|
||||||
val mercator = projection.toProjection(moscow)
|
val mercator = projection.toProjection(moscow)
|
||||||
val backwards = projection.toGeodetic(mercator)
|
val backwards = projection.toGeodetic(mercator)
|
||||||
assertEquals(moscow.latitude.degrees, backwards.latitude.degrees, 1e-6)
|
assertEquals(moscow.latitude.toDegrees().value, backwards.latitude.toDegrees().value, 1e-6)
|
||||||
assertEquals(moscow.longitude.degrees, backwards.longitude.degrees, 1e-6)
|
assertEquals(moscow.longitude.toDegrees().value, backwards.longitude.toDegrees().value, 1e-6)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,14 +6,24 @@ plugins {
|
|||||||
|
|
||||||
val kmathVersion: String by rootProject.extra
|
val kmathVersion: String by rootProject.extra
|
||||||
|
|
||||||
kscience{
|
kscience {
|
||||||
jvm()
|
jvm()
|
||||||
js()
|
// js()
|
||||||
useSerialization{
|
wasm{
|
||||||
|
browser {
|
||||||
|
testTask {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useCoroutines()
|
||||||
|
|
||||||
|
useSerialization {
|
||||||
json()
|
json()
|
||||||
}
|
}
|
||||||
|
|
||||||
useSerialization(sourceSet = space.kscience.gradle.DependencySourceSet.TEST){
|
useSerialization(sourceSet = space.kscience.gradle.DependencySourceSet.TEST) {
|
||||||
protobuf()
|
protobuf()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,8 +33,11 @@ kotlin {
|
|||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api(projects.trajectoryKt)
|
api(projects.trajectoryKt)
|
||||||
|
api(compose.runtime)
|
||||||
api(compose.foundation)
|
api(compose.foundation)
|
||||||
api("io.github.oshai:kotlin-logging:5.1.0")
|
api(compose.material)
|
||||||
|
api(compose.ui)
|
||||||
|
api("io.github.oshai:kotlin-logging:6.0.3")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
package center.sciprog.attributes
|
|
||||||
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.builtins.serializer
|
|
||||||
|
|
||||||
public interface Attribute<T>
|
|
||||||
|
|
||||||
public abstract class SerializableAttribute<T>(
|
|
||||||
public val serialId: String,
|
|
||||||
public val serializer: KSerializer<T>,
|
|
||||||
) : Attribute<T> {
|
|
||||||
override fun toString(): String = serialId
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface AttributeWithDefault<T> : Attribute<T> {
|
|
||||||
public val default: T
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface SetAttribute<V> : Attribute<Set<V>>
|
|
||||||
|
|
||||||
public object NameAttribute : SerializableAttribute<String>("name", String.serializer())
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
|||||||
package center.sciprog.attributes
|
|
||||||
|
|
||||||
import center.sciprog.maps.features.Feature
|
|
||||||
import center.sciprog.maps.features.ZAttribute
|
|
||||||
import kotlin.jvm.JvmInline
|
|
||||||
|
|
||||||
@JvmInline
|
|
||||||
public value class Attributes internal constructor(public val content: Map<out Attribute<*>, Any>) {
|
|
||||||
|
|
||||||
public val keys: Set<Attribute<*>> get() = content.keys
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
public operator fun <T> get(attribute: Attribute<T>): T? = content[attribute] as? T
|
|
||||||
|
|
||||||
override fun toString(): String = "Attributes(value=${content.entries})"
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
public val EMPTY: Attributes = Attributes(emptyMap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun Attributes.isEmpty(): Boolean = content.isEmpty()
|
|
||||||
|
|
||||||
public fun <T> Attributes.getOrDefault(attribute: AttributeWithDefault<T>): T = get(attribute) ?: attribute.default
|
|
||||||
|
|
||||||
public fun <T, A : Attribute<T>> Attributes.withAttribute(
|
|
||||||
attribute: A,
|
|
||||||
attrValue: T?,
|
|
||||||
): Attributes = Attributes(
|
|
||||||
if (attrValue == null) {
|
|
||||||
content - attribute
|
|
||||||
} else {
|
|
||||||
content + (attribute to attrValue)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an element to a [SetAttribute]
|
|
||||||
*/
|
|
||||||
public fun <T, A : SetAttribute<T>> Attributes.withAttributeElement(
|
|
||||||
attribute: A,
|
|
||||||
attrValue: T,
|
|
||||||
): Attributes {
|
|
||||||
val currentSet: Set<T> = get(attribute) ?: emptySet()
|
|
||||||
return Attributes(
|
|
||||||
content + (attribute to (currentSet + attrValue))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove an element from [SetAttribute]
|
|
||||||
*/
|
|
||||||
public fun <T, A : SetAttribute<T>> Attributes.withoutAttributeElement(
|
|
||||||
attribute: A,
|
|
||||||
attrValue: T,
|
|
||||||
): Attributes {
|
|
||||||
val currentSet: Set<T> = get(attribute) ?: emptySet()
|
|
||||||
return Attributes(
|
|
||||||
content + (attribute to (currentSet - attrValue))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <T : Any, A : Attribute<T>> Attributes(
|
|
||||||
attribute: A,
|
|
||||||
attrValue: T,
|
|
||||||
): Attributes = Attributes(mapOf(attribute to attrValue))
|
|
||||||
|
|
||||||
public operator fun Attributes.plus(other: Attributes): Attributes = Attributes(content + other.content)
|
|
@ -1,47 +0,0 @@
|
|||||||
package center.sciprog.attributes
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A safe builder for [Attributes]
|
|
||||||
*/
|
|
||||||
public class AttributesBuilder internal constructor(private val map: MutableMap<Attribute<*>, Any> = mutableMapOf()) {
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
public operator fun <T> get(attribute: Attribute<T>): T? = map[attribute] as? T
|
|
||||||
|
|
||||||
public operator fun <V> Attribute<V>.invoke(value: V?) {
|
|
||||||
if (value == null) {
|
|
||||||
map.remove(this)
|
|
||||||
} else {
|
|
||||||
map[this] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun from(attributes: Attributes) {
|
|
||||||
map.putAll(attributes.content)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun <V> SetAttribute<V>.add(
|
|
||||||
attrValue: V,
|
|
||||||
) {
|
|
||||||
val currentSet: Set<V> = get(this) ?: emptySet()
|
|
||||||
map[this] = currentSet + attrValue
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove an element from [SetAttribute]
|
|
||||||
*/
|
|
||||||
public fun <V> SetAttribute<V>.remove(
|
|
||||||
attrValue: V,
|
|
||||||
) {
|
|
||||||
val currentSet: Set<V> = get(this) ?: emptySet()
|
|
||||||
map[this] = currentSet - attrValue
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun build(): Attributes = Attributes(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun AttributesBuilder(
|
|
||||||
attributes: Attributes,
|
|
||||||
): AttributesBuilder = AttributesBuilder(attributes.content.toMutableMap())
|
|
||||||
|
|
||||||
public fun Attributes(builder: AttributesBuilder.() -> Unit): Attributes = AttributesBuilder().apply(builder).build()
|
|
@ -17,8 +17,9 @@ import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
|||||||
import androidx.compose.ui.unit.Dp
|
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.Attributes
|
import org.jetbrains.skia.Font
|
||||||
import center.sciprog.attributes.NameAttribute
|
import space.kscience.NameAttribute
|
||||||
|
import space.kscience.attributes.Attributes
|
||||||
import space.kscience.kmath.geometry.Angle
|
import space.kscience.kmath.geometry.Angle
|
||||||
import space.kscience.kmath.nd.Structure2D
|
import space.kscience.kmath.nd.Structure2D
|
||||||
|
|
||||||
@ -331,7 +332,7 @@ public data class TextFeature<T : Any>(
|
|||||||
public val position: T,
|
public val position: T,
|
||||||
public val text: String,
|
public val text: String,
|
||||||
override val attributes: Attributes = Attributes.EMPTY,
|
override val attributes: Attributes = Attributes.EMPTY,
|
||||||
public val fontConfig: FeatureFont.() -> Unit,
|
public val fontConfig: Font.() -> Unit,
|
||||||
) : DraggableFeature<T> {
|
) : DraggableFeature<T> {
|
||||||
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(position, position)
|
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(position, position)
|
||||||
|
|
||||||
|
@ -15,8 +15,8 @@ import androidx.compose.ui.text.TextMeasurer
|
|||||||
import androidx.compose.ui.text.drawText
|
import androidx.compose.ui.text.drawText
|
||||||
import androidx.compose.ui.text.rememberTextMeasurer
|
import androidx.compose.ui.text.rememberTextMeasurer
|
||||||
import androidx.compose.ui.unit.DpRect
|
import androidx.compose.ui.unit.DpRect
|
||||||
import center.sciprog.attributes.Attributes
|
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import space.kscience.attributes.Attributes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An extension of [DrawScope] to include map-specific features
|
* An extension of [DrawScope] to include map-specific features
|
||||||
@ -52,7 +52,7 @@ public class ComposeFeatureDrawScope<T : Any>(
|
|||||||
) : FeatureDrawScope<T>(state), DrawScope by drawScope {
|
) : FeatureDrawScope<T>(state), DrawScope by drawScope {
|
||||||
override fun drawText(text: String, position: Offset, attributes: Attributes) {
|
override fun drawText(text: String, position: Offset, attributes: Attributes) {
|
||||||
try {
|
try {
|
||||||
drawText(textMeasurer?: error("Text measurer not defined"), text, position)
|
drawText(textMeasurer ?: error("Text measurer not defined"), text, position)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
logger.error(ex) { "Failed to measure text" }
|
logger.error(ex) { "Failed to measure text" }
|
||||||
}
|
}
|
||||||
@ -61,7 +61,7 @@ public class ComposeFeatureDrawScope<T : Any>(
|
|||||||
override fun painterFor(feature: PainterFeature<T>): Painter =
|
override fun painterFor(feature: PainterFeature<T>): Painter =
|
||||||
painterCache[feature] ?: error("Can't resolve painter for $feature")
|
painterCache[feature] ?: error("Can't resolve painter for $feature")
|
||||||
|
|
||||||
public companion object{
|
public companion object {
|
||||||
private val logger = KotlinLogging.logger("ComposeFeatureDrawScope")
|
private val logger = KotlinLogging.logger("ComposeFeatureDrawScope")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
package center.sciprog.maps.features
|
|
||||||
|
|
||||||
public expect class FeatureFont {
|
|
||||||
public var size: Float
|
|
||||||
}
|
|
@ -9,8 +9,9 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||||||
import androidx.compose.ui.unit.Dp
|
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.Attribute
|
import org.jetbrains.skia.Font
|
||||||
import center.sciprog.attributes.Attributes
|
import space.kscience.attributes.Attribute
|
||||||
|
import space.kscience.attributes.Attributes
|
||||||
import space.kscience.kmath.geometry.Angle
|
import space.kscience.kmath.geometry.Angle
|
||||||
import space.kscience.kmath.nd.*
|
import space.kscience.kmath.nd.*
|
||||||
import space.kscience.kmath.structures.Buffer
|
import space.kscience.kmath.structures.Buffer
|
||||||
@ -285,7 +286,7 @@ public fun <T : Any> FeatureGroup<T>.scalableImage(
|
|||||||
public fun <T : Any> FeatureGroup<T>.text(
|
public fun <T : Any> FeatureGroup<T>.text(
|
||||||
position: T,
|
position: T,
|
||||||
text: String,
|
text: String,
|
||||||
font: FeatureFont.() -> Unit = { size = 16f },
|
font: Font.() -> Unit = { size = 16f },
|
||||||
attributes: Attributes = Attributes.EMPTY,
|
attributes: Attributes = Attributes.EMPTY,
|
||||||
id: String? = null,
|
id: String? = null,
|
||||||
): FeatureRef<T, TextFeature<T>> = feature(
|
): FeatureRef<T, TextFeature<T>> = feature(
|
||||||
@ -293,14 +294,14 @@ public fun <T : Any> FeatureGroup<T>.text(
|
|||||||
TextFeature(space, position, text, fontConfig = font, attributes = attributes)
|
TextFeature(space, position, text, fontConfig = font, attributes = attributes)
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun <T> StructureND(shape: ShapeND, initializer: (IntArray) -> T): StructureND<T> {
|
//public fun <T> StructureND(shape: ShapeND, initializer: (IntArray) -> T): StructureND<T> {
|
||||||
val strides = Strides(shape)
|
// val strides = Strides(shape)
|
||||||
return BufferND(strides, Buffer.boxing(strides.linearSize) { initializer(strides.index(it)) })
|
// return BufferND(strides, Buffer(strides.linearSize) { initializer(strides.index(it)) })
|
||||||
}
|
//}
|
||||||
|
|
||||||
public fun <T> Structure2D(rows: Int, columns: Int, initializer: (IntArray) -> T): Structure2D<T> {
|
public inline fun <reified T> Structure2D(rows: Int, columns: Int, initializer: (IntArray) -> T): Structure2D<T> {
|
||||||
val strides = Strides(ShapeND(rows, columns))
|
val strides = Strides(ShapeND(rows, columns))
|
||||||
return BufferND(strides, Buffer.boxing(strides.linearSize) { initializer(strides.index(it)) }).as2D()
|
return BufferND(strides, Buffer(strides.linearSize) { initializer(strides.index(it)) }).as2D()
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <T : Any> FeatureGroup<T>.pixelMap(
|
public fun <T : Any> FeatureGroup<T>.pixelMap(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package center.sciprog.maps.features
|
package center.sciprog.maps.features
|
||||||
|
|
||||||
import center.sciprog.attributes.Attributes
|
import space.kscience.attributes.Attributes
|
||||||
import kotlin.jvm.JvmName
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ public fun <T : Any> FeatureGroup<T>.draggableLine(
|
|||||||
space,
|
space,
|
||||||
aId.resolve().center,
|
aId.resolve().center,
|
||||||
bId.resolve().center,
|
bId.resolve().center,
|
||||||
Attributes {
|
Attributes<FeatureGroup<T>> {
|
||||||
ZAttribute(-10f)
|
ZAttribute(-10f)
|
||||||
lineId?.attributes?.let { from(it) }
|
lineId?.attributes?.let { from(it) }
|
||||||
}
|
}
|
||||||
@ -51,7 +51,7 @@ public fun <T : Any> FeatureGroup<T>.draggableMultiLine(
|
|||||||
MultiLineFeature(
|
MultiLineFeature(
|
||||||
space,
|
space,
|
||||||
points.map { it.resolve().center },
|
points.map { it.resolve().center },
|
||||||
Attributes {
|
Attributes<FeatureGroup<T>>{
|
||||||
ZAttribute(-10f)
|
ZAttribute(-10f)
|
||||||
polygonId?.attributes?.let { from(it) }
|
polygonId?.attributes?.let { from(it) }
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,8 @@ import androidx.compose.ui.graphics.Path
|
|||||||
import androidx.compose.ui.graphics.PointMode
|
import androidx.compose.ui.graphics.PointMode
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.graphics.drawscope.translate
|
import androidx.compose.ui.graphics.drawscope.translate
|
||||||
import center.sciprog.attributes.plus
|
import space.kscience.attributes.plus
|
||||||
import space.kscience.kmath.PerformancePitfall
|
import space.kscience.kmath.PerformancePitfall
|
||||||
import space.kscience.kmath.geometry.degrees
|
|
||||||
|
|
||||||
|
|
||||||
//internal fun Color.toPaint(): Paint = Paint().apply {
|
//internal fun Color.toPaint(): Paint = Paint().apply {
|
||||||
@ -57,8 +56,8 @@ public fun <T : Any> FeatureDrawScope<T>.drawFeature(
|
|||||||
|
|
||||||
drawArc(
|
drawArc(
|
||||||
color = color,
|
color = color,
|
||||||
startAngle = (feature.startAngle.degrees).toFloat(),
|
startAngle = (feature.startAngle.toDegrees().value).toFloat(),
|
||||||
sweepAngle = (feature.arcLength.degrees).toFloat(),
|
sweepAngle = (feature.arcLength.toDegrees().value).toFloat(),
|
||||||
useCenter = false,
|
useCenter = false,
|
||||||
topLeft = dpRect.topLeft,
|
topLeft = dpRect.topLeft,
|
||||||
size = size,
|
size = size,
|
||||||
@ -91,7 +90,11 @@ public fun <T : Any> FeatureDrawScope<T>.drawFeature(
|
|||||||
|
|
||||||
is FeatureGroup -> {
|
is FeatureGroup -> {
|
||||||
feature.featureMap.values.forEach {
|
feature.featureMap.values.forEach {
|
||||||
drawFeature(it.withAttributes { feature.attributes + this })
|
drawFeature(
|
||||||
|
it.withAttributes {
|
||||||
|
feature.attributes + this
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,10 +6,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.PathEffect
|
import androidx.compose.ui.graphics.PathEffect
|
||||||
import androidx.compose.ui.input.pointer.PointerEvent
|
import androidx.compose.ui.input.pointer.PointerEvent
|
||||||
import androidx.compose.ui.input.pointer.PointerKeyboardModifiers
|
import androidx.compose.ui.input.pointer.PointerKeyboardModifiers
|
||||||
import center.sciprog.attributes.Attribute
|
import space.kscience.attributes.*
|
||||||
import center.sciprog.attributes.AttributesBuilder
|
|
||||||
import center.sciprog.attributes.SetAttribute
|
|
||||||
import center.sciprog.attributes.withAttribute
|
|
||||||
|
|
||||||
public object ZAttribute : Attribute<Float>
|
public object ZAttribute : Attribute<Float>
|
||||||
|
|
||||||
@ -50,21 +47,19 @@ public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.zoomRange(range: FloatRang
|
|||||||
public object AlphaAttribute : Attribute<Float>
|
public object AlphaAttribute : Attribute<Float>
|
||||||
|
|
||||||
public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.modifyAttributes(
|
public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.modifyAttributes(
|
||||||
modification: AttributesBuilder.() -> Unit
|
modification: AttributesBuilder<F>.() -> Unit,
|
||||||
): FeatureRef<T, F> {
|
): FeatureRef<T, F> {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
parent.feature(
|
parent.feature(
|
||||||
id,
|
id,
|
||||||
resolve().withAttributes {
|
resolve().withAttributes { modify(modification) } as F
|
||||||
AttributesBuilder(this).apply(modification).build()
|
|
||||||
} as F
|
|
||||||
)
|
)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <T : Any, F : Feature<T>, V> FeatureRef<T, F>.modifyAttribute(
|
public fun <T : Any, F : Feature<T>, V> FeatureRef<T, F>.modifyAttribute(
|
||||||
key: Attribute<V>,
|
key: Attribute<V>,
|
||||||
value: V?,
|
value: V,
|
||||||
): FeatureRef<T, F> {
|
): FeatureRef<T, F> {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
parent.feature(id, resolve().withAttributes { withAttribute(key, value) } as F)
|
parent.feature(id, resolve().withAttributes { withAttribute(key, value) } as F)
|
||||||
|
@ -1,13 +1,22 @@
|
|||||||
package center.sciprog.attributes
|
package center.sciprog.attributes
|
||||||
|
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.modules.SerializersModule
|
import kotlinx.serialization.modules.SerializersModule
|
||||||
import kotlinx.serialization.modules.contextual
|
import kotlinx.serialization.modules.contextual
|
||||||
import kotlinx.serialization.protobuf.ProtoBuf
|
import kotlinx.serialization.protobuf.ProtoBuf
|
||||||
|
import kotlinx.serialization.serializer
|
||||||
|
import space.kscience.AttributesSerializer
|
||||||
|
import space.kscience.NameAttribute
|
||||||
|
import space.kscience.SerializableAttribute
|
||||||
|
import space.kscience.attributes.Attributes
|
||||||
|
import space.kscience.equals
|
||||||
import kotlin.test.Ignore
|
import kotlin.test.Ignore
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
internal class AttributesSerializationTest {
|
internal class AttributesSerializationTest {
|
||||||
|
|
||||||
@ -28,32 +37,36 @@ internal class AttributesSerializationTest {
|
|||||||
override fun toString(): String = "test"
|
override fun toString(): String = "test"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val serializer = AttributesSerializer(setOf(NameAttribute, TestAttribute, ContainerAttribute))
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun restoreFromJson() {
|
fun restoreFromJson() {
|
||||||
|
|
||||||
val json = Json {
|
val json = Json {
|
||||||
serializersModule = SerializersModule {
|
serializersModule = SerializersModule {
|
||||||
contextual(AttributesSerializer(setOf(NameAttribute, TestAttribute, ContainerAttribute)))
|
contextual(serializer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val attributes = Attributes {
|
val attributes = Attributes<Any> {
|
||||||
NameAttribute("myTest")
|
NameAttribute("myTest")
|
||||||
TestAttribute(mapOf("a" to "aa", "b" to "bb"))
|
TestAttribute(mapOf("a" to "aa", "b" to "bb"))
|
||||||
ContainerAttribute(
|
ContainerAttribute(
|
||||||
Container(
|
Container(
|
||||||
Attributes {
|
Attributes<Any> {
|
||||||
TestAttribute(mapOf("a" to "aa", "b" to "bb"))
|
TestAttribute(mapOf("a" to "aa", "b" to "bb"))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val serialized: String = json.encodeToString(attributes)
|
|
||||||
|
val serialized: String = json.encodeToString(serializer, attributes)
|
||||||
println(serialized)
|
println(serialized)
|
||||||
|
|
||||||
val restored: Attributes = json.decodeFromString(serialized)
|
val restored: Attributes = json.decodeFromString(serializer, serialized)
|
||||||
|
|
||||||
assertEquals(attributes, restored)
|
assertTrue { Attributes.equals(attributes, restored) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
@ -62,25 +75,25 @@ internal class AttributesSerializationTest {
|
|||||||
fun restoreFromProtoBuf() {
|
fun restoreFromProtoBuf() {
|
||||||
val protoBuf = ProtoBuf {
|
val protoBuf = ProtoBuf {
|
||||||
serializersModule = SerializersModule {
|
serializersModule = SerializersModule {
|
||||||
contextual(AttributesSerializer(setOf(NameAttribute, TestAttribute, ContainerAttribute)))
|
contextual(serializer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val attributes = Attributes {
|
val attributes = Attributes<Any> {
|
||||||
NameAttribute("myTest")
|
NameAttribute("myTest")
|
||||||
TestAttribute(mapOf("a" to "aa", "b" to "bb"))
|
TestAttribute(mapOf("a" to "aa", "b" to "bb"))
|
||||||
ContainerAttribute(
|
ContainerAttribute(
|
||||||
Container(
|
Container(
|
||||||
Attributes {
|
Attributes<Any> {
|
||||||
TestAttribute(mapOf("a" to "aa", "b" to "bb"))
|
TestAttribute(mapOf("a" to "aa", "b" to "bb"))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val serialized = protoBuf.encodeToByteArray(attributes)
|
val serialized = protoBuf.encodeToByteArray(serializer, attributes)
|
||||||
|
|
||||||
val restored: Attributes = protoBuf.decodeFromByteArray(serialized)
|
val restored: Attributes = protoBuf.decodeFromByteArray(serializer, serialized)
|
||||||
|
|
||||||
assertEquals(attributes, restored)
|
assertEquals(attributes, restored)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
package center.sciprog.maps.features
|
|
||||||
|
|
||||||
public actual class FeatureFont {
|
|
||||||
public actual var size: Float = 16f
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
package center.sciprog.maps.features
|
|
||||||
|
|
||||||
import org.jetbrains.skia.Font
|
|
||||||
|
|
||||||
public actual typealias FeatureFont = Font
|
|
@ -6,7 +6,9 @@ plugins {
|
|||||||
|
|
||||||
kscience{
|
kscience{
|
||||||
jvm()
|
jvm()
|
||||||
js()
|
// js()
|
||||||
|
wasm()
|
||||||
|
|
||||||
useSerialization {
|
useSerialization {
|
||||||
json()
|
json()
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import center.sciprog.maps.coordinates.Gmc
|
|||||||
import center.sciprog.maps.coordinates.meters
|
import center.sciprog.maps.coordinates.meters
|
||||||
import center.sciprog.maps.geojson.GeoJsonGeometry.Companion.COORDINATES_KEY
|
import center.sciprog.maps.geojson.GeoJsonGeometry.Companion.COORDINATES_KEY
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
import space.kscience.kmath.geometry.degrees
|
|
||||||
import kotlin.jvm.JvmInline
|
import kotlin.jvm.JvmInline
|
||||||
|
|
||||||
public sealed interface GeoJsonGeometry : GeoJson {
|
public sealed interface GeoJsonGeometry : GeoJson {
|
||||||
@ -35,8 +34,8 @@ internal fun JsonElement.toGmc() = jsonArray.run {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun Gmc.toJsonArray(): JsonArray = buildJsonArray {
|
internal fun Gmc.toJsonArray(): JsonArray = buildJsonArray {
|
||||||
add(longitude.degrees)
|
add(longitude.toDegrees().value)
|
||||||
add(latitude.degrees)
|
add(latitude.toDegrees().value)
|
||||||
elevation?.let {
|
elevation?.let {
|
||||||
add(it.meters)
|
add(it.meters)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package center.sciprog.maps.geojson
|
package center.sciprog.maps.geojson
|
||||||
|
|
||||||
import center.sciprog.attributes.SerializableAttribute
|
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.serializer
|
import kotlinx.serialization.serializer
|
||||||
|
import space.kscience.SerializableAttribute
|
||||||
|
|
||||||
public object GeoJsonPropertiesAttribute : SerializableAttribute<JsonObject>("properties", serializer())
|
public object GeoJsonPropertiesAttribute : SerializableAttribute<JsonObject>("properties", serializer())
|
@ -1,12 +1,12 @@
|
|||||||
package center.sciprog.maps.geojson
|
package center.sciprog.maps.geojson
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import center.sciprog.attributes.NameAttribute
|
|
||||||
import center.sciprog.maps.coordinates.Gmc
|
import center.sciprog.maps.coordinates.Gmc
|
||||||
import center.sciprog.maps.features.*
|
import center.sciprog.maps.features.*
|
||||||
import kotlinx.serialization.json.contentOrNull
|
import kotlinx.serialization.json.contentOrNull
|
||||||
import kotlinx.serialization.json.intOrNull
|
import kotlinx.serialization.json.intOrNull
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import space.kscience.NameAttribute
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -6,7 +6,8 @@ plugins {
|
|||||||
|
|
||||||
kscience{
|
kscience{
|
||||||
jvm()
|
jvm()
|
||||||
js()
|
// js()
|
||||||
|
wasm()
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
@ -14,8 +15,6 @@ kotlin {
|
|||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api(projects.mapsKtFeatures)
|
api(projects.mapsKtFeatures)
|
||||||
api("io.github.microutils:kotlin-logging:2.1.23")
|
|
||||||
api(compose.foundation)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getByName("jvmMain"){
|
getByName("jvmMain"){
|
||||||
|
@ -3,14 +3,10 @@ package center.sciprog.maps.scheme
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.PathEffect
|
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
|
||||||
import androidx.compose.ui.graphics.drawscope.clipRect
|
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import center.sciprog.maps.compose.canvasControls
|
import center.sciprog.maps.compose.canvasControls
|
||||||
import center.sciprog.maps.features.*
|
import center.sciprog.maps.features.*
|
||||||
import mu.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import kotlin.math.abs
|
|||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
public data class XY(override val x: Float, override val y: Float): Vector2D<Float>
|
public data class XY(override val x: Float, override val y: Float) : Vector2D<Float>
|
||||||
|
|
||||||
public fun XY(x: Number, y: Number): XY = XY(x.toFloat(), y.toFloat())
|
public fun XY(x: Number, y: Number): XY = XY(x.toFloat(), y.toFloat())
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||||||
import androidx.compose.ui.unit.Dp
|
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.Attributes
|
|
||||||
import center.sciprog.maps.features.*
|
import center.sciprog.maps.features.*
|
||||||
|
import space.kscience.attributes.Attributes
|
||||||
import space.kscience.kmath.geometry.Angle
|
import space.kscience.kmath.geometry.Angle
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
|
|
||||||
|
@ -301,7 +301,7 @@ internal class SvgCanvas(val graphics: SVGGraphics2D) : Canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal class SvgDrawContext(val graphics: SVGGraphics2D, override var size: Size) : DrawContext {
|
internal class SvgDrawContext(val graphics: SVGGraphics2D, override var size: Size) : DrawContext {
|
||||||
override val canvas: Canvas = SvgCanvas(graphics)
|
override var canvas: Canvas = SvgCanvas(graphics)
|
||||||
|
|
||||||
override val transform: DrawTransform = asDrawTransform()
|
override val transform: DrawTransform = asDrawTransform()
|
||||||
}
|
}
|
@ -4,15 +4,18 @@ import androidx.compose.ui.geometry.CornerRadius
|
|||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.*
|
import androidx.compose.ui.graphics.*
|
||||||
import androidx.compose.ui.graphics.drawscope.*
|
import androidx.compose.ui.graphics.drawscope.DrawContext
|
||||||
|
import androidx.compose.ui.graphics.drawscope.DrawStyle
|
||||||
|
import androidx.compose.ui.graphics.drawscope.Fill
|
||||||
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import center.sciprog.attributes.Attributes
|
|
||||||
import center.sciprog.maps.features.*
|
import center.sciprog.maps.features.*
|
||||||
import center.sciprog.maps.scheme.XY
|
import center.sciprog.maps.scheme.XY
|
||||||
import org.jfree.svg.SVGGraphics2D
|
import org.jfree.svg.SVGGraphics2D
|
||||||
|
import space.kscience.attributes.Attributes
|
||||||
import java.awt.BasicStroke
|
import java.awt.BasicStroke
|
||||||
import java.awt.geom.*
|
import java.awt.geom.*
|
||||||
import java.awt.image.AffineTransformOp
|
import java.awt.image.AffineTransformOp
|
||||||
|
@ -61,6 +61,6 @@ include(
|
|||||||
":demo:scheme",
|
":demo:scheme",
|
||||||
":demo:polygon-editor",
|
":demo:polygon-editor",
|
||||||
":demo:trajectory-playground",
|
":demo:trajectory-playground",
|
||||||
":demo:maps-js"
|
":demo:maps-wasm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -11,9 +11,12 @@ kscience{
|
|||||||
jvm()
|
jvm()
|
||||||
js()
|
js()
|
||||||
native()
|
native()
|
||||||
|
wasm()
|
||||||
|
|
||||||
useContextReceivers()
|
useContextReceivers()
|
||||||
useSerialization()
|
useSerialization{
|
||||||
|
json()
|
||||||
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
api("space.kscience:kmath-geometry:$kmathVersion")
|
api("space.kscience:kmath-geometry:$kmathVersion")
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
@file:Suppress("UNCHECKED_CAST")
|
@file:Suppress("UNCHECKED_CAST")
|
||||||
|
|
||||||
package center.sciprog.attributes
|
package space.kscience
|
||||||
|
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
import kotlinx.serialization.encoding.Decoder
|
import kotlinx.serialization.encoding.Decoder
|
||||||
import kotlinx.serialization.encoding.Encoder
|
import kotlinx.serialization.encoding.Encoder
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
|
import space.kscience.attributes.Attribute
|
||||||
|
import space.kscience.attributes.Attributes
|
||||||
|
|
||||||
public class AttributesSerializer(
|
public class AttributesSerializer(
|
||||||
private val serializableAttributes: Set<SerializableAttribute<*>>,
|
private val serializableAttributes: Set<SerializableAttribute<*>>,
|
||||||
@ -29,12 +32,16 @@ public class AttributesSerializer(
|
|||||||
|
|
||||||
attr to value
|
attr to value
|
||||||
}
|
}
|
||||||
return Attributes(attributeMap)
|
return object : Attributes {
|
||||||
|
override val content: Map<out Attribute<*>, Any?> = attributeMap
|
||||||
|
override fun toString(): String = "Attributes(value=${content.entries})"
|
||||||
|
override fun equals(other: Any?): Boolean = other is Attributes && Attributes.equals(this, other)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: Attributes) {
|
override fun serialize(encoder: Encoder, value: Attributes) {
|
||||||
val json = buildJsonObject {
|
val json = buildJsonObject {
|
||||||
value.content.forEach { (key: Attribute<*>, value: Any) ->
|
value.content.forEach { (key: Attribute<*>, value: Any?) ->
|
||||||
if (key !in serializableAttributes) error("An attribute key '$key' is not in the list of allowed attributes for this serializer")
|
if (key !in serializableAttributes) error("An attribute key '$key' is not in the list of allowed attributes for this serializer")
|
||||||
val serializableKey = key as SerializableAttribute
|
val serializableKey = key as SerializableAttribute
|
||||||
|
|
||||||
@ -46,10 +53,22 @@ public class AttributesSerializer(
|
|||||||
|
|
||||||
put(
|
put(
|
||||||
serializableKey.serialId,
|
serializableKey.serialId,
|
||||||
json.encodeToJsonElement(serializableKey.serializer as KSerializer<Any>, value)
|
json.encodeToJsonElement(serializableKey.serializer as KSerializer<Any?>, value)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsonSerializer.serialize(encoder, json)
|
jsonSerializer.serialize(encoder, json)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract class SerializableAttribute<T>(
|
||||||
|
public val serialId: String,
|
||||||
|
public val serializer: KSerializer<T>,
|
||||||
|
) : Attribute<T> {
|
||||||
|
override fun toString(): String = serialId
|
||||||
|
}
|
||||||
|
|
||||||
|
public object NameAttribute : SerializableAttribute<String>("name", String.serializer())
|
||||||
|
|
||||||
|
public fun Attributes.Companion.equals(a1: Attributes, a2: Attributes): Boolean =
|
||||||
|
a1.keys == a2.keys && a1.keys.all { a1[it] == a2[it] }
|
@ -1,21 +1,25 @@
|
|||||||
package space.kscience.kmath.geometry
|
package space.kscience
|
||||||
|
|
||||||
|
import space.kscience.kmath.geometry.*
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Circle2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
|
||||||
|
import space.kscience.kmath.structures.Float64
|
||||||
import space.kscience.trajectory.*
|
import space.kscience.trajectory.*
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.sign
|
import kotlin.math.sign
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
public fun Euclidean2DSpace.circle(x: Number, y: Number, radius: Number): Circle2D =
|
public fun Float64Space2D.circle(x: Number, y: Number, radius: Number): Circle2D<Float64> =
|
||||||
Circle2D(vector(x, y), radius = radius.toDouble())
|
Circle2D(vector(x, y), radius = radius.toDouble())
|
||||||
|
|
||||||
public fun Euclidean2DSpace.segment(begin: DoubleVector2D, end: DoubleVector2D): LineSegment2D =
|
public fun Float64Space2D.segment(begin: Vector2D<Float64>, end: Vector2D<Float64>): LineSegment2D =
|
||||||
LineSegment(begin, end)
|
LineSegment(begin, end)
|
||||||
|
|
||||||
public fun Euclidean2DSpace.segment(x1: Number, y1: Number, x2: Number, y2: Number): LineSegment2D =
|
public fun Float64Space2D.segment(x1: Number, y1: Number, x2: Number, y2: Number): LineSegment2D =
|
||||||
LineSegment(vector(x1, y1), vector(x2, y2))
|
LineSegment(vector(x1, y1), vector(x2, y2))
|
||||||
|
|
||||||
public fun Euclidean2DSpace.intersectsOrInside(circle1: Circle2D, circle2: Circle2D): Boolean {
|
public fun Float64Space2D.intersectsOrInside(circle1: Circle2D<Float64>, circle2: Circle2D<Float64>): Boolean {
|
||||||
val distance = norm(circle2.center - circle1.center)
|
val distance = norm(circle2.center - circle1.center)
|
||||||
return distance <= circle1.radius + circle2.radius
|
return distance <= circle1.radius + circle2.radius
|
||||||
}
|
}
|
||||||
@ -23,7 +27,7 @@ public fun Euclidean2DSpace.intersectsOrInside(circle1: Circle2D, circle2: Circl
|
|||||||
/**
|
/**
|
||||||
* https://mathworld.wolfram.com/Circle-LineIntersection.html
|
* https://mathworld.wolfram.com/Circle-LineIntersection.html
|
||||||
*/
|
*/
|
||||||
public fun Euclidean2DSpace.intersects(segment: LineSegment2D, circle: Circle2D): Boolean {
|
public fun Float64Space2D.intersects(segment: LineSegment2D, circle: Circle2D<Float64>): Boolean {
|
||||||
val direction = segment.end - segment.begin
|
val direction = segment.end - segment.begin
|
||||||
val radiusVector = segment.begin - circle.center
|
val radiusVector = segment.begin - circle.center
|
||||||
|
|
||||||
@ -43,14 +47,14 @@ public fun Euclidean2DSpace.intersects(segment: LineSegment2D, circle: Circle2D)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public fun Euclidean2DSpace.intersects(circle: Circle2D, segment: LineSegment2D): Boolean =
|
public fun Float64Space2D.intersects(circle: Circle2D<Float64>, segment: LineSegment2D): Boolean =
|
||||||
intersects(segment, circle)
|
intersects(segment, circle)
|
||||||
|
|
||||||
public fun Euclidean2DSpace.intersects(segment1: LineSegment2D, segment2: LineSegment2D): Boolean {
|
public fun Float64Space2D.intersects(segment1: LineSegment2D, segment2: LineSegment2D): Boolean {
|
||||||
infix fun DoubleVector2D.cross(v2: DoubleVector2D): Double = x * v2.y - y * v2.x
|
infix fun Vector2D<Float64>.cross(v2: Vector2D<Float64>): Double = x * v2.y - y * v2.x
|
||||||
infix fun DoubleVector2D.crossSign(v2: DoubleVector2D) = cross(v2).sign
|
infix fun Vector2D<Float64>.crossSign(v2: Vector2D<Float64>) = cross(v2).sign
|
||||||
|
|
||||||
return with(Euclidean2DSpace) {
|
return with(Float64Space2D) {
|
||||||
(segment2.begin - segment1.begin) crossSign (segment2.end - segment1.begin) !=
|
(segment2.begin - segment1.begin) crossSign (segment2.end - segment1.begin) !=
|
||||||
(segment2.begin - segment1.end) crossSign (segment2.end - segment1.end) &&
|
(segment2.begin - segment1.end) crossSign (segment2.end - segment1.end) &&
|
||||||
(segment1.begin - segment2.begin) crossSign (segment1.end - segment2.begin) !=
|
(segment1.begin - segment2.begin) crossSign (segment1.end - segment2.begin) !=
|
||||||
@ -58,7 +62,7 @@ public fun Euclidean2DSpace.intersects(segment1: LineSegment2D, segment2: LineSe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun Euclidean2DSpace.intersectsTrajectory(segment: LineSegment2D, trajectory: Trajectory2D): Boolean =
|
public fun Float64Space2D.intersectsTrajectory(segment: LineSegment2D, trajectory: Trajectory2D): Boolean =
|
||||||
when (trajectory) {
|
when (trajectory) {
|
||||||
is CircleTrajectory2D -> intersects(segment, trajectory.circle)
|
is CircleTrajectory2D -> intersects(segment, trajectory.circle)
|
||||||
is StraightTrajectory2D -> intersects(segment, trajectory)
|
is StraightTrajectory2D -> intersects(segment, trajectory)
|
||||||
@ -72,7 +76,7 @@ public fun Euclidean2DSpace.intersectsTrajectory(segment: LineSegment2D, traject
|
|||||||
*
|
*
|
||||||
* @param bearing is counted the same way as in [Pose2D], from positive y clockwise
|
* @param bearing is counted the same way as in [Pose2D], from positive y clockwise
|
||||||
*/
|
*/
|
||||||
public fun Circle2D.tangent(bearing: Angle, direction: Trajectory2D.Direction): Pose2D = with(Euclidean2DSpace) {
|
public fun Circle2D<Float64>.tangent(bearing: Angle, direction: Trajectory2D.Direction): Pose2D = with(Float64Space2D) {
|
||||||
val coordinates: Vector2D<Double> = vector(center.x + radius * sin(bearing), center.y + radius * cos(bearing))
|
val coordinates: Vector2D<Double> = vector(center.x + radius * sin(bearing), center.y + radius * cos(bearing))
|
||||||
val tangentAngle = when (direction) {
|
val tangentAngle = when (direction) {
|
||||||
Trajectory2D.R -> bearing + Angle.piDiv2
|
Trajectory2D.R -> bearing + Angle.piDiv2
|
||||||
@ -82,7 +86,7 @@ public fun Circle2D.tangent(bearing: Angle, direction: Trajectory2D.Direction):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public fun CircleTrajectory2D.containsPoint(point: DoubleVector2D): Boolean = with(Euclidean2DSpace) {
|
public fun CircleTrajectory2D.containsPoint(point: Vector2D<Float64>): Boolean = with(Float64Space2D) {
|
||||||
val radiusVector = point - center
|
val radiusVector = point - center
|
||||||
if (abs(norm(radiusVector) - circle.radius) > 1e-4 * circle.radius) error("Wrong radius")
|
if (abs(norm(radiusVector) - circle.radius) > 1e-4 * circle.radius) error("Wrong radius")
|
||||||
val radiusVectorBearing = radiusVector.bearing
|
val radiusVectorBearing = radiusVector.bearing
|
@ -1,21 +0,0 @@
|
|||||||
package space.kscience.kmath.geometry
|
|
||||||
|
|
||||||
import space.kscience.kmath.misc.zipWithNextCircular
|
|
||||||
import space.kscience.trajectory.Trajectory2D
|
|
||||||
|
|
||||||
public fun Euclidean2DSpace.polygon(points: List<DoubleVector2D>): Polygon<Double> = object : Polygon<Double> {
|
|
||||||
override val points: List<Vector2D<Double>> get() = points
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun Euclidean2DSpace.intersects(polygon: Polygon<Double>, segment: LineSegment2D): Boolean =
|
|
||||||
polygon.points.zipWithNextCircular { l, r -> segment(l, r) }.any { intersects(it, segment) }
|
|
||||||
|
|
||||||
public fun Euclidean2DSpace.intersects(polygon: Polygon<Double>, circle: Circle2D): Boolean =
|
|
||||||
polygon.points.zipWithNextCircular { l, r -> segment(l, r) }.any { intersects(it, circle) }
|
|
||||||
|
|
||||||
public fun Euclidean2DSpace.intersectsTrajectory(polygon: Polygon<Double>, trajectory: Trajectory2D): Boolean =
|
|
||||||
polygon.points.zipWithNextCircular { l, r ->
|
|
||||||
segment(l, r)
|
|
||||||
}.any { edge ->
|
|
||||||
intersectsTrajectory(edge, trajectory)
|
|
||||||
}
|
|
@ -0,0 +1,28 @@
|
|||||||
|
package space.kscience
|
||||||
|
|
||||||
|
import space.kscience.kmath.geometry.LineSegment2D
|
||||||
|
import space.kscience.kmath.geometry.Polygon
|
||||||
|
import space.kscience.kmath.geometry.Vector2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Circle2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
|
||||||
|
import space.kscience.kmath.misc.zipWithNextCircular
|
||||||
|
import space.kscience.kmath.structures.Float64
|
||||||
|
import space.kscience.trajectory.Trajectory2D
|
||||||
|
|
||||||
|
public fun Float64Space2D.polygon(points: List<Vector2D<Double>>): Polygon<Vector2D<Float64>> =
|
||||||
|
object : Polygon<Vector2D<Float64>> {
|
||||||
|
override val points: List<Vector2D<Double>> get() = points
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun Float64Space2D.intersects(polygon: Polygon<Vector2D<Float64>>, segment: LineSegment2D): Boolean =
|
||||||
|
polygon.points.zipWithNextCircular { l, r -> segment(l, r) }.any { intersects(it, segment) }
|
||||||
|
|
||||||
|
public fun Float64Space2D.intersects(polygon: Polygon<Vector2D<Float64>>, circle: Circle2D<Float64>): Boolean =
|
||||||
|
polygon.points.zipWithNextCircular { l, r -> segment(l, r) }.any { intersects(it, circle) }
|
||||||
|
|
||||||
|
public fun Float64Space2D.intersectsTrajectory(polygon: Polygon<Vector2D<Float64>>, trajectory: Trajectory2D): Boolean =
|
||||||
|
polygon.points.zipWithNextCircular { l, r ->
|
||||||
|
segment(l, r)
|
||||||
|
}.any { edge ->
|
||||||
|
intersectsTrajectory(edge, trajectory)
|
||||||
|
}
|
@ -5,61 +5,70 @@
|
|||||||
|
|
||||||
package space.kscience.trajectory
|
package space.kscience.trajectory
|
||||||
|
|
||||||
import space.kscience.kmath.geometry.*
|
import space.kscience.kmath.geometry.cos
|
||||||
import space.kscience.kmath.geometry.Euclidean2DSpace.distanceTo
|
import space.kscience.kmath.geometry.euclidean2d.Circle2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D.distanceTo
|
||||||
|
import space.kscience.kmath.geometry.normalized
|
||||||
|
import space.kscience.kmath.geometry.radians
|
||||||
|
import space.kscience.kmath.geometry.sin
|
||||||
|
import space.kscience.kmath.structures.Float64
|
||||||
import space.kscience.trajectory.Trajectory2D.*
|
import space.kscience.trajectory.Trajectory2D.*
|
||||||
import kotlin.math.acos
|
import kotlin.math.acos
|
||||||
|
|
||||||
internal fun Pose2D.getLeftCircle(radius: Double): Circle2D = getTangentCircles(radius).first
|
internal fun Pose2D.getLeftCircle(radius: Double): Circle2D<Float64> = getTangentCircles(radius).first
|
||||||
|
|
||||||
internal fun Pose2D.getRightCircle(radius: Double): Circle2D = getTangentCircles(radius).second
|
internal fun Pose2D.getRightCircle(radius: Double): Circle2D<Float64> = getTangentCircles(radius).second
|
||||||
|
|
||||||
internal fun Pose2D.getTangentCircles(radius: Double): Pair<Circle2D, Circle2D> = with(Euclidean2DSpace) {
|
internal fun Pose2D.getTangentCircles(radius: Double): Pair<Circle2D<Float64>, Circle2D<Float64>> =
|
||||||
val dX = radius * cos(bearing)
|
with(Float64Space2D) {
|
||||||
val dY = radius * sin(bearing)
|
val dX = radius * cos(bearing)
|
||||||
return Circle2D(vector(x - dX, y + dY), radius) to Circle2D(vector(x + dX, y - dY), radius)
|
val dY = radius * sin(bearing)
|
||||||
}
|
return Circle2D(vector(x - dX, y + dY), radius) to Circle2D(vector(x + dX, y - dY), radius)
|
||||||
|
}
|
||||||
|
|
||||||
private fun outerTangent(from: Circle2D, to: Circle2D, direction: Direction): StraightTrajectory2D =
|
private fun Float64Space2D.outerTangent(
|
||||||
with(Euclidean2DSpace) {
|
from: Circle2D<Float64>,
|
||||||
val centers = StraightTrajectory2D(from.center, to.center)
|
to: Circle2D<Float64>,
|
||||||
val p1 = when (direction) {
|
direction: Direction,
|
||||||
L -> vector(
|
): StraightTrajectory2D {
|
||||||
from.center.x - from.radius * cos(centers.bearing),
|
val centers = StraightTrajectory2D(from.center, to.center)
|
||||||
from.center.y + from.radius * sin(centers.bearing)
|
val p1 = when (direction) {
|
||||||
)
|
L -> vector(
|
||||||
|
from.center.x - from.radius * cos(centers.bearing),
|
||||||
|
from.center.y + from.radius * sin(centers.bearing)
|
||||||
|
)
|
||||||
|
|
||||||
R -> vector(
|
R -> vector(
|
||||||
from.center.x + from.radius * cos(centers.bearing),
|
from.center.x + from.radius * cos(centers.bearing),
|
||||||
from.center.y - from.radius * sin(centers.bearing)
|
from.center.y - from.radius * sin(centers.bearing)
|
||||||
)
|
|
||||||
}
|
|
||||||
return StraightTrajectory2D(
|
|
||||||
p1,
|
|
||||||
vector(p1.x + (centers.end.x - centers.begin.x), p1.y + (centers.end.y - centers.begin.y))
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
return StraightTrajectory2D(
|
||||||
|
p1,
|
||||||
|
vector(p1.x + (centers.end.x - centers.begin.x), p1.y + (centers.end.y - centers.begin.y))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun innerTangent(
|
private fun Float64Space2D.innerTangent(
|
||||||
from: Circle2D,
|
from: Circle2D<Float64>,
|
||||||
to: Circle2D,
|
to: Circle2D<Float64>,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
): StraightTrajectory2D? =
|
): StraightTrajectory2D? {
|
||||||
with(Euclidean2DSpace) {
|
val centers = StraightTrajectory2D(from.center, to.center)
|
||||||
val centers = StraightTrajectory2D(from.center, to.center)
|
if (centers.length < from.radius * 2) return null
|
||||||
if (centers.length < from.radius * 2) return null
|
val angle = when (direction) {
|
||||||
val angle = when (direction) {
|
L -> centers.bearing + acos(from.radius * 2 / centers.length).radians
|
||||||
L -> centers.bearing + acos(from.radius * 2 / centers.length).radians
|
R -> centers.bearing - acos(from.radius * 2 / centers.length).radians
|
||||||
R -> centers.bearing - acos(from.radius * 2 / centers.length).radians
|
}.normalized()
|
||||||
}.normalized()
|
|
||||||
|
|
||||||
val dX = from.radius * sin(angle)
|
val dX = from.radius * sin(angle)
|
||||||
val dY = from.radius * cos(angle)
|
val dY = from.radius * cos(angle)
|
||||||
val p1 = vector(from.center.x + dX, from.center.y + dY)
|
val p1 = vector(from.center.x + dX, from.center.y + dY)
|
||||||
val p2 = vector(to.center.x - dX, to.center.y - dY)
|
val p2 = vector(to.center.x - dX, to.center.y - dY)
|
||||||
return StraightTrajectory2D(p1, p2)
|
return StraightTrajectory2D(p1, p2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Suppress("DuplicatedCode")
|
@Suppress("DuplicatedCode")
|
||||||
@ -118,7 +127,7 @@ public object DubinsPath {
|
|||||||
all(start, end, turningRadius).minBy { it.length }
|
all(start, end, turningRadius).minBy { it.length }
|
||||||
|
|
||||||
public fun rlr(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D? =
|
public fun rlr(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D? =
|
||||||
with(Euclidean2DSpace) {
|
with(Float64Space2D) {
|
||||||
val c1 = start.getRightCircle(turningRadius)
|
val c1 = start.getRightCircle(turningRadius)
|
||||||
val c2 = end.getRightCircle(turningRadius)
|
val c2 = end.getRightCircle(turningRadius)
|
||||||
val centers = StraightTrajectory2D(c1.center, c2.center)
|
val centers = StraightTrajectory2D(c1.center, c2.center)
|
||||||
@ -162,7 +171,7 @@ public object DubinsPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fun lrl(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D? =
|
public fun lrl(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D? =
|
||||||
with(Euclidean2DSpace) {
|
with(Float64Space2D) {
|
||||||
val c1 = start.getLeftCircle(turningRadius)
|
val c1 = start.getLeftCircle(turningRadius)
|
||||||
val c2 = end.getLeftCircle(turningRadius)
|
val c2 = end.getLeftCircle(turningRadius)
|
||||||
val centers = StraightTrajectory2D(c1.center, c2.center)
|
val centers = StraightTrajectory2D(c1.center, c2.center)
|
||||||
@ -208,7 +217,7 @@ public object DubinsPath {
|
|||||||
public fun rsr(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D {
|
public fun rsr(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D {
|
||||||
val c1 = start.getRightCircle(turningRadius)
|
val c1 = start.getRightCircle(turningRadius)
|
||||||
val c2 = end.getRightCircle(turningRadius)
|
val c2 = end.getRightCircle(turningRadius)
|
||||||
val s = outerTangent(c1, c2, L)
|
val s = Float64Space2D.outerTangent(c1, c2, L)
|
||||||
val a1 = CircleTrajectory2D(c1.center, start, s.begin, R)
|
val a1 = CircleTrajectory2D(c1.center, start, s.begin, R)
|
||||||
val a3 = CircleTrajectory2D(c2.center, s.end, end, R)
|
val a3 = CircleTrajectory2D(c2.center, s.end, end, R)
|
||||||
return CompositeTrajectory2D(a1, s, a3)
|
return CompositeTrajectory2D(a1, s, a3)
|
||||||
@ -217,7 +226,7 @@ public object DubinsPath {
|
|||||||
public fun lsl(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D {
|
public fun lsl(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D {
|
||||||
val c1 = start.getLeftCircle(turningRadius)
|
val c1 = start.getLeftCircle(turningRadius)
|
||||||
val c2 = end.getLeftCircle(turningRadius)
|
val c2 = end.getLeftCircle(turningRadius)
|
||||||
val s = outerTangent(c1, c2, R)
|
val s = Float64Space2D.outerTangent(c1, c2, R)
|
||||||
val a1 = CircleTrajectory2D(c1.center, start, s.begin, L)
|
val a1 = CircleTrajectory2D(c1.center, start, s.begin, L)
|
||||||
val a3 = CircleTrajectory2D(c2.center, s.end, end, L)
|
val a3 = CircleTrajectory2D(c2.center, s.end, end, L)
|
||||||
return CompositeTrajectory2D(a1, s, a3)
|
return CompositeTrajectory2D(a1, s, a3)
|
||||||
@ -226,7 +235,7 @@ public object DubinsPath {
|
|||||||
public fun rsl(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D? {
|
public fun rsl(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D? {
|
||||||
val c1 = start.getRightCircle(turningRadius)
|
val c1 = start.getRightCircle(turningRadius)
|
||||||
val c2 = end.getLeftCircle(turningRadius)
|
val c2 = end.getLeftCircle(turningRadius)
|
||||||
val s = innerTangent(c1, c2, R)
|
val s = Float64Space2D.innerTangent(c1, c2, R)
|
||||||
if (s == null || c1.center.distanceTo(c2.center) < turningRadius * 2) return null
|
if (s == null || c1.center.distanceTo(c2.center) < turningRadius * 2) return null
|
||||||
|
|
||||||
val a1 = CircleTrajectory2D(c1.center, start, s.begin, R)
|
val a1 = CircleTrajectory2D(c1.center, start, s.begin, R)
|
||||||
@ -237,7 +246,7 @@ public object DubinsPath {
|
|||||||
public fun lsr(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D? {
|
public fun lsr(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D? {
|
||||||
val c1 = start.getLeftCircle(turningRadius)
|
val c1 = start.getLeftCircle(turningRadius)
|
||||||
val c2 = end.getRightCircle(turningRadius)
|
val c2 = end.getRightCircle(turningRadius)
|
||||||
val s = innerTangent(c1, c2, L)
|
val s = Float64Space2D.innerTangent(c1, c2, L)
|
||||||
if (s == null || c1.center.distanceTo(c2.center) < turningRadius * 2) return null
|
if (s == null || c1.center.distanceTo(c2.center) < turningRadius * 2) return null
|
||||||
|
|
||||||
val a1 = CircleTrajectory2D(c1.center, start, s.begin, L)
|
val a1 = CircleTrajectory2D(c1.center, start, s.begin, L)
|
||||||
|
@ -5,8 +5,15 @@
|
|||||||
|
|
||||||
package space.kscience.trajectory
|
package space.kscience.trajectory
|
||||||
|
|
||||||
import space.kscience.kmath.geometry.*
|
import space.kscience.intersectsTrajectory
|
||||||
|
import space.kscience.kmath.geometry.Angle
|
||||||
|
import space.kscience.kmath.geometry.Polygon
|
||||||
|
import space.kscience.kmath.geometry.Vector2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Circle2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
|
||||||
import space.kscience.kmath.misc.zipWithNextCircular
|
import space.kscience.kmath.misc.zipWithNextCircular
|
||||||
|
import space.kscience.kmath.structures.Float64
|
||||||
|
import space.kscience.polygon
|
||||||
|
|
||||||
|
|
||||||
public interface Obstacle {
|
public interface Obstacle {
|
||||||
@ -30,7 +37,7 @@ public interface Obstacle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CircleObstacle(val circle: Circle2D) : Obstacle {
|
private class CircleObstacle(val circle: Circle2D<Float64>) : Obstacle {
|
||||||
override val center: Vector2D<Double> get() = circle.center
|
override val center: Vector2D<Double> get() = circle.center
|
||||||
|
|
||||||
override val arcs: List<CircleTrajectory2D>
|
override val arcs: List<CircleTrajectory2D>
|
||||||
@ -41,7 +48,7 @@ private class CircleObstacle(val circle: Circle2D) : Obstacle {
|
|||||||
|
|
||||||
|
|
||||||
override fun intersectsTrajectory(trajectory: Trajectory2D): Boolean =
|
override fun intersectsTrajectory(trajectory: Trajectory2D): Boolean =
|
||||||
Euclidean2DSpace.intersectsTrajectory(circumvention, trajectory)
|
Float64Space2D.intersectsTrajectory(circumvention, trajectory)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
@ -67,18 +74,18 @@ private class CoreObstacle(override val circumvention: CompositeTrajectory2D) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val center: Vector2D<Double> by lazy {
|
override val center: Vector2D<Double> by lazy {
|
||||||
Euclidean2DSpace.vector(
|
Float64Space2D.vector(
|
||||||
arcs.sumOf { it.center.x } / arcs.size,
|
arcs.sumOf { it.center.x } / arcs.size,
|
||||||
arcs.sumOf { it.center.y } / arcs.size
|
arcs.sumOf { it.center.y } / arcs.size
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val core: Polygon<Double> by lazy {
|
val core: Polygon<Vector2D<Float64>> by lazy {
|
||||||
Euclidean2DSpace.polygon(arcs.map { it.circle.center })
|
Float64Space2D.polygon(arcs.map { it.circle.center })
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun intersectsTrajectory(trajectory: Trajectory2D): Boolean =
|
override fun intersectsTrajectory(trajectory: Trajectory2D): Boolean =
|
||||||
Euclidean2DSpace.intersectsTrajectory(core, trajectory)
|
Float64Space2D.intersectsTrajectory(core, trajectory)
|
||||||
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@ -99,7 +106,7 @@ private class CoreObstacle(override val circumvention: CompositeTrajectory2D) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun Obstacle(circles: List<Circle2D>): Obstacle = with(Euclidean2DSpace) {
|
public fun Obstacle(circles: List<Circle2D<Float64>>): Obstacle = with(Float64Space2D) {
|
||||||
require(circles.isNotEmpty()) { "Can't create circumvention for an empty obstacle" }
|
require(circles.isNotEmpty()) { "Can't create circumvention for an empty obstacle" }
|
||||||
//Create a single circle obstacle
|
//Create a single circle obstacle
|
||||||
if(circles.size == 1) return CircleObstacle(circles.first())
|
if(circles.size == 1) return CircleObstacle(circles.first())
|
||||||
@ -123,7 +130,7 @@ public fun Obstacle(circles: List<Circle2D>): Obstacle = with(Euclidean2DSpace)
|
|||||||
(it.center - center).bearing
|
(it.center - center).bearing
|
||||||
}
|
}
|
||||||
|
|
||||||
val tangents = convex.zipWithNextCircular { a: Circle2D, b: Circle2D ->
|
val tangents = convex.zipWithNextCircular { a: Circle2D<Float64>, b: Circle2D<Float64> ->
|
||||||
tangentsBetweenCircles(a, b)[DubinsPath.Type.RSR]
|
tangentsBetweenCircles(a, b)[DubinsPath.Type.RSR]
|
||||||
?: error("Can't find right handed circumvention")
|
?: error("Can't find right handed circumvention")
|
||||||
}
|
}
|
||||||
@ -144,7 +151,7 @@ public fun Obstacle(circles: List<Circle2D>): Obstacle = with(Euclidean2DSpace)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public fun Obstacle(vararg circles: Circle2D): Obstacle = Obstacle(listOf(*circles))
|
public fun Obstacle(vararg circles: Circle2D<Float64>): Obstacle = Obstacle(listOf(*circles))
|
||||||
|
|
||||||
public fun Obstacle(points: List<Vector2D<Double>>, radius: Double): Obstacle =
|
public fun Obstacle(points: List<Vector2D<Double>>, radius: Double): Obstacle =
|
||||||
Obstacle(points.map { Circle2D(it, radius) })
|
Obstacle(points.map { Circle2D(it, radius) })
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
package space.kscience.trajectory
|
package space.kscience.trajectory
|
||||||
|
|
||||||
import space.kscience.kmath.geometry.*
|
import space.kscience.containsPoint
|
||||||
|
import space.kscience.intersects
|
||||||
|
import space.kscience.intersectsOrInside
|
||||||
|
import space.kscience.kmath.geometry.Angle
|
||||||
|
import space.kscience.kmath.geometry.Polygon
|
||||||
|
import space.kscience.kmath.geometry.Vector2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Circle2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
|
||||||
|
import space.kscience.kmath.structures.Float64
|
||||||
import kotlin.collections.component1
|
import kotlin.collections.component1
|
||||||
import kotlin.collections.component2
|
import kotlin.collections.component2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The same as [intersectsTrajectory], but bypasses same circles or same straights
|
* The same as [intersectsTrajectory], but bypasses same circles or same straights
|
||||||
*/
|
*/
|
||||||
private fun Euclidean2DSpace.intersectsOtherTrajectory(a: Trajectory2D, b: Trajectory2D): Boolean = when (a) {
|
private fun Float64Space2D.intersectsOtherTrajectory(a: Trajectory2D, b: Trajectory2D): Boolean = when (a) {
|
||||||
is CircleTrajectory2D -> when (b) {
|
is CircleTrajectory2D -> when (b) {
|
||||||
is CircleTrajectory2D -> a != b && intersectsOrInside(a.circle, b.circle)
|
is CircleTrajectory2D -> a != b && intersectsOrInside(a.circle, b.circle)
|
||||||
is StraightTrajectory2D -> intersects(a.circle, b)
|
is StraightTrajectory2D -> intersects(a.circle, b)
|
||||||
@ -32,7 +40,7 @@ public class Obstacles(public val obstacles: List<Obstacle>) {
|
|||||||
val direction: Trajectory2D.Direction,
|
val direction: Trajectory2D.Direction,
|
||||||
) {
|
) {
|
||||||
val obstacle: Obstacle get() = obstacles[obstacleIndex]
|
val obstacle: Obstacle get() = obstacles[obstacleIndex]
|
||||||
val circle: Circle2D get() = obstacle.arcs[nodeIndex].circle
|
val circle: Circle2D<Float64> get() = obstacle.arcs[nodeIndex].circle
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class ObstacleTangent(
|
private inner class ObstacleTangent(
|
||||||
@ -44,7 +52,7 @@ public class Obstacles(public val obstacles: List<Obstacle>) {
|
|||||||
* If false, this tangent intersects another obstacle
|
* If false, this tangent intersects another obstacle
|
||||||
*/
|
*/
|
||||||
val isValid by lazy {
|
val isValid by lazy {
|
||||||
with(Euclidean2DSpace) {
|
with(Float64Space2D) {
|
||||||
obstacles.indices.none {
|
obstacles.indices.none {
|
||||||
it != from?.obstacleIndex && it != to?.obstacleIndex && obstacles[it].intersectsTrajectory(
|
it != from?.obstacleIndex && it != to?.obstacleIndex && obstacles[it].intersectsTrajectory(
|
||||||
tangentTrajectory
|
tangentTrajectory
|
||||||
@ -64,7 +72,7 @@ public class Obstacles(public val obstacles: List<Obstacle>) {
|
|||||||
private fun tangentsBetween(
|
private fun tangentsBetween(
|
||||||
firstIndex: Int,
|
firstIndex: Int,
|
||||||
secondIndex: Int,
|
secondIndex: Int,
|
||||||
): Map<DubinsPath.Type, ObstacleTangent> = with(Euclidean2DSpace) {
|
): Map<DubinsPath.Type, ObstacleTangent> = with(Float64Space2D) {
|
||||||
val first = obstacles[firstIndex]
|
val first = obstacles[firstIndex]
|
||||||
val second = obstacles[secondIndex]
|
val second = obstacles[secondIndex]
|
||||||
buildMap {
|
buildMap {
|
||||||
@ -99,7 +107,7 @@ public class Obstacles(public val obstacles: List<Obstacle>) {
|
|||||||
private fun tangentsFromArc(
|
private fun tangentsFromArc(
|
||||||
arc: CircleTrajectory2D,
|
arc: CircleTrajectory2D,
|
||||||
obstacleIndex: Int,
|
obstacleIndex: Int,
|
||||||
): Map<DubinsPath.Type, ObstacleTangent> = with(Euclidean2DSpace) {
|
): Map<DubinsPath.Type, ObstacleTangent> = with(Float64Space2D) {
|
||||||
val obstacle = obstacles[obstacleIndex]
|
val obstacle = obstacles[obstacleIndex]
|
||||||
buildMap {
|
buildMap {
|
||||||
for (circleIndex in obstacle.arcs.indices) {
|
for (circleIndex in obstacle.arcs.indices) {
|
||||||
@ -127,7 +135,7 @@ public class Obstacles(public val obstacles: List<Obstacle>) {
|
|||||||
obstacleIndex: Int,
|
obstacleIndex: Int,
|
||||||
obstacleDirection: Trajectory2D.Direction,
|
obstacleDirection: Trajectory2D.Direction,
|
||||||
arc: CircleTrajectory2D,
|
arc: CircleTrajectory2D,
|
||||||
): ObstacleTangent? = with(Euclidean2DSpace) {
|
): ObstacleTangent? = with(Float64Space2D) {
|
||||||
val obstacle = obstacles[obstacleIndex]
|
val obstacle = obstacles[obstacleIndex]
|
||||||
for (circleIndex in obstacle.arcs.indices) {
|
for (circleIndex in obstacle.arcs.indices) {
|
||||||
val obstacleArc = obstacle.arcs[circleIndex]
|
val obstacleArc = obstacle.arcs[circleIndex]
|
||||||
@ -207,7 +215,7 @@ public class Obstacles(public val obstacles: List<Obstacle>) {
|
|||||||
|
|
||||||
private fun avoiding(
|
private fun avoiding(
|
||||||
dubinsPath: CompositeTrajectory2D,
|
dubinsPath: CompositeTrajectory2D,
|
||||||
): Collection<Trajectory2D> = with(Euclidean2DSpace) {
|
): Collection<Trajectory2D> = with(Float64Space2D) {
|
||||||
//fast return if no obstacles intersect the direct path
|
//fast return if no obstacles intersect the direct path
|
||||||
if (obstacles.none { it.intersectsTrajectory(dubinsPath) }) return listOf(dubinsPath)
|
if (obstacles.none { it.intersectsTrajectory(dubinsPath) }) return listOf(dubinsPath)
|
||||||
|
|
||||||
@ -337,7 +345,7 @@ public class Obstacles(public val obstacles: List<Obstacle>) {
|
|||||||
start: Pose2D,
|
start: Pose2D,
|
||||||
finish: Pose2D,
|
finish: Pose2D,
|
||||||
radius: Double,
|
radius: Double,
|
||||||
vararg polygons: Polygon<Double>,
|
vararg polygons: Polygon<Vector2D<Float64>>,
|
||||||
): List<Trajectory2D> {
|
): List<Trajectory2D> {
|
||||||
val obstacles: List<Obstacle> = polygons.map { polygon ->
|
val obstacles: List<Obstacle> = polygons.map { polygon ->
|
||||||
Obstacle(polygon.points, radius)
|
Obstacle(polygon.points, radius)
|
||||||
@ -350,7 +358,7 @@ public class Obstacles(public val obstacles: List<Obstacle>) {
|
|||||||
start: Pose2D,
|
start: Pose2D,
|
||||||
finish: Pose2D,
|
finish: Pose2D,
|
||||||
radius: Double,
|
radius: Double,
|
||||||
polygons: Collection<Polygon<Double>>,
|
polygons: Collection<Polygon<Vector2D<Float64>>>,
|
||||||
): List<Trajectory2D> {
|
): List<Trajectory2D> {
|
||||||
val obstacles: List<Obstacle> = polygons.map { polygon ->
|
val obstacles: List<Obstacle> = polygons.map { polygon ->
|
||||||
Obstacle(polygon.points, radius)
|
Obstacle(polygon.points, radius)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* Copyright 2018-2022 KMath contributors.
|
* Copyright 2018-2022 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.
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
*/
|
*/
|
||||||
@file:UseSerializers(Euclidean2DSpace.VectorSerializer::class)
|
@file:UseSerializers(Float64Space2D.VectorSerializer::class)
|
||||||
|
|
||||||
package space.kscience.trajectory
|
package space.kscience.trajectory
|
||||||
|
|
||||||
@ -14,14 +14,16 @@ import kotlinx.serialization.descriptors.SerialDescriptor
|
|||||||
import kotlinx.serialization.encoding.Decoder
|
import kotlinx.serialization.encoding.Decoder
|
||||||
import kotlinx.serialization.encoding.Encoder
|
import kotlinx.serialization.encoding.Encoder
|
||||||
import space.kscience.kmath.geometry.*
|
import space.kscience.kmath.geometry.*
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
|
||||||
|
import space.kscience.kmath.structures.Float64
|
||||||
import kotlin.math.atan2
|
import kotlin.math.atan2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combination of [Vector] and its view angle (clockwise from positive y-axis direction)
|
* Combination of [Vector] and its view angle (clockwise from positive y-axis direction)
|
||||||
*/
|
*/
|
||||||
@Serializable(Pose2DSerializer::class)
|
@Serializable(Pose2DSerializer::class)
|
||||||
public interface Pose2D : DoubleVector2D {
|
public interface Pose2D : Vector2D<Float64> {
|
||||||
public val coordinates: DoubleVector2D
|
public val coordinates: Vector2D<Float64>
|
||||||
public val bearing: Angle
|
public val bearing: Angle
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,9 +33,9 @@ public interface Pose2D : DoubleVector2D {
|
|||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public fun bearingToVector(bearing: Angle): Vector2D<Double> =
|
public fun bearingToVector(bearing: Angle): Vector2D<Double> =
|
||||||
Euclidean2DSpace.vector(cos(bearing), sin(bearing))
|
Float64Space2D.vector(cos(bearing), sin(bearing))
|
||||||
|
|
||||||
public fun vectorToBearing(vector2D: DoubleVector2D): Angle {
|
public fun vectorToBearing(vector2D: Vector2D<Float64>): Angle {
|
||||||
require(vector2D.x != 0.0 || vector2D.y != 0.0) { "Can't get bearing of zero vector" }
|
require(vector2D.x != 0.0 || vector2D.y != 0.0) { "Can't get bearing of zero vector" }
|
||||||
return atan2(vector2D.y, vector2D.x).radians
|
return atan2(vector2D.y, vector2D.x).radians
|
||||||
}
|
}
|
||||||
@ -43,25 +45,24 @@ public interface Pose2D : DoubleVector2D {
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
public class PhaseVector2D(
|
public class PhaseVector2D(
|
||||||
override val coordinates: DoubleVector2D,
|
override val coordinates: Vector2D<Float64>,
|
||||||
public val velocity: DoubleVector2D,
|
public val velocity: Vector2D<Float64>,
|
||||||
) : Pose2D, DoubleVector2D by coordinates {
|
) : Pose2D, Vector2D<Float64> by coordinates {
|
||||||
override val bearing: Angle get() = atan2(velocity.x, velocity.y).radians
|
override val bearing: Angle get() = atan2(velocity.x, velocity.y).radians
|
||||||
|
|
||||||
override fun reversed(): Pose2D = with(Euclidean2DSpace) { PhaseVector2D(coordinates, -velocity) }
|
override fun reversed(): Pose2D = with(Float64Space2D) { PhaseVector2D(coordinates, -velocity) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("DubinsPose2D")
|
@SerialName("DubinsPose2D")
|
||||||
private class Pose2DImpl(
|
private class Pose2DImpl(
|
||||||
override val coordinates: DoubleVector2D,
|
override val coordinates: Vector2D<Float64>,
|
||||||
override val bearing: Angle,
|
override val bearing: Angle,
|
||||||
) : Pose2D, DoubleVector2D by coordinates {
|
) : Pose2D, Vector2D<Float64> by coordinates {
|
||||||
|
|
||||||
override fun reversed(): Pose2D = Pose2DImpl(coordinates, bearing.plus(Angle.pi).normalized())
|
override fun reversed(): Pose2D = Pose2DImpl(coordinates, bearing.plus(Angle.pi).normalized())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
override fun toString(): String = "Pose2D(x=$x, y=$y, bearing=$bearing)"
|
override fun toString(): String = "Pose2D(x=$x, y=$y, bearing=$bearing)"
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
@ -96,11 +97,11 @@ public object Pose2DSerializer : KSerializer<Pose2D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun Pose2D(coordinate: DoubleVector2D, bearing: Angle): Pose2D =
|
public fun Pose2D(coordinate: Vector2D<Float64>, bearing: Angle): Pose2D =
|
||||||
Pose2DImpl(coordinate, bearing)
|
Pose2DImpl(coordinate, bearing)
|
||||||
|
|
||||||
public fun Pose2D(point: DoubleVector2D, direction: DoubleVector2D): Pose2D =
|
public fun Pose2D(point: Vector2D<Float64>, direction: Vector2D<Float64>): Pose2D =
|
||||||
Pose2D(point, Pose2D.vectorToBearing(direction))
|
Pose2D(point, Pose2D.vectorToBearing(direction))
|
||||||
|
|
||||||
public fun Pose2D(x: Number, y: Number, bearing: Angle): Pose2D =
|
public fun Pose2D(x: Number, y: Number, bearing: Angle): Pose2D =
|
||||||
Pose2DImpl(Euclidean2DSpace.vector(x, y), bearing)
|
Pose2DImpl(Float64Space2D.vector(x, y), bearing)
|
@ -2,16 +2,21 @@
|
|||||||
* Copyright 2018-2022 KMath contributors.
|
* Copyright 2018-2022 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.
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
*/
|
*/
|
||||||
@file:UseSerializers(Euclidean2DSpace.VectorSerializer::class)
|
@file:UseSerializers(Float64Space2D.VectorSerializer::class)
|
||||||
|
|
||||||
package space.kscience.trajectory
|
package space.kscience.trajectory
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.UseSerializers
|
import kotlinx.serialization.UseSerializers
|
||||||
|
import space.kscience.intersects
|
||||||
|
import space.kscience.intersectsOrInside
|
||||||
import space.kscience.kmath.geometry.*
|
import space.kscience.kmath.geometry.*
|
||||||
import space.kscience.kmath.geometry.Euclidean2DSpace.distanceTo
|
import space.kscience.kmath.geometry.euclidean2d.Circle2D
|
||||||
import space.kscience.kmath.geometry.Euclidean2DSpace.minus
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D.distanceTo
|
||||||
|
import space.kscience.kmath.structures.Float64
|
||||||
|
import space.kscience.tangent
|
||||||
import kotlin.math.atan2
|
import kotlin.math.atan2
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -44,7 +49,7 @@ public sealed interface Trajectory2D {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public val DoubleVector2D.bearing: Angle get() = (atan2(x, y).radians).normalized()
|
public val Vector2D<Float64>.bearing: Angle get() = (atan2(x, y).radians).normalized()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Straight path segment. The order of start and end defines the direction
|
* Straight path segment. The order of start and end defines the direction
|
||||||
@ -52,13 +57,13 @@ public val DoubleVector2D.bearing: Angle get() = (atan2(x, y).radians).normalize
|
|||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("straight")
|
@SerialName("straight")
|
||||||
public data class StraightTrajectory2D(
|
public data class StraightTrajectory2D(
|
||||||
override val begin: DoubleVector2D,
|
override val begin: Vector2D<Float64>,
|
||||||
override val end: DoubleVector2D,
|
override val end: Vector2D<Float64>,
|
||||||
) : Trajectory2D, LineSegment2D {
|
) : Trajectory2D, LineSegment2D {
|
||||||
|
|
||||||
override val length: Double get() = begin.distanceTo(end)
|
override val length: Double get() = begin.distanceTo(end)
|
||||||
|
|
||||||
public val bearing: Angle get() = (end - begin).bearing
|
public val bearing: Angle get() = with(Float64Space2D) { (end - begin).bearing }
|
||||||
|
|
||||||
override val beginPose: Pose2D get() = Pose2D(begin, bearing)
|
override val beginPose: Pose2D get() = Pose2D(begin, bearing)
|
||||||
override val endPose: Pose2D get() = Pose2D(end, bearing)
|
override val endPose: Pose2D get() = Pose2D(end, bearing)
|
||||||
@ -75,7 +80,7 @@ public fun StraightTrajectory2D(segment: LineSegment2D): StraightTrajectory2D =
|
|||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("arc")
|
@SerialName("arc")
|
||||||
public data class CircleTrajectory2D(
|
public data class CircleTrajectory2D(
|
||||||
public val circle: Circle2D,
|
public val circle: Circle2D<Float64>,
|
||||||
public val arcStart: Angle,
|
public val arcStart: Angle,
|
||||||
public val arcAngle: Angle,
|
public val arcAngle: Angle,
|
||||||
) : Trajectory2D {
|
) : Trajectory2D {
|
||||||
@ -86,7 +91,7 @@ public data class CircleTrajectory2D(
|
|||||||
override val endPose: Pose2D get() = circle.tangent(arcEnd, direction)
|
override val endPose: Pose2D get() = circle.tangent(arcEnd, direction)
|
||||||
|
|
||||||
override val length: Double by lazy {
|
override val length: Double by lazy {
|
||||||
circle.radius * kotlin.math.abs(arcAngle.radians)
|
circle.radius * kotlin.math.abs(arcAngle.toRadians().value)
|
||||||
}
|
}
|
||||||
|
|
||||||
val center: Vector2D<Double> get() = circle.center
|
val center: Vector2D<Double> get() = circle.center
|
||||||
@ -98,11 +103,11 @@ public data class CircleTrajectory2D(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fun CircleTrajectory2D(
|
public fun CircleTrajectory2D(
|
||||||
center: DoubleVector2D,
|
center: Vector2D<Float64>,
|
||||||
start: DoubleVector2D,
|
start: Vector2D<Float64>,
|
||||||
end: DoubleVector2D,
|
end: Vector2D<Float64>,
|
||||||
direction: Trajectory2D.Direction,
|
direction: Trajectory2D.Direction,
|
||||||
): CircleTrajectory2D = with(Euclidean2DSpace) {
|
): CircleTrajectory2D = with(Float64Space2D) {
|
||||||
val startVector = start - center
|
val startVector = start - center
|
||||||
val endVector = end - center
|
val endVector = end - center
|
||||||
val startRadius = norm(startVector)
|
val startRadius = norm(startVector)
|
||||||
@ -131,11 +136,11 @@ public fun CircleTrajectory2D(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fun CircleTrajectory2D(
|
public fun CircleTrajectory2D(
|
||||||
circle: Circle2D,
|
circle: Circle2D<Float64>,
|
||||||
start: DoubleVector2D,
|
start: Vector2D<Float64>,
|
||||||
end: DoubleVector2D,
|
end: Vector2D<Float64>,
|
||||||
direction: Trajectory2D.Direction,
|
direction: Trajectory2D.Direction,
|
||||||
): CircleTrajectory2D = with(Euclidean2DSpace) {
|
): CircleTrajectory2D = with(Float64Space2D) {
|
||||||
val startVector = start - circle.center
|
val startVector = start - circle.center
|
||||||
val endVector = end - circle.center
|
val endVector = end - circle.center
|
||||||
val startBearing = startVector.bearing
|
val startBearing = startVector.bearing
|
||||||
@ -161,10 +166,10 @@ public fun CircleTrajectory2D(
|
|||||||
|
|
||||||
@Deprecated("Use angle notation instead")
|
@Deprecated("Use angle notation instead")
|
||||||
public fun CircleTrajectory2D(
|
public fun CircleTrajectory2D(
|
||||||
circle: Circle2D,
|
circle: Circle2D<Float64>,
|
||||||
beginPose: Pose2D,
|
beginPose: Pose2D,
|
||||||
endPose: Pose2D,
|
endPose: Pose2D,
|
||||||
): CircleTrajectory2D = with(Euclidean2DSpace) {
|
): CircleTrajectory2D = with(Float64Space2D) {
|
||||||
val vectorToBegin = beginPose - circle.center
|
val vectorToBegin = beginPose - circle.center
|
||||||
val vectorToEnd = endPose - circle.center
|
val vectorToEnd = endPose - circle.center
|
||||||
//TODO check pose bearing
|
//TODO check pose bearing
|
||||||
@ -185,7 +190,7 @@ public class CompositeTrajectory2D(public val segments: List<Trajectory2D>) : Tr
|
|||||||
public fun CompositeTrajectory2D(vararg segments: Trajectory2D): CompositeTrajectory2D =
|
public fun CompositeTrajectory2D(vararg segments: Trajectory2D): CompositeTrajectory2D =
|
||||||
CompositeTrajectory2D(segments.toList())
|
CompositeTrajectory2D(segments.toList())
|
||||||
|
|
||||||
public fun Euclidean2DSpace.intersectsTrajectory(a: Trajectory2D, b: Trajectory2D): Boolean = when (a) {
|
public fun Float64Space2D.intersectsTrajectory(a: Trajectory2D, b: Trajectory2D): Boolean = when (a) {
|
||||||
is CircleTrajectory2D -> when (b) {
|
is CircleTrajectory2D -> when (b) {
|
||||||
is CircleTrajectory2D -> intersectsOrInside(a.circle, b.circle)
|
is CircleTrajectory2D -> intersectsOrInside(a.circle, b.circle)
|
||||||
is StraightTrajectory2D -> intersects(a.circle, b)
|
is StraightTrajectory2D -> intersects(a.circle, b)
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package space.kscience.trajectory
|
package space.kscience.trajectory
|
||||||
|
|
||||||
|
import space.kscience.containsPoint
|
||||||
import space.kscience.kmath.geometry.*
|
import space.kscience.kmath.geometry.*
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Circle2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
|
||||||
|
import space.kscience.kmath.structures.Float64
|
||||||
import space.kscience.trajectory.DubinsPath.Type
|
import space.kscience.trajectory.DubinsPath.Type
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
@ -10,9 +14,9 @@ import kotlin.math.*
|
|||||||
* This method returns a map of segments using [DubinsPath] connection type notation.
|
* This method returns a map of segments using [DubinsPath] connection type notation.
|
||||||
*/
|
*/
|
||||||
internal fun tangentsBetweenCircles(
|
internal fun tangentsBetweenCircles(
|
||||||
first: Circle2D,
|
first: Circle2D<Float64>,
|
||||||
second: Circle2D,
|
second: Circle2D<Float64>,
|
||||||
): Map<Type, StraightTrajectory2D> = with(Euclidean2DSpace) {
|
): Map<Type, StraightTrajectory2D> = with(Float64Space2D) {
|
||||||
// Distance between centers
|
// Distance between centers
|
||||||
val distanceBetweenCenters: Double = first.center.distanceTo(second.center)
|
val distanceBetweenCenters: Double = first.center.distanceTo(second.center)
|
||||||
|
|
||||||
|
@ -5,6 +5,11 @@
|
|||||||
|
|
||||||
package space.kscience.kmath.geometry
|
package space.kscience.kmath.geometry
|
||||||
|
|
||||||
|
import space.kscience.circle
|
||||||
|
import space.kscience.containsPoint
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Circle2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.circumference
|
||||||
import space.kscience.trajectory.CircleTrajectory2D
|
import space.kscience.trajectory.CircleTrajectory2D
|
||||||
import space.kscience.trajectory.Trajectory2D
|
import space.kscience.trajectory.Trajectory2D
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
@ -16,7 +21,7 @@ import kotlin.test.assertTrue
|
|||||||
class ArcTests {
|
class ArcTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun arc() = with(Euclidean2DSpace) {
|
fun arc() = with(Float64Space2D) {
|
||||||
val circle = Circle2D(vector(0.0, 0.0), 2.0)
|
val circle = Circle2D(vector(0.0, 0.0), 2.0)
|
||||||
val arc = CircleTrajectory2D(
|
val arc = CircleTrajectory2D(
|
||||||
circle.center,
|
circle.center,
|
||||||
@ -25,12 +30,12 @@ class ArcTests {
|
|||||||
Trajectory2D.R
|
Trajectory2D.R
|
||||||
)
|
)
|
||||||
assertEquals(circle.circumference / 4, arc.length, 1.0)
|
assertEquals(circle.circumference / 4, arc.length, 1.0)
|
||||||
assertEquals(0.0, arc.beginPose.bearing.degrees)
|
assertEquals(0.0, arc.beginPose.bearing.toDegrees().value)
|
||||||
assertEquals(90.0, arc.endPose.bearing.degrees)
|
assertEquals(90.0, arc.endPose.bearing.toDegrees().value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun quarter() = with(Euclidean2DSpace) {
|
fun quarter() = with(Float64Space2D) {
|
||||||
val circle = circle(1, 0, 1)
|
val circle = circle(1, 0, 1)
|
||||||
val arc = CircleTrajectory2D(
|
val arc = CircleTrajectory2D(
|
||||||
circle,
|
circle,
|
||||||
@ -38,11 +43,11 @@ class ArcTests {
|
|||||||
(PI/2).radians
|
(PI/2).radians
|
||||||
)
|
)
|
||||||
assertEquals(Trajectory2D.R, arc.direction)
|
assertEquals(Trajectory2D.R, arc.direction)
|
||||||
assertEquals(PI, arc.arcEnd.radians, 1e-4)
|
assertEquals(PI, arc.arcEnd.toRadians().value, 1e-4)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun arcContains() = with(Euclidean2DSpace) {
|
fun arcContains() = with(Float64Space2D) {
|
||||||
val circle = circle(0, 0, 1.0)
|
val circle = circle(0, 0, 1.0)
|
||||||
|
|
||||||
val arc1 = CircleTrajectory2D(circle, Angle.pi / 4, Angle.piDiv2)
|
val arc1 = CircleTrajectory2D(circle, Angle.pi / 4, Angle.piDiv2)
|
||||||
|
@ -5,6 +5,14 @@
|
|||||||
|
|
||||||
package space.kscience.kmath.geometry
|
package space.kscience.kmath.geometry
|
||||||
|
|
||||||
|
import space.kscience.circle
|
||||||
|
import space.kscience.intersects
|
||||||
|
import space.kscience.intersectsOrInside
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Circle2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.circumference
|
||||||
|
import space.kscience.kmath.structures.Float64
|
||||||
|
import space.kscience.segment
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
@ -16,7 +24,7 @@ class CircleTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun circle() {
|
fun circle() {
|
||||||
val center = Euclidean2DSpace.vector(0.0, 0.0)
|
val center = Float64Space2D.vector(0.0, 0.0)
|
||||||
val radius = 2.0
|
val radius = 2.0
|
||||||
val expectedCircumference = 12.56637
|
val expectedCircumference = 12.56637
|
||||||
val circle = Circle2D(center, radius)
|
val circle = Circle2D(center, radius)
|
||||||
@ -24,7 +32,7 @@ class CircleTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun circleIntersection() = with(Euclidean2DSpace) {
|
fun circleIntersection() = with(Float64Space2D) {
|
||||||
assertTrue {
|
assertTrue {
|
||||||
intersectsOrInside(
|
intersectsOrInside(
|
||||||
circle(0.0, 0.0, 1.0),
|
circle(0.0, 0.0, 1.0),
|
||||||
@ -46,7 +54,7 @@ class CircleTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun circleLineIntersection() = with(Euclidean2DSpace) {
|
fun circleLineIntersection() = with(Float64Space2D) {
|
||||||
assertTrue {
|
assertTrue {
|
||||||
intersects(circle(0, 0, 1.0), segment(1, 1, -1, 1))
|
intersects(circle(0, 0, 1.0), segment(1, 1, -1, 1))
|
||||||
}
|
}
|
||||||
@ -84,7 +92,7 @@ class CircleTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Euclidean2DSpace.oldIntersect(circle: Circle2D, segment: LineSegment2D): Boolean{
|
private fun Float64Space2D.oldIntersect(circle: Circle2D<Float64>, segment: LineSegment2D): Boolean{
|
||||||
val begin = segment.begin
|
val begin = segment.begin
|
||||||
val end = segment.end
|
val end = segment.end
|
||||||
val lengthSquared = (begin.x - end.x).pow(2) + (begin.y - end.y).pow(2)
|
val lengthSquared = (begin.x - end.x).pow(2) + (begin.y - end.y).pow(2)
|
||||||
@ -111,7 +119,7 @@ class CircleTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun oldCircleLineIntersection() = with(Euclidean2DSpace){
|
fun oldCircleLineIntersection() = with(Float64Space2D){
|
||||||
assertTrue {
|
assertTrue {
|
||||||
oldIntersect(circle(0, 0, 1.1), segment(1, 1, -1, 1))
|
oldIntersect(circle(0, 0, 1.1), segment(1, 1, -1, 1))
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
package space.kscience.kmath.geometry
|
package space.kscience.kmath.geometry
|
||||||
|
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
|
||||||
import space.kscience.trajectory.StraightTrajectory2D
|
import space.kscience.trajectory.StraightTrajectory2D
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
@ -14,22 +15,22 @@ import kotlin.test.assertEquals
|
|||||||
class LineTests {
|
class LineTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun lineTest() = with(Euclidean2DSpace){
|
fun lineTest() = with(Float64Space2D){
|
||||||
val straight = StraightTrajectory2D(vector(0.0, 0.0), vector(100.0, 100.0))
|
val straight = StraightTrajectory2D(vector(0.0, 0.0), vector(100.0, 100.0))
|
||||||
assertEquals(sqrt(100.0.pow(2) + 100.0.pow(2)), straight.length)
|
assertEquals(sqrt(100.0.pow(2) + 100.0.pow(2)), straight.length)
|
||||||
assertEquals(45.0, straight.bearing.degrees)
|
assertEquals(45.0, straight.bearing.toDegrees().value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun lineAngleTest() = with(Euclidean2DSpace){
|
fun lineAngleTest() = with(Float64Space2D){
|
||||||
//val zero = Vector2D(0.0, 0.0)
|
//val zero = Vector2D(0.0, 0.0)
|
||||||
val north = StraightTrajectory2D(zero, vector(0.0, 2.0))
|
val north = StraightTrajectory2D(zero, vector(0.0, 2.0))
|
||||||
assertEquals(0.0, north.bearing.degrees)
|
assertEquals(0.0, north.bearing.toDegrees().value)
|
||||||
val east = StraightTrajectory2D(zero, vector(2.0, 0.0))
|
val east = StraightTrajectory2D(zero, vector(2.0, 0.0))
|
||||||
assertEquals(90.0, east.bearing.degrees)
|
assertEquals(90.0, east.bearing.toDegrees().value)
|
||||||
val south = StraightTrajectory2D(zero, vector(0.0, -2.0))
|
val south = StraightTrajectory2D(zero, vector(0.0, -2.0))
|
||||||
assertEquals(180.0, south.bearing.degrees)
|
assertEquals(180.0, south.bearing.toDegrees().value)
|
||||||
val west = StraightTrajectory2D(zero, vector(-2.0, 0.0))
|
val west = StraightTrajectory2D(zero, vector(-2.0, 0.0))
|
||||||
assertEquals(270.0, west.bearing.degrees)
|
assertEquals(270.0, west.bearing.toDegrees().value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
package space.kscience.trajectory
|
package space.kscience.trajectory
|
||||||
|
|
||||||
import space.kscience.kmath.geometry.Euclidean2DSpace
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNotNull
|
||||||
@ -14,7 +14,7 @@ import kotlin.test.assertNotNull
|
|||||||
class DubinsTests {
|
class DubinsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun dubinsTest() = with(Euclidean2DSpace) {
|
fun dubinsTest() = with(Float64Space2D) {
|
||||||
val straight = StraightTrajectory2D(vector(0.0, 0.0), vector(100.0, 100.0))
|
val straight = StraightTrajectory2D(vector(0.0, 0.0), vector(100.0, 100.0))
|
||||||
val lineP1 = straight.shift(1, 10.0).inverse()
|
val lineP1 = straight.shift(1, 10.0).inverse()
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ class DubinsTests {
|
|||||||
assertEquals(c.beginPose, b.endPose, 1e-4)
|
assertEquals(c.beginPose, b.endPose, 1e-4)
|
||||||
} else if (b is StraightTrajectory2D) {
|
} else if (b is StraightTrajectory2D) {
|
||||||
assertEquals(a.endPose, Pose2D(b.begin, b.bearing), 1e-4)
|
assertEquals(a.endPose, Pose2D(b.begin, b.bearing), 1e-4)
|
||||||
assertEquals(c.beginPose, Pose2D(b.end, b.bearing),1e-4)
|
assertEquals(c.beginPose, Pose2D(b.end, b.bearing), 1e-4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,10 @@
|
|||||||
package space.kscience.trajectory
|
package space.kscience.trajectory
|
||||||
|
|
||||||
import space.kscience.kmath.geometry.Angle
|
import space.kscience.kmath.geometry.Angle
|
||||||
import space.kscience.kmath.geometry.Circle2D
|
|
||||||
import space.kscience.kmath.geometry.Degrees
|
import space.kscience.kmath.geometry.Degrees
|
||||||
import space.kscience.kmath.geometry.Euclidean2DSpace.vector
|
|
||||||
import space.kscience.kmath.geometry.degrees
|
import space.kscience.kmath.geometry.degrees
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Circle2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -18,7 +18,7 @@ import kotlin.test.assertTrue
|
|||||||
class ObstacleTest {
|
class ObstacleTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun equalObstacles() {
|
fun equalObstacles() = with(Float64Space2D) {
|
||||||
val circle1 = Circle2D(vector(1.0, 6.5), 0.5)
|
val circle1 = Circle2D(vector(1.0, 6.5), 0.5)
|
||||||
val circle2 = Circle2D(vector(1.0, 6.5), 0.5)
|
val circle2 = Circle2D(vector(1.0, 6.5), 0.5)
|
||||||
assertEquals(circle1, circle2)
|
assertEquals(circle1, circle2)
|
||||||
@ -28,7 +28,7 @@ class ObstacleTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun singePoint() {
|
fun singePoint() = with(Float64Space2D) {
|
||||||
val outputTangents: List<Trajectory2D> = Obstacles.avoidObstacles(
|
val outputTangents: List<Trajectory2D> = Obstacles.avoidObstacles(
|
||||||
Pose2D(-5, -1, Angle.pi / 4),
|
Pose2D(-5, -1, Angle.pi / 4),
|
||||||
Pose2D(20, 4, Angle.pi * 3 / 4),
|
Pose2D(20, 4, Angle.pi * 3 / 4),
|
||||||
@ -41,7 +41,7 @@ class ObstacleTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun twoObstacles() {
|
fun twoObstacles() = with(Float64Space2D) {
|
||||||
val paths = Obstacles.avoidObstacles(
|
val paths = Obstacles.avoidObstacles(
|
||||||
Pose2D(-5, -1, Angle.pi / 4),
|
Pose2D(-5, -1, Angle.pi / 4),
|
||||||
Pose2D(20, 4, Angle.pi * 3 / 4),
|
Pose2D(20, 4, Angle.pi * 3 / 4),
|
||||||
@ -64,7 +64,7 @@ class ObstacleTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun circumvention() {
|
fun circumvention() = with(Float64Space2D) {
|
||||||
val obstacle = Obstacle(
|
val obstacle = Obstacle(
|
||||||
Circle2D(vector(0.0, 0.0), 1.0),
|
Circle2D(vector(0.0, 0.0), 1.0),
|
||||||
Circle2D(vector(0.0, 1.0), 1.0),
|
Circle2D(vector(0.0, 1.0), 1.0),
|
||||||
@ -80,7 +80,7 @@ class ObstacleTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun closePoints() {
|
fun closePoints() = with(Float64Space2D) {
|
||||||
val obstacle = Obstacle(
|
val obstacle = Obstacle(
|
||||||
Circle2D(vector(0.0, 0.0), 1.0),
|
Circle2D(vector(0.0, 0.0), 1.0),
|
||||||
Circle2D(vector(0.0, 1.0), 1.0),
|
Circle2D(vector(0.0, 1.0), 1.0),
|
||||||
@ -100,7 +100,7 @@ class ObstacleTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun largeCoordinates() {
|
fun largeCoordinates() = with(Float64Space2D) {
|
||||||
val startPoints = listOf(
|
val startPoints = listOf(
|
||||||
Pose2D(x = 484149.535516561, y = 2995086.2534208703, bearing = Degrees(3.401475378237137))
|
Pose2D(x = 484149.535516561, y = 2995086.2534208703, bearing = Degrees(3.401475378237137))
|
||||||
)
|
)
|
||||||
|
@ -5,18 +5,17 @@
|
|||||||
|
|
||||||
package space.kscience.trajectory
|
package space.kscience.trajectory
|
||||||
|
|
||||||
import space.kscience.kmath.geometry.Circle2D
|
|
||||||
import space.kscience.kmath.geometry.Euclidean2DSpace
|
|
||||||
import space.kscience.kmath.geometry.Euclidean2DSpace.vector
|
|
||||||
import space.kscience.kmath.geometry.LineSegment
|
import space.kscience.kmath.geometry.LineSegment
|
||||||
import space.kscience.kmath.geometry.equalsLine
|
import space.kscience.kmath.geometry.equalsLine
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Circle2D
|
||||||
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class TangentTest {
|
class TangentTest {
|
||||||
@Test
|
@Test
|
||||||
fun tangents() {
|
fun tangents() = with(Float64Space2D) {
|
||||||
val c1 = Circle2D(vector(0.0, 0.0), 1.0)
|
val c1 = Circle2D(vector(0.0, 0.0), 1.0)
|
||||||
val c2 = Circle2D(vector(4.0, 0.0), 1.0)
|
val c2 = Circle2D(vector(4.0, 0.0), 1.0)
|
||||||
val routes = listOf(
|
val routes = listOf(
|
||||||
@ -50,12 +49,12 @@ class TangentTest {
|
|||||||
|
|
||||||
assertEquals(routes, tangentMapKeys)
|
assertEquals(routes, tangentMapKeys)
|
||||||
for (i in segments.indices) {
|
for (i in segments.indices) {
|
||||||
assertTrue(segments[i].equalsLine(Euclidean2DSpace, tangentMapValues[i]))
|
assertTrue(segments[i].equalsLine(Float64Space2D, tangentMapValues[i]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun concentric(){
|
fun concentric() = with(Float64Space2D) {
|
||||||
val c1 = Circle2D(vector(0.0, 0.0), 10.0)
|
val c1 = Circle2D(vector(0.0, 0.0), 10.0)
|
||||||
val c2 = Circle2D(vector(0.0, 0.0), 1.0)
|
val c2 = Circle2D(vector(0.0, 0.0), 1.0)
|
||||||
assertEquals(emptyMap(), tangentsBetweenCircles(c1, c2))
|
assertEquals(emptyMap(), tangentsBetweenCircles(c1, c2))
|
||||||
|
@ -5,20 +5,19 @@
|
|||||||
|
|
||||||
package space.kscience.trajectory
|
package space.kscience.trajectory
|
||||||
|
|
||||||
import space.kscience.kmath.geometry.Euclidean2DSpace
|
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
|
||||||
import space.kscience.kmath.geometry.radians
|
|
||||||
import space.kscience.kmath.geometry.sin
|
import space.kscience.kmath.geometry.sin
|
||||||
|
|
||||||
|
|
||||||
fun assertEquals(expected: Pose2D, actual: Pose2D, precision: Double = 1e-6){
|
fun assertEquals(expected: Pose2D, actual: Pose2D, precision: Double = 1e-6) {
|
||||||
kotlin.test.assertEquals(expected.x, actual.x, precision)
|
kotlin.test.assertEquals(expected.x, actual.x, precision)
|
||||||
kotlin.test.assertEquals(expected.y, actual.y, precision)
|
kotlin.test.assertEquals(expected.y, actual.y, precision)
|
||||||
kotlin.test.assertEquals(expected.bearing.radians, actual.bearing.radians, precision)
|
kotlin.test.assertEquals(expected.bearing.toRadians().value, actual.bearing.toRadians().value, precision)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun StraightTrajectory2D.inverse() = StraightTrajectory2D(end, begin)
|
fun StraightTrajectory2D.inverse() = StraightTrajectory2D(end, begin)
|
||||||
|
|
||||||
fun StraightTrajectory2D.shift(shift: Int, width: Double): StraightTrajectory2D = with(Euclidean2DSpace) {
|
fun StraightTrajectory2D.shift(shift: Int, width: Double): StraightTrajectory2D = with(Float64Space2D) {
|
||||||
val dX = width * sin(inverse().bearing)
|
val dX = width * sin(inverse().bearing)
|
||||||
val dY = width * sin(bearing)
|
val dY = width * sin(bearing)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user