Add arc feature. Add (approximate) ellipsoid
This commit is contained in:
parent
4c3aefcfae
commit
14acd88358
14
README.md
14
README.md
@ -1 +1,13 @@
|
|||||||
This repository is a work-in-progress implementation of Map-with-markers component for Compose-Multiplatform
|
This repository is a work-in-progress implementation of Map-with-markers component for Compose-Multiplatform
|
||||||
|
|
||||||
|
## [maps-kt-core](maps-kt-core)
|
||||||
|
A multiplatform coordinates representation and conversion.
|
||||||
|
|
||||||
|
## [maps-kt-compose](maps-kt-compose)
|
||||||
|
A compose multiplatform (currently desktop only, contributions of android target are welcome) implementation of a map component, features and builder.
|
||||||
|
|
||||||
|
## [maps-kt-scheme](maps-kt-scheme)
|
||||||
|
An alternative component used for the same functionality on 2D schemes. Not all features from maps could be ported because it requires some code duplication (ideas for common API are welcome).
|
||||||
|
|
||||||
|
## [demo](demo)
|
||||||
|
Demonstration projects for different features
|
@ -5,11 +5,11 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.Home
|
import androidx.compose.material.icons.filled.Home
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.geometry.Size
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.window.Window
|
import androidx.compose.ui.window.Window
|
||||||
import androidx.compose.ui.window.application
|
import androidx.compose.ui.window.application
|
||||||
import center.sciprog.maps.compose.*
|
import center.sciprog.maps.compose.*
|
||||||
|
import center.sciprog.maps.coordinates.Distance
|
||||||
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
|
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
|
||||||
import center.sciprog.maps.coordinates.MapViewPoint
|
import center.sciprog.maps.coordinates.MapViewPoint
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
@ -67,16 +67,15 @@ fun App() {
|
|||||||
centerCoordinates = pointTwo,
|
centerCoordinates = pointTwo,
|
||||||
)
|
)
|
||||||
|
|
||||||
custom(position = pointThree) {
|
draw(position = pointThree) {
|
||||||
drawRect(
|
drawLine(start = Offset(-10f, -10f), end = Offset(10f, 10f), color = Color.Red)
|
||||||
color = Color.Red,
|
drawLine(start = Offset(-10f, 10f), end = Offset(10f, -10f), color = Color.Red)
|
||||||
topLeft = Offset(-10f, -10f),
|
|
||||||
size = Size(20f, 20f)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arc(pointOne, Distance(10.0), 0f, PI)
|
||||||
|
|
||||||
line(pointOne, pointTwo)
|
line(pointOne, pointTwo)
|
||||||
text(pointOne, "Home")
|
text(pointOne, "Home", font = { size = 32f })
|
||||||
|
|
||||||
centerCoordinates?.let {
|
centerCoordinates?.let {
|
||||||
group(id = "center") {
|
group(id = "center") {
|
||||||
|
@ -14,8 +14,7 @@ import center.sciprog.maps.coordinates.GeodeticMapCoordinates
|
|||||||
import center.sciprog.maps.coordinates.GmcBox
|
import center.sciprog.maps.coordinates.GmcBox
|
||||||
import center.sciprog.maps.coordinates.wrapAll
|
import center.sciprog.maps.coordinates.wrapAll
|
||||||
|
|
||||||
//TODO replace zoom range with zoom-based representation change
|
public interface MapFeature {
|
||||||
public sealed interface MapFeature {
|
|
||||||
public val zoomRange: IntRange
|
public val zoomRange: IntRange
|
||||||
public fun getBoundingBox(zoom: Int): GmcBox?
|
public fun getBoundingBox(zoom: Int): GmcBox?
|
||||||
}
|
}
|
||||||
@ -42,7 +41,7 @@ public class MapDrawFeature(
|
|||||||
public val position: GeodeticMapCoordinates,
|
public val position: GeodeticMapCoordinates,
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
override val zoomRange: IntRange = defaultZoomRange,
|
||||||
public val drawFeature: DrawScope.() -> Unit,
|
public val drawFeature: DrawScope.() -> Unit,
|
||||||
) : MapFeature{
|
) : MapFeature {
|
||||||
override fun getBoundingBox(zoom: Int): GmcBox {
|
override fun getBoundingBox(zoom: Int): GmcBox {
|
||||||
//TODO add box computation
|
//TODO add box computation
|
||||||
return GmcBox(position, position)
|
return GmcBox(position, position)
|
||||||
@ -58,6 +57,15 @@ public class MapCircleFeature(
|
|||||||
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(center, center)
|
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(center, center)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class MapRectangleFeature(
|
||||||
|
public val center: GeodeticMapCoordinates,
|
||||||
|
override val zoomRange: IntRange = defaultZoomRange,
|
||||||
|
public val size: DpSize = DpSize(5.dp, 5.dp),
|
||||||
|
public val color: Color = Color.Red,
|
||||||
|
) : MapFeature {
|
||||||
|
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(center, center)
|
||||||
|
}
|
||||||
|
|
||||||
public class MapLineFeature(
|
public class MapLineFeature(
|
||||||
public val a: GeodeticMapCoordinates,
|
public val a: GeodeticMapCoordinates,
|
||||||
public val b: GeodeticMapCoordinates,
|
public val b: GeodeticMapCoordinates,
|
||||||
@ -67,13 +75,14 @@ public class MapLineFeature(
|
|||||||
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(a, b)
|
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(a, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MapTextFeature(
|
public class MapArcFeature(
|
||||||
public val position: GeodeticMapCoordinates,
|
public val oval: GmcBox,
|
||||||
public val text: String,
|
public val startAngle: Float,
|
||||||
|
public val endAngle: Float,
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
override val zoomRange: IntRange = defaultZoomRange,
|
||||||
public val color: Color = Color.Red,
|
public val color: Color = Color.Red,
|
||||||
) : MapFeature {
|
) : MapFeature {
|
||||||
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position)
|
override fun getBoundingBox(zoom: Int): GmcBox = oval
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MapBitmapImageFeature(
|
public class MapBitmapImageFeature(
|
||||||
@ -81,7 +90,7 @@ public class MapBitmapImageFeature(
|
|||||||
public val image: ImageBitmap,
|
public val image: ImageBitmap,
|
||||||
public val size: IntSize = IntSize(15, 15),
|
public val size: IntSize = IntSize(15, 15),
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
override val zoomRange: IntRange = defaultZoomRange,
|
||||||
) : MapFeature{
|
) : MapFeature {
|
||||||
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position)
|
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,6 +117,6 @@ public fun MapVectorImageFeature(
|
|||||||
public class MapFeatureGroup(
|
public class MapFeatureGroup(
|
||||||
public val children: Map<FeatureId, MapFeature>,
|
public val children: Map<FeatureId, MapFeature>,
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
override val zoomRange: IntRange = defaultZoomRange,
|
||||||
) : MapFeature{
|
) : MapFeature {
|
||||||
override fun getBoundingBox(zoom: Int): GmcBox? = children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
|
override fun getBoundingBox(zoom: Int): GmcBox? = children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
|
||||||
}
|
}
|
@ -8,7 +8,9 @@ import androidx.compose.ui.graphics.drawscope.DrawScope
|
|||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import center.sciprog.maps.coordinates.Distance
|
||||||
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
|
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
|
||||||
|
import center.sciprog.maps.coordinates.GmcBox
|
||||||
|
|
||||||
public typealias FeatureId = String
|
public typealias FeatureId = String
|
||||||
|
|
||||||
@ -55,7 +57,17 @@ public fun MapFeatureBuilder.circle(
|
|||||||
id, MapCircleFeature(centerCoordinates.toCoordinates(), zoomRange, size, color)
|
id, MapCircleFeature(centerCoordinates.toCoordinates(), zoomRange, size, color)
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun MapFeatureBuilder.custom(
|
public fun MapFeatureBuilder.rectangle(
|
||||||
|
centerCoordinates: Pair<Double, Double>,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
size: DpSize = DpSize(5.dp, 5.dp),
|
||||||
|
color: Color = Color.Red,
|
||||||
|
id: FeatureId? = null,
|
||||||
|
): FeatureId = addFeature(
|
||||||
|
id, MapRectangleFeature(centerCoordinates.toCoordinates(), zoomRange, size, color)
|
||||||
|
)
|
||||||
|
|
||||||
|
public fun MapFeatureBuilder.draw(
|
||||||
position: Pair<Double, Double>,
|
position: Pair<Double, Double>,
|
||||||
zoomRange: IntRange = defaultZoomRange,
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
id: FeatureId? = null,
|
id: FeatureId? = null,
|
||||||
@ -68,23 +80,41 @@ public fun MapFeatureBuilder.line(
|
|||||||
zoomRange: IntRange = defaultZoomRange,
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
color: Color = Color.Red,
|
color: Color = Color.Red,
|
||||||
id: FeatureId? = null,
|
id: FeatureId? = null,
|
||||||
): FeatureId = addFeature(id, MapLineFeature(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), zoomRange, color))
|
): FeatureId = addFeature(
|
||||||
|
id,
|
||||||
|
MapLineFeature(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), zoomRange, color)
|
||||||
|
)
|
||||||
|
|
||||||
public fun MapFeatureBuilder.text(
|
public fun MapFeatureBuilder.arc(
|
||||||
position: GeodeticMapCoordinates,
|
oval: GmcBox,
|
||||||
text: String,
|
startAngle: Number,
|
||||||
|
endAngle: Number,
|
||||||
zoomRange: IntRange = defaultZoomRange,
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
color: Color = Color.Red,
|
color: Color = Color.Red,
|
||||||
id: FeatureId? = null,
|
id: FeatureId? = null,
|
||||||
): FeatureId = addFeature(id, MapTextFeature(position, text, zoomRange, color))
|
): FeatureId = addFeature(
|
||||||
|
id,
|
||||||
|
MapArcFeature(oval, startAngle.toFloat(), endAngle.toFloat(), zoomRange, color)
|
||||||
|
)
|
||||||
|
|
||||||
public fun MapFeatureBuilder.text(
|
public fun MapFeatureBuilder.arc(
|
||||||
position: Pair<Double, Double>,
|
center: Pair<Double, Double>,
|
||||||
text: String,
|
radius: Distance,
|
||||||
|
startAngle: Number,
|
||||||
|
endAngle: Number,
|
||||||
zoomRange: IntRange = defaultZoomRange,
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
color: Color = Color.Red,
|
color: Color = Color.Red,
|
||||||
id: FeatureId? = null,
|
id: FeatureId? = null,
|
||||||
): FeatureId = addFeature(id, MapTextFeature(position.toCoordinates(), text, zoomRange, color))
|
): FeatureId = addFeature(
|
||||||
|
id,
|
||||||
|
MapArcFeature(
|
||||||
|
GmcBox.withCenter(center.toCoordinates(), radius, radius),
|
||||||
|
startAngle.toFloat(),
|
||||||
|
endAngle.toFloat(),
|
||||||
|
zoomRange,
|
||||||
|
color
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
public fun MapFeatureBuilder.image(
|
public fun MapFeatureBuilder.image(
|
||||||
|
@ -17,7 +17,8 @@ public data class MapViewConfig(
|
|||||||
val onClick: MapViewPoint.() -> Unit = {},
|
val onClick: MapViewPoint.() -> Unit = {},
|
||||||
val onViewChange: MapViewPoint.() -> Unit = {},
|
val onViewChange: MapViewPoint.() -> Unit = {},
|
||||||
val onSelect: (GmcBox) -> Unit = {},
|
val onSelect: (GmcBox) -> Unit = {},
|
||||||
val zoomOnSelect: Boolean = true
|
val zoomOnSelect: Boolean = true,
|
||||||
|
val resetViewPoint: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
package center.sciprog.maps.compose
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
|
||||||
|
import center.sciprog.maps.coordinates.GmcBox
|
||||||
|
import org.jetbrains.skia.Font
|
||||||
|
|
||||||
|
|
||||||
|
public class MapTextFeature(
|
||||||
|
public val position: GeodeticMapCoordinates,
|
||||||
|
public val text: String,
|
||||||
|
override val zoomRange: IntRange = defaultZoomRange,
|
||||||
|
public val color: Color,
|
||||||
|
public val fontConfig: Font.() -> Unit,
|
||||||
|
) : MapFeature {
|
||||||
|
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun MapFeatureBuilder.text(
|
||||||
|
position: GeodeticMapCoordinates,
|
||||||
|
text: String,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
font: Font.() -> Unit = { size = 16f },
|
||||||
|
id: FeatureId? = null,
|
||||||
|
): FeatureId = addFeature(id, MapTextFeature(position, text, zoomRange, color, font))
|
||||||
|
|
||||||
|
public fun MapFeatureBuilder.text(
|
||||||
|
position: Pair<Double, Double>,
|
||||||
|
text: String,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
font: Font.() -> Unit = { size = 16f },
|
||||||
|
id: FeatureId? = null,
|
||||||
|
): FeatureId = addFeature(id, MapTextFeature(position.toCoordinates(), text, zoomRange, color, font))
|
@ -9,11 +9,8 @@ import androidx.compose.ui.ExperimentalComposeUiApi
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.geometry.Rect
|
import androidx.compose.ui.geometry.Rect
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.*
|
||||||
import androidx.compose.ui.graphics.PathEffect
|
|
||||||
import androidx.compose.ui.graphics.drawscope.*
|
import androidx.compose.ui.graphics.drawscope.*
|
||||||
import androidx.compose.ui.graphics.nativeCanvas
|
|
||||||
import androidx.compose.ui.graphics.toArgb
|
|
||||||
import androidx.compose.ui.input.pointer.*
|
import androidx.compose.ui.input.pointer.*
|
||||||
import androidx.compose.ui.unit.*
|
import androidx.compose.ui.unit.*
|
||||||
import center.sciprog.maps.coordinates.*
|
import center.sciprog.maps.coordinates.*
|
||||||
@ -63,6 +60,10 @@ public actual fun MapView(
|
|||||||
mutableStateOf(null)
|
mutableStateOf(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.resetViewPoint) {
|
||||||
|
viewPointInternal = null
|
||||||
|
}
|
||||||
|
|
||||||
val viewPoint: MapViewPoint by derivedStateOf {
|
val viewPoint: MapViewPoint by derivedStateOf {
|
||||||
viewPointInternal ?: if (config.inferViewBoxFromFeatures) {
|
viewPointInternal ?: if (config.inferViewBoxFromFeatures) {
|
||||||
features.values.computeBoundingBox(1)?.let { box ->
|
features.values.computeBoundingBox(1)?.let { box ->
|
||||||
@ -223,7 +224,26 @@ public actual fun MapView(
|
|||||||
feature.size,
|
feature.size,
|
||||||
center = feature.center.toOffset()
|
center = feature.center.toOffset()
|
||||||
)
|
)
|
||||||
|
is MapRectangleFeature -> drawRect(
|
||||||
|
feature.color,
|
||||||
|
topLeft = feature.center.toOffset() - Offset(
|
||||||
|
feature.size.width.toPx() / 2,
|
||||||
|
feature.size.height.toPx() / 2
|
||||||
|
),
|
||||||
|
size = feature.size.toSize()
|
||||||
|
)
|
||||||
is MapLineFeature -> drawLine(feature.color, feature.a.toOffset(), feature.b.toOffset())
|
is MapLineFeature -> drawLine(feature.color, feature.a.toOffset(), feature.b.toOffset())
|
||||||
|
is MapArcFeature -> {
|
||||||
|
val topLeft = feature.oval.topLeft.toOffset()
|
||||||
|
val bottomRight = feature.oval.bottomRight.toOffset()
|
||||||
|
|
||||||
|
val path = Path().apply {
|
||||||
|
addArcRad(Rect(topLeft, bottomRight), feature.startAngle, feature.endAngle - feature.startAngle)
|
||||||
|
}
|
||||||
|
|
||||||
|
drawPath(path, color = feature.color, style = Stroke())
|
||||||
|
|
||||||
|
}
|
||||||
is MapBitmapImageFeature -> drawImage(feature.image, feature.position.toOffset())
|
is MapBitmapImageFeature -> drawImage(feature.image, feature.position.toOffset())
|
||||||
is MapVectorImageFeature -> {
|
is MapVectorImageFeature -> {
|
||||||
val offset = feature.position.toOffset()
|
val offset = feature.position.toOffset()
|
||||||
@ -240,7 +260,7 @@ public actual fun MapView(
|
|||||||
feature.text,
|
feature.text,
|
||||||
offset.x + 5,
|
offset.x + 5,
|
||||||
offset.y - 5,
|
offset.y - 5,
|
||||||
Font().apply { size = 16f },
|
Font().apply(feature.fontConfig),
|
||||||
feature.color.toPaint()
|
feature.color.toPaint()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -255,6 +275,9 @@ public actual fun MapView(
|
|||||||
drawFeature(zoom, it)
|
drawFeature(zoom, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else -> {
|
||||||
|
logger.error { "Unrecognized feature type: ${feature::class}" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import kotlin.io.path.*
|
|||||||
/**
|
/**
|
||||||
* A [MapTileProvider] based on Open Street Map API. With in-memory and file cache
|
* A [MapTileProvider] based on Open Street Map API. With in-memory and file cache
|
||||||
*/
|
*/
|
||||||
class OpenStreetMapTileProvider(
|
public class OpenStreetMapTileProvider(
|
||||||
private val client: HttpClient,
|
private val client: HttpClient,
|
||||||
private val cacheDirectory: Path,
|
private val cacheDirectory: Path,
|
||||||
parallelism: Int = 1,
|
parallelism: Int = 1,
|
||||||
@ -93,7 +93,7 @@ class OpenStreetMapTileProvider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
public companion object {
|
||||||
private val logger = KotlinLogging.logger("OpenStreetMapCache")
|
private val logger = KotlinLogging.logger("OpenStreetMapCache")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package center.sciprog.maps.coordinates
|
||||||
|
|
||||||
|
import kotlin.jvm.JvmInline
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
public value class Distance(public val kilometers: Double)
|
||||||
|
|
||||||
|
public operator fun Distance.div(other: Distance): Double = kilometers / other.kilometers
|
||||||
|
|
||||||
|
public operator fun Distance.plus(other: Distance): Distance = Distance(kilometers + other.kilometers)
|
||||||
|
public operator fun Distance.minus(other: Distance): Distance = Distance(kilometers - other.kilometers)
|
||||||
|
|
||||||
|
public operator fun Distance.times(number: Number): Distance = Distance(kilometers * number.toDouble())
|
||||||
|
public operator fun Distance.div(number: Number): Distance = Distance(kilometers / number.toDouble())
|
||||||
|
|
||||||
|
|
||||||
|
public val Distance.meters: Double get() = kilometers * 1000
|
@ -0,0 +1,14 @@
|
|||||||
|
package center.sciprog.maps.coordinates
|
||||||
|
|
||||||
|
public class Ellipsoid(public val equatorRadius: Distance, public val polarRadius: Distance) {
|
||||||
|
public companion object {
|
||||||
|
public val WGS84: Ellipsoid = Ellipsoid(
|
||||||
|
equatorRadius = Distance(6378.137),
|
||||||
|
polarRadius = Distance(6356.7523142)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public val Ellipsoid.f: Double get() = (equatorRadius.kilometers - polarRadius.kilometers) / equatorRadius.kilometers
|
||||||
|
|
||||||
|
public val Ellipsoid.inverseF: Double get() = equatorRadius.kilometers / (equatorRadius.kilometers - polarRadius.kilometers)
|
@ -1,21 +1,34 @@
|
|||||||
package center.sciprog.maps.coordinates
|
package center.sciprog.maps.coordinates
|
||||||
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.cos
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
public data class GmcBox(
|
public data class GmcBox(
|
||||||
public val a: GeodeticMapCoordinates,
|
public val a: GeodeticMapCoordinates,
|
||||||
public val b: GeodeticMapCoordinates,
|
public val b: GeodeticMapCoordinates,
|
||||||
)
|
) {
|
||||||
|
public companion object {
|
||||||
public fun GmcBox(
|
public fun withCenter(
|
||||||
latitudes: ClosedFloatingPointRange<Double>,
|
center: GeodeticMapCoordinates,
|
||||||
longitudes: ClosedFloatingPointRange<Double>,
|
width: Distance,
|
||||||
): GmcBox = GmcBox(
|
height: Distance,
|
||||||
GeodeticMapCoordinates.ofRadians(latitudes.start, longitudes.start),
|
ellipsoid: Ellipsoid = Ellipsoid.WGS84,
|
||||||
GeodeticMapCoordinates.ofRadians(latitudes.endInclusive, longitudes.endInclusive)
|
): GmcBox {
|
||||||
)
|
val r = ellipsoid.equatorRadius * cos(center.latitude)
|
||||||
|
val a = GeodeticMapCoordinates.ofRadians(
|
||||||
|
center.latitude - height / ellipsoid.polarRadius / 2,
|
||||||
|
center.longitude - width / r / 2
|
||||||
|
)
|
||||||
|
val b = GeodeticMapCoordinates.ofRadians(
|
||||||
|
center.latitude + height / ellipsoid.polarRadius / 2,
|
||||||
|
center.longitude + width / r / 2
|
||||||
|
)
|
||||||
|
return GmcBox(a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public val GmcBox.center: GeodeticMapCoordinates
|
public val GmcBox.center: GeodeticMapCoordinates
|
||||||
get() = GeodeticMapCoordinates.ofRadians(
|
get() = GeodeticMapCoordinates.ofRadians(
|
||||||
@ -23,16 +36,33 @@ public val GmcBox.center: GeodeticMapCoordinates
|
|||||||
(a.longitude + b.longitude) / 2
|
(a.longitude + b.longitude) / 2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum longitude
|
||||||
|
*/
|
||||||
public val GmcBox.left: Double get() = min(a.longitude, b.longitude)
|
public val GmcBox.left: Double get() = min(a.longitude, b.longitude)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* maximum longitude
|
||||||
|
*/
|
||||||
public val GmcBox.right: Double get() = max(a.longitude, b.longitude)
|
public val GmcBox.right: Double get() = max(a.longitude, b.longitude)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum latitude
|
||||||
|
*/
|
||||||
public val GmcBox.top: Double get() = max(a.latitude, b.latitude)
|
public val GmcBox.top: Double get() = max(a.latitude, b.latitude)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum latitude
|
||||||
|
*/
|
||||||
public val GmcBox.bottom: Double get() = min(a.latitude, b.latitude)
|
public val GmcBox.bottom: Double get() = min(a.latitude, b.latitude)
|
||||||
|
|
||||||
//TODO take curvature into account
|
//TODO take curvature into account
|
||||||
public val GmcBox.width: Double get() = abs(a.longitude - b.longitude)
|
public val GmcBox.width: Double get() = abs(a.longitude - b.longitude)
|
||||||
public val GmcBox.height: Double get() = abs(a.latitude - b.latitude)
|
public val GmcBox.height: Double get() = abs(a.latitude - b.latitude)
|
||||||
|
|
||||||
|
public val GmcBox.topLeft: GeodeticMapCoordinates get() = GeodeticMapCoordinates.ofRadians(top, left)
|
||||||
|
public val GmcBox.bottomRight: GeodeticMapCoordinates get() = GeodeticMapCoordinates.ofRadians(bottom, right)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute a minimal bounding box including all given boxes. Return null if collection is empty
|
* Compute a minimal bounding box including all given boxes. Return null if collection is empty
|
||||||
*/
|
*/
|
||||||
@ -43,5 +73,5 @@ public fun Collection<GmcBox>.wrapAll(): GmcBox? {
|
|||||||
val maxLat = maxOf { it.top }
|
val maxLat = maxOf { it.top }
|
||||||
val minLong = minOf { it.left }
|
val minLong = minOf { it.left }
|
||||||
val maxLong = maxOf { it.right }
|
val maxLong = maxOf { it.right }
|
||||||
return GmcBox(minLat..maxLat, minLong..maxLong)
|
return GmcBox(GeodeticMapCoordinates.ofRadians(minLat, minLong), GeodeticMapCoordinates.ofRadians(maxLat, maxLong))
|
||||||
}
|
}
|
@ -1,20 +1,11 @@
|
|||||||
import org.jetbrains.compose.compose
|
import org.jetbrains.compose.compose
|
||||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("multiplatform")
|
kotlin("multiplatform")
|
||||||
id("org.jetbrains.compose")
|
id("org.jetbrains.compose")
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "center.sciprog"
|
|
||||||
version = "1.0-SNAPSHOT"
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm {
|
jvm {
|
||||||
compilations.all {
|
compilations.all {
|
||||||
@ -32,20 +23,8 @@ kotlin {
|
|||||||
val jvmMain by getting {
|
val jvmMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
api(compose.desktop.currentOs)
|
api(compose.desktop.currentOs)
|
||||||
implementation("ch.qos.logback:logback-classic:1.2.11")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val jvmTest by getting
|
val jvmTest by getting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compose.desktop {
|
|
||||||
application {
|
|
||||||
mainClass = "MainKt"
|
|
||||||
nativeDistributions {
|
|
||||||
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
|
||||||
packageName = "compose-scheme"
|
|
||||||
packageVersion = "1.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -82,7 +82,7 @@ fun SchemeFeatureBuilder.circle(
|
|||||||
id, SchemeCircleFeature(centerCoordinates.toCoordinates(), scaleRange, size, color)
|
id, SchemeCircleFeature(centerCoordinates.toCoordinates(), scaleRange, size, color)
|
||||||
)
|
)
|
||||||
|
|
||||||
fun SchemeFeatureBuilder.custom(
|
fun SchemeFeatureBuilder.draw(
|
||||||
position: Pair<Number, Number>,
|
position: Pair<Number, Number>,
|
||||||
scaleRange: FloatRange = defaultScaleRange,
|
scaleRange: FloatRange = defaultScaleRange,
|
||||||
id: FeatureId? = null,
|
id: FeatureId? = null,
|
Loading…
Reference in New Issue
Block a user