API fixes

This commit is contained in:
Alexander Nozik 2023-01-06 22:16:46 +03:00
parent 6278235b51
commit 190834634f
8 changed files with 96 additions and 135 deletions

View File

@ -8,7 +8,7 @@ plugins {
allprojects { allprojects {
group = "center.sciprog" group = "center.sciprog"
version = "0.2.0-dev-1" version = "0.2.1-dev-1"
} }
apiValidation{ apiValidation{

View File

@ -1,9 +1,10 @@
kotlin.code.style=official kotlin.code.style=official
compose.version=1.2.2 compose.version=1.2.2
agp.version=7.3.1
agp.version=4.2.2
android.useAndroidX=true android.useAndroidX=true
org.jetbrains.compose.experimental.jscanvas.enabled=true org.jetbrains.compose.experimental.jscanvas.enabled=true
org.gradle.jvmargs=-Xmx4096m
toolsVersion=0.13.3-kotlin-1.7.20 toolsVersion=0.13.3-kotlin-1.7.20

View File

@ -2,7 +2,6 @@ package center.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.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import center.sciprog.maps.coordinates.Gmc import center.sciprog.maps.coordinates.Gmc
import center.sciprog.maps.features.* import center.sciprog.maps.features.*
@ -10,8 +9,8 @@ import center.sciprog.maps.features.*
@Composable @Composable
public expect fun MapView( public expect fun MapView(
mapState: MapViewScope, viewScope: MapViewScope,
featuresState: FeatureGroup<Gmc>, features: FeatureGroup<Gmc>,
modifier: Modifier = Modifier.fillMaxSize(), modifier: Modifier = Modifier.fillMaxSize(),
) )
@ -21,27 +20,21 @@ public expect fun MapView(
@Composable @Composable
public fun MapView( public fun MapView(
mapTileProvider: MapTileProvider, mapTileProvider: MapTileProvider,
initialViewPoint: MapViewPoint? = null, features: FeatureGroup<Gmc>,
initialViewPoint: ViewPoint<Gmc>? = null,
initialRectangle: Rectangle<Gmc>? = null, initialRectangle: Rectangle<Gmc>? = null,
featureMap: Map<FeatureId<*>, MapFeature>,
config: ViewConfig<Gmc> = ViewConfig(), config: ViewConfig<Gmc> = ViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(), modifier: Modifier = Modifier.fillMaxSize(),
) { ) {
val featureState = remember(featureMap) { val mapState: MapViewScope = MapViewScope.remember(
FeatureGroup.build(WebMercatorSpace) {
featureMap.forEach { feature(it.key.id, it.value) }
}
}
val mapState: MapViewScope = rememberMapState(
mapTileProvider, mapTileProvider,
config, config,
initialViewPoint = initialViewPoint, initialViewPoint = initialViewPoint,
initialRectangle = initialRectangle ?: featureState.features.computeBoundingBox(WebMercatorSpace, Float.MAX_VALUE), initialRectangle = initialRectangle ?: features.getBoundingBox(Float.MAX_VALUE),
) )
MapView(mapState, featureState, modifier) MapView(mapState, features, modifier)
} }
/** /**
@ -54,7 +47,7 @@ public fun MapView(
@Composable @Composable
public fun MapView( public fun MapView(
mapTileProvider: MapTileProvider, mapTileProvider: MapTileProvider,
initialViewPoint: MapViewPoint? = null, initialViewPoint: ViewPoint<Gmc>? = null,
initialRectangle: Rectangle<Gmc>? = null, initialRectangle: Rectangle<Gmc>? = null,
config: ViewConfig<Gmc> = ViewConfig(), config: ViewConfig<Gmc> = ViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(), modifier: Modifier = Modifier.fillMaxSize(),
@ -63,11 +56,14 @@ public fun MapView(
val featureState = FeatureGroup.remember(WebMercatorSpace, buildFeatures) val featureState = FeatureGroup.remember(WebMercatorSpace, buildFeatures)
val mapState: MapViewScope = rememberMapState( val mapState: MapViewScope = MapViewScope.remember(
mapTileProvider, mapTileProvider,
config, config,
initialViewPoint = initialViewPoint, initialViewPoint = initialViewPoint,
initialRectangle = initialRectangle ?: featureState.features.computeBoundingBox(WebMercatorSpace, Float.MAX_VALUE), initialRectangle = initialRectangle ?: featureState.features.computeBoundingBox(
WebMercatorSpace,
Float.MAX_VALUE
),
) )
MapView(mapState, featureState, modifier) MapView(mapState, featureState, modifier)

View File

@ -1,13 +1,15 @@
package center.sciprog.maps.compose package center.sciprog.maps.compose
import center.sciprog.maps.coordinates.* import center.sciprog.maps.coordinates.GeodeticMapCoordinates
import center.sciprog.maps.coordinates.Gmc
import center.sciprog.maps.coordinates.WebMercatorProjection
import center.sciprog.maps.coordinates.radians
import center.sciprog.maps.features.ViewPoint import center.sciprog.maps.features.ViewPoint
import kotlin.math.pow
/** /**
* Observable position on the map. Includes observation coordinate and [zoom] factor * Observable position on the map. Includes observation coordinate and [zoom] factor
*/ */
public data class MapViewPoint( internal data class MapViewPoint(
override val focus: GeodeticMapCoordinates, override val focus: GeodeticMapCoordinates,
override val zoom: Float, override val zoom: Float,
) : ViewPoint<Gmc>{ ) : ViewPoint<Gmc>{
@ -16,29 +18,4 @@ public data class MapViewPoint(
public companion object{ public companion object{
public val globe: MapViewPoint = MapViewPoint(GeodeticMapCoordinates(0.0.radians, 0.0.radians), 1f) public val globe: MapViewPoint = MapViewPoint(GeodeticMapCoordinates(0.0.radians, 0.0.radians), 1f)
} }
}
public fun MapViewPoint.move(delta: GeodeticMapCoordinates): MapViewPoint {
val newCoordinates = GeodeticMapCoordinates(
(focus.latitude + delta.latitude).coerceIn(
-MercatorProjection.MAXIMUM_LATITUDE,
MercatorProjection.MAXIMUM_LATITUDE
),
focus.longitude + delta.longitude
)
return MapViewPoint(newCoordinates, zoom)
}
public fun MapViewPoint.zoom(
zoomDelta: Float,
invariant: GeodeticMapCoordinates = focus,
): MapViewPoint = if (invariant == focus) {
copy(zoom = (zoom + zoomDelta).coerceIn(2f, 18f))
} else {
val difScale = (1 - 2f.pow(-zoomDelta))
val newCenter = GeodeticMapCoordinates(
focus.latitude + (invariant.latitude - focus.latitude) * difScale,
focus.longitude + (invariant.longitude - focus.longitude) * difScale
)
MapViewPoint(newCenter, (zoom + zoomDelta).coerceIn(2f, 18f))
} }

View File

@ -60,7 +60,7 @@ public class MapViewScope internal constructor(
canvasSize.height.value / rectangle.latitudeDelta.radians.value canvasSize.height.value / rectangle.latitudeDelta.radians.value
) * PI / mapTileProvider.tileSize ) * PI / mapTileProvider.tileSize
) )
return MapViewPoint(rectangle.center, zoom.toFloat()) return space.ViewPoint(rectangle.center, zoom.toFloat())
} }
override fun ViewPoint<Gmc>.moveBy(x: Dp, y: Dp): ViewPoint<Gmc> { override fun ViewPoint<Gmc>.moveBy(x: Dp, y: Dp): ViewPoint<Gmc> {
@ -73,22 +73,24 @@ public class MapViewScope internal constructor(
), ),
focus.longitude + (deltaX / scaleFactor).radians focus.longitude + (deltaX / scaleFactor).radians
) )
return MapViewPoint(newCoordinates, zoom) return space.ViewPoint(newCoordinates, zoom)
} }
}
@Composable public companion object {
internal fun rememberMapState( @Composable
mapTileProvider: MapTileProvider, public fun remember(
config: ViewConfig<Gmc>, mapTileProvider: MapTileProvider,
initialViewPoint: MapViewPoint? = null, config: ViewConfig<Gmc> = ViewConfig(),
initialRectangle: Rectangle<Gmc>? = null, initialViewPoint: ViewPoint<Gmc>? = null,
): MapViewScope = remember { initialRectangle: Rectangle<Gmc>? = null,
MapViewScope(mapTileProvider, config).also { mapState-> ): MapViewScope = remember {
if (initialViewPoint != null) { MapViewScope(mapTileProvider, config).also { mapState ->
mapState.viewPoint = initialViewPoint if (initialViewPoint != null) {
} else if (initialRectangle != null) { mapState.viewPoint = initialViewPoint
mapState.viewPoint = mapState.computeViewPoint(initialRectangle) } else if (initialRectangle != null) {
mapState.viewPoint = mapState.computeViewPoint(initialRectangle)
}
}
} }
} }
} }

