Feature builder. Color modification example

This commit is contained in:
Alexander Nozik 2022-07-11 18:32:36 +03:00
parent 92abd3db43
commit ed4b603077
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
5 changed files with 115 additions and 59 deletions

View File

@ -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))

View File

@ -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,

View File

@ -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)
}

View File

@ -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)
// }
// }
} }
} }
} }

View File

@ -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)
} }
} }