Drag done
This commit is contained in:
parent
5a1d3d701f
commit
491a4e6000
@ -7,7 +7,8 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PointMode
|
||||
import androidx.compose.ui.input.pointer.isPrimaryPressed
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import center.sciprog.maps.compose.*
|
||||
@ -51,32 +52,24 @@ fun App() {
|
||||
|
||||
|
||||
val pointOne = 55.568548 to 37.568604
|
||||
var pointTwo: Pair<Double, Double> by remember { mutableStateOf(55.929444 to 37.518434) }
|
||||
val pointTwo = 55.929444 to 37.518434
|
||||
val pointThree = 60.929444 to 37.518434
|
||||
|
||||
val dragPoint = 55.744 to 37.614
|
||||
|
||||
MapView(
|
||||
mapTileProvider = mapTileProvider,
|
||||
initialViewPoint = viewPoint,
|
||||
config = MapViewConfig(
|
||||
inferViewBoxFromFeatures = true,
|
||||
onViewChange = { centerCoordinates = focus },
|
||||
dragHandle = { event, start, end ->
|
||||
if (!event.buttons.isPrimaryPressed) {
|
||||
true
|
||||
} else if (start.focus.latitude.degrees.value in (pointTwo.first - 0.05)..(pointTwo.first + 0.05) &&
|
||||
start.focus.longitude.degrees.value in (pointTwo.second - 0.05)..(pointTwo.second + 0.05)
|
||||
) {
|
||||
pointTwo = pointTwo.first + (end.focus.latitude - start.focus.latitude).degrees.value to
|
||||
pointTwo.second + (end.focus.longitude - start.focus.longitude).degrees.value
|
||||
false// returning false, because when we are dragging circle we don't want to drag map
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
)
|
||||
) {
|
||||
|
||||
image(pointOne, Icons.Filled.Home)
|
||||
|
||||
rectangle(dragPoint, id = "dragMe", size = DpSize(10.dp, 10.dp)).draggable()
|
||||
|
||||
points(
|
||||
points = listOf(
|
||||
55.742465 to 37.615812,
|
||||
@ -89,7 +82,7 @@ fun App() {
|
||||
pointMode = PointMode.Polygon
|
||||
)
|
||||
|
||||
//remember feature Id
|
||||
//remember feature ID
|
||||
val circleId: FeatureId = circle(
|
||||
centerCoordinates = pointTwo,
|
||||
)
|
||||
|
@ -11,16 +11,20 @@ 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 center.sciprog.maps.coordinates.GeodeticMapCoordinates
|
||||
import center.sciprog.maps.coordinates.GmcRectangle
|
||||
import center.sciprog.maps.coordinates.wrapAll
|
||||
import center.sciprog.maps.coordinates.*
|
||||
import kotlin.math.floor
|
||||
|
||||
public interface MapFeature {
|
||||
public val zoomRange: IntRange
|
||||
public fun getBoundingBox(zoom: Int): GmcRectangle?
|
||||
public fun getBoundingBox(zoom: Double): GmcRectangle?
|
||||
|
||||
}
|
||||
|
||||
public fun Iterable<MapFeature>.computeBoundingBox(zoom: Int): GmcRectangle? =
|
||||
public interface DraggableMapFeature : MapFeature {
|
||||
public fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature
|
||||
}
|
||||
|
||||
public fun Iterable<MapFeature>.computeBoundingBox(zoom: Double): GmcRectangle? =
|
||||
mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
|
||||
|
||||
internal fun Pair<Double, Double>.toCoordinates() = GeodeticMapCoordinates.ofDegrees(first, second)
|
||||
@ -35,18 +39,21 @@ public class MapFeatureSelector(
|
||||
) : MapFeature {
|
||||
override val zoomRange: IntRange get() = defaultZoomRange
|
||||
|
||||
override fun getBoundingBox(zoom: Int): GmcRectangle? = selector(zoom).getBoundingBox(zoom)
|
||||
override fun getBoundingBox(zoom: Double): GmcRectangle? = selector(floor(zoom).toInt()).getBoundingBox(zoom)
|
||||
}
|
||||
|
||||
public class MapDrawFeature(
|
||||
public val position: GeodeticMapCoordinates,
|
||||
override val zoomRange: IntRange = defaultZoomRange,
|
||||
public val drawFeature: DrawScope.() -> Unit,
|
||||
) : MapFeature {
|
||||
override fun getBoundingBox(zoom: Int): GmcRectangle {
|
||||
) : DraggableMapFeature {
|
||||
override fun getBoundingBox(zoom: Double): GmcRectangle {
|
||||
//TODO add box computation
|
||||
return GmcRectangle(position, position)
|
||||
}
|
||||
|
||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
||||
MapDrawFeature(newCoordinates, zoomRange, drawFeature)
|
||||
}
|
||||
|
||||
public class MapPointsFeature(
|
||||
@ -54,9 +61,9 @@ public class MapPointsFeature(
|
||||
override val zoomRange: IntRange = defaultZoomRange,
|
||||
public val stroke: Float = 2f,
|
||||
public val color: Color = Color.Red,
|
||||
public val pointMode: PointMode = PointMode.Points
|
||||
public val pointMode: PointMode = PointMode.Points,
|
||||
) : MapFeature {
|
||||
override fun getBoundingBox(zoom: Int): GmcRectangle {
|
||||
override fun getBoundingBox(zoom: Double): GmcRectangle {
|
||||
return GmcRectangle(points.first(), points.last())
|
||||
}
|
||||
}
|
||||
@ -66,8 +73,14 @@ public class MapCircleFeature(
|
||||
override val zoomRange: IntRange = defaultZoomRange,
|
||||
public val size: Float = 5f,
|
||||
public val color: Color = Color.Red,
|
||||
) : MapFeature {
|
||||
override fun getBoundingBox(zoom: Int): GmcRectangle = GmcRectangle(center, center)
|
||||
) : DraggableMapFeature {
|
||||
override fun getBoundingBox(zoom: Double): GmcRectangle {
|
||||
val scale = WebMercatorProjection.scaleFactor(zoom)
|
||||
return GmcRectangle.square(center, (size/scale).radians, (size/scale).radians)
|
||||
}
|
||||
|
||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
||||
MapCircleFeature(newCoordinates, zoomRange, size, color)
|
||||
}
|
||||
|
||||
public class MapRectangleFeature(
|
||||
@ -75,8 +88,14 @@ public class MapRectangleFeature(
|
||||
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): GmcRectangle = GmcRectangle(center, center)
|
||||
) : DraggableMapFeature {
|
||||
override fun getBoundingBox(zoom: Double): GmcRectangle {
|
||||
val scale = WebMercatorProjection.scaleFactor(zoom)
|
||||
return GmcRectangle.square(center, (size.height.value/scale).radians, (size.width.value/scale).radians)
|
||||
}
|
||||
|
||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
||||
MapRectangleFeature(newCoordinates, zoomRange, size, color)
|
||||
}
|
||||
|
||||
public class MapLineFeature(
|
||||
@ -85,7 +104,7 @@ public class MapLineFeature(
|
||||
override val zoomRange: IntRange = defaultZoomRange,
|
||||
public val color: Color = Color.Red,
|
||||
) : MapFeature {
|
||||
override fun getBoundingBox(zoom: Int): GmcRectangle = GmcRectangle(a, b)
|
||||
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(a, b)
|
||||
}
|
||||
|
||||
public class MapArcFeature(
|
||||
@ -95,7 +114,7 @@ public class MapArcFeature(
|
||||
override val zoomRange: IntRange = defaultZoomRange,
|
||||
public val color: Color = Color.Red,
|
||||
) : MapFeature {
|
||||
override fun getBoundingBox(zoom: Int): GmcRectangle = oval
|
||||
override fun getBoundingBox(zoom: Double): GmcRectangle = oval
|
||||
}
|
||||
|
||||
public class MapBitmapImageFeature(
|
||||
@ -103,8 +122,11 @@ public class MapBitmapImageFeature(
|
||||
public val image: ImageBitmap,
|
||||
public val size: IntSize = IntSize(15, 15),
|
||||
override val zoomRange: IntRange = defaultZoomRange,
|
||||
) : MapFeature {
|
||||
override fun getBoundingBox(zoom: Int): GmcRectangle = GmcRectangle(position, position)
|
||||
) : DraggableMapFeature {
|
||||
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position)
|
||||
|
||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
||||
MapBitmapImageFeature(newCoordinates, image, size, zoomRange)
|
||||
}
|
||||
|
||||
public class MapVectorImageFeature(
|
||||
@ -112,8 +134,11 @@ public class MapVectorImageFeature(
|
||||
public val painter: Painter,
|
||||
public val size: DpSize,
|
||||
override val zoomRange: IntRange = defaultZoomRange,
|
||||
) : MapFeature {
|
||||
override fun getBoundingBox(zoom: Int): GmcRectangle = GmcRectangle(position, position)
|
||||
) : DraggableMapFeature{
|
||||
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position)
|
||||
|
||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
||||
MapVectorImageFeature(newCoordinates,painter, size, zoomRange)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ -131,7 +156,8 @@ public class MapFeatureGroup(
|
||||
public val children: Map<FeatureId, MapFeature>,
|
||||
override val zoomRange: IntRange = defaultZoomRange,
|
||||
) : MapFeature {
|
||||
override fun getBoundingBox(zoom: Int): GmcRectangle? = children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
|
||||
override fun getBoundingBox(zoom: Double): GmcRectangle? =
|
||||
children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
|
||||
}
|
||||
|
||||
public class MapTextFeature(
|
||||
@ -140,6 +166,9 @@ public class MapTextFeature(
|
||||
override val zoomRange: IntRange = defaultZoomRange,
|
||||
public val color: Color,
|
||||
public val fontConfig: MapTextFeatureFont.() -> Unit,
|
||||
) : MapFeature {
|
||||
override fun getBoundingBox(zoom: Int): GmcRectangle = GmcRectangle(position, position)
|
||||
) : DraggableMapFeature{
|
||||
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position)
|
||||
|
||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
||||
MapTextFeature(newCoordinates, text, zoomRange, color, fontConfig)
|
||||
}
|
||||
|
@ -15,27 +15,53 @@ import center.sciprog.maps.coordinates.GmcRectangle
|
||||
|
||||
public typealias FeatureId = String
|
||||
|
||||
public interface MapFeatureAttributeKey<T>
|
||||
|
||||
|
||||
public class MapFeatureAttributeSet(private val map: Map<MapFeatureAttributeKey<*>, *>) {
|
||||
public operator fun <T> get(key: MapFeatureAttributeKey<*>): T? = map[key]?.let {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
it as T
|
||||
}
|
||||
}
|
||||
|
||||
public interface MapFeatureBuilder {
|
||||
public fun addFeature(id: FeatureId?, feature: MapFeature): FeatureId
|
||||
|
||||
public fun build(): SnapshotStateMap<FeatureId, MapFeature>
|
||||
public fun <T> setAttribute(id: FeatureId, key: MapFeatureAttributeKey<T>, value: T)
|
||||
|
||||
public val features: MutableMap<FeatureId, MapFeature>
|
||||
|
||||
public fun attributes(): Map<FeatureId, MapFeatureAttributeSet>
|
||||
|
||||
//TODO use context receiver for that
|
||||
public fun FeatureId.draggable(enabled: Boolean = true) {
|
||||
setAttribute(this, DraggableAttribute, enabled)
|
||||
}
|
||||
}
|
||||
|
||||
internal class MapFeatureBuilderImpl(initialFeatures: Map<FeatureId, MapFeature>) : MapFeatureBuilder {
|
||||
internal class MapFeatureBuilderImpl(
|
||||
override val features: SnapshotStateMap<FeatureId, MapFeature>,
|
||||
) : MapFeatureBuilder {
|
||||
|
||||
private val attributes = SnapshotStateMap<FeatureId, SnapshotStateMap<MapFeatureAttributeKey<out Any?>, in Any?>>()
|
||||
|
||||
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 {
|
||||
val safeId = id ?: generateID(feature)
|
||||
content[id ?: generateID(feature)] = feature
|
||||
features[id ?: generateID(feature)] = feature
|
||||
return safeId
|
||||
}
|
||||
|
||||
override fun build(): SnapshotStateMap<FeatureId, MapFeature> = content
|
||||
override fun <T> setAttribute(id: FeatureId, key: MapFeatureAttributeKey<T>, value: T) {
|
||||
attributes.getOrPut(id) { SnapshotStateMap() }[key] = value
|
||||
}
|
||||
|
||||
override fun attributes(): Map<FeatureId, MapFeatureAttributeSet> =
|
||||
attributes.mapValues { MapFeatureAttributeSet(it.value) }
|
||||
|
||||
}
|
||||
|
||||
public fun MapFeatureBuilder.circle(
|
||||
@ -123,7 +149,7 @@ public fun MapFeatureBuilder.points(
|
||||
stroke: Float = 2f,
|
||||
color: Color = Color.Red,
|
||||
pointMode: PointMode = PointMode.Points,
|
||||
id: FeatureId? = null
|
||||
id: FeatureId? = null,
|
||||
): FeatureId = addFeature(id, MapPointsFeature(points.map { it.toCoordinates() }, zoomRange, stroke, color, pointMode))
|
||||
|
||||
@Composable
|
||||
@ -140,7 +166,7 @@ public fun MapFeatureBuilder.group(
|
||||
id: FeatureId? = null,
|
||||
builder: MapFeatureBuilder.() -> Unit,
|
||||
): FeatureId {
|
||||
val map = MapFeatureBuilderImpl(emptyMap()).apply(builder).build()
|
||||
val map = MapFeatureBuilderImpl(mutableStateMapOf()).apply(builder).features
|
||||
val feature = MapFeatureGroup(map, zoomRange)
|
||||
return addFeature(id, feature)
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
package center.sciprog.maps.compose
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.PointerEvent
|
||||
import androidx.compose.ui.input.pointer.isPrimaryPressed
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import center.sciprog.maps.coordinates.*
|
||||
import kotlin.math.PI
|
||||
@ -19,10 +20,33 @@ public fun interface DragHandle {
|
||||
*
|
||||
* @return true if default event processors should be used after this one
|
||||
*/
|
||||
public fun drag(event: PointerEvent, start: MapViewPoint, end: MapViewPoint): Boolean
|
||||
public fun handle(event: PointerEvent, start: MapViewPoint, end: MapViewPoint): Boolean
|
||||
|
||||
public companion object {
|
||||
public val BYPASS: DragHandle = DragHandle { _, _, _ -> true }
|
||||
|
||||
/**
|
||||
* Process only events with primary button pressed
|
||||
*/
|
||||
public fun withPrimaryButton(
|
||||
block: (event: PointerEvent, start: MapViewPoint, end: MapViewPoint) -> Boolean,
|
||||
): DragHandle = DragHandle { event, start, end ->
|
||||
if (event.buttons.isPrimaryPressed) {
|
||||
block(event, start, end)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine several handles into one
|
||||
*/
|
||||
public fun combine(vararg handles: DragHandle): DragHandle = DragHandle { event, start, end ->
|
||||
handles.forEach {
|
||||
if (!it.handle(event, start, end)) return@DragHandle false
|
||||
}
|
||||
return@DragHandle true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,53 +73,83 @@ public expect fun MapView(
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
|
||||
private fun prepareConfig(initialConfig: MapViewConfig, featureBuilder: MapFeatureBuilder): MapViewConfig {
|
||||
val draggableFeatureIds: Set<FeatureId> = featureBuilder.attributes().filterValues {
|
||||
it[DraggableAttribute] ?: false
|
||||
}.keys
|
||||
|
||||
val features = featureBuilder.features
|
||||
|
||||
val featureDrag = DragHandle.withPrimaryButton { _, start, end ->
|
||||
val zoom = start.zoom
|
||||
draggableFeatureIds.forEach { id ->
|
||||
val feature = features[id] as? DraggableMapFeature ?: return@forEach
|
||||
//val border = WebMercatorProjection.scaleFactor(zoom)
|
||||
val boundingBox = feature.getBoundingBox(zoom) ?: return@forEach
|
||||
if (start.focus in boundingBox) {
|
||||
features[id] = feature.withCoordinates(end.focus)
|
||||
return@withPrimaryButton false
|
||||
}
|
||||
}
|
||||
return@withPrimaryButton true
|
||||
}
|
||||
return initialConfig.copy(
|
||||
dragHandle = DragHandle.combine(featureDrag, initialConfig.dragHandle),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
public fun MapView(
|
||||
mapTileProvider: MapTileProvider,
|
||||
initialViewPoint: MapViewPoint,
|
||||
features: Map<FeatureId, MapFeature> = emptyMap(),
|
||||
config: MapViewConfig = MapViewConfig(),
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
|
||||
) {
|
||||
val featuresBuilder = MapFeatureBuilderImpl(features)
|
||||
val featuresBuilder = MapFeatureBuilderImpl(mutableStateMapOf())
|
||||
featuresBuilder.buildFeatures()
|
||||
val features = remember { featuresBuilder.features }
|
||||
|
||||
val newConfig = remember(features) {
|
||||
prepareConfig(config, featuresBuilder)
|
||||
}
|
||||
|
||||
MapView(
|
||||
mapTileProvider,
|
||||
{ initialViewPoint },
|
||||
featuresBuilder.build(),
|
||||
config,
|
||||
features,
|
||||
newConfig,
|
||||
modifier
|
||||
)
|
||||
}
|
||||
|
||||
internal fun GmcRectangle.computeViewPoint(mapTileProvider: MapTileProvider): (canvasSize: DpSize) -> MapViewPoint =
|
||||
{ canvasSize ->
|
||||
val zoom = log2(
|
||||
min(
|
||||
canvasSize.width.value / longitudeDelta.radians.value,
|
||||
canvasSize.height.value / latitudeDelta.radians.value
|
||||
) * PI / mapTileProvider.tileSize
|
||||
)
|
||||
MapViewPoint(center, zoom)
|
||||
}
|
||||
|
||||
@Composable
|
||||
public fun MapView(
|
||||
internal fun GmcRectangle.computeViewPoint(
|
||||
mapTileProvider: MapTileProvider,
|
||||
box: GmcRectangle,
|
||||
features: Map<FeatureId, MapFeature> = emptyMap(),
|
||||
config: MapViewConfig = MapViewConfig(),
|
||||
modifier: Modifier = Modifier.fillMaxSize(),
|
||||
buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
|
||||
) {
|
||||
val featuresBuilder = MapFeatureBuilderImpl(features)
|
||||
featuresBuilder.buildFeatures()
|
||||
MapView(
|
||||
mapTileProvider,
|
||||
box.computeViewPoint(mapTileProvider),
|
||||
featuresBuilder.build(),
|
||||
config,
|
||||
modifier
|
||||
): (canvasSize: DpSize) -> MapViewPoint = { canvasSize ->
|
||||
val zoom = log2(
|
||||
min(
|
||||
canvasSize.width.value / longitudeDelta.radians.value,
|
||||
canvasSize.height.value / latitudeDelta.radians.value
|
||||
) * PI / mapTileProvider.tileSize
|
||||
)
|
||||
MapViewPoint(center, zoom)
|
||||
}
|
||||
//
|
||||
//@Composable
|
||||
//public fun MapView(
|
||||
// mapTileProvider: MapTileProvider,
|
||||
// box: GmcRectangle,
|
||||
// config: MapViewConfig = MapViewConfig(),
|
||||
// modifier: Modifier = Modifier.fillMaxSize(),
|
||||
// buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
|
||||
//) {
|
||||
// val builder by derivedStateOf { MapFeatureBuilderImpl().apply(buildFeatures) }
|
||||
//
|
||||
// MapView(
|
||||
// mapTileProvider,
|
||||
// box.computeViewPoint(mapTileProvider),
|
||||
// builder.features,
|
||||
// prepareConfig(config, builder),
|
||||
// modifier
|
||||
// )
|
||||
//}
|
@ -0,0 +1,3 @@
|
||||
package center.sciprog.maps.compose
|
||||
|
||||
public object DraggableAttribute: MapFeatureAttributeKey<Boolean>
|
@ -5,6 +5,7 @@ import androidx.compose.foundation.gestures.drag
|
||||
import androidx.compose.foundation.gestures.forEachGesture
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
@ -66,7 +67,7 @@ public actual fun MapView(
|
||||
|
||||
val viewPoint: MapViewPoint by derivedStateOf {
|
||||
viewPointInternal ?: if (config.inferViewBoxFromFeatures) {
|
||||
features.values.computeBoundingBox(1)?.let { box ->
|
||||
features.values.computeBoundingBox(1.0)?.let { box ->
|
||||
val zoom = log2(
|
||||
min(
|
||||
canvasSize.width.value / box.longitudeDelta.radians.value,
|
||||
@ -132,8 +133,9 @@ public actual fun MapView(
|
||||
)
|
||||
val dpEnd = DpOffset(dragChange.position.x.toDp(), dragChange.position.y.toDp())
|
||||
|
||||
//apply drag handle and check if it prohibits the drag even propagation
|
||||
if (
|
||||
!config.dragHandle.drag(
|
||||
!config.dragHandle.handle(
|
||||
event,
|
||||
MapViewPoint(dpStart.toGeodetic(), viewPoint.zoom),
|
||||
MapViewPoint(dpEnd.toGeodetic(), viewPoint.zoom)
|
||||
|
@ -50,7 +50,7 @@ public class GeodeticMapCoordinates(
|
||||
/**
|
||||
* Short name for GeodeticMapCoordinates
|
||||
*/
|
||||
public typealias GMC = GeodeticMapCoordinates
|
||||
public typealias Gmc = GeodeticMapCoordinates
|
||||
|
||||
//public interface GeoToScreenConversion {
|
||||
// public fun getScreenX(gmc: GeodeticMapCoordinates): Double
|
||||
|
@ -57,8 +57,8 @@ public fun GeoEllipsoid.meridianCurve(
|
||||
}
|
||||
|
||||
return GmcCurve(
|
||||
forward = GmcPose(GMC(fromLatitude, longitude), if (up) zero else pi),
|
||||
backward = GmcPose(GMC(toLatitude, longitude), if (up) pi else zero),
|
||||
forward = GmcPose(Gmc(fromLatitude, longitude), if (up) zero else pi),
|
||||
backward = GmcPose(Gmc(toLatitude, longitude), if (up) pi else zero),
|
||||
distance = s
|
||||
)
|
||||
}
|
||||
@ -70,8 +70,8 @@ public fun GeoEllipsoid.parallelCurve(latitude: Angle, fromLongitude: Angle, toL
|
||||
require(latitude in (-piDiv2)..(piDiv2)) { "Latitude must be in (-90, 90) degrees range" }
|
||||
val right = toLongitude > fromLongitude
|
||||
return GmcCurve(
|
||||
forward = GmcPose(GMC(latitude, fromLongitude), if (right) piDiv2.radians else -piDiv2.radians),
|
||||
backward = GmcPose(GMC(latitude, toLongitude), if (right) -piDiv2.radians else piDiv2.radians),
|
||||
forward = GmcPose(Gmc(latitude, fromLongitude), if (right) piDiv2.radians else -piDiv2.radians),
|
||||
backward = GmcPose(Gmc(latitude, toLongitude), if (right) -piDiv2.radians else piDiv2.radians),
|
||||
distance = reducedRadius(latitude) * abs((fromLongitude - toLongitude).radians.value)
|
||||
)
|
||||
}
|
||||
@ -186,7 +186,7 @@ public fun GeoEllipsoid.curveInDirection(
|
||||
val L = lambda - (1 - C) * f * sinAlpha *
|
||||
(sigma.value + C * sinSigma * (cosSigmaM2 + C * cosSigma * (-1 + 2 * cos2SigmaM2)))
|
||||
|
||||
val endPoint = GMC(phi2, L.radians)
|
||||
val endPoint = Gmc(phi2, L.radians)
|
||||
|
||||
// eq. 12
|
||||
|
||||
@ -212,7 +212,7 @@ public fun GeoEllipsoid.curveInDirection(
|
||||
* @return solution to the inverse geodetic problem
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection", "LocalVariableName")
|
||||
public fun GeoEllipsoid.curveBetween(start: GMC, end: GMC, precision: Double = 1e-6): GmcCurve {
|
||||
public fun GeoEllipsoid.curveBetween(start: Gmc, end: Gmc, precision: Double = 1e-6): GmcCurve {
|
||||
//
|
||||
// All equation numbers refer back to Vincenty's publication:
|
||||
// See http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf
|
||||
|
@ -9,29 +9,39 @@ package center.sciprog.maps.coordinates
|
||||
public data class GmcRectangle(
|
||||
public val a: GeodeticMapCoordinates,
|
||||
public val b: GeodeticMapCoordinates,
|
||||
public val ellipsoid: GeoEllipsoid = GeoEllipsoid.WGS84,
|
||||
) {
|
||||
public companion object {
|
||||
|
||||
/**
|
||||
* A quasi-square section.
|
||||
*/
|
||||
public fun square(
|
||||
center: GeodeticMapCoordinates,
|
||||
height: Angle,
|
||||
width: Angle,
|
||||
): GmcRectangle {
|
||||
val a = GeodeticMapCoordinates(
|
||||
center.latitude - (height / 2),
|
||||
center.longitude - (width / 2)
|
||||
)
|
||||
val b = GeodeticMapCoordinates(
|
||||
center.latitude + (height / 2),
|
||||
center.longitude + (width / 2)
|
||||
)
|
||||
return GmcRectangle(a, b)
|
||||
}
|
||||
|
||||
/**
|
||||
* A quasi-square section. Note that latitudinal distance could be imprecise for large distances
|
||||
*/
|
||||
public fun square(
|
||||
center: GeodeticMapCoordinates,
|
||||
width: Distance,
|
||||
height: Distance,
|
||||
width: Distance,
|
||||
ellipsoid: GeoEllipsoid = GeoEllipsoid.WGS84,
|
||||
): GmcRectangle {
|
||||
val reducedRadius = ellipsoid.reducedRadius(center.latitude)
|
||||
val a = GeodeticMapCoordinates(
|
||||
center.latitude - (height / ellipsoid.polarRadius / 2).radians,
|
||||
center.longitude - (width / reducedRadius / 2).radians
|
||||
)
|
||||
val b = GeodeticMapCoordinates(
|
||||
center.latitude + (height / ellipsoid.polarRadius / 2).radians,
|
||||
center.longitude + (width / reducedRadius / 2).radians
|
||||
)
|
||||
return GmcRectangle(a, b, ellipsoid)
|
||||
return square(center, (height / ellipsoid.polarRadius).radians, (width / reducedRadius).radians)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -68,6 +78,30 @@ public val GmcRectangle.latitudeDelta: Angle get() = abs(a.latitude - b.latitude
|
||||
public val GmcRectangle.topLeft: GeodeticMapCoordinates get() = GeodeticMapCoordinates(top, left)
|
||||
public val GmcRectangle.bottomRight: GeodeticMapCoordinates get() = GeodeticMapCoordinates(bottom, right)
|
||||
|
||||
//public fun GmcRectangle.enlarge(
|
||||
// top: Distance,
|
||||
// bottom: Distance = top,
|
||||
// left: Distance = top,
|
||||
// right: Distance = left,
|
||||
//): GmcRectangle {
|
||||
//
|
||||
//}
|
||||
//
|
||||
//public fun GmcRectangle.enlarge(
|
||||
// top: Angle,
|
||||
// bottom: Angle = top,
|
||||
// left: Angle = top,
|
||||
// right: Angle = left,
|
||||
//): GmcRectangle {
|
||||
//
|
||||
//}
|
||||
|
||||
/**
|
||||
* Check if coordinate is inside the box
|
||||
*/
|
||||
public operator fun GmcRectangle.contains(coordinate: Gmc): Boolean =
|
||||
coordinate.latitude in (bottom..top) && coordinate.longitude in (left..right)
|
||||
|
||||
/**
|
||||
* Compute a minimal bounding box including all given boxes. Return null if collection is empty
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user