diff --git a/build.gradle.kts b/build.gradle.kts index abaa2d7..a624464 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,16 +5,15 @@ plugins { id("space.kscience.gradle.project") } -val kmathVersion: String by extra("0.3.1") +val kmathVersion: String by extra("0.4.0-RC2") allprojects { group = "center.sciprog" - version = "0.3.0-dev-1" + version = "0.3.0-dev-2" repositories { mavenLocal() maven("https://repo.kotlin.link") - maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") } } diff --git a/demo/maps-js/build.gradle.kts b/demo/maps-js/build.gradle.kts deleted file mode 100644 index 3f46b2c..0000000 --- a/demo/maps-js/build.gradle.kts +++ /dev/null @@ -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 {} -} \ No newline at end of file diff --git a/demo/maps-js/src/jsMain/kotlin/Main.kt b/demo/maps-js/src/jsMain/kotlin/Main.kt deleted file mode 100644 index f67c8df..0000000 --- a/demo/maps-js/src/jsMain/kotlin/Main.kt +++ /dev/null @@ -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(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> { 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() - } - } -} diff --git a/demo/maps-js/src/jsMain/resources/index.html b/demo/maps-js/src/jsMain/resources/index.html deleted file mode 100644 index 225341b..0000000 --- a/demo/maps-js/src/jsMain/resources/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - Maps-kt demo - - - - -
- - - \ No newline at end of file diff --git a/demo/maps-wasm/build.gradle.kts b/demo/maps-wasm/build.gradle.kts new file mode 100644 index 0000000..58aa306 --- /dev/null +++ b/demo/maps-wasm/build.gradle.kts @@ -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{} + } +} \ No newline at end of file diff --git a/demo/maps-wasm/src/wasmJsMain/kotlin/Main.kt b/demo/maps-wasm/src/wasmJsMain/kotlin/Main.kt new file mode 100644 index 0000000..8c655aa --- /dev/null +++ b/demo/maps-wasm/src/wasmJsMain/kotlin/Main.kt @@ -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 = 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 = remember { + features.getBoundingBox(1f)?.computeViewPoint() ?: XYViewPoint(XY(0f, 0f)) + } + + var viewPoint: ViewPoint 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() + } +} diff --git a/demo/maps-wasm/src/wasmJsMain/resources/index.html b/demo/maps-wasm/src/wasmJsMain/resources/index.html new file mode 100644 index 0000000..5b0eca0 --- /dev/null +++ b/demo/maps-wasm/src/wasmJsMain/resources/index.html @@ -0,0 +1,12 @@ + + + + + Compose App + + + + + + + \ No newline at end of file diff --git a/demo/maps-wasm/src/wasmJsMain/resources/middle-earth.jpg b/demo/maps-wasm/src/wasmJsMain/resources/middle-earth.jpg new file mode 100644 index 0000000..4ed4735 Binary files /dev/null and b/demo/maps-wasm/src/wasmJsMain/resources/middle-earth.jpg differ diff --git a/demo/maps/src/jvmMain/kotlin/Main.kt b/demo/maps/src/jvmMain/kotlin/Main.kt index 1081d2b..c9afb2a 100644 --- a/demo/maps/src/jvmMain/kotlin/Main.kt +++ b/demo/maps/src/jvmMain/kotlin/Main.kt @@ -13,7 +13,6 @@ import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application -import center.sciprog.attributes.Attributes import center.sciprog.maps.compose.* import center.sciprog.maps.coordinates.GeodeticMapCoordinates import center.sciprog.maps.coordinates.Gmc @@ -29,6 +28,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import space.kscience.attributes.Attributes import space.kscience.kmath.geometry.Angle import space.kscience.kmath.geometry.degrees import space.kscience.kmath.geometry.radians @@ -37,7 +37,7 @@ import kotlin.math.PI import kotlin.random.Random 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) @@ -151,8 +151,8 @@ fun App() { 0.005.degrees ) { gmc -> Color( - red = ((gmc.latitude + Angle.piDiv2).degrees * 10 % 1f).toFloat(), - green = ((gmc.longitude + Angle.pi).degrees * 10 % 1f).toFloat(), + red = ((gmc.latitude + Angle.piDiv2).toDegrees().value * 10 % 1f).toFloat(), + green = ((gmc.longitude + Angle.pi).toDegrees().value * 10 % 1f).toFloat(), blue = 0f, alpha = 0.3f ) diff --git a/demo/scheme/src/jvmMain/resources/joker2023.png b/demo/scheme/src/jvmMain/resources/joker2023.png new file mode 100644 index 0000000..0b0dd84 Binary files /dev/null and b/demo/scheme/src/jvmMain/resources/joker2023.png differ diff --git a/demo/trajectory-playground/src/jvmMain/kotlin/Main.kt b/demo/trajectory-playground/src/jvmMain/kotlin/Main.kt index fde1f7e..d6a5cb7 100644 --- a/demo/trajectory-playground/src/jvmMain/kotlin/Main.kt +++ b/demo/trajectory-playground/src/jvmMain/kotlin/Main.kt @@ -12,13 +12,13 @@ import center.sciprog.maps.features.* import center.sciprog.maps.scheme.SchemeView import center.sciprog.maps.scheme.XY import space.kscience.kmath.geometry.Angle -import space.kscience.kmath.geometry.Circle2D -import space.kscience.kmath.geometry.DoubleVector2D -import space.kscience.kmath.geometry.Euclidean2DSpace +import space.kscience.kmath.geometry.Vector2D +import space.kscience.kmath.geometry.euclidean2d.Circle2D +import space.kscience.kmath.geometry.euclidean2d.Float64Space2D import space.kscience.trajectory.* import kotlin.random.Random -private fun DoubleVector2D.toXY() = XY(x.toFloat(), y.toFloat()) +private fun Vector2D.toXY() = XY(x.toFloat(), y.toFloat()) private val random = Random(123) @@ -32,7 +32,7 @@ fun FeatureGroup.trajectory( bCoordinates = trajectory.end.toXY(), ).color(colorPicker(trajectory)) - is CircleTrajectory2D -> with(Euclidean2DSpace) { + is CircleTrajectory2D -> with(Float64Space2D) { val topLeft = 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.obstacle(obstacle: Obstacle, colorPicker: (Trajectory2D) -> polygon(obstacle.arcs.map { it.center.toXY() }).color(Color.Gray) } -fun FeatureGroup.pose(pose2D: Pose2D) = with(Euclidean2DSpace) { +fun FeatureGroup.pose(pose2D: Pose2D) = with(Float64Space2D) { line(pose2D.toXY(), (pose2D + Pose2D.bearingToVector(pose2D.bearing)).toXY()) } @Composable @Preview -fun closePoints() { +fun closePoints() = with(Float64Space2D){ SchemeView { val obstacle = Obstacle( - Circle2D(Euclidean2DSpace.vector(0.0, 0.0), 1.0), - Circle2D(Euclidean2DSpace.vector(0.0, 1.0), 1.0), - Circle2D(Euclidean2DSpace.vector(1.0, 1.0), 1.0), - Circle2D(Euclidean2DSpace.vector(1.0, 0.0), 1.0) + Circle2D(vector(0.0, 0.0), 1.0), + Circle2D(vector(0.0, 1.0), 1.0), + Circle2D(vector(1.0, 1.0), 1.0), + Circle2D(vector(1.0, 0.0), 1.0) ) val enter = Pose2D(-0.8, -0.8, Angle.pi) val exit = Pose2D(-0.8, -0.8, Angle.piDiv2) @@ -101,7 +101,7 @@ fun closePoints() { @Preview fun singleObstacle() { 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 exit = Pose2D(20, 4, Angle.pi * 3 / 4) @@ -123,19 +123,19 @@ fun singleObstacle() { @Composable @Preview -fun doubleObstacle() { +fun doubleObstacle() = with(Float64Space2D){ SchemeView { val obstacles = arrayOf( Obstacle( - Circle2D(Euclidean2DSpace.vector(1.0, 6.5), 0.5), - Circle2D(Euclidean2DSpace.vector(2.0, 1.0), 0.5), - Circle2D(Euclidean2DSpace.vector(6.0, 0.0), 0.5), - Circle2D(Euclidean2DSpace.vector(5.0, 5.0), 0.5) + Circle2D(vector(1.0, 6.5), 0.5), + Circle2D(vector(2.0, 1.0), 0.5), + Circle2D(vector(6.0, 0.0), 0.5), + Circle2D(vector(5.0, 5.0), 0.5) ), Obstacle( - Circle2D(Euclidean2DSpace.vector(10.0, 1.0), 0.5), - Circle2D(Euclidean2DSpace.vector(16.0, 0.0), 0.5), - Circle2D(Euclidean2DSpace.vector(14.0, 6.0), 0.5), - Circle2D(Euclidean2DSpace.vector(9.0, 4.0), 0.5) + Circle2D(vector(10.0, 1.0), 0.5), + Circle2D(vector(16.0, 0.0), 0.5), + Circle2D(vector(14.0, 6.0), 0.5), + Circle2D(vector(9.0, 4.0), 0.5) ) ) diff --git a/gradle.properties b/gradle.properties index fe14959..2f435d5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,7 @@ 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 agp.version=8.1.0 @@ -9,4 +10,4 @@ android.enableJetifier=true org.gradle.jvmargs=-Xmx4096m -toolsVersion=0.15.0-kotlin-1.9.20 \ No newline at end of file +toolsVersion=0.15.2-kotlin-1.9.22 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e411586..17655d0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME 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 zipStorePath=wrapper/dists diff --git a/maps-kt-compose/build.gradle.kts b/maps-kt-compose/build.gradle.kts index c7a0359..c27905f 100644 --- a/maps-kt-compose/build.gradle.kts +++ b/maps-kt-compose/build.gradle.kts @@ -7,7 +7,8 @@ plugins { kscience { jvm() - js() + wasm() + useCoroutines() } @@ -27,12 +28,12 @@ kotlin { api("io.ktor:ktor-client-cio") } } - - getByName("jsMain"){ - dependencies { - api("io.ktor:ktor-client-js") - } - } +// +// getByName("jsMain"){ +// dependencies { +// api("io.ktor:ktor-client-js") +// } +// } getByName("jvmTest") { dependencies { diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapCanvasState.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapCanvasState.kt index 3530230..e5ad739 100644 --- a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapCanvasState.kt +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapCanvasState.kt @@ -61,8 +61,8 @@ public class MapCanvasState private constructor( override fun computeViewPoint(rectangle: Rectangle): ViewPoint { val zoom = log2( min( - canvasSize.width.value / rectangle.longitudeDelta.radians, - canvasSize.height.value / rectangle.latitudeDelta.radians + canvasSize.width.value / rectangle.longitudeDelta.toRadians().value, + canvasSize.height.value / rectangle.latitudeDelta.toRadians().value ) * 2 * PI / mapTileProvider.tileSize ).coerceIn(0.0..22.0) return space.ViewPoint(rectangle.center, zoom.toFloat()) diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/mapFeatures.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/mapFeatures.kt index 29fb11a..d2141aa 100644 --- a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/mapFeatures.kt +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/mapFeatures.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import center.sciprog.maps.coordinates.* import center.sciprog.maps.features.* +import org.jetbrains.skia.Font import space.kscience.kmath.geometry.Angle import kotlin.math.ceil @@ -139,7 +140,7 @@ public fun FeatureGroup.icon( public fun FeatureGroup.text( position: Pair, text: String, - font: FeatureFont.() -> Unit = { size = 16f }, + font: Font.() -> Unit = { size = 16f }, id: String? = null, ): FeatureRef> = feature( id, diff --git a/maps-kt-core/build.gradle.kts b/maps-kt-core/build.gradle.kts index b5ecff3..75ea1e5 100644 --- a/maps-kt-core/build.gradle.kts +++ b/maps-kt-core/build.gradle.kts @@ -9,6 +9,8 @@ kscience{ jvm() js() native() + wasm() + useSerialization() dependencies{ diff --git a/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/GeodeticMapCoordinates.kt b/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/GeodeticMapCoordinates.kt index 8484b3b..0308f65 100644 --- a/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/GeodeticMapCoordinates.kt +++ b/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/GeodeticMapCoordinates.kt @@ -22,7 +22,6 @@ public class GeodeticMapCoordinates( "Longitude $longitude is not in (-PI..PI) range" } } - override val x: Angle get() = longitude override val y: Angle get() = latitude @@ -43,7 +42,7 @@ public class GeodeticMapCoordinates( } 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)" } diff --git a/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/GmcCurve.kt b/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/GmcCurve.kt index 3587156..8456cda 100644 --- a/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/GmcCurve.kt +++ b/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/GmcCurve.kt @@ -79,7 +79,7 @@ public fun GeoEllipsoid.parallelCurve(latitude: Angle, fromLongitude: Angle, toL return GmcCurve( 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), - distance = reducedRadius(latitude) * abs((fromLongitude - toLongitude).radians) + distance = reducedRadius(latitude) * abs((fromLongitude - toLongitude).toRadians().value) ) } diff --git a/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/MercatorProjection.kt b/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/MercatorProjection.kt index beaa8de..478ffd0 100644 --- a/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/MercatorProjection.kt +++ b/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/MercatorProjection.kt @@ -57,12 +57,12 @@ public open class MercatorProjection( return if (ellipsoid === GeoEllipsoid.sphere) { GeodeticMapCoordinates.ofRadians( atan(sinh(pc.y / ellipsoid.equatorRadius)), - baseLongitude.radians + (pc.x / ellipsoid.equatorRadius), + baseLongitude.toRadians().value + (pc.x / ellipsoid.equatorRadius), ) } else { GeodeticMapCoordinates.ofRadians( 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) { 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)) ) } else { val sinPhi = sin(gmc.latitude) 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) * ((1 - e * sinPhi) / (1 + e * sinPhi)).pow(e / 2) ) diff --git a/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/WebMercatorProjection.kt b/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/WebMercatorProjection.kt index 87134b4..1b7bd6d 100644 --- a/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/WebMercatorProjection.kt +++ b/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/WebMercatorProjection.kt @@ -6,7 +6,6 @@ package center.sciprog.maps.coordinates import space.kscience.kmath.geometry.abs -import space.kscience.kmath.geometry.radians import kotlin.math.* 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()) return WebMercatorCoordinates( zoom = zoom, - x = scaleFactor * (gmc.longitude.radians + PI).toFloat(), - y = scaleFactor * (PI - ln(tan(PI / 4 + gmc.latitude.radians / 2))).toFloat() + x = scaleFactor * (gmc.longitude.toRadians().value + PI).toFloat(), + y = scaleFactor * (PI - ln(tan(PI / 4 + gmc.latitude.toRadians().value / 2))).toFloat() ) } diff --git a/maps-kt-core/src/commonTest/kotlin/center/sciprog/maps/coordinates/DistanceTest.kt b/maps-kt-core/src/commonTest/kotlin/center/sciprog/maps/coordinates/DistanceTest.kt index a0ff074..7f42a46 100644 --- a/maps-kt-core/src/commonTest/kotlin/center/sciprog/maps/coordinates/DistanceTest.kt +++ b/maps-kt-core/src/commonTest/kotlin/center/sciprog/maps/coordinates/DistanceTest.kt @@ -21,7 +21,7 @@ internal class DistanceTest { val distance = curve.distance 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 @@ -30,7 +30,7 @@ internal class DistanceTest { GmcPose(moscow, (-0.6947937116552751).radians), Distance(632.035426877) ) - assertEquals(spb.latitude.radians, curve.backward.latitude.radians, 0.0001) - assertEquals(spb.longitude.radians, curve.backward.longitude.radians, 0.0001) + assertEquals(spb.latitude.toRadians().value, curve.backward.latitude.toRadians().value, 0.0001) + assertEquals(spb.longitude.toRadians().value, curve.backward.longitude.toRadians().value, 0.0001) } } \ No newline at end of file diff --git a/maps-kt-core/src/commonTest/kotlin/center/sciprog/maps/coordinates/MercatorTest.kt b/maps-kt-core/src/commonTest/kotlin/center/sciprog/maps/coordinates/MercatorTest.kt index 06576b5..f58a90f 100644 --- a/maps-kt-core/src/commonTest/kotlin/center/sciprog/maps/coordinates/MercatorTest.kt +++ b/maps-kt-core/src/commonTest/kotlin/center/sciprog/maps/coordinates/MercatorTest.kt @@ -1,6 +1,5 @@ package center.sciprog.maps.coordinates -import space.kscience.kmath.geometry.degrees import kotlin.test.Test import kotlin.test.assertEquals @@ -13,8 +12,8 @@ class MercatorTest { assertEquals(4186.0120709, mercator.x.kilometers, 1e-4) assertEquals(7510.9013658, mercator.y.kilometers, 1e-4) val backwards = MapProjection.epsg3857.toGeodetic(mercator) - assertEquals(moscow.latitude.degrees, backwards.latitude.degrees, 1e-6) - assertEquals(moscow.longitude.degrees, backwards.longitude.degrees, 1e-6) + assertEquals(moscow.latitude.toDegrees().value, backwards.latitude.toDegrees().value, 1e-6) + assertEquals(moscow.longitude.toDegrees().value, backwards.longitude.toDegrees().value, 1e-6) } @Test @@ -23,7 +22,7 @@ class MercatorTest { val projection = MercatorProjection(ellipsoid = GeoEllipsoid.WGS84) val mercator = projection.toProjection(moscow) val backwards = projection.toGeodetic(mercator) - assertEquals(moscow.latitude.degrees, backwards.latitude.degrees, 1e-6) - assertEquals(moscow.longitude.degrees, backwards.longitude.degrees, 1e-6) + assertEquals(moscow.latitude.toDegrees().value, backwards.latitude.toDegrees().value, 1e-6) + assertEquals(moscow.longitude.toDegrees().value, backwards.longitude.toDegrees().value, 1e-6) } } \ No newline at end of file diff --git a/maps-kt-features/build.gradle.kts b/maps-kt-features/build.gradle.kts index e718828..b1d7202 100644 --- a/maps-kt-features/build.gradle.kts +++ b/maps-kt-features/build.gradle.kts @@ -6,14 +6,24 @@ plugins { val kmathVersion: String by rootProject.extra -kscience{ +kscience { jvm() - js() - useSerialization{ +// js() + wasm{ + browser { + testTask { + enabled = false + } + } + } + + useCoroutines() + + useSerialization { json() } - useSerialization(sourceSet = space.kscience.gradle.DependencySourceSet.TEST){ + useSerialization(sourceSet = space.kscience.gradle.DependencySourceSet.TEST) { protobuf() } } @@ -23,8 +33,11 @@ kotlin { commonMain { dependencies { api(projects.trajectoryKt) + api(compose.runtime) 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") } } } diff --git a/maps-kt-features/src/commonMain/kotlin/center.sciprog.attributes/Attribute.kt b/maps-kt-features/src/commonMain/kotlin/center.sciprog.attributes/Attribute.kt deleted file mode 100644 index 492b74f..0000000 --- a/maps-kt-features/src/commonMain/kotlin/center.sciprog.attributes/Attribute.kt +++ /dev/null @@ -1,22 +0,0 @@ -package center.sciprog.attributes - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.builtins.serializer - -public interface Attribute - -public abstract class SerializableAttribute( - public val serialId: String, - public val serializer: KSerializer, -) : Attribute { - override fun toString(): String = serialId -} - -public interface AttributeWithDefault : Attribute { - public val default: T -} - -public interface SetAttribute : Attribute> - -public object NameAttribute : SerializableAttribute("name", String.serializer()) - diff --git a/maps-kt-features/src/commonMain/kotlin/center.sciprog.attributes/Attributes.kt b/maps-kt-features/src/commonMain/kotlin/center.sciprog.attributes/Attributes.kt deleted file mode 100644 index 128eab8..0000000 --- a/maps-kt-features/src/commonMain/kotlin/center.sciprog.attributes/Attributes.kt +++ /dev/null @@ -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, Any>) { - - public val keys: Set> get() = content.keys - - @Suppress("UNCHECKED_CAST") - public operator fun get(attribute: Attribute): 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 Attributes.getOrDefault(attribute: AttributeWithDefault): T = get(attribute) ?: attribute.default - -public fun > 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 > Attributes.withAttributeElement( - attribute: A, - attrValue: T, -): Attributes { - val currentSet: Set = get(attribute) ?: emptySet() - return Attributes( - content + (attribute to (currentSet + attrValue)) - ) -} - -/** - * Remove an element from [SetAttribute] - */ -public fun > Attributes.withoutAttributeElement( - attribute: A, - attrValue: T, -): Attributes { - val currentSet: Set = get(attribute) ?: emptySet() - return Attributes( - content + (attribute to (currentSet - attrValue)) - ) -} - -public fun > Attributes( - attribute: A, - attrValue: T, -): Attributes = Attributes(mapOf(attribute to attrValue)) - -public operator fun Attributes.plus(other: Attributes): Attributes = Attributes(content + other.content) diff --git a/maps-kt-features/src/commonMain/kotlin/center.sciprog.attributes/AttributesBuilder.kt b/maps-kt-features/src/commonMain/kotlin/center.sciprog.attributes/AttributesBuilder.kt deleted file mode 100644 index c61e20e..0000000 --- a/maps-kt-features/src/commonMain/kotlin/center.sciprog.attributes/AttributesBuilder.kt +++ /dev/null @@ -1,47 +0,0 @@ -package center.sciprog.attributes - -/** - * A safe builder for [Attributes] - */ -public class AttributesBuilder internal constructor(private val map: MutableMap, Any> = mutableMapOf()) { - - @Suppress("UNCHECKED_CAST") - public operator fun get(attribute: Attribute): T? = map[attribute] as? T - - public operator fun Attribute.invoke(value: V?) { - if (value == null) { - map.remove(this) - } else { - map[this] = value - } - } - - public fun from(attributes: Attributes) { - map.putAll(attributes.content) - } - - public fun SetAttribute.add( - attrValue: V, - ) { - val currentSet: Set = get(this) ?: emptySet() - map[this] = currentSet + attrValue - } - - /** - * Remove an element from [SetAttribute] - */ - public fun SetAttribute.remove( - attrValue: V, - ) { - val currentSet: Set = 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() \ No newline at end of file diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/Feature.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/Feature.kt index 110a3a1..6fb7bda 100644 --- a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/Feature.kt +++ b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/Feature.kt @@ -17,8 +17,9 @@ import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import center.sciprog.attributes.Attributes -import center.sciprog.attributes.NameAttribute +import org.jetbrains.skia.Font +import space.kscience.NameAttribute +import space.kscience.attributes.Attributes import space.kscience.kmath.geometry.Angle import space.kscience.kmath.nd.Structure2D @@ -331,7 +332,7 @@ public data class TextFeature( public val position: T, public val text: String, override val attributes: Attributes = Attributes.EMPTY, - public val fontConfig: FeatureFont.() -> Unit, + public val fontConfig: Font.() -> Unit, ) : DraggableFeature { override fun getBoundingBox(zoom: Float): Rectangle = space.Rectangle(position, position) diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureDrawScope.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureDrawScope.kt index b2f8d79..a2368fc 100644 --- a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureDrawScope.kt +++ b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureDrawScope.kt @@ -15,8 +15,8 @@ import androidx.compose.ui.text.TextMeasurer import androidx.compose.ui.text.drawText import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.unit.DpRect -import center.sciprog.attributes.Attributes import io.github.oshai.kotlinlogging.KotlinLogging +import space.kscience.attributes.Attributes /** * An extension of [DrawScope] to include map-specific features @@ -52,7 +52,7 @@ public class ComposeFeatureDrawScope( ) : FeatureDrawScope(state), DrawScope by drawScope { override fun drawText(text: String, position: Offset, attributes: Attributes) { try { - drawText(textMeasurer?: error("Text measurer not defined"), text, position) + drawText(textMeasurer ?: error("Text measurer not defined"), text, position) } catch (ex: Exception) { logger.error(ex) { "Failed to measure text" } } @@ -61,7 +61,7 @@ public class ComposeFeatureDrawScope( override fun painterFor(feature: PainterFeature): Painter = painterCache[feature] ?: error("Can't resolve painter for $feature") - public companion object{ + public companion object { private val logger = KotlinLogging.logger("ComposeFeatureDrawScope") } } diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureFont.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureFont.kt deleted file mode 100644 index 04838c2..0000000 --- a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureFont.kt +++ /dev/null @@ -1,5 +0,0 @@ -package center.sciprog.maps.features - -public expect class FeatureFont { - public var size: Float -} \ No newline at end of file diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureGroup.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureGroup.kt index 9d4f573..9b55c2a 100644 --- a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureGroup.kt +++ b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/FeatureGroup.kt @@ -9,8 +9,9 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import center.sciprog.attributes.Attribute -import center.sciprog.attributes.Attributes +import org.jetbrains.skia.Font +import space.kscience.attributes.Attribute +import space.kscience.attributes.Attributes import space.kscience.kmath.geometry.Angle import space.kscience.kmath.nd.* import space.kscience.kmath.structures.Buffer @@ -285,7 +286,7 @@ public fun FeatureGroup.scalableImage( public fun FeatureGroup.text( position: T, text: String, - font: FeatureFont.() -> Unit = { size = 16f }, + font: Font.() -> Unit = { size = 16f }, attributes: Attributes = Attributes.EMPTY, id: String? = null, ): FeatureRef> = feature( @@ -293,14 +294,14 @@ public fun FeatureGroup.text( TextFeature(space, position, text, fontConfig = font, attributes = attributes) ) -public fun StructureND(shape: ShapeND, initializer: (IntArray) -> T): StructureND { - val strides = Strides(shape) - return BufferND(strides, Buffer.boxing(strides.linearSize) { initializer(strides.index(it)) }) -} +//public fun StructureND(shape: ShapeND, initializer: (IntArray) -> T): StructureND { +// val strides = Strides(shape) +// return BufferND(strides, Buffer(strides.linearSize) { initializer(strides.index(it)) }) +//} -public fun Structure2D(rows: Int, columns: Int, initializer: (IntArray) -> T): Structure2D { +public inline fun Structure2D(rows: Int, columns: Int, initializer: (IntArray) -> T): Structure2D { 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 FeatureGroup.pixelMap( diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/compositeFeatures.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/compositeFeatures.kt index ac360e6..67e7967 100644 --- a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/compositeFeatures.kt +++ b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/compositeFeatures.kt @@ -1,6 +1,6 @@ package center.sciprog.maps.features -import center.sciprog.attributes.Attributes +import space.kscience.attributes.Attributes import kotlin.jvm.JvmName @@ -18,7 +18,7 @@ public fun FeatureGroup.draggableLine( space, aId.resolve().center, bId.resolve().center, - Attributes { + Attributes> { ZAttribute(-10f) lineId?.attributes?.let { from(it) } } @@ -51,7 +51,7 @@ public fun FeatureGroup.draggableMultiLine( MultiLineFeature( space, points.map { it.resolve().center }, - Attributes { + Attributes>{ ZAttribute(-10f) polygonId?.attributes?.let { from(it) } } diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/drawFeature.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/drawFeature.kt index 1f4ce2e..c6b1522 100644 --- a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/drawFeature.kt +++ b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/drawFeature.kt @@ -8,9 +8,8 @@ import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PointMode import androidx.compose.ui.graphics.drawscope.Stroke 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.geometry.degrees //internal fun Color.toPaint(): Paint = Paint().apply { @@ -57,8 +56,8 @@ public fun FeatureDrawScope.drawFeature( drawArc( color = color, - startAngle = (feature.startAngle.degrees).toFloat(), - sweepAngle = (feature.arcLength.degrees).toFloat(), + startAngle = (feature.startAngle.toDegrees().value).toFloat(), + sweepAngle = (feature.arcLength.toDegrees().value).toFloat(), useCenter = false, topLeft = dpRect.topLeft, size = size, @@ -91,7 +90,11 @@ public fun FeatureDrawScope.drawFeature( is FeatureGroup -> { feature.featureMap.values.forEach { - drawFeature(it.withAttributes { feature.attributes + this }) + drawFeature( + it.withAttributes { + feature.attributes + this + } + ) } } diff --git a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/mapFeatureAttributes.kt b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/mapFeatureAttributes.kt index 95c28cf..08b9280 100644 --- a/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/mapFeatureAttributes.kt +++ b/maps-kt-features/src/commonMain/kotlin/center/sciprog/maps/features/mapFeatureAttributes.kt @@ -6,10 +6,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerKeyboardModifiers -import center.sciprog.attributes.Attribute -import center.sciprog.attributes.AttributesBuilder -import center.sciprog.attributes.SetAttribute -import center.sciprog.attributes.withAttribute +import space.kscience.attributes.* public object ZAttribute : Attribute @@ -50,21 +47,19 @@ public fun > FeatureRef.zoomRange(range: FloatRang public object AlphaAttribute : Attribute public fun > FeatureRef.modifyAttributes( - modification: AttributesBuilder.() -> Unit + modification: AttributesBuilder.() -> Unit, ): FeatureRef { @Suppress("UNCHECKED_CAST") parent.feature( id, - resolve().withAttributes { - AttributesBuilder(this).apply(modification).build() - } as F + resolve().withAttributes { modify(modification) } as F ) return this } public fun , V> FeatureRef.modifyAttribute( key: Attribute, - value: V?, + value: V, ): FeatureRef { @Suppress("UNCHECKED_CAST") parent.feature(id, resolve().withAttributes { withAttribute(key, value) } as F) diff --git a/maps-kt-features/src/commonTest/kotlin/center/sciprog/attributes/AttributesSerializationTest.kt b/maps-kt-features/src/commonTest/kotlin/center/sciprog/attributes/AttributesSerializationTest.kt index 9c4c84e..5cb8951 100644 --- a/maps-kt-features/src/commonTest/kotlin/center/sciprog/attributes/AttributesSerializationTest.kt +++ b/maps-kt-features/src/commonTest/kotlin/center/sciprog/attributes/AttributesSerializationTest.kt @@ -1,13 +1,22 @@ 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.modules.SerializersModule import kotlinx.serialization.modules.contextual 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.Test import kotlin.test.assertEquals +import kotlin.test.assertTrue internal class AttributesSerializationTest { @@ -28,32 +37,36 @@ internal class AttributesSerializationTest { override fun toString(): String = "test" } + val serializer = AttributesSerializer(setOf(NameAttribute, TestAttribute, ContainerAttribute)) + @Test fun restoreFromJson() { + val json = Json { serializersModule = SerializersModule { - contextual(AttributesSerializer(setOf(NameAttribute, TestAttribute, ContainerAttribute))) + contextual(serializer) } } - val attributes = Attributes { + val attributes = Attributes { NameAttribute("myTest") TestAttribute(mapOf("a" to "aa", "b" to "bb")) ContainerAttribute( Container( - Attributes { + Attributes { TestAttribute(mapOf("a" to "aa", "b" to "bb")) } ) ) } - val serialized: String = json.encodeToString(attributes) + + val serialized: String = json.encodeToString(serializer, attributes) 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) @@ -62,25 +75,25 @@ internal class AttributesSerializationTest { fun restoreFromProtoBuf() { val protoBuf = ProtoBuf { serializersModule = SerializersModule { - contextual(AttributesSerializer(setOf(NameAttribute, TestAttribute, ContainerAttribute))) + contextual(serializer) } } - val attributes = Attributes { + val attributes = Attributes { NameAttribute("myTest") TestAttribute(mapOf("a" to "aa", "b" to "bb")) ContainerAttribute( Container( - Attributes { + Attributes { 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) } diff --git a/maps-kt-features/src/jsMain/kotlin/center/sciprog/maps/features/FeatureFont.kt b/maps-kt-features/src/jsMain/kotlin/center/sciprog/maps/features/FeatureFont.kt deleted file mode 100644 index 3bca24e..0000000 --- a/maps-kt-features/src/jsMain/kotlin/center/sciprog/maps/features/FeatureFont.kt +++ /dev/null @@ -1,5 +0,0 @@ -package center.sciprog.maps.features - -public actual class FeatureFont { - public actual var size: Float = 16f -} \ No newline at end of file diff --git a/maps-kt-features/src/jvmMain/kotlin/center/sciprog/maps/features/FeatureFont.kt b/maps-kt-features/src/jvmMain/kotlin/center/sciprog/maps/features/FeatureFont.kt deleted file mode 100644 index 6ec7512..0000000 --- a/maps-kt-features/src/jvmMain/kotlin/center/sciprog/maps/features/FeatureFont.kt +++ /dev/null @@ -1,5 +0,0 @@ -package center.sciprog.maps.features - -import org.jetbrains.skia.Font - -public actual typealias FeatureFont = Font \ No newline at end of file diff --git a/maps-kt-geojson/build.gradle.kts b/maps-kt-geojson/build.gradle.kts index 2203034..dd243fa 100644 --- a/maps-kt-geojson/build.gradle.kts +++ b/maps-kt-geojson/build.gradle.kts @@ -6,7 +6,9 @@ plugins { kscience{ jvm() - js() +// js() + wasm() + useSerialization { json() } diff --git a/maps-kt-geojson/src/commonMain/kotlin/center/sciprog/maps/geojson/GeoJsonGeometry.kt b/maps-kt-geojson/src/commonMain/kotlin/center/sciprog/maps/geojson/GeoJsonGeometry.kt index 1c461a5..c372f40 100644 --- a/maps-kt-geojson/src/commonMain/kotlin/center/sciprog/maps/geojson/GeoJsonGeometry.kt +++ b/maps-kt-geojson/src/commonMain/kotlin/center/sciprog/maps/geojson/GeoJsonGeometry.kt @@ -4,7 +4,6 @@ import center.sciprog.maps.coordinates.Gmc import center.sciprog.maps.coordinates.meters import center.sciprog.maps.geojson.GeoJsonGeometry.Companion.COORDINATES_KEY import kotlinx.serialization.json.* -import space.kscience.kmath.geometry.degrees import kotlin.jvm.JvmInline public sealed interface GeoJsonGeometry : GeoJson { @@ -35,8 +34,8 @@ internal fun JsonElement.toGmc() = jsonArray.run { } internal fun Gmc.toJsonArray(): JsonArray = buildJsonArray { - add(longitude.degrees) - add(latitude.degrees) + add(longitude.toDegrees().value) + add(latitude.toDegrees().value) elevation?.let { add(it.meters) } diff --git a/maps-kt-geojson/src/commonMain/kotlin/center/sciprog/maps/geojson/GeoJsonPropertiesAttribute.kt b/maps-kt-geojson/src/commonMain/kotlin/center/sciprog/maps/geojson/GeoJsonPropertiesAttribute.kt index 2643e96..27a08c5 100644 --- a/maps-kt-geojson/src/commonMain/kotlin/center/sciprog/maps/geojson/GeoJsonPropertiesAttribute.kt +++ b/maps-kt-geojson/src/commonMain/kotlin/center/sciprog/maps/geojson/GeoJsonPropertiesAttribute.kt @@ -1,7 +1,7 @@ package center.sciprog.maps.geojson -import center.sciprog.attributes.SerializableAttribute import kotlinx.serialization.json.JsonObject import kotlinx.serialization.serializer +import space.kscience.SerializableAttribute public object GeoJsonPropertiesAttribute : SerializableAttribute("properties", serializer()) \ No newline at end of file diff --git a/maps-kt-geojson/src/commonMain/kotlin/center/sciprog/maps/geojson/geoJsonToMap.kt b/maps-kt-geojson/src/commonMain/kotlin/center/sciprog/maps/geojson/geoJsonToMap.kt index 7b91d3b..49ceed7 100644 --- a/maps-kt-geojson/src/commonMain/kotlin/center/sciprog/maps/geojson/geoJsonToMap.kt +++ b/maps-kt-geojson/src/commonMain/kotlin/center/sciprog/maps/geojson/geoJsonToMap.kt @@ -1,12 +1,12 @@ package center.sciprog.maps.geojson import androidx.compose.ui.graphics.Color -import center.sciprog.attributes.NameAttribute import center.sciprog.maps.coordinates.Gmc import center.sciprog.maps.features.* import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.intOrNull import kotlinx.serialization.json.jsonPrimitive +import space.kscience.NameAttribute /** diff --git a/maps-kt-scheme/build.gradle.kts b/maps-kt-scheme/build.gradle.kts index 6264557..0e27186 100644 --- a/maps-kt-scheme/build.gradle.kts +++ b/maps-kt-scheme/build.gradle.kts @@ -6,7 +6,8 @@ plugins { kscience{ jvm() - js() +// js() + wasm() } kotlin { @@ -14,8 +15,6 @@ kotlin { commonMain { dependencies { api(projects.mapsKtFeatures) - api("io.github.microutils:kotlin-logging:2.1.23") - api(compose.foundation) } } getByName("jvmMain"){ diff --git a/maps-kt-scheme/src/commonMain/kotlin/center/sciprog/maps/scheme/SchemeView.kt b/maps-kt-scheme/src/commonMain/kotlin/center/sciprog/maps/scheme/SchemeView.kt index da4eeba..e95fb12 100644 --- a/maps-kt-scheme/src/commonMain/kotlin/center/sciprog/maps/scheme/SchemeView.kt +++ b/maps-kt-scheme/src/commonMain/kotlin/center/sciprog/maps/scheme/SchemeView.kt @@ -3,14 +3,10 @@ package center.sciprog.maps.scheme import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable 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 center.sciprog.maps.compose.canvasControls import center.sciprog.maps.features.* -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import kotlin.math.min diff --git a/maps-kt-scheme/src/commonMain/kotlin/center/sciprog/maps/scheme/XY.kt b/maps-kt-scheme/src/commonMain/kotlin/center/sciprog/maps/scheme/XY.kt index e4bba6f..7e8446b 100644 --- a/maps-kt-scheme/src/commonMain/kotlin/center/sciprog/maps/scheme/XY.kt +++ b/maps-kt-scheme/src/commonMain/kotlin/center/sciprog/maps/scheme/XY.kt @@ -10,7 +10,7 @@ import kotlin.math.abs import kotlin.math.max import kotlin.math.min -public data class XY(override val x: Float, override val y: Float): Vector2D +public data class XY(override val x: Float, override val y: Float) : Vector2D public fun XY(x: Number, y: Number): XY = XY(x.toFloat(), y.toFloat()) diff --git a/maps-kt-scheme/src/commonMain/kotlin/center/sciprog/maps/scheme/schemeFeatures.kt b/maps-kt-scheme/src/commonMain/kotlin/center/sciprog/maps/scheme/schemeFeatures.kt index 0a2a3bd..92f4fae 100644 --- a/maps-kt-scheme/src/commonMain/kotlin/center/sciprog/maps/scheme/schemeFeatures.kt +++ b/maps-kt-scheme/src/commonMain/kotlin/center/sciprog/maps/scheme/schemeFeatures.kt @@ -8,8 +8,8 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import center.sciprog.attributes.Attributes import center.sciprog.maps.features.* +import space.kscience.attributes.Attributes import space.kscience.kmath.geometry.Angle import kotlin.math.ceil diff --git a/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/SvgCanvas.kt b/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/SvgCanvas.kt index d90b11a..44025b9 100644 --- a/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/SvgCanvas.kt +++ b/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/SvgCanvas.kt @@ -301,7 +301,7 @@ internal class SvgCanvas(val graphics: SVGGraphics2D) : Canvas { } 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() } \ No newline at end of file diff --git a/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/SvgDrawScope.kt b/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/SvgDrawScope.kt index 2da269a..0bc3b71 100644 --- a/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/SvgDrawScope.kt +++ b/maps-kt-scheme/src/jvmMain/kotlin/center/sciprog/maps/svg/SvgDrawScope.kt @@ -4,15 +4,18 @@ import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size 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.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection -import center.sciprog.attributes.Attributes import center.sciprog.maps.features.* import center.sciprog.maps.scheme.XY import org.jfree.svg.SVGGraphics2D +import space.kscience.attributes.Attributes import java.awt.BasicStroke import java.awt.geom.* import java.awt.image.AffineTransformOp diff --git a/settings.gradle.kts b/settings.gradle.kts index 4254c8c..cfda25e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -61,6 +61,6 @@ include( ":demo:scheme", ":demo:polygon-editor", ":demo:trajectory-playground", - ":demo:maps-js" + ":demo:maps-wasm" ) diff --git a/trajectory-kt/build.gradle.kts b/trajectory-kt/build.gradle.kts index 45512f0..0ce04c3 100644 --- a/trajectory-kt/build.gradle.kts +++ b/trajectory-kt/build.gradle.kts @@ -11,9 +11,12 @@ kscience{ jvm() js() native() + wasm() useContextReceivers() - useSerialization() + useSerialization{ + json() + } dependencies { api("space.kscience:kmath-geometry:$kmathVersion") } diff --git a/maps-kt-features/src/commonMain/kotlin/center.sciprog.attributes/AttributesSerializer.kt b/trajectory-kt/src/commonMain/kotlin/space/kscience/AttributesSerializer.kt similarity index 68% rename from maps-kt-features/src/commonMain/kotlin/center.sciprog.attributes/AttributesSerializer.kt rename to trajectory-kt/src/commonMain/kotlin/space/kscience/AttributesSerializer.kt index 690f923..9efa48e 100644 --- a/maps-kt-features/src/commonMain/kotlin/center.sciprog.attributes/AttributesSerializer.kt +++ b/trajectory-kt/src/commonMain/kotlin/space/kscience/AttributesSerializer.kt @@ -1,12 +1,15 @@ @file:Suppress("UNCHECKED_CAST") -package center.sciprog.attributes +package space.kscience import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.* +import space.kscience.attributes.Attribute +import space.kscience.attributes.Attributes public class AttributesSerializer( private val serializableAttributes: Set>, @@ -29,12 +32,16 @@ public class AttributesSerializer( attr to value } - return Attributes(attributeMap) + return object : Attributes { + override val content: Map, 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) { 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") val serializableKey = key as SerializableAttribute @@ -46,10 +53,22 @@ public class AttributesSerializer( put( serializableKey.serialId, - json.encodeToJsonElement(serializableKey.serializer as KSerializer, value) + json.encodeToJsonElement(serializableKey.serializer as KSerializer, value) ) } } jsonSerializer.serialize(encoder, json) } -} \ No newline at end of file +} + +public abstract class SerializableAttribute( + public val serialId: String, + public val serializer: KSerializer, +) : Attribute { + override fun toString(): String = serialId +} + +public object NameAttribute : SerializableAttribute("name", String.serializer()) + +public fun Attributes.Companion.equals(a1: Attributes, a2: Attributes): Boolean = + a1.keys == a2.keys && a1.keys.all { a1[it] == a2[it] } \ No newline at end of file diff --git a/trajectory-kt/src/commonMain/kotlin/space/kscience/kmath/geometry/geometryExtensions.kt b/trajectory-kt/src/commonMain/kotlin/space/kscience/geometryExtensions.kt similarity index 64% rename from trajectory-kt/src/commonMain/kotlin/space/kscience/kmath/geometry/geometryExtensions.kt rename to trajectory-kt/src/commonMain/kotlin/space/kscience/geometryExtensions.kt index 3a06630..dc33e89 100644 --- a/trajectory-kt/src/commonMain/kotlin/space/kscience/kmath/geometry/geometryExtensions.kt +++ b/trajectory-kt/src/commonMain/kotlin/space/kscience/geometryExtensions.kt @@ -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 kotlin.math.abs import kotlin.math.pow import kotlin.math.sign 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 = Circle2D(vector(x, y), radius = radius.toDouble()) -public fun Euclidean2DSpace.segment(begin: DoubleVector2D, end: DoubleVector2D): LineSegment2D = +public fun Float64Space2D.segment(begin: Vector2D, end: Vector2D): LineSegment2D = 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)) -public fun Euclidean2DSpace.intersectsOrInside(circle1: Circle2D, circle2: Circle2D): Boolean { +public fun Float64Space2D.intersectsOrInside(circle1: Circle2D, circle2: Circle2D): Boolean { val distance = norm(circle2.center - circle1.center) 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 */ -public fun Euclidean2DSpace.intersects(segment: LineSegment2D, circle: Circle2D): Boolean { +public fun Float64Space2D.intersects(segment: LineSegment2D, circle: Circle2D): Boolean { val direction = segment.end - segment.begin 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, segment: LineSegment2D): Boolean = intersects(segment, circle) -public fun Euclidean2DSpace.intersects(segment1: LineSegment2D, segment2: LineSegment2D): Boolean { - infix fun DoubleVector2D.cross(v2: DoubleVector2D): Double = x * v2.y - y * v2.x - infix fun DoubleVector2D.crossSign(v2: DoubleVector2D) = cross(v2).sign +public fun Float64Space2D.intersects(segment1: LineSegment2D, segment2: LineSegment2D): Boolean { + infix fun Vector2D.cross(v2: Vector2D): Double = x * v2.y - y * v2.x + infix fun Vector2D.crossSign(v2: Vector2D) = cross(v2).sign - return with(Euclidean2DSpace) { + return with(Float64Space2D) { (segment2.begin - segment1.begin) crossSign (segment2.end - segment1.begin) != (segment2.begin - segment1.end) crossSign (segment2.end - segment1.end) && (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) { is CircleTrajectory2D -> intersects(segment, trajectory.circle) 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 */ -public fun Circle2D.tangent(bearing: Angle, direction: Trajectory2D.Direction): Pose2D = with(Euclidean2DSpace) { +public fun Circle2D.tangent(bearing: Angle, direction: Trajectory2D.Direction): Pose2D = with(Float64Space2D) { val coordinates: Vector2D = vector(center.x + radius * sin(bearing), center.y + radius * cos(bearing)) val tangentAngle = when (direction) { 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): Boolean = with(Float64Space2D) { val radiusVector = point - center if (abs(norm(radiusVector) - circle.radius) > 1e-4 * circle.radius) error("Wrong radius") val radiusVectorBearing = radiusVector.bearing diff --git a/trajectory-kt/src/commonMain/kotlin/space/kscience/kmath/geometry/polygonExtensions.kt b/trajectory-kt/src/commonMain/kotlin/space/kscience/kmath/geometry/polygonExtensions.kt deleted file mode 100644 index efa3277..0000000 --- a/trajectory-kt/src/commonMain/kotlin/space/kscience/kmath/geometry/polygonExtensions.kt +++ /dev/null @@ -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): Polygon = object : Polygon { - override val points: List> get() = points -} - -public fun Euclidean2DSpace.intersects(polygon: Polygon, segment: LineSegment2D): Boolean = - polygon.points.zipWithNextCircular { l, r -> segment(l, r) }.any { intersects(it, segment) } - -public fun Euclidean2DSpace.intersects(polygon: Polygon, circle: Circle2D): Boolean = - polygon.points.zipWithNextCircular { l, r -> segment(l, r) }.any { intersects(it, circle) } - -public fun Euclidean2DSpace.intersectsTrajectory(polygon: Polygon, trajectory: Trajectory2D): Boolean = - polygon.points.zipWithNextCircular { l, r -> - segment(l, r) - }.any { edge -> - intersectsTrajectory(edge, trajectory) - } \ No newline at end of file diff --git a/trajectory-kt/src/commonMain/kotlin/space/kscience/polygonExtensions.kt b/trajectory-kt/src/commonMain/kotlin/space/kscience/polygonExtensions.kt new file mode 100644 index 0000000..205db56 --- /dev/null +++ b/trajectory-kt/src/commonMain/kotlin/space/kscience/polygonExtensions.kt @@ -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>): Polygon> = + object : Polygon> { + override val points: List> get() = points + } + +public fun Float64Space2D.intersects(polygon: Polygon>, segment: LineSegment2D): Boolean = + polygon.points.zipWithNextCircular { l, r -> segment(l, r) }.any { intersects(it, segment) } + +public fun Float64Space2D.intersects(polygon: Polygon>, circle: Circle2D): Boolean = + polygon.points.zipWithNextCircular { l, r -> segment(l, r) }.any { intersects(it, circle) } + +public fun Float64Space2D.intersectsTrajectory(polygon: Polygon>, trajectory: Trajectory2D): Boolean = + polygon.points.zipWithNextCircular { l, r -> + segment(l, r) + }.any { edge -> + intersectsTrajectory(edge, trajectory) + } \ No newline at end of file diff --git a/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/DubinsPath.kt b/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/DubinsPath.kt index a4736e9..ae870c9 100644 --- a/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/DubinsPath.kt +++ b/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/DubinsPath.kt @@ -5,61 +5,70 @@ package space.kscience.trajectory -import space.kscience.kmath.geometry.* -import space.kscience.kmath.geometry.Euclidean2DSpace.distanceTo +import space.kscience.kmath.geometry.cos +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 kotlin.math.acos -internal fun Pose2D.getLeftCircle(radius: Double): Circle2D = getTangentCircles(radius).first +internal fun Pose2D.getLeftCircle(radius: Double): Circle2D = getTangentCircles(radius).first -internal fun Pose2D.getRightCircle(radius: Double): Circle2D = getTangentCircles(radius).second +internal fun Pose2D.getRightCircle(radius: Double): Circle2D = getTangentCircles(radius).second -internal fun Pose2D.getTangentCircles(radius: Double): Pair = with(Euclidean2DSpace) { - val dX = radius * cos(bearing) - val dY = radius * sin(bearing) - return Circle2D(vector(x - dX, y + dY), radius) to Circle2D(vector(x + dX, y - dY), radius) -} +internal fun Pose2D.getTangentCircles(radius: Double): Pair, Circle2D> = + with(Float64Space2D) { + val dX = radius * cos(bearing) + 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 = - with(Euclidean2DSpace) { - val centers = StraightTrajectory2D(from.center, to.center) - val p1 = when (direction) { - L -> vector( - from.center.x - from.radius * cos(centers.bearing), - from.center.y + from.radius * sin(centers.bearing) - ) +private fun Float64Space2D.outerTangent( + from: Circle2D, + to: Circle2D, + direction: Direction, +): StraightTrajectory2D { + val centers = StraightTrajectory2D(from.center, to.center) + val p1 = when (direction) { + L -> vector( + from.center.x - from.radius * cos(centers.bearing), + from.center.y + from.radius * sin(centers.bearing) + ) - R -> vector( - from.center.x + from.radius * cos(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)) + R -> vector( + from.center.x + from.radius * cos(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)) + ) +} -private fun innerTangent( - from: Circle2D, - to: Circle2D, +private fun Float64Space2D.innerTangent( + from: Circle2D, + to: Circle2D, direction: Direction, -): StraightTrajectory2D? = - with(Euclidean2DSpace) { - val centers = StraightTrajectory2D(from.center, to.center) - if (centers.length < from.radius * 2) return null - val angle = when (direction) { - L -> centers.bearing + acos(from.radius * 2 / centers.length).radians - R -> centers.bearing - acos(from.radius * 2 / centers.length).radians - }.normalized() +): StraightTrajectory2D? { + val centers = StraightTrajectory2D(from.center, to.center) + if (centers.length < from.radius * 2) return null + val angle = when (direction) { + L -> centers.bearing + acos(from.radius * 2 / centers.length).radians + R -> centers.bearing - acos(from.radius * 2 / centers.length).radians + }.normalized() - val dX = from.radius * sin(angle) - val dY = from.radius * cos(angle) - val p1 = vector(from.center.x + dX, from.center.y + dY) - val p2 = vector(to.center.x - dX, to.center.y - dY) - return StraightTrajectory2D(p1, p2) - } + val dX = from.radius * sin(angle) + val dY = from.radius * cos(angle) + val p1 = vector(from.center.x + dX, from.center.y + dY) + val p2 = vector(to.center.x - dX, to.center.y - dY) + return StraightTrajectory2D(p1, p2) +} @Suppress("DuplicatedCode") @@ -118,7 +127,7 @@ public object DubinsPath { all(start, end, turningRadius).minBy { it.length } public fun rlr(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D? = - with(Euclidean2DSpace) { + with(Float64Space2D) { val c1 = start.getRightCircle(turningRadius) val c2 = end.getRightCircle(turningRadius) val centers = StraightTrajectory2D(c1.center, c2.center) @@ -162,7 +171,7 @@ public object DubinsPath { } public fun lrl(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D? = - with(Euclidean2DSpace) { + with(Float64Space2D) { val c1 = start.getLeftCircle(turningRadius) val c2 = end.getLeftCircle(turningRadius) val centers = StraightTrajectory2D(c1.center, c2.center) @@ -208,7 +217,7 @@ public object DubinsPath { public fun rsr(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D { val c1 = start.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 a3 = CircleTrajectory2D(c2.center, s.end, end, R) return CompositeTrajectory2D(a1, s, a3) @@ -217,7 +226,7 @@ public object DubinsPath { public fun lsl(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D { val c1 = start.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 a3 = CircleTrajectory2D(c2.center, s.end, end, L) return CompositeTrajectory2D(a1, s, a3) @@ -226,7 +235,7 @@ public object DubinsPath { public fun rsl(start: Pose2D, end: Pose2D, turningRadius: Double): CompositeTrajectory2D? { val c1 = start.getRightCircle(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 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? { val c1 = start.getLeftCircle(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 val a1 = CircleTrajectory2D(c1.center, start, s.begin, L) diff --git a/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/Obstacle.kt b/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/Obstacle.kt index d52038b..9de8253 100644 --- a/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/Obstacle.kt +++ b/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/Obstacle.kt @@ -5,8 +5,15 @@ 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.structures.Float64 +import space.kscience.polygon public interface Obstacle { @@ -30,7 +37,7 @@ public interface Obstacle { } } -private class CircleObstacle(val circle: Circle2D) : Obstacle { +private class CircleObstacle(val circle: Circle2D) : Obstacle { override val center: Vector2D get() = circle.center override val arcs: List @@ -41,7 +48,7 @@ private class CircleObstacle(val circle: Circle2D) : Obstacle { override fun intersectsTrajectory(trajectory: Trajectory2D): Boolean = - Euclidean2DSpace.intersectsTrajectory(circumvention, trajectory) + Float64Space2D.intersectsTrajectory(circumvention, trajectory) override fun equals(other: Any?): Boolean { if (this === other) return true @@ -67,18 +74,18 @@ private class CoreObstacle(override val circumvention: CompositeTrajectory2D) : } override val center: Vector2D by lazy { - Euclidean2DSpace.vector( + Float64Space2D.vector( arcs.sumOf { it.center.x } / arcs.size, arcs.sumOf { it.center.y } / arcs.size ) } - val core: Polygon by lazy { - Euclidean2DSpace.polygon(arcs.map { it.circle.center }) + val core: Polygon> by lazy { + Float64Space2D.polygon(arcs.map { it.circle.center }) } override fun intersectsTrajectory(trajectory: Trajectory2D): Boolean = - Euclidean2DSpace.intersectsTrajectory(core, trajectory) + Float64Space2D.intersectsTrajectory(core, trajectory) override fun equals(other: Any?): Boolean { @@ -99,7 +106,7 @@ private class CoreObstacle(override val circumvention: CompositeTrajectory2D) : } } -public fun Obstacle(circles: List): Obstacle = with(Euclidean2DSpace) { +public fun Obstacle(circles: List>): Obstacle = with(Float64Space2D) { require(circles.isNotEmpty()) { "Can't create circumvention for an empty obstacle" } //Create a single circle obstacle if(circles.size == 1) return CircleObstacle(circles.first()) @@ -123,7 +130,7 @@ public fun Obstacle(circles: List): Obstacle = with(Euclidean2DSpace) (it.center - center).bearing } - val tangents = convex.zipWithNextCircular { a: Circle2D, b: Circle2D -> + val tangents = convex.zipWithNextCircular { a: Circle2D, b: Circle2D -> tangentsBetweenCircles(a, b)[DubinsPath.Type.RSR] ?: error("Can't find right handed circumvention") } @@ -144,7 +151,7 @@ public fun Obstacle(circles: List): Obstacle = with(Euclidean2DSpace) } -public fun Obstacle(vararg circles: Circle2D): Obstacle = Obstacle(listOf(*circles)) +public fun Obstacle(vararg circles: Circle2D): Obstacle = Obstacle(listOf(*circles)) public fun Obstacle(points: List>, radius: Double): Obstacle = Obstacle(points.map { Circle2D(it, radius) }) diff --git a/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/Obstacles.kt b/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/Obstacles.kt index 060a189..1a4ccf7 100644 --- a/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/Obstacles.kt +++ b/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/Obstacles.kt @@ -1,13 +1,21 @@ 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.component2 /** * 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 -> a != b && intersectsOrInside(a.circle, b.circle) is StraightTrajectory2D -> intersects(a.circle, b) @@ -32,7 +40,7 @@ public class Obstacles(public val obstacles: List) { val direction: Trajectory2D.Direction, ) { val obstacle: Obstacle get() = obstacles[obstacleIndex] - val circle: Circle2D get() = obstacle.arcs[nodeIndex].circle + val circle: Circle2D get() = obstacle.arcs[nodeIndex].circle } private inner class ObstacleTangent( @@ -44,7 +52,7 @@ public class Obstacles(public val obstacles: List) { * If false, this tangent intersects another obstacle */ val isValid by lazy { - with(Euclidean2DSpace) { + with(Float64Space2D) { obstacles.indices.none { it != from?.obstacleIndex && it != to?.obstacleIndex && obstacles[it].intersectsTrajectory( tangentTrajectory @@ -64,7 +72,7 @@ public class Obstacles(public val obstacles: List) { private fun tangentsBetween( firstIndex: Int, secondIndex: Int, - ): Map = with(Euclidean2DSpace) { + ): Map = with(Float64Space2D) { val first = obstacles[firstIndex] val second = obstacles[secondIndex] buildMap { @@ -99,7 +107,7 @@ public class Obstacles(public val obstacles: List) { private fun tangentsFromArc( arc: CircleTrajectory2D, obstacleIndex: Int, - ): Map = with(Euclidean2DSpace) { + ): Map = with(Float64Space2D) { val obstacle = obstacles[obstacleIndex] buildMap { for (circleIndex in obstacle.arcs.indices) { @@ -127,7 +135,7 @@ public class Obstacles(public val obstacles: List) { obstacleIndex: Int, obstacleDirection: Trajectory2D.Direction, arc: CircleTrajectory2D, - ): ObstacleTangent? = with(Euclidean2DSpace) { + ): ObstacleTangent? = with(Float64Space2D) { val obstacle = obstacles[obstacleIndex] for (circleIndex in obstacle.arcs.indices) { val obstacleArc = obstacle.arcs[circleIndex] @@ -207,7 +215,7 @@ public class Obstacles(public val obstacles: List) { private fun avoiding( dubinsPath: CompositeTrajectory2D, - ): Collection = with(Euclidean2DSpace) { + ): Collection = with(Float64Space2D) { //fast return if no obstacles intersect the direct path if (obstacles.none { it.intersectsTrajectory(dubinsPath) }) return listOf(dubinsPath) @@ -337,7 +345,7 @@ public class Obstacles(public val obstacles: List) { start: Pose2D, finish: Pose2D, radius: Double, - vararg polygons: Polygon, + vararg polygons: Polygon>, ): List { val obstacles: List = polygons.map { polygon -> Obstacle(polygon.points, radius) @@ -350,7 +358,7 @@ public class Obstacles(public val obstacles: List) { start: Pose2D, finish: Pose2D, radius: Double, - polygons: Collection>, + polygons: Collection>>, ): List { val obstacles: List = polygons.map { polygon -> Obstacle(polygon.points, radius) diff --git a/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/Pose2D.kt b/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/Pose2D.kt index 9e86f77..0007739 100644 --- a/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/Pose2D.kt +++ b/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/Pose2D.kt @@ -2,7 +2,7 @@ * 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. */ -@file:UseSerializers(Euclidean2DSpace.VectorSerializer::class) +@file:UseSerializers(Float64Space2D.VectorSerializer::class) package space.kscience.trajectory @@ -14,14 +14,16 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import space.kscience.kmath.geometry.* +import space.kscience.kmath.geometry.euclidean2d.Float64Space2D +import space.kscience.kmath.structures.Float64 import kotlin.math.atan2 /** * Combination of [Vector] and its view angle (clockwise from positive y-axis direction) */ @Serializable(Pose2DSerializer::class) -public interface Pose2D : DoubleVector2D { - public val coordinates: DoubleVector2D +public interface Pose2D : Vector2D { + public val coordinates: Vector2D public val bearing: Angle /** @@ -31,9 +33,9 @@ public interface Pose2D : DoubleVector2D { public companion object { public fun bearingToVector(bearing: Angle): Vector2D = - Euclidean2DSpace.vector(cos(bearing), sin(bearing)) + Float64Space2D.vector(cos(bearing), sin(bearing)) - public fun vectorToBearing(vector2D: DoubleVector2D): Angle { + public fun vectorToBearing(vector2D: Vector2D): Angle { require(vector2D.x != 0.0 || vector2D.y != 0.0) { "Can't get bearing of zero vector" } return atan2(vector2D.y, vector2D.x).radians } @@ -43,25 +45,24 @@ public interface Pose2D : DoubleVector2D { @Serializable public class PhaseVector2D( - override val coordinates: DoubleVector2D, - public val velocity: DoubleVector2D, -) : Pose2D, DoubleVector2D by coordinates { + override val coordinates: Vector2D, + public val velocity: Vector2D, +) : Pose2D, Vector2D by coordinates { 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 @SerialName("DubinsPose2D") private class Pose2DImpl( - override val coordinates: DoubleVector2D, + override val coordinates: Vector2D, override val bearing: Angle, -) : Pose2D, DoubleVector2D by coordinates { +) : Pose2D, Vector2D by coordinates { override fun reversed(): Pose2D = Pose2DImpl(coordinates, bearing.plus(Angle.pi).normalized()) - override fun toString(): String = "Pose2D(x=$x, y=$y, bearing=$bearing)" override fun equals(other: Any?): Boolean { if (this === other) return true @@ -96,11 +97,11 @@ public object Pose2DSerializer : KSerializer { } } -public fun Pose2D(coordinate: DoubleVector2D, bearing: Angle): Pose2D = +public fun Pose2D(coordinate: Vector2D, bearing: Angle): Pose2D = Pose2DImpl(coordinate, bearing) -public fun Pose2D(point: DoubleVector2D, direction: DoubleVector2D): Pose2D = +public fun Pose2D(point: Vector2D, direction: Vector2D): Pose2D = Pose2D(point, Pose2D.vectorToBearing(direction)) public fun Pose2D(x: Number, y: Number, bearing: Angle): Pose2D = - Pose2DImpl(Euclidean2DSpace.vector(x, y), bearing) \ No newline at end of file + Pose2DImpl(Float64Space2D.vector(x, y), bearing) \ No newline at end of file diff --git a/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/Trajectory2D.kt b/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/Trajectory2D.kt index 1254bd1..ddf7a67 100644 --- a/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/Trajectory2D.kt +++ b/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/Trajectory2D.kt @@ -2,16 +2,21 @@ * 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. */ -@file:UseSerializers(Euclidean2DSpace.VectorSerializer::class) +@file:UseSerializers(Float64Space2D.VectorSerializer::class) package space.kscience.trajectory import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.UseSerializers +import space.kscience.intersects +import space.kscience.intersectsOrInside import space.kscience.kmath.geometry.* -import space.kscience.kmath.geometry.Euclidean2DSpace.distanceTo -import space.kscience.kmath.geometry.Euclidean2DSpace.minus +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.structures.Float64 +import space.kscience.tangent import kotlin.math.atan2 @Serializable @@ -44,7 +49,7 @@ public sealed interface Trajectory2D { } -public val DoubleVector2D.bearing: Angle get() = (atan2(x, y).radians).normalized() +public val Vector2D.bearing: Angle get() = (atan2(x, y).radians).normalized() /** * 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 @SerialName("straight") public data class StraightTrajectory2D( - override val begin: DoubleVector2D, - override val end: DoubleVector2D, + override val begin: Vector2D, + override val end: Vector2D, ) : Trajectory2D, LineSegment2D { 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 endPose: Pose2D get() = Pose2D(end, bearing) @@ -75,7 +80,7 @@ public fun StraightTrajectory2D(segment: LineSegment2D): StraightTrajectory2D = @Serializable @SerialName("arc") public data class CircleTrajectory2D( - public val circle: Circle2D, + public val circle: Circle2D, public val arcStart: Angle, public val arcAngle: Angle, ) : Trajectory2D { @@ -86,7 +91,7 @@ public data class CircleTrajectory2D( override val endPose: Pose2D get() = circle.tangent(arcEnd, direction) override val length: Double by lazy { - circle.radius * kotlin.math.abs(arcAngle.radians) + circle.radius * kotlin.math.abs(arcAngle.toRadians().value) } val center: Vector2D get() = circle.center @@ -98,11 +103,11 @@ public data class CircleTrajectory2D( } public fun CircleTrajectory2D( - center: DoubleVector2D, - start: DoubleVector2D, - end: DoubleVector2D, + center: Vector2D, + start: Vector2D, + end: Vector2D, direction: Trajectory2D.Direction, -): CircleTrajectory2D = with(Euclidean2DSpace) { +): CircleTrajectory2D = with(Float64Space2D) { val startVector = start - center val endVector = end - center val startRadius = norm(startVector) @@ -131,11 +136,11 @@ public fun CircleTrajectory2D( } public fun CircleTrajectory2D( - circle: Circle2D, - start: DoubleVector2D, - end: DoubleVector2D, + circle: Circle2D, + start: Vector2D, + end: Vector2D, direction: Trajectory2D.Direction, -): CircleTrajectory2D = with(Euclidean2DSpace) { +): CircleTrajectory2D = with(Float64Space2D) { val startVector = start - circle.center val endVector = end - circle.center val startBearing = startVector.bearing @@ -161,10 +166,10 @@ public fun CircleTrajectory2D( @Deprecated("Use angle notation instead") public fun CircleTrajectory2D( - circle: Circle2D, + circle: Circle2D, beginPose: Pose2D, endPose: Pose2D, -): CircleTrajectory2D = with(Euclidean2DSpace) { +): CircleTrajectory2D = with(Float64Space2D) { val vectorToBegin = beginPose - circle.center val vectorToEnd = endPose - circle.center //TODO check pose bearing @@ -185,7 +190,7 @@ public class CompositeTrajectory2D(public val segments: List) : Tr public fun CompositeTrajectory2D(vararg segments: Trajectory2D): CompositeTrajectory2D = 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 -> intersectsOrInside(a.circle, b.circle) is StraightTrajectory2D -> intersects(a.circle, b) diff --git a/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/circumvention.kt b/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/circumvention.kt index 968c595..6286dbb 100644 --- a/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/circumvention.kt +++ b/trajectory-kt/src/commonMain/kotlin/space/kscience/trajectory/circumvention.kt @@ -1,6 +1,10 @@ package space.kscience.trajectory +import space.kscience.containsPoint 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 kotlin.math.* @@ -10,9 +14,9 @@ import kotlin.math.* * This method returns a map of segments using [DubinsPath] connection type notation. */ internal fun tangentsBetweenCircles( - first: Circle2D, - second: Circle2D, -): Map = with(Euclidean2DSpace) { + first: Circle2D, + second: Circle2D, +): Map = with(Float64Space2D) { // Distance between centers val distanceBetweenCenters: Double = first.center.distanceTo(second.center) diff --git a/trajectory-kt/src/commonTest/kotlin/space/kscience/kmath/geometry/ArcTests.kt b/trajectory-kt/src/commonTest/kotlin/space/kscience/kmath/geometry/ArcTests.kt index 793c367..8517dfc 100644 --- a/trajectory-kt/src/commonTest/kotlin/space/kscience/kmath/geometry/ArcTests.kt +++ b/trajectory-kt/src/commonTest/kotlin/space/kscience/kmath/geometry/ArcTests.kt @@ -5,6 +5,11 @@ 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.Trajectory2D import kotlin.math.PI @@ -16,7 +21,7 @@ import kotlin.test.assertTrue class ArcTests { @Test - fun arc() = with(Euclidean2DSpace) { + fun arc() = with(Float64Space2D) { val circle = Circle2D(vector(0.0, 0.0), 2.0) val arc = CircleTrajectory2D( circle.center, @@ -25,12 +30,12 @@ class ArcTests { Trajectory2D.R ) assertEquals(circle.circumference / 4, arc.length, 1.0) - assertEquals(0.0, arc.beginPose.bearing.degrees) - assertEquals(90.0, arc.endPose.bearing.degrees) + assertEquals(0.0, arc.beginPose.bearing.toDegrees().value) + assertEquals(90.0, arc.endPose.bearing.toDegrees().value) } @Test - fun quarter() = with(Euclidean2DSpace) { + fun quarter() = with(Float64Space2D) { val circle = circle(1, 0, 1) val arc = CircleTrajectory2D( circle, @@ -38,11 +43,11 @@ class ArcTests { (PI/2).radians ) assertEquals(Trajectory2D.R, arc.direction) - assertEquals(PI, arc.arcEnd.radians, 1e-4) + assertEquals(PI, arc.arcEnd.toRadians().value, 1e-4) } @Test - fun arcContains() = with(Euclidean2DSpace) { + fun arcContains() = with(Float64Space2D) { val circle = circle(0, 0, 1.0) val arc1 = CircleTrajectory2D(circle, Angle.pi / 4, Angle.piDiv2) diff --git a/trajectory-kt/src/commonTest/kotlin/space/kscience/kmath/geometry/CircleTests.kt b/trajectory-kt/src/commonTest/kotlin/space/kscience/kmath/geometry/CircleTests.kt index 4c0bc15..5191824 100644 --- a/trajectory-kt/src/commonTest/kotlin/space/kscience/kmath/geometry/CircleTests.kt +++ b/trajectory-kt/src/commonTest/kotlin/space/kscience/kmath/geometry/CircleTests.kt @@ -5,6 +5,14 @@ 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.sqrt import kotlin.test.Test @@ -16,7 +24,7 @@ class CircleTests { @Test fun circle() { - val center = Euclidean2DSpace.vector(0.0, 0.0) + val center = Float64Space2D.vector(0.0, 0.0) val radius = 2.0 val expectedCircumference = 12.56637 val circle = Circle2D(center, radius) @@ -24,7 +32,7 @@ class CircleTests { } @Test - fun circleIntersection() = with(Euclidean2DSpace) { + fun circleIntersection() = with(Float64Space2D) { assertTrue { intersectsOrInside( circle(0.0, 0.0, 1.0), @@ -46,7 +54,7 @@ class CircleTests { } @Test - fun circleLineIntersection() = with(Euclidean2DSpace) { + fun circleLineIntersection() = with(Float64Space2D) { assertTrue { 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, segment: LineSegment2D): Boolean{ val begin = segment.begin val end = segment.end val lengthSquared = (begin.x - end.x).pow(2) + (begin.y - end.y).pow(2) @@ -111,7 +119,7 @@ class CircleTests { } @Test - fun oldCircleLineIntersection() = with(Euclidean2DSpace){ + fun oldCircleLineIntersection() = with(Float64Space2D){ assertTrue { oldIntersect(circle(0, 0, 1.1), segment(1, 1, -1, 1)) } diff --git a/trajectory-kt/src/commonTest/kotlin/space/kscience/kmath/geometry/LineTests.kt b/trajectory-kt/src/commonTest/kotlin/space/kscience/kmath/geometry/LineTests.kt index 480e598..eaea514 100644 --- a/trajectory-kt/src/commonTest/kotlin/space/kscience/kmath/geometry/LineTests.kt +++ b/trajectory-kt/src/commonTest/kotlin/space/kscience/kmath/geometry/LineTests.kt @@ -5,6 +5,7 @@ package space.kscience.kmath.geometry +import space.kscience.kmath.geometry.euclidean2d.Float64Space2D import space.kscience.trajectory.StraightTrajectory2D import kotlin.math.pow import kotlin.math.sqrt @@ -14,22 +15,22 @@ import kotlin.test.assertEquals class LineTests { @Test - fun lineTest() = with(Euclidean2DSpace){ + fun lineTest() = with(Float64Space2D){ 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(45.0, straight.bearing.degrees) + assertEquals(45.0, straight.bearing.toDegrees().value) } @Test - fun lineAngleTest() = with(Euclidean2DSpace){ + fun lineAngleTest() = with(Float64Space2D){ //val zero = Vector2D(0.0, 0.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)) - assertEquals(90.0, east.bearing.degrees) + assertEquals(90.0, east.bearing.toDegrees().value) 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)) - assertEquals(270.0, west.bearing.degrees) + assertEquals(270.0, west.bearing.toDegrees().value) } } diff --git a/trajectory-kt/src/commonTest/kotlin/space/kscience/trajectory/DubinsTests.kt b/trajectory-kt/src/commonTest/kotlin/space/kscience/trajectory/DubinsTests.kt index 77617d4..82b507a 100644 --- a/trajectory-kt/src/commonTest/kotlin/space/kscience/trajectory/DubinsTests.kt +++ b/trajectory-kt/src/commonTest/kotlin/space/kscience/trajectory/DubinsTests.kt @@ -5,7 +5,7 @@ package space.kscience.trajectory -import space.kscience.kmath.geometry.Euclidean2DSpace +import space.kscience.kmath.geometry.euclidean2d.Float64Space2D import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -14,7 +14,7 @@ import kotlin.test.assertNotNull class DubinsTests { @Test - fun dubinsTest() = with(Euclidean2DSpace) { + fun dubinsTest() = with(Float64Space2D) { val straight = StraightTrajectory2D(vector(0.0, 0.0), vector(100.0, 100.0)) val lineP1 = straight.shift(1, 10.0).inverse() @@ -53,7 +53,7 @@ class DubinsTests { assertEquals(c.beginPose, b.endPose, 1e-4) } else if (b is StraightTrajectory2D) { 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) } } } diff --git a/trajectory-kt/src/commonTest/kotlin/space/kscience/trajectory/ObstacleTest.kt b/trajectory-kt/src/commonTest/kotlin/space/kscience/trajectory/ObstacleTest.kt index dd67436..ce08217 100644 --- a/trajectory-kt/src/commonTest/kotlin/space/kscience/trajectory/ObstacleTest.kt +++ b/trajectory-kt/src/commonTest/kotlin/space/kscience/trajectory/ObstacleTest.kt @@ -6,10 +6,10 @@ package space.kscience.trajectory import space.kscience.kmath.geometry.Angle -import space.kscience.kmath.geometry.Circle2D import space.kscience.kmath.geometry.Degrees -import space.kscience.kmath.geometry.Euclidean2DSpace.vector 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.test.Test import kotlin.test.assertEquals @@ -18,7 +18,7 @@ import kotlin.test.assertTrue class ObstacleTest { @Test - fun equalObstacles() { + fun equalObstacles() = with(Float64Space2D) { val circle1 = Circle2D(vector(1.0, 6.5), 0.5) val circle2 = Circle2D(vector(1.0, 6.5), 0.5) assertEquals(circle1, circle2) @@ -28,7 +28,7 @@ class ObstacleTest { } @Test - fun singePoint() { + fun singePoint() = with(Float64Space2D) { val outputTangents: List = Obstacles.avoidObstacles( Pose2D(-5, -1, Angle.pi / 4), Pose2D(20, 4, Angle.pi * 3 / 4), @@ -41,7 +41,7 @@ class ObstacleTest { } @Test - fun twoObstacles() { + fun twoObstacles() = with(Float64Space2D) { val paths = Obstacles.avoidObstacles( Pose2D(-5, -1, Angle.pi / 4), Pose2D(20, 4, Angle.pi * 3 / 4), @@ -64,7 +64,7 @@ class ObstacleTest { } @Test - fun circumvention() { + fun circumvention() = with(Float64Space2D) { val obstacle = Obstacle( Circle2D(vector(0.0, 0.0), 1.0), Circle2D(vector(0.0, 1.0), 1.0), @@ -80,7 +80,7 @@ class ObstacleTest { } @Test - fun closePoints() { + fun closePoints() = with(Float64Space2D) { val obstacle = Obstacle( Circle2D(vector(0.0, 0.0), 1.0), Circle2D(vector(0.0, 1.0), 1.0), @@ -100,7 +100,7 @@ class ObstacleTest { } @Test - fun largeCoordinates() { + fun largeCoordinates() = with(Float64Space2D) { val startPoints = listOf( Pose2D(x = 484149.535516561, y = 2995086.2534208703, bearing = Degrees(3.401475378237137)) ) diff --git a/trajectory-kt/src/commonTest/kotlin/space/kscience/trajectory/TangentTest.kt b/trajectory-kt/src/commonTest/kotlin/space/kscience/trajectory/TangentTest.kt index e5831b0..56c9b4b 100644 --- a/trajectory-kt/src/commonTest/kotlin/space/kscience/trajectory/TangentTest.kt +++ b/trajectory-kt/src/commonTest/kotlin/space/kscience/trajectory/TangentTest.kt @@ -5,18 +5,17 @@ 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.equalsLine +import space.kscience.kmath.geometry.euclidean2d.Circle2D +import space.kscience.kmath.geometry.euclidean2d.Float64Space2D import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue class TangentTest { @Test - fun tangents() { + fun tangents() = with(Float64Space2D) { val c1 = Circle2D(vector(0.0, 0.0), 1.0) val c2 = Circle2D(vector(4.0, 0.0), 1.0) val routes = listOf( @@ -50,12 +49,12 @@ class TangentTest { assertEquals(routes, tangentMapKeys) for (i in segments.indices) { - assertTrue(segments[i].equalsLine(Euclidean2DSpace, tangentMapValues[i])) + assertTrue(segments[i].equalsLine(Float64Space2D, tangentMapValues[i])) } } @Test - fun concentric(){ + fun concentric() = with(Float64Space2D) { val c1 = Circle2D(vector(0.0, 0.0), 10.0) val c2 = Circle2D(vector(0.0, 0.0), 1.0) assertEquals(emptyMap(), tangentsBetweenCircles(c1, c2)) diff --git a/trajectory-kt/src/commonTest/kotlin/space/kscience/trajectory/testutils.kt b/trajectory-kt/src/commonTest/kotlin/space/kscience/trajectory/testutils.kt index 86f8054..5014411 100644 --- a/trajectory-kt/src/commonTest/kotlin/space/kscience/trajectory/testutils.kt +++ b/trajectory-kt/src/commonTest/kotlin/space/kscience/trajectory/testutils.kt @@ -5,20 +5,19 @@ package space.kscience.trajectory -import space.kscience.kmath.geometry.Euclidean2DSpace -import space.kscience.kmath.geometry.radians +import space.kscience.kmath.geometry.euclidean2d.Float64Space2D 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.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.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 dY = width * sin(bearing)