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)
|
||||
|
||||
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)
|
||||
@ -29,18 +29,6 @@ class MapCircleFeature(
|
||||
val color: Color = Color.Red,
|
||||
) : 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(
|
||||
val a: GeodeticMapCoordinates,
|
||||
val b: GeodeticMapCoordinates,
|
||||
@ -48,13 +36,6 @@ class MapLineFeature(
|
||||
val color: Color = Color.Red,
|
||||
) : 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(
|
||||
val position: GeodeticMapCoordinates,
|
||||
val text: String,
|
||||
|
@ -2,6 +2,8 @@ package centre.sciprog.maps.compose
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||
import androidx.compose.ui.Modifier
|
||||
import centre.sciprog.maps.GeodeticMapCoordinates
|
||||
import centre.sciprog.maps.MapViewPoint
|
||||
@ -10,7 +12,20 @@ import centre.sciprog.maps.MapViewPoint
|
||||
expect fun MapView(
|
||||
initialViewPoint: MapViewPoint,
|
||||
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(),
|
||||
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.filled.Home
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import centre.sciprog.maps.GeodeticMapCoordinates
|
||||
@ -14,24 +15,11 @@ import centre.sciprog.maps.compose.*
|
||||
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.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
|
||||
@Preview
|
||||
fun App() {
|
||||
@ -40,24 +28,10 @@ fun App() {
|
||||
val viewPoint = remember {
|
||||
MapViewPoint(
|
||||
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 mapTileProvider = remember { OpenStreetMapTileProvider(scope, HttpClient(CIO), Path.of("mapCache")) }
|
||||
|
||||
@ -66,8 +40,30 @@ fun App() {
|
||||
Column {
|
||||
//display click coordinates
|
||||
Text(coordinates?.toString() ?: "")
|
||||
MapView(viewPoint, mapTileProvider, features = features) {
|
||||
coordinates = it
|
||||
MapView(viewPoint, mapTileProvider, onClick = { gmc: GeodeticMapCoordinates -> coordinates = gmc }) {
|
||||
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.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
|
||||
@ -41,9 +42,9 @@ private val logger = KotlinLogging.logger("MapView")
|
||||
actual fun MapView(
|
||||
initialViewPoint: MapViewPoint,
|
||||
mapTileProvider: MapTileProvider,
|
||||
features: Collection<MapFeature>,
|
||||
modifier: Modifier,
|
||||
features: SnapshotStateMap<FeatureId, MapFeature>,
|
||||
onClick: (GeodeticMapCoordinates) -> Unit,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
var viewPoint by remember { mutableStateOf(initialViewPoint) }
|
||||
|
||||
@ -162,7 +163,7 @@ actual fun MapView(
|
||||
topLeft = offset
|
||||
)
|
||||
}
|
||||
features.filter { zoom in it.zoomRange }.forEach { feature ->
|
||||
features.values.filter { zoom in it.zoomRange }.forEach { feature ->
|
||||
drawFeature(zoom, feature)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user