2022-07-23 10:58:16 +03:00
|
|
|
package center.sciprog.maps.compose
|
2022-07-11 09:36:43 +03:00
|
|
|
|
|
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
2022-09-13 18:27:57 +03:00
|
|
|
import androidx.compose.runtime.*
|
|
|
|
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
2022-07-11 09:36:43 +03:00
|
|
|
import androidx.compose.ui.Modifier
|
2022-08-30 10:29:56 +03:00
|
|
|
import androidx.compose.ui.input.pointer.PointerEvent
|
2022-08-31 22:48:52 +03:00
|
|
|
import androidx.compose.ui.input.pointer.isPrimaryPressed
|
2022-07-14 20:19:57 +03:00
|
|
|
import androidx.compose.ui.unit.DpSize
|
2022-07-23 11:24:37 +03:00
|
|
|
import center.sciprog.maps.coordinates.*
|
2022-07-14 20:19:57 +03:00
|
|
|
import kotlin.math.PI
|
|
|
|
import kotlin.math.log2
|
|
|
|
import kotlin.math.min
|
2022-07-11 09:36:43 +03:00
|
|
|
|
2022-07-13 11:36:02 +03:00
|
|
|
|
2022-08-30 10:29:56 +03:00
|
|
|
public fun interface DragHandle {
|
|
|
|
/**
|
|
|
|
* @param event - qualifiers of the event used for drag
|
|
|
|
* @param start - is a point where drag begins, end is a point where drag ends
|
|
|
|
* @param end - end point of the drag
|
|
|
|
*
|
|
|
|
* @return true if default event processors should be used after this one
|
|
|
|
*/
|
2022-08-31 22:48:52 +03:00
|
|
|
public fun handle(event: PointerEvent, start: MapViewPoint, end: MapViewPoint): Boolean
|
2022-08-30 10:29:56 +03:00
|
|
|
|
|
|
|
public companion object {
|
|
|
|
public val BYPASS: DragHandle = DragHandle { _, _, _ -> true }
|
2022-08-31 22:48:52 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
}
|
2022-08-30 10:29:56 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-19 12:31:09 +03:00
|
|
|
//TODO consider replacing by modifier
|
2022-07-28 16:47:14 +06:00
|
|
|
/**
|
|
|
|
*/
|
2022-07-23 13:49:47 +03:00
|
|
|
public data class MapViewConfig(
|
2022-07-13 11:36:02 +03:00
|
|
|
val zoomSpeed: Double = 1.0 / 3.0,
|
2022-08-30 10:29:56 +03:00
|
|
|
val onClick: MapViewPoint.(PointerEvent) -> Unit = {},
|
|
|
|
val dragHandle: DragHandle = DragHandle.BYPASS,
|
2022-07-19 12:31:09 +03:00
|
|
|
val onViewChange: MapViewPoint.() -> Unit = {},
|
2022-08-29 13:38:46 +03:00
|
|
|
val onSelect: (GmcRectangle) -> Unit = {},
|
2022-07-23 18:37:14 +03:00
|
|
|
val zoomOnSelect: Boolean = true,
|
2022-09-13 18:27:57 +03:00
|
|
|
val onCanvasSizeChange: (DpSize) -> Unit = {},
|
2022-07-13 11:36:02 +03:00
|
|
|
)
|
|
|
|
|
2022-07-11 09:36:43 +03:00
|
|
|
@Composable
|
2022-07-23 13:49:47 +03:00
|
|
|
public expect fun MapView(
|
2022-07-11 09:36:43 +03:00
|
|
|
mapTileProvider: MapTileProvider,
|
2022-09-13 18:27:57 +03:00
|
|
|
initialViewPoint: MapViewPoint,
|
2022-07-11 18:43:05 +03:00
|
|
|
features: Map<FeatureId, MapFeature>,
|
2022-07-13 11:36:02 +03:00
|
|
|
config: MapViewConfig = MapViewConfig(),
|
2022-07-11 18:32:36 +03:00
|
|
|
modifier: Modifier = Modifier.fillMaxSize(),
|
|
|
|
)
|
|
|
|
|
2022-09-09 21:16:54 +03:00
|
|
|
|
|
|
|
internal fun GmcRectangle.computeViewPoint(
|
|
|
|
mapTileProvider: MapTileProvider,
|
2022-09-13 13:44:38 +03:00
|
|
|
canvasSize: DpSize,
|
|
|
|
): MapViewPoint {
|
2022-09-09 21:16:54 +03:00
|
|
|
val zoom = log2(
|
|
|
|
min(
|
|
|
|
canvasSize.width.value / longitudeDelta.radians.value,
|
|
|
|
canvasSize.height.value / latitudeDelta.radians.value
|
|
|
|
) * PI / mapTileProvider.tileSize
|
|
|
|
)
|
2022-09-13 13:44:38 +03:00
|
|
|
return MapViewPoint(center, zoom)
|
2022-09-09 21:16:54 +03:00
|
|
|
}
|
|
|
|
|
2022-09-13 13:44:38 +03:00
|
|
|
/**
|
|
|
|
* Draw a map using convenient parameters. If neither [initialViewPoint], noe [initialRectangle] is defined,
|
|
|
|
* use map features to infer view region.
|
|
|
|
* @param initialViewPoint The view point of the map using center and zoom. Is used if provided
|
|
|
|
* @param initialRectangle The rectangle to be used for view point computation. Used if [initialViewPoint] is not defined.
|
|
|
|
* @param buildFeatures - a builder for features
|
|
|
|
*/
|
2022-07-11 18:32:36 +03:00
|
|
|
@Composable
|
2022-07-23 13:49:47 +03:00
|
|
|
public fun MapView(
|
2022-07-14 20:19:57 +03:00
|
|
|
mapTileProvider: MapTileProvider,
|
2022-09-09 21:16:54 +03:00
|
|
|
initialViewPoint: MapViewPoint? = null,
|
2022-09-13 13:44:38 +03:00
|
|
|
initialRectangle: GmcRectangle? = null,
|
2022-07-14 20:19:57 +03:00
|
|
|
config: MapViewConfig = MapViewConfig(),
|
|
|
|
modifier: Modifier = Modifier.fillMaxSize(),
|
2022-07-23 13:49:47 +03:00
|
|
|
buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
|
2022-07-14 20:19:57 +03:00
|
|
|
) {
|
2022-08-31 22:48:52 +03:00
|
|
|
|
2022-09-13 19:01:23 +03:00
|
|
|
var viewPointOverride by remember(initialViewPoint, initialRectangle) { mutableStateOf(initialViewPoint ?: MapViewPoint.globe) }
|
2022-09-13 18:27:57 +03:00
|
|
|
|
|
|
|
val featuresBuilder = MapFeatureBuilderImpl(mutableStateMapOf()).apply { buildFeatures() }
|
|
|
|
|
2022-09-13 19:01:23 +03:00
|
|
|
val features: SnapshotStateMap<FeatureId, MapFeature> = remember(buildFeatures) { featuresBuilder.features }
|
2022-09-13 18:27:57 +03:00
|
|
|
|
2022-09-13 19:01:23 +03:00
|
|
|
val attributes = remember(buildFeatures) { featuresBuilder.attributes }
|
2022-09-13 18:27:57 +03:00
|
|
|
|
2022-09-13 19:01:23 +03:00
|
|
|
val featureDrag by derivedStateOf {
|
2022-09-13 18:27:57 +03:00
|
|
|
DragHandle.withPrimaryButton { _, start, end ->
|
|
|
|
val zoom = start.zoom
|
|
|
|
attributes.filterValues {
|
|
|
|
it[DraggableAttribute] ?: false
|
|
|
|
}.keys.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
|
|
|
|
}
|
2022-08-31 22:48:52 +03:00
|
|
|
}
|
|
|
|
|
2022-09-13 18:27:57 +03:00
|
|
|
val newConfig = config.copy(
|
|
|
|
dragHandle = DragHandle.combine(featureDrag, config.dragHandle),
|
|
|
|
onCanvasSizeChange = { canvasSize ->
|
|
|
|
viewPointOverride = initialViewPoint
|
2022-09-13 13:44:38 +03:00
|
|
|
?: initialRectangle?.computeViewPoint(mapTileProvider, canvasSize)
|
2022-09-13 18:27:57 +03:00
|
|
|
?: features.values.computeBoundingBox(1.0)?.computeViewPoint(mapTileProvider, canvasSize)
|
|
|
|
?: MapViewPoint.globe
|
|
|
|
|
|
|
|
config.onCanvasSizeChange(canvasSize)
|
|
|
|
}
|
2022-07-19 12:31:09 +03:00
|
|
|
)
|
|
|
|
|
2022-09-13 18:27:57 +03:00
|
|
|
MapView(
|
|
|
|
mapTileProvider = mapTileProvider,
|
|
|
|
initialViewPoint = viewPointOverride,
|
|
|
|
features = features,
|
|
|
|
config = newConfig,
|
|
|
|
modifier = modifier,
|
|
|
|
)
|
|
|
|
}
|