View File

@ -1,7 +1,6 @@
package center.sciprog.maps.compose package center.sciprog.maps.compose
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@ -44,11 +43,10 @@ private val logger = KotlinLogging.logger("MapView")
*/ */
@Composable @Composable
public actual fun MapView( public actual fun MapView(
mapState: MapViewScope, viewScope: MapViewScope,
featuresState: FeatureGroup<Gmc>, features: FeatureGroup<Gmc>,
modifier: Modifier, modifier: Modifier,
): Unit = with(mapState) { ): Unit = with(viewScope) {
val mapTiles = remember(mapTileProvider) { mutableStateListOf<MapTile>() } val mapTiles = remember(mapTileProvider) { mutableStateListOf<MapTile>() }
// Load tiles asynchronously // Load tiles asynchronously
@ -89,57 +87,56 @@ public actual fun MapView(
} }
} }
} }
key(viewScope, features) {
val painterCache: Map<PainterFeature<Gmc>, Painter> =
features.features.filterIsInstance<PainterFeature<Gmc>>().associateWith { it.getPainter() }
val painterCache: Map<PainterFeature<Gmc>, Painter> = key(featuresState) { Canvas(modifier = modifier.mapControls(viewScope, features)) {
featuresState.features.filterIsInstance<PainterFeature<Gmc>>().associateWith { it.getPainter() }
}
if (canvasSize != size.toDpSize()) {
Canvas(modifier = modifier.mapControls(mapState, featuresState).fillMaxSize()) { logger.debug { "Recalculate canvas. Size: $size" }
config.onCanvasSizeChange(canvasSize)
if (canvasSize != size.toDpSize()) { canvasSize = size.toDpSize()
logger.debug { "Recalculate canvas. Size: $size" }
config.onCanvasSizeChange(canvasSize)
canvasSize = size.toDpSize()
}
clipRect {
val tileSize = IntSize(
ceil((mapTileProvider.tileSize.dp * tileScale).toPx()).toInt(),
ceil((mapTileProvider.tileSize.dp * tileScale).toPx()).toInt()
)
mapTiles.forEach { (id, image) ->
//converting back from tile index to screen offset
val offset = IntOffset(
(canvasSize.width / 2 + (mapTileProvider.toCoordinate(id.i).dp - centerCoordinates.x.dp) * tileScale).roundToPx(),
(canvasSize.height / 2 + (mapTileProvider.toCoordinate(id.j).dp - centerCoordinates.y.dp) * tileScale).roundToPx()
)
drawImage(
image = image.toComposeImageBitmap(),
dstOffset = offset,
dstSize = tileSize
)
} }
featuresState.featureMap.values.sortedBy { it.z } clipRect {
.filter { viewPoint.zoom in it.zoomRange } val tileSize = IntSize(
.forEach { feature -> ceil((mapTileProvider.tileSize.dp * tileScale).toPx()).toInt(),
drawFeature(mapState, painterCache, feature) ceil((mapTileProvider.tileSize.dp * tileScale).toPx()).toInt()
}
}
selectRect?.let { dpRect ->
val rect = dpRect.toRect()
drawRect(
color = Color.Blue,
topLeft = rect.topLeft,
size = rect.size,
alpha = 0.5f,
style = Stroke(
width = 2f,
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)
) )
) mapTiles.forEach { (id, image) ->
//converting back from tile index to screen offset
val offset = IntOffset(
(canvasSize.width / 2 + (mapTileProvider.toCoordinate(id.i).dp - centerCoordinates.x.dp) * tileScale).roundToPx(),
(canvasSize.height / 2 + (mapTileProvider.toCoordinate(id.j).dp - centerCoordinates.y.dp) * tileScale).roundToPx()
)
drawImage(
image = image.toComposeImageBitmap(),
dstOffset = offset,
dstSize = tileSize
)
}
features.featureMap.values.sortedBy { it.z }
.filter { viewPoint.zoom in it.zoomRange }
.forEach { feature ->
drawFeature(viewScope, painterCache, feature)
}
}
selectRect?.let { dpRect ->
val rect = dpRect.toRect()
drawRect(
color = Color.Blue,
topLeft = rect.topLeft,
size = rect.size,
alpha = 0.5f,
style = Stroke(
width = 2f,
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)
)
)
}
} }
} }
} }

