diff --git a/build.gradle.kts b/build.gradle.kts index 260de07..58e19c0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,12 @@ plugins { allprojects { group = "center.sciprog" - version = "0.2.1-dev-4" + version = "0.2.2-dev-1" + + repositories { + mavenLocal() + maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev") + } } ksciencePublish{ diff --git a/demo/maps/src/jvmMain/kotlin/Main.kt b/demo/maps/src/jvmMain/kotlin/Main.kt index c012842..89be627 100644 --- a/demo/maps/src/jvmMain/kotlin/Main.kt +++ b/demo/maps/src/jvmMain/kotlin/Main.kt @@ -28,12 +28,15 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import space.kscience.kmath.geometry.Angle +import space.kscience.kmath.geometry.degrees +import space.kscience.kmath.geometry.radians import java.nio.file.Path import kotlin.math.PI import kotlin.random.Random -private fun GeodeticMapCoordinates.toShortString(): String = - "${(latitude.degrees.value).toString().take(6)}:${(longitude.degrees.value).toString().take(6)}" +public fun GeodeticMapCoordinates.toShortString(): String = + "${(latitude.degrees).toString().take(6)}:${(longitude.degrees).toString().take(6)}" @OptIn(ExperimentalFoundationApi::class) @@ -124,13 +127,13 @@ fun App() { }.launchIn(scope) //Add click listeners for all polygons - forEachWithType> { id, feature -> - id.onClick(PointerMatcher.Primary) { - println("Click on $id") + forEachWithType> { ref -> + ref.onClick(PointerMatcher.Primary) { + println("Click on ${ref.id}") //draw in top-level scope with(this@MapView) { points( - feature.points, + ref.resolve().points, stroke = 4f, pointMode = PointMode.Polygon, attributes = Attributes(ZAttribute, 10f), diff --git a/demo/scheme/src/jvmMain/kotlin/Main.kt b/demo/scheme/src/jvmMain/kotlin/Main.kt index 9e1e847..ec4acde 100644 --- a/demo/scheme/src/jvmMain/kotlin/Main.kt +++ b/demo/scheme/src/jvmMain/kotlin/Main.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.window.application 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 center.sciprog.maps.svg.FeatureStateSnapshot import center.sciprog.maps.svg.exportToSvg diff --git a/gradle.properties b/gradle.properties index 2a403eb..794b690 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,10 +1,10 @@ kotlin.code.style=official -compose.version=1.2.2 +compose.version=1.3.0 agp.version=7.3.1 android.useAndroidX=true org.jetbrains.compose.experimental.jscanvas.enabled=true org.gradle.jvmargs=-Xmx4096m -toolsVersion=0.13.3-kotlin-1.7.20 \ No newline at end of file +toolsVersion=0.13.4-kotlin-1.8.0 \ No newline at end of file diff --git a/maps-kt-compose/build.gradle.kts b/maps-kt-compose/build.gradle.kts index 8b50a14..9745d09 100644 --- a/maps-kt-compose/build.gradle.kts +++ b/maps-kt-compose/build.gradle.kts @@ -40,10 +40,6 @@ kotlin { } } -java { - targetCompatibility = space.kscience.gradle.KScienceVersions.JVM_TARGET -} - tasks.withType { useJUnitPlatform() } diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/GmcRectangle.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/GmcRectangle.kt index 8bcd095..e276a66 100644 --- a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/GmcRectangle.kt +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/GmcRectangle.kt @@ -1,10 +1,10 @@ package center.sciprog.maps.compose -import center.sciprog.maps.coordinates.Angle import center.sciprog.maps.coordinates.GeodeticMapCoordinates import center.sciprog.maps.coordinates.Gmc -import center.sciprog.maps.coordinates.abs import center.sciprog.maps.features.Rectangle +import space.kscience.kmath.geometry.Angle +import space.kscience.kmath.geometry.abs /** * A section of the map between two parallels and two meridians. The figure represents a square in a Mercator projection. diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapViewScope.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapViewScope.kt index b8ecaf1..5db4e7e 100644 --- a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapViewScope.kt +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapViewScope.kt @@ -6,8 +6,12 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpRect import androidx.compose.ui.unit.dp -import center.sciprog.maps.coordinates.* +import center.sciprog.maps.coordinates.Gmc +import center.sciprog.maps.coordinates.MercatorProjection +import center.sciprog.maps.coordinates.WebMercatorCoordinates +import center.sciprog.maps.coordinates.WebMercatorProjection import center.sciprog.maps.features.* +import space.kscience.kmath.geometry.radians import kotlin.math.* public class MapViewScope internal constructor( @@ -56,8 +60,8 @@ public class MapViewScope internal constructor( override fun computeViewPoint(rectangle: Rectangle): ViewPoint { val zoom = log2( min( - canvasSize.width.value / rectangle.longitudeDelta.radians.value, - canvasSize.height.value / rectangle.latitudeDelta.radians.value + canvasSize.width.value / rectangle.longitudeDelta.radians, + canvasSize.height.value / rectangle.latitudeDelta.radians ) * PI / mapTileProvider.tileSize ) return space.ViewPoint(rectangle.center, zoom.toFloat()) diff --git a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/WebMercatorSpace.kt b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/WebMercatorSpace.kt index 09a8897..8eb9016 100644 --- a/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/WebMercatorSpace.kt +++ b/maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/WebMercatorSpace.kt @@ -7,6 +7,8 @@ import center.sciprog.maps.coordinates.* import center.sciprog.maps.features.CoordinateSpace import center.sciprog.maps.features.Rectangle import center.sciprog.maps.features.ViewPoint +import space.kscience.kmath.geometry.Angle +import space.kscience.kmath.geometry.radians import kotlin.math.abs import kotlin.math.floor import kotlin.math.pow 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 36e0986..c28bda7 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 @@ -6,8 +6,13 @@ 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.maps.coordinates.* +import center.sciprog.maps.coordinates.Distance +import center.sciprog.maps.coordinates.GeodeticMapCoordinates +import center.sciprog.maps.coordinates.Gmc +import center.sciprog.maps.coordinates.GmcCurve import center.sciprog.maps.features.* +import space.kscience.kmath.geometry.Angle +import space.kscience.kmath.geometry.radians internal fun FeatureGroup.coordinatesOf(pair: Pair) = @@ -19,7 +24,7 @@ public fun FeatureGroup.circle( centerCoordinates: Pair, size: Dp = 5.dp, id: String? = null, -): FeatureId> = feature( +): FeatureRef> = feature( id, CircleFeature(space, coordinatesOf(centerCoordinates), size) ) @@ -27,7 +32,7 @@ public fun FeatureGroup.rectangle( centerCoordinates: Pair, size: DpSize = DpSize(5.dp, 5.dp), id: String? = null, -): FeatureId> = feature( +): FeatureRef> = feature( id, RectangleFeature(space, coordinatesOf(centerCoordinates), size) ) @@ -36,7 +41,7 @@ public fun FeatureGroup.draw( position: Pair, id: String? = null, draw: DrawScope.() -> Unit, -): FeatureId> = feature( +): FeatureRef> = feature( id, DrawFeature(space, coordinatesOf(position), drawFeature = draw) ) @@ -45,7 +50,7 @@ public fun FeatureGroup.draw( public fun FeatureGroup.line( curve: GmcCurve, id: String? = null, -): FeatureId> = feature( +): FeatureRef> = feature( id, LineFeature(space, curve.forward.coordinates, curve.backward.coordinates) ) @@ -55,7 +60,7 @@ public fun FeatureGroup.line( aCoordinates: Pair, bCoordinates: Pair, id: String? = null, -): FeatureId> = feature( +): FeatureRef> = feature( id, LineFeature(space, coordinatesOf(aCoordinates), coordinatesOf(bCoordinates)) ) @@ -67,7 +72,7 @@ public fun FeatureGroup.arc( startAngle: Angle, arcLength: Angle, id: String? = null, -): FeatureId> = feature( +): FeatureRef> = feature( id, ArcFeature( space, @@ -82,7 +87,7 @@ public fun FeatureGroup.points( stroke: Float = 2f, pointMode: PointMode = PointMode.Points, id: String? = null, -): FeatureId> = +): FeatureRef> = feature(id, PointsFeature(space, points.map(::coordinatesOf), stroke, pointMode)) public fun FeatureGroup.image( @@ -90,7 +95,7 @@ public fun FeatureGroup.image( image: ImageVector, size: DpSize = DpSize(20.dp, 20.dp), id: String? = null, -): FeatureId> = feature( +): FeatureRef> = feature( id, VectorImageFeature( space, @@ -105,7 +110,7 @@ public fun FeatureGroup.text( text: String, font: FeatureFont.() -> Unit = { size = 16f }, id: String? = null, -): FeatureId> = feature( +): FeatureRef> = feature( id, TextFeature(space, coordinatesOf(position), text, fontConfig = font) ) diff --git a/maps-kt-core/build.gradle.kts b/maps-kt-core/build.gradle.kts index 408fd0e..888c522 100644 --- a/maps-kt-core/build.gradle.kts +++ b/maps-kt-core/build.gradle.kts @@ -3,6 +3,16 @@ plugins { `maven-publish` } +val kmathVersion: String by rootProject.extra("0.3.1-dev-10") + +kscience{ + useSerialization() + + dependencies{ + api("space.kscience:kmath-trajectory:$kmathVersion") + } +} + readme { description = "Core cartography, UI-agnostic" maturity = space.kscience.gradle.Maturity.DEVELOPMENT diff --git a/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/Angle.kt b/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/Angle.kt deleted file mode 100644 index f78c3e8..0000000 --- a/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/Angle.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2018-2021 KMath contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package center.sciprog.maps.coordinates - -import kotlin.jvm.JvmInline -import kotlin.math.PI -import kotlin.math.floor - -// Taken from KMath dev version, to be used directly in the future - - -public sealed interface Angle : Comparable { - public val radians: Radians - public val degrees: Degrees - - public operator fun plus(other: Angle): Angle - public operator fun minus(other: Angle): Angle - - public operator fun times(other: Number): Angle - public operator fun div(other: Number): Angle - public operator fun div(other: Angle): Double - public operator fun unaryMinus(): Angle - - public companion object { - public val zero: Angle = 0.radians - public val pi: Angle = PI.radians - public val piTimes2: Angle = (2 * PI).radians - public val piDiv2: Angle = (PI / 2).radians - } -} - -/** - * Type safe radians - */ -@JvmInline -public value class Radians(public val value: Double) : Angle { - override val radians: Radians - get() = this - override val degrees: Degrees - get() = Degrees(value * 180 / PI) - - public override fun plus(other: Angle): Radians = Radians(value + other.radians.value) - public override fun minus(other: Angle): Radians = Radians(value - other.radians.value) - - public override fun times(other: Number): Radians = Radians(value * other.toDouble()) - public override fun div(other: Number): Radians = Radians(value / other.toDouble()) - override fun div(other: Angle): Double = value / other.radians.value - public override fun unaryMinus(): Radians = Radians(-value) - - override fun compareTo(other: Angle): Int = value.compareTo(other.radians.value) -} - -public fun sin(angle: Angle): Double = kotlin.math.sin(angle.radians.value) -public fun cos(angle: Angle): Double = kotlin.math.cos(angle.radians.value) -public fun tan(angle: Angle): Double = kotlin.math.tan(angle.radians.value) - -public val Number.radians: Radians get() = Radians(toDouble()) - -/** - * Type safe degrees - */ -@JvmInline -public value class Degrees(public val value: Double) : Angle { - override val radians: Radians - get() = Radians(value * PI / 180) - override val degrees: Degrees - get() = this - - public override fun plus(other: Angle): Degrees = Degrees(value + other.degrees.value) - public override fun minus(other: Angle): Degrees = Degrees(value - other.degrees.value) - - public override fun times(other: Number): Degrees = Degrees(value * other.toDouble()) - public override fun div(other: Number): Degrees = Degrees(value / other.toDouble()) - override fun div(other: Angle): Double = value / other.degrees.value - public override fun unaryMinus(): Degrees = Degrees(-value) - - override fun compareTo(other: Angle): Int = value.compareTo(other.degrees.value) -} - -public val Number.degrees: Degrees get() = Degrees(toDouble()) - -/** - * Normalized angle 2 PI range symmetric around [center]. By default, uses (0, 2PI) range. - */ -public fun Angle.normalized(center: Angle = Angle.pi): Angle = - this - Angle.piTimes2 * floor((radians.value + PI - center.radians.value) / PI/2) - -public fun abs(angle: Angle): Angle = if (angle < Angle.zero) -angle else angle - -public fun Radians.toFloat(): Float = value.toFloat() - -public fun Degrees.toFloat(): Float = value.toFloat() diff --git a/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/GeoEllipsoid.kt b/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/GeoEllipsoid.kt index 35d69b6..95d8538 100644 --- a/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/GeoEllipsoid.kt +++ b/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/GeoEllipsoid.kt @@ -1,5 +1,7 @@ package center.sciprog.maps.coordinates +import space.kscience.kmath.geometry.Angle +import space.kscience.kmath.geometry.tan import kotlin.math.pow import kotlin.math.sqrt 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 5065188..687e7bf 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 @@ -1,5 +1,10 @@ package center.sciprog.maps.coordinates +import space.kscience.kmath.geometry.Angle +import space.kscience.kmath.geometry.degrees +import space.kscience.kmath.geometry.normalized +import space.kscience.kmath.geometry.radians + /** * Geodetic coordinated * @@ -11,8 +16,12 @@ public class GeodeticMapCoordinates( public val elevation: Distance? = null, ) { init { - require(latitude in (-Angle.piDiv2)..(Angle.piDiv2)) { "Latitude $latitude is not in (-PI/2)..(PI/2)" } - require(longitude in (-Angle.pi..Angle.pi)) { "Longitude $longitude is not in (-PI..PI) range" } + require(latitude in (-Angle.piDiv2)..(Angle.piDiv2)) { + "Latitude $latitude is not in (-PI/2)..(PI/2)" + } + require(longitude in (-Angle.pi..Angle.pi)) { + "Longitude $longitude is not in (-PI..PI) range" + } } override fun equals(other: Any?): Boolean { @@ -34,7 +43,7 @@ public class GeodeticMapCoordinates( } override fun toString(): String { - return "GMC(latitude=${latitude.degrees.value} deg, longitude=${longitude.degrees.value} deg)" + return "GMC(latitude=${latitude.degrees} deg, longitude=${longitude.degrees} deg)" } @@ -42,7 +51,7 @@ public class GeodeticMapCoordinates( public fun normalized( latitude: Angle, longitude: Angle, - elevation: Distance = 0.kilometers, + elevation: Distance? = null, ): GeodeticMapCoordinates = GeodeticMapCoordinates( latitude, longitude.normalized(Angle.zero), elevation ) @@ -50,14 +59,14 @@ public class GeodeticMapCoordinates( public fun ofRadians( latitude: Double, longitude: Double, - elevation: Distance = 0.kilometers, + elevation: Distance? = null, ): GeodeticMapCoordinates = normalized(latitude.radians, longitude.radians, elevation) public fun ofDegrees( latitude: Double, longitude: Double, - elevation: Distance = 0.kilometers, - ): GeodeticMapCoordinates = normalized(latitude.degrees.radians, longitude.degrees.radians, elevation) + elevation: Distance? = null, + ): GeodeticMapCoordinates = normalized(latitude.degrees, longitude.degrees, elevation) } } 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 7775c1b..a99c159 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 @@ -1,8 +1,6 @@ package center.sciprog.maps.coordinates -import center.sciprog.maps.coordinates.Angle.Companion.pi -import center.sciprog.maps.coordinates.Angle.Companion.piDiv2 -import center.sciprog.maps.coordinates.Angle.Companion.zero +import space.kscience.kmath.geometry.* import kotlin.math.* /** @@ -20,6 +18,8 @@ public class GmcCurve( } } +public operator fun ClosedRange.contains(angle: Angle): Boolean = contains(angle.toRadians()) + /** * Reverse direction and order of ends */ @@ -34,8 +34,8 @@ public fun GeoEllipsoid.meridianCurve( toLatitude: Angle, step: Radians = 0.015.radians, ): GmcCurve { - require(fromLatitude in (-piDiv2)..(piDiv2)) { "Latitude must be in (-90, 90) degrees range" } - require(toLatitude in (-piDiv2)..(piDiv2)) { "Latitude must be in (-90, 90) degrees range" } + require(fromLatitude in (-Angle.piDiv2)..(Angle.piDiv2)) { "Latitude must be in (-90, 90) degrees range" } + require(toLatitude in (-Angle.piDiv2)..(Angle.piDiv2)) { "Latitude must be in (-90, 90) degrees range" } fun smallDistance(from: Radians, to: Radians): Distance = equatorRadius * (1 - eSquared) * @@ -48,14 +48,14 @@ public fun GeoEllipsoid.meridianCurve( val integrateTo: Radians if (up) { - integrateFrom = fromLatitude.radians - integrateTo = toLatitude.radians + integrateFrom = fromLatitude.toRadians() + integrateTo = toLatitude.toRadians() } else { - integrateTo = fromLatitude.radians - integrateFrom = toLatitude.radians + integrateTo = fromLatitude.toRadians() + integrateFrom = toLatitude.toRadians() } - var current = integrateFrom + var current: Radians = integrateFrom var s = Distance(0.0) while (current < integrateTo) { val next = minOf(current + step, integrateTo) @@ -64,8 +64,8 @@ public fun GeoEllipsoid.meridianCurve( } return GmcCurve( - forward = GmcPose(Gmc.normalized(fromLatitude, longitude), if (up) zero else pi), - backward = GmcPose(Gmc.normalized(toLatitude, longitude), if (up) pi else zero), + forward = GmcPose(Gmc.normalized(fromLatitude, longitude), if (up) Angle.zero else Angle.pi), + backward = GmcPose(Gmc.normalized(toLatitude, longitude), if (up) Angle.pi else Angle.zero), distance = s ) } @@ -74,12 +74,12 @@ public fun GeoEllipsoid.meridianCurve( * Compute a curve alongside a parallel */ public fun GeoEllipsoid.parallelCurve(latitude: Angle, fromLongitude: Angle, toLongitude: Angle): GmcCurve { - require(latitude in (-piDiv2)..(piDiv2)) { "Latitude must be in (-90, 90) degrees range" } + require(latitude in (-Angle.piDiv2)..(Angle.piDiv2)) { "Latitude must be in (-90, 90) degrees range" } val right = toLongitude > fromLongitude return GmcCurve( - forward = GmcPose(Gmc.normalized(latitude, fromLongitude), if (right) piDiv2.radians else -piDiv2.radians), - backward = GmcPose(Gmc.normalized(latitude, toLongitude), if (right) -piDiv2.radians else piDiv2.radians), - distance = reducedRadius(latitude) * abs((fromLongitude - toLongitude).radians.value) + 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) ) } @@ -258,7 +258,7 @@ public fun GeoEllipsoid.curveBetween(start: Gmc, end: Gmc, precision: Double = 1 val cosU1cosU2 = cosU1 * cosU2 // eq. 13 - var lambda = omega + var lambda: Angle = omega // intermediates we'll need to compute 's' var A = 0.0 @@ -329,11 +329,11 @@ public fun GeoEllipsoid.curveBetween(start: Gmc, end: Gmc, precision: Double = 1 // didn't converge? must be N/S if (!converged) { if (phi1 > phi2) { - alpha1 = pi.radians + alpha1 = Angle.pi.toRadians() alpha2 = 0.0.radians } else if (phi1 < phi2) { alpha1 = 0.0.radians - alpha2 = pi.radians + alpha2 = Angle.pi.toRadians() } else { error("Start and end point coinside.") } @@ -348,7 +348,7 @@ public fun GeoEllipsoid.curveBetween(start: Gmc, end: Gmc, precision: Double = 1 alpha2 = atan2( cosU1 * sin(lambda), -sinU1cosU2 + cosU1sinU2 * cos(lambda) - ).radians + pi + ).radians + Angle.pi } return GmcCurve( GmcPose(start, alpha1), diff --git a/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/GmcPose.kt b/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/GmcPose.kt index e573dc5..8c5e591 100644 --- a/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/GmcPose.kt +++ b/maps-kt-core/src/commonMain/kotlin/center/sciprog/maps/coordinates/GmcPose.kt @@ -1,5 +1,8 @@ package center.sciprog.maps.coordinates +import space.kscience.kmath.geometry.Angle +import space.kscience.kmath.geometry.normalized + /** * A coordinate-bearing pair */ 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 b411e84..d09d06d 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 @@ -5,7 +5,7 @@ package center.sciprog.maps.coordinates -import center.sciprog.maps.coordinates.Angle.Companion.pi +import space.kscience.kmath.geometry.* import kotlin.math.* public data class ProjectionCoordinates(val x: Distance, val y: Distance) @@ -17,7 +17,7 @@ public interface MapProjection { public fun toGeodetic(pc: T): GeodeticMapCoordinates public fun toProjection(gmc: GeodeticMapCoordinates): T - public companion object{ + public companion object { public val epsg3857: MercatorProjection = MercatorProjection() } } @@ -35,7 +35,7 @@ public open class MercatorProjection( override fun toGeodetic(pc: ProjectionCoordinates): GeodeticMapCoordinates { val res = GeodeticMapCoordinates.ofRadians( atan(sinh(pc.y / ellipsoid.equatorRadius)), - baseLongitude.radians.value + (pc.x / ellipsoid.equatorRadius), + baseLongitude.radians + (pc.x / ellipsoid.equatorRadius), ) return if (ellipsoid === GeoEllipsoid.sphere) { @@ -58,15 +58,15 @@ public open class MercatorProjection( return if (ellipsoid === GeoEllipsoid.sphere) { ProjectionCoordinates( - x = ellipsoid.equatorRadius * (gmc.longitude - baseLongitude).radians.value, - y = ellipsoid.equatorRadius * ln(tan(pi / 4 + gmc.latitude / 2)) + x = ellipsoid.equatorRadius * (gmc.longitude - baseLongitude).radians, + 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.value, + x = ellipsoid.equatorRadius * (gmc.longitude - baseLongitude).radians, y = ellipsoid.equatorRadius * ln( - tan(pi / 4 + gmc.latitude / 2) * ((1 - e * sinPhi) / (1 + e * sinPhi)).pow(e / 2) + tan(Angle.pi / 4 + gmc.latitude / 2) * ((1 - e * sinPhi) / (1 + e * sinPhi)).pow(e / 2) ) ) } 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 c7c6c1b..5135e6e 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 @@ -5,6 +5,8 @@ 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) @@ -32,8 +34,8 @@ public object WebMercatorProjection { val scaleFactor = scaleFactor(zoom.toFloat()) return WebMercatorCoordinates( zoom = zoom, - x = scaleFactor * (gmc.longitude.radians.value + PI).toFloat(), - y = scaleFactor * (PI - ln(tan(PI / 4 + gmc.latitude.radians.value / 2))).toFloat() + x = scaleFactor * (gmc.longitude.radians + PI).toFloat(), + y = scaleFactor * (PI - ln(tan(PI / 4 + gmc.latitude.radians / 2))).toFloat() ) } diff --git a/maps-kt-core/src/commonTest/kotlin/center/sciprog/maps/coordinates/AngleTest.kt b/maps-kt-core/src/commonTest/kotlin/center/sciprog/maps/coordinates/AngleTest.kt deleted file mode 100644 index 5a98f41..0000000 --- a/maps-kt-core/src/commonTest/kotlin/center/sciprog/maps/coordinates/AngleTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package center.sciprog.maps.coordinates - -import kotlin.test.Test -import kotlin.test.assertEquals - -class AngleTest { - @Test - fun normalization(){ - assertEquals(30.degrees, 390.degrees.normalized()) - assertEquals(30.degrees, (-330).degrees.normalized()) - assertEquals(200.degrees, 200.degrees.normalized()) - assertEquals(30.degrees, 390.degrees.normalized(Angle.zero)) - assertEquals(30.degrees, (-330).degrees.normalized(Angle.zero)) - assertEquals((-160).degrees, 200.degrees.normalized(Angle.zero)) - } -} \ No newline at end of file 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 9e6442a..a0ff074 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 @@ -1,5 +1,6 @@ package center.sciprog.maps.coordinates +import space.kscience.kmath.geometry.radians import kotlin.test.Test import kotlin.test.assertEquals @@ -20,7 +21,7 @@ internal class DistanceTest { val distance = curve.distance assertEquals(632.035426877, distance.kilometers, 0.0001) - assertEquals(-0.6947937116552751, curve.forward.bearing.radians.value, 0.0001) + assertEquals(-0.6947937116552751, curve.forward.bearing.radians, 0.0001) } @Test @@ -29,7 +30,7 @@ internal class DistanceTest { GmcPose(moscow, (-0.6947937116552751).radians), Distance(632.035426877) ) - assertEquals(spb.latitude.radians.value,curve.backward.latitude.radians.value, 0.0001) - assertEquals(spb.longitude.radians.value,curve.backward.longitude.radians.value, 0.0001) + assertEquals(spb.latitude.radians, curve.backward.latitude.radians, 0.0001) + assertEquals(spb.longitude.radians, curve.backward.longitude.radians, 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 a4018f6..2273d23 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,5 +1,6 @@ package center.sciprog.maps.coordinates +import space.kscience.kmath.geometry.degrees import kotlin.test.Test import kotlin.test.assertEquals @@ -12,7 +13,7 @@ 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.value, backwards.latitude.degrees.value, 0.001) - assertEquals(moscow.longitude.degrees.value, backwards.longitude.degrees.value, 0.001) + assertEquals(moscow.latitude.degrees, backwards.latitude.degrees, 0.001) + assertEquals(moscow.longitude.degrees, backwards.longitude.degrees, 0.001) } } \ 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 e826d6a..673d20d 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 @@ -185,12 +185,14 @@ public data class LineFeature( override fun getBoundingBox(zoom: Float): Rectangle = space.Rectangle(a, b) + private val clickRadius get() = attributes[ClickRadius] ?: 20f + override fun contains(viewPoint: ViewPoint): Boolean = with(space) { viewPoint.focus in getBoundingBox(viewPoint.zoom) && viewPoint.focus.distanceToLine( a, b, viewPoint.zoom - ).value < 5f + ).value < clickRadius } override fun withAttributes(modify: (Attributes) -> Attributes): Feature = copy(attributes = modify(attributes)) 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 ba42c77..e09f1f3 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 @@ -1,26 +1,28 @@ package center.sciprog.maps.features -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.PointerMatcher import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.remember import androidx.compose.runtime.snapshots.SnapshotStateMap -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PointMode import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.input.pointer.PointerEvent -import androidx.compose.ui.input.pointer.PointerKeyboardModifiers import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import center.sciprog.attributes.* -import kotlin.jvm.JvmInline -@JvmInline -public value class FeatureId>(public val id: String) +//@JvmInline +//public value class FeatureId>(public val id: String) + +public class FeatureRef>(public val id: String, public val parent: FeatureGroup) + +@Suppress("UNCHECKED_CAST") +public fun > FeatureRef.resolve(): F = + parent.featureMap[id]?.let { it as F } ?: error("Feature with id=$id not found") + +public val > FeatureRef.attributes: Attributes get() = resolve().attributes /** * A group of other features @@ -30,10 +32,10 @@ public data class FeatureGroup( public val featureMap: SnapshotStateMap> = mutableStateMapOf(), override val attributes: Attributes = Attributes.EMPTY, ) : CoordinateSpace by space, Feature { - - @Suppress("UNCHECKED_CAST") - public operator fun > get(id: FeatureId): F = - featureMap[id.id]?.let { it as F } ?: error("Feature with id=$id not found") +// +// @Suppress("UNCHECKED_CAST") +// public operator fun > get(id: FeatureId): F = +// featureMap[id.id]?.let { it as F } ?: error("Feature with id=$id not found") private var uidCounter = 0 @@ -43,13 +45,13 @@ public data class FeatureGroup( "@${feature::class.simpleName}[${uidCounter++}]" } - public fun > feature(id: String?, feature: F): FeatureId { + public fun > feature(id: String?, feature: F): FeatureRef { val safeId = id ?: generateUID(feature) featureMap[safeId] = feature - return FeatureId(safeId) + return FeatureRef(safeId, this) } - public fun > feature(id: FeatureId, feature: F): FeatureId = feature(id.id, feature) +// public fun > feature(id: FeatureId, feature: F): FeatureId = feature(id.id, feature) public val features: Collection> get() = featureMap.values.sortedByDescending { it.z } @@ -72,10 +74,10 @@ public data class FeatureGroup( } } } - - @Suppress("UNCHECKED_CAST") - public fun getAttribute(id: FeatureId>, key: Attribute): A? = - get(id).attributes[key] +// +// @Suppress("UNCHECKED_CAST") +// public fun getAttribute(id: FeatureId>, key: Attribute): A? = +// get(id).attributes[key] override fun getBoundingBox(zoom: Float): Rectangle? = with(space) { @@ -84,123 +86,6 @@ public data class FeatureGroup( override fun withAttributes(modify: Attributes.() -> Attributes): Feature = copy(attributes = modify(attributes)) - public fun > FeatureId.modifyAttributes(modify: AttributesBuilder.() -> Unit): FeatureId { - feature( - this, - get(this).withAttributes { - AttributesBuilder(this).apply(modify).build() - } - ) - return this - } - - public fun , V> FeatureId.modifyAttribute(key: Attribute, value: V?): FeatureId { - feature(this, get(this).withAttributes { withAttribute(key, value) }) - return this - } - - /** - * Add drag to this feature - * - * @param constraint optional drag constraint - * - * TODO use context receiver for that - */ - @Suppress("UNCHECKED_CAST") - public fun > FeatureId.draggable( - constraint: ((T) -> T)? = null, - listener: (PointerEvent.(from: ViewPoint, to: ViewPoint) -> Unit)? = null, - ): FeatureId { - if (getAttribute(this, DraggableAttribute) == null) { - val handle = DragHandle.withPrimaryButton { event, start, end -> - val feature = featureMap[id] as? DraggableFeature ?: return@withPrimaryButton DragResult(end) - start as ViewPoint - end as ViewPoint - if (start in feature) { - val finalPosition = constraint?.invoke(end.focus) ?: end.focus - feature(id, feature.withCoordinates(finalPosition)) - feature.attributes[DragListenerAttribute]?.forEach { - it.handle(event, start, ViewPoint(finalPosition, end.zoom)) - } - DragResult(ViewPoint(finalPosition, end.zoom), false) - } else { - DragResult(end, true) - } - } - modifyAttribute(DraggableAttribute, handle) - } - - //Apply callback - if (listener != null) { - onDrag(listener) - } - return this - } - - - @Suppress("UNCHECKED_CAST") - public fun > FeatureId.onDrag( - listener: PointerEvent.(from: ViewPoint, to: ViewPoint) -> Unit, - ): FeatureId = modifyAttributes { - DragListenerAttribute.add( - DragListener { event, from, to -> event.listener(from as ViewPoint, to as ViewPoint) } - ) - } - - @Suppress("UNCHECKED_CAST") - public fun > FeatureId.onClick( - onClick: PointerEvent.(click: ViewPoint) -> Unit, - ): FeatureId = modifyAttributes { - ClickListenerAttribute.add( - MouseListener { event, point -> - event.onClick(point as ViewPoint) - } - ) - } - - @OptIn(ExperimentalFoundationApi::class) - @Suppress("UNCHECKED_CAST") - public fun > FeatureId.onClick( - pointerMatcher: PointerMatcher, - keyboardModifiers: PointerKeyboardModifiers.() -> Boolean = {true}, - onClick: PointerEvent.(click: ViewPoint) -> Unit, - ): FeatureId = modifyAttributes { - ClickListenerAttribute.add( - MouseListener { event, point -> - if (pointerMatcher.matches(event) && keyboardModifiers(event.keyboardModifiers)) { - event.onClick(point as ViewPoint) - } - } - ) - } - - @Suppress("UNCHECKED_CAST") - public fun > FeatureId.onHover( - onClick: PointerEvent.(move: ViewPoint) -> Unit, - ): FeatureId = modifyAttributes { - HoverListenerAttribute.add( - MouseListener { event, point -> event.onClick(point as ViewPoint) } - ) - } - -// @Suppress("UNCHECKED_CAST") -// @OptIn(ExperimentalFoundationApi::class) -// public fun > FeatureId.onTap( -// pointerMatcher: PointerMatcher = PointerMatcher.Primary, -// keyboardFilter: PointerKeyboardModifiers.() -> Boolean = { true }, -// onTap: (point: ViewPoint) -> Unit, -// ): FeatureId = modifyAttributes { -// TapListenerAttribute.add( -// TapListener(pointerMatcher, keyboardFilter) { point -> onTap(point as ViewPoint) } -// ) -// } - - public fun > FeatureId.color(color: Color): FeatureId = - modifyAttribute(ColorAttribute, color) - - public fun > FeatureId.zoomRange(range: FloatRange): FeatureId = - modifyAttribute(ZoomRangeAttribute, range) - public companion object { @@ -252,18 +137,18 @@ public fun FeatureGroup.forEachWithAttributeUntil( } public inline fun > FeatureGroup.forEachWithType( - crossinline block: FeatureGroup.(FeatureId, feature: F) -> Unit, + crossinline block: (FeatureRef) -> Unit, ) { visit { id, feature -> - if (feature is F) block(FeatureId(id), feature) + if (feature is F) block(FeatureRef(id, this)) } } public inline fun > FeatureGroup.forEachWithTypeUntil( - crossinline block: FeatureGroup.(FeatureId, feature: F) -> Boolean, + crossinline block: (FeatureRef) -> Boolean, ) { visitUntil { id, feature -> - if (feature is F) block(FeatureId(id), feature) else true + if (feature is F) block(FeatureRef(id, this)) else true } } @@ -271,7 +156,7 @@ public fun FeatureGroup.circle( center: T, size: Dp = 5.dp, id: String? = null, -): FeatureId> = feature( +): FeatureRef> = feature( id, CircleFeature(space, center, size) ) @@ -279,7 +164,7 @@ public fun FeatureGroup.rectangle( centerCoordinates: T, size: DpSize = DpSize(5.dp, 5.dp), id: String? = null, -): FeatureId> = feature( +): FeatureRef> = feature( id, RectangleFeature(space, centerCoordinates, size) ) @@ -287,7 +172,7 @@ public fun FeatureGroup.draw( position: T, id: String? = null, draw: DrawScope.() -> Unit, -): FeatureId> = feature( +): FeatureRef> = feature( id, DrawFeature(space, position, drawFeature = draw) ) @@ -296,7 +181,7 @@ public fun FeatureGroup.line( aCoordinates: T, bCoordinates: T, id: String? = null, -): FeatureId> = feature( +): FeatureRef> = feature( id, LineFeature(space, aCoordinates, bCoordinates) ) @@ -306,7 +191,7 @@ public fun FeatureGroup.arc( startAngle: Float, arcLength: Float, id: String? = null, -): FeatureId> = feature( +): FeatureRef> = feature( id, ArcFeature(space, oval, startAngle, arcLength) ) @@ -317,7 +202,7 @@ public fun FeatureGroup.points( pointMode: PointMode = PointMode.Points, attributes: Attributes = Attributes.EMPTY, id: String? = null, -): FeatureId> = feature( +): FeatureRef> = feature( id, PointsFeature(space, points, stroke, pointMode, attributes) ) @@ -325,7 +210,7 @@ public fun FeatureGroup.points( public fun FeatureGroup.polygon( points: List, id: String? = null, -): FeatureId> = feature( +): FeatureRef> = feature( id, PolygonFeature(space, points) ) @@ -335,7 +220,7 @@ public fun FeatureGroup.image( image: ImageVector, size: DpSize = DpSize(image.defaultWidth, image.defaultHeight), id: String? = null, -): FeatureId> = +): FeatureRef> = feature( id, VectorImageFeature( @@ -349,7 +234,7 @@ public fun FeatureGroup.image( public fun FeatureGroup.group( id: String? = null, builder: FeatureGroup.() -> Unit, -): FeatureId> { +): FeatureRef> { val collection = FeatureGroup(space).apply(builder) val feature = FeatureGroup(space, collection.featureMap) return feature(id, feature) @@ -359,7 +244,7 @@ public fun FeatureGroup.scalableImage( box: Rectangle, id: String? = null, painter: @Composable () -> Painter, -): FeatureId> = feature( +): FeatureRef> = feature( id, ScalableImageFeature(space, box, painter = painter) ) @@ -369,7 +254,7 @@ public fun FeatureGroup.text( text: String, font: FeatureFont.() -> Unit = { size = 16f }, id: String? = null, -): FeatureId> = feature( +): FeatureRef> = feature( id, TextFeature(space, position, text, fontConfig = font) ) 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 f874942..7a30a74 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 @@ -4,18 +4,18 @@ import center.sciprog.attributes.Attributes public fun FeatureGroup.draggableLine( - aId: FeatureId>, - bId: FeatureId>, + aId: FeatureRef>, + bId: FeatureRef>, id: String? = null, -): FeatureId> { - var lineId: FeatureId>? = null +): FeatureRef> { + var lineId: FeatureRef>? = null - fun drawLine(): FeatureId> { + fun drawLine(): FeatureRef> { //save attributes before update - val attributes: Attributes? = lineId?.let(::get)?.attributes + val attributes: Attributes? = lineId?.attributes val currentId = line( - get(aId).center, - get(bId).center, + aId.resolve().center, + bId.resolve().center, lineId?.id ?: id ) currentId.modifyAttributes { 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 d66e7a4..0284b67 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 @@ -1,8 +1,14 @@ package center.sciprog.maps.features +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.PointerMatcher import androidx.compose.ui.graphics.Color +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 public object ZAttribute : Attribute @@ -10,6 +16,11 @@ public object DraggableAttribute : Attribute> public object DragListenerAttribute : SetAttribute> +/** + * Click radius for point-like and line objects + */ +public object ClickRadius : Attribute + public object ClickListenerAttribute : SetAttribute> public object HoverListenerAttribute : SetAttribute> @@ -22,4 +33,124 @@ public object ColorAttribute : Attribute public object ZoomRangeAttribute : Attribute -public object AlphaAttribute : Attribute \ No newline at end of file +public object AlphaAttribute : Attribute + + +public fun > FeatureRef.modifyAttributes(modify: AttributesBuilder.() -> Unit): FeatureRef { + @Suppress("UNCHECKED_CAST") + parent.feature( + id, + resolve().withAttributes { + AttributesBuilder(this).apply(modify).build() + } as F + ) + return this +} + +public fun , V> FeatureRef.modifyAttribute(key: Attribute, value: V?): FeatureRef{ + @Suppress("UNCHECKED_CAST") + parent.feature(id, resolve().withAttributes { withAttribute(key, value) } as F) + return this +} + +/** + * Add drag to this feature + * + * @param constraint optional drag constraint + * + * TODO use context receiver for that + */ +@Suppress("UNCHECKED_CAST") +public fun > FeatureRef.draggable( + constraint: ((T) -> T)? = null, + listener: (PointerEvent.(from: ViewPoint, to: ViewPoint) -> Unit)? = null, +): FeatureRef = with(parent){ + if (attributes[DraggableAttribute] == null) { + val handle = DragHandle.withPrimaryButton { event, start, end -> + val feature = featureMap[id] as? DraggableFeature ?: return@withPrimaryButton DragResult(end) + start as ViewPoint + end as ViewPoint + if (start in feature) { + val finalPosition = constraint?.invoke(end.focus) ?: end.focus + feature(id, feature.withCoordinates(finalPosition)) + feature.attributes[DragListenerAttribute]?.forEach { + it.handle(event, start, ViewPoint(finalPosition, end.zoom)) + } + DragResult(ViewPoint(finalPosition, end.zoom), false) + } else { + DragResult(end, true) + } + } + modifyAttribute(DraggableAttribute, handle) + } + + //Apply callback + if (listener != null) { + onDrag(listener) + } + return this@draggable +} + + +@Suppress("UNCHECKED_CAST") +public fun > FeatureRef.onDrag( + listener: PointerEvent.(from: ViewPoint, to: ViewPoint) -> Unit, +): FeatureRef = modifyAttributes { + DragListenerAttribute.add( + DragListener { event, from, to -> event.listener(from as ViewPoint, to as ViewPoint) } + ) +} + +@Suppress("UNCHECKED_CAST") +public fun > FeatureRef.onClick( + onClick: PointerEvent.(click: ViewPoint) -> Unit, +): FeatureRef = modifyAttributes { + ClickListenerAttribute.add( + MouseListener { event, point -> + event.onClick(point as ViewPoint) + } + ) +} + +@OptIn(ExperimentalFoundationApi::class) +@Suppress("UNCHECKED_CAST") +public fun > FeatureRef.onClick( + pointerMatcher: PointerMatcher, + keyboardModifiers: PointerKeyboardModifiers.() -> Boolean = { true }, + onClick: PointerEvent.(click: ViewPoint) -> Unit, +): FeatureRef = modifyAttributes { + ClickListenerAttribute.add( + MouseListener { event, point -> + if (pointerMatcher.matches(event) && keyboardModifiers(event.keyboardModifiers)) { + event.onClick(point as ViewPoint) + } + } + ) +} + +@Suppress("UNCHECKED_CAST") +public fun > FeatureRef.onHover( + onClick: PointerEvent.(move: ViewPoint) -> Unit, +): FeatureRef = modifyAttributes { + HoverListenerAttribute.add( + MouseListener { event, point -> event.onClick(point as ViewPoint) } + ) +} + +// @Suppress("UNCHECKED_CAST") +// @OptIn(ExperimentalFoundationApi::class) +// public fun > FeatureId.onTap( +// pointerMatcher: PointerMatcher = PointerMatcher.Primary, +// keyboardFilter: PointerKeyboardModifiers.() -> Boolean = { true }, +// onTap: (point: ViewPoint) -> Unit, +// ): FeatureId = modifyAttributes { +// TapListenerAttribute.add( +// TapListener(pointerMatcher, keyboardFilter) { point -> onTap(point as ViewPoint) } +// ) +// } + +public fun > FeatureRef.color(color: Color): FeatureRef = + modifyAttribute(ColorAttribute, color) + +public fun > FeatureRef.zoomRange(range: FloatRange): FeatureRef = + modifyAttribute(ZoomRangeAttribute, range) 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 75087e8..1c461a5 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 @@ -1,10 +1,10 @@ package center.sciprog.maps.geojson import center.sciprog.maps.coordinates.Gmc -import center.sciprog.maps.coordinates.kilometers 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 { @@ -30,13 +30,13 @@ internal fun JsonElement.toGmc() = jsonArray.run { Gmc.ofDegrees( get(1).jsonPrimitive.double, get(0).jsonPrimitive.double, - get(2).jsonPrimitive.doubleOrNull?.meters ?: 0.kilometers + getOrNull(2)?.jsonPrimitive?.doubleOrNull?.meters ) } internal fun Gmc.toJsonArray(): JsonArray = buildJsonArray { - add(longitude.degrees.value) - add(latitude.degrees.value) + add(longitude.degrees) + add(latitude.degrees) elevation?.let { add(it.meters) } 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 d677a72..b6b20c0 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 @@ -16,7 +16,7 @@ import kotlinx.serialization.json.jsonPrimitive public fun FeatureGroup.geoJsonGeometry( geometry: GeoJsonGeometry, id: String? = null, -): FeatureId> = when (geometry) { +): FeatureRef> = when (geometry) { is GeoJsonLineString -> points( geometry.coordinates, pointMode = PointMode.Lines @@ -59,7 +59,7 @@ public fun FeatureGroup.geoJsonGeometry( public fun FeatureGroup.geoJsonFeature( geoJson: GeoJsonFeature, id: String? = null, -): FeatureId> { +): FeatureRef> { val geometry = geoJson.geometry ?: return group {} val idOverride = id ?: geoJson.getProperty("id")?.jsonPrimitive?.contentOrNull @@ -81,7 +81,7 @@ public fun FeatureGroup.geoJsonFeature( public fun FeatureGroup.geoJson( geoJson: GeoJson, id: String? = null, -): FeatureId> = when (geoJson) { +): FeatureRef> = when (geoJson) { is GeoJsonFeature -> geoJsonFeature(geoJson, id = id) is GeoJsonFeatureCollection -> group(id = id) { geoJson.features.forEach { diff --git a/maps-kt-geojson/src/jvmMain/kotlin/center/sciprog/maps/geojson/geoJsonFeatureJvm.kt b/maps-kt-geojson/src/jvmMain/kotlin/center/sciprog/maps/geojson/geoJsonFeatureJvm.kt index 4339a31..da84729 100644 --- a/maps-kt-geojson/src/jvmMain/kotlin/center/sciprog/maps/geojson/geoJsonFeatureJvm.kt +++ b/maps-kt-geojson/src/jvmMain/kotlin/center/sciprog/maps/geojson/geoJsonFeatureJvm.kt @@ -3,7 +3,7 @@ package center.sciprog.maps.geojson import center.sciprog.maps.coordinates.Gmc import center.sciprog.maps.features.Feature import center.sciprog.maps.features.FeatureGroup -import center.sciprog.maps.features.FeatureId +import center.sciprog.maps.features.FeatureRef import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonObject import java.net.URL @@ -14,7 +14,7 @@ import java.net.URL public fun FeatureGroup.geoJson( geoJsonUrl: URL, id: String? = null, -): FeatureId> { +): FeatureRef> { val jsonString = geoJsonUrl.readText() val json = Json.parseToJsonElement(jsonString).jsonObject val geoJson = GeoJson(json) 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 756d26b..2f0af99 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 @@ -18,7 +18,7 @@ fun FeatureGroup.background( offset: XY = XY(0f, 0f), id: String? = null, painter: @Composable () -> Painter, -): FeatureId> { +): FeatureRef> { val box = XYRectangle( offset, XY(width + offset.x, height + offset.y) @@ -38,19 +38,19 @@ fun FeatureGroup.circle( centerCoordinates: Pair, size: Dp = 5.dp, id: String? = null, -): FeatureId> = circle(centerCoordinates.toCoordinates(), size, id = id) +): FeatureRef> = circle(centerCoordinates.toCoordinates(), size, id = id) fun FeatureGroup.draw( position: Pair, id: String? = null, draw: DrawScope.() -> Unit, -): FeatureId> = draw(position.toCoordinates(), id = id, draw = draw) +): FeatureRef> = draw(position.toCoordinates(), id = id, draw = draw) fun FeatureGroup.line( aCoordinates: Pair, bCoordinates: Pair, id: String? = null, -): FeatureId> = line(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), id) +): FeatureRef> = line(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), id) public fun FeatureGroup.arc( @@ -59,7 +59,7 @@ public fun FeatureGroup.arc( startAngle: Float, arcLength: Float, id: String? = null, -): FeatureId> = arc( +): FeatureRef> = arc( oval = XYCoordinateSpace.Rectangle(center.toCoordinates(), radius, radius), startAngle = startAngle, arcLength = arcLength, @@ -71,12 +71,12 @@ fun FeatureGroup.image( image: ImageVector, size: DpSize = DpSize(image.defaultWidth, image.defaultHeight), id: String? = null, -): FeatureId> = +): FeatureRef> = image(position.toCoordinates(), image, size = size, id = id) fun FeatureGroup.text( position: Pair, text: String, id: String? = null, -): FeatureId> = text(position.toCoordinates(), text, id = id) +): FeatureRef> = text(position.toCoordinates(), text, id = id) diff --git a/settings.gradle.kts b/settings.gradle.kts index 8014588..af40e60 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,6 +8,7 @@ pluginManagement { val toolsVersion: String by extra repositories { + mavenLocal() google() gradlePluginPortal() mavenCentral()