Compare commits

..

4 Commits

Author SHA1 Message Date
a.kalmakhanov
516d8d0233 State Encapsulation Finished 2022-08-11 13:24:56 +06:00
a.kalmakhanov
6b6fa45596 Merge branch 'main' into state_encapsulation
# Conflicts:
#	demo/maps/src/jvmMain/kotlin/Main.kt
#	maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapViewJvm.kt
2022-08-11 12:13:30 +06:00
a.kalmakhanov
13b44a8091 State Encapsulation In progress 2022-08-11 11:58:26 +06:00
a.kalmakhanov
ccf61951dc Clustering implementation for demo 2022-07-28 19:30:50 +06:00
135 changed files with 1815 additions and 88422 deletions

View File

@ -1,14 +1,14 @@
import kotlin.io.path.readText import kotlin.io.path.readText
job("Build") { job("Build") {
gradlew("spc.registry.jetbrains.space/p/sci/containers/kotlin-ci:1.0.3", "build") gradlew("openjdk:11", "build")
} }
job("Publish") { job("Publish") {
startOn { startOn {
gitPush { enabled = false } gitPush { enabled = false }
} }
container("spc.registry.jetbrains.space/p/sci/containers/kotlin-ci:1.0.3") { container("openjdk:11") {
env["SPACE_USER"] = Secrets("space_user") env["SPACE_USER"] = Secrets("space_user")
env["SPACE_TOKEN"] = Secrets("space_token") env["SPACE_TOKEN"] = Secrets("space_token")
kotlinScript { api -> kotlinScript { api ->

View File

@ -1,15 +0,0 @@
# Changelog
## Unreleased
### Added
### Changed
### Deprecated
### Removed
### Fixed
### Security

View File

@ -1,73 +1,13 @@
# Maps-kt
This repository is a work-in-progress implementation of Map-with-markers component for Compose-Multiplatform This repository is a work-in-progress implementation of Map-with-markers component for Compose-Multiplatform
![](docs/images/Screenshot%202023-01-12%20110429.png) ## [maps-kt-core](maps-kt-core)
A multiplatform coordinates representation and conversion.
## Modules ## [maps-kt-compose](maps-kt-compose)
A compose multiplatform (currently desktop only, contributions of android target are welcome) implementation of a map component, features and builder.
## [maps-kt-scheme](maps-kt-scheme)
An alternative component used for the same functionality on 2D schemes. Not all features from maps could be ported because it requires some code duplication (ideas for common API are welcome).
### [demo](demo) ## [demo](demo)
> Demonstration projects for different features
>
> **Maturity**: EXPERIMENTAL
### [maps-kt-compose](maps-kt-compose)
> Compose-multiplaform implementation for web-mercator tiled maps
>
> **Maturity**: EXPERIMENTAL
>
> **Features:**
> - [osm](maps-kt-compose/#) : OpenStreetMap tile provider.
### [maps-kt-core](maps-kt-core)
> Core cartography, UI-agnostic
>
> **Maturity**: DEVELOPMENT
>
> **Features:**
> - [angles and distances](maps-kt-core/#) : Type-safe angle and distance measurements.
> - [ellipsoid](maps-kt-core/#) : Ellipsoid geometry and distances
> - [mercator](maps-kt-core/#) : Mercator and web-mercator projections
### [maps-kt-features](maps-kt-features)
>
>
> **Maturity**: EXPERIMENTAL
### [maps-kt-geojson](maps-kt-geojson)
>
>
> **Maturity**: EXPERIMENTAL
### [maps-kt-scheme](maps-kt-scheme)
>
>
> **Maturity**: EXPERIMENTAL
### [trajectory-kt](trajectory-kt)
> Path and trajectory optimization
>
> **Maturity**: EXPERIMENTAL
### [demo/maps](demo/maps)
>
>
> **Maturity**: EXPERIMENTAL
### [demo/polygon-editor](demo/polygon-editor)
>
>
> **Maturity**: EXPERIMENTAL
### [demo/scheme](demo/scheme)
>
>
> **Maturity**: EXPERIMENTAL
### [demo/trajectory-playground](demo/trajectory-playground)
>
>
> **Maturity**: EXPERIMENTAL

View File

@ -1,51 +1,90 @@
import space.kscience.gradle.isInDevelopment
import space.kscience.gradle.useApache2Licence
import space.kscience.gradle.useSPCTeam
plugins { plugins {
id("space.kscience.gradle.project") base
} }
val kmathVersion: String by extra("0.3.1-dev-RC") val ktorVersion by extra("2.0.3")
allprojects { allprojects {
group = "center.sciprog" group = "center.sciprog"
version = "0.2.2" version = "0.1.0-SNAPSHOT"
repositories {
mavenLocal()
maven("https://repo.kotlin.link")
maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
}
} }
ksciencePublish { tasks.create("version") {
pom("https://github.com/SciProgCentre/maps-kt") { group = "publishing"
useApache2Licence() val versionFile = project.buildDir.resolve("project-version.txt")
useSPCTeam() outputs.file(versionFile)
doLast {
versionFile.createNewFile()
versionFile.writeText(project.version.toString())
println(project.version)
} }
github("SciProgCentre", "maps-kt")
space(
if (isInDevelopment) {
"https://maven.pkg.jetbrains.space/spc/p/sci/dev"
} else {
"https://maven.pkg.jetbrains.space/spc/p/sci/maven"
}
)
sonatype()
} }
subprojects { subprojects {
repositories { repositories {
maven("https://maven.pkg.jetbrains.space/mipt-npm/p/sci/dev")
google() google()
mavenCentral() mavenCentral()
maven("https://repo.kotlin.link")
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
} }
plugins.withId("maven-publish") {
configure<PublishingExtension> {
val vcs = "https://github.com/mipt-npm/maps-kt"
// Process each publication we have in this project
publications {
withType<MavenPublication> {
pom {
name.set(project.name)
description.set(project.description)
url.set(vcs)
licenses {
license {
name.set("The Apache Software License, Version 2.0")
url.set("https://www.apache.org/licenses/LICENSE-2.0.txt")
distribution.set("repo")
}
}
developers {
developer {
id.set("SPC")
name.set("Scientific programming centre")
organization.set("MIPT")
organizationUrl.set("https://sciprog.center/")
}
}
scm {
url.set(vcs)
tag.set(project.version.toString())
}
}
}
}
val spaceRepo = "https://maven.pkg.jetbrains.space/mipt-npm/p/sci/maven"
val spaceUser: String? = project.findProperty("publishing.space.user") as? String
val spaceToken: String? = project.findProperty("publishing.space.token") as? String
if (spaceUser != null && spaceToken != null) {
project.logger.info("Adding mipt-npm Space publishing to project [${project.name}]")
repositories.maven {
name = "space"
url = uri(spaceRepo)
credentials {
username = spaceUser
password = spaceToken
}
}
}
}
}
} }
readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md")

View File

@ -1,4 +0,0 @@
# Module demo

View File

@ -1,4 +0,0 @@
# Module maps

View File

@ -1,18 +0,0 @@
public final class ComposableSingletons$MainKt {
public static final field INSTANCE LComposableSingletons$MainKt;
public static field lambda-1 Lkotlin/jvm/functions/Function2;
public static field lambda-2 Lkotlin/jvm/functions/Function3;
public static field lambda-3 Lkotlin/jvm/functions/Function3;
public fun <init> ()V
public final fun getLambda-1$maps ()Lkotlin/jvm/functions/Function2;
public final fun getLambda-2$maps ()Lkotlin/jvm/functions/Function3;
public final fun getLambda-3$maps ()Lkotlin/jvm/functions/Function3;
}
public final class MainKt {
public static final fun App (Landroidx/compose/runtime/Composer;I)V
public static final fun main ()V
public static synthetic fun main ([Ljava/lang/String;)V
public static final fun toShortString (Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;)Ljava/lang/String;
}

View File

@ -1,3 +1,4 @@
import org.jetbrains.compose.compose
import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins { plugins {
@ -8,15 +9,18 @@ plugins {
val ktorVersion: String by rootProject.extra val ktorVersion: String by rootProject.extra
kotlin { kotlin {
jvmToolchain(11) jvm {
jvm() compilations.all {
kotlinOptions.jvmTarget = "11"
}
withJava()
}
sourceSets { sourceSets {
val jvmMain by getting { val jvmMain by getting {
dependencies { dependencies {
implementation(projects.mapsKtCompose) implementation(projects.mapsKtCompose)
implementation(projects.mapsKtGeojson)
implementation(compose.desktop.currentOs) implementation(compose.desktop.currentOs)
implementation("io.ktor:ktor-client-cio") implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("ch.qos.logback:logback-classic:1.2.11") implementation("ch.qos.logback:logback-classic:1.2.11")
} }
} }
@ -24,8 +28,7 @@ kotlin {
} }
} }
compose { compose.desktop {
desktop {
application { application {
mainClass = "MainKt" mainClass = "MainKt"
nativeDistributions { nativeDistributions {
@ -34,5 +37,4 @@ compose {
packageVersion = "1.0.0" packageVersion = "1.0.0"
} }
} }
}
} }

View File

@ -0,0 +1,6 @@
import kotlin.math.PI
fun Double.toDegrees() = this * 180 / PI
fun Double.toRadians() = this * PI / 180

View File

@ -1,51 +1,42 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.PointerMatcher
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
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.Composable import androidx.compose.runtime.*
import androidx.compose.runtime.remember import androidx.compose.ui.geometry.Offset
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.graphics.PointMode
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
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 center.sciprog.attributes.Attributes
import center.sciprog.maps.compose.* import center.sciprog.maps.compose.*
import center.sciprog.maps.coordinates.* import center.sciprog.maps.coordinates.*
import center.sciprog.maps.features.*
import center.sciprog.maps.geojson.geoJson
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.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import space.kscience.kmath.geometry.Angle
import space.kscience.kmath.geometry.degrees
import space.kscience.kmath.geometry.radians
import java.nio.file.Path import java.nio.file.Path
import kotlin.math.PI import kotlin.math.PI
import kotlin.random.Random import kotlin.random.Random
public fun GeodeticMapCoordinates.toShortString(): String = private fun GeodeticMapCoordinates.toShortString(): String =
"${(latitude.degrees).toString().take(6)}:${(longitude.degrees).toString().take(6)}" "${(latitude * 180.0 / PI).toString().take(6)}:${(longitude * 180.0 / PI).toString().take(6)}"
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
@Preview @Preview
fun App() { fun App() {
MaterialTheme { MaterialTheme {
//create a view point
val viewPoint = remember {
MapViewPoint(
GeodeticMapCoordinates.ofDegrees(55.7558, 37.6173),
8.0
)
}
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val mapTileProvider = remember { val mapTileProvider = remember {
OpenStreetMapTileProvider( OpenStreetMapTileProvider(
client = HttpClient(CIO), client = HttpClient(CIO),
@ -53,44 +44,20 @@ fun App() {
) )
} }
val centerCoordinates = MutableStateFlow<Gmc?>(null) var centerCoordinates by remember { mutableStateOf<GeodeticMapCoordinates?>(null) }
val pointOne = 55.568548 to 37.568604 val pointOne = 55.568548 to 37.568604
val pointTwo = 55.929444 to 37.518434 var pointTwo by remember { mutableStateOf(55.929444 to 37.518434) }
// val pointThree = 60.929444 to 37.518434 val pointThree = 60.929444 to 37.518434
MapView( val state = MapViewState(
mapTileProvider = mapTileProvider, mapTileProvider = mapTileProvider,
config = ViewConfig( initialViewPoint = { viewPoint },
onViewChange = { centerCoordinates.value = focus },
onClick = { _, viewPoint ->
println(viewPoint)
}
)
) { ) {
image(pointOne, Icons.Filled.Home)
geoJson(javaClass.getResource("/moscow.geo.json")!!) points(
.modifyAttribute(ColorAttribute, Color.Blue)
.modifyAttribute(AlphaAttribute, 0.4f)
icon(pointOne, Icons.Filled.Home)
val marker1 = rectangle(55.744 to 38.614, size = DpSize(10.dp, 10.dp)).color(Color.Magenta)
val marker2 = rectangle(55.8 to 38.5, size = DpSize(10.dp, 10.dp)).color(Color.Magenta)
val marker3 = rectangle(56.0 to 38.5, size = DpSize(10.dp, 10.dp)).color(Color.Magenta)
draggableLine(marker1, marker2, id = "line 1").color(Color.Red).onClick {
println("line 1 clicked")
}
draggableLine(marker2, marker3, id = "line 2").color(Color.DarkGray).onClick {
println("line 2 clicked")
}
draggableLine(marker3, marker1, id = "line 3").color(Color.Blue).onClick {
println("line 3 clicked")
}
multiLine(
points = listOf( points = listOf(
55.742465 to 37.615812, 55.742465 to 37.615812,
55.742713 to 37.616370, 55.742713 to 37.616370,
@ -99,73 +66,83 @@ fun App() {
55.742086 to 37.616566, 55.742086 to 37.616566,
55.741715 to 37.616716 55.741715 to 37.616716
), ),
pointMode = PointMode.Polygon
) )
//remember feature ID //remember feature Id
val circleId = circle( val circleId: FeatureId = circle(
centerCoordinates = pointTwo, centerCoordinates = pointTwo,
) )
scope.launch {
while (isActive) { draw(
delay(200) position = pointThree,
circleId.color(Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat())) getBoundingBox = {
GmcBox.withCenter(
center = GeodeticMapCoordinates.ofDegrees(
pointThree.first,
pointThree.second
),
height = Distance(0.001),
width = Distance(0.001)
)
} }
) {
drawLine(start = Offset(-10f, -10f), end = Offset(10f, 10f), color = Color.Red)
drawLine(start = Offset(-10f, 10f), end = Offset(10f, -10f), color = Color.Red)
} }
arc(pointOne, Distance(10.0), 0f, PI)
// draw(position = pointThree) {
// drawLine(start = Offset(-10f, -10f), end = Offset(10f, 10f), color = Color.Red)
// drawLine(start = Offset(-10f, 10f), end = Offset(10f, -10f), color = Color.Red)
// }
arc(pointOne, 10.0.kilometers, (PI / 4).radians, -Angle.pi / 2)
line(pointOne, pointTwo, id = "line") line(pointOne, pointTwo, id = "line")
text(pointOne, "Home", font = { size = 32f }) text(pointOne, "Home", font = { size = 32f })
pixelMap( centerCoordinates?.let {
space.Rectangle(
Gmc(latitude = 55.58461879539754.degrees, longitude = 37.8746197303493.degrees),
Gmc(latitude = 55.442792937592415.degrees, longitude = 38.132240805463844.degrees)
),
0.005.degrees,
0.005.degrees
) { gmc ->
Color(
red = ((gmc.latitude + Angle.piDiv2).degrees*10 % 1f).toFloat(),
green = ((gmc.longitude + Angle.pi).degrees*10 % 1f).toFloat(),
blue = 0f
).copy(alpha = 0.3f)
}
centerCoordinates.filterNotNull().onEach {
group(id = "center") { group(id = "center") {
circle(center = it, id = "circle", size = 1.dp).color(Color.Blue) circle(center = it, color = Color.Blue, size = 1f)
text(position = it, it.toShortString(), id = "text").color(Color.Blue) text(position = it, it.toShortString(), color = Color.Blue)
}
} }
}.launchIn(scope)
//Add click listeners for all polygons scope.launch {
forEachWithType<Gmc, PolygonFeature<Gmc>> { ref -> while (isActive) {
ref.onClick(PointerMatcher.Primary) { delay(200)
println("Click on ${ref.id}") //Overwrite a feature with new color
//draw in top-level scope circle(
with(this@MapView) { pointTwo,
multiLine( id = circleId,
ref.resolve().points, color = Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat())
attributes = Attributes(ZAttribute, 10f), )
id = "selected",
).modifyAttribute(StrokeAttribute, 4f).color(Color.Magenta)
} }
} }
} }
val config = MapViewConfig(
onViewChange = { centerCoordinates = focus },
onDrag = { start, end ->
val markerRadius = 5f
val startPosition = with(state) { start.focus.toOffset(this@MapViewConfig) }
val markerLocation = with(state) {
GeodeticMapCoordinates.ofDegrees(pointTwo.first, pointTwo.second).toOffset(this@MapViewConfig)
} }
if (startPosition.x in (markerLocation.x - markerRadius)..(markerLocation.x + markerRadius) &&
startPosition.y in (markerLocation.y - markerRadius)..(markerLocation.y + markerRadius)
) {
pointTwo = pointTwo.first + (end.focus.latitude - start.focus.latitude).toDegrees() to
pointTwo.second + (end.focus.longitude - start.focus.longitude).toDegrees()
false// returning false, because when we are dragging circle we don't want to drag map
} else true
}
)
MapView(
mapViewState = state,
mapViewConfig = config
)
} }
} }
fun main() = application { fun main() = application {
Window(onCloseRequest = ::exitApplication, title = "Maps-kt demo", icon = painterResource("SPC-logo.png")) { Window(onCloseRequest = ::exitApplication) {
App() App()
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +0,0 @@
# Module polygon-editor

View File

@ -1,19 +0,0 @@
public final class ComposableSingletons$MainKt {
public static final field INSTANCE LComposableSingletons$MainKt;
public static field lambda-1 Lkotlin/jvm/functions/Function3;
public static field lambda-2 Lkotlin/jvm/functions/Function2;
public static field lambda-3 Lkotlin/jvm/functions/Function3;
public static field lambda-4 Lkotlin/jvm/functions/Function3;
public fun <init> ()V
public final fun getLambda-1$polygon_editor ()Lkotlin/jvm/functions/Function3;
public final fun getLambda-2$polygon_editor ()Lkotlin/jvm/functions/Function2;
public final fun getLambda-3$polygon_editor ()Lkotlin/jvm/functions/Function3;
public final fun getLambda-4$polygon_editor ()Lkotlin/jvm/functions/Function3;
}
public final class MainKt {
public static final fun App (Landroidx/compose/runtime/Composer;I)V
public static final fun main ()V
public static synthetic fun main ([Ljava/lang/String;)V
}

View File

@ -1,36 +0,0 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
val ktorVersion: String by rootProject.extra
kotlin {
jvm()
jvmToolchain(11)
sourceSets {
val jvmMain by getting {
dependencies {
implementation(projects.mapsKtScheme)
implementation(compose.desktop.currentOs)
implementation("ch.qos.logback:logback-classic:1.2.11")
}
}
val jvmTest by getting
}
}
compose{
desktop {
application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "polygon-editor-demo"
packageVersion = "1.0.0"
}
}
}
}

View File

@ -1,80 +0,0 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.material.CursorDropdownMenu
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.input.pointer.isSecondaryPressed
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import center.sciprog.maps.features.*
import center.sciprog.maps.scheme.SchemeView
import center.sciprog.maps.scheme.XY
import center.sciprog.maps.scheme.XYCoordinateSpace
import center.sciprog.maps.scheme.XYViewScope
@Composable
@Preview
fun App() {
MaterialTheme {
var clickPoint by remember { mutableStateOf<XY?>(null) }
val myPolygon: SnapshotStateList<XY> = remember { mutableStateListOf<XY>() }
val featureState: FeatureGroup<XY> = FeatureGroup.remember(XYCoordinateSpace) {
multiLine(
listOf(XY(0f, 0f), XY(0f, 1f), XY(1f, 1f), XY(1f, 0f), XY(0f, 0f)),
id = "frame"
)
}
val mapState: XYViewScope = XYViewScope.remember(
config = ViewConfig<XY>(
onClick = { event, point ->
if (event.buttons.isSecondaryPressed) {
clickPoint = point.focus
}
}
),
initialRectangle = featureState.getBoundingBox(1f),
)
CursorDropdownMenu(clickPoint != null, { clickPoint = null }) {
clickPoint?.let { point ->
TextButton({
myPolygon.add(point)
if (myPolygon.isNotEmpty()) {
featureState.group(id = "polygon") {
val pointRefs = myPolygon.mapIndexed { index, xy ->
circle(xy, id = "point[$index]").draggable { _, to ->
myPolygon[index] = to.focus
}
}
draggableMultiLine(
pointRefs + pointRefs.first(),
)
}
}
clickPoint = null
}) {
Text("Create node")
}
}
}
SchemeView(
mapState,
featureState,
)
}
}
fun main() = application {
Window(title = "Polygon editor demo", onCloseRequest = ::exitApplication) {
App()
}
}

View File

@ -1,4 +0,0 @@
# Module scheme

View File

@ -1,17 +0,0 @@
public final class ComposableSingletons$MainKt {
public static final field INSTANCE LComposableSingletons$MainKt;
public static field lambda-1 Lkotlin/jvm/functions/Function2;
public static field lambda-2 Lkotlin/jvm/functions/Function3;
public static field lambda-3 Lkotlin/jvm/functions/Function3;
public fun <init> ()V
public final fun getLambda-1$scheme ()Lkotlin/jvm/functions/Function2;
public final fun getLambda-2$scheme ()Lkotlin/jvm/functions/Function3;
public final fun getLambda-3$scheme ()Lkotlin/jvm/functions/Function3;
}
public final class MainKt {
public static final fun App (Landroidx/compose/runtime/Composer;I)V
public static final fun main ()V
public static synthetic fun main ([Ljava/lang/String;)V
}

View File

@ -1,3 +1,4 @@
import org.jetbrains.compose.compose
import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins { plugins {
@ -8,8 +9,12 @@ plugins {
val ktorVersion: String by rootProject.extra val ktorVersion: String by rootProject.extra
kotlin { kotlin {
jvm() jvm {
jvmToolchain(11) compilations.all {
kotlinOptions.jvmTarget = "11"
}
withJava()
}
sourceSets { sourceSets {
val jvmMain by getting { val jvmMain by getting {
dependencies { dependencies {
@ -22,8 +27,7 @@ kotlin {
} }
} }
compose{ compose.desktop {
desktop {
application { application {
mainClass = "MainKt" mainClass = "MainKt"
nativeDistributions { nativeDistributions {
@ -32,5 +36,4 @@ compose{
packageVersion = "1.0.0" packageVersion = "1.0.0"
} }
} }
}
} }

View File

@ -1,104 +1,47 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.ContextMenuArea
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
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 center.sciprog.maps.features.FeatureGroup
import center.sciprog.maps.features.ViewConfig
import center.sciprog.maps.features.ViewPoint
import center.sciprog.maps.features.color
import center.sciprog.maps.scheme.* import center.sciprog.maps.scheme.*
import center.sciprog.maps.svg.FeatureStateSnapshot
import center.sciprog.maps.svg.exportToSvg
import center.sciprog.maps.svg.snapshot
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import space.kscience.kmath.geometry.Angle
import java.awt.Desktop
import java.nio.file.Files
@Composable @Composable
@Preview @Preview
fun App() { fun App() {
MaterialTheme { MaterialTheme {
val scope = rememberCoroutineScope() //create a view point
val viewPoint = remember {
val schemeFeaturesState: FeatureGroup<XY> = FeatureGroup.remember(XYCoordinateSpace) { SchemeViewPoint(
background(1600f, 1200f) { painterResource("middle-earth.jpg") } SchemeCoordinates(0f, 0f),
circle(410.52737 to 868.7676).color(Color.Blue) 1f
text(410.52737 to 868.7676, "Shire").color(Color.Blue)
circle(1132.0881 to 394.99127).color(Color.Red)
text(1132.0881 to 394.99127, "Ordruin").color(Color.Red)
arc(center = 1132.0881 to 394.99127, radius = 20f, startAngle = Angle.zero, Angle.piTimes2)
//circle(410.52737 to 868.7676, id = "hobbit")
scope.launch {
var t = 0.0
while (isActive) {
val x = 410.52737 + t * (1132.0881 - 410.52737)
val y = 868.7676 + t * (394.99127 - 868.7676)
circle(x to y, id = "hobbit").color(Color.Green)
delay(100)
t += 0.005
if (t >= 1.0) t = 0.0
}
}
}
val initialViewPoint: ViewPoint<XY> = remember {
schemeFeaturesState.getBoundingBox(1f)?.computeViewPoint() ?: XYViewPoint(XY(0f, 0f))
}
var viewPoint: ViewPoint<XY> by remember { mutableStateOf(initialViewPoint) }
var snapshot: FeatureStateSnapshot<XY>? by remember { mutableStateOf(null) }
if (snapshot == null) {
snapshot = schemeFeaturesState.snapshot()
}
ContextMenuArea(
items = {
listOf(
ContextMenuItem("Export to SVG") {
snapshot?.let {
val path = Files.createTempFile("scheme-kt-", ".svg")
it.exportToSvg(viewPoint, 800.0, 800.0, path)
println(path.toFile())
Desktop.getDesktop().browse(path.toFile().toURI())
}
},
) )
} }
) {
val mapState: XYViewScope = XYViewScope.remember(
ViewConfig(
onClick = { _, click ->
println("${click.focus.x}, ${click.focus.y}")
},
onViewChange = { viewPoint = this }
),
initialViewPoint = initialViewPoint,
)
SchemeView( SchemeView(
mapState, viewPoint,
schemeFeaturesState, config = SchemeViewConfig(
) inferViewBoxFromFeatures = true,
onClick = {
println("${focus.x}, ${focus.y}")
}
)
) {
background(painterResource("middle-earth.jpg"))
circle(410.52737 to 868.7676, color = Color.Blue)
text(410.52737 to 868.7676,"Shire", color = Color.Blue)
circle(1132.0881 to 394.99127, color = Color.Red)
text(1132.0881 to 394.99127, "Ordruin",color = Color.Red)
} }
} }
} }
fun main() = application { fun main() = application {
Window(title = "Scheme demo", onCloseRequest = ::exitApplication) { Window(onCloseRequest = ::exitApplication) {
App() App()
} }
} }

View File

@ -1,4 +0,0 @@
# Module trajectory-playground

View File

@ -1,25 +0,0 @@
public final class ComposableSingletons$MainKt {
public static final field INSTANCE LComposableSingletons$MainKt;
public static field lambda-1 Lkotlin/jvm/functions/Function2;
public static field lambda-2 Lkotlin/jvm/functions/Function3;
public static field lambda-3 Lkotlin/jvm/functions/Function3;
public fun <init> ()V
public final fun getLambda-1$trajectory_playground ()Lkotlin/jvm/functions/Function2;
public final fun getLambda-2$trajectory_playground ()Lkotlin/jvm/functions/Function3;
public final fun getLambda-3$trajectory_playground ()Lkotlin/jvm/functions/Function3;
}
public final class MainKt {
public static final fun closePoints (Landroidx/compose/runtime/Composer;I)V
public static final fun doubleObstacle (Landroidx/compose/runtime/Composer;I)V
public static final fun main ()V
public static synthetic fun main ([Ljava/lang/String;)V
public static final fun obstacle (Lcenter/sciprog/maps/features/FeatureGroup;Lspace/kscience/trajectory/Obstacle;Lkotlin/jvm/functions/Function1;)V
public static synthetic fun obstacle$default (Lcenter/sciprog/maps/features/FeatureGroup;Lspace/kscience/trajectory/Obstacle;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public static final fun playground (Landroidx/compose/runtime/Composer;I)V
public static final fun pose (Lcenter/sciprog/maps/features/FeatureGroup;Lspace/kscience/trajectory/Pose2D;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun singleObstacle (Landroidx/compose/runtime/Composer;I)V
public static final fun trajectory (Lcenter/sciprog/maps/features/FeatureGroup;Lspace/kscience/trajectory/Trajectory2D;Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun trajectory$default (Lcenter/sciprog/maps/features/FeatureGroup;Lspace/kscience/trajectory/Trajectory2D;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
}

View File

@ -1,30 +0,0 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
val ktorVersion: String by rootProject.extra
kotlin {
jvm()
jvmToolchain(11)
sourceSets {
val jvmMain by getting {
dependencies {
implementation(projects.mapsKtScheme)
implementation(projects.trajectoryKt)
implementation(compose.desktop.currentOs)
implementation(spclibs.logback.classic)
}
}
val jvmTest by getting
}
}
compose {
desktop {
application {
mainClass = "MainKt"
}
}
}

View File

@ -1,195 +0,0 @@
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import center.sciprog.maps.features.*
import center.sciprog.maps.scheme.SchemeView
import center.sciprog.maps.scheme.XY
import space.kscience.kmath.geometry.Angle
import space.kscience.kmath.geometry.Circle2D
import space.kscience.kmath.geometry.DoubleVector2D
import space.kscience.kmath.geometry.Euclidean2DSpace
import space.kscience.trajectory.*
import kotlin.random.Random
private fun DoubleVector2D.toXY() = XY(x.toFloat(), y.toFloat())
private val random = Random(123)
fun FeatureGroup<XY>.trajectory(
trajectory: Trajectory2D,
colorPicker: (Trajectory2D) -> Color = { Color.Blue },
): FeatureRef<XY, FeatureGroup<XY>> = group {
when (trajectory) {
is StraightTrajectory2D -> line(
aCoordinates = trajectory.begin.toXY(),
bCoordinates = trajectory.end.toXY(),
).color(colorPicker(trajectory))
is CircleTrajectory2D -> with(Euclidean2DSpace) {
val topLeft = trajectory.circle.center + vector(-trajectory.circle.radius, trajectory.circle.radius)
val bottomRight = trajectory.circle.center + vector(trajectory.circle.radius, -trajectory.circle.radius)
val rectangle = Rectangle(
topLeft.toXY(),
bottomRight.toXY()
)
arc(
oval = rectangle,
startAngle = trajectory.arcStart - Angle.piDiv2,
arcLength = trajectory.arcAngle,
).color(colorPicker(trajectory))
}
is CompositeTrajectory2D -> trajectory.segments.forEach {
trajectory(it, colorPicker)
}
}
}
fun FeatureGroup<XY>.obstacle(obstacle: Obstacle, colorPicker: (Trajectory2D) -> Color = { Color.Red }) {
trajectory(obstacle.circumvention, colorPicker)
polygon(obstacle.arcs.map { it.center.toXY() }).color(Color.Gray)
}
fun FeatureGroup<XY>.pose(pose2D: Pose2D) = with(Euclidean2DSpace) {
line(pose2D.toXY(), (pose2D + Pose2D.bearingToVector(pose2D.bearing)).toXY())
}
@Composable
@Preview
fun closePoints() {
SchemeView {
val obstacle = Obstacle(
Circle2D(Euclidean2DSpace.vector(0.0, 0.0), 1.0),
Circle2D(Euclidean2DSpace.vector(0.0, 1.0), 1.0),
Circle2D(Euclidean2DSpace.vector(1.0, 1.0), 1.0),
Circle2D(Euclidean2DSpace.vector(1.0, 0.0), 1.0)
)
val enter = Pose2D(-0.8, -0.8, Angle.pi)
val exit = Pose2D(-0.8, -0.8, Angle.piDiv2)
pose(enter)
pose(exit)
val paths: List<Trajectory2D> = Obstacles.avoidObstacles(
enter,
exit,
1.0,
obstacle
)
obstacle(obstacle)
paths.forEach {
val color = Color(random.nextInt())
trajectory(it) { color }
}
}
}
@Composable
@Preview
fun singleObstacle() {
SchemeView {
val obstacle = Obstacle(Circle2D(Euclidean2DSpace.vector(7.0, 1.0), 5.0))
val enter = Pose2D(-5, -1, Angle.pi / 4)
val exit = Pose2D(20, 4, Angle.pi * 3 / 4)
pose(enter)
pose(exit)
obstacle(obstacle)
Obstacles.avoidObstacles(
enter,
exit,
0.5,
obstacle
).forEach {
val color = Color(random.nextInt())
trajectory(it) { color }
}
}
}
@Composable
@Preview
fun doubleObstacle() {
SchemeView {
val obstacles = arrayOf(
Obstacle(
Circle2D(Euclidean2DSpace.vector(1.0, 6.5), 0.5),
Circle2D(Euclidean2DSpace.vector(2.0, 1.0), 0.5),
Circle2D(Euclidean2DSpace.vector(6.0, 0.0), 0.5),
Circle2D(Euclidean2DSpace.vector(5.0, 5.0), 0.5)
), Obstacle(
Circle2D(Euclidean2DSpace.vector(10.0, 1.0), 0.5),
Circle2D(Euclidean2DSpace.vector(16.0, 0.0), 0.5),
Circle2D(Euclidean2DSpace.vector(14.0, 6.0), 0.5),
Circle2D(Euclidean2DSpace.vector(9.0, 4.0), 0.5)
)
)
obstacles.forEach { obstacle(it) }
val enter = Pose2D(-5, -1, Angle.pi / 4)
val exit = Pose2D(20, 4, Angle.pi * 3 / 4)
pose(enter)
pose(exit)
Obstacles.avoidObstacles(
enter,
exit,
0.5,
*obstacles
).forEach {
val color = Color(random.nextInt())
trajectory(it) { color }
}
}
}
@Composable
@Preview
fun playground() {
val examples = listOf(
"Close starting points",
"Single obstacle",
"Two obstacles",
)
var currentExample by remember { mutableStateOf(examples.first()) }
Scaffold(floatingActionButton = {
Column {
examples.forEach {
Button(onClick = { currentExample = it }) {
Text(it)
}
}
}
}) {
when (currentExample) {
examples[0] -> closePoints()
examples[1] -> singleObstacle()
examples[2] -> doubleObstacle()
}
}
}
fun main() = application {
Window(title = "Trajectory-playground", onCloseRequest = ::exitApplication) {
MaterialTheme {
playground()
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 KiB

View File

@ -1,17 +0,0 @@
## Artifact:
The Maven coordinates of this project are `${group}:${name}:${version}`.
**Gradle Kotlin DSL:**
```kotlin
repositories {
maven("https://repo.kotlin.link")
mavenCentral()
// development and snapshot versions
maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
}
dependencies {
implementation("${group}:${name}:${version}")
}
```

View File

@ -1,9 +0,0 @@
# Maps-kt
This repository is a work-in-progress implementation of Map-with-markers component for Compose-Multiplatform
![](docs/images/Screenshot%202023-01-12%20110429.png)
## Modules
${modules}

View File

@ -1,10 +1,5 @@
kotlin.code.style=official kotlin.code.style=official
kotlin.version=1.6.10
compose.version=1.4.0 compose.version=1.1.1
agp.version=7.4.2 agp.version=4.2.2
android.useAndroidX=true android.useAndroidX=true
org.jetbrains.compose.experimental.jscanvas.enabled=true
org.gradle.jvmargs=-Xmx4096m
toolsVersion=0.14.6-kotlin-1.8.20

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +0,0 @@
# Module kmath-core
The core interfaces of KMath.
- [osm](#) : OpenStreetMap tile provider.
## Artifact:
The Maven coordinates of this project are `center.sciprog:maps-kt-compose:0.2.2`.
**Gradle Groovy:**
```groovy
repositories {
maven { url 'https://repo.kotlin.link' }
mavenCentral()
}
dependencies {
implementation 'center.sciprog:maps-kt-compose:0.2.2'
}
```
**Gradle Kotlin DSL:**
```kotlin
repositories {
maven("https://repo.kotlin.link")
mavenCentral()
}
dependencies {
implementation("center.sciprog:maps-kt-compose:0.2.2")
}
```

View File

@ -1,43 +1,30 @@
import org.jetbrains.compose.compose
plugins { plugins {
id("space.kscience.gradle.mpp") kotlin("multiplatform")
id("org.jetbrains.compose") id("org.jetbrains.compose")
`maven-publish` `maven-publish`
} }
kscience{ val ktorVersion: String by rootProject.extra
jvm()
}
kotlin { kotlin {
explicitApi = org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode.Warning
jvm {
compilations.all {
kotlinOptions.jvmTarget = "11"
}
}
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api(projects.mapsKtCore) api(projects.mapsKtCore)
api(projects.mapsKtFeatures)
api(compose.foundation) api(compose.foundation)
api(project.dependencies.platform(spclibs.ktor.bom)) api("io.ktor:ktor-client-core:$ktorVersion")
api("io.ktor:ktor-client-core")
api("io.github.microutils:kotlin-logging:2.1.23") api("io.github.microutils:kotlin-logging:2.1.23")
} }
} }
val jvmTest by getting { val jvmMain by getting
dependencies { val jvmTest by getting
implementation("io.ktor:ktor-client-cio")
implementation(compose.desktop.currentOs)
implementation(spclibs.kotlinx.coroutines.test)
implementation(spclibs.logback.classic)
}
}
} }
} }
readme {
description = "Compose-multiplaform implementation for web-mercator tiled maps"
maturity = space.kscience.gradle.Maturity.EXPERIMENTAL
propertyByTemplate("artifact", rootProject.file("docs/templates/ARTIFACT-TEMPLATE.md"))
feature(
id = "osm",
) { "OpenStreetMap tile provider." }
}

View File

@ -1,7 +0,0 @@
# Module kmath-core
The core interfaces of KMath.
${features}
${artifact}

View File

@ -1,74 +0,0 @@
package center.sciprog.maps.compose
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
import center.sciprog.maps.coordinates.Gmc
import center.sciprog.maps.features.Rectangle
import space.kscience.kmath.geometry.Angle
import space.kscience.kmath.geometry.abs
internal fun Angle.isBetween(a: Angle, b: Angle) = this in a..b || this in b..a
/**
* A section of the map between two parallels and two meridians. The figure represents a square in a Mercator projection.
* Params are two opposing "corners" of quasi-square.
*
* Note that this is a rectangle only on a Mercator projection.
*/
internal data class GmcRectangle(
override val a: GeodeticMapCoordinates,
override val b: GeodeticMapCoordinates,
) : Rectangle<Gmc> {
override val center: GeodeticMapCoordinates
get() = GeodeticMapCoordinates.normalized(
(a.latitude + b.latitude) / 2,
(a.longitude + b.longitude) / 2
)
override fun contains(point: Gmc): Boolean =
point.latitude.isBetween(a.latitude, b.latitude) && point.longitude.isBetween(a.longitude, b.longitude)
}
/**
* Minimum longitude
*/
public val Rectangle<Gmc>.left: Angle get() = minOf(a.longitude, b.longitude)
/**
* maximum longitude
*/
public val Rectangle<Gmc>.right: Angle get() = maxOf(a.longitude, b.longitude)
/**
* Maximum latitude
*/
public val Rectangle<Gmc>.top: Angle get() = maxOf(a.latitude, b.latitude)
/**
* Minimum latitude
*/
public val Rectangle<Gmc>.bottom: Angle get() = minOf(a.latitude, b.latitude)
public val Rectangle<Gmc>.longitudeDelta: Angle get() = abs(a.longitude - b.longitude)
public val Rectangle<Gmc>.latitudeDelta: Angle get() = abs(a.latitude - b.latitude)
public val Rectangle<Gmc>.topLeft: Gmc get() = Gmc.normalized(top, left)
public val Rectangle<Gmc>.bottomRight: Gmc get() = Gmc.normalized(bottom, right)
//public fun GmcRectangle.enlarge(
// top: Distance,
// bottom: Distance = top,
// left: Distance = top,
// right: Distance = left,
//): GmcRectangle {
//
//}
//
//public fun GmcRectangle.enlarge(
// top: Angle,
// bottom: Angle = top,
// left: Angle = top,
// right: Angle = left,
//): GmcRectangle {
//
//}

View File

@ -0,0 +1,133 @@
package center.sciprog.maps.compose
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.PointMode
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
import center.sciprog.maps.coordinates.GmcBox
import center.sciprog.maps.coordinates.wrapAll
public interface MapFeature {
public val zoomRange: IntRange
public fun getBoundingBox(zoom: Int): GmcBox?
}
public fun Iterable<MapFeature>.computeBoundingBox(zoom: Int): GmcBox? =
mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
internal fun Pair<Double, Double>.toCoordinates() = GeodeticMapCoordinates.ofDegrees(first, second)
internal val defaultZoomRange = 1..18
/**
* A feature that decides what to show depending on the zoom value (it could change size of shape)
*/
public class MapFeatureSelector(
public val selector: (zoom: Int) -> MapFeature,
) : MapFeature {
override val zoomRange: IntRange get() = defaultZoomRange
override fun getBoundingBox(zoom: Int): GmcBox? = selector(zoom).getBoundingBox(zoom)
}
public class MapDrawFeature(
public val position: GeodeticMapCoordinates,
override val zoomRange: IntRange = defaultZoomRange,
private val computeBoundingBox: (zoom: Int) -> GmcBox,
public val drawFeature: DrawScope.() -> Unit,
) : MapFeature {
override fun getBoundingBox(zoom: Int): GmcBox = computeBoundingBox(zoom)
}
public class MapPointsFeature(
public val points: List<GeodeticMapCoordinates>,
override val zoomRange: IntRange = defaultZoomRange,
public val stroke: Float = 2f,
public val color: Color = Color.Red,
public val pointMode: PointMode = PointMode.Points
) : MapFeature {
override fun getBoundingBox(zoom: Int): GmcBox {
return GmcBox(points.first(), points.last())
}
}
public class MapCircleFeature(
public val center: GeodeticMapCoordinates,
override val zoomRange: IntRange = defaultZoomRange,
public val size: Float = 5f,
public val color: Color = Color.Red,
) : MapFeature {
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(center, center)
}
public class MapRectangleFeature(
public val center: GeodeticMapCoordinates,
override val zoomRange: IntRange = defaultZoomRange,
public val size: DpSize = DpSize(5.dp, 5.dp),
public val color: Color = Color.Red,
) : MapFeature {
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(center, center)
}
public class MapLineFeature(
public val a: GeodeticMapCoordinates,
public val b: GeodeticMapCoordinates,
override val zoomRange: IntRange = defaultZoomRange,
public val color: Color = Color.Red,
) : MapFeature {
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(a, b)
}
public class MapArcFeature(
public val oval: GmcBox,
public val startAngle: Float,
public val endAngle: Float,
override val zoomRange: IntRange = defaultZoomRange,
public val color: Color = Color.Red,
) : MapFeature {
override fun getBoundingBox(zoom: Int): GmcBox = oval
}
public class MapBitmapImageFeature(
public val position: GeodeticMapCoordinates,
public val image: ImageBitmap,
public val size: IntSize = IntSize(15, 15),
override val zoomRange: IntRange = defaultZoomRange,
) : MapFeature {
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position)
}
public class MapVectorImageFeature(
public val position: GeodeticMapCoordinates,
public val painter: Painter,
public val size: DpSize,
override val zoomRange: IntRange = defaultZoomRange,
) : MapFeature {
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position)
}
@Composable
public fun MapVectorImageFeature(
position: GeodeticMapCoordinates,
image: ImageVector,
size: DpSize = DpSize(20.dp, 20.dp),
zoomRange: IntRange = defaultZoomRange,
): MapVectorImageFeature = MapVectorImageFeature(position, rememberVectorPainter(image), size, zoomRange)
/**
* A group of other features
*/
public class MapFeatureGroup(
public val children: Map<FeatureId, MapFeature>,
override val zoomRange: IntRange = defaultZoomRange,
) : MapFeature {
override fun getBoundingBox(zoom: Int): GmcBox? = children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
}

View File

@ -0,0 +1,157 @@
package center.sciprog.maps.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PointMode
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import center.sciprog.maps.coordinates.Distance
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
import center.sciprog.maps.coordinates.GmcBox
public typealias FeatureId = String
public interface MapFeatureBuilder {
public fun addFeature(id: FeatureId?, feature: MapFeature): FeatureId
public fun build(): SnapshotStateMap<FeatureId, MapFeature>
}
internal class MapFeatureBuilderImpl(initialFeatures: Map<FeatureId, MapFeature>) : MapFeatureBuilder {
private val content: SnapshotStateMap<FeatureId, MapFeature> = mutableStateMapOf<FeatureId, MapFeature>().apply {
putAll(initialFeatures)
}
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
}
public fun MapFeatureBuilder.circle(
center: GeodeticMapCoordinates,
zoomRange: IntRange = defaultZoomRange,
size: Float = 5f,
color: Color = Color.Red,
id: FeatureId? = null,
): FeatureId = addFeature(
id, MapCircleFeature(center, zoomRange, size, color)
)
public fun MapFeatureBuilder.circle(
centerCoordinates: Pair<Double, Double>,
zoomRange: IntRange = defaultZoomRange,
size: Float = 5f,
color: Color = Color.Red,
id: FeatureId? = null,
): FeatureId = addFeature(
id, MapCircleFeature(centerCoordinates.toCoordinates(), zoomRange, size, color)
)
public fun MapFeatureBuilder.rectangle(
centerCoordinates: Pair<Double, Double>,
zoomRange: IntRange = defaultZoomRange,
size: DpSize = DpSize(5.dp, 5.dp),
color: Color = Color.Red,
id: FeatureId? = null,
): FeatureId = addFeature(
id, MapRectangleFeature(centerCoordinates.toCoordinates(), zoomRange, size, color)
)
public fun MapFeatureBuilder.draw(
position: Pair<Double, Double>,
zoomRange: IntRange = defaultZoomRange,
id: FeatureId? = null,
getBoundingBox: (Int) -> GmcBox,
drawFeature: DrawScope.() -> Unit,
): FeatureId = addFeature(id, MapDrawFeature(position.toCoordinates(), zoomRange, getBoundingBox, drawFeature))
public fun MapFeatureBuilder.line(
aCoordinates: Pair<Double, Double>,
bCoordinates: Pair<Double, Double>,
zoomRange: IntRange = defaultZoomRange,
color: Color = Color.Red,
id: FeatureId? = null,
): FeatureId = addFeature(
id,
MapLineFeature(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), zoomRange, color)
)
public fun MapFeatureBuilder.arc(
oval: GmcBox,
startAngle: Number,
endAngle: Number,
zoomRange: IntRange = defaultZoomRange,
color: Color = Color.Red,
id: FeatureId? = null,
): FeatureId = addFeature(
id,
MapArcFeature(oval, startAngle.toFloat(), endAngle.toFloat(), zoomRange, color)
)
public fun MapFeatureBuilder.arc(
center: Pair<Double, Double>,
radius: Distance,
startAngle: Number,
endAngle: Number,
zoomRange: IntRange = defaultZoomRange,
color: Color = Color.Red,
id: FeatureId? = null,
): FeatureId = addFeature(
id,
MapArcFeature(
GmcBox.withCenter(center.toCoordinates(), radius, radius),
startAngle.toFloat(),
endAngle.toFloat(),
zoomRange,
color
)
)
public fun MapFeatureBuilder.points(
points: List<Pair<Double, Double>>,
zoomRange: IntRange = defaultZoomRange,
stroke: Float = 2f,
color: Color = Color.Red,
pointMode: PointMode = PointMode.Points,
id: FeatureId? = null
): FeatureId = addFeature(id, MapPointsFeature(points.map { it.toCoordinates() }, zoomRange, stroke, color, pointMode))
@Composable
public fun MapFeatureBuilder.image(
position: Pair<Double, Double>,
image: ImageVector,
size: DpSize = DpSize(20.dp, 20.dp),
zoomRange: IntRange = defaultZoomRange,
id: FeatureId? = null,
): FeatureId = addFeature(id, MapVectorImageFeature(position.toCoordinates(), image, size, zoomRange))
public fun MapFeatureBuilder.group(
zoomRange: IntRange = defaultZoomRange,
id: FeatureId? = null,
builder: MapFeatureBuilder.() -> Unit,
): FeatureId {
val map = MapFeatureBuilderImpl(emptyMap()).apply(builder).build()
val feature = MapFeatureGroup(map, zoomRange)
return addFeature(id, feature)
}
public fun MapFeatureBuilder.featureSelector(
id: FeatureId? = null,
onSelect: MapFeatureBuilder.(zoom: Int) -> MapFeature
): FeatureId = addFeature(
id = id,
feature = MapFeatureSelector(
selector = { onSelect(this, it) }
)
)

View File

@ -0,0 +1,40 @@
package center.sciprog.maps.compose
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.NativeCanvas
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
import center.sciprog.maps.coordinates.GmcBox
public expect class Font constructor() {
public var size: Float
}
public expect fun NativeCanvas.drawString(text: String, x: Float, y: Float, font: Font, color: Color)
public class MapTextFeature(
public val position: GeodeticMapCoordinates,
public val text: String,
override val zoomRange: IntRange = defaultZoomRange,
public val color: Color,
public val fontConfig: Font.() -> Unit,
) : MapFeature {
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position)
}
public fun MapFeatureBuilder.text(
position: GeodeticMapCoordinates,
text: String,
zoomRange: IntRange = defaultZoomRange,
color: Color = Color.Red,
font: Font.() -> Unit = { size = 16f },
id: FeatureId? = null,
): FeatureId = addFeature(id, MapTextFeature(position, text, zoomRange, color, font))
public fun MapFeatureBuilder.text(
position: Pair<Double, Double>,
text: String,
zoomRange: IntRange = defaultZoomRange,
color: Color = Color.Red,
font: Font.() -> Unit = { size = 16f },
id: FeatureId? = null,
): FeatureId = addFeature(id, MapTextFeature(position.toCoordinates(), text, zoomRange, color, font))

View File

@ -1,8 +1,8 @@
package center.sciprog.maps.compose package center.sciprog.maps.compose
import androidx.compose.ui.graphics.ImageBitmap
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import org.jetbrains.skia.Image
import kotlin.math.floor import kotlin.math.floor
public data class TileId( public data class TileId(
@ -13,7 +13,7 @@ public data class TileId(
public data class MapTile( public data class MapTile(
val id: TileId, val id: TileId,
val image: Image, val image: ImageBitmap,
) )
public interface MapTileProvider { public interface MapTileProvider {
@ -21,9 +21,9 @@ public interface MapTileProvider {
public val tileSize: Int get() = DEFAULT_TILE_SIZE public val tileSize: Int get() = DEFAULT_TILE_SIZE
public fun toIndex(d: Float): Int = floor(d / tileSize).toInt() public fun toIndex(d: Double): Int = floor(d / tileSize).toInt()
public fun toCoordinate(i: Int): Float = (i * tileSize).toFloat() public fun toCoordinate(i: Int): Double = (i * tileSize).toDouble()
public companion object { public companion object {
public const val DEFAULT_TILE_SIZE: Int = 256 public const val DEFAULT_TILE_SIZE: Int = 256

View File

@ -3,68 +3,87 @@ 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.ui.Modifier import androidx.compose.ui.Modifier
import center.sciprog.maps.coordinates.Gmc import androidx.compose.ui.unit.Density
import center.sciprog.maps.features.* import androidx.compose.ui.unit.DpSize
import center.sciprog.maps.coordinates.*
import kotlin.math.PI
import kotlin.math.log2
import kotlin.math.min
//TODO consider replacing by modifier
/**
* @param onDrag - returns true if you want to drag a map and false, if you want to make map stationary.
* start - is a point where drag begins, end is a point where drag ends
*/
public data class MapViewConfig(
val zoomSpeed: Double = 1.0 / 3.0,
val onClick: MapViewPoint.() -> Unit = {},
val onDrag: Density.(start: MapViewPoint, end: MapViewPoint) -> Boolean = { _, _ -> true },
val onViewChange: MapViewPoint.() -> Unit = {},
val onSelect: (GmcBox) -> Unit = {},
val zoomOnSelect: Boolean = true,
val resetViewPoint: Boolean = false
)
@Composable @Composable
public expect fun MapView( public expect fun MapView(
viewScope: MapViewScope, modifier: Modifier = Modifier,
features: FeatureGroup<Gmc>, mapViewState: MapViewState,
modifier: Modifier = Modifier.fillMaxSize(), mapViewConfig: MapViewConfig,
) )
/**
* A builder for a Map with static features.
*/
@Composable @Composable
public fun MapView( public fun MapView(
mapTileProvider: MapTileProvider, mapTileProvider: MapTileProvider,
features: FeatureGroup<Gmc>, initialViewPoint: MapViewPoint,
initialViewPoint: ViewPoint<Gmc>? = null, features: Map<FeatureId, MapFeature> = emptyMap(),
initialRectangle: Rectangle<Gmc>? = null, config: MapViewConfig = MapViewConfig(),
config: ViewConfig<Gmc> = ViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(), modifier: Modifier = Modifier.fillMaxSize(),
buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
) { ) {
val featuresBuilder = MapFeatureBuilderImpl(features)
val mapState: MapViewScope = MapViewScope.remember( featuresBuilder.buildFeatures()
mapTileProvider, MapView(
config, mapViewState = MapViewState(
initialViewPoint = initialViewPoint, mapTileProvider = mapTileProvider,
initialRectangle = initialRectangle ?: features.getBoundingBox(Float.MAX_VALUE), initialViewPoint = { initialViewPoint },
) features = featuresBuilder.build(),
MapView(mapState, features, modifier)
}
/**
* 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
*/
@Composable
public fun MapView(
mapTileProvider: MapTileProvider,
initialViewPoint: ViewPoint<Gmc>? = null,
initialRectangle: Rectangle<Gmc>? = null,
config: ViewConfig<Gmc> = ViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
buildFeatures: FeatureGroup<Gmc>.() -> Unit = {},
) {
val featureState = FeatureGroup.remember(WebMercatorSpace, buildFeatures)
val mapState: MapViewScope = MapViewScope.remember(
mapTileProvider,
config,
initialViewPoint = initialViewPoint,
initialRectangle = initialRectangle ?: featureState.features.computeBoundingBox(
WebMercatorSpace,
Float.MAX_VALUE
), ),
mapViewConfig = config,
modifier = modifier
)
}
internal fun GmcBox.computeViewPoint(mapTileProvider: MapTileProvider): (canvasSize: DpSize) -> MapViewPoint =
{ canvasSize ->
val zoom = log2(
min(
canvasSize.width.value / width,
canvasSize.height.value / height
) * PI / mapTileProvider.tileSize
)
MapViewPoint(center, zoom)
}
@Composable
public fun MapView(
mapTileProvider: MapTileProvider,
box: GmcBox,
features: Map<FeatureId, MapFeature> = emptyMap(),
config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
) {
val featuresBuilder = MapFeatureBuilderImpl(features)
featuresBuilder.buildFeatures()
MapView(
mapViewState = MapViewState(
mapTileProvider = mapTileProvider,
features = featuresBuilder.build(),
initialViewPoint = box.computeViewPoint(mapTileProvider),
),
modifier = modifier,
mapViewConfig = config,
) )
MapView(mapState, featureState, modifier)
} }

View File

@ -1,20 +0,0 @@
package center.sciprog.maps.compose
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
import center.sciprog.maps.coordinates.Gmc
import center.sciprog.maps.coordinates.WebMercatorProjection
import center.sciprog.maps.features.ViewPoint
/**
* Observable position on the map. Includes observation coordinate and [zoom] factor
*/
internal data class MapViewPoint(
override val focus: GeodeticMapCoordinates,
override val zoom: Float,
) : ViewPoint<Gmc>{
val scaleFactor: Float by lazy { WebMercatorProjection.scaleFactor(zoom) }
public companion object{
public val globe: MapViewPoint = MapViewPoint(Gmc.ofRadians(0.0, 0.0), 1f)
}
}

View File

@ -1,100 +0,0 @@
package center.sciprog.maps.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.DpRect
import androidx.compose.ui.unit.dp
import center.sciprog.maps.coordinates.Gmc
import center.sciprog.maps.coordinates.MercatorProjection
import center.sciprog.maps.coordinates.WebMercatorCoordinates
import center.sciprog.maps.coordinates.WebMercatorProjection
import center.sciprog.maps.features.*
import space.kscience.kmath.geometry.radians
import kotlin.math.*
public class MapViewScope internal constructor(
public val mapTileProvider: MapTileProvider,
config: ViewConfig<Gmc>,
) : CoordinateViewScope<Gmc>(config) {
override val space: CoordinateSpace<Gmc> get() = WebMercatorSpace
private val scaleFactor: Float
get() = WebMercatorProjection.scaleFactor(zoom)
public val intZoom: Int get() = floor(zoom).toInt()
public val centerCoordinates: WebMercatorCoordinates
get() = WebMercatorProjection.toMercator(viewPoint.focus, intZoom) ?: WebMercatorCoordinates(intZoom, 0f, 0f)
public val tileScale: Float
get() = 2f.pow(zoom - floor(zoom))
/*
* Convert screen independent offset to GMC, adjusting for fractional zoom
*/
override fun DpOffset.toCoordinates(): Gmc {
val mercator = WebMercatorCoordinates(
intZoom,
(x - canvasSize.width / 2).value / tileScale + centerCoordinates.x,
(y - canvasSize.height / 2).value / tileScale + centerCoordinates.y,
)
return WebMercatorProjection.toGeodetic(mercator)
}
override fun Gmc.toDpOffset(): DpOffset {
val mercator = WebMercatorProjection.toMercator(this, intZoom) ?: WebMercatorCoordinates(intZoom, 0f, 0f)
return DpOffset(
(canvasSize.width / 2 + (mercator.x.dp - centerCoordinates.x.dp) * tileScale),
(canvasSize.height / 2 + (mercator.y.dp - centerCoordinates.y.dp) * tileScale)
)
}
override fun Rectangle<Gmc>.toDpRect(): DpRect {
val topLeft = topLeft.toDpOffset()
val bottomRight = bottomRight.toDpOffset()
return DpRect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y)
}
override fun computeViewPoint(rectangle: Rectangle<Gmc>): ViewPoint<Gmc> {
val zoom = log2(
min(
canvasSize.width.value / rectangle.longitudeDelta.radians,
canvasSize.height.value / rectangle.latitudeDelta.radians
) * 2 * PI / mapTileProvider.tileSize
)
return space.ViewPoint(rectangle.center, zoom.toFloat())
}
override fun ViewPoint<Gmc>.moveBy(x: Dp, y: Dp): ViewPoint<Gmc> {
val deltaX = x.value / tileScale
val deltaY = y.value / tileScale
val newCoordinates = Gmc.normalized(
(focus.latitude + (deltaY / scaleFactor).radians).coerceIn(
-MercatorProjection.MAXIMUM_LATITUDE,
MercatorProjection.MAXIMUM_LATITUDE
),
focus.longitude + (deltaX / scaleFactor).radians
)
return space.ViewPoint(newCoordinates, zoom)
}
public companion object {
@Composable
public fun remember(
mapTileProvider: MapTileProvider,
config: ViewConfig<Gmc> = ViewConfig(),
initialViewPoint: ViewPoint<Gmc>? = null,
initialRectangle: Rectangle<Gmc>? = null,
): MapViewScope = remember {
MapViewScope(mapTileProvider, config).also { mapState ->
if (initialViewPoint != null) {
mapState.viewPoint = initialViewPoint
} else if (initialRectangle != null) {
mapState.viewPoint = mapState.computeViewPoint(initialRectangle)
}
}
}
}
}

View File

@ -0,0 +1,181 @@
package center.sciprog.maps.compose
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import center.sciprog.maps.coordinates.*
import mu.KotlinLogging
import kotlin.math.*
@Composable
public fun MapViewState(
initialViewPoint: (canvasSize: DpSize) -> MapViewPoint,
mapTileProvider: MapTileProvider,
features: Map<FeatureId, MapFeature> = emptyMap(),
inferViewBoxFromFeatures: Boolean = false,
buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
): MapViewState {
val featuresBuilder = MapFeatureBuilderImpl(features)
featuresBuilder.buildFeatures()
return MapViewState(
initialViewPoint = initialViewPoint,
mapTileProvider = mapTileProvider,
features = featuresBuilder.build(),
inferViewBoxFromFeatures = inferViewBoxFromFeatures
)
}
public class MapViewState(
public val initialViewPoint: (canvasSize: DpSize) -> MapViewPoint,
public val mapTileProvider: MapTileProvider,
public val features: Map<FeatureId, MapFeature> = emptyMap(),
inferViewBoxFromFeatures: Boolean = false,
) {
public var canvasSize: DpSize by mutableStateOf(DpSize(512.dp, 512.dp))
public var viewPointInternal: MapViewPoint? by mutableStateOf(null)
public val viewPoint: MapViewPoint by derivedStateOf {
viewPointInternal ?: if (inferViewBoxFromFeatures) {
features.values.computeBoundingBox(1)?.let { box ->
val zoom = log2(
min(
canvasSize.width.value / box.width,
canvasSize.height.value / box.height
) * PI / mapTileProvider.tileSize
)
MapViewPoint(box.center, zoom)
} ?: initialViewPoint(canvasSize)
} else {
initialViewPoint(canvasSize)
}
}
public val zoom: Int by derivedStateOf { floor(viewPoint.zoom).toInt() }
public val tileScale: Double by derivedStateOf { 2.0.pow(viewPoint.zoom - zoom) }
public val mapTiles: SnapshotStateList<MapTile> = mutableStateListOf()
public val centerCoordinates: WebMercatorCoordinates by derivedStateOf {
WebMercatorProjection.toMercator(
viewPoint.focus,
zoom
)
}
public fun DpOffset.toMercator(): WebMercatorCoordinates = WebMercatorCoordinates(
zoom,
(x - canvasSize.width / 2).value / tileScale + centerCoordinates.x,
(y - canvasSize.height / 2).value / tileScale + centerCoordinates.y,
)
/*
* Convert screen independent offset to GMC, adjusting for fractional zoom
*/
public fun DpOffset.toGeodetic(): GeodeticMapCoordinates =
with(this@MapViewState) { WebMercatorProjection.toGeodetic(toMercator()) }
// Selection rectangle. If null - no selection
public var selectRect: Rect? by mutableStateOf(null)
public fun WebMercatorCoordinates.toOffset(density: Density): Offset =
with(density) {
with(this@MapViewState) {
Offset(
(canvasSize.width / 2 + (x.dp - centerCoordinates.x.dp) * tileScale.toFloat()).toPx(),
(canvasSize.height / 2 + (y.dp - centerCoordinates.y.dp) * tileScale.toFloat()).toPx()
)
}
}
//Convert GMC to offset in pixels (not DP), adjusting for zoom
public fun GeodeticMapCoordinates.toOffset(density: Density): Offset =
WebMercatorProjection.toMercator(this, zoom).toOffset(density)
private val logger = KotlinLogging.logger("MapViewState")
public fun DrawScope.drawFeature(zoom: Int, feature: MapFeature) {
when (feature) {
is MapFeatureSelector -> drawFeature(zoom, feature.selector(zoom))
is MapCircleFeature -> drawCircle(
feature.color,
feature.size,
center = feature.center.toOffset(this@drawFeature)
)
is MapRectangleFeature -> drawRect(
feature.color,
topLeft = feature.center.toOffset(this@drawFeature) - Offset(
feature.size.width.toPx() / 2,
feature.size.height.toPx() / 2
),
size = feature.size.toSize()
)
is MapLineFeature -> drawLine(
feature.color,
feature.a.toOffset(this@drawFeature),
feature.b.toOffset(this@drawFeature)
)
is MapArcFeature -> {
val topLeft = feature.oval.topLeft.toOffset(this@drawFeature)
val bottomRight = feature.oval.bottomRight.toOffset(this@drawFeature)
val path = Path().apply {
addArcRad(Rect(topLeft, bottomRight), feature.startAngle, feature.endAngle - feature.startAngle)
}
drawPath(path, color = feature.color, style = Stroke())
}
is MapBitmapImageFeature -> drawImage(
image = feature.image,
topLeft = feature.position.toOffset(this@drawFeature)
)
is MapVectorImageFeature -> {
val offset = feature.position.toOffset(this@drawFeature)
val size = feature.size.toSize()
translate(offset.x - size.width / 2, offset.y - size.height / 2) {
with(feature.painter) {
draw(size)
}
}
}
is MapTextFeature -> drawIntoCanvas { canvas ->
val offset = feature.position.toOffset(this@drawFeature)
canvas.nativeCanvas.drawString(
feature.text,
offset.x + 5,
offset.y - 5,
Font().apply(feature.fontConfig),
feature.color
)
}
is MapDrawFeature -> {
val offset = feature.position.toOffset(this)
translate(offset.x, offset.y) {
feature.drawFeature(this)
}
}
is MapFeatureGroup -> {
feature.children.values.forEach {
drawFeature(zoom, it)
}
}
else -> {
logger.error { "Unrecognized feature type: ${feature::class}" }
}
}
}
}

View File

@ -1,136 +0,0 @@
package center.sciprog.maps.compose
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import center.sciprog.maps.coordinates.*
import center.sciprog.maps.features.CoordinateSpace
import center.sciprog.maps.features.Rectangle
import center.sciprog.maps.features.ViewPoint
import space.kscience.kmath.geometry.Angle
import space.kscience.kmath.geometry.radians
import kotlin.math.abs
import kotlin.math.floor
import kotlin.math.pow
public object WebMercatorSpace : CoordinateSpace<Gmc> {
private fun intZoom(zoom: Float): Int = floor(zoom).toInt()
private fun tileScale(zoom: Float): Float = 2f.pow(zoom - floor(zoom))
override fun Rectangle(first: Gmc, second: Gmc): Rectangle<Gmc> = GmcRectangle(first, second)
override fun Rectangle(center: Gmc, zoom: Float, size: DpSize): Rectangle<Gmc> {
val scale = WebMercatorProjection.scaleFactor(zoom)
return Rectangle(center, (size.width.value / scale).radians, (size.height.value / scale).radians)
}
override val defaultViewPoint: ViewPoint<Gmc> = MapViewPoint.globe
override fun ViewPoint(center: Gmc, zoom: Float): ViewPoint<Gmc> = MapViewPoint(center, zoom)
override fun ViewPoint<Gmc>.moveBy(delta: Gmc): ViewPoint<Gmc> {
val newCoordinates = Gmc.normalized(
(focus.latitude + delta.latitude).coerceIn(
-MercatorProjection.MAXIMUM_LATITUDE,
MercatorProjection.MAXIMUM_LATITUDE
),
focus.longitude + delta.longitude
)
return MapViewPoint(newCoordinates, zoom)
}
override fun ViewPoint<Gmc>.zoomBy(zoomDelta: Float, invariant: Gmc): ViewPoint<Gmc> = if (invariant == focus) {
ViewPoint(focus, (zoom + zoomDelta).coerceIn(2f, 18f))
} else {
val difScale = (1 - 2f.pow(-zoomDelta))
val newCenter = Gmc.normalized(
focus.latitude + (invariant.latitude - focus.latitude) * difScale,
focus.longitude + (invariant.longitude - focus.longitude) * difScale
)
MapViewPoint(newCenter, (zoom + zoomDelta).coerceIn(2f, 18f))
}
override fun Rectangle<Gmc>.withCenter(center: Gmc): Rectangle<Gmc> =
Rectangle(center, height = latitudeDelta, width = longitudeDelta)
override fun Collection<Rectangle<Gmc>>.wrapRectangles(): Rectangle<Gmc>? {
if (isEmpty()) return null
//TODO optimize computation
val minLat = minOf { it.bottom }
val maxLat = maxOf { it.top }
val minLong = minOf { it.left }
val maxLong = maxOf { it.right }
return GmcRectangle(Gmc.normalized(minLat, minLong), Gmc.normalized(maxLat, maxLong))
}
override fun Collection<Gmc>.wrapPoints(): Rectangle<Gmc>? {
if (isEmpty()) return null
//TODO optimize computation
val minLat = minOf { it.latitude }
val maxLat = maxOf { it.latitude }
val minLong = minOf { it.longitude }
val maxLong = maxOf { it.longitude }
return GmcRectangle(Gmc.normalized(minLat, minLong), Gmc.normalized(maxLat, maxLong))
}
override fun Gmc.offsetTo(b: Gmc, zoom: Float): DpOffset {
val intZoom = intZoom(zoom)
val mercatorA = WebMercatorProjection.toMercator(this, intZoom) ?: WebMercatorCoordinates(intZoom, 0f, 0f)
val mercatorB = WebMercatorProjection.toMercator(b, intZoom) ?: WebMercatorCoordinates(intZoom, 0f, 0f)
val tileScale = tileScale(zoom)
return DpOffset(
(mercatorA.x - mercatorB.x).dp * tileScale,
(mercatorA.y - mercatorB.y).dp * tileScale
)
}
override fun Gmc.isInsidePolygon(points: List<Gmc>): Boolean = points.zipWithNext().count { (left, right) ->
//using raytracing algorithm with the ray pointing "up"
val longitudeRange = if (right.longitude >= left.longitude) {
left.longitude..right.longitude
} else {
right.longitude..left.longitude
}
if (longitude !in longitudeRange) return@count false
val longitudeDelta = right.longitude - left.longitude
left.latitude * abs((right.longitude - longitude) / longitudeDelta) +
right.latitude * abs((longitude - left.longitude) / longitudeDelta) >= latitude
} % 2 == 1
}
/**
* A quasi-square section. Note that latitudinal distance could be imprecise for large distances
*/
public fun CoordinateSpace<Gmc>.Rectangle(
center: Gmc,
height: Distance,
width: Distance,
ellipsoid: GeoEllipsoid = GeoEllipsoid.WGS84,
): Rectangle<Gmc> {
val reducedRadius = ellipsoid.reducedRadius(center.latitude)
return Rectangle(center, (height / ellipsoid.polarRadius).radians, (width / reducedRadius).radians)
}
/**
* A quasi-square section.
*/
public fun CoordinateSpace<Gmc>.Rectangle(
center: GeodeticMapCoordinates,
height: Angle,
width: Angle,
): Rectangle<Gmc> {
val a = Gmc.normalized(
center.latitude - (height / 2),
center.longitude - (width / 2)
)
val b = Gmc.normalized(
center.latitude + (height / 2),
center.longitude + (width / 2)
)
return GmcRectangle(a, b)
}

View File

@ -1,143 +0,0 @@
package center.sciprog.maps.compose
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import center.sciprog.maps.coordinates.Distance
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
import center.sciprog.maps.coordinates.Gmc
import center.sciprog.maps.coordinates.GmcCurve
import center.sciprog.maps.features.*
import space.kscience.kmath.geometry.Angle
import kotlin.math.ceil
internal fun FeatureGroup<Gmc>.coordinatesOf(pair: Pair<Number, Number>) =
GeodeticMapCoordinates.ofDegrees(pair.first.toDouble(), pair.second.toDouble())
public typealias MapFeature = Feature<Gmc>
public fun FeatureGroup<Gmc>.circle(
centerCoordinates: Pair<Number, Number>,
size: Dp = 5.dp,
id: String? = null,
): FeatureRef<Gmc, CircleFeature<Gmc>> = feature(
id, CircleFeature(space, coordinatesOf(centerCoordinates), size)
)
public fun FeatureGroup<Gmc>.rectangle(
centerCoordinates: Pair<Number, Number>,
size: DpSize = DpSize(5.dp, 5.dp),
id: String? = null,
): FeatureRef<Gmc, RectangleFeature<Gmc>> = feature(
id, RectangleFeature(space, coordinatesOf(centerCoordinates), size)
)
public fun FeatureGroup<Gmc>.draw(
position: Pair<Number, Number>,
id: String? = null,
draw: DrawScope.() -> Unit,
): FeatureRef<Gmc, DrawFeature<Gmc>> = feature(
id,
DrawFeature(space, coordinatesOf(position), drawFeature = draw)
)
public fun FeatureGroup<Gmc>.line(
curve: GmcCurve,
id: String? = null,
): FeatureRef<Gmc, LineFeature<Gmc>> = feature(
id,
LineFeature(space, curve.forward.coordinates, curve.backward.coordinates)
)
public fun FeatureGroup<Gmc>.line(
aCoordinates: Pair<Double, Double>,
bCoordinates: Pair<Double, Double>,
id: String? = null,
): FeatureRef<Gmc, LineFeature<Gmc>> = feature(
id,
LineFeature(space, coordinatesOf(aCoordinates), coordinatesOf(bCoordinates))
)
public fun FeatureGroup<Gmc>.arc(
center: Pair<Double, Double>,
radius: Distance,
startAngle: Angle,
arcLength: Angle,
id: String? = null,
): FeatureRef<Gmc, ArcFeature<Gmc>> = feature(
id,
ArcFeature(
space,
oval = space.Rectangle(coordinatesOf(center), radius, radius),
startAngle = startAngle,
arcLength = arcLength,
)
)
public fun FeatureGroup<Gmc>.points(
points: List<Pair<Double, Double>>,
id: String? = null,
): FeatureRef<Gmc, PointsFeature<Gmc>> = feature(id, PointsFeature(space, points.map(::coordinatesOf)))
public fun FeatureGroup<Gmc>.multiLine(
points: List<Pair<Double, Double>>,
id: String? = null,
): FeatureRef<Gmc, MultiLineFeature<Gmc>> = feature(id, MultiLineFeature(space, points.map(::coordinatesOf)))
public fun FeatureGroup<Gmc>.icon(
position: Pair<Double, Double>,
image: ImageVector,
size: DpSize = DpSize(20.dp, 20.dp),
id: String? = null,
): FeatureRef<Gmc, VectorIconFeature<Gmc>> = feature(
id,
VectorIconFeature(
space,
coordinatesOf(position),
size,
image,
)
)
public fun FeatureGroup<Gmc>.text(
position: Pair<Double, Double>,
text: String,
font: FeatureFont.() -> Unit = { size = 16f },
id: String? = null,
): FeatureRef<Gmc, TextFeature<Gmc>> = feature(
id,
TextFeature(space, coordinatesOf(position), text, fontConfig = font)
)
public fun FeatureGroup<Gmc>.pixelMap(
rectangle: Rectangle<Gmc>,
latitudeDelta: Angle,
longitudeDelta: Angle,
id: String? = null,
builder: (Gmc) -> Color?,
): FeatureRef<Gmc, PixelMapFeature<Gmc>> = feature(
id,
PixelMapFeature(
space,
rectangle,
Structure2D(
ceil(rectangle.longitudeDelta / latitudeDelta).toInt(),
ceil(rectangle.latitudeDelta / longitudeDelta).toInt()
) { (i, j) ->
val longitude = rectangle.left + longitudeDelta * i
val latitude = rectangle.bottom + latitudeDelta * j
builder(
Gmc(latitude, longitude)
)
}
)
)

View File

@ -0,0 +1,23 @@
package center.sciprog.maps.compose
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.NativeCanvas
import androidx.compose.ui.graphics.toArgb
import org.jetbrains.skia.Paint
public actual typealias Font = org.jetbrains.skia.Font
public actual fun NativeCanvas.drawString(
text: String,
x: Float,
y: Float,
font: Font,
color: Color
) {
drawString(text, x, y, font, color.toPaint())
}
private fun Color.toPaint(): Paint = Paint().apply {
isAntiAlias = true
color = toArgb()
}

View File

@ -0,0 +1,213 @@
package center.sciprog.maps.compose
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.drag
import androidx.compose.foundation.gestures.forEachGesture
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.drawscope.*
import androidx.compose.ui.input.pointer.*
import androidx.compose.ui.unit.*
import center.sciprog.maps.coordinates.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
import mu.KotlinLogging
import kotlin.math.*
private fun IntRange.intersect(other: IntRange) = max(first, other.first)..min(last, other.last)
internal fun MapViewPoint.move(deltaX: Double, deltaY: Double): MapViewPoint {
val newCoordinates = GeodeticMapCoordinates.ofRadians(
(focus.latitude + deltaY / scaleFactor).coerceIn(
-MercatorProjection.MAXIMUM_LATITUDE,
MercatorProjection.MAXIMUM_LATITUDE
),
focus.longitude + deltaX / scaleFactor
)
return MapViewPoint(newCoordinates, zoom)
}
private val logger = KotlinLogging.logger("MapView")
/**
* A component that renders map and provides basic map manipulation capabilities
*/
@Composable
public actual fun MapView(
modifier: Modifier,
mapViewState: MapViewState,
mapViewConfig: MapViewConfig,
) {
with(mapViewState) {
@OptIn(ExperimentalComposeUiApi::class)
val canvasModifier = modifier.pointerInput(Unit) {
forEachGesture {
awaitPointerEventScope {
fun Offset.toDpOffset() = DpOffset(x.toDp(), y.toDp())
val event: PointerEvent = awaitPointerEvent()
event.changes.forEach { change ->
if (event.buttons.isPrimaryPressed) {
//Evaluating selection frame
if (event.keyboardModifiers.isShiftPressed) {
selectRect = Rect(change.position, change.position)
drag(change.id) { dragChange ->
selectRect?.let { rect ->
val offset = dragChange.position
selectRect = Rect(
min(offset.x, rect.left),
min(offset.y, rect.top),
max(offset.x, rect.right),
max(offset.y, rect.bottom)
)
}
}
selectRect?.let { rect ->
//Use selection override if it is defined
val gmcBox = GmcBox(
rect.topLeft.toDpOffset().toGeodetic(),
rect.bottomRight.toDpOffset().toGeodetic()
)
mapViewConfig.onSelect(gmcBox)
if (mapViewConfig.zoomOnSelect) {
val newViewPoint = gmcBox.computeViewPoint(mapTileProvider).invoke(canvasSize)
mapViewConfig.onViewChange(newViewPoint)
viewPointInternal = newViewPoint
}
selectRect = null
}
} else {
val dragStart = change.position
val dpPos = DpOffset(dragStart.x.toDp(), dragStart.y.toDp())
mapViewConfig.onClick(
MapViewPoint(
dpPos.toGeodetic() ,
viewPoint.zoom
)
)
drag(change.id) { dragChange ->
val dragAmount = dragChange.position - dragChange.previousPosition
val dpStart =
DpOffset(
dragChange.previousPosition.x.toDp(),
dragChange.previousPosition.y.toDp()
)
val dpEnd = DpOffset(dragChange.position.x.toDp(), dragChange.position.y.toDp())
if (!mapViewConfig.onDrag(
this,
MapViewPoint(dpStart.toGeodetic(), viewPoint.zoom),
MapViewPoint(dpEnd.toGeodetic(), viewPoint.zoom)
)
) return@drag
val newViewPoint = viewPoint.move(
-dragAmount.x.toDp().value / tileScale,
+dragAmount.y.toDp().value / tileScale
)
mapViewConfig.onViewChange(newViewPoint)
viewPointInternal = newViewPoint
}
}
}
}
}
}
}.onPointerEvent(PointerEventType.Scroll) {
val change = it.changes.first()
val (xPos, yPos) = change.position
//compute invariant point of translation
val invariant = DpOffset(xPos.toDp(), yPos.toDp()).toGeodetic()
val newViewPoint = viewPoint.zoom(-change.scrollDelta.y.toDouble() * mapViewConfig.zoomSpeed, invariant)
mapViewConfig.onViewChange(newViewPoint)
viewPointInternal = newViewPoint
}.fillMaxSize()
// Load tiles asynchronously
LaunchedEffect(viewPoint, canvasSize) {
with(mapTileProvider) {
val indexRange = 0 until 2.0.pow(zoom).toInt()
val left = centerCoordinates.x - canvasSize.width.value / 2 / tileScale
val right = centerCoordinates.x + canvasSize.width.value / 2 / tileScale
val horizontalIndices: IntRange = (toIndex(left)..toIndex(right)).intersect(indexRange)
val top = (centerCoordinates.y + canvasSize.height.value / 2 / tileScale)
val bottom = (centerCoordinates.y - canvasSize.height.value / 2 / tileScale)
val verticalIndices: IntRange = (toIndex(bottom)..toIndex(top)).intersect(indexRange)
mapTiles.clear()
for (j in verticalIndices) {
for (i in horizontalIndices) {
val id = TileId(zoom, i, j)
//start all
val deferred = loadTileAsync(id)
//wait asynchronously for it to finish
launch {
try {
mapTiles += deferred.await()
} catch (ex: Exception) {
if (ex !is CancellationException) {
//displaying the error is maps responsibility
logger.error(ex) { "Failed to load tile with id=$id" }
}
}
}
}
}
}
}
Canvas(canvasModifier) {
if (mapViewState.canvasSize != size.toDpSize()) {
mapViewState.canvasSize = size.toDpSize()
logger.debug { "Recalculate canvas. Size: $size" }
}
clipRect {
val tileSize = IntSize(
ceil((mapTileProvider.tileSize.dp * tileScale.toFloat()).toPx()).toInt(),
ceil((mapTileProvider.tileSize.dp * tileScale.toFloat()).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.toFloat()).roundToPx(),
(canvasSize.height / 2 + (mapTileProvider.toCoordinate(id.j).dp - centerCoordinates.y.dp) * tileScale.toFloat()).roundToPx()
)
drawImage(
image = image,
dstOffset = offset,
dstSize = tileSize
)
}
features.values.filter { zoom in it.zoomRange }.forEach { feature ->
drawFeature(zoom, feature)
}
}
selectRect?.let { rect ->
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,8 +1,11 @@
package center.sciprog.maps.compose package center.sciprog.maps.compose
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toComposeImageBitmap
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.readBytes import io.ktor.client.statement.readBytes
import io.ktor.utils.io.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -21,26 +24,25 @@ import kotlin.io.path.*
public class OpenStreetMapTileProvider( public class OpenStreetMapTileProvider(
private val client: HttpClient, private val client: HttpClient,
private val cacheDirectory: Path, private val cacheDirectory: Path,
parallelism: Int = 4, parallelism: Int = 1,
cacheCapacity: Int = 200, cacheCapacity: Int = 200,
private val osmBaseUrl: String = "https://tile.openstreetmap.org",
) : MapTileProvider { ) : MapTileProvider {
private val semaphore = Semaphore(parallelism) private val semaphore = Semaphore(parallelism)
private val cache = LruCache<TileId, Deferred<Image>>(cacheCapacity) private val cache = LruCache<TileId, Deferred<ImageBitmap>>(cacheCapacity)
private fun TileId.osmUrl() = URL("$osmBaseUrl/${zoom}/${i}/${j}.png") private fun TileId.osmUrl() = URL("https://tile.openstreetmap.org/${zoom}/${i}/${j}.png")
private fun TileId.cacheFilePath() = cacheDirectory.resolve("${zoom}/${i}/${j}.png") private fun TileId.cacheFilePath() = cacheDirectory.resolve("${zoom}/${i}/${j}.png")
/** /**
* Download and cache the tile image * Download and cache the tile image
*/ */
private fun CoroutineScope.downloadImageAsync(id: TileId): Deferred<Image> = async(Dispatchers.IO) { private fun CoroutineScope.downloadImageAsync(id: TileId) = async(Dispatchers.IO) {
id.cacheFilePath()?.let { path -> id.cacheFilePath()?.let { path ->
if (path.exists()) { if (path.exists()) {
try { try {
return@async Image.makeFromEncoded(path.readBytes()) return@async Image.makeFromEncoded(path.readBytes()).toComposeImageBitmap()
} catch (ex: Exception) { } catch (ex: Exception) {
logger.debug { "Failed to load image from $path" } logger.debug { "Failed to load image from $path" }
path.deleteIfExists() path.deleteIfExists()
@ -52,7 +54,9 @@ public class OpenStreetMapTileProvider(
semaphore.withPermit { semaphore.withPermit {
val url = id.osmUrl() val url = id.osmUrl()
val byteArray = client.get(url).readBytes() val byteArray = client.get(url).readBytes()
logger.debug { "Finished downloading map tile with id $id from $url" } logger.debug { "Finished downloading map tile with id $id from $url" }
id.cacheFilePath()?.let { path -> id.cacheFilePath()?.let { path ->
logger.debug { "Caching map tile $id to $path" } logger.debug { "Caching map tile $id to $path" }
@ -60,7 +64,7 @@ public class OpenStreetMapTileProvider(
path.writeBytes(byteArray) path.writeBytes(byteArray)
} }
Image.makeFromEncoded(byteArray) Image.makeFromEncoded(byteArray).toComposeImageBitmap()
} }
} }
@ -69,17 +73,21 @@ public class OpenStreetMapTileProvider(
): Deferred<MapTile> { ): Deferred<MapTile> {
//start image download //start image download
val imageDeferred: Deferred<Image> = cache.getOrPut(tileId) { val imageDeferred = cache.getOrPut(tileId) {
downloadImageAsync(tileId) downloadImageAsync(tileId)
} }
//collect the result asynchronously //collect the result asynchronously
return async { return async {
val image: Image = runCatching { imageDeferred.await() }.onFailure { val image = try {
logger.error(it) { "Failed to load tile image with id=$tileId" } imageDeferred.await()
} catch (ex: Exception) {
cache.remove(tileId) cache.remove(tileId)
}.getOrThrow() if(ex !is CancellationException) {
logger.error(ex) { "Failed to load tile image with id=$tileId" }
}
throw ex
}
MapTile(tileId, image) MapTile(tileId, image)
} }
} }

View File

@ -1,146 +0,0 @@
package center.sciprog.maps.compose
import androidx.compose.foundation.Canvas
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.clipRect
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.graphics.toComposeImageBitmap
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import center.sciprog.attributes.z
import center.sciprog.maps.coordinates.Gmc
import center.sciprog.maps.features.FeatureGroup
import center.sciprog.maps.features.PainterFeature
import center.sciprog.maps.features.drawFeature
import center.sciprog.maps.features.zoomRange
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import mu.KotlinLogging
import org.jetbrains.skia.Image
import org.jetbrains.skia.Paint
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
private fun Color.toPaint(): Paint = Paint().apply {
isAntiAlias = true
color = toArgb()
}
private fun IntRange.intersect(other: IntRange) = max(first, other.first)..min(last, other.last)
private val logger = KotlinLogging.logger("MapView")
/**
* A component that renders map and provides basic map manipulation capabilities
*/
@Composable
public actual fun MapView(
viewScope: MapViewScope,
features: FeatureGroup<Gmc>,
modifier: Modifier,
): Unit = with(viewScope) {
val mapTiles = remember(mapTileProvider) { mutableStateMapOf<TileId, Image>() }
// Load tiles asynchronously
LaunchedEffect(viewPoint, canvasSize) {
with(mapTileProvider) {
val indexRange = 0 until 2.0.pow(intZoom).toInt()
val left = centerCoordinates.x - canvasSize.width.value / 2 / tileScale
val right = centerCoordinates.x + canvasSize.width.value / 2 / tileScale
val horizontalIndices: IntRange = (toIndex(left)..toIndex(right)).intersect(indexRange)
val top = (centerCoordinates.y + canvasSize.height.value / 2 / tileScale)
val bottom = (centerCoordinates.y - canvasSize.height.value / 2 / tileScale)
val verticalIndices: IntRange = (toIndex(bottom)..toIndex(top)).intersect(indexRange)
for (j in verticalIndices) {
for (i in horizontalIndices) {
val id = TileId(intZoom, i, j)
//ensure that failed tiles do not fail the application
supervisorScope {
//start all
val deferred = loadTileAsync(id)
//wait asynchronously for it to finish
launch {
try {
val tile = deferred.await()
mapTiles[tile.id] = tile.image
} catch (ex: Exception) {
//displaying the error is maps responsibility
logger.error(ex) { "Failed to load tile with id=$id" }
}
}
}
mapTiles.keys.filter {
it.zoom != intZoom || it.j !in verticalIndices || it.i !in horizontalIndices
}.forEach {
mapTiles.remove(it)
}
}
}
}
}
key(viewScope, features) {
val painterCache: Map<PainterFeature<Gmc>, Painter> =
features.features.filterIsInstance<PainterFeature<Gmc>>().associateWith { it.getPainter() }
Canvas(modifier = modifier.mapControls(viewScope, features)) {
if (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
)
}
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,45 +0,0 @@
package center.sciprog.maps.compose
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import java.nio.file.Files
import kotlin.test.assertFails
@OptIn(ExperimentalCoroutinesApi::class)
class OsmTileProviderTest {
// @get:Rule
// val rule = createComposeRule()
@Test
fun testCorrectOsm() = runTest {
val provider = OpenStreetMapTileProvider(HttpClient(CIO), Files.createTempDirectory("mapCache"))
val tileId = TileId(3, 1, 1)
with(provider) {
loadTileAsync(tileId).await()
}
}
@Test
fun testFailedOsm() = runTest {
val provider = OpenStreetMapTileProvider(
HttpClient(CIO),
Files.createTempDirectory("mapCache"),
osmBaseUrl = "https://tile.openstreetmap1.org"
)
val tileId = TileId(3, 1, 1)
supervisorScope {
with(provider) {
val deferred = loadTileAsync(tileId)
assertFails {
deferred.await()
}
}
}
}
}

View File

@ -1,35 +0,0 @@
# Module kmath-core
The core interfaces of KMath.
- [angles and distances](#) : Type-safe angle and distance measurements.
- [ellipsoid](#) : Ellipsoid geometry and distances
- [mercator](#) : Mercator and web-mercator projections
## Artifact:
The Maven coordinates of this project are `center.sciprog:maps-kt-core:0.2.2`.
**Gradle Groovy:**
```groovy
repositories {
maven { url 'https://repo.kotlin.link' }
mavenCentral()
}
dependencies {
implementation 'center.sciprog:maps-kt-core:0.2.2'
}
```
**Gradle Kotlin DSL:**
```kotlin
repositories {
maven("https://repo.kotlin.link")
mavenCentral()
}
dependencies {
implementation("center.sciprog:maps-kt-core:0.2.2")
}
```

View File

@ -1,250 +0,0 @@
public final class center/sciprog/maps/coordinates/Distance : java/lang/Comparable {
public static final field Companion Lcenter/sciprog/maps/coordinates/Distance$Companion;
public static final synthetic fun box-impl (D)Lcenter/sciprog/maps/coordinates/Distance;
public synthetic fun compareTo (Ljava/lang/Object;)I
public fun compareTo-LPoRJxU (D)I
public static fun compareTo-LPoRJxU (DD)I
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (DLjava/lang/Object;)Z
public static final fun equals-impl0 (DD)Z
public final fun getKilometers ()D
public fun hashCode ()I
public static fun hashCode-impl (D)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (D)Ljava/lang/String;
public final synthetic fun unbox-impl ()D
}
public final class center/sciprog/maps/coordinates/Distance$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lcenter/sciprog/maps/coordinates/Distance$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize-h1Ihu8M (Lkotlinx/serialization/encoding/Decoder;)D
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize-xeUJnDc (Lkotlinx/serialization/encoding/Encoder;D)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class center/sciprog/maps/coordinates/Distance$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class center/sciprog/maps/coordinates/DistanceKt {
public static final fun div-XO6jDg8 (DLjava/lang/Number;)D
public static final fun div-yHr8yDM (DD)D
public static final fun getKilometers (Ljava/lang/Number;)D
public static final fun getMeters (Ljava/lang/Number;)D
public static final fun getMeters-LPoRJxU (D)D
public static final fun minus-yHr8yDM (DD)D
public static final fun plus-yHr8yDM (DD)D
public static final fun times-XO6jDg8 (DLjava/lang/Number;)D
}
public final class center/sciprog/maps/coordinates/GeoEllipsoid {
public static final field Companion Lcenter/sciprog/maps/coordinates/GeoEllipsoid$Companion;
public synthetic fun <init> (DDLkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (ILcenter/sciprog/maps/coordinates/Distance;Lcenter/sciprog/maps/coordinates/Distance;DDDDLkotlinx/serialization/internal/SerializationConstructorMarker;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getESquared ()D
public final fun getEccentricity ()D
public final fun getEquatorRadius-zipobaw ()D
public final fun getF ()D
public final fun getInverseF ()D
public final fun getPolarRadius-zipobaw ()D
public static final synthetic fun write$Self (Lcenter/sciprog/maps/coordinates/GeoEllipsoid;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}
public final class center/sciprog/maps/coordinates/GeoEllipsoid$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lcenter/sciprog/maps/coordinates/GeoEllipsoid$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcenter/sciprog/maps/coordinates/GeoEllipsoid;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcenter/sciprog/maps/coordinates/GeoEllipsoid;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class center/sciprog/maps/coordinates/GeoEllipsoid$Companion {
public final fun getGRS80 ()Lcenter/sciprog/maps/coordinates/GeoEllipsoid;
public final fun getSphere ()Lcenter/sciprog/maps/coordinates/GeoEllipsoid;
public final fun getWGS84 ()Lcenter/sciprog/maps/coordinates/GeoEllipsoid;
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class center/sciprog/maps/coordinates/GeoEllipsoidKt {
public static final fun reducedRadius (Lcenter/sciprog/maps/coordinates/GeoEllipsoid;Lspace/kscience/kmath/geometry/Angle;)D
}
public final class center/sciprog/maps/coordinates/GeodeticMapCoordinates : space/kscience/kmath/geometry/Vector2D {
public static final field Companion Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates$Companion;
public synthetic fun <init> (ILspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;Lcenter/sciprog/maps/coordinates/Distance;Lkotlinx/serialization/internal/SerializationConstructorMarker;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;Lcenter/sciprog/maps/coordinates/Distance;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;Lcenter/sciprog/maps/coordinates/Distance;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun equals (Ljava/lang/Object;)Z
public final fun getElevation-irfbOC8 ()Lcenter/sciprog/maps/coordinates/Distance;
public final fun getLatitude ()Lspace/kscience/kmath/geometry/Angle;
public final fun getLongitude ()Lspace/kscience/kmath/geometry/Angle;
public synthetic fun getX ()Ljava/lang/Object;
public fun getX ()Lspace/kscience/kmath/geometry/Angle;
public synthetic fun getY ()Ljava/lang/Object;
public fun getY ()Lspace/kscience/kmath/geometry/Angle;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public static final synthetic fun write$Self (Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}
public final class center/sciprog/maps/coordinates/GeodeticMapCoordinates$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class center/sciprog/maps/coordinates/GeodeticMapCoordinates$Companion {
public final fun normalized-8x94Bck (Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;Lcenter/sciprog/maps/coordinates/Distance;)Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;
public static synthetic fun normalized-8x94Bck$default (Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates$Companion;Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;Lcenter/sciprog/maps/coordinates/Distance;ILjava/lang/Object;)Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;
public final fun ofDegrees-8x94Bck (DDLcenter/sciprog/maps/coordinates/Distance;)Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;
public static synthetic fun ofDegrees-8x94Bck$default (Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates$Companion;DDLcenter/sciprog/maps/coordinates/Distance;ILjava/lang/Object;)Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;
public final fun ofRadians-8x94Bck (DDLcenter/sciprog/maps/coordinates/Distance;)Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;
public static synthetic fun ofRadians-8x94Bck$default (Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates$Companion;DDLcenter/sciprog/maps/coordinates/Distance;ILjava/lang/Object;)Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class center/sciprog/maps/coordinates/GmcCurve {
public synthetic fun <init> (Lcenter/sciprog/maps/coordinates/GmcPose;Lcenter/sciprog/maps/coordinates/GmcPose;DLkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getBackward ()Lcenter/sciprog/maps/coordinates/GmcPose;
public final fun getDistance-zipobaw ()D
public final fun getForward ()Lcenter/sciprog/maps/coordinates/GmcPose;
public fun toString ()Ljava/lang/String;
}
public final class center/sciprog/maps/coordinates/GmcCurveKt {
public static final fun contains (Lkotlin/ranges/ClosedRange;Lspace/kscience/kmath/geometry/Angle;)Z
public static final fun curveBetween (Lcenter/sciprog/maps/coordinates/GeoEllipsoid;Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;D)Lcenter/sciprog/maps/coordinates/GmcCurve;
public static synthetic fun curveBetween$default (Lcenter/sciprog/maps/coordinates/GeoEllipsoid;Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;DILjava/lang/Object;)Lcenter/sciprog/maps/coordinates/GmcCurve;
public static final fun curveInDirection-EeP4nfk (Lcenter/sciprog/maps/coordinates/GeoEllipsoid;Lcenter/sciprog/maps/coordinates/GmcPose;DD)Lcenter/sciprog/maps/coordinates/GmcCurve;
public static synthetic fun curveInDirection-EeP4nfk$default (Lcenter/sciprog/maps/coordinates/GeoEllipsoid;Lcenter/sciprog/maps/coordinates/GmcPose;DDILjava/lang/Object;)Lcenter/sciprog/maps/coordinates/GmcCurve;
public static final fun meridianCurve-L4GImx8 (Lcenter/sciprog/maps/coordinates/GeoEllipsoid;Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;D)Lcenter/sciprog/maps/coordinates/GmcCurve;
public static synthetic fun meridianCurve-L4GImx8$default (Lcenter/sciprog/maps/coordinates/GeoEllipsoid;Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;DILjava/lang/Object;)Lcenter/sciprog/maps/coordinates/GmcCurve;
public static final fun parallelCurve (Lcenter/sciprog/maps/coordinates/GeoEllipsoid;Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;)Lcenter/sciprog/maps/coordinates/GmcCurve;
public static final fun reversed (Lcenter/sciprog/maps/coordinates/GmcCurve;)Lcenter/sciprog/maps/coordinates/GmcCurve;
}
public final class center/sciprog/maps/coordinates/GmcPose {
public static final field Companion Lcenter/sciprog/maps/coordinates/GmcPose$Companion;
public synthetic fun <init> (ILcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;Lspace/kscience/kmath/geometry/Angle;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;Lspace/kscience/kmath/geometry/Angle;)V
public final fun component1 ()Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;
public final fun component2 ()Lspace/kscience/kmath/geometry/Angle;
public final fun copy (Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;Lspace/kscience/kmath/geometry/Angle;)Lcenter/sciprog/maps/coordinates/GmcPose;
public static synthetic fun copy$default (Lcenter/sciprog/maps/coordinates/GmcPose;Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;Lspace/kscience/kmath/geometry/Angle;ILjava/lang/Object;)Lcenter/sciprog/maps/coordinates/GmcPose;
public fun equals (Ljava/lang/Object;)Z
public final fun getBearing ()Lspace/kscience/kmath/geometry/Angle;
public final fun getCoordinates ()Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;
public final fun getLatitude ()Lspace/kscience/kmath/geometry/Angle;
public final fun getLongitude ()Lspace/kscience/kmath/geometry/Angle;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public static final synthetic fun write$Self (Lcenter/sciprog/maps/coordinates/GmcPose;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}
public final class center/sciprog/maps/coordinates/GmcPose$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lcenter/sciprog/maps/coordinates/GmcPose$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcenter/sciprog/maps/coordinates/GmcPose;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcenter/sciprog/maps/coordinates/GmcPose;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class center/sciprog/maps/coordinates/GmcPose$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class center/sciprog/maps/coordinates/GmcPoseKt {
public static final fun reversed (Lcenter/sciprog/maps/coordinates/GmcPose;)Lcenter/sciprog/maps/coordinates/GmcPose;
}
public abstract interface class center/sciprog/maps/coordinates/MapProjection {
public static final field Companion Lcenter/sciprog/maps/coordinates/MapProjection$Companion;
public abstract fun toGeodetic (Ljava/lang/Object;)Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;
public abstract fun toProjection (Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;)Ljava/lang/Object;
}
public final class center/sciprog/maps/coordinates/MapProjection$Companion {
public final fun getEpsg3857 ()Lcenter/sciprog/maps/coordinates/MercatorProjection;
}
public class center/sciprog/maps/coordinates/MercatorProjection : center/sciprog/maps/coordinates/MapProjection {
public static final field Companion Lcenter/sciprog/maps/coordinates/MercatorProjection$Companion;
public fun <init> ()V
public synthetic fun <init> (ILspace/kscience/kmath/geometry/Angle;Lcenter/sciprog/maps/coordinates/GeoEllipsoid;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (Lspace/kscience/kmath/geometry/Angle;Lcenter/sciprog/maps/coordinates/GeoEllipsoid;)V
public synthetic fun <init> (Lspace/kscience/kmath/geometry/Angle;Lcenter/sciprog/maps/coordinates/GeoEllipsoid;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getBaseLongitude ()Lspace/kscience/kmath/geometry/Angle;
public final fun getEllipsoid ()Lcenter/sciprog/maps/coordinates/GeoEllipsoid;
public fun toGeodetic (Lcenter/sciprog/maps/coordinates/ProjectionCoordinates;)Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;
public synthetic fun toGeodetic (Ljava/lang/Object;)Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;
public fun toProjection (Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;)Lcenter/sciprog/maps/coordinates/ProjectionCoordinates;
public synthetic fun toProjection (Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;)Ljava/lang/Object;
public static final synthetic fun write$Self (Lcenter/sciprog/maps/coordinates/MercatorProjection;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}
public final class center/sciprog/maps/coordinates/MercatorProjection$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lcenter/sciprog/maps/coordinates/MercatorProjection$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcenter/sciprog/maps/coordinates/MercatorProjection;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcenter/sciprog/maps/coordinates/MercatorProjection;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class center/sciprog/maps/coordinates/MercatorProjection$Companion {
public final fun getMAXIMUM_LATITUDE ()Lspace/kscience/kmath/geometry/Angle;
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class center/sciprog/maps/coordinates/ProjectionCoordinates {
public synthetic fun <init> (DDLkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1-zipobaw ()D
public final fun component2-zipobaw ()D
public final fun copy-yHr8yDM (DD)Lcenter/sciprog/maps/coordinates/ProjectionCoordinates;
public static synthetic fun copy-yHr8yDM$default (Lcenter/sciprog/maps/coordinates/ProjectionCoordinates;DDILjava/lang/Object;)Lcenter/sciprog/maps/coordinates/ProjectionCoordinates;
public fun equals (Ljava/lang/Object;)Z
public final fun getX-zipobaw ()D
public final fun getY-zipobaw ()D
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class center/sciprog/maps/coordinates/WebMercatorCoordinates {
public fun <init> (IFF)V
public final fun component1 ()I
public final fun component2 ()F
public final fun component3 ()F
public final fun copy (IFF)Lcenter/sciprog/maps/coordinates/WebMercatorCoordinates;
public static synthetic fun copy$default (Lcenter/sciprog/maps/coordinates/WebMercatorCoordinates;IFFILjava/lang/Object;)Lcenter/sciprog/maps/coordinates/WebMercatorCoordinates;
public fun equals (Ljava/lang/Object;)Z
public final fun getX ()F
public final fun getY ()F
public final fun getZoom ()I
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class center/sciprog/maps/coordinates/WebMercatorProjection {
public static final field INSTANCE Lcenter/sciprog/maps/coordinates/WebMercatorProjection;
public final fun scaleFactor (F)F
public final fun toGeodetic (Lcenter/sciprog/maps/coordinates/WebMercatorCoordinates;)Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;
public final fun toMercator (Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;I)Lcenter/sciprog/maps/coordinates/WebMercatorCoordinates;
}

View File

@ -1,34 +1,18 @@
plugins { plugins {
id("space.kscience.gradle.mpp") kotlin("multiplatform")
`maven-publish` `maven-publish`
} }
val kmathVersion: String by rootProject.extra val ktorVersion: String by rootProject.extra
kscience{ kotlin {
jvm() explicitApi = org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode.Warning
js() jvm {
useSerialization() compilations.all {
kotlinOptions.jvmTarget = "11"
dependencies{ }
api(projects.trajectoryKt) }
js(IR) {
browser()
} }
} }
readme {
description = "Core cartography, UI-agnostic"
maturity = space.kscience.gradle.Maturity.DEVELOPMENT
propertyByTemplate("artifact", rootProject.file("docs/templates/ARTIFACT-TEMPLATE.md"))
feature(
id = "angles and distances",
) { "Type-safe angle and distance measurements." }
feature(
id = "ellipsoid",
) { "Ellipsoid geometry and distances" }
feature(
id = "mercator",
) { "Mercator and web-mercator projections" }
}

View File

@ -1,7 +0,0 @@
# Module kmath-core
The core interfaces of KMath.
${features}
${artifact}

View File

@ -1,16 +1,9 @@
package center.sciprog.maps.coordinates package center.sciprog.maps.coordinates
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline import kotlin.jvm.JvmInline
@Serializable
@JvmInline @JvmInline
public value class Distance internal constructor(public val kilometers: Double) : Comparable<Distance> { public value class Distance(public val kilometers: Double)
override fun compareTo(other: Distance): Int = this.kilometers.compareTo(other.kilometers)
}
public val Number.kilometers: Distance get() = Distance(toDouble())
public val Number.meters: Distance get() = Distance(toDouble() / 1000)
public operator fun Distance.div(other: Distance): Double = kilometers / other.kilometers public operator fun Distance.div(other: Distance): Double = kilometers / other.kilometers
@ -20,4 +13,5 @@ public operator fun Distance.minus(other: Distance): Distance = Distance(kilomet
public operator fun Distance.times(number: Number): Distance = Distance(kilometers * number.toDouble()) public operator fun Distance.times(number: Number): Distance = Distance(kilometers * number.toDouble())
public operator fun Distance.div(number: Number): Distance = Distance(kilometers / number.toDouble()) public operator fun Distance.div(number: Number): Distance = Distance(kilometers / number.toDouble())
public val Distance.meters: Double get() = kilometers * 1000 public val Distance.meters: Double get() = kilometers * 1000

View File

@ -0,0 +1,14 @@
package center.sciprog.maps.coordinates
public class Ellipsoid(public val equatorRadius: Distance, public val polarRadius: Distance) {
public companion object {
public val WGS84: Ellipsoid = Ellipsoid(
equatorRadius = Distance(6378.137),
polarRadius = Distance(6356.7523142)
)
}
}
public val Ellipsoid.f: Double get() = (equatorRadius.kilometers - polarRadius.kilometers) / equatorRadius.kilometers
public val Ellipsoid.inverseF: Double get() = equatorRadius.kilometers / (equatorRadius.kilometers - polarRadius.kilometers)

View File

@ -1,80 +0,0 @@
package center.sciprog.maps.coordinates
import kotlinx.serialization.Serializable
import space.kscience.kmath.geometry.Angle
import space.kscience.kmath.geometry.tan
import kotlin.math.pow
import kotlin.math.sqrt
@Serializable
public class GeoEllipsoid(public val equatorRadius: Distance, public val polarRadius: Distance) {
/**
* Flattening https://en.wikipedia.org/wiki/Flattening
*/
public val f: Double = (equatorRadius.kilometers - polarRadius.kilometers) / equatorRadius.kilometers
/**
* Inverse flattening
*/
public val inverseF: Double = equatorRadius.kilometers / (equatorRadius.kilometers - polarRadius.kilometers)
/**
* Eccentricity squared
*/
public val eSquared: Double = 2 * f - f * f
public val eccentricity: Double = sqrt(eSquared)
public companion object {
public val WGS84: GeoEllipsoid = GeoEllipsoid(
equatorRadius = Distance(6378.137),
polarRadius = Distance(6356.752314245)
)
public val GRS80: GeoEllipsoid = GeoEllipsoid(
equatorRadius = Distance(6378.137),
polarRadius = Distance(6356.752314140)
)
public val sphere: GeoEllipsoid = GeoEllipsoid(
equatorRadius = Distance(6378.137),
polarRadius = Distance(6378.137)
)
// /**
// * https://en.wikipedia.org/wiki/Great-circle_distance
// */
// public fun greatCircleAngleBetween(r1: GMC, r2: GMC): Radians = acos(
// sin(r1.latitude) * sin(r2.latitude) + cos(r1.latitude) * cos(r2.latitude) * cos(r1.longitude - r2.longitude)
// ).radians
}
}
/**
* A radius of circle normal to the axis of the ellipsoid at given latitude
*/
public fun GeoEllipsoid.reducedRadius(latitude: Angle): Distance {
val reducedLatitudeTan = (1 - f) * tan(latitude)
return equatorRadius / sqrt(1.0 + reducedLatitudeTan.pow(2))
}
//
//
///**
// * Compute distance between two map points using giv
// * https://en.wikipedia.org/wiki/Geographical_distance#Lambert's_formula_for_long_lines
// */
//public fun GeoEllipsoid.lambertDistanceBetween(r1: GMC, r2: GMC): Distance {
// val s = greatCircleAngleBetween(r1, r2)
//
// val b1: Double = (1 - f) * tan(r1.latitude)
// val b2: Double = (1 - f) * tan(r2.latitude)
// val p = (b1 + b2) / 2
// val q = (b2 - b1) / 2
//
// val x = (s.value - sin(s)) * sin(p).pow(2) * cos(q).pow(2) / cos(s / 2).pow(2)
// val y = (s.value + sin(s)) * cos(p).pow(2) * sin(q).pow(2) / sin(s / 2).pow(2)
//
// return equatorRadius * (s.value - f / 2 * (x + y))
//}

View File

@ -1,31 +1,14 @@
package center.sciprog.maps.coordinates package center.sciprog.maps.coordinates
import kotlinx.serialization.Serializable import kotlin.math.PI
import space.kscience.kmath.geometry.*
/** /**
* Geodetic coordinated * Geodetic coordinated
*
* @param elevation is optional
*/ */
@Serializable public class GeodeticMapCoordinates private constructor(
public class GeodeticMapCoordinates( public val latitude: Double,
public val latitude: Angle, public val longitude: Double,
public val longitude: Angle, ){
public val elevation: Distance? = null,
) : Vector2D<Angle> {
init {
require(latitude in (-Angle.piDiv2)..(Angle.piDiv2)) {
"Latitude $latitude is not in (-PI/2)..(PI/2)"
}
require(longitude in (-Angle.pi..Angle.pi)) {
"Longitude $longitude is not in (-PI..PI) range"
}
}
override val x: Angle get() = longitude
override val y: Angle get() = latitude
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
@ -33,7 +16,10 @@ public class GeodeticMapCoordinates(
other as GeodeticMapCoordinates other as GeodeticMapCoordinates
return latitude == other.latitude && longitude == other.longitude if (latitude != other.latitude) return false
if (longitude != other.longitude) return false
return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
@ -43,39 +29,23 @@ public class GeodeticMapCoordinates(
} }
override fun toString(): String { override fun toString(): String {
return "GMC(latitude=${latitude.degrees} deg, longitude=${longitude.degrees} deg)" return "GeodeticCoordinates(latitude=${latitude / PI * 180} deg, longitude=${longitude / PI * 180} deg)"
} }
public companion object { public companion object {
public fun normalized( public fun ofRadians(latitude: Double, longitude: Double): GeodeticMapCoordinates {
latitude: Angle, require(latitude in (-PI/2)..(PI/2)) { "Latitude $latitude is not in (-PI/2)..(PI/2)" }
longitude: Angle, return GeodeticMapCoordinates(latitude, longitude.rem(PI / 2))
elevation: Distance? = null, }
): GeodeticMapCoordinates = GeodeticMapCoordinates(
latitude.coerceIn(-Angle.piDiv2..Angle.piDiv2), longitude.normalized(Angle.zero), elevation
)
public fun ofRadians( public fun ofDegrees(latitude: Double, longitude: Double): GeodeticMapCoordinates {
latitude: Double, require(latitude in (-90.0)..(90.0)) { "Latitude $latitude is not in -90..90" }
longitude: Double, return GeodeticMapCoordinates(latitude * PI / 180, (longitude.rem(180) * PI / 180))
elevation: Distance? = null, }
): GeodeticMapCoordinates = normalized(latitude.radians, longitude.radians, elevation)
public fun ofDegrees(
latitude: Double,
longitude: Double,
elevation: Distance? = null,
): GeodeticMapCoordinates = normalized(latitude.degrees, longitude.degrees, elevation)
} }
} }
/**
* Short name for GeodeticMapCoordinates
*/
public typealias Gmc = GeodeticMapCoordinates
//public interface GeoToScreenConversion { //public interface GeoToScreenConversion {
// public fun getScreenX(gmc: GeodeticMapCoordinates): Double // public fun getScreenX(gmc: GeodeticMapCoordinates): Double
// public fun getScreenY(gmc: GeodeticMapCoordinates): Double // public fun getScreenY(gmc: GeodeticMapCoordinates): Double

View File

@ -0,0 +1,77 @@
package center.sciprog.maps.coordinates
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.min
public data class GmcBox(
public val a: GeodeticMapCoordinates,
public val b: GeodeticMapCoordinates,
) {
public companion object {
public fun withCenter(
center: GeodeticMapCoordinates,
width: Distance,
height: Distance,
ellipsoid: Ellipsoid = Ellipsoid.WGS84,
): GmcBox {
val r = ellipsoid.equatorRadius * cos(center.latitude)
val a = GeodeticMapCoordinates.ofRadians(
center.latitude - height / ellipsoid.polarRadius / 2,
center.longitude - width / r / 2
)
val b = GeodeticMapCoordinates.ofRadians(
center.latitude + height / ellipsoid.polarRadius / 2,
center.longitude + width / r / 2
)
return GmcBox(a, b)
}
}
}
public val GmcBox.center: GeodeticMapCoordinates
get() = GeodeticMapCoordinates.ofRadians(
(a.latitude + b.latitude) / 2,
(a.longitude + b.longitude) / 2
)
/**
* Minimum longitude
*/
public val GmcBox.left: Double get() = min(a.longitude, b.longitude)
/**
* maximum longitude
*/
public val GmcBox.right: Double get() = max(a.longitude, b.longitude)
/**
* Maximum latitude
*/
public val GmcBox.top: Double get() = max(a.latitude, b.latitude)
/**
* Minimum latitude
*/
public val GmcBox.bottom: Double get() = min(a.latitude, b.latitude)
//TODO take curvature into account
public val GmcBox.width: Double get() = abs(a.longitude - b.longitude)
public val GmcBox.height: Double get() = abs(a.latitude - b.latitude)
public val GmcBox.topLeft: GeodeticMapCoordinates get() = GeodeticMapCoordinates.ofRadians(top, left)
public val GmcBox.bottomRight: GeodeticMapCoordinates get() = GeodeticMapCoordinates.ofRadians(bottom, right)
/**
* Compute a minimal bounding box including all given boxes. Return null if collection is empty
*/
public fun Collection<GmcBox>.wrapAll(): GmcBox? {
if (isEmpty()) return null
//TODO optimize computation
val minLat = minOf { it.bottom }
val maxLat = maxOf { it.top }
val minLong = minOf { it.left }
val maxLong = maxOf { it.right }
return GmcBox(GeodeticMapCoordinates.ofRadians(minLat, minLong), GeodeticMapCoordinates.ofRadians(maxLat, maxLong))
}

View File

@ -1,358 +0,0 @@
package center.sciprog.maps.coordinates
import space.kscience.kmath.geometry.*
import kotlin.math.*
/**
* A directed straight (geodetic) segment on a spheroid with given start, direction, end point and distance.
* @param forward coordinate of a start point with forward direction
* @param backward coordinate of an end point with backward direction
*/
public class GmcCurve(
public val forward: GmcPose,
public val backward: GmcPose,
public val distance: Distance,
){
override fun toString(): String {
return "GmcCurve(from: ${forward.coordinates}, to: ${backward.coordinates})"
}
}
public operator fun ClosedRange<Radians>.contains(angle: Angle): Boolean = contains(angle.toRadians())
/**
* Reverse direction and order of ends
*/
public fun GmcCurve.reversed(): GmcCurve = GmcCurve(backward.reversed(), forward.reversed(), distance)
/**
* Compute a curve alongside a meridian
*/
public fun GeoEllipsoid.meridianCurve(
longitude: Angle,
fromLatitude: Angle,
toLatitude: Angle,
step: Radians = 0.015.radians,
): GmcCurve {
require(fromLatitude in (-Angle.piDiv2)..(Angle.piDiv2)) { "Latitude must be in (-90, 90) degrees range" }
require(toLatitude in (-Angle.piDiv2)..(Angle.piDiv2)) { "Latitude must be in (-90, 90) degrees range" }
fun smallDistance(from: Radians, to: Radians): Distance = equatorRadius *
(1 - eSquared) *
(1 - eSquared * sin(from).pow(2)).pow(-1.5) *
abs((from - to).value)
val up = toLatitude > fromLatitude
val integrateFrom: Radians
val integrateTo: Radians
if (up) {
integrateFrom = fromLatitude.toRadians()
integrateTo = toLatitude.toRadians()
} else {
integrateTo = fromLatitude.toRadians()
integrateFrom = toLatitude.toRadians()
}
var current: Radians = integrateFrom
var s = Distance(0.0)
while (current < integrateTo) {
val next = minOf(current + step, integrateTo)
s += smallDistance(current, next)
current = next
}
return GmcCurve(
forward = GmcPose(Gmc.normalized(fromLatitude, longitude), if (up) Angle.zero else Angle.pi),
backward = GmcPose(Gmc.normalized(toLatitude, longitude), if (up) Angle.pi else Angle.zero),
distance = s
)
}
/**
* Compute a curve alongside a parallel
*/
public fun GeoEllipsoid.parallelCurve(latitude: Angle, fromLongitude: Angle, toLongitude: Angle): GmcCurve {
require(latitude in (-Angle.piDiv2)..(Angle.piDiv2)) { "Latitude must be in (-90, 90) degrees range" }
val right = toLongitude > fromLongitude
return GmcCurve(
forward = GmcPose(Gmc.normalized(latitude, fromLongitude), if (right) Angle.piDiv2 else -Angle.piDiv2),
backward = GmcPose(Gmc.normalized(latitude, toLongitude), if (right) -Angle.piDiv2 else Angle.piDiv2),
distance = reducedRadius(latitude) * abs((fromLongitude - toLongitude).radians)
)
}
/**
* Taken from https://github.com/mgavaghan/geodesy
* https://github.com/mgavaghan/geodesy/blob/ab1c6969dc964ff34929911f055b27525909ef3f/src/main/java/org/gavaghan/geodesy/GeodeticCalculator.java#L58
*
* Calculate the destination and final bearing after traveling a specified
* distance, and a specified starting bearing, for an initial location. This
* is the solution to the direct geodetic problem.
*
* @param start starting location
* @return solution to the direct geodetic problem
*/
@Suppress("SpellCheckingInspection", "LocalVariableName")
public fun GeoEllipsoid.curveInDirection(
start: GmcPose,
distance: Distance,
precision: Double = 1e-6,
): GmcCurve {
val a: Distance = equatorRadius
val b: Distance = polarRadius
val aSquared: Double = a.kilometers.pow(2)
val bSquared: Double = b.kilometers.pow(2)
val phi1 = start.latitude
val alpha1 = start.bearing
val cosAlpha1: Double = cos(alpha1)
val sinAlpha1: Double = sin(alpha1)
val tanU1: Double = (1.0 - f) * tan(phi1)
val cosU1: Double = 1.0 / sqrt(1.0 + tanU1 * tanU1)
val sinU1 = tanU1 * cosU1
// eq. 1
val sigma1: Radians = atan2(tanU1, cosAlpha1).radians
// eq. 2
val sinAlpha: Double = cosU1 * sinAlpha1
val sin2Alpha = sinAlpha * sinAlpha
val cos2Alpha = 1 - sin2Alpha
val uSquared = cos2Alpha * (aSquared - bSquared) / bSquared
// eq. 3
val A: Double = 1 + uSquared / 16384 * (4096 + uSquared * (-768 + uSquared * (320 - 175 * uSquared)))
// eq. 4
val B: Double = uSquared / 1024 * (256 + uSquared * (-128 + uSquared * (74 - 47 * uSquared)))
// iterate until there is a negligible change in sigma
val sOverbA: Radians = (distance / b / A).radians
var sigma: Radians = sOverbA
var sinSigma: Double
var prevSigma: Radians = sOverbA
var sigmaM2: Radians
var cosSigmaM2: Double
var cos2SigmaM2: Double
while (!prevSigma.value.isNaN()) {
// eq. 5
sigmaM2 = sigma1 * 2.0 + sigma
cosSigmaM2 = cos(sigmaM2)
cos2SigmaM2 = cosSigmaM2 * cosSigmaM2
sinSigma = sin(sigma)
// val cosSigma: Double = cos(sigma)
// eq. 6
val deltaSigma = B * sinSigma *
(cosSigmaM2 + B / 4.0 * (cos(sigma) * (-1 + 2 * cos2SigmaM2) -
B / 6.0 * cosSigmaM2 * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM2)))
// eq. 7
sigma = sOverbA + deltaSigma.radians
// break after converging to tolerance
if (abs((sigma - prevSigma).value) < precision) break
prevSigma = sigma
}
sigmaM2 = sigma1 * 2.0 + sigma
cosSigmaM2 = cos(sigmaM2)
cos2SigmaM2 = cosSigmaM2 * cosSigmaM2
val cosSigma: Double = cos(sigma)
sinSigma = sin(sigma)
// eq. 8
val phi2: Radians = atan2(
sinU1 * cosSigma + cosU1 * sinSigma * cosAlpha1,
(1.0 - f) * sqrt(
sin2Alpha + (sinU1 * sinSigma - cosU1 * cosSigma * cosAlpha1).pow(2)
)
).radians
// eq. 9
// This fixes the pole crossing defect spotted by Matt Feemster. When a
// path passes a pole and essentially crosses a line of latitude twice -
// once in each direction - the longitude calculation got messed up.
//
// Using atan2 instead of atan fixes the defect. The change is in the
// next 3 lines.
//
// double tanLambda = sinSigma * sinAlpha1 / (cosU1 * cosSigma - sinU1 *
// sinSigma * cosAlpha1);
// double lambda = Math.atan(tanLambda);
val lambda: Double = atan2(
sinSigma * sinAlpha1,
cosU1 * cosSigma - sinU1 * sinSigma * cosAlpha1
)
// eq. 10
val C = f / 16 * cos2Alpha * (4 + f * (4 - 3 * cos2Alpha))
// eq. 11
val L = lambda - (1 - C) * f * sinAlpha *
(sigma.value + C * sinSigma * (cosSigmaM2 + C * cosSigma * (-1 + 2 * cos2SigmaM2)))
val endPoint = Gmc.normalized(phi2, start.longitude + L.radians)
// eq. 12
return GmcCurve(
start,
GmcPose(
endPoint,
atan2(sinAlpha, -sinU1 * sinSigma + cosU1 * cosSigma * cosAlpha1).radians
),
distance
)
}
/**
* Taken from https://github.com/mgavaghan/geodesy
*
* Calculate the geodetic curve between two points on a specified reference
* ellipsoid. This is the solution to the inverse geodetic problem.
*
* @receiver reference ellipsoid to use
* @param start starting coordinates
* @param end ending coordinates
* @return solution to the inverse geodetic problem
*/
@Suppress("SpellCheckingInspection", "LocalVariableName")
public fun GeoEllipsoid.curveBetween(start: Gmc, end: Gmc, precision: Double = 1e-6): GmcCurve {
//
// All equation numbers refer back to Vincenty's publication:
// See http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf
//
// get constants
val a = equatorRadius
val b = polarRadius
if(start == end) error("Can't compute a curve because start and end coincide at $start")
// get parameters as radians
val phi1 = start.latitude
val lambda1 = start.longitude
val phi2 = end.latitude
val lambda2 = end.longitude
// calculations
val a2 = a.kilometers * a.kilometers
val b2 = b.kilometers * b.kilometers
val a2b2b2 = (a2 - b2) / b2
val omega: Angle = lambda2 - lambda1
val tanphi1: Double = tan(phi1)
val tanU1 = (1.0 - f) * tanphi1
val U1: Double = atan(tanU1)
val sinU1: Double = sin(U1)
val cosU1: Double = cos(U1)
val tanphi2: Double = tan(phi2)
val tanU2 = (1.0 - f) * tanphi2
val U2: Double = atan(tanU2)
val sinU2: Double = sin(U2)
val cosU2: Double = cos(U2)
val sinU1sinU2 = sinU1 * sinU2
val cosU1sinU2 = cosU1 * sinU2
val sinU1cosU2 = sinU1 * cosU2
val cosU1cosU2 = cosU1 * cosU2
// eq. 13
var lambda: Angle = omega
// intermediates we'll need to compute 's'
var A = 0.0
var sigma = 0.0
var deltasigma = 0.0
var lambda0: Angle
var converged = false
for (i in 0..19) {
lambda0 = lambda
val sinlambda: Double = sin(lambda)
val coslambda: Double = cos(lambda)
// eq. 14
val sin2sigma =
cosU2 * sinlambda * cosU2 * sinlambda + (cosU1sinU2 - sinU1cosU2 * coslambda) * (cosU1sinU2 - sinU1cosU2 * coslambda)
val sinsigma: Double = sqrt(sin2sigma)
// eq. 15
val cossigma = sinU1sinU2 + cosU1cosU2 * coslambda
// eq. 16
sigma = atan2(sinsigma, cossigma)
// eq. 17 Careful! sin2sigma might be almost 0!
val sinalpha = if (sin2sigma == 0.0) 0.0 else cosU1cosU2 * sinlambda / sinsigma
val alpha: Double = asin(sinalpha)
val cosalpha: Double = cos(alpha)
val cos2alpha = cosalpha * cosalpha
// eq. 18 Careful! cos2alpha might be almost 0!
val cos2sigmam = if (cos2alpha == 0.0) 0.0 else cossigma - 2 * sinU1sinU2 / cos2alpha
val u2 = cos2alpha * a2b2b2
val cos2sigmam2 = cos2sigmam * cos2sigmam
// eq. 3
A = 1.0 + u2 / 16384 * (4096 + u2 * (-768 + u2 * (320 - 175 * u2)))
// eq. 4
val B = u2 / 1024 * (256 + u2 * (-128 + u2 * (74 - 47 * u2)))
// eq. 6
deltasigma =
B * sinsigma * (cos2sigmam + B / 4 * (cossigma * (-1 + 2 * cos2sigmam2) - B / 6 * cos2sigmam * (-3 + 4 * sin2sigma) * (-3 + 4 * cos2sigmam2)))
// eq. 10
val C = f / 16 * cos2alpha * (4 + f * (4 - 3 * cos2alpha))
// eq. 11 (modified)
lambda = omega + (
(1 - C) * f * sinalpha *
(sigma + C * sinsigma * (cos2sigmam + C * cossigma * (-1 + 2 * cos2sigmam2)))
).radians
// see how much improvement we got
val change: Double = abs((lambda - lambda0) / lambda)
if (i > 1 && change < precision) {
converged = true
break
}
}
// eq. 19
val s: Distance = b * A * (sigma - deltasigma)
val alpha1: Radians
val alpha2: Radians
// didn't converge? must be N/S
if (!converged) {
if (phi1 > phi2) {
alpha1 = Angle.pi.toRadians()
alpha2 = 0.0.radians
} else if (phi1 < phi2) {
alpha1 = 0.0.radians
alpha2 = Angle.pi.toRadians()
} else {
error("Start and end point coinside.")
}
} else {
// eq. 20
alpha1 = atan2(
cosU2 * sin(lambda),
cosU1sinU2 - sinU1cosU2 * cos(lambda)
).radians
// eq. 21
alpha2 = atan2(
cosU1 * sin(lambda),
-sinU1cosU2 + cosU1sinU2 * cos(lambda)
).radians + Angle.pi
}
return GmcCurve(
GmcPose(start, alpha1),
GmcPose(end, alpha2),
s
)
}

View File

@ -1,17 +0,0 @@
package center.sciprog.maps.coordinates
import kotlinx.serialization.Serializable
import space.kscience.kmath.geometry.Angle
import space.kscience.kmath.geometry.normalized
/**
* A coordinate-bearing pair
*/
@Serializable
public data class GmcPose(val coordinates: GeodeticMapCoordinates, val bearing: Angle) {
val latitude: Angle get() = coordinates.latitude
val longitude: Angle get() = coordinates.longitude
}
public fun GmcPose.reversed(): GmcPose = copy(bearing = (bearing + Angle.pi).normalized())

View File

@ -0,0 +1,38 @@
package center.sciprog.maps.coordinates
import kotlin.math.pow
/**
* Observable position on the map. Includes observation coordinate and [zoom] factor
*/
public data class MapViewPoint(
val focus: GeodeticMapCoordinates,
val zoom: Double,
) {
val scaleFactor: Double by lazy { WebMercatorProjection.scaleFactor(zoom) }
}
public fun MapViewPoint.move(delta: GeodeticMapCoordinates): MapViewPoint {
val newCoordinates = GeodeticMapCoordinates.ofRadians(
(focus.latitude + delta.latitude).coerceIn(
-MercatorProjection.MAXIMUM_LATITUDE,
MercatorProjection.MAXIMUM_LATITUDE
),
focus.longitude + delta.longitude
)
return MapViewPoint(newCoordinates, zoom)
}
public fun MapViewPoint.zoom(
zoomDelta: Double,
invariant: GeodeticMapCoordinates = focus,
): MapViewPoint = if (invariant == focus) {
copy(zoom = (zoom + zoomDelta).coerceIn(2.0, 18.0))
} else {
val difScale = (1 - 2.0.pow(-zoomDelta))
val newCenter = GeodeticMapCoordinates.ofRadians(
focus.latitude + (invariant.latitude - focus.latitude) * difScale,
focus.longitude + (invariant.longitude - focus.longitude) * difScale
)
MapViewPoint(newCenter, (zoom + zoomDelta).coerceIn(2.0, 18.0))
}

View File

@ -5,92 +5,51 @@
package center.sciprog.maps.coordinates package center.sciprog.maps.coordinates
import kotlinx.serialization.Serializable
import space.kscience.kmath.geometry.*
import kotlin.math.* import kotlin.math.*
public data class ProjectionCoordinates(val x: Distance, val y: Distance) public data class MercatorCoordinates(val x: Double, val y: Double)
/**
* @param T the type of projection coordinates
*/
public interface MapProjection<T : Any> {
public fun toGeodetic(pc: T): GeodeticMapCoordinates
public fun toProjection(gmc: GeodeticMapCoordinates): T
public companion object {
public val epsg3857: MercatorProjection = MercatorProjection()
}
}
/** /**
* @param baseLongitude the longitude offset in radians * @param baseLongitude the longitude offset in radians
* @param ellipsoid - a [GeoEllipsoid] to be used for conversion * @param radius the average radius of the Earth
* @param correctedRadius optional radius correction to account for ellipsoid model
*/ */
@Serializable
public open class MercatorProjection( public open class MercatorProjection(
public val baseLongitude: Angle = Angle.zero, public val baseLongitude: Double = 0.0,
public val ellipsoid: GeoEllipsoid = GeoEllipsoid.sphere, protected val radius: Double = DEFAULT_EARTH_RADIUS,
) : MapProjection<ProjectionCoordinates> { private val correctedRadius: ((GeodeticMapCoordinates) -> Double)? = null,
) {
public fun toGeodetic(mc: MercatorCoordinates): GeodeticMapCoordinates {
/** val res = GeodeticMapCoordinates.ofRadians(
* Taken from https://github.com/geotools/geotools/blob/main/modules/library/referencing/src/main/java/org/geotools/referencing/operation/projection/Mercator.java#L164 atan(sinh(mc.y / radius)),
*/ baseLongitude + mc.x / radius,
private fun cphi2(ts: Double): Double { )
val eccnth: Double = 0.5 * ellipsoid.eccentricity return if (correctedRadius != null) {
var phi: Double = PI / 2 - 2.0 * atan(ts) val r = correctedRadius.invoke(res)
for (i in 0 until 15) {
val con: Double = ellipsoid.eccentricity * sin(phi)
val dphi: Double = PI / 2 - 2.0 * atan(ts * ((1 - con) / (1 + con)).pow(eccnth)) - phi
phi += dphi
if (abs(dphi) <= 1e-10) {
return phi
}
}
error("Inverse mercator projection transformation failed to converge")
}
override fun toGeodetic(pc: ProjectionCoordinates): GeodeticMapCoordinates {
return if (ellipsoid === GeoEllipsoid.sphere) {
GeodeticMapCoordinates.ofRadians( GeodeticMapCoordinates.ofRadians(
atan(sinh(pc.y / ellipsoid.equatorRadius)), atan(sinh(mc.y / r)),
baseLongitude.radians + (pc.x / ellipsoid.equatorRadius), baseLongitude + mc.x / r,
) )
} else { } else {
GeodeticMapCoordinates.ofRadians( res
cphi2(exp(-(pc.y / ellipsoid.equatorRadius))),
baseLongitude.radians + (pc.x / ellipsoid.equatorRadius)
)
} }
} }
/** /**
* https://en.wikipedia.org/wiki/Web_Mercator_projection#Formulas * https://en.wikipedia.org/wiki/Web_Mercator_projection#Formulas
*/ */
override fun toProjection(gmc: GeodeticMapCoordinates): ProjectionCoordinates { public fun toMercator(gmc: GeodeticMapCoordinates): MercatorCoordinates {
require(abs(gmc.latitude) <= MAXIMUM_LATITUDE) { "Latitude exceeds the maximum latitude for mercator coordinates" } require(abs(gmc.latitude) <= MAXIMUM_LATITUDE) { "Latitude exceeds the maximum latitude for mercator coordinates" }
val e = sqrt(ellipsoid.eSquared) val r = correctedRadius?.invoke(gmc) ?: radius
return MercatorCoordinates(
return if (ellipsoid === GeoEllipsoid.sphere) { x = r * (gmc.longitude - baseLongitude),
ProjectionCoordinates( y = r * ln(tan(PI / 4 + gmc.latitude / 2))
x = ellipsoid.equatorRadius * (gmc.longitude - baseLongitude).radians,
y = ellipsoid.equatorRadius * ln(tan(Angle.pi / 4 + gmc.latitude / 2))
)
} else {
val sinPhi = sin(gmc.latitude)
ProjectionCoordinates(
x = ellipsoid.equatorRadius * (gmc.longitude - baseLongitude).radians,
y = ellipsoid.equatorRadius * ln(
tan(Angle.pi / 4 + gmc.latitude / 2) * ((1 - e * sinPhi) / (1 + e * sinPhi)).pow(e / 2)
)
) )
} }
}
public companion object { public companion object : MercatorProjection(0.0, 6378137.0) {
public val MAXIMUM_LATITUDE: Angle = 85.05113.degrees public const val MAXIMUM_LATITUDE: Double = 85.05113
public val DEFAULT_EARTH_RADIUS: Double = radius
} }
} }

View File

@ -5,21 +5,19 @@
package center.sciprog.maps.coordinates package center.sciprog.maps.coordinates
import space.kscience.kmath.geometry.abs
import space.kscience.kmath.geometry.radians
import kotlin.math.* import kotlin.math.*
public data class WebMercatorCoordinates(val zoom: Int, val x: Float, val y: Float) public data class WebMercatorCoordinates(val zoom: Int, val x: Double, val y: Double)
public object WebMercatorProjection { public object WebMercatorProjection {
/** /**
* Compute radians to projection coordinates ratio for given [zoom] factor * Compute radians to projection coordinates ratio for given [zoom] factor
*/ */
public fun scaleFactor(zoom: Float): Float = (256.0 / 2 / PI * 2f.pow(zoom)).toFloat() public fun scaleFactor(zoom: Double): Double = 256.0 / 2 / PI * 2.0.pow(zoom)
public fun toGeodetic(mercator: WebMercatorCoordinates): GeodeticMapCoordinates { public fun toGeodetic(mercator: WebMercatorCoordinates): GeodeticMapCoordinates {
val scaleFactor = scaleFactor(mercator.zoom.toFloat()) val scaleFactor = scaleFactor(mercator.zoom.toDouble())
val longitude = mercator.x / scaleFactor - PI val longitude = mercator.x / scaleFactor - PI
val latitude = (atan(exp(PI - mercator.y / scaleFactor)) - PI / 4) * 2 val latitude = (atan(exp(PI - mercator.y / scaleFactor)) - PI / 4) * 2
return GeodeticMapCoordinates.ofRadians(latitude, longitude) return GeodeticMapCoordinates.ofRadians(latitude, longitude)
@ -27,17 +25,15 @@ public object WebMercatorProjection {
/** /**
* https://en.wikipedia.org/wiki/Web_Mercator_projection#Formulas * https://en.wikipedia.org/wiki/Web_Mercator_projection#Formulas
*
* return null if gmc is outside of possible coordinate scope for WebMercator
*/ */
public fun toMercator(gmc: GeodeticMapCoordinates, zoom: Int): WebMercatorCoordinates? { public fun toMercator(gmc: GeodeticMapCoordinates, zoom: Int): WebMercatorCoordinates {
if (abs(gmc.latitude) > MercatorProjection.MAXIMUM_LATITUDE) return null require(abs(gmc.latitude) <= MercatorProjection.MAXIMUM_LATITUDE) { "Latitude exceeds the maximum latitude for mercator coordinates" }
val scaleFactor = scaleFactor(zoom.toFloat()) val scaleFactor = scaleFactor(zoom.toDouble())
return WebMercatorCoordinates( return WebMercatorCoordinates(
zoom = zoom, zoom = zoom,
x = scaleFactor * (gmc.longitude.radians + PI).toFloat(), x = scaleFactor * (gmc.longitude + PI),
y = scaleFactor * (PI - ln(tan(PI / 4 + gmc.latitude.radians / 2))).toFloat() y = scaleFactor * (PI - ln(tan(PI / 4 + gmc.latitude / 2)))
) )
} }

View File

@ -1,36 +0,0 @@
package center.sciprog.maps.coordinates
import space.kscience.kmath.geometry.radians
import kotlin.test.Test
import kotlin.test.assertEquals
internal class DistanceTest {
companion object {
val moscow = Gmc.ofDegrees(55.76058287719673, 37.60358622841869)
val spb = Gmc.ofDegrees(59.926686023580444, 30.36038109122013)
}
@Test
fun ellipsoidParameters() {
assertEquals(298.257223563, GeoEllipsoid.WGS84.inverseF, 1e-6)
}
@Test
fun curveBetween() {
val curve = GeoEllipsoid.WGS84.curveBetween(moscow, spb)
val distance = curve.distance
assertEquals(632.035426877, distance.kilometers, 0.0001)
assertEquals(-0.6947937116552751, curve.forward.bearing.radians, 0.0001)
}
@Test
fun curveInDirection() {
val curve = GeoEllipsoid.WGS84.curveInDirection(
GmcPose(moscow, (-0.6947937116552751).radians), Distance(632.035426877)
)
assertEquals(spb.latitude.radians, curve.backward.latitude.radians, 0.0001)
assertEquals(spb.longitude.radians, curve.backward.longitude.radians, 0.0001)
}
}

View File

@ -1,29 +0,0 @@
package center.sciprog.maps.coordinates
import space.kscience.kmath.geometry.degrees
import kotlin.test.Test
import kotlin.test.assertEquals
class MercatorTest {
@Test
fun sphereForwardBackward(){
val moscow = Gmc.ofDegrees(55.76058287719673, 37.60358622841869)
val mercator = MapProjection.epsg3857.toProjection(moscow)
//https://epsg.io/transform#s_srs=4326&t_srs=3857&x=37.6035862&y=55.7605829
assertEquals(4186.0120709, mercator.x.kilometers, 1e-4)
assertEquals(7510.9013658, mercator.y.kilometers, 1e-4)
val backwards = MapProjection.epsg3857.toGeodetic(mercator)
assertEquals(moscow.latitude.degrees, backwards.latitude.degrees, 1e-6)
assertEquals(moscow.longitude.degrees, backwards.longitude.degrees, 1e-6)
}
@Test
fun ellipseForwardBackward(){
val moscow = Gmc.ofDegrees(55.76058287719673, 37.60358622841869)
val projection = MercatorProjection(ellipsoid = GeoEllipsoid.WGS84)
val mercator = projection.toProjection(moscow)
val backwards = projection.toGeodetic(mercator)
assertEquals(moscow.latitude.degrees, backwards.latitude.degrees, 1e-6)
assertEquals(moscow.longitude.degrees, backwards.longitude.degrees, 1e-6)
}
}

View File

@ -1,32 +0,0 @@
# Module maps-kt-features
## Usage
## Artifact:
The Maven coordinates of this project are `center.sciprog:maps-kt-features:0.2.2`.
**Gradle Groovy:**
```groovy
repositories {
maven { url 'https://repo.kotlin.link' }
mavenCentral()
}
dependencies {
implementation 'center.sciprog:maps-kt-features:0.2.2'
}
```
**Gradle Kotlin DSL:**
```kotlin
repositories {
maven("https://repo.kotlin.link")
mavenCentral()
}
dependencies {
implementation("center.sciprog:maps-kt-features:0.2.2")
}
```

View File

@ -1,765 +0,0 @@
public abstract interface class center/sciprog/attributes/Attribute {
}
public abstract interface class center/sciprog/attributes/AttributeWithDefault : center/sciprog/attributes/Attribute {
public abstract fun getDefault ()Ljava/lang/Object;
}
public final class center/sciprog/attributes/Attributes {
public static final field Companion Lcenter/sciprog/attributes/Attributes$Companion;
public static final synthetic fun box-impl (Ljava/util/Map;)Lcenter/sciprog/attributes/Attributes;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Ljava/util/Map;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Ljava/util/Map;Ljava/util/Map;)Z
public static final fun get-impl (Ljava/util/Map;Lcenter/sciprog/attributes/Attribute;)Ljava/lang/Object;
public final fun getContent ()Ljava/util/Map;
public static final fun getKeys-impl (Ljava/util/Map;)Ljava/util/Set;
public fun hashCode ()I
public static fun hashCode-impl (Ljava/util/Map;)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Ljava/util/Map;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Ljava/util/Map;
}
public final class center/sciprog/attributes/Attributes$Companion {
public final fun getEMPTY-hlWAoAs ()Ljava/util/Map;
}
public final class center/sciprog/attributes/AttributesBuilder {
public static final field $stable I
public fun <init> ()V
public final fun add (Lcenter/sciprog/attributes/SetAttribute;Ljava/lang/Object;)V
public final fun build-hlWAoAs ()Ljava/util/Map;
public final fun from-xbryb2s (Ljava/util/Map;)V
public final fun get (Lcenter/sciprog/attributes/Attribute;)Ljava/lang/Object;
public final fun invoke (Lcenter/sciprog/attributes/Attribute;Ljava/lang/Object;)V
public final fun remove (Lcenter/sciprog/attributes/SetAttribute;Ljava/lang/Object;)V
}
public final class center/sciprog/attributes/AttributesBuilderKt {
public static final fun Attributes (Lkotlin/jvm/functions/Function1;)Ljava/util/Map;
public static final fun AttributesBuilder-xbryb2s (Ljava/util/Map;)Lcenter/sciprog/attributes/AttributesBuilder;
}
public final class center/sciprog/attributes/AttributesKt {
public static final fun Attributes (Lcenter/sciprog/attributes/Attribute;Ljava/lang/Object;)Ljava/util/Map;
public static final fun getOrDefault-hZ80nBk (Ljava/util/Map;Lcenter/sciprog/attributes/AttributeWithDefault;)Ljava/lang/Object;
public static final fun getZ (Lcenter/sciprog/maps/features/Feature;)F
public static final fun isEmpty-xbryb2s (Ljava/util/Map;)Z
public static final fun plus-0GKEh6s (Ljava/util/Map;Ljava/util/Map;)Ljava/util/Map;
public static final fun withAttribute-svNjpeE (Ljava/util/Map;Lcenter/sciprog/attributes/Attribute;Ljava/lang/Object;)Ljava/util/Map;
public static final fun withAttributeElement-svNjpeE (Ljava/util/Map;Lcenter/sciprog/attributes/SetAttribute;Ljava/lang/Object;)Ljava/util/Map;
public static final fun withoutAttributeElement-svNjpeE (Ljava/util/Map;Lcenter/sciprog/attributes/SetAttribute;Ljava/lang/Object;)Ljava/util/Map;
}
public final class center/sciprog/attributes/AttributesSerializer : kotlinx/serialization/KSerializer {
public static final field $stable I
public fun <init> (Ljava/util/Set;)V
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize-7icwqAM (Lkotlinx/serialization/encoding/Decoder;)Ljava/util/Map;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize-izrVrxc (Lkotlinx/serialization/encoding/Encoder;Ljava/util/Map;)V
}
public final class center/sciprog/attributes/NameAttribute : center/sciprog/attributes/SerializableAttribute {
public static final field $stable I
public static final field INSTANCE Lcenter/sciprog/attributes/NameAttribute;
}
public abstract class center/sciprog/attributes/SerializableAttribute : center/sciprog/attributes/Attribute {
public static final field $stable I
public fun <init> (Ljava/lang/String;Lkotlinx/serialization/KSerializer;)V
public final fun getSerialId ()Ljava/lang/String;
public final fun getSerializer ()Lkotlinx/serialization/KSerializer;
public fun toString ()Ljava/lang/String;
}
public abstract interface class center/sciprog/attributes/SetAttribute : center/sciprog/attributes/Attribute {
}
public final class center/sciprog/maps/compose/ClickGesturesKt {
public static final fun detectClicks (Landroidx/compose/ui/input/pointer/PointerInputScope;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun detectClicks$default (Landroidx/compose/ui/input/pointer/PointerInputScope;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public static final fun getPosition (Landroidx/compose/ui/input/pointer/PointerEvent;)J
}
public final class center/sciprog/maps/compose/MapControlsKt {
public static final fun mapControls (Landroidx/compose/ui/Modifier;Lcenter/sciprog/maps/features/CoordinateViewScope;Lcenter/sciprog/maps/features/FeatureGroup;)Landroidx/compose/ui/Modifier;
}
public final class center/sciprog/maps/features/AlphaAttribute : center/sciprog/attributes/Attribute {
public static final field $stable I
public static final field INSTANCE Lcenter/sciprog/maps/features/AlphaAttribute;
}
public final class center/sciprog/maps/features/ArcFeature : center/sciprog/maps/features/DraggableFeature {
public static final field $stable I
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/Rectangle;Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/Rectangle;Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun component2 ()Lcenter/sciprog/maps/features/Rectangle;
public final fun component3 ()Lspace/kscience/kmath/geometry/Angle;
public final fun component4 ()Lspace/kscience/kmath/geometry/Angle;
public final fun component5-hlWAoAs ()Ljava/util/Map;
public final fun copy-CqjioLk (Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/Rectangle;Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;Ljava/util/Map;)Lcenter/sciprog/maps/features/ArcFeature;
public static synthetic fun copy-CqjioLk$default (Lcenter/sciprog/maps/features/ArcFeature;Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/Rectangle;Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;Ljava/util/Map;ILjava/lang/Object;)Lcenter/sciprog/maps/features/ArcFeature;
public fun equals (Ljava/lang/Object;)Z
public final fun getArcLength ()Lspace/kscience/kmath/geometry/Angle;
public fun getAttributes-hlWAoAs ()Ljava/util/Map;
public fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public final fun getOval ()Lcenter/sciprog/maps/features/Rectangle;
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun getStartAngle ()Lspace/kscience/kmath/geometry/Angle;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
public fun withCoordinates (Ljava/lang/Object;)Lcenter/sciprog/maps/features/Feature;
}
public abstract interface class center/sciprog/maps/features/Area {
public abstract fun contains (Ljava/lang/Object;)Z
}
public final class center/sciprog/maps/features/BitmapIconFeature : center/sciprog/maps/features/MarkerFeature {
public static final field $stable I
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;JLandroidx/compose/ui/graphics/ImageBitmap;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;JLandroidx/compose/ui/graphics/ImageBitmap;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun component2 ()Ljava/lang/Object;
public final fun component3-MYxV2XQ ()J
public final fun component4 ()Landroidx/compose/ui/graphics/ImageBitmap;
public final fun component5-hlWAoAs ()Ljava/util/Map;
public final fun copy-xcw5jm0 (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;JLandroidx/compose/ui/graphics/ImageBitmap;Ljava/util/Map;)Lcenter/sciprog/maps/features/BitmapIconFeature;
public static synthetic fun copy-xcw5jm0$default (Lcenter/sciprog/maps/features/BitmapIconFeature;Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;JLandroidx/compose/ui/graphics/ImageBitmap;Ljava/util/Map;ILjava/lang/Object;)Lcenter/sciprog/maps/features/BitmapIconFeature;
public fun equals (Ljava/lang/Object;)Z
public fun getAttributes-hlWAoAs ()Ljava/util/Map;
public fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public fun getCenter ()Ljava/lang/Object;
public final fun getImage ()Landroidx/compose/ui/graphics/ImageBitmap;
public final fun getSize-MYxV2XQ ()J
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
public fun withCoordinates (Ljava/lang/Object;)Lcenter/sciprog/maps/features/Feature;
}
public final class center/sciprog/maps/features/CircleFeature : center/sciprog/maps/features/MarkerFeature {
public static final field $stable I
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;FLjava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;FLjava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun component2 ()Ljava/lang/Object;
public final fun component3-D9Ej5fM ()F
public final fun component4-hlWAoAs ()Ljava/util/Map;
public fun contains (Lcenter/sciprog/maps/features/ViewPoint;)Z
public final fun copy-eji0nfc (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;FLjava/util/Map;)Lcenter/sciprog/maps/features/CircleFeature;
public static synthetic fun copy-eji0nfc$default (Lcenter/sciprog/maps/features/CircleFeature;Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;FLjava/util/Map;ILjava/lang/Object;)Lcenter/sciprog/maps/features/CircleFeature;
public fun equals (Ljava/lang/Object;)Z
public fun getAttributes-hlWAoAs ()Ljava/util/Map;
public fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public fun getCenter ()Ljava/lang/Object;
public final fun getRadius-D9Ej5fM ()F
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
public fun withCoordinates (Ljava/lang/Object;)Lcenter/sciprog/maps/features/Feature;
}
public final class center/sciprog/maps/features/ClickListenerAttribute : center/sciprog/attributes/SetAttribute {
public static final field $stable I
public static final field INSTANCE Lcenter/sciprog/maps/features/ClickListenerAttribute;
}
public final class center/sciprog/maps/features/ClickRadius : center/sciprog/attributes/Attribute {
public static final field $stable I
public static final field INSTANCE Lcenter/sciprog/maps/features/ClickRadius;
}
public final class center/sciprog/maps/features/ColorAttribute : center/sciprog/attributes/Attribute {
public static final field $stable I
public static final field INSTANCE Lcenter/sciprog/maps/features/ColorAttribute;
}
public final class center/sciprog/maps/features/CompositeFeaturesKt {
public static final fun draggableLine (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/maps/features/FeatureRef;Lcenter/sciprog/maps/features/FeatureRef;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun draggableLine$default (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/maps/features/FeatureRef;Lcenter/sciprog/maps/features/FeatureRef;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun draggableMultiLine (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/util/List;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun draggableMultiLine$default (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/util/List;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun draggableMultiLineFromPoints (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/util/List;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun draggableMultiLineFromPoints$default (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/util/List;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
}
public abstract interface class center/sciprog/maps/features/CoordinateSpace {
public abstract fun Rectangle (Ljava/lang/Object;Ljava/lang/Object;)Lcenter/sciprog/maps/features/Rectangle;
public abstract fun Rectangle-Iw8sGQE (Ljava/lang/Object;FJ)Lcenter/sciprog/maps/features/Rectangle;
public abstract fun ViewPoint (Ljava/lang/Object;F)Lcenter/sciprog/maps/features/ViewPoint;
public fun distanceTo-ccRj1GA (Ljava/lang/Object;Ljava/lang/Object;F)F
public fun distanceToLine-DwT6o7Y (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;F)F
public abstract fun getDefaultViewPoint ()Lcenter/sciprog/maps/features/ViewPoint;
public abstract fun isInsidePolygon (Ljava/lang/Object;Ljava/util/List;)Z
public abstract fun moveBy (Lcenter/sciprog/maps/features/ViewPoint;Ljava/lang/Object;)Lcenter/sciprog/maps/features/ViewPoint;
public abstract fun offsetTo-LQHvzoY (Ljava/lang/Object;Ljava/lang/Object;F)J
public abstract fun withCenter (Lcenter/sciprog/maps/features/Rectangle;Ljava/lang/Object;)Lcenter/sciprog/maps/features/Rectangle;
public abstract fun wrapPoints (Ljava/util/Collection;)Lcenter/sciprog/maps/features/Rectangle;
public abstract fun wrapRectangles (Ljava/util/Collection;)Lcenter/sciprog/maps/features/Rectangle;
public abstract fun zoomBy (Lcenter/sciprog/maps/features/ViewPoint;FLjava/lang/Object;)Lcenter/sciprog/maps/features/ViewPoint;
public static synthetic fun zoomBy$default (Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/ViewPoint;FLjava/lang/Object;ILjava/lang/Object;)Lcenter/sciprog/maps/features/ViewPoint;
}
public final class center/sciprog/maps/features/CoordinateSpaceKt {
public static final fun Rectangle-Iw8sGQE (Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/ViewPoint;J)Lcenter/sciprog/maps/features/Rectangle;
}
public abstract class center/sciprog/maps/features/CoordinateViewScope {
public static final field $stable I
public fun <init> (Lcenter/sciprog/maps/features/ViewConfig;)V
public abstract fun computeViewPoint (Lcenter/sciprog/maps/features/Rectangle;)Lcenter/sciprog/maps/features/ViewPoint;
public final fun getCanvasSize-MYxV2XQ ()J
public final fun getConfig ()Lcenter/sciprog/maps/features/ViewConfig;
public final fun getSelectRect ()Landroidx/compose/ui/unit/DpRect;
public abstract fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun getViewPoint ()Lcenter/sciprog/maps/features/ViewPoint;
public final fun getZoom ()F
public abstract fun moveBy-VpY3zN4 (Lcenter/sciprog/maps/features/ViewPoint;FF)Lcenter/sciprog/maps/features/ViewPoint;
public final fun setCanvasSize-EaSLcWc (J)V
public final fun setSelectRect (Landroidx/compose/ui/unit/DpRect;)V
public final fun setViewPoint (Lcenter/sciprog/maps/features/ViewPoint;)V
public final fun toCoordinates-3MmeM6k (JLandroidx/compose/ui/unit/Density;)Ljava/lang/Object;
public abstract fun toCoordinates-jo-Fl9I (J)Ljava/lang/Object;
public abstract fun toDpOffset-gVRvYmI (Ljava/lang/Object;)J
public abstract fun toDpRect (Lcenter/sciprog/maps/features/Rectangle;)Landroidx/compose/ui/unit/DpRect;
public final fun toOffset-dBAh8RU (Ljava/lang/Object;Landroidx/compose/ui/unit/Density;)J
}
public final class center/sciprog/maps/features/CoordinateViewScopeKt {
public static final fun getBottomRight (Landroidx/compose/ui/unit/DpRect;)J
public static final fun getTopLeft (Landroidx/compose/ui/unit/DpRect;)J
}
public abstract interface class center/sciprog/maps/features/DomainFeature : center/sciprog/maps/features/Feature {
public fun contains (Lcenter/sciprog/maps/features/ViewPoint;)Z
}
public abstract interface class center/sciprog/maps/features/DragHandle {
public static final field Companion Lcenter/sciprog/maps/features/DragHandle$Companion;
public abstract fun handle (Landroidx/compose/ui/input/pointer/PointerEvent;Lcenter/sciprog/maps/features/ViewPoint;Lcenter/sciprog/maps/features/ViewPoint;)Lcenter/sciprog/maps/features/DragResult;
}
public final class center/sciprog/maps/features/DragHandle$Companion {
public final fun bypass ()Lcenter/sciprog/maps/features/DragHandle;
public final fun combine ([Lcenter/sciprog/maps/features/DragHandle;)Lcenter/sciprog/maps/features/DragHandle;
public final fun withPrimaryButton (Lkotlin/jvm/functions/Function3;)Lcenter/sciprog/maps/features/DragHandle;
}
public abstract interface class center/sciprog/maps/features/DragListener {
public abstract fun handle (Landroidx/compose/ui/input/pointer/PointerEvent;Lcenter/sciprog/maps/features/ViewPoint;Lcenter/sciprog/maps/features/ViewPoint;)V
}
public final class center/sciprog/maps/features/DragListenerAttribute : center/sciprog/attributes/SetAttribute {
public static final field $stable I
public static final field INSTANCE Lcenter/sciprog/maps/features/DragListenerAttribute;
}
public final class center/sciprog/maps/features/DragResult {
public static final field $stable I
public fun <init> (Lcenter/sciprog/maps/features/ViewPoint;Z)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/ViewPoint;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/features/ViewPoint;
public final fun component2 ()Z
public final fun copy (Lcenter/sciprog/maps/features/ViewPoint;Z)Lcenter/sciprog/maps/features/DragResult;
public static synthetic fun copy$default (Lcenter/sciprog/maps/features/DragResult;Lcenter/sciprog/maps/features/ViewPoint;ZILjava/lang/Object;)Lcenter/sciprog/maps/features/DragResult;
public fun equals (Ljava/lang/Object;)Z
public final fun getHandleNext ()Z
public final fun getResult ()Lcenter/sciprog/maps/features/ViewPoint;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class center/sciprog/maps/features/DraggableAttribute : center/sciprog/attributes/Attribute {
public static final field $stable I
public static final field INSTANCE Lcenter/sciprog/maps/features/DraggableAttribute;
}
public abstract interface class center/sciprog/maps/features/DraggableFeature : center/sciprog/maps/features/DomainFeature {
public abstract fun withCoordinates (Ljava/lang/Object;)Lcenter/sciprog/maps/features/Feature;
}
public final class center/sciprog/maps/features/DrawFeature : center/sciprog/maps/features/DraggableFeature {
public static final field $stable I
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;Ljava/util/Map;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun component2 ()Ljava/lang/Object;
public final fun component3-hlWAoAs ()Ljava/util/Map;
public final fun component4 ()Lkotlin/jvm/functions/Function1;
public final fun copy-4xdkkFs (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/DrawFeature;
public static synthetic fun copy-4xdkkFs$default (Lcenter/sciprog/maps/features/DrawFeature;Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;Ljava/util/Map;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcenter/sciprog/maps/features/DrawFeature;
public fun equals (Ljava/lang/Object;)Z
public fun getAttributes-hlWAoAs ()Ljava/util/Map;
public fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public final fun getDrawFeature ()Lkotlin/jvm/functions/Function1;
public final fun getPosition ()Ljava/lang/Object;
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
public fun withCoordinates (Ljava/lang/Object;)Lcenter/sciprog/maps/features/Feature;
}
public final class center/sciprog/maps/features/DrawFeatureKt {
public static final fun drawFeature (Landroidx/compose/ui/graphics/drawscope/DrawScope;Lcenter/sciprog/maps/features/CoordinateViewScope;Ljava/util/Map;Lcenter/sciprog/maps/features/Feature;)V
}
public abstract interface class center/sciprog/maps/features/Feature {
public abstract fun getAttributes-hlWAoAs ()Ljava/util/Map;
public abstract fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public static synthetic fun getBoundingBox$default (Lcenter/sciprog/maps/features/Feature;FILjava/lang/Object;)Lcenter/sciprog/maps/features/Rectangle;
public abstract fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public abstract fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
}
public final class center/sciprog/maps/features/FeatureGroup : center/sciprog/maps/features/CoordinateSpace, center/sciprog/maps/features/Feature {
public static final field $stable I
public static final field Companion Lcenter/sciprog/maps/features/FeatureGroup$Companion;
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun Rectangle (Ljava/lang/Object;Ljava/lang/Object;)Lcenter/sciprog/maps/features/Rectangle;
public fun Rectangle-Iw8sGQE (Ljava/lang/Object;FJ)Lcenter/sciprog/maps/features/Rectangle;
public fun ViewPoint (Ljava/lang/Object;F)Lcenter/sciprog/maps/features/ViewPoint;
public final fun component1 ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun component2 ()Landroidx/compose/runtime/snapshots/SnapshotStateMap;
public final fun component3-hlWAoAs ()Ljava/util/Map;
public final fun copy-EHyjM0Q (Lcenter/sciprog/maps/features/CoordinateSpace;Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/util/Map;)Lcenter/sciprog/maps/features/FeatureGroup;
public static synthetic fun copy-EHyjM0Q$default (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/maps/features/CoordinateSpace;Landroidx/compose/runtime/snapshots/SnapshotStateMap;Ljava/util/Map;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureGroup;
public fun distanceTo-ccRj1GA (Ljava/lang/Object;Ljava/lang/Object;F)F
public fun distanceToLine-DwT6o7Y (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;F)F
public fun equals (Ljava/lang/Object;)Z
public final fun feature (Ljava/lang/String;Lcenter/sciprog/maps/features/Feature;)Lcenter/sciprog/maps/features/FeatureRef;
public fun getAttributes-hlWAoAs ()Ljava/util/Map;
public fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public fun getDefaultViewPoint ()Lcenter/sciprog/maps/features/ViewPoint;
public final fun getFeatureMap ()Landroidx/compose/runtime/snapshots/SnapshotStateMap;
public final fun getFeatures ()Ljava/util/Collection;
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public fun hashCode ()I
public fun isInsidePolygon (Ljava/lang/Object;Ljava/util/List;)Z
public fun moveBy (Lcenter/sciprog/maps/features/ViewPoint;Ljava/lang/Object;)Lcenter/sciprog/maps/features/ViewPoint;
public fun offsetTo-LQHvzoY (Ljava/lang/Object;Ljava/lang/Object;F)J
public final fun removeFeature (Ljava/lang/String;)V
public fun toString ()Ljava/lang/String;
public final fun visit (Lkotlin/jvm/functions/Function3;)V
public final fun visitUntil (Lkotlin/jvm/functions/Function3;)V
public fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
public fun withCenter (Lcenter/sciprog/maps/features/Rectangle;Ljava/lang/Object;)Lcenter/sciprog/maps/features/Rectangle;
public fun wrapPoints (Ljava/util/Collection;)Lcenter/sciprog/maps/features/Rectangle;
public fun wrapRectangles (Ljava/util/Collection;)Lcenter/sciprog/maps/features/Rectangle;
public fun zoomBy (Lcenter/sciprog/maps/features/ViewPoint;FLjava/lang/Object;)Lcenter/sciprog/maps/features/ViewPoint;
}
public final class center/sciprog/maps/features/FeatureGroup$Companion {
public final fun build (Lcenter/sciprog/maps/features/CoordinateSpace;Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/FeatureGroup;
public static synthetic fun build$default (Lcenter/sciprog/maps/features/FeatureGroup$Companion;Lcenter/sciprog/maps/features/CoordinateSpace;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureGroup;
public final fun remember (Lcenter/sciprog/maps/features/CoordinateSpace;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Lcenter/sciprog/maps/features/FeatureGroup;
}
public final class center/sciprog/maps/features/FeatureGroupKt {
public static final fun Structure2D (IILkotlin/jvm/functions/Function1;)Lspace/kscience/kmath/nd/Structure2D;
public static final fun StructureND-qL90JFI ([ILkotlin/jvm/functions/Function1;)Lspace/kscience/kmath/nd/StructureND;
public static final fun arc-f9v9FWM (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/maps/features/Rectangle;Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;Ljava/util/Map;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun arc-f9v9FWM$default (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/maps/features/Rectangle;Lspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun circle-vJIlCZg (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/lang/Object;FLjava/util/Map;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun circle-vJIlCZg$default (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/lang/Object;FLjava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun draw-UqNt6FI (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun draw-UqNt6FI$default (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun forEachWithAttribute (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/attributes/Attribute;Lkotlin/jvm/functions/Function4;)V
public static final fun forEachWithAttributeUntil (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/attributes/Attribute;Lkotlin/jvm/functions/Function4;)V
public static final fun getAttributes (Lcenter/sciprog/maps/features/FeatureRef;)Ljava/util/Map;
public static final fun group-WhaQnWU (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/util/Map;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun group-WhaQnWU$default (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/util/Map;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun icon-Rwk-ljY (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/lang/Object;Landroidx/compose/ui/graphics/vector/ImageVector;JLjava/util/Map;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun icon-Rwk-ljY$default (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/lang/Object;Landroidx/compose/ui/graphics/vector/ImageVector;JLjava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun line-JCOR_Dk (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun line-JCOR_Dk$default (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun multiLine-4xdkkFs (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun multiLine-4xdkkFs$default (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun pixelMap-JCOR_Dk (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/maps/features/Rectangle;Lspace/kscience/kmath/nd/Structure2D;Ljava/util/Map;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun pixelMap-JCOR_Dk$default (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/maps/features/Rectangle;Lspace/kscience/kmath/nd/Structure2D;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun points-4xdkkFs (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun points-4xdkkFs$default (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun polygon-4xdkkFs (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun polygon-4xdkkFs$default (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun rectangle-H_bhbsI (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/lang/Object;JLjava/util/Map;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun rectangle-H_bhbsI$default (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/lang/Object;JLjava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun resolve (Lcenter/sciprog/maps/features/FeatureRef;)Lcenter/sciprog/maps/features/Feature;
public static final fun scalableImage-UqNt6FI (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/maps/features/Rectangle;Ljava/util/Map;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun scalableImage-UqNt6FI$default (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/maps/features/Rectangle;Ljava/util/Map;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun text-f9v9FWM (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Ljava/util/Map;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun text-f9v9FWM$default (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
}
public final class center/sciprog/maps/features/FeatureKt {
public static final fun computeBoundingBox (Ljava/lang/Iterable;Lcenter/sciprog/maps/features/CoordinateSpace;F)Lcenter/sciprog/maps/features/Rectangle;
public static final fun getColor (Lcenter/sciprog/maps/features/Feature;)Landroidx/compose/ui/graphics/Color;
public static final fun getName (Lcenter/sciprog/maps/features/Feature;)Ljava/lang/String;
public static final fun getZoomRange (Lcenter/sciprog/maps/features/Feature;)Lkotlin/ranges/ClosedFloatingPointRange;
}
public final class center/sciprog/maps/features/FeatureRef {
public static final field $stable I
public fun <init> (Ljava/lang/String;Lcenter/sciprog/maps/features/FeatureGroup;)V
public final fun getId ()Ljava/lang/String;
public final fun getParent ()Lcenter/sciprog/maps/features/FeatureGroup;
}
public final class center/sciprog/maps/features/FeatureSelector : center/sciprog/maps/features/Feature {
public static final field $stable I
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/util/Map;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun component2-hlWAoAs ()Ljava/util/Map;
public final fun component3 ()Lkotlin/jvm/functions/Function1;
public final fun copy-I8jStn0 (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/FeatureSelector;
public static synthetic fun copy-I8jStn0$default (Lcenter/sciprog/maps/features/FeatureSelector;Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/util/Map;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureSelector;
public fun equals (Ljava/lang/Object;)Z
public fun getAttributes-hlWAoAs ()Ljava/util/Map;
public fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public final fun getSelector ()Lkotlin/jvm/functions/Function1;
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
}
public final class center/sciprog/maps/features/HoverListenerAttribute : center/sciprog/attributes/SetAttribute {
public static final field $stable I
public static final field INSTANCE Lcenter/sciprog/maps/features/HoverListenerAttribute;
}
public final class center/sciprog/maps/features/LineFeature : center/sciprog/maps/features/DomainFeature, center/sciprog/maps/features/LineSegmentFeature {
public static final field $stable I
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun component2 ()Ljava/lang/Object;
public final fun component3 ()Ljava/lang/Object;
public final fun component4-hlWAoAs ()Ljava/util/Map;
public fun contains (Lcenter/sciprog/maps/features/ViewPoint;)Z
public final fun copy-a7qfu0U (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Map;)Lcenter/sciprog/maps/features/LineFeature;
public static synthetic fun copy-a7qfu0U$default (Lcenter/sciprog/maps/features/LineFeature;Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Map;ILjava/lang/Object;)Lcenter/sciprog/maps/features/LineFeature;
public fun equals (Ljava/lang/Object;)Z
public final fun getA ()Ljava/lang/Object;
public fun getAttributes-hlWAoAs ()Ljava/util/Map;
public final fun getB ()Ljava/lang/Object;
public fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public final fun getCenter ()Ljava/lang/Object;
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
}
public abstract interface class center/sciprog/maps/features/LineSegmentFeature : center/sciprog/maps/features/Feature {
}
public final class center/sciprog/maps/features/MapFeatureAttributesKt {
public static final fun color-4WTKRHQ (Lcenter/sciprog/maps/features/FeatureRef;J)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun draggable (Lcenter/sciprog/maps/features/FeatureRef;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun draggable$default (Lcenter/sciprog/maps/features/FeatureRef;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun modifyAttribute (Lcenter/sciprog/maps/features/FeatureRef;Lcenter/sciprog/attributes/Attribute;Ljava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun modifyAttributes (Lcenter/sciprog/maps/features/FeatureRef;Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun onClick (Lcenter/sciprog/maps/features/FeatureRef;Landroidx/compose/foundation/PointerMatcher;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun onClick (Lcenter/sciprog/maps/features/FeatureRef;Lkotlin/jvm/functions/Function2;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun onClick$default (Lcenter/sciprog/maps/features/FeatureRef;Landroidx/compose/foundation/PointerMatcher;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun onDrag (Lcenter/sciprog/maps/features/FeatureRef;Lkotlin/jvm/functions/Function3;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun onHover (Lcenter/sciprog/maps/features/FeatureRef;Lkotlin/jvm/functions/Function2;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun pathEffect (Lcenter/sciprog/maps/features/FeatureRef;Landroidx/compose/ui/graphics/PathEffect;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun stroke (Lcenter/sciprog/maps/features/FeatureRef;F)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun zoomRange (Lcenter/sciprog/maps/features/FeatureRef;Lkotlin/ranges/ClosedFloatingPointRange;)Lcenter/sciprog/maps/features/FeatureRef;
}
public abstract interface class center/sciprog/maps/features/MarkerFeature : center/sciprog/maps/features/DraggableFeature {
public abstract fun getCenter ()Ljava/lang/Object;
}
public abstract interface class center/sciprog/maps/features/MouseListener {
public static final field Companion Lcenter/sciprog/maps/features/MouseListener$Companion;
public abstract fun handle (Landroidx/compose/ui/input/pointer/PointerEvent;Lcenter/sciprog/maps/features/ViewPoint;)V
}
public final class center/sciprog/maps/features/MouseListener$Companion {
public final fun withPrimaryButton (Lkotlin/jvm/functions/Function2;)Lcenter/sciprog/maps/features/MouseListener;
}
public final class center/sciprog/maps/features/MultiLineFeature : center/sciprog/maps/features/DomainFeature, center/sciprog/maps/features/LineSegmentFeature {
public static final field $stable I
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/util/List;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun component2 ()Ljava/util/List;
public final fun component3-hlWAoAs ()Ljava/util/Map;
public fun contains (Lcenter/sciprog/maps/features/ViewPoint;)Z
public final fun copy-EHyjM0Q (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/util/List;Ljava/util/Map;)Lcenter/sciprog/maps/features/MultiLineFeature;
public static synthetic fun copy-EHyjM0Q$default (Lcenter/sciprog/maps/features/MultiLineFeature;Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lcenter/sciprog/maps/features/MultiLineFeature;
public fun equals (Ljava/lang/Object;)Z
public fun getAttributes-hlWAoAs ()Ljava/util/Map;
public fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public final fun getPoints ()Ljava/util/List;
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
}
public abstract interface class center/sciprog/maps/features/PainterFeature : center/sciprog/maps/features/Feature {
public abstract fun getPainter (Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/painter/Painter;
}
public final class center/sciprog/maps/features/PathEffectAttribute : center/sciprog/attributes/Attribute {
public static final field $stable I
public static final field INSTANCE Lcenter/sciprog/maps/features/PathEffectAttribute;
}
public final class center/sciprog/maps/features/PathFeature : center/sciprog/maps/features/DraggableFeature {
public static final field $stable I
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/Rectangle;Landroidx/compose/ui/graphics/Path;Landroidx/compose/ui/graphics/Brush;Landroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/geometry/Rect;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/Rectangle;Landroidx/compose/ui/graphics/Path;Landroidx/compose/ui/graphics/Brush;Landroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/geometry/Rect;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun component2 ()Lcenter/sciprog/maps/features/Rectangle;
public final fun component3 ()Landroidx/compose/ui/graphics/Path;
public final fun component4 ()Landroidx/compose/ui/graphics/Brush;
public final fun component5 ()Landroidx/compose/ui/graphics/drawscope/DrawStyle;
public final fun component6 ()Landroidx/compose/ui/geometry/Rect;
public final fun component7-hlWAoAs ()Ljava/util/Map;
public final fun copy-mDmFKLY (Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/Rectangle;Landroidx/compose/ui/graphics/Path;Landroidx/compose/ui/graphics/Brush;Landroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/geometry/Rect;Ljava/util/Map;)Lcenter/sciprog/maps/features/PathFeature;
public static synthetic fun copy-mDmFKLY$default (Lcenter/sciprog/maps/features/PathFeature;Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/Rectangle;Landroidx/compose/ui/graphics/Path;Landroidx/compose/ui/graphics/Brush;Landroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/geometry/Rect;Ljava/util/Map;ILjava/lang/Object;)Lcenter/sciprog/maps/features/PathFeature;
public fun equals (Ljava/lang/Object;)Z
public fun getAttributes-hlWAoAs ()Ljava/util/Map;
public fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public final fun getBrush ()Landroidx/compose/ui/graphics/Brush;
public final fun getPath ()Landroidx/compose/ui/graphics/Path;
public final fun getRectangle ()Lcenter/sciprog/maps/features/Rectangle;
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun getStyle ()Landroidx/compose/ui/graphics/drawscope/DrawStyle;
public final fun getTargetRect ()Landroidx/compose/ui/geometry/Rect;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
public fun withCoordinates (Ljava/lang/Object;)Lcenter/sciprog/maps/features/Feature;
}
public final class center/sciprog/maps/features/PixelMapFeature : center/sciprog/maps/features/Feature {
public static final field $stable I
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/Rectangle;Lspace/kscience/kmath/nd/Structure2D;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/Rectangle;Lspace/kscience/kmath/nd/Structure2D;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun component2 ()Lcenter/sciprog/maps/features/Rectangle;
public final fun component3 ()Lspace/kscience/kmath/nd/Structure2D;
public final fun component4-hlWAoAs ()Ljava/util/Map;
public final fun copy-a7qfu0U (Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/Rectangle;Lspace/kscience/kmath/nd/Structure2D;Ljava/util/Map;)Lcenter/sciprog/maps/features/PixelMapFeature;
public static synthetic fun copy-a7qfu0U$default (Lcenter/sciprog/maps/features/PixelMapFeature;Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/Rectangle;Lspace/kscience/kmath/nd/Structure2D;Ljava/util/Map;ILjava/lang/Object;)Lcenter/sciprog/maps/features/PixelMapFeature;
public fun equals (Ljava/lang/Object;)Z
public fun getAttributes-hlWAoAs ()Ljava/util/Map;
public fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public final fun getPixelMap ()Lspace/kscience/kmath/nd/Structure2D;
public final fun getRectangle ()Lcenter/sciprog/maps/features/Rectangle;
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
}
public final class center/sciprog/maps/features/PointsFeature : center/sciprog/maps/features/Feature {
public static final field $stable I
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/util/List;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun component2 ()Ljava/util/List;
public final fun component3-hlWAoAs ()Ljava/util/Map;
public final fun copy-EHyjM0Q (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/util/List;Ljava/util/Map;)Lcenter/sciprog/maps/features/PointsFeature;
public static synthetic fun copy-EHyjM0Q$default (Lcenter/sciprog/maps/features/PointsFeature;Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lcenter/sciprog/maps/features/PointsFeature;
public fun equals (Ljava/lang/Object;)Z
public fun getAttributes-hlWAoAs ()Ljava/util/Map;
public fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public final fun getPoints ()Ljava/util/List;
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
}
public final class center/sciprog/maps/features/PolygonFeature : center/sciprog/maps/features/DomainFeature {
public static final field $stable I
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/util/List;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun component2 ()Ljava/util/List;
public final fun component3-hlWAoAs ()Ljava/util/Map;
public fun contains (Lcenter/sciprog/maps/features/ViewPoint;)Z
public final fun copy-EHyjM0Q (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/util/List;Ljava/util/Map;)Lcenter/sciprog/maps/features/PolygonFeature;
public static synthetic fun copy-EHyjM0Q$default (Lcenter/sciprog/maps/features/PolygonFeature;Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lcenter/sciprog/maps/features/PolygonFeature;
public fun equals (Ljava/lang/Object;)Z
public fun getAttributes-hlWAoAs ()Ljava/util/Map;
public fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public final fun getPoints ()Ljava/util/List;
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
}
public abstract interface class center/sciprog/maps/features/Rectangle : center/sciprog/maps/features/Area {
public abstract fun getA ()Ljava/lang/Object;
public abstract fun getB ()Ljava/lang/Object;
public abstract fun getCenter ()Ljava/lang/Object;
}
public final class center/sciprog/maps/features/RectangleFeature : center/sciprog/maps/features/MarkerFeature {
public static final field $stable I
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;JLjava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;JLjava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun component2 ()Ljava/lang/Object;
public final fun component3-MYxV2XQ ()J
public final fun component4-hlWAoAs ()Ljava/util/Map;
public final fun copy-tzWo0Eo (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;JLjava/util/Map;)Lcenter/sciprog/maps/features/RectangleFeature;
public static synthetic fun copy-tzWo0Eo$default (Lcenter/sciprog/maps/features/RectangleFeature;Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;JLjava/util/Map;ILjava/lang/Object;)Lcenter/sciprog/maps/features/RectangleFeature;
public fun equals (Ljava/lang/Object;)Z
public fun getAttributes-hlWAoAs ()Ljava/util/Map;
public fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public fun getCenter ()Ljava/lang/Object;
public final fun getSize-MYxV2XQ ()J
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
public fun withCoordinates (Ljava/lang/Object;)Lcenter/sciprog/maps/features/Feature;
}
public final class center/sciprog/maps/features/ScalableImageFeature : center/sciprog/maps/features/Feature, center/sciprog/maps/features/PainterFeature {
public static final field $stable I
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/Rectangle;Ljava/util/Map;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/Rectangle;Ljava/util/Map;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun component2 ()Lcenter/sciprog/maps/features/Rectangle;
public final fun component3-hlWAoAs ()Ljava/util/Map;
public final fun component4 ()Lkotlin/jvm/functions/Function2;
public final fun copy-4xdkkFs (Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/Rectangle;Ljava/util/Map;Lkotlin/jvm/functions/Function2;)Lcenter/sciprog/maps/features/ScalableImageFeature;
public static synthetic fun copy-4xdkkFs$default (Lcenter/sciprog/maps/features/ScalableImageFeature;Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/features/Rectangle;Ljava/util/Map;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcenter/sciprog/maps/features/ScalableImageFeature;
public fun equals (Ljava/lang/Object;)Z
public fun getAttributes-hlWAoAs ()Ljava/util/Map;
public fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public final fun getPainter ()Lkotlin/jvm/functions/Function2;
public fun getPainter (Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/painter/Painter;
public final fun getRectangle ()Lcenter/sciprog/maps/features/Rectangle;
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
}
public final class center/sciprog/maps/features/StrokeAttribute : center/sciprog/attributes/Attribute {
public static final field $stable I
public static final field INSTANCE Lcenter/sciprog/maps/features/StrokeAttribute;
}
public final class center/sciprog/maps/features/TextFeature : center/sciprog/maps/features/DraggableFeature {
public static final field $stable I
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;Ljava/lang/String;Ljava/util/Map;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;Ljava/lang/String;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun component2 ()Ljava/lang/Object;
public final fun component3 ()Ljava/lang/String;
public final fun component4-hlWAoAs ()Ljava/util/Map;
public final fun component5 ()Lkotlin/jvm/functions/Function1;
public final fun copy-JCOR_Dk (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;Ljava/lang/String;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/TextFeature;
public static synthetic fun copy-JCOR_Dk$default (Lcenter/sciprog/maps/features/TextFeature;Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;Ljava/lang/String;Ljava/util/Map;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcenter/sciprog/maps/features/TextFeature;
public fun equals (Ljava/lang/Object;)Z
public fun getAttributes-hlWAoAs ()Ljava/util/Map;
public fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public final fun getFontConfig ()Lkotlin/jvm/functions/Function1;
public final fun getPosition ()Ljava/lang/Object;
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun getText ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
public fun withCoordinates (Ljava/lang/Object;)Lcenter/sciprog/maps/features/Feature;
}
public final class center/sciprog/maps/features/VectorIconFeature : center/sciprog/maps/features/MarkerFeature, center/sciprog/maps/features/PainterFeature {
public static final field $stable I
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;JLandroidx/compose/ui/graphics/vector/ImageVector;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;JLandroidx/compose/ui/graphics/vector/ImageVector;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/features/CoordinateSpace;
public final fun component2 ()Ljava/lang/Object;
public final fun component3-MYxV2XQ ()J
public final fun component4 ()Landroidx/compose/ui/graphics/vector/ImageVector;
public final fun component5-hlWAoAs ()Ljava/util/Map;
public final fun copy-xcw5jm0 (Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;JLandroidx/compose/ui/graphics/vector/ImageVector;Ljava/util/Map;)Lcenter/sciprog/maps/features/VectorIconFeature;
public static synthetic fun copy-xcw5jm0$default (Lcenter/sciprog/maps/features/VectorIconFeature;Lcenter/sciprog/maps/features/CoordinateSpace;Ljava/lang/Object;JLandroidx/compose/ui/graphics/vector/ImageVector;Ljava/util/Map;ILjava/lang/Object;)Lcenter/sciprog/maps/features/VectorIconFeature;
public fun equals (Ljava/lang/Object;)Z
public fun getAttributes-hlWAoAs ()Ljava/util/Map;
public fun getBoundingBox (F)Lcenter/sciprog/maps/features/Rectangle;
public fun getCenter ()Ljava/lang/Object;
public final fun getImage ()Landroidx/compose/ui/graphics/vector/ImageVector;
public synthetic fun getPainter (Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/painter/Painter;
public fun getPainter (Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/vector/VectorPainter;
public final fun getSize-MYxV2XQ ()J
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun withAttributes (Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/Feature;
public fun withCoordinates (Ljava/lang/Object;)Lcenter/sciprog/maps/features/Feature;
}
public final class center/sciprog/maps/features/ViewConfig {
public static final field $stable I
public fun <init> ()V
public fun <init> (FLcenter/sciprog/maps/features/MouseListener;Lcenter/sciprog/maps/features/DragHandle;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ZZ)V
public synthetic fun <init> (FLcenter/sciprog/maps/features/MouseListener;Lcenter/sciprog/maps/features/DragHandle;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()F
public final fun component2 ()Lcenter/sciprog/maps/features/MouseListener;
public final fun component3 ()Lcenter/sciprog/maps/features/DragHandle;
public final fun component4 ()Lkotlin/jvm/functions/Function1;
public final fun component5 ()Lkotlin/jvm/functions/Function1;
public final fun component6 ()Lkotlin/jvm/functions/Function1;
public final fun component7 ()Z
public final fun component8 ()Z
public final fun copy (FLcenter/sciprog/maps/features/MouseListener;Lcenter/sciprog/maps/features/DragHandle;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ZZ)Lcenter/sciprog/maps/features/ViewConfig;
public static synthetic fun copy$default (Lcenter/sciprog/maps/features/ViewConfig;FLcenter/sciprog/maps/features/MouseListener;Lcenter/sciprog/maps/features/DragHandle;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ZZILjava/lang/Object;)Lcenter/sciprog/maps/features/ViewConfig;
public fun equals (Ljava/lang/Object;)Z
public final fun getDragHandle ()Lcenter/sciprog/maps/features/DragHandle;
public final fun getOnCanvasSizeChange ()Lkotlin/jvm/functions/Function1;
public final fun getOnClick ()Lcenter/sciprog/maps/features/MouseListener;
public final fun getOnSelect ()Lkotlin/jvm/functions/Function1;
public final fun getOnViewChange ()Lkotlin/jvm/functions/Function1;
public final fun getZoomOnDoubleClick ()Z
public final fun getZoomOnSelect ()Z
public final fun getZoomSpeed ()F
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public abstract interface class center/sciprog/maps/features/ViewPoint {
public abstract fun getFocus ()Ljava/lang/Object;
public abstract fun getZoom ()F
}
public final class center/sciprog/maps/features/VisibleAttribute : center/sciprog/attributes/Attribute {
public static final field $stable I
public static final field INSTANCE Lcenter/sciprog/maps/features/VisibleAttribute;
}
public final class center/sciprog/maps/features/ZAttribute : center/sciprog/attributes/Attribute {
public static final field $stable I
public static final field INSTANCE Lcenter/sciprog/maps/features/ZAttribute;
}
public final class center/sciprog/maps/features/ZoomRangeAttribute : center/sciprog/attributes/Attribute {
public static final field $stable I
public static final field INSTANCE Lcenter/sciprog/maps/features/ZoomRangeAttribute;
}

View File

@ -1,30 +0,0 @@
plugins {
id("space.kscience.gradle.mpp")
id("org.jetbrains.compose")
`maven-publish`
}
val kmathVersion: String by rootProject.extra
kscience{
jvm()
js()
useSerialization{
json()
}
useSerialization(sourceSet = space.kscience.gradle.DependencySourceSet.TEST){
protobuf()
}
}
kotlin {
sourceSets {
commonMain {
dependencies {
api(projects.trajectoryKt)
api(compose.foundation)
}
}
}
}

View File

@ -1,22 +0,0 @@
package center.sciprog.attributes
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
public interface Attribute<T>
public abstract class SerializableAttribute<T>(
public val serialId: String,
public val serializer: KSerializer<T>,
) : Attribute<T> {
override fun toString(): String = serialId
}
public interface AttributeWithDefault<T> : Attribute<T> {
public val default: T
}
public interface SetAttribute<V> : Attribute<Set<V>>
public object NameAttribute : SerializableAttribute<String>("name", String.serializer())

View File

@ -1,74 +0,0 @@
package center.sciprog.attributes
import center.sciprog.maps.features.Feature
import center.sciprog.maps.features.ZAttribute
import kotlin.jvm.JvmInline
@JvmInline
public value class Attributes internal constructor(public val content: Map<out Attribute<*>, Any>) {
public val keys: Set<Attribute<*>> get() = content.keys
@Suppress("UNCHECKED_CAST")
public operator fun <T> get(attribute: Attribute<T>): T? = content[attribute] as? T
override fun toString(): String = "Attributes(value=${content.entries})"
public companion object {
public val EMPTY: Attributes = Attributes(emptyMap())
}
}
public fun Attributes.isEmpty(): Boolean = content.isEmpty()
public fun <T> Attributes.getOrDefault(attribute: AttributeWithDefault<T>): T = get(attribute) ?: attribute.default
public fun <T, A : Attribute<T>> Attributes.withAttribute(
attribute: A,
attrValue: T?,
): Attributes = Attributes(
if (attrValue == null) {
content - attribute
} else {
content + (attribute to attrValue)
}
)
/**
* Add an element to a [SetAttribute]
*/
public fun <T, A : SetAttribute<T>> Attributes.withAttributeElement(
attribute: A,
attrValue: T,
): Attributes {
val currentSet: Set<T> = get(attribute) ?: emptySet()
return Attributes(
content + (attribute to (currentSet + attrValue))
)
}
/**
* Remove an element from [SetAttribute]
*/
public fun <T, A : SetAttribute<T>> Attributes.withoutAttributeElement(
attribute: A,
attrValue: T,
): Attributes {
val currentSet: Set<T> = get(attribute) ?: emptySet()
return Attributes(
content + (attribute to (currentSet - attrValue))
)
}
public fun <T : Any, A : Attribute<T>> Attributes(
attribute: A,
attrValue: T,
): Attributes = Attributes(mapOf(attribute to attrValue))
public operator fun Attributes.plus(other: Attributes): Attributes = Attributes(content + other.content)
public val Feature<*>.z: Float
get() = attributes[ZAttribute] ?: 0f
// set(value) {
// attributes[ZAttribute] = value
// }

View File

@ -1,47 +0,0 @@
package center.sciprog.attributes
/**
* A safe builder for [Attributes]
*/
public class AttributesBuilder internal constructor(private val map: MutableMap<Attribute<*>, Any> = mutableMapOf()) {
@Suppress("UNCHECKED_CAST")
public operator fun <T> get(attribute: Attribute<T>): T? = map[attribute] as? T
public operator fun <V> Attribute<V>.invoke(value: V?) {
if (value == null) {
map.remove(this)
} else {
map[this] = value
}
}
public fun from(attributes: Attributes) {
map.putAll(attributes.content)
}
public fun <V> SetAttribute<V>.add(
attrValue: V,
) {
val currentSet: Set<V> = get(this) ?: emptySet()
map[this] = currentSet + attrValue
}
/**
* Remove an element from [SetAttribute]
*/
public fun <V> SetAttribute<V>.remove(
attrValue: V,
) {
val currentSet: Set<V> = get(this) ?: emptySet()
map[this] = currentSet - attrValue
}
public fun build(): Attributes = Attributes(map)
}
public fun AttributesBuilder(
attributes: Attributes,
): AttributesBuilder = AttributesBuilder(attributes.content.toMutableMap())
public fun Attributes(builder: AttributesBuilder.() -> Unit): Attributes = AttributesBuilder().apply(builder).build()

View File

@ -1,55 +0,0 @@
@file:Suppress("UNCHECKED_CAST")
package center.sciprog.attributes
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.*
public class AttributesSerializer(
private val serializableAttributes: Set<SerializableAttribute<*>>,
) : KSerializer<Attributes> {
private val jsonSerializer = JsonObject.serializer()
override val descriptor: SerialDescriptor get() = jsonSerializer.descriptor
override fun deserialize(decoder: Decoder): Attributes {
val jsonElement = jsonSerializer.deserialize(decoder)
val attributeMap: Map<SerializableAttribute<*>, Any> = jsonElement.entries.associate { (key, element) ->
val attr = serializableAttributes.find { it.serialId == key }
?: error("Attribute serializer for key $key not found")
val json = if (decoder is JsonDecoder) {
decoder.json
} else {
Json { serializersModule = decoder.serializersModule }
}
val value = json.decodeFromJsonElement(attr.serializer, element) ?: error("Null values are not allowed")
attr to value
}
return Attributes(attributeMap)
}
override fun serialize(encoder: Encoder, value: Attributes) {
val json = buildJsonObject {
value.content.forEach { (key: Attribute<*>, value: Any) ->
if (key !in serializableAttributes) error("An attribute key '$key' is not in the list of allowed attributes for this serializer")
val serializableKey = key as SerializableAttribute
val json = if (encoder is JsonEncoder) {
encoder.json
} else {
Json { serializersModule = encoder.serializersModule }
}
put(
serializableKey.serialId,
json.encodeToJsonElement(serializableKey.serializer as KSerializer<Any>, value)
)
}
}
jsonSerializer.serialize(encoder, json)
}
}

View File

@ -1,86 +0,0 @@
package center.sciprog.maps.features
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import kotlin.math.abs
import kotlin.math.sqrt
public interface Area<T : Any> {
public operator fun contains(point: T): Boolean
}
/**
* A map coordinates rectangle. [a] and [b] represent opposing angles
* of the rectangle without specifying which ones.
*/
public interface Rectangle<T : Any> : Area<T> {
public val a: T
public val b: T
public val center: T
}
/**
* A context for map/scheme coordinates manipulation
*/
public interface CoordinateSpace<T : Any> {
/**
* Build a rectangle by two opposing corners
*/
public fun Rectangle(first: T, second: T): Rectangle<T>
/**
* Build a rectangle of visual size [size]
*/
public fun Rectangle(center: T, zoom: Float, size: DpSize): Rectangle<T>
/**
* A view point used by default
*/
public val defaultViewPoint: ViewPoint<T>
/**
* Create a [ViewPoint] associated with this coordinate space.
*/
public fun ViewPoint(center: T, zoom: Float): ViewPoint<T>
public fun ViewPoint<T>.moveBy(delta: T): ViewPoint<T>
public fun ViewPoint<T>.zoomBy(
zoomDelta: Float,
invariant: T = focus,
): ViewPoint<T>
/**
* Move given rectangle to be centered at [center]
*/
public fun Rectangle<T>.withCenter(center: T): Rectangle<T>
public fun Collection<Rectangle<T>>.wrapRectangles(): Rectangle<T>?
public fun Collection<T>.wrapPoints(): Rectangle<T>?
public fun T.offsetTo(b: T, zoom: Float): DpOffset
public fun T.distanceTo(b: T, zoom: Float): Dp {
val offset: DpOffset = offsetTo(b, zoom)
return sqrt(offset.x.value * offset.x.value + offset.y.value * offset.y.value).dp
}
public fun T.distanceToLine(a: T, b: T, zoom: Float): Dp {
val d12 = a.offsetTo(b, zoom)
val d01 = offsetTo(a, zoom)
val distanceVale = abs(d12.x.value * d01.y.value - d12.y.value * d01.x.value) / a.distanceTo(b, zoom).value
return distanceVale.dp
}
public fun T.isInsidePolygon(points: List<T>): Boolean
}
public fun <T : Any> CoordinateSpace<T>.Rectangle(viewPoint: ViewPoint<T>, size: DpSize): Rectangle<T> =
Rectangle(viewPoint.focus, viewPoint.zoom, size)

View File

@ -1,63 +0,0 @@
package center.sciprog.maps.features
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.*
public abstract class CoordinateViewScope<T : Any>(
public val config: ViewConfig<T>,
) {
public abstract val space: CoordinateSpace<T>
private var canvasSizeState: MutableState<DpSize?> = mutableStateOf(null)
private var viewPointState: MutableState<ViewPoint<T>?> = mutableStateOf(null)
public var canvasSize: DpSize
get() = canvasSizeState.value ?: DpSize(512.dp, 512.dp)
set(value) {
canvasSizeState.value = value
}
public var viewPoint: ViewPoint<T>
get() = viewPointState.value ?: space.defaultViewPoint
set(value) {
viewPointState.value = value
config.onViewChange(viewPoint)
}
public val zoom: Float get() = viewPoint.zoom
// Selection rectangle. If null - no selection
public var selectRect: DpRect? by mutableStateOf(null)
public abstract fun DpOffset.toCoordinates(): T
public abstract fun T.toDpOffset(): DpOffset
public fun T.toOffset(density: Density): Offset = with(density) {
val dpOffset = this@toOffset.toDpOffset()
Offset(dpOffset.x.toPx(), dpOffset.y.toPx())
}
public fun Offset.toCoordinates(density: Density): T = with(density) {
val dpOffset = DpOffset(x.toDp(), y.toDp())
dpOffset.toCoordinates()
}
public abstract fun Rectangle<T>.toDpRect(): DpRect
public abstract fun ViewPoint<T>.moveBy(x: Dp, y: Dp): ViewPoint<T>
public abstract fun computeViewPoint(rectangle: Rectangle<T>): ViewPoint<T>
}
public val DpRect.topLeft: DpOffset get() = DpOffset(left, top)
public val DpRect.bottomRight: DpOffset get() = DpOffset(right, bottom)

View File

@ -1,56 +0,0 @@
package center.sciprog.maps.features
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.isPrimaryPressed
/**
* @param result - the endpoint of the drag to perform constrained drag
* @param handleNext - if false do not evaluate subsequent drag handles
*/
public data class DragResult<T : Any>(val result: ViewPoint<T>, val handleNext: Boolean = true)
public fun interface DragListener<in T : Any> {
public fun handle(event: PointerEvent, from: ViewPoint<T>, to: ViewPoint<T>)
}
public fun interface DragHandle<T : Any> {
/**
* @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
*/
public fun handle(event: PointerEvent, start: ViewPoint<T>, end: ViewPoint<T>): DragResult<T>
public companion object {
public fun <T : Any> bypass(): DragHandle<T> = DragHandle<T> { _, _, end -> DragResult(end) }
/**
* Process only events with primary button pressed
*/
public fun <T : Any> withPrimaryButton(
block: (event: PointerEvent, start: ViewPoint<T>, end: ViewPoint<T>) -> DragResult<T>,
): DragHandle<T> = DragHandle { event, start, end ->
if (event.buttons.isPrimaryPressed) {
block(event, start, end)
} else {
DragResult(end)
}
}
/**
* Combine several handles into one
*/
public fun <T : Any> combine(vararg handles: DragHandle<T>): DragHandle<T> = DragHandle { event, start, end ->
var current: ViewPoint<T> = end
handles.forEach {
val result = it.handle(event, start, current)
if (!result.handleNext) return@DragHandle result else {
current = result.result
}
}
return@DragHandle DragResult(current)
}
}
}

View File

@ -1,364 +0,0 @@
package center.sciprog.maps.features
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.DrawStyle
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.VectorPainter
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import center.sciprog.attributes.Attributes
import center.sciprog.attributes.NameAttribute
import space.kscience.kmath.geometry.Angle
import space.kscience.kmath.nd.Structure2D
public typealias FloatRange = ClosedFloatingPointRange<Float>
public interface Feature<T : Any> {
public val space: CoordinateSpace<T>
public val attributes: Attributes
public fun getBoundingBox(zoom: Float = Float.MAX_VALUE): Rectangle<T>?
public fun withAttributes(modify: Attributes.() -> Attributes): Feature<T>
}
public val Feature<*>.color: Color? get() = attributes[ColorAttribute]
public val Feature<*>.zoomRange: FloatRange
get() = attributes[ZoomRangeAttribute] ?: Float.NEGATIVE_INFINITY..Float.POSITIVE_INFINITY
public val Feature<*>.name: String?
get() = attributes[NameAttribute]
public interface PainterFeature<T : Any> : Feature<T> {
@Composable
public fun getPainter(): Painter
}
public interface DomainFeature<T : Any> : Feature<T> {
public operator fun contains(viewPoint: ViewPoint<T>): Boolean = getBoundingBox(viewPoint.zoom)?.let {
viewPoint.focus in it
} ?: false
}
public interface DraggableFeature<T : Any> : DomainFeature<T> {
public fun withCoordinates(newCoordinates: T): Feature<T>
}
/**
* A draggable marker feature. Other features could be bound to this one.
*/
public interface MarkerFeature<T : Any> : DraggableFeature<T> {
public val center: T
}
public fun <T : Any> Iterable<Feature<T>>.computeBoundingBox(
space: CoordinateSpace<T>,
zoom: Float,
): Rectangle<T>? = with(space) {
mapNotNull { it.getBoundingBox(zoom) }.wrapRectangles()
}
/**
* A feature that decides what to show depending on the zoom value (it could change size of shape)
*/
@Stable
public data class FeatureSelector<T : Any>(
override val space: CoordinateSpace<T>,
override val attributes: Attributes = Attributes.EMPTY,
public val selector: (zoom: Float) -> Feature<T>,
) : Feature<T> {
override fun getBoundingBox(zoom: Float): Rectangle<T>? = selector(zoom).getBoundingBox(zoom)
override fun withAttributes(modify: Attributes.() -> Attributes): Feature<T> = copy(attributes = modify(attributes))
}
@Stable
public data class PathFeature<T : Any>(
override val space: CoordinateSpace<T>,
public val rectangle: Rectangle<T>,
public val path: Path,
public val brush: Brush,
public val style: DrawStyle = Fill,
public val targetRect: Rect = path.getBounds(),
override val attributes: Attributes = Attributes.EMPTY,
) : DraggableFeature<T> {
override fun withCoordinates(newCoordinates: T): Feature<T> = with(space) {
PathFeature(
space = space,
rectangle = rectangle.withCenter(newCoordinates),
path = path,
brush = brush,
style = style,
targetRect = targetRect,
)
}
override fun getBoundingBox(zoom: Float): Rectangle<T> = rectangle
override fun withAttributes(modify: Attributes.() -> Attributes): Feature<T> = copy(attributes = modify(attributes))
}
@Stable
public data class PointsFeature<T : Any>(
override val space: CoordinateSpace<T>,
public val points: List<T>,
override val attributes: Attributes = Attributes.EMPTY,
) : Feature<T> {
private val boundingBox by lazy {
with(space) { points.wrapPoints() }
}
override fun getBoundingBox(zoom: Float): Rectangle<T>? = boundingBox
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
}
public interface LineSegmentFeature<T : Any> : Feature<T>
@Stable
public data class LineFeature<T : Any>(
override val space: CoordinateSpace<T>,
public val a: T,
public val b: T,
override val attributes: Attributes = Attributes.EMPTY,
) : DomainFeature<T>, LineSegmentFeature<T> {
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(a, b)
val center: T by lazy { getBoundingBox().center }
override fun contains(viewPoint: ViewPoint<T>): Boolean = with(space) {
val length = a.distanceTo(b, viewPoint.zoom)
viewPoint.focus.distanceTo(center, viewPoint.zoom) <= length / 2 &&
viewPoint.focus.distanceToLine(a, b, viewPoint.zoom).value <= clickRadius
}
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
}
public data class MultiLineFeature<T : Any>(
override val space: CoordinateSpace<T>,
public val points: List<T>,
override val attributes: Attributes = Attributes.EMPTY,
) : DomainFeature<T>, LineSegmentFeature<T> {
private val boundingBox by lazy {
with(space) { points.wrapPoints() }
}
override fun getBoundingBox(zoom: Float): Rectangle<T>? = boundingBox
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
override fun contains(viewPoint: ViewPoint<T>): Boolean = with(space) {
val boundingBox = getBoundingBox(viewPoint.zoom) ?: return@with false
viewPoint.focus in boundingBox && points.zipWithNext().minOf { (a, b) ->
viewPoint.focus.distanceToLine(
a,
b,
viewPoint.zoom
).value
} < clickRadius
}
}
private val <T : Any, F : LineSegmentFeature<T>> F.clickRadius get() = attributes[ClickRadius] ?: 10f
@Stable
public data class PolygonFeature<T : Any>(
override val space: CoordinateSpace<T>,
public val points: List<T>,
override val attributes: Attributes = Attributes.EMPTY,
) : DomainFeature<T> {
private val boundingBox: Rectangle<T>? by lazy {
with(space) { points.wrapPoints() }
}
override fun getBoundingBox(zoom: Float): Rectangle<T>? = boundingBox
override fun contains(viewPoint: ViewPoint<T>): Boolean {
val boundingBox = boundingBox ?: return false
return viewPoint.focus in boundingBox && with(space) { viewPoint.focus.isInsidePolygon(points) }
}
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
}
@Stable
public data class CircleFeature<T : Any>(
override val space: CoordinateSpace<T>,
override val center: T,
public val radius: Dp = 5.dp,
override val attributes: Attributes = Attributes.EMPTY,
) : MarkerFeature<T> {
override fun getBoundingBox(zoom: Float): Rectangle<T> =
space.Rectangle(center, zoom, DpSize(radius * 2, radius * 2))
override fun contains(viewPoint: ViewPoint<T>): Boolean = with(space) {
viewPoint.focus.distanceTo(center, viewPoint.zoom) < radius
}
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(center = newCoordinates)
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
}
@Stable
public data class RectangleFeature<T : Any>(
override val space: CoordinateSpace<T>,
override val center: T,
public val size: DpSize = DpSize(5.dp, 5.dp),
override val attributes: Attributes = Attributes.EMPTY,
) : MarkerFeature<T> {
override fun getBoundingBox(zoom: Float): Rectangle<T> =
space.Rectangle(center, zoom, size)
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(center = newCoordinates)
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
}
/**
* @param startAngle the angle from 3 o'clock downwards for the start of the arc in radians
* @param arcLength arc length in radians
*/
@Stable
public data class ArcFeature<T : Any>(
override val space: CoordinateSpace<T>,
public val oval: Rectangle<T>,
public val startAngle: Angle,
public val arcLength: Angle,
override val attributes: Attributes = Attributes.EMPTY,
) : DraggableFeature<T> {
override fun getBoundingBox(zoom: Float): Rectangle<T> = oval
override fun withCoordinates(newCoordinates: T): Feature<T> =
copy(oval = with(space) { oval.withCenter(newCoordinates) })
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
}
public data class DrawFeature<T : Any>(
override val space: CoordinateSpace<T>,
public val position: T,
override val attributes: Attributes = Attributes.EMPTY,
public val drawFeature: DrawScope.() -> Unit,
) : DraggableFeature<T> {
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(position, position)
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(position = newCoordinates)
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
}
/**
* Fixed size bitmap icon
*/
@Stable
public data class BitmapIconFeature<T : Any>(
override val space: CoordinateSpace<T>,
override val center: T,
public val size: DpSize,
public val image: ImageBitmap,
override val attributes: Attributes = Attributes.EMPTY,
) : MarkerFeature<T> {
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(center, zoom, size)
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(center = newCoordinates)
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
}
/**
* Fixed size vector icon
*/
@Stable
public data class VectorIconFeature<T : Any>(
override val space: CoordinateSpace<T>,
override val center: T,
public val size: DpSize,
public val image: ImageVector,
override val attributes: Attributes = Attributes.EMPTY,
) : MarkerFeature<T>, PainterFeature<T> {
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(center, zoom, size)
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(center = newCoordinates)
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
@Composable
override fun getPainter(): VectorPainter = rememberVectorPainter(image)
}
/**
* An image that is bound to coordinates and is scaled (and possibly warped) together with them
*
* @param rectangle the size of background in scheme size units. The screen units to scheme units ratio equals scale.
*/
public data class ScalableImageFeature<T : Any>(
override val space: CoordinateSpace<T>,
public val rectangle: Rectangle<T>,
override val attributes: Attributes = Attributes.EMPTY,
public val painter: @Composable () -> Painter,
) : Feature<T>, PainterFeature<T> {
@Composable
override fun getPainter(): Painter = painter.invoke()
override fun getBoundingBox(zoom: Float): Rectangle<T> = rectangle
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
}
public data class TextFeature<T : Any>(
override val space: CoordinateSpace<T>,
public val position: T,
public val text: String,
override val attributes: Attributes = Attributes.EMPTY,
public val fontConfig: FeatureFont.() -> Unit,
) : DraggableFeature<T> {
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(position, position)
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(position = newCoordinates)
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
}
/**
* A pixel map representation on the map/scheme.
* [rectangle] describes the boundary of the pixel map.
* [pixelMap] contained indexed color of pixels. Minimal indices correspond to bottom-left corner of the rectangle.
* Maximal indices - top-right.
*/
public data class PixelMapFeature<T : Any>(
override val space: CoordinateSpace<T>,
val rectangle: Rectangle<T>,
val pixelMap: Structure2D<Color?>,
override val attributes: Attributes = Attributes.EMPTY,
) : Feature<T> {
init {
require(pixelMap.shape[0] > 0) { "Empty dimensions in pixel map are not allowed" }
require(pixelMap.shape[1] > 0) { "Empty dimensions in pixel map are not allowed" }
}
override fun getBoundingBox(zoom: Float): Rectangle<T> = rectangle
override fun withAttributes(modify: Attributes.() -> Attributes): Feature<T> = copy(attributes = modify(attributes))
}

View File

@ -1,5 +0,0 @@
package center.sciprog.maps.features
public expect class FeatureFont {
public var size: Float
}

View File

@ -1,305 +0,0 @@
package center.sciprog.maps.features
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import center.sciprog.attributes.*
import space.kscience.kmath.geometry.Angle
import space.kscience.kmath.nd.*
import space.kscience.kmath.structures.Buffer
//@JvmInline
//public value class FeatureId<out F : Feature<*>>(public val id: String)
public class FeatureRef<T : Any, out F : Feature<T>>(public val id: String, public val parent: FeatureGroup<T>)
@Suppress("UNCHECKED_CAST")
public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.resolve(): F =
parent.featureMap[id]?.let { it as F } ?: error("Feature with id=$id not found")
public val <T : Any, F : Feature<T>> FeatureRef<T, F>.attributes: Attributes get() = resolve().attributes
/**
* A group of other features
*/
public data class FeatureGroup<T : Any>(
override val space: CoordinateSpace<T>,
public val featureMap: SnapshotStateMap<String, Feature<T>> = mutableStateMapOf(),
override val attributes: Attributes = Attributes.EMPTY,
) : CoordinateSpace<T> by space, Feature<T> {
//
// @Suppress("UNCHECKED_CAST")
// public operator fun <F : Feature<T>> get(id: FeatureId<F>): F =
// featureMap[id.id]?.let { it as F } ?: error("Feature with id=$id not found")
private var uidCounter = 0
private fun generateUID(feature: Feature<T>?): String = if (feature == null) {
"@group[${uidCounter++}]"
} else {
"@${feature::class.simpleName}[${uidCounter++}]"
}
public fun <F : Feature<T>> feature(id: String?, feature: F): FeatureRef<T, F> {
val safeId = id ?: generateUID(feature)
featureMap[safeId] = feature
return FeatureRef(safeId, this)
}
public fun removeFeature(id: String) {
featureMap.remove(id)
}
// public fun <F : Feature<T>> feature(id: FeatureId<F>, feature: F): FeatureId<F> = feature(id.id, feature)
public val features: Collection<Feature<T>> get() = featureMap.values.sortedByDescending { it.z }
public fun visit(visitor: FeatureGroup<T>.(id: String, feature: Feature<T>) -> Unit) {
featureMap.entries.sortedByDescending { it.value.z }.forEach { (key, feature) ->
if (feature is FeatureGroup<T>) {
feature.visit(visitor)
} else {
visitor(this, key, feature)
}
}
}
public fun visitUntil(visitor: FeatureGroup<T>.(id: String, feature: Feature<T>) -> Boolean) {
featureMap.entries.sortedByDescending { it.value.z }.forEach { (key, feature) ->
if (feature is FeatureGroup<T>) {
feature.visitUntil(visitor)
} else {
if (!visitor(this, key, feature)) return@visitUntil
}
}
}
//
// @Suppress("UNCHECKED_CAST")
// public fun <A> getAttribute(id: FeatureId<Feature<T>>, key: Attribute<A>): A? =
// get(id).attributes[key]
override fun getBoundingBox(zoom: Float): Rectangle<T>? = with(space) {
featureMap.values.mapNotNull { it.getBoundingBox(zoom) }.wrapRectangles()
}
override fun withAttributes(modify: Attributes.() -> Attributes): Feature<T> = copy(attributes = modify(attributes))
public companion object {
/**
* Build, but do not remember map feature state
*/
public fun <T : Any> build(
coordinateSpace: CoordinateSpace<T>,
builder: FeatureGroup<T>.() -> Unit = {},
): FeatureGroup<T> = FeatureGroup(coordinateSpace).apply(builder)
/**
* Build and remember map feature state
*/
@Composable
public fun <T : Any> remember(
coordinateSpace: CoordinateSpace<T>,
builder: FeatureGroup<T>.() -> Unit = {},
): FeatureGroup<T> = remember {
build(coordinateSpace, builder)
}
}
}
/**
* Process all features with a given attribute from the one with highest [z] to lowest
*/
public fun <T : Any, A> FeatureGroup<T>.forEachWithAttribute(
key: Attribute<A>,
block: FeatureGroup<T>.(id: String, feature: Feature<T>, attributeValue: A) -> Unit,
) {
visit { id, feature ->
feature.attributes[key]?.let {
block(id, feature, it)
}
}
}
public fun <T : Any, A> FeatureGroup<T>.forEachWithAttributeUntil(
key: Attribute<A>,
block: FeatureGroup<T>.(id: String, feature: Feature<T>, attributeValue: A) -> Boolean,
) {
visitUntil { id, feature ->
feature.attributes[key]?.let {
block(id, feature, it)
} ?: true
}
}
public inline fun <T : Any, reified F : Feature<T>> FeatureGroup<T>.forEachWithType(
crossinline block: (FeatureRef<T, F>) -> Unit,
) {
visit { id, feature ->
if (feature is F) block(FeatureRef(id, this))
}
}
public inline fun <T : Any, reified F : Feature<T>> FeatureGroup<T>.forEachWithTypeUntil(
crossinline block: (FeatureRef<T, F>) -> Boolean,
) {
visitUntil { id, feature ->
if (feature is F) block(FeatureRef(id, this)) else true
}
}
public fun <T : Any> FeatureGroup<T>.circle(
center: T,
size: Dp = 5.dp,
attributes: Attributes = Attributes.EMPTY,
id: String? = null,
): FeatureRef<T, CircleFeature<T>> = feature(
id, CircleFeature(space, center, size, attributes)
)
public fun <T : Any> FeatureGroup<T>.rectangle(
centerCoordinates: T,
size: DpSize = DpSize(5.dp, 5.dp),
attributes: Attributes = Attributes.EMPTY,
id: String? = null,
): FeatureRef<T, RectangleFeature<T>> = feature(
id, RectangleFeature(space, centerCoordinates, size, attributes)
)
public fun <T : Any> FeatureGroup<T>.draw(
position: T,
attributes: Attributes = Attributes.EMPTY,
id: String? = null,
draw: DrawScope.() -> Unit,
): FeatureRef<T, DrawFeature<T>> = feature(
id,
DrawFeature(space, position, drawFeature = draw, attributes = attributes)
)
public fun <T : Any> FeatureGroup<T>.line(
aCoordinates: T,
bCoordinates: T,
attributes: Attributes = Attributes.EMPTY,
id: String? = null,
): FeatureRef<T, LineFeature<T>> = feature(
id,
LineFeature(space, aCoordinates, bCoordinates, attributes)
)
public fun <T : Any> FeatureGroup<T>.arc(
oval: Rectangle<T>,
startAngle: Angle,
arcLength: Angle,
attributes: Attributes = Attributes.EMPTY,
id: String? = null,
): FeatureRef<T, ArcFeature<T>> = feature(
id,
ArcFeature(space, oval, startAngle, arcLength, attributes)
)
public fun <T : Any> FeatureGroup<T>.points(
points: List<T>,
attributes: Attributes = Attributes.EMPTY,
id: String? = null,
): FeatureRef<T, PointsFeature<T>> = feature(
id,
PointsFeature(space, points, attributes)
)
public fun <T : Any> FeatureGroup<T>.multiLine(
points: List<T>,
attributes: Attributes = Attributes.EMPTY,
id: String? = null,
): FeatureRef<T, MultiLineFeature<T>> = feature(
id,
MultiLineFeature(space, points, attributes)
)
public fun <T : Any> FeatureGroup<T>.polygon(
points: List<T>,
attributes: Attributes = Attributes.EMPTY,
id: String? = null,
): FeatureRef<T, PolygonFeature<T>> = feature(
id,
PolygonFeature(space, points, attributes)
)
public fun <T : Any> FeatureGroup<T>.icon(
position: T,
image: ImageVector,
size: DpSize = DpSize(image.defaultWidth, image.defaultHeight),
attributes: Attributes = Attributes.EMPTY,
id: String? = null,
): FeatureRef<T, VectorIconFeature<T>> =
feature(
id,
VectorIconFeature(
space,
position,
size,
image,
attributes
)
)
public fun <T : Any> FeatureGroup<T>.group(
attributes: Attributes = Attributes.EMPTY,
id: String? = null,
builder: FeatureGroup<T>.() -> Unit,
): FeatureRef<T, FeatureGroup<T>> {
val collection = FeatureGroup(space).apply(builder)
val feature = FeatureGroup(space, collection.featureMap, attributes)
return feature(id, feature)
}
public fun <T : Any> FeatureGroup<T>.scalableImage(
box: Rectangle<T>,
attributes: Attributes = Attributes.EMPTY,
id: String? = null,
painter: @Composable () -> Painter,
): FeatureRef<T, ScalableImageFeature<T>> = feature(
id,
ScalableImageFeature<T>(space, box, painter = painter, attributes = attributes)
)
public fun <T : Any> FeatureGroup<T>.text(
position: T,
text: String,
font: FeatureFont.() -> Unit = { size = 16f },
attributes: Attributes = Attributes.EMPTY,
id: String? = null,
): FeatureRef<T, TextFeature<T>> = feature(
id,
TextFeature(space, position, text, fontConfig = font, attributes = attributes)
)
public fun <T> StructureND(shape: ShapeND, initializer: (IntArray) -> T): StructureND<T> {
val strides = Strides(shape)
return BufferND(strides, Buffer.boxing(strides.linearSize) { initializer(strides.index(it)) })
}
public fun <T> Structure2D(rows: Int, columns: Int, initializer: (IntArray) -> T): Structure2D<T> {
val strides = Strides(ShapeND(rows, columns))
return BufferND(strides, Buffer.boxing(strides.linearSize) { initializer(strides.index(it)) }).as2D()
}
public fun <T : Any> FeatureGroup<T>.pixelMap(
rectangle: Rectangle<T>,
pixelMap: Structure2D<Color?>,
attributes: Attributes = Attributes.EMPTY,
id: String? = null,
): FeatureRef<T, PixelMapFeature<T>> = feature(
id,
PixelMapFeature(space, rectangle, pixelMap, attributes = attributes)
)

View File

@ -1,25 +0,0 @@
package center.sciprog.maps.features
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.isPrimaryPressed
public fun interface MouseListener<in T : Any> {
public fun handle(event: PointerEvent, point: ViewPoint<T>): Unit
public companion object {
public fun <T : Any> withPrimaryButton(
block: (event: PointerEvent, click: ViewPoint<T>) -> Unit,
): MouseListener<T> = MouseListener { event, click ->
if (event.buttons.isPrimaryPressed) {
block(event, click)
}
}
}
}
//@OptIn(ExperimentalFoundationApi::class)
//public class TapListener<in T : Any>(
// public val pointerMatcher: PointerMatcher,
// public val keyboardFilter: PointerKeyboardModifiers.() -> Boolean = { true },
// public val onTap: (point: ViewPoint<T>) -> Unit,
//)

View File

@ -1,14 +0,0 @@
package center.sciprog.maps.features
import androidx.compose.ui.unit.DpSize
public data class ViewConfig<T : Any>(
val zoomSpeed: Float = 1f / 3f,
val onClick: MouseListener<T>? = null,
val dragHandle: DragHandle<T>? = null,
val onViewChange: ViewPoint<T>.() -> Unit = {},
val onSelect: (Rectangle<T>) -> Unit = {},
val onCanvasSizeChange: (DpSize) -> Unit = {},
val zoomOnSelect: Boolean = true,
val zoomOnDoubleClick: Boolean = true
)

View File

@ -1,9 +0,0 @@
package center.sciprog.maps.features
/**
* @param T type of coordinates used for the view point
*/
public interface ViewPoint<out T: Any> {
public val focus: T
public val zoom: Float
}

View File

@ -1,82 +0,0 @@
package center.sciprog.maps.features
import center.sciprog.attributes.Attributes
import kotlin.jvm.JvmName
public fun <T : Any> FeatureGroup<T>.draggableLine(
aId: FeatureRef<T, MarkerFeature<T>>,
bId: FeatureRef<T, MarkerFeature<T>>,
id: String? = null,
): FeatureRef<T, LineFeature<T>> {
var lineId: FeatureRef<T, LineFeature<T>>? = null
fun drawLine(): FeatureRef<T, LineFeature<T>> {
val currentId = feature(
lineId?.id ?: id,
LineFeature(
space,
aId.resolve().center,
bId.resolve().center,
Attributes {
ZAttribute(-10f)
lineId?.attributes?.let { from(it) }
}
)
)
lineId = currentId
return currentId
}
aId.draggable { _, _ ->
drawLine()
}
bId.draggable { _, _ ->
drawLine()
}
return drawLine()
}
public fun <T : Any> FeatureGroup<T>.draggableMultiLine(
points: List<FeatureRef<T, MarkerFeature<T>>>,
id: String? = null,
): FeatureRef<T, MultiLineFeature<T>> {
var polygonId: FeatureRef<T, MultiLineFeature<T>>? = null
fun drawLines(): FeatureRef<T, MultiLineFeature<T>> {
val currentId = feature(
polygonId?.id ?: id,
MultiLineFeature(
space,
points.map { it.resolve().center },
Attributes {
ZAttribute(-10f)
polygonId?.attributes?.let { from(it) }
}
)
)
polygonId = currentId
return currentId
}
points.forEach {
it.draggable { _, _ ->
drawLines()
}
}
return drawLines()
}
@JvmName("draggableMultiLineFromPoints")
public fun <T : Any> FeatureGroup<T>.draggableMultiLine(
points: List<T>,
id: String? = null,
): FeatureRef<T, MultiLineFeature<T>> {
val pointRefs = points.map {
circle(it)
}
return draggableMultiLine(pointRefs, id)
}

View File

@ -1,172 +0,0 @@
package center.sciprog.maps.features
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.PointerMatcher
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerKeyboardModifiers
import center.sciprog.attributes.Attribute
import center.sciprog.attributes.AttributesBuilder
import center.sciprog.attributes.SetAttribute
import center.sciprog.attributes.withAttribute
public object ZAttribute : Attribute<Float>
public object DraggableAttribute : Attribute<DragHandle<Any>>
public object DragListenerAttribute : SetAttribute<DragListener<Any>>
/**
* Click radius for point-like and line objects
*/
public object ClickRadius : Attribute<Float>
public object ClickListenerAttribute : SetAttribute<MouseListener<Any>>
public object HoverListenerAttribute : SetAttribute<MouseListener<Any>>
//public object TapListenerAttribute : SetAttribute<TapListener<Any>>
public object VisibleAttribute : Attribute<Boolean>
public object ColorAttribute : Attribute<Color>
public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.color(color: Color): FeatureRef<T, F> =
modifyAttribute(ColorAttribute, color)
public object ZoomRangeAttribute : Attribute<FloatRange>
public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.zoomRange(range: FloatRange): FeatureRef<T, F> =
modifyAttribute(ZoomRangeAttribute, range)
public object AlphaAttribute : Attribute<Float>
public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.modifyAttributes(modify: AttributesBuilder.() -> Unit): FeatureRef<T, F> {
@Suppress("UNCHECKED_CAST")
parent.feature(
id,
resolve().withAttributes {
AttributesBuilder(this).apply(modify).build()
} as F
)
return this
}
public fun <T : Any, F : Feature<T>, V> FeatureRef<T, F>.modifyAttribute(
key: Attribute<V>,
value: V?,
): FeatureRef<T, F> {
@Suppress("UNCHECKED_CAST")
parent.feature(id, resolve().withAttributes { withAttribute(key, value) } as F)
return this
}
/**
* Add drag to this feature
*
* @param constraint optional drag constraint
*/
@Suppress("UNCHECKED_CAST")
public fun <T : Any, F : DraggableFeature<T>> FeatureRef<T, F>.draggable(
constraint: ((T) -> T)? = null,
listener: (PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit)? = null,
): FeatureRef<T, F> = with(parent) {
if (attributes[DraggableAttribute] == null) {
val handle = DragHandle.withPrimaryButton<Any> { event, start, end ->
val feature = featureMap[id] as? DraggableFeature<T> ?: return@withPrimaryButton DragResult(end)
start as ViewPoint<T>
end as ViewPoint<T>
if (start in feature) {
val finalPosition = constraint?.invoke(end.focus) ?: end.focus
feature(id, feature.withCoordinates(finalPosition))
feature.attributes[DragListenerAttribute]?.forEach {
it.handle(event, start, ViewPoint(finalPosition, end.zoom))
}
DragResult(ViewPoint(finalPosition, end.zoom), false)
} else {
DragResult(end, true)
}
}
modifyAttribute(DraggableAttribute, handle)
}
//Apply callback
if (listener != null) {
onDrag(listener)
}
return this@draggable
}
@Suppress("UNCHECKED_CAST")
public fun <T : Any, F : DraggableFeature<T>> FeatureRef<T, F>.onDrag(
listener: PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit,
): FeatureRef<T, F> = modifyAttributes {
DragListenerAttribute.add(
DragListener { event, from, to -> event.listener(from as ViewPoint<T>, to as ViewPoint<T>) }
)
}
@Suppress("UNCHECKED_CAST")
public fun <T : Any, F : DomainFeature<T>> FeatureRef<T, F>.onClick(
onClick: PointerEvent.(click: ViewPoint<T>) -> Unit,
): FeatureRef<T, F> = modifyAttributes {
ClickListenerAttribute.add(
MouseListener { event, point ->
event.onClick(point as ViewPoint<T>)
}
)
}
@OptIn(ExperimentalFoundationApi::class)
@Suppress("UNCHECKED_CAST")
public fun <T : Any, F : DomainFeature<T>> FeatureRef<T, F>.onClick(
pointerMatcher: PointerMatcher,
keyboardModifiers: PointerKeyboardModifiers.() -> Boolean = { true },
onClick: PointerEvent.(click: ViewPoint<T>) -> Unit,
): FeatureRef<T, F> = modifyAttributes {
ClickListenerAttribute.add(
MouseListener { event, point ->
if (pointerMatcher.matches(event) && keyboardModifiers(event.keyboardModifiers)) {
event.onClick(point as ViewPoint<T>)
}
}
)
}
@Suppress("UNCHECKED_CAST")
public fun <T : Any, F : DomainFeature<T>> FeatureRef<T, F>.onHover(
onClick: PointerEvent.(move: ViewPoint<T>) -> Unit,
): FeatureRef<T, F> = modifyAttributes {
HoverListenerAttribute.add(
MouseListener { event, point -> event.onClick(point as ViewPoint<T>) }
)
}
// @Suppress("UNCHECKED_CAST")
// @OptIn(ExperimentalFoundationApi::class)
// public fun <F : DomainFeature<T>> FeatureId<F>.onTap(
// pointerMatcher: PointerMatcher = PointerMatcher.Primary,
// keyboardFilter: PointerKeyboardModifiers.() -> Boolean = { true },
// onTap: (point: ViewPoint<T>) -> Unit,
// ): FeatureId<F> = modifyAttributes {
// TapListenerAttribute.add(
// TapListener(pointerMatcher, keyboardFilter) { point -> onTap(point as ViewPoint<T>) }
// )
// }
public object PathEffectAttribute : Attribute<PathEffect>
public fun <T : Any> FeatureRef<T, LineSegmentFeature<T>>.pathEffect(effect: PathEffect): FeatureRef<T, LineSegmentFeature<T>> =
modifyAttribute(PathEffectAttribute, effect)
public object StrokeAttribute : Attribute<Float>
public fun <T : Any, F : LineSegmentFeature<T>> FeatureRef<T, F>.stroke(width: Float): FeatureRef<T, F> =
modifyAttribute(StrokeAttribute, width)

View File

@ -1,87 +0,0 @@
package center.sciprog.attributes
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.contextual
import kotlinx.serialization.protobuf.ProtoBuf
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
internal class AttributesSerializationTest {
@Serializable
internal class Container(@Contextual val attributes: Attributes) {
override fun equals(other: Any?): Boolean = (other as? Container)?.attributes?.equals(attributes) ?: false
override fun hashCode(): Int = attributes.hashCode()
override fun toString(): String = attributes.toString()
}
internal object ContainerAttribute : SerializableAttribute<Container>("container", serializer()) {
override fun toString(): String = "container"
}
internal object TestAttribute : SerializableAttribute<Map<String, String>>("test", serializer()) {
override fun toString(): String = "test"
}
@Test
fun restoreFromJson() {
val json = Json {
serializersModule = SerializersModule {
contextual(AttributesSerializer(setOf(NameAttribute, TestAttribute, ContainerAttribute)))
}
}
val attributes = Attributes {
NameAttribute("myTest")
TestAttribute(mapOf("a" to "aa", "b" to "bb"))
ContainerAttribute(
Container(
Attributes {
TestAttribute(mapOf("a" to "aa", "b" to "bb"))
}
)
)
}
val serialized: String = json.encodeToString(attributes)
println(serialized)
val restored: Attributes = json.decodeFromString(serialized)
assertEquals(attributes, restored)
}
@OptIn(ExperimentalSerializationApi::class)
@Test
@Ignore
fun restoreFromProtoBuf() {
val protoBuf = ProtoBuf {
serializersModule = SerializersModule {
contextual(AttributesSerializer(setOf(NameAttribute, TestAttribute, ContainerAttribute)))
}
}
val attributes = Attributes {
NameAttribute("myTest")
TestAttribute(mapOf("a" to "aa", "b" to "bb"))
ContainerAttribute(
Container(
Attributes {
TestAttribute(mapOf("a" to "aa", "b" to "bb"))
}
)
)
}
val serialized = protoBuf.encodeToByteArray(attributes)
val restored: Attributes = protoBuf.decodeFromByteArray(serialized)
assertEquals(attributes, restored)
}
}

View File

@ -1,5 +0,0 @@
package center.sciprog.maps.features
public actual class FeatureFont {
public actual var size: Float = 16f
}

View File

@ -1,306 +0,0 @@
package center.sciprog.maps.compose
import androidx.compose.foundation.gestures.GestureCancellationException
import androidx.compose.foundation.gestures.PressGestureScope
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.*
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.unit.Density
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
/*
* Clone of tap gestures for mouse
*/
private val NoPressGesture: suspend PressGestureScope.(event: PointerEvent) -> Unit = { }
internal fun PointerEvent.consume() = changes.forEach { it.consume() }
internal val PointerEvent.firstChange get() = changes.first()
public val PointerEvent.position: Offset get() = firstChange.position
/**
* Detects tap, double-tap, and long press gestures and calls [onClick], [onDoubleClick], and
* [onLongClick], respectively, when detected. [onPress] is called when the press is detected
* and the [PressGestureScope.tryAwaitRelease] and [PressGestureScope.awaitRelease] can be
* used to detect when pointers have released or the gesture was canceled.
* The first pointer down and final pointer up are consumed, and in the
* case of long press, all changes after the long press is detected are consumed.
*
* Each function parameter receives an [Offset] representing the position relative to the containing
* element. The [Offset] can be outside the actual bounds of the element itself meaning the numbers
* can be negative or larger than the element bounds if the touch target is smaller than the
* [ViewConfiguration.minimumTouchTargetSize].
*
* When [onDoubleClick] is provided, the tap gesture is detected only after
* the [ViewConfiguration.doubleTapMinTimeMillis] has passed and [onDoubleClick] is called if the
* second tap is started before [ViewConfiguration.doubleTapTimeoutMillis]. If [onDoubleClick] is not
* provided, then [onClick] is called when the pointer up has been received.
*
* After the initial [onPress], if the pointer moves out of the input area, the position change
* is consumed, or another gesture consumes the down or up events, the gestures are considered
* canceled. That means [onDoubleClick], [onLongClick], and [onClick] will not be called after a
* gesture has been canceled.
*
* If the first down event is consumed somewhere else, the entire gesture will be skipped,
* including [onPress].
*/
public suspend fun PointerInputScope.detectClicks(
onDoubleClick: (Density.(PointerEvent) -> Unit)? = null,
onLongClick: (Density.(PointerEvent) -> Unit)? = null,
onPress: suspend PressGestureScope.(event: PointerEvent) -> Unit = NoPressGesture,
onClick: (Density.(PointerEvent) -> Unit)? = null,
): Unit = coroutineScope {
// special signal to indicate to the sending side that it shouldn't intercept and consume
// cancel/up events as we're only require down events
val pressScope = PressGestureScopeImpl(this@detectClicks)
awaitEachGesture {
val down = awaitFirstDownEvent()
down.consume()
pressScope.reset()
if (onPress !== NoPressGesture) launch {
pressScope.onPress(down)
}
val longPressTimeout = onLongClick?.let {
viewConfiguration.longPressTimeoutMillis
} ?: (Long.MAX_VALUE / 2)
var upOrCancel: PointerEvent? = null
try {
// wait for first tap up or long press
upOrCancel = withTimeout(longPressTimeout) {
waitForUpOrCancellation()
}
if (upOrCancel == null) {
pressScope.cancel() // tap-up was canceled
} else {
upOrCancel.consume()
pressScope.release()
}
} catch (_: PointerEventTimeoutCancellationException) {
onLongClick?.invoke(this, down)
consumeUntilUp()
pressScope.release()
}
if (upOrCancel != null) {
// tap was successful.
if (onDoubleClick == null) {
onClick?.invoke(this, down) // no need to check for double-tap.
} else {
// check for second tap
val secondDown = awaitSecondDown(upOrCancel.firstChange)
if (secondDown == null) {
onClick?.invoke(this, down) // no valid second tap started
} else {
// Second tap down detected
pressScope.reset()
if (onPress !== NoPressGesture) {
launch { pressScope.onPress(secondDown) }
}
try {
// Might have a long second press as the second tap
withTimeout(longPressTimeout) {
val secondUp = waitForUpOrCancellation()
if (secondUp != null) {
secondUp.consume()
pressScope.release()
onDoubleClick(secondDown)
} else {
pressScope.cancel()
onClick?.invoke(this, down)
}
}
} catch (e: PointerEventTimeoutCancellationException) {
// The first tap was valid, but the second tap is a long press.
// notify for the first tap
onClick?.invoke(this, down)
// notify for the long press
onLongClick?.invoke(this, secondDown)
consumeUntilUp()
pressScope.release()
}
}
}
}
}
}
/**
* Consumes all pointer events until nothing is pressed and then returns. This method assumes
* that something is currently pressed.
*/
private suspend fun AwaitPointerEventScope.consumeUntilUp() {
do {
val event = awaitPointerEvent()
event.consume()
} while (event.changes.any { it.pressed })
}
/**
* Waits for [ViewConfiguration.doubleTapTimeoutMillis] for a second press event. If a
* second press event is received before the time-out, it is returned or `null` is returned
* if no second press is received.
*/
private suspend fun AwaitPointerEventScope.awaitSecondDown(
firstUp: PointerInputChange,
): PointerEvent? = withTimeoutOrNull(viewConfiguration.doubleTapTimeoutMillis) {
val minUptime = firstUp.uptimeMillis + viewConfiguration.doubleTapMinTimeMillis
var event: PointerEvent
// The second tap doesn't count if it happens before DoubleTapMinTime of the first tap
do {
event = awaitFirstDownEvent()
} while (event.firstChange.uptimeMillis < minUptime)
event
}
///**
// * Shortcut for cases when we only need to get press/click logic, as for cases without long press
// * and double click we don't require channelling or any other complications.
// *
// * Each function parameter receives an [Offset] representing the position relative to the containing
// * element. The [Offset] can be outside the actual bounds of the element itself meaning the numbers
// * can be negative or larger than the element bounds if the touch target is smaller than the
// * [ViewConfiguration.minimumTouchTargetSize].
// */
//internal suspend fun PointerInputScope.detectTapAndPress(
// onPress: suspend PressGestureScope.(event: PointerEvent, Offset) -> Unit = NoPressGesture,
// onTap: ((Offset) -> Unit)? = null,
//) {
// val pressScope = PressGestureScopeImpl(this)
// forEachGesture {
// coroutineScope {
// pressScope.reset()
// awaitPointerEventScope {
//
// val down = awaitFirstDownEvent()
//
// down.consume()
//
// if (onPress !== NoPressGesture) {
// launch { pressScope.onPress(down, down.position) }
// }
//
// val up = waitForUpOrCancellation()
// if (up == null) {
// pressScope.cancel() // tap-up was canceled
// } else {
// up.consume()
// pressScope.release()
// onTap?.invoke(up.position)
// }
// }
// }
// }
//}
/**
* Reads events until the first down is received. If [requireUnconsumed] is `true` and the first
* down is consumed in the [PointerEventPass.Main] pass, that gesture is ignored.
*/
internal suspend fun AwaitPointerEventScope.awaitFirstDownEvent(
requireUnconsumed: Boolean = true,
): PointerEvent = awaitFirstDownEventOnPass(pass = PointerEventPass.Main, requireUnconsumed = requireUnconsumed)
internal suspend fun AwaitPointerEventScope.awaitFirstDownEventOnPass(
pass: PointerEventPass,
requireUnconsumed: Boolean,
): PointerEvent {
var event: PointerEvent
do {
event = awaitPointerEvent(pass)
} while (!event.changes.all {
if (requireUnconsumed) it.changedToDown() else it.changedToDownIgnoreConsumed()
})
return event
}
/**
* Reads events until all pointers are up or the gesture was canceled. The gesture
* is considered canceled when a pointer leaves the event region, a position change
* has been consumed or a pointer down change event was consumed in the [PointerEventPass.Main]
* pass. If the gesture was not canceled, the final up change is returned or `null` if the
* event was canceled.
*/
internal suspend fun AwaitPointerEventScope.waitForUpOrCancellation(): PointerEvent? {
while (true) {
val event = awaitPointerEvent(PointerEventPass.Main)
if (event.changes.all { it.changedToUp() }) {
// All pointers are up
return event
}
if (event.changes.any {
it.isConsumed || it.isOutOfBounds(size, extendedTouchPadding)
}
) {
return null // Canceled
}
// Check for cancel by position consumption. We can look on the Final pass of the
// existing pointer event because it comes after the Main pass we checked above.
val consumeCheck = awaitPointerEvent(PointerEventPass.Final)
if (consumeCheck.changes.any { it.isConsumed }) {
return null
}
}
}
/**
* [detectTapGestures]'s implementation of [PressGestureScope].
*/
internal class PressGestureScopeImpl(
density: Density,
) : PressGestureScope, Density by density {
private var isReleased = false
private var isCanceled = false
private val mutex = Mutex(locked = false)
/**
* Called when a gesture has been canceled.
*/
fun cancel() {
isCanceled = true
mutex.unlock()
}
/**
* Called when all pointers are up.
*/
fun release() {
isReleased = true
mutex.unlock()
}
/**
* Called when a new gesture has started.
*/
fun reset() {
mutex.tryLock() // If tryAwaitRelease wasn't called, this will be unlocked.
isReleased = false
isCanceled = false
}
override suspend fun awaitRelease() {
if (!tryAwaitRelease()) {
throw GestureCancellationException("The press gesture was canceled.")
}
}
override suspend fun tryAwaitRelease(): Boolean {
if (!isReleased && !isCanceled) {
mutex.lock()
}
return isReleased
}
}

View File

@ -1,189 +0,0 @@
package center.sciprog.maps.compose
import androidx.compose.foundation.gestures.drag
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.*
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.DpRect
import androidx.compose.ui.unit.dp
import center.sciprog.maps.features.*
import kotlin.math.max
import kotlin.math.min
/**
* Create a modifier for Map/Scheme canvas controls on desktop
* @param features a collection of features to be rendered in descending [ZAttribute] order
*/
public fun <T : Any> Modifier.mapControls(
state: CoordinateViewScope<T>,
features: FeatureGroup<T>,
): Modifier = with(state) {
// //selecting all tapabales ahead of time
// val allTapable = buildMap {
// features.forEachWithAttribute(TapListenerAttribute) { _, feature, listeners ->
// put(feature, listeners)
// }
// }
pointerInput(Unit) {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
val coordinates = event.changes.first().position.toCoordinates(this)
val point = space.ViewPoint(coordinates, zoom)
if (event.type == PointerEventType.Move) {
features.forEachWithAttribute(HoverListenerAttribute) { _, feature, listeners ->
if (point in feature as DomainFeature) {
listeners.forEach { it.handle(event, point) }
return@forEachWithAttribute
}
}
}
}
}
}.pointerInput(Unit) {
detectClicks(
onDoubleClick = if (state.config.zoomOnDoubleClick) {
{ event ->
val invariant = event.position.toCoordinates(this)
viewPoint = with(space) {
viewPoint.zoomBy(
if (event.buttons.isPrimaryPressed) 1f else if (event.buttons.isSecondaryPressed) -1f else 0f,
invariant
)
}
}
} else null,
onClick = { event ->
val coordinates = event.position.toCoordinates(this)
val point = space.ViewPoint(coordinates, zoom)
config.onClick?.handle(
event,
point
)
features.forEachWithAttributeUntil(ClickListenerAttribute) { _, feature, listeners ->
if (point in (feature as DomainFeature)) {
listeners.forEach { it.handle(event, point) }
false
} else {
true
}
}
}
)
}.pointerInput(Unit) {
awaitPointerEventScope {
while (true) {
val event: PointerEvent = awaitPointerEvent()
event.changes.forEach { change ->
if (event.type == PointerEventType.Scroll) {
val (xPos, yPos) = change.position
//compute invariant point of translation
val invariant = DpOffset(xPos.toDp(), yPos.toDp()).toCoordinates()
viewPoint = with(space) {
viewPoint.zoomBy(-change.scrollDelta.y * config.zoomSpeed, invariant)
}
change.consume()
}
//val dragStart = change.position
//val dpPos = DpOffset(dragStart.x.toDp(), dragStart.y.toDp())
//start selection
val selectionStart: Offset? =
if (event.buttons.isPrimaryPressed && event.keyboardModifiers.isShiftPressed) {
change.position
} else {
null
}
drag(change.id) { dragChange ->
val dragAmount: Offset = dragChange.position - dragChange.previousPosition
//apply drag handle and check if it prohibits the drag even propagation
if (selectionStart == null) {
val dragStart = space.ViewPoint(
dragChange.previousPosition.toCoordinates(this),
zoom
)
val dragEnd = space.ViewPoint(
dragChange.position.toCoordinates(this),
zoom
)
val dragResult = config.dragHandle?.handle(event, dragStart, dragEnd)
if (dragResult?.handleNext == false) return@drag
var continueAfter = true
features.forEachWithAttributeUntil(DraggableAttribute) { _, _, handler ->
handler.handle(event, dragStart, dragEnd).handleNext.also {
if (!it) continueAfter = false
}
}
if (!continueAfter) return@drag
}
if (event.buttons.isPrimaryPressed) {
//If selection process is started, modify the frame
selectionStart?.let { start ->
val offset = dragChange.position
selectRect = DpRect(
min(offset.x, start.x).dp,
min(offset.y, start.y).dp,
max(offset.x, start.x).dp,
max(offset.y, start.y).dp
)
return@drag
}
// config.onClick(MapViewPoint(dpPos.toGeodetic(), viewPoint.zoom), event)
//If no selection, drag map
viewPoint = viewPoint.moveBy(
-dragAmount.x.toDp(),
dragAmount.y.toDp()
)
}
}
// evaluate selection
selectRect?.let { rect ->
//Use selection override if it is defined
val coordinateRect = space.Rectangle(
rect.topLeft.toCoordinates(),
rect.bottomRight.toCoordinates()
)
config.onSelect(coordinateRect)
if (config.zoomOnSelect) {
viewPoint = computeViewPoint(coordinateRect)
}
selectRect = null
}
}
}
}
}
}
/*
.pointerInput(Unit) {
allTapable.forEach { (feature, listeners) ->
listeners.forEach { listener ->
detectTapGestures(listener.pointerMatcher, listener.keyboardFilter) { offset ->
val point = space.ViewPoint(offset.toCoordinates(this@pointerInput), zoom)
if (point in feature as DomainFeature) {
listener.onTap(point)
}
}
}
}
}
*/

View File

@ -1,5 +0,0 @@
package center.sciprog.maps.features
import org.jetbrains.skia.Font
public actual typealias FeatureFont = Font

View File

@ -1,198 +0,0 @@
package center.sciprog.maps.features
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.drawscope.*
import androidx.compose.ui.graphics.painter.Painter
import center.sciprog.attributes.plus
import org.jetbrains.skia.Font
import org.jetbrains.skia.Paint
import space.kscience.kmath.PerformancePitfall
import space.kscience.kmath.geometry.degrees
internal fun Color.toPaint(): Paint = Paint().apply {
isAntiAlias = true
color = toArgb()
}
public fun <T : Any> DrawScope.drawFeature(
state: CoordinateViewScope<T>,
painterCache: Map<PainterFeature<T>, Painter>,
feature: Feature<T>,
): Unit = with(state) {
val color = feature.color ?: Color.Red
val alpha = feature.attributes[AlphaAttribute] ?: 1f
fun T.toOffset(): Offset = toOffset(this@drawFeature)
when (feature) {
is FeatureSelector -> drawFeature(state, painterCache, feature.selector(state.zoom))
is CircleFeature -> drawCircle(
color,
feature.radius.toPx(),
center = feature.center.toOffset()
)
is RectangleFeature -> drawRect(
color,
topLeft = feature.center.toOffset() - Offset(
feature.size.width.toPx() / 2,
feature.size.height.toPx() / 2
),
size = feature.size.toSize()
)
is LineFeature -> drawLine(
color,
feature.a.toOffset(),
feature.b.toOffset(),
strokeWidth = feature.attributes[StrokeAttribute] ?: Stroke.HairlineWidth,
pathEffect = feature.attributes[PathEffectAttribute]
)
is ArcFeature -> {
val dpRect = feature.oval.toDpRect().toRect()
val size = Size(dpRect.width, dpRect.height)
drawArc(
color = color,
startAngle = (feature.startAngle.degrees).toFloat(),
sweepAngle = (feature.arcLength.degrees).toFloat(),
useCenter = false,
topLeft = dpRect.topLeft,
size = size,
style = Stroke(),
alpha = alpha
)
}
is BitmapIconFeature -> drawImage(feature.image, feature.center.toOffset())
is VectorIconFeature -> {
val offset = feature.center.toOffset()
val size = feature.size.toSize()
translate(offset.x - size.width / 2, offset.y - size.height / 2) {
with(painterCache[feature]!!) {
draw(size)
}
}
}
is TextFeature -> drawIntoCanvas { canvas ->
val offset = feature.position.toOffset()
canvas.nativeCanvas.drawString(
feature.text,
offset.x + 5,
offset.y - 5,
Font().apply(feature.fontConfig),
(feature.color ?: Color.Black).toPaint()
)
}
is DrawFeature -> {
val offset = feature.position.toOffset()
translate(offset.x, offset.y) {
feature.drawFeature(this)
}
}
is FeatureGroup -> {
feature.featureMap.values.forEach {
drawFeature(state, painterCache, it.withAttributes {
feature.attributes + this
})
}
}
is PathFeature -> {
TODO("MapPathFeature not implemented")
// val offset = feature.rectangle.center.toOffset() - feature.targetRect.center
// translate(offset.x, offset.y) {
// sca
// drawPath(feature.path, brush = feature.brush, style = feature.style)
// }
}
is PointsFeature -> {
val points = feature.points.map { it.toOffset() }
drawPoints(
points = points,
color = color,
strokeWidth = feature.attributes[StrokeAttribute] ?: Stroke.HairlineWidth,
pointMode = PointMode.Points,
pathEffect = feature.attributes[PathEffectAttribute],
alpha = alpha
)
}
is MultiLineFeature -> {
val points = feature.points.map { it.toOffset() }
drawPoints(
points = points,
color = color,
strokeWidth = feature.attributes[StrokeAttribute] ?: Stroke.HairlineWidth,
pointMode = PointMode.Polygon,
pathEffect = feature.attributes[PathEffectAttribute],
alpha = alpha
)
}
is PolygonFeature -> {
val points = feature.points.map { it.toOffset() }
val last = points.last()
val polygonPath = Path()
polygonPath.moveTo(last.x, last.y)
for ((x, y) in points) {
polygonPath.lineTo(x, y)
}
drawPath(
path = polygonPath,
color = color,
alpha = alpha
)
}
is ScalableImageFeature -> {
val rect = feature.rectangle.toDpRect().toRect()
val offset = rect.topLeft
translate(offset.x, offset.y) {
with(painterCache[feature]!!) {
draw(rect.size)
}
}
}
is PixelMapFeature -> {
val rect = feature.rectangle.toDpRect().toRect()
val xStep = rect.size.width / feature.pixelMap.shape[0]
val yStep = rect.size.height / feature.pixelMap.shape[1]
val pixelSize = Size(xStep, yStep)
//TODO add re-clasterization for small pixel scales
val offset = rect.topLeft
translate(offset.x, offset.y) {
@OptIn(PerformancePitfall::class)
feature.pixelMap.elements().forEach { (index, color: Color?) ->
val (i, j) = index
if (color != null) {
drawRect(
color,
topLeft = Offset(
x = i * xStep,
y = rect.height - j * yStep
),
size = pixelSize
)
}
}
}
}
else -> {
//logger.error { "Unrecognized feature type: ${feature::class}" }
}
}
}

View File

@ -1,32 +0,0 @@
# Module maps-kt-geojson
## Usage
## Artifact:
The Maven coordinates of this project are `center.sciprog:maps-kt-geojson:0.2.2`.
**Gradle Groovy:**
```groovy
repositories {
maven { url 'https://repo.kotlin.link' }
mavenCentral()
}
dependencies {
implementation 'center.sciprog:maps-kt-geojson:0.2.2'
}
```
**Gradle Kotlin DSL:**
```kotlin
repositories {
maven("https://repo.kotlin.link")
mavenCentral()
}
dependencies {
implementation("center.sciprog:maps-kt-geojson:0.2.2")
}
```

View File

@ -1,224 +0,0 @@
public abstract interface class center/sciprog/maps/geojson/GeoJson {
public static final field Companion Lcenter/sciprog/maps/geojson/GeoJson$Companion;
public static final field PROPERTIES_KEY Ljava/lang/String;
public static final field TYPE_KEY Ljava/lang/String;
public abstract fun getJson ()Lkotlinx/serialization/json/JsonObject;
public fun getType ()Ljava/lang/String;
}
public final class center/sciprog/maps/geojson/GeoJson$Companion {
public static final field PROPERTIES_KEY Ljava/lang/String;
public static final field TYPE_KEY Ljava/lang/String;
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class center/sciprog/maps/geojson/GeoJsonFeature : center/sciprog/maps/geojson/GeoJson {
public static final field Companion Lcenter/sciprog/maps/geojson/GeoJsonFeature$Companion;
public static final field GEOMETRY_KEY Ljava/lang/String;
public static final synthetic fun box-impl (Lkotlinx/serialization/json/JsonObject;)Lcenter/sciprog/maps/geojson/GeoJsonFeature;
public static fun constructor-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Z
public static final fun getGeometry-impl (Lkotlinx/serialization/json/JsonObject;)Lcenter/sciprog/maps/geojson/GeoJsonGeometry;
public fun getJson ()Lkotlinx/serialization/json/JsonObject;
public static final fun getProperties-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject;
public fun hashCode ()I
public static fun hashCode-impl (Lkotlinx/serialization/json/JsonObject;)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Lkotlinx/serialization/json/JsonObject;
}
public final class center/sciprog/maps/geojson/GeoJsonFeature$Companion {
}
public final class center/sciprog/maps/geojson/GeoJsonFeatureCollection : center/sciprog/maps/geojson/GeoJson, java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker {
public static final field Companion Lcenter/sciprog/maps/geojson/GeoJsonFeatureCollection$Companion;
public static final field FEATURES_KEY Ljava/lang/String;
public static final synthetic fun box-impl (Lkotlinx/serialization/json/JsonObject;)Lcenter/sciprog/maps/geojson/GeoJsonFeatureCollection;
public static fun constructor-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Z
public static final fun getFeatures-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/util/List;
public fun getJson ()Lkotlinx/serialization/json/JsonObject;
public static final fun getProperties-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject;
public fun hashCode ()I
public static fun hashCode-impl (Lkotlinx/serialization/json/JsonObject;)I
public fun iterator ()Ljava/util/Iterator;
public static fun iterator-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/util/Iterator;
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Lkotlinx/serialization/json/JsonObject;
}
public final class center/sciprog/maps/geojson/GeoJsonFeatureCollection$Companion {
public final fun parse-oOQ2h9Q (Ljava/lang/String;)Lkotlinx/serialization/json/JsonObject;
}
public final class center/sciprog/maps/geojson/GeoJsonFeatureJvmKt {
public static final fun geoJson (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/net/URL;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun geoJson$default (Lcenter/sciprog/maps/features/FeatureGroup;Ljava/net/URL;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
}
public abstract interface class center/sciprog/maps/geojson/GeoJsonGeometry : center/sciprog/maps/geojson/GeoJson {
public static final field COORDINATES_KEY Ljava/lang/String;
public static final field Companion Lcenter/sciprog/maps/geojson/GeoJsonGeometry$Companion;
}
public final class center/sciprog/maps/geojson/GeoJsonGeometry$Companion {
public static final field COORDINATES_KEY Ljava/lang/String;
}
public final class center/sciprog/maps/geojson/GeoJsonGeometryCollection : center/sciprog/maps/geojson/GeoJsonGeometry {
public static final synthetic fun box-impl (Lkotlinx/serialization/json/JsonObject;)Lcenter/sciprog/maps/geojson/GeoJsonGeometryCollection;
public static fun constructor-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Z
public static final fun getGeometries-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/util/List;
public fun getJson ()Lkotlinx/serialization/json/JsonObject;
public fun hashCode ()I
public static fun hashCode-impl (Lkotlinx/serialization/json/JsonObject;)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Lkotlinx/serialization/json/JsonObject;
}
public final class center/sciprog/maps/geojson/GeoJsonGeometryKt {
public static final fun GeoJsonGeometry (Lkotlinx/serialization/json/JsonObject;)Lcenter/sciprog/maps/geojson/GeoJsonGeometry;
public static final fun GeoJsonGeometryCollection (Ljava/util/List;Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/json/JsonObject;
public static final fun GeoJsonLineString (Ljava/util/List;Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/json/JsonObject;
public static synthetic fun GeoJsonLineString$default (Ljava/util/List;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/serialization/json/JsonObject;
public static final fun GeoJsonMultiLineString (Ljava/util/List;Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/json/JsonObject;
public static synthetic fun GeoJsonMultiLineString$default (Ljava/util/List;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/serialization/json/JsonObject;
public static final fun GeoJsonMultiPoint (Ljava/util/List;Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/json/JsonObject;
public static synthetic fun GeoJsonMultiPoint$default (Ljava/util/List;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/serialization/json/JsonObject;
public static final fun GeoJsonMultiPolygon (Ljava/util/List;Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/json/JsonObject;
public static final fun GeoJsonPoint (Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/json/JsonObject;
public static synthetic fun GeoJsonPoint$default (Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/serialization/json/JsonObject;
public static final fun GeoJsonPolygon (Ljava/util/List;Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/json/JsonObject;
}
public final class center/sciprog/maps/geojson/GeoJsonKt {
public static final fun GeoJson (Lkotlinx/serialization/json/JsonObject;)Lcenter/sciprog/maps/geojson/GeoJson;
public static final fun GeoJsonFeature (Lcenter/sciprog/maps/geojson/GeoJsonGeometry;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/json/JsonObject;
public static synthetic fun GeoJsonFeature$default (Lcenter/sciprog/maps/geojson/GeoJsonGeometry;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/serialization/json/JsonObject;
public static final fun GeoJsonFeatureCollection (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/json/JsonObject;
public static synthetic fun GeoJsonFeatureCollection$default (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/serialization/json/JsonObject;
public static final fun getProperty-c0BrZC0 (Lkotlinx/serialization/json/JsonObject;Ljava/lang/String;)Lkotlinx/serialization/json/JsonElement;
}
public final class center/sciprog/maps/geojson/GeoJsonLineString : center/sciprog/maps/geojson/GeoJsonGeometry {
public static final synthetic fun box-impl (Lkotlinx/serialization/json/JsonObject;)Lcenter/sciprog/maps/geojson/GeoJsonLineString;
public static fun constructor-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Z
public static final fun getCoordinates-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/util/List;
public fun getJson ()Lkotlinx/serialization/json/JsonObject;
public fun hashCode ()I
public static fun hashCode-impl (Lkotlinx/serialization/json/JsonObject;)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Lkotlinx/serialization/json/JsonObject;
}
public final class center/sciprog/maps/geojson/GeoJsonMultiLineString : center/sciprog/maps/geojson/GeoJsonGeometry {
public static final synthetic fun box-impl (Lkotlinx/serialization/json/JsonObject;)Lcenter/sciprog/maps/geojson/GeoJsonMultiLineString;
public static fun constructor-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Z
public static final fun getCoordinates-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/util/List;
public fun getJson ()Lkotlinx/serialization/json/JsonObject;
public fun hashCode ()I
public static fun hashCode-impl (Lkotlinx/serialization/json/JsonObject;)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Lkotlinx/serialization/json/JsonObject;
}
public final class center/sciprog/maps/geojson/GeoJsonMultiPoint : center/sciprog/maps/geojson/GeoJsonGeometry {
public static final synthetic fun box-impl (Lkotlinx/serialization/json/JsonObject;)Lcenter/sciprog/maps/geojson/GeoJsonMultiPoint;
public static fun constructor-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Z
public static final fun getCoordinates-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/util/List;
public fun getJson ()Lkotlinx/serialization/json/JsonObject;
public fun hashCode ()I
public static fun hashCode-impl (Lkotlinx/serialization/json/JsonObject;)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Lkotlinx/serialization/json/JsonObject;
}
public final class center/sciprog/maps/geojson/GeoJsonMultiPolygon : center/sciprog/maps/geojson/GeoJsonGeometry {
public static final synthetic fun box-impl (Lkotlinx/serialization/json/JsonObject;)Lcenter/sciprog/maps/geojson/GeoJsonMultiPolygon;
public static fun constructor-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Z
public static final fun getCoordinates-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/util/List;
public fun getJson ()Lkotlinx/serialization/json/JsonObject;
public fun hashCode ()I
public static fun hashCode-impl (Lkotlinx/serialization/json/JsonObject;)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Lkotlinx/serialization/json/JsonObject;
}
public final class center/sciprog/maps/geojson/GeoJsonPoint : center/sciprog/maps/geojson/GeoJsonGeometry {
public static final synthetic fun box-impl (Lkotlinx/serialization/json/JsonObject;)Lcenter/sciprog/maps/geojson/GeoJsonPoint;
public static fun constructor-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Z
public static final fun getCoordinates-impl (Lkotlinx/serialization/json/JsonObject;)Lcenter/sciprog/maps/coordinates/GeodeticMapCoordinates;
public fun getJson ()Lkotlinx/serialization/json/JsonObject;
public fun hashCode ()I
public static fun hashCode-impl (Lkotlinx/serialization/json/JsonObject;)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Lkotlinx/serialization/json/JsonObject;
}
public final class center/sciprog/maps/geojson/GeoJsonPolygon : center/sciprog/maps/geojson/GeoJsonGeometry {
public static final synthetic fun box-impl (Lkotlinx/serialization/json/JsonObject;)Lcenter/sciprog/maps/geojson/GeoJsonPolygon;
public static fun constructor-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Z
public static final fun getCoordinates-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/util/List;
public fun getJson ()Lkotlinx/serialization/json/JsonObject;
public fun hashCode ()I
public static fun hashCode-impl (Lkotlinx/serialization/json/JsonObject;)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Lkotlinx/serialization/json/JsonObject;
}
public final class center/sciprog/maps/geojson/GeoJsonPropertiesAttribute : center/sciprog/attributes/SerializableAttribute {
public static final field INSTANCE Lcenter/sciprog/maps/geojson/GeoJsonPropertiesAttribute;
}
public final class center/sciprog/maps/geojson/GeoJsonSerializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Lcenter/sciprog/maps/geojson/GeoJsonSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcenter/sciprog/maps/geojson/GeoJson;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcenter/sciprog/maps/geojson/GeoJson;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
}
public final class center/sciprog/maps/geojson/GeoJsonToMapKt {
public static final fun geoJson (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/maps/geojson/GeoJson;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun geoJson$default (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/maps/geojson/GeoJson;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun geoJsonFeature-wsmmyRA (Lcenter/sciprog/maps/features/FeatureGroup;Lkotlinx/serialization/json/JsonObject;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun geoJsonFeature-wsmmyRA$default (Lcenter/sciprog/maps/features/FeatureGroup;Lkotlinx/serialization/json/JsonObject;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun geoJsonGeometry (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/maps/geojson/GeoJsonGeometry;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun geoJsonGeometry$default (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/maps/geojson/GeoJsonGeometry;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
}

View File

@ -1,18 +0,0 @@
plugins {
id("space.kscience.gradle.mpp")
`maven-publish`
}
kscience{
jvm()
js()
useSerialization {
json()
}
dependencies{
api(projects.mapsKtCore)
api(projects.mapsKtFeatures)
api(spclibs.kotlinx.serialization.json)
}
}

View File

@ -1,111 +0,0 @@
package center.sciprog.maps.geojson
import center.sciprog.maps.geojson.GeoJson.Companion.PROPERTIES_KEY
import center.sciprog.maps.geojson.GeoJson.Companion.TYPE_KEY
import center.sciprog.maps.geojson.GeoJsonFeatureCollection.Companion.FEATURES_KEY
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.*
import kotlin.jvm.JvmInline
/**
* A utility class to work with GeoJson (https://geojson.org/)
*/
@Serializable(GeoJsonSerializer::class)
public sealed interface GeoJson {
public val json: JsonObject
public val type: String get() = json[TYPE_KEY]?.jsonPrimitive?.content ?: error("Not a GeoJson")
public companion object {
public const val TYPE_KEY: String = "type"
public const val PROPERTIES_KEY: String = "properties"
}
}
@JvmInline
public value class GeoJsonFeature(override val json: JsonObject) : GeoJson {
init {
require(type == "Feature") { "Not a GeoJson Feature" }
}
public val properties: JsonObject? get() = json[PROPERTIES_KEY]?.jsonObject
public val geometry: GeoJsonGeometry? get() = json[GEOMETRY_KEY]?.jsonObject?.let { GeoJsonGeometry(it) }
public companion object {
public const val GEOMETRY_KEY: String = "geometry"
}
}
/**
* A builder function for [GeoJsonFeature]
*/
public fun GeoJsonFeature(
geometry: GeoJsonGeometry?,
properties: JsonObject? = null,
builder: JsonObjectBuilder.() -> Unit = {},
): GeoJsonFeature = GeoJsonFeature(
buildJsonObject {
put(TYPE_KEY, "Feature")
geometry?.json?.let { put(GeoJsonFeature.GEOMETRY_KEY, it) }
properties?.let { put(PROPERTIES_KEY, it) }
builder()
}
)
public fun GeoJsonFeature.getProperty(key: String): JsonElement? = json[key] ?: properties?.get(key)
@JvmInline
public value class GeoJsonFeatureCollection(override val json: JsonObject) : GeoJson, Iterable<GeoJsonFeature> {
init {
require(type == "FeatureCollection") { "Not a GeoJson FeatureCollection" }
}
public val properties: JsonObject? get() = json[PROPERTIES_KEY]?.jsonObject
public val features: List<GeoJsonFeature>
get() = json[FEATURES_KEY]?.jsonArray?.map {
GeoJsonFeature(it.jsonObject)
} ?: error("Features not defined in GeoJson features collection")
override fun iterator(): Iterator<GeoJsonFeature> = features.iterator()
public companion object {
public const val FEATURES_KEY: String = "features"
public fun parse(string: String): GeoJsonFeatureCollection = GeoJsonFeatureCollection(
Json.parseToJsonElement(string).jsonObject
)
}
}
/**
* A builder for [GeoJsonFeatureCollection]
*/
public fun GeoJsonFeatureCollection(
features: List<GeoJsonFeature>,
properties: JsonObject? = null,
builder: JsonObjectBuilder.() -> Unit = {},
): GeoJsonFeatureCollection = GeoJsonFeatureCollection(
buildJsonObject {
put(TYPE_KEY, "FeatureCollection")
putJsonArray(FEATURES_KEY) {
features.forEach {
add(it.json)
}
}
properties?.let { put(PROPERTIES_KEY, it) }
builder()
}
)
/**
* Generic Json to GeoJson converter
*/
public fun GeoJson(json: JsonObject): GeoJson =
when (json[TYPE_KEY]?.jsonPrimitive?.contentOrNull ?: error("Not a GeoJson")) {
"Feature" -> GeoJsonFeature(json)
"FeatureCollection" -> GeoJsonFeatureCollection(json)
else -> GeoJsonGeometry(json)
}

View File

@ -1,226 +0,0 @@
package center.sciprog.maps.geojson
import center.sciprog.maps.coordinates.Gmc
import center.sciprog.maps.coordinates.meters
import center.sciprog.maps.geojson.GeoJsonGeometry.Companion.COORDINATES_KEY
import kotlinx.serialization.json.*
import space.kscience.kmath.geometry.degrees
import kotlin.jvm.JvmInline
public sealed interface GeoJsonGeometry : GeoJson {
public companion object {
public const val COORDINATES_KEY: String = "coordinates"
}
}
public fun GeoJsonGeometry(json: JsonObject): GeoJsonGeometry {
return when (val type = json[GeoJson.TYPE_KEY]?.jsonPrimitive?.content ?: error("Not a GeoJson object")) {
"Point" -> GeoJsonPoint(json)
"MultiPoint" -> GeoJsonMultiPoint(json)
"LineString" -> GeoJsonLineString(json)
"MultiLineString" -> GeoJsonMultiLineString(json)
"Polygon" -> GeoJsonPolygon(json)
"MultiPolygon" -> GeoJsonMultiPolygon(json)
"GeometryCollection" -> GeoJsonGeometryCollection(json)
else -> error("Type '$type' is not recognised as a geometry type")
}
}
internal fun JsonElement.toGmc() = jsonArray.run {
Gmc.ofDegrees(
get(1).jsonPrimitive.double,
get(0).jsonPrimitive.double,
getOrNull(2)?.jsonPrimitive?.doubleOrNull?.meters
)
}
internal fun Gmc.toJsonArray(): JsonArray = buildJsonArray {
add(longitude.degrees)
add(latitude.degrees)
elevation?.let {
add(it.meters)
}
}
private fun List<Gmc>.listToJsonArray(): JsonArray = buildJsonArray {
forEach {
add(it.toJsonArray())
}
}
private fun List<List<Gmc>>.listOfListsToJsonArray(): JsonArray = buildJsonArray {
forEach {
add(it.listToJsonArray())
}
}
@JvmInline
public value class GeoJsonPoint(override val json: JsonObject) : GeoJsonGeometry {
init {
require(type == "Point") { "Not a GeoJson Point geometry" }
}
public val coordinates: Gmc
get() = json[COORDINATES_KEY]?.toGmc()
?: error("Coordinates are not provided")
}
public fun GeoJsonPoint(
coordinates: Gmc,
modification: JsonObjectBuilder.() -> Unit = {},
): GeoJsonPoint = GeoJsonPoint(
buildJsonObject {
put(GeoJson.TYPE_KEY, "Point")
put(COORDINATES_KEY, coordinates.toJsonArray())
modification()
}
)
@JvmInline
public value class GeoJsonMultiPoint(override val json: JsonObject) : GeoJsonGeometry {
init {
require(type == "MultiPoint") { "Not a GeoJson MultiPoint geometry" }
}
public val coordinates: List<Gmc>
get() = json[COORDINATES_KEY]?.jsonArray
?.map { it.toGmc() }
?: error("Coordinates are not provided")
}
public fun GeoJsonMultiPoint(
coordinates: List<Gmc>,
modification: JsonObjectBuilder.() -> Unit = {},
): GeoJsonMultiPoint = GeoJsonMultiPoint(
buildJsonObject {
put(GeoJson.TYPE_KEY, "MultiPoint")
put(COORDINATES_KEY, coordinates.listToJsonArray())
modification()
}
)
@JvmInline
public value class GeoJsonLineString(override val json: JsonObject) : GeoJsonGeometry {
init {
require(type == "LineString") { "Not a GeoJson LineString geometry" }
}
public val coordinates: List<Gmc>
get() = json[COORDINATES_KEY]?.jsonArray
?.map { it.toGmc() }
?: error("Coordinates are not provided")
}
public fun GeoJsonLineString(
coordinates: List<Gmc>,
modification: JsonObjectBuilder.() -> Unit = {},
): GeoJsonLineString = GeoJsonLineString(
buildJsonObject {
put(GeoJson.TYPE_KEY, "LineString")
put(COORDINATES_KEY, coordinates.listToJsonArray())
modification()
}
)
@JvmInline
public value class GeoJsonMultiLineString(override val json: JsonObject) : GeoJsonGeometry {
init {
require(type == "MultiLineString") { "Not a GeoJson MultiLineString geometry" }
}
public val coordinates: List<List<Gmc>>
get() = json[COORDINATES_KEY]?.jsonArray?.map { lineJson ->
lineJson.jsonArray.map {
it.toGmc()
}
} ?: error("Coordinates are not provided")
}
public fun GeoJsonMultiLineString(
coordinates: List<List<Gmc>>,
modification: JsonObjectBuilder.() -> Unit = {},
): GeoJsonMultiLineString = GeoJsonMultiLineString(
buildJsonObject {
put(GeoJson.TYPE_KEY, "MultiLineString")
put(COORDINATES_KEY, coordinates.listOfListsToJsonArray())
modification()
}
)
@JvmInline
public value class GeoJsonPolygon(override val json: JsonObject) : GeoJsonGeometry {
init {
require(type == "Polygon") { "Not a GeoJson Polygon geometry" }
}
public val coordinates: List<List<Gmc>>
get() = json[COORDINATES_KEY]?.jsonArray?.map { polygon ->
polygon.jsonArray.map { point ->
point.toGmc()
}
} ?: error("Coordinates are not provided")
}
public fun GeoJsonPolygon(
coordinates: List<List<Gmc>>,
modification: JsonObjectBuilder.() -> Unit
): GeoJsonPolygon = GeoJsonPolygon(
buildJsonObject {
put(GeoJson.TYPE_KEY, "Polygon")
put(COORDINATES_KEY, coordinates.listOfListsToJsonArray())
modification()
}
)
@JvmInline
public value class GeoJsonMultiPolygon(override val json: JsonObject) : GeoJsonGeometry {
init {
require(type == "MultiPolygon") { "Not a GeoJson MultiPolygon geometry" }
}
public val coordinates: List<List<List<Gmc>>>
get() = json[COORDINATES_KEY]?.jsonArray?.map { allPolygons ->
allPolygons.jsonArray.map { polygon ->
polygon.jsonArray.map { point ->
point.toGmc()
}
}
} ?: error("Coordinates are not provided")
}
public fun GeoJsonMultiPolygon(
coordinates: List<List<List<Gmc>>>,
modification: JsonObjectBuilder.() -> Unit
): GeoJsonMultiPolygon = GeoJsonMultiPolygon(
buildJsonObject {
put(GeoJson.TYPE_KEY, "MultiPolygon")
put(COORDINATES_KEY, buildJsonArray { coordinates.forEach { add(it.listOfListsToJsonArray()) } })
modification()
}
)
@JvmInline
public value class GeoJsonGeometryCollection(override val json: JsonObject) : GeoJsonGeometry {
init {
require(type == "GeometryCollection") { "Not a GeoJson GeometryCollection geometry" }
}
public val geometries: List<GeoJsonGeometry>
get() = json["geometries"]?.jsonArray?.map { GeoJsonGeometry(it.jsonObject) } ?: emptyList()
}
public fun GeoJsonGeometryCollection(
geometries: List<GeoJsonGeometry>,
modification: JsonObjectBuilder.() -> Unit
): GeoJsonGeometryCollection =
GeoJsonGeometryCollection(
buildJsonObject {
put(GeoJson.TYPE_KEY, "GeometryCollection")
put("geometries", buildJsonArray {
geometries.forEach {
add(it.json)
}
})
modification()
}
)

View File

@ -1,7 +0,0 @@
package center.sciprog.maps.geojson
import center.sciprog.attributes.SerializableAttribute
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.serializer
public object GeoJsonPropertiesAttribute : SerializableAttribute<JsonObject>("properties", serializer())

View File

@ -1,21 +0,0 @@
package center.sciprog.maps.geojson
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonObject
public object GeoJsonSerializer : KSerializer<GeoJson> {
private val serializer = JsonObject.serializer()
override val descriptor: SerialDescriptor
get() = serializer.descriptor
override fun deserialize(decoder: Decoder): GeoJson = GeoJson(serializer.deserialize(decoder))
override fun serialize(encoder: Encoder, value: GeoJson) {
serializer.serialize(encoder, value.json)
}
}

View File

@ -1,87 +0,0 @@
package center.sciprog.maps.geojson
import androidx.compose.ui.graphics.Color
import center.sciprog.attributes.NameAttribute
import center.sciprog.maps.coordinates.Gmc
import center.sciprog.maps.features.*
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.jsonPrimitive
/**
* Add a single Json geometry to a feature builder
*/
public fun FeatureGroup<Gmc>.geoJsonGeometry(
geometry: GeoJsonGeometry,
id: String? = null,
): FeatureRef<Gmc, Feature<Gmc>> = when (geometry) {
is GeoJsonLineString -> multiLine(
geometry.coordinates,
)
is GeoJsonMultiLineString -> group(id = id) {
geometry.coordinates.forEach {
multiLine(it)
}
}
is GeoJsonMultiPoint -> points(
geometry.coordinates,
)
is GeoJsonMultiPolygon -> group(id = id) {
geometry.coordinates.forEach {
polygon(
it.first(),
)
}
}
is GeoJsonPoint -> circle(geometry.coordinates, id = id)
is GeoJsonPolygon -> polygon(
geometry.coordinates.first(),
)
is GeoJsonGeometryCollection -> group(id = id) {
geometry.geometries.forEach {
geoJsonGeometry(it)
}
}
}
public fun FeatureGroup<Gmc>.geoJsonFeature(
geoJson: GeoJsonFeature,
id: String? = null,
): FeatureRef<Gmc, Feature<Gmc>> {
val geometry = geoJson.geometry ?: return group {}
val idOverride = id ?: geoJson.getProperty("id")?.jsonPrimitive?.contentOrNull
return geoJsonGeometry(geometry, idOverride).modifyAttributes {
geoJson.properties?.let {
GeoJsonPropertiesAttribute(it)
}
geoJson.getProperty("name")?.jsonPrimitive?.contentOrNull?.let {
NameAttribute(it)
}
geoJson.getProperty("color")?.jsonPrimitive?.intOrNull?.let {
ColorAttribute(Color(it))
}
}
}
public fun FeatureGroup<Gmc>.geoJson(
geoJson: GeoJson,
id: String? = null,
): FeatureRef<Gmc, Feature<Gmc>> = when (geoJson) {
is GeoJsonFeature -> geoJsonFeature(geoJson, id = id)
is GeoJsonFeatureCollection -> group(id = id) {
geoJson.features.forEach {
geoJsonFeature(it)
}
}
is GeoJsonGeometry -> geoJsonGeometry(geoJson, id = id)
}

View File

@ -1,23 +0,0 @@
package center.sciprog.maps.geojson
import center.sciprog.maps.coordinates.Gmc
import center.sciprog.maps.features.Feature
import center.sciprog.maps.features.FeatureGroup
import center.sciprog.maps.features.FeatureRef
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import java.net.URL
/**
* Add geojson features from url
*/
public fun FeatureGroup<Gmc>.geoJson(
geoJsonUrl: URL,
id: String? = null,
): FeatureRef<Gmc, Feature<Gmc>> {
val jsonString = geoJsonUrl.readText()
val json = Json.parseToJsonElement(jsonString).jsonObject
val geoJson = GeoJson(json)
return geoJson(geoJson, id)
}

View File

@ -1,17 +0,0 @@
plugins {
id("space.kscience.gradle.jvm")
`maven-publish`
}
repositories {
maven("https://repo.osgeo.org/repository/release/")
}
dependencies {
api("org.geotools:gt-geotiff:27.2") {
exclude(group = "javax.media", module = "jai_core")
}
api(projects.mapsKtCore)
api(projects.mapsKtFeatures)
}

View File

@ -1,26 +0,0 @@
package center.sciprog.maps.geotiff
import center.sciprog.maps.coordinates.Gmc
import center.sciprog.maps.features.Feature
import center.sciprog.maps.features.FeatureGroup
import center.sciprog.maps.features.FeatureRef
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import org.geotools.gce.geotiff.GeoTiffReader
import org.geotools.util.factory.Hints
import java.io.File
import java.net.URL
public fun FeatureGroup<Gmc>.geoJson(
geoTiffUrl: URL,
id: String? = null,
): FeatureRef<Gmc, Feature<Gmc>> {
val reader = GeoTiffReader
val jsonString = geoJsonUrl.readText()
val json = Json.parseToJsonElement(jsonString).jsonObject
val geoJson = GeoJson(json)
return geoJson(geoJson, id)
}

Some files were not shown because too many files have changed in this diff Show More