Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dc8f7cdfc | ||
| c7d1797617 | |||
| 2fdec494fb | |||
| 5e548fcc65 | |||
| 307e42eac2 | |||
| 52d0d959de |
@@ -1,11 +1,10 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.window.Window
|
||||
@@ -13,14 +12,20 @@ import androidx.compose.ui.window.application
|
||||
import centre.sciprog.maps.GeodeticMapCoordinates
|
||||
import centre.sciprog.maps.MapViewPoint
|
||||
import centre.sciprog.maps.compose.*
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.engine.cio.*
|
||||
import centre.sciprog.maps.toDegrees
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.cio.CIO
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import java.nio.file.Path
|
||||
import kotlin.math.PI
|
||||
import kotlin.random.Random
|
||||
|
||||
private fun GeodeticMapCoordinates.toShortString(): String =
|
||||
"${(latitude * 180.0 / PI).toString().take(6)}:${(longitude * 180.0 / PI).toString().take(6)}"
|
||||
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun App() {
|
||||
@@ -41,48 +46,64 @@ fun App() {
|
||||
)
|
||||
}
|
||||
|
||||
var coordinates by remember { mutableStateOf<GeodeticMapCoordinates?>(null) }
|
||||
var centerCoordinates by remember { mutableStateOf<GeodeticMapCoordinates?>(null) }
|
||||
|
||||
Column {
|
||||
//display click coordinates
|
||||
Text(coordinates?.toString() ?: "")
|
||||
MapView(
|
||||
mapTileProvider = mapTileProvider,
|
||||
initialViewPoint = viewPoint,
|
||||
onClick = { coordinates = focus },
|
||||
config = MapViewConfig(inferViewBoxFromFeatures = true)
|
||||
) {
|
||||
val pointOne = 55.568548 to 37.568604
|
||||
val pointTwo = 55.929444 to 37.518434
|
||||
val pointThree = 60.929444 to 37.518434
|
||||
val pointOne = 55.568548 to 37.568604
|
||||
var pointTwo by remember { mutableStateOf(55.929444 to 37.518434) }
|
||||
val pointThree = 60.929444 to 37.518434
|
||||
|
||||
image(pointOne, Icons.Filled.Home)
|
||||
|
||||
//remember feature Id
|
||||
val circleId: FeatureId = circle(
|
||||
centerCoordinates = pointTwo,
|
||||
)
|
||||
|
||||
custom(position = pointThree) {
|
||||
drawRect(
|
||||
color = Color.Red,
|
||||
topLeft = it,
|
||||
size = Size(20f, 20f)
|
||||
)
|
||||
MapView(
|
||||
mapTileProvider = mapTileProvider,
|
||||
initialViewPoint = viewPoint,
|
||||
config = MapViewConfig(
|
||||
inferViewBoxFromFeatures = true,
|
||||
onViewChange = { centerCoordinates = focus },
|
||||
onDrag = { start, end ->
|
||||
if (start.focus.latitude.toDegrees() in (pointTwo.first - 0.05)..(pointTwo.first + 0.05) &&
|
||||
start.focus.longitude.toDegrees() in (pointTwo.second - 0.05)..(pointTwo.second + 0.05)
|
||||
) {
|
||||
pointTwo = pointTwo.first + (end.focus.latitude - start.focus.latitude).toDegrees() to
|
||||
pointTwo.second + (end.focus.longitude - start.focus.longitude).toDegrees()
|
||||
false
|
||||
} else true
|
||||
}
|
||||
line(pointOne, pointTwo)
|
||||
text(pointOne, "Home")
|
||||
)
|
||||
) {
|
||||
|
||||
scope.launch {
|
||||
while (isActive) {
|
||||
delay(200)
|
||||
//Overwrite a feature with new color
|
||||
circle(
|
||||
pointTwo,
|
||||
id = circleId,
|
||||
color = Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat())
|
||||
)
|
||||
}
|
||||
image(pointOne, Icons.Filled.Home)
|
||||
|
||||
//remember feature Id
|
||||
val circleId: FeatureId = circle(
|
||||
centerCoordinates = pointTwo,
|
||||
)
|
||||
|
||||
custom(position = pointThree) {
|
||||
drawRect(
|
||||
color = Color.Red,
|
||||
topLeft = Offset(-10f, -10f),
|
||||
size = Size(20f, 20f)
|
||||
)
|
||||
}
|
||||
|
||||
line(pointOne, pointTwo, id = "Line")
|
||||
text(pointOne, "Home")
|
||||
|
||||
centerCoordinates?.let {
|
||||
group(id = "center") {
|
||||
circle(center = it, color = Color.Blue, size = 1f)
|
||||
text(position = it, it.toShortString(), color = Color.Blue)
|
||||
}
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
while (isActive) {
|
||||
delay(200)
|
||||
//Overwrite a feature with new color
|
||||
circle(
|
||||
pointTwo,
|
||||
id = circleId,
|
||||
color = Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -3,12 +3,12 @@ package centre.sciprog.maps.compose
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import centre.sciprog.maps.GmcBox
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import centre.sciprog.maps.GeodeticMapCoordinates
|
||||
|
||||
typealias FeatureId = String
|
||||
|
||||
@@ -23,6 +23,7 @@ internal class MapFeatureBuilder(initialFeatures: Map<FeatureId, MapFeature>) :
|
||||
private val content: SnapshotStateMap<FeatureId, MapFeature> = mutableStateMapOf<FeatureId, MapFeature>().apply {
|
||||
putAll(initialFeatures)
|
||||
}
|
||||
|
||||
private fun generateID(feature: MapFeature): FeatureId = "@feature[${feature.hashCode().toUInt()}]"
|
||||
|
||||
override fun addFeature(id: FeatureId?, feature: MapFeature): FeatureId {
|
||||
@@ -34,6 +35,16 @@ internal class MapFeatureBuilder(initialFeatures: Map<FeatureId, MapFeature>) :
|
||||
override fun build(): SnapshotStateMap<FeatureId, MapFeature> = content
|
||||
}
|
||||
|
||||
fun FeatureBuilder.circle(
|
||||
center: GeodeticMapCoordinates,
|
||||
zoomRange: IntRange = defaultZoomRange,
|
||||
size: Float = 5f,
|
||||
color: Color = Color.Red,
|
||||
id: FeatureId? = null,
|
||||
) = addFeature(
|
||||
id, MapCircleFeature(center, zoomRange, size, color)
|
||||
)
|
||||
|
||||
fun FeatureBuilder.circle(
|
||||
centerCoordinates: Pair<Double, Double>,
|
||||
zoomRange: IntRange = defaultZoomRange,
|
||||
@@ -46,18 +57,10 @@ fun FeatureBuilder.circle(
|
||||
|
||||
fun FeatureBuilder.custom(
|
||||
position: Pair<Double, Double>,
|
||||
zoomRange: IntRange = defaultZoomRange,
|
||||
id: FeatureId? = null,
|
||||
customFeatureBuilder: DrawScope.(Offset) -> Unit,
|
||||
) = addFeature(id, object : MapCustomFeature(position = position.toCoordinates()) {
|
||||
override fun drawFeature(drawScope: DrawScope, offset: Offset) {
|
||||
customFeatureBuilder(drawScope, offset)
|
||||
}
|
||||
|
||||
override fun getBoundingBox(zoom: Int): GmcBox {
|
||||
return GmcBox(position.toCoordinates(), position.toCoordinates())
|
||||
}
|
||||
|
||||
})
|
||||
drawFeature: DrawScope.() -> Unit,
|
||||
) = addFeature(id, MapDrawFeature(position.toCoordinates(), zoomRange, drawFeature))
|
||||
|
||||
fun FeatureBuilder.line(
|
||||
aCoordinates: Pair<Double, Double>,
|
||||
@@ -67,6 +70,14 @@ fun FeatureBuilder.line(
|
||||
id: FeatureId? = null,
|
||||
) = addFeature(id, MapLineFeature(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), zoomRange, color))
|
||||
|
||||
fun FeatureBuilder.text(
|
||||
position: GeodeticMapCoordinates,
|
||||
text: String,
|
||||
zoomRange: IntRange = defaultZoomRange,
|
||||
color: Color = Color.Red,
|
||||
id: FeatureId? = null,
|
||||
) = addFeature(id, MapTextFeature(position, text, zoomRange, color))
|
||||
|
||||
fun FeatureBuilder.text(
|
||||
position: Pair<Double, Double>,
|
||||
text: String,
|
||||
@@ -79,7 +90,17 @@ fun FeatureBuilder.text(
|
||||
fun FeatureBuilder.image(
|
||||
position: Pair<Double, Double>,
|
||||
image: ImageVector,
|
||||
size: Size = Size(20f, 20f),
|
||||
size: DpSize = DpSize(20.dp, 20.dp),
|
||||
zoomRange: IntRange = defaultZoomRange,
|
||||
id: FeatureId? = null,
|
||||
) = addFeature(id, MapVectorImageFeature(position.toCoordinates(), image, size, zoomRange))
|
||||
) = addFeature(id, MapVectorImageFeature(position.toCoordinates(), image, size, zoomRange))
|
||||
|
||||
fun FeatureBuilder.group(
|
||||
zoomRange: IntRange = defaultZoomRange,
|
||||
id: FeatureId? = null,
|
||||
builder: FeatureBuilder.() -> Unit,
|
||||
): FeatureId {
|
||||
val map = MapFeatureBuilder(emptyMap()).apply(builder).build()
|
||||
val feature = MapFeatureGroup(map, zoomRange)
|
||||
return addFeature(id, feature)
|
||||
}
|
||||
@@ -9,17 +9,20 @@ 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.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import centre.sciprog.maps.GeodeticMapCoordinates
|
||||
import centre.sciprog.maps.GmcBox
|
||||
import centre.sciprog.maps.wrapAll
|
||||
|
||||
//TODO replace zoom range with zoom-based representation change
|
||||
sealed class MapFeature(val zoomRange: IntRange) {
|
||||
abstract fun getBoundingBox(zoom: Int): GmcBox
|
||||
abstract fun getBoundingBox(zoom: Int): GmcBox?
|
||||
}
|
||||
|
||||
fun Iterable<MapFeature>.computeBoundingBox(zoom: Int): GmcBox? = map { it.getBoundingBox(zoom) }.wrapAll()
|
||||
fun Iterable<MapFeature>.computeBoundingBox(zoom: Int): GmcBox? =
|
||||
mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
|
||||
|
||||
internal fun Pair<Double, Double>.toCoordinates() = GeodeticMapCoordinates.ofDegrees(first, second)
|
||||
|
||||
@@ -29,15 +32,18 @@ internal val defaultZoomRange = 1..18
|
||||
* A feature that decides what to show depending on the zoom value (it could change size of shape)
|
||||
*/
|
||||
class MapFeatureSelector(val selector: (zoom: Int) -> MapFeature) : MapFeature(defaultZoomRange) {
|
||||
override fun getBoundingBox(zoom: Int): GmcBox = selector(zoom).getBoundingBox(zoom)
|
||||
|
||||
override fun getBoundingBox(zoom: Int): GmcBox? = selector(zoom).getBoundingBox(zoom)
|
||||
}
|
||||
|
||||
abstract class MapCustomFeature(
|
||||
class MapDrawFeature(
|
||||
val position: GeodeticMapCoordinates,
|
||||
zoomRange: IntRange = defaultZoomRange,
|
||||
val position: GeodeticMapCoordinates
|
||||
val drawFeature: DrawScope.() -> Unit,
|
||||
) : MapFeature(zoomRange) {
|
||||
abstract fun drawFeature(drawScope: DrawScope, offset: Offset)
|
||||
override fun getBoundingBox(zoom: Int): GmcBox {
|
||||
//TODO add box computation
|
||||
return GmcBox(position, position)
|
||||
}
|
||||
}
|
||||
|
||||
class MapCircleFeature(
|
||||
@@ -76,11 +82,10 @@ class MapBitmapImageFeature(
|
||||
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position)
|
||||
}
|
||||
|
||||
|
||||
class MapVectorImageFeature(
|
||||
val position: GeodeticMapCoordinates,
|
||||
val painter: Painter,
|
||||
val size: Size,
|
||||
val size: DpSize,
|
||||
zoomRange: IntRange = defaultZoomRange,
|
||||
) : MapFeature(zoomRange) {
|
||||
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position)
|
||||
@@ -90,6 +95,16 @@ class MapVectorImageFeature(
|
||||
fun MapVectorImageFeature(
|
||||
position: GeodeticMapCoordinates,
|
||||
image: ImageVector,
|
||||
size: Size = Size(20f, 20f),
|
||||
size: DpSize = DpSize(20.dp, 20.dp),
|
||||
zoomRange: IntRange = defaultZoomRange,
|
||||
): MapVectorImageFeature = MapVectorImageFeature(position, rememberVectorPainter(image), size, zoomRange)
|
||||
|
||||
/**
|
||||
* A group of other features
|
||||
*/
|
||||
class MapFeatureGroup(
|
||||
val children: Map<FeatureId, MapFeature>,
|
||||
zoomRange: IntRange = defaultZoomRange,
|
||||
) : MapFeature(zoomRange) {
|
||||
override fun getBoundingBox(zoom: Int): GmcBox? = children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
|
||||
}
|
||||
|
||||
@@ -10,9 +10,15 @@ import kotlin.math.log2
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
//TODO consider replacing by modifier
|
||||
data class MapViewConfig(
|
||||
val zoomSpeed: Double = 1.0 / 3.0,
|
||||
val inferViewBoxFromFeatures: Boolean = false
|
||||
val inferViewBoxFromFeatures: Boolean = false,
|
||||
val onClick: MapViewPoint.() -> Unit = {},
|
||||
val onViewChange: MapViewPoint.() -> Unit = {},
|
||||
val onSelect: (GmcBox) -> Unit = {},
|
||||
val onDrag: (MapViewPoint, MapViewPoint) -> Boolean = { _, _ -> true },
|
||||
val zoomOnSelect: Boolean = true
|
||||
)
|
||||
|
||||
@Composable
|
||||
@@ -20,8 +26,6 @@ expect fun MapView(
|
||||
mapTileProvider: MapTileProvider,
|
||||
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
|
||||
features: Map<FeatureId, MapFeature>,
|
||||
onClick: MapViewPoint.() -> Unit = {},
|
||||
//TODO consider replacing by modifier
|
||||
config: MapViewConfig = MapViewConfig(),
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
@@ -31,36 +35,48 @@ fun MapView(
|
||||
mapTileProvider: MapTileProvider,
|
||||
initialViewPoint: MapViewPoint,
|
||||
features: Map<FeatureId, MapFeature> = emptyMap(),
|
||||
onClick: MapViewPoint.() -> Unit = {},
|
||||
config: MapViewConfig = MapViewConfig(),
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
buildFeatures: @Composable (FeatureBuilder.() -> Unit) = {},
|
||||
) {
|
||||
val featuresBuilder = MapFeatureBuilder(features)
|
||||
featuresBuilder.buildFeatures()
|
||||
MapView(mapTileProvider, { initialViewPoint }, featuresBuilder.build(), onClick, config, modifier)
|
||||
MapView(
|
||||
mapTileProvider,
|
||||
{ initialViewPoint },
|
||||
featuresBuilder.build(),
|
||||
config,
|
||||
modifier
|
||||
)
|
||||
}
|
||||
|
||||
internal fun GmcBox.getComputeViewPoint(mapTileProvider: MapTileProvider): (canvasSize: DpSize) -> MapViewPoint =
|
||||
{ canvasSize ->
|
||||
val zoom = log2(
|
||||
min(
|
||||
canvasSize.width.value / width,
|
||||
canvasSize.height.value / height
|
||||
) * PI / mapTileProvider.tileSize
|
||||
)
|
||||
MapViewPoint(center, zoom)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MapView(
|
||||
mapTileProvider: MapTileProvider,
|
||||
box: GmcBox,
|
||||
features: Map<FeatureId, MapFeature> = emptyMap(),
|
||||
onClick: MapViewPoint.() -> Unit = {},
|
||||
config: MapViewConfig = MapViewConfig(),
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
buildFeatures: @Composable (FeatureBuilder.() -> Unit) = {},
|
||||
) {
|
||||
val featuresBuilder = MapFeatureBuilder(features)
|
||||
featuresBuilder.buildFeatures()
|
||||
val computeViewPoint: (canvasSize: DpSize) -> MapViewPoint = { canvasSize ->
|
||||
val zoom = log2(
|
||||
min(
|
||||
canvasSize.width.value / box.width,
|
||||
canvasSize.height.value / box.height
|
||||
) * PI / mapTileProvider.tileSize
|
||||
)
|
||||
MapViewPoint(box.center, zoom)
|
||||
}
|
||||
MapView(mapTileProvider, computeViewPoint, featuresBuilder.build(), onClick, config, modifier)
|
||||
MapView(
|
||||
mapTileProvider,
|
||||
box.getComputeViewPoint(mapTileProvider),
|
||||
featuresBuilder.build(),
|
||||
config,
|
||||
modifier
|
||||
)
|
||||
}
|
||||
@@ -48,13 +48,12 @@ private val logger = KotlinLogging.logger("MapView")
|
||||
/**
|
||||
* A component that renders map and provides basic map manipulation capabilities
|
||||
*/
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
|
||||
@Composable
|
||||
actual fun MapView(
|
||||
mapTileProvider: MapTileProvider,
|
||||
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
|
||||
features: Map<FeatureId, MapFeature>,
|
||||
onClick: MapViewPoint.() -> Unit,
|
||||
config: MapViewConfig,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
@@ -102,9 +101,12 @@ actual fun MapView(
|
||||
// Selection rectangle. If null - no selection
|
||||
var selectRect by remember { mutableStateOf<Rect?>(null) }
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
val canvasModifier = modifier.pointerInput(Unit) {
|
||||
forEachGesture {
|
||||
awaitPointerEventScope {
|
||||
fun Offset.toDpOffset() = DpOffset(x.toDp(), y.toDp())
|
||||
|
||||
val event: PointerEvent = awaitPointerEvent()
|
||||
event.changes.forEach { change ->
|
||||
if (event.buttons.isPrimaryPressed) {
|
||||
@@ -123,29 +125,40 @@ actual fun MapView(
|
||||
}
|
||||
}
|
||||
selectRect?.let { rect ->
|
||||
val (centerX, centerY) = rect.center
|
||||
val centerGmc = DpOffset(centerX.toDp(), centerY.toDp()).toGeodetic()
|
||||
|
||||
val horizontalZoom: Float = log2(canvasSize.width.toPx() / rect.width)
|
||||
val verticalZoom: Float = log2(canvasSize.height.toPx() / rect.height)
|
||||
|
||||
|
||||
viewPointOverride = MapViewPoint(
|
||||
centerGmc,
|
||||
viewPoint.zoom + min(verticalZoom, horizontalZoom)
|
||||
//Use selection override if it is defined
|
||||
val gmcBox = GmcBox(
|
||||
rect.topLeft.toDpOffset().toGeodetic(),
|
||||
rect.bottomRight.toDpOffset().toGeodetic()
|
||||
)
|
||||
config.onSelect(gmcBox)
|
||||
if (config.zoomOnSelect) {
|
||||
val newViewPoint = gmcBox.getComputeViewPoint(mapTileProvider).invoke(canvasSize)
|
||||
|
||||
config.onViewChange(newViewPoint)
|
||||
viewPointOverride = newViewPoint
|
||||
}
|
||||
selectRect = null
|
||||
}
|
||||
} else {
|
||||
val dragStart = change.position
|
||||
val dpPos = DpOffset(dragStart.x.toDp(), dragStart.y.toDp())
|
||||
onClick(MapViewPoint(dpPos.toGeodetic(), viewPoint.zoom))
|
||||
config.onClick(MapViewPoint(dpPos.toGeodetic(), viewPoint.zoom))
|
||||
drag(change.id) { dragChange ->
|
||||
val dpStartPos =
|
||||
DpOffset(dragChange.previousPosition.x.toDp(), dragChange.previousPosition.y.toDp())
|
||||
val dpEndPos = DpOffset(dragChange.position.x.toDp(), dragChange.position.y.toDp())
|
||||
if (!config.onDrag(
|
||||
MapViewPoint(dpStartPos.toGeodetic(), viewPoint.zoom),
|
||||
MapViewPoint(dpEndPos.toGeodetic(), viewPoint.zoom)
|
||||
)
|
||||
) return@drag
|
||||
val dragAmount = dragChange.position - dragChange.previousPosition
|
||||
viewPointOverride = viewPoint.move(
|
||||
val newViewPoint = viewPoint.move(
|
||||
-dragAmount.x.toDp().value / tileScale,
|
||||
+dragAmount.y.toDp().value / tileScale
|
||||
)
|
||||
config.onViewChange(newViewPoint)
|
||||
viewPointOverride = newViewPoint
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,7 +170,9 @@ actual fun MapView(
|
||||
val (xPos, yPos) = change.position
|
||||
//compute invariant point of translation
|
||||
val invariant = DpOffset(xPos.toDp(), yPos.toDp()).toGeodetic()
|
||||
viewPointOverride = viewPoint.zoom(-change.scrollDelta.y.toDouble() * config.zoomSpeed, invariant)
|
||||
val newViewPoint = viewPoint.zoom(-change.scrollDelta.y.toDouble() * config.zoomSpeed, invariant)
|
||||
config.onViewChange(newViewPoint)
|
||||
viewPointOverride = newViewPoint
|
||||
}.fillMaxSize()
|
||||
|
||||
|
||||
@@ -220,9 +235,10 @@ actual fun MapView(
|
||||
is MapBitmapImageFeature -> drawImage(feature.image, feature.position.toOffset())
|
||||
is MapVectorImageFeature -> {
|
||||
val offset = feature.position.toOffset()
|
||||
translate(offset.x - feature.size.width / 2, offset.y - feature.size.height / 2) {
|
||||
val size = feature.size.toSize()
|
||||
translate(offset.x - size.width / 2, offset.y - size.height / 2) {
|
||||
with(feature.painter) {
|
||||
draw(feature.size)
|
||||
draw(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,9 +252,16 @@ actual fun MapView(
|
||||
feature.color.toPaint()
|
||||
)
|
||||
}
|
||||
is MapCustomFeature -> drawIntoCanvas { canvas ->
|
||||
is MapDrawFeature -> {
|
||||
val offset = feature.position.toOffset()
|
||||
feature.drawFeature(this, offset)
|
||||
translate(offset.x, offset.y) {
|
||||
feature.drawFeature(this)
|
||||
}
|
||||
}
|
||||
is MapFeatureGroup -> {
|
||||
feature.children.values.forEach {
|
||||
drawFeature(zoom, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package centre.sciprog.maps
|
||||
|
||||
import kotlin.math.PI
|
||||
|
||||
fun Double.toDegrees() = this * 180 / PI
|
||||
|
||||
fun Double.toRadians() = this * PI / 180
|
||||
Reference in New Issue
Block a user