Feature builder. Color modification example
This commit is contained in:
parent
92abd3db43
commit
ed4b603077
@ -0,0 +1,63 @@
|
|||||||
|
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.Size
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
|
||||||
|
typealias FeatureId = String
|
||||||
|
|
||||||
|
interface FeatureBuilder {
|
||||||
|
fun addFeature(id: FeatureId?, feature: MapFeature): FeatureId
|
||||||
|
|
||||||
|
fun build(): SnapshotStateMap<FeatureId, MapFeature>
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class MapFeatureBuilder(private val content: SnapshotStateMap<FeatureId, MapFeature> = mutableStateMapOf()) : FeatureBuilder {
|
||||||
|
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
|
||||||
|
return safeId
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun build(): SnapshotStateMap<FeatureId, MapFeature> = content
|
||||||
|
}
|
||||||
|
|
||||||
|
fun FeatureBuilder.circle(
|
||||||
|
centerCoordinates: Pair<Double, Double>,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
size: Float = 5f,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
id: FeatureId? = null,
|
||||||
|
) = addFeature(
|
||||||
|
id, MapCircleFeature(centerCoordinates.toCoordinates(), zoomRange, size, color)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun FeatureBuilder.line(
|
||||||
|
aCoordinates: Pair<Double, Double>,
|
||||||
|
bCoordinates: Pair<Double, Double>,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
id: FeatureId? = null,
|
||||||
|
) = addFeature(id, MapLineFeature(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), zoomRange, color))
|
||||||
|
|
||||||
|
fun FeatureBuilder.text(
|
||||||
|
position: Pair<Double, Double>,
|
||||||
|
text: String,
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
color: Color = Color.Red,
|
||||||
|
id: FeatureId? = null,
|
||||||
|
) = addFeature(id, MapTextFeature(position.toCoordinates(), text, zoomRange, color))
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FeatureBuilder.image(
|
||||||
|
position: Pair<Double, Double>,
|
||||||
|
image: ImageVector,
|
||||||
|
size: Size = Size(20f, 20f),
|
||||||
|
zoomRange: IntRange = defaultZoomRange,
|
||||||
|
id: FeatureId? = null,
|
||||||
|
) = addFeature(id, MapVectorImageFeature(position.toCoordinates(), image, size, zoomRange))
|
@ -15,7 +15,7 @@ sealed class MapFeature(val zoomRange: IntRange)
|
|||||||
|
|
||||||
internal fun Pair<Double, Double>.toCoordinates() = GeodeticMapCoordinates.ofDegrees(first, second)
|
internal fun Pair<Double, Double>.toCoordinates() = GeodeticMapCoordinates.ofDegrees(first, second)
|
||||||
|
|
||||||
private val defaultZoomRange = 1..18
|
internal val defaultZoomRange = 1..18
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A feature that decides what to show depending on the zoom value (it could change size of shape)
|
* A feature that decides what to show depending on the zoom value (it could change size of shape)
|
||||||
@ -29,18 +29,6 @@ class MapCircleFeature(
|
|||||||
val color: Color = Color.Red,
|
val color: Color = Color.Red,
|
||||||
) : MapFeature(zoomRange)
|
) : MapFeature(zoomRange)
|
||||||
|
|
||||||
fun MapCircleFeature(
|
|
||||||
centerCoordinates: Pair<Double, Double>,
|
|
||||||
zoomRange: IntRange = defaultZoomRange,
|
|
||||||
size: Float = 5f,
|
|
||||||
color: Color = Color.Red,
|
|
||||||
) = MapCircleFeature(
|
|
||||||
centerCoordinates.toCoordinates(),
|
|
||||||
zoomRange,
|
|
||||||
size,
|
|
||||||
color
|
|
||||||
)
|
|
||||||
|
|
||||||
class MapLineFeature(
|
class MapLineFeature(
|
||||||
val a: GeodeticMapCoordinates,
|
val a: GeodeticMapCoordinates,
|
||||||
val b: GeodeticMapCoordinates,
|
val b: GeodeticMapCoordinates,
|
||||||
@ -48,13 +36,6 @@ class MapLineFeature(
|
|||||||
val color: Color = Color.Red,
|
val color: Color = Color.Red,
|
||||||
) : MapFeature(zoomRange)
|
) : MapFeature(zoomRange)
|
||||||
|
|
||||||
fun MapLineFeature(
|
|
||||||
aCoordinates: Pair<Double, Double>,
|
|
||||||
bCoordinates: Pair<Double, Double>,
|
|
||||||
zoomRange: IntRange = defaultZoomRange,
|
|
||||||
color: Color = Color.Red,
|
|
||||||
) = MapLineFeature(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), zoomRange, color)
|
|
||||||
|
|
||||||
class MapTextFeature(
|
class MapTextFeature(
|
||||||
val position: GeodeticMapCoordinates,
|
val position: GeodeticMapCoordinates,
|
||||||
val text: String,
|
val text: String,
|
||||||
|
@ -2,6 +2,8 @@ package centre.sciprog.maps.compose
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
|
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import centre.sciprog.maps.GeodeticMapCoordinates
|
import centre.sciprog.maps.GeodeticMapCoordinates
|
||||||
import centre.sciprog.maps.MapViewPoint
|
import centre.sciprog.maps.MapViewPoint
|
||||||
@ -10,7 +12,20 @@ import centre.sciprog.maps.MapViewPoint
|
|||||||
expect fun MapView(
|
expect fun MapView(
|
||||||
initialViewPoint: MapViewPoint,
|
initialViewPoint: MapViewPoint,
|
||||||
mapTileProvider: MapTileProvider,
|
mapTileProvider: MapTileProvider,
|
||||||
features: Collection<MapFeature> = emptyList(),
|
features: SnapshotStateMap<FeatureId, MapFeature> = mutableStateMapOf(),
|
||||||
|
onClick: (GeodeticMapCoordinates) -> Unit = {},
|
||||||
|
modifier: Modifier = Modifier.fillMaxSize(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MapView(
|
||||||
|
initialViewPoint: MapViewPoint,
|
||||||
|
mapTileProvider: MapTileProvider,
|
||||||
modifier: Modifier = Modifier.fillMaxSize(),
|
modifier: Modifier = Modifier.fillMaxSize(),
|
||||||
onClick: (GeodeticMapCoordinates) -> Unit = {},
|
onClick: (GeodeticMapCoordinates) -> Unit = {},
|
||||||
)
|
addFeatures: @Composable FeatureBuilder.() -> Unit,
|
||||||
|
) {
|
||||||
|
val featuresBuilder = MapFeatureBuilder()
|
||||||
|
featuresBuilder.addFeatures()
|
||||||
|
MapView(initialViewPoint, mapTileProvider, featuresBuilder.build(), onClick, modifier)
|
||||||
|
}
|
@ -6,6 +6,7 @@ import androidx.compose.material.Text
|
|||||||
import androidx.compose.material.icons.Icons
|
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.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 centre.sciprog.maps.GeodeticMapCoordinates
|
import centre.sciprog.maps.GeodeticMapCoordinates
|
||||||
@ -14,24 +15,11 @@ import centre.sciprog.maps.compose.*
|
|||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.engine.cio.CIO
|
import io.ktor.client.engine.cio.CIO
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
/**
|
|
||||||
* initial set of features
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
private fun initialFeatures() = buildList {
|
|
||||||
val pointOne = 55.568548 to 37.568604
|
|
||||||
val pointTwo = 55.929444 to 37.518434
|
|
||||||
add(MapVectorImageFeature(pointOne.toCoordinates(), Icons.Filled.Home))
|
|
||||||
// add(MapCircleFeature(pointOne))
|
|
||||||
add(MapCircleFeature(pointTwo))
|
|
||||||
add(MapLineFeature(pointOne, pointTwo))
|
|
||||||
add(MapTextFeature(pointOne.toCoordinates(), "Home"))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun App() {
|
fun App() {
|
||||||
@ -40,24 +28,10 @@ fun App() {
|
|||||||
val viewPoint = remember {
|
val viewPoint = remember {
|
||||||
MapViewPoint(
|
MapViewPoint(
|
||||||
GeodeticMapCoordinates.ofDegrees(55.7558, 37.6173),
|
GeodeticMapCoordinates.ofDegrees(55.7558, 37.6173),
|
||||||
6.0
|
8.0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// observable list of features
|
|
||||||
val features = mutableStateListOf<MapFeature>().apply {
|
|
||||||
addAll(initialFeatures())
|
|
||||||
}
|
|
||||||
|
|
||||||
// // test dynamic rendering
|
|
||||||
// LaunchedEffect(features) {
|
|
||||||
// repeat(10000) {
|
|
||||||
// delay(10)
|
|
||||||
// val randomPoint = Random.nextDouble(55.568548, 55.929444) to Random.nextDouble(37.518434, 37.568604)
|
|
||||||
// features.add(MapCircleFeature(randomPoint))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val mapTileProvider = remember { OpenStreetMapTileProvider(scope, HttpClient(CIO), Path.of("mapCache")) }
|
val mapTileProvider = remember { OpenStreetMapTileProvider(scope, HttpClient(CIO), Path.of("mapCache")) }
|
||||||
|
|
||||||
@ -66,8 +40,30 @@ fun App() {
|
|||||||
Column {
|
Column {
|
||||||
//display click coordinates
|
//display click coordinates
|
||||||
Text(coordinates?.toString() ?: "")
|
Text(coordinates?.toString() ?: "")
|
||||||
MapView(viewPoint, mapTileProvider, features = features) {
|
MapView(viewPoint, mapTileProvider, onClick = { gmc: GeodeticMapCoordinates -> coordinates = gmc }) {
|
||||||
coordinates = it
|
val pointOne = 55.568548 to 37.568604
|
||||||
|
val pointTwo = 55.929444 to 37.518434
|
||||||
|
image(pointOne, Icons.Filled.Home)
|
||||||
|
val circleId: FeatureId = circle(pointTwo)
|
||||||
|
line(pointOne, pointTwo)
|
||||||
|
text(pointOne, "Home")
|
||||||
|
|
||||||
|
scope.launch {
|
||||||
|
while (isActive){
|
||||||
|
delay(200)
|
||||||
|
circle(pointTwo, id = circleId, color = Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // test dynamic rendering
|
||||||
|
// scope.launch{
|
||||||
|
// repeat(10000) {
|
||||||
|
// delay(10)
|
||||||
|
// val randomPoint =
|
||||||
|
// Random.nextDouble(55.568548, 55.929444) to Random.nextDouble(37.518434, 37.568604)
|
||||||
|
// circle(randomPoint)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import androidx.compose.foundation.Canvas
|
|||||||
import androidx.compose.foundation.gestures.detectDragGestures
|
import androidx.compose.foundation.gestures.detectDragGestures
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
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
|
||||||
@ -41,9 +42,9 @@ private val logger = KotlinLogging.logger("MapView")
|
|||||||
actual fun MapView(
|
actual fun MapView(
|
||||||
initialViewPoint: MapViewPoint,
|
initialViewPoint: MapViewPoint,
|
||||||
mapTileProvider: MapTileProvider,
|
mapTileProvider: MapTileProvider,
|
||||||
features: Collection<MapFeature>,
|
features: SnapshotStateMap<FeatureId, MapFeature>,
|
||||||
modifier: Modifier,
|
|
||||||
onClick: (GeodeticMapCoordinates) -> Unit,
|
onClick: (GeodeticMapCoordinates) -> Unit,
|
||||||
|
modifier: Modifier,
|
||||||
) {
|
) {
|
||||||
var viewPoint by remember { mutableStateOf(initialViewPoint) }
|
var viewPoint by remember { mutableStateOf(initialViewPoint) }
|
||||||
|
|
||||||
@ -162,7 +163,7 @@ actual fun MapView(
|
|||||||
topLeft = offset
|
topLeft = offset
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
features.filter { zoom in it.zoomRange }.forEach { feature ->
|
features.values.filter { zoom in it.zoomRange }.forEach { feature ->
|
||||||
drawFeature(zoom, feature)
|
drawFeature(zoom, feature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user