View File

@ -1,11 +1,9 @@
package center.sciprog.attributes package center.sciprog.attributes
import androidx.compose.runtime.Stable
import center.sciprog.maps.features.Feature import center.sciprog.maps.features.Feature
import center.sciprog.maps.features.ZAttribute import center.sciprog.maps.features.ZAttribute
import kotlin.jvm.JvmInline import kotlin.jvm.JvmInline
@Stable
@JvmInline @JvmInline
public value class Attributes internal constructor(internal val map: Map<Attribute<*>, Any>) { public value class Attributes internal constructor(internal val map: Map<Attribute<*>, Any>) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")

View File

@ -23,15 +23,15 @@ private val logger = KotlinLogging.logger("SchemeView")
@Composable @Composable
public fun SchemeView( public fun SchemeView(
state: XYViewScope, state: XYViewScope,
featuresState: FeatureGroup<XY>, features: FeatureGroup<XY>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier.fillMaxSize(),
) = key(state, featuresState) { ) = key(state, features) {
with(state) { with(state) {
//Can't do that inside canvas //Can't do that inside canvas
val painterCache: Map<PainterFeature<XY>, Painter> = val painterCache: Map<PainterFeature<XY>, Painter> =
featuresState.features.filterIsInstance<PainterFeature<XY>>().associateWith { it.getPainter() } features.features.filterIsInstance<PainterFeature<XY>>().associateWith { it.getPainter() }
Canvas(modifier = modifier.mapControls(state, featuresState).fillMaxSize()) { Canvas(modifier = modifier.mapControls(state, features)) {
if (canvasSize != size.toDpSize()) { if (canvasSize != size.toDpSize()) {
canvasSize = size.toDpSize() canvasSize = size.toDpSize()
@ -39,7 +39,7 @@ public fun SchemeView(
} }
clipRect { clipRect {
featuresState.featureMap.values.sortedBy { it.z } features.featureMap.values.sortedBy { it.z }
.filter { viewPoint.zoom in it.zoomRange } .filter { viewPoint.zoom in it.zoomRange }
.forEach { feature -> .forEach { feature ->
drawFeature(state, painterCache, feature) drawFeature(state, painterCache, feature)
@ -80,30 +80,20 @@ public fun Rectangle<XY>.computeViewPoint(
*/ */
@Composable @Composable
public fun SchemeView( public fun SchemeView(
features: FeatureGroup<XY>,
initialViewPoint: ViewPoint<XY>? = null, initialViewPoint: ViewPoint<XY>? = null,
initialRectangle: Rectangle<XY>? = null, initialRectangle: Rectangle<XY>? = null,
featureMap: Map<FeatureId<*>, Feature<XY>>,
config: ViewConfig<XY> = ViewConfig(), config: ViewConfig<XY> = ViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(), modifier: Modifier = Modifier.fillMaxSize(),
) { ) {
val featureState = key(featureMap) {
FeatureGroup.build(XYCoordinateSpace) {
featureMap.forEach { feature(it.key.id, it.value) }
}
}
val state = XYViewScope.remember( val state = XYViewScope.remember(
config, config,
initialViewPoint = initialViewPoint, initialViewPoint = initialViewPoint,
initialRectangle = initialRectangle ?: featureState.features.computeBoundingBox( initialRectangle = initialRectangle ?: features.getBoundingBox(Float.MAX_VALUE),
XYCoordinateSpace,
Float.MAX_VALUE
),
) )
SchemeView(state, featureState, modifier) SchemeView(state, features, modifier)
} }
/** /**