Compare commits
122 Commits
Author | SHA1 | Date | |
---|---|---|---|
a059697f5c | |||
1ccd35f152 | |||
|
7981422f94 | ||
d8548ab162 | |||
5d0e0f054e | |||
|
869aee887a | ||
11b11118fa | |||
05ef3aa4dd | |||
8947998e0c | |||
74b86dbc59 | |||
35efce087e | |||
fcf0600d0c | |||
f0da3efd27 | |||
614ca8d6f3 | |||
8bc1987acf | |||
b06fc5c87a | |||
|
dde5b2c9f7 | ||
5e35ce8b5f | |||
a6a5baa352 | |||
4e7ef35280 | |||
cac5841401 | |||
cdaa17a3b9 | |||
3fc52dd60f | |||
375de71ca6 | |||
df675c8d45 | |||
e553e33d4c | |||
dbfe61b949 | |||
3ab875d87c | |||
dcee205b7c | |||
4658418517 | |||
3b43446f82 | |||
2ed9d72029 | |||
1bcae1f362 | |||
94a91e9ff9 | |||
1ca1b3cd43 | |||
a89a5cf36a | |||
1a7646f311 | |||
8f489ea0f9 | |||
7623e5f622 | |||
dab9784089 | |||
90eb7b4575 | |||
3219e13fa7 | |||
ce25690dfb | |||
e7784c2960 | |||
e80c9406c9 | |||
2343f37655 | |||
50ccfeab70 | |||
a23b9954cd | |||
82a1260e3f | |||
9a7e086591 | |||
b6a3ce0fe7 | |||
0f00abb1b2 | |||
190834634f | |||
6278235b51 | |||
5a3a4b059e | |||
f288a17243 | |||
ad938a614a | |||
ffc77dc611 | |||
69e7b058d2 | |||
70fe9cf97a | |||
cb160110e7 | |||
ea8c5571bb | |||
ba962acf5c | |||
a8a3da7e70 | |||
26c3e589da | |||
7643968d39 | |||
d34c5099f6 | |||
15ad690129 | |||
9e3eec9533 | |||
56ccd66db5 | |||
33cadb1d15 | |||
8969b2b094 | |||
5da23b83c1 | |||
7e7cb0a260 | |||
572adf041f | |||
42dbbeea58 | |||
5ed46b278c | |||
8a94438dfd | |||
5b95adc649 | |||
fb13fa1431 | |||
9b8ba884e1 | |||
c2157c8351 | |||
7735d667bc | |||
0d9efadcb8 | |||
8451cc3aa1 | |||
edeb422335 | |||
5448929d31 | |||
5561a4188c | |||
1ebcb02f0a | |||
e7cb006a3c | |||
5ab7862660 | |||
737bbbde6a | |||
77e27c7eb6 | |||
28663a010d | |||
c679680cf0 | |||
6a4531fe1d | |||
c8c6aef24e | |||
3e31effa80 | |||
e106ceef9a | |||
c82f47a786 | |||
b68fa02fa4 | |||
9c5c4b5747 | |||
354cd8c9a2 | |||
409d749054 | |||
6decba8e83 | |||
2b01b8e316 | |||
8c79c913e6 | |||
693f608b14 | |||
4c71b8e7bb | |||
fa51fc03db | |||
6b0a2566bd | |||
4cabfcf16f | |||
1808a9b7d3 | |||
890a1bceca | |||
8e143eb81c | |||
8429577fd8 | |||
25e9eed88d | |||
42e0a4c46d | |||
cda8d8e76f | |||
7939677e5a | |||
48e425bbae | |||
74e878e7e5 |
@ -1,14 +1,14 @@
|
|||||||
import kotlin.io.path.readText
|
import kotlin.io.path.readText
|
||||||
|
|
||||||
job("Build") {
|
job("Build") {
|
||||||
gradlew("openjdk:11", "build")
|
gradlew("spc.registry.jetbrains.space/p/sci/containers/kotlin-ci:1.0.3", "build")
|
||||||
}
|
}
|
||||||
|
|
||||||
job("Publish") {
|
job("Publish") {
|
||||||
startOn {
|
startOn {
|
||||||
gitPush { enabled = false }
|
gitPush { enabled = false }
|
||||||
}
|
}
|
||||||
container("openjdk:11") {
|
container("spc.registry.jetbrains.space/p/sci/containers/kotlin-ci:1.0.3") {
|
||||||
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 ->
|
||||||
|
15
CHANGELOG.md
Normal file
15
CHANGELOG.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
### Security
|
76
README.md
76
README.md
@ -1,13 +1,73 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
## [maps-kt-core](maps-kt-core)
|
![](docs/images/Screenshot%202023-01-12%20110429.png)
|
||||||
A multiplatform coordinates representation and conversion.
|
|
||||||
|
|
||||||
## [maps-kt-compose](maps-kt-compose)
|
## Modules
|
||||||
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
|
||||||
|
@ -1,90 +1,51 @@
|
|||||||
|
import space.kscience.gradle.isInDevelopment
|
||||||
|
import space.kscience.gradle.useApache2Licence
|
||||||
|
import space.kscience.gradle.useSPCTeam
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
base
|
id("space.kscience.gradle.project")
|
||||||
}
|
}
|
||||||
|
|
||||||
val ktorVersion by extra("2.0.3")
|
val kmathVersion: String by extra("0.3.1-dev-RC")
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "center.sciprog"
|
group = "center.sciprog"
|
||||||
version = "0.1.0-SNAPSHOT"
|
version = "0.2.2"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
maven("https://repo.kotlin.link")
|
||||||
|
maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.create("version") {
|
ksciencePublish {
|
||||||
group = "publishing"
|
pom("https://github.com/SciProgCentre/maps-kt") {
|
||||||
val versionFile = project.buildDir.resolve("project-version.txt")
|
useApache2Licence()
|
||||||
outputs.file(versionFile)
|
useSPCTeam()
|
||||||
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")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
4
demo/README.md
Normal file
4
demo/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Module demo
|
||||||
|
|
||||||
|
|
||||||
|
|
4
demo/maps/README.md
Normal file
4
demo/maps/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Module maps
|
||||||
|
|
||||||
|
|
||||||
|
|
18
demo/maps/api/maps.api
Normal file
18
demo/maps/api/maps.api
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,3 @@
|
|||||||
import org.jetbrains.compose.compose
|
|
||||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
@ -9,18 +8,15 @@ plugins {
|
|||||||
val ktorVersion: String by rootProject.extra
|
val ktorVersion: String by rootProject.extra
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm {
|
jvmToolchain(11)
|
||||||
compilations.all {
|
jvm()
|
||||||
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:$ktorVersion")
|
implementation("io.ktor:ktor-client-cio")
|
||||||
implementation("ch.qos.logback:logback-classic:1.2.11")
|
implementation("ch.qos.logback:logback-classic:1.2.11")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,7 +24,8 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compose.desktop {
|
compose {
|
||||||
|
desktop {
|
||||||
application {
|
application {
|
||||||
mainClass = "MainKt"
|
mainClass = "MainKt"
|
||||||
nativeDistributions {
|
nativeDistributions {
|
||||||
@ -37,4 +34,5 @@ compose.desktop {
|
|||||||
packageVersion = "1.0.0"
|
packageVersion = "1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,46 +1,51 @@
|
|||||||
// 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.*
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.PointMode
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
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.Distance
|
import center.sciprog.maps.coordinates.*
|
||||||
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
|
import center.sciprog.maps.features.*
|
||||||
import center.sciprog.maps.coordinates.MapViewPoint
|
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
|
||||||
|
|
||||||
private fun GeodeticMapCoordinates.toShortString(): String =
|
public fun GeodeticMapCoordinates.toShortString(): String =
|
||||||
"${(latitude.degrees.value).toString().take(6)}:${(longitude.degrees.value).toString().take(6)}"
|
"${(latitude.degrees).toString().take(6)}:${(longitude.degrees).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),
|
||||||
@ -48,29 +53,44 @@ fun App() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var centerCoordinates by remember { mutableStateOf<GeodeticMapCoordinates?>(null) }
|
val centerCoordinates = MutableStateFlow<Gmc?>(null)
|
||||||
|
|
||||||
|
|
||||||
val pointOne = 55.568548 to 37.568604
|
val pointOne = 55.568548 to 37.568604
|
||||||
val pointTwo = 55.929444 to 37.518434
|
val pointTwo = 55.929444 to 37.518434
|
||||||
val pointThree = 60.929444 to 37.518434
|
// val pointThree = 60.929444 to 37.518434
|
||||||
|
|
||||||
val dragPoint = 55.744 to 37.614
|
|
||||||
|
|
||||||
MapView(
|
MapView(
|
||||||
mapTileProvider = mapTileProvider,
|
mapTileProvider = mapTileProvider,
|
||||||
initialViewPoint = viewPoint,
|
config = ViewConfig(
|
||||||
config = MapViewConfig(
|
onViewChange = { centerCoordinates.value = focus },
|
||||||
inferViewBoxFromFeatures = true,
|
onClick = { _, viewPoint ->
|
||||||
onViewChange = { centerCoordinates = focus },
|
println(viewPoint)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
image(pointOne, Icons.Filled.Home)
|
geoJson(javaClass.getResource("/moscow.geo.json")!!)
|
||||||
|
.modifyAttribute(ColorAttribute, Color.Blue)
|
||||||
|
.modifyAttribute(AlphaAttribute, 0.4f)
|
||||||
|
|
||||||
rectangle(dragPoint, id = "dragMe", size = DpSize(10.dp, 10.dp)).draggable()
|
icon(pointOne, Icons.Filled.Home)
|
||||||
|
|
||||||
points(
|
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,
|
||||||
@ -79,48 +99,73 @@ 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: FeatureId = circle(
|
val circleId = circle(
|
||||||
centerCoordinates = pointTwo,
|
centerCoordinates = pointTwo,
|
||||||
)
|
)
|
||||||
|
scope.launch {
|
||||||
draw(position = pointThree) {
|
while (isActive) {
|
||||||
drawLine(start = Offset(-10f, -10f), end = Offset(10f, 10f), color = Color.Red)
|
delay(200)
|
||||||
drawLine(start = Offset(-10f, 10f), end = Offset(10f, -10f), color = Color.Red)
|
circleId.color(Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 })
|
||||||
|
|
||||||
centerCoordinates?.let {
|
pixelMap(
|
||||||
group(id = "center") {
|
space.Rectangle(
|
||||||
circle(center = it, color = Color.Blue, size = 1f)
|
Gmc(latitude = 55.58461879539754.degrees, longitude = 37.8746197303493.degrees),
|
||||||
text(position = it, it.toShortString(), color = Color.Blue)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.launch {
|
centerCoordinates.filterNotNull().onEach {
|
||||||
while (isActive) {
|
group(id = "center") {
|
||||||
delay(200)
|
circle(center = it, id = "circle", size = 1.dp).color(Color.Blue)
|
||||||
//Overwrite a feature with new color
|
text(position = it, it.toShortString(), id = "text").color(Color.Blue)
|
||||||
circle(
|
}
|
||||||
pointTwo,
|
}.launchIn(scope)
|
||||||
id = circleId,
|
|
||||||
color = Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat())
|
//Add click listeners for all polygons
|
||||||
)
|
forEachWithType<Gmc, PolygonFeature<Gmc>> { ref ->
|
||||||
|
ref.onClick(PointerMatcher.Primary) {
|
||||||
|
println("Click on ${ref.id}")
|
||||||
|
//draw in top-level scope
|
||||||
|
with(this@MapView) {
|
||||||
|
multiLine(
|
||||||
|
ref.resolve().points,
|
||||||
|
attributes = Attributes(ZAttribute, 10f),
|
||||||
|
id = "selected",
|
||||||
|
).modifyAttribute(StrokeAttribute, 4f).color(Color.Magenta)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
Window(onCloseRequest = ::exitApplication) {
|
Window(onCloseRequest = ::exitApplication, title = "Maps-kt demo", icon = painterResource("SPC-logo.png")) {
|
||||||
App()
|
App()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
demo/maps/src/jvmMain/resources/SPC-logo.png
Normal file
BIN
demo/maps/src/jvmMain/resources/SPC-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
76302
demo/maps/src/jvmMain/resources/moscow.geo.json
Normal file
76302
demo/maps/src/jvmMain/resources/moscow.geo.json
Normal file
File diff suppressed because it is too large
Load Diff
4
demo/polygon-editor/README.md
Normal file
4
demo/polygon-editor/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Module polygon-editor
|
||||||
|
|
||||||
|
|
||||||
|
|
19
demo/polygon-editor/api/polygon-editor.api
Normal file
19
demo/polygon-editor/api/polygon-editor.api
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
36
demo/polygon-editor/build.gradle.kts
Normal file
36
demo/polygon-editor/build.gradle.kts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
demo/polygon-editor/src/jvmMain/kotlin/Main.kt
Normal file
80
demo/polygon-editor/src/jvmMain/kotlin/Main.kt
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// 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()
|
||||||
|
}
|
||||||
|
}
|
4
demo/scheme/README.md
Normal file
4
demo/scheme/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Module scheme
|
||||||
|
|
||||||
|
|
||||||
|
|
17
demo/scheme/api/scheme.api
Normal file
17
demo/scheme/api/scheme.api
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,3 @@
|
|||||||
import org.jetbrains.compose.compose
|
|
||||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
@ -9,12 +8,8 @@ plugins {
|
|||||||
val ktorVersion: String by rootProject.extra
|
val ktorVersion: String by rootProject.extra
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm {
|
jvm()
|
||||||
compilations.all {
|
jvmToolchain(11)
|
||||||
kotlinOptions.jvmTarget = "11"
|
|
||||||
}
|
|
||||||
withJava()
|
|
||||||
}
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
val jvmMain by getting {
|
val jvmMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -27,7 +22,8 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compose.desktop {
|
compose{
|
||||||
|
desktop {
|
||||||
application {
|
application {
|
||||||
mainClass = "MainKt"
|
mainClass = "MainKt"
|
||||||
nativeDistributions {
|
nativeDistributions {
|
||||||
@ -36,4 +32,5 @@ compose.desktop {
|
|||||||
packageVersion = "1.0.0"
|
packageVersion = "1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,67 +1,104 @@
|
|||||||
// 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.Composable
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
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.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.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
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 {
|
||||||
//create a view point
|
|
||||||
val viewPoint = remember {
|
|
||||||
SchemeViewPoint(
|
|
||||||
SchemeCoordinates(0f, 0f),
|
|
||||||
1f
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val schemeFeaturesState: FeatureGroup<XY> = FeatureGroup.remember(XYCoordinateSpace) {
|
||||||
|
background(1600f, 1200f) { 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)
|
||||||
|
arc(center = 1132.0881 to 394.99127, radius = 20f, startAngle = Angle.zero, Angle.piTimes2)
|
||||||
|
|
||||||
SchemeView(
|
//circle(410.52737 to 868.7676, id = "hobbit")
|
||||||
viewPoint,
|
|
||||||
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)
|
|
||||||
|
|
||||||
val hobbitId = circle(410.52737 to 868.7676)
|
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
var t = 0.0
|
var t = 0.0
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
val x = 410.52737 + t * (1132.0881 - 410.52737)
|
val x = 410.52737 + t * (1132.0881 - 410.52737)
|
||||||
val y = 868.7676 + t * (394.99127 - 868.7676)
|
val y = 868.7676 + t * (394.99127 - 868.7676)
|
||||||
circle(x to y, color = Color.Green, id = hobbitId)
|
circle(x to y, id = "hobbit").color(Color.Green)
|
||||||
delay(100)
|
delay(100)
|
||||||
t += 0.005
|
t += 0.005
|
||||||
if (t >= 1.0) t = 0.0
|
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(
|
||||||
|
mapState,
|
||||||
|
schemeFeaturesState,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
Window(onCloseRequest = ::exitApplication) {
|
Window(title = "Scheme demo", onCloseRequest = ::exitApplication) {
|
||||||
App()
|
App()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 469 KiB After Width: | Height: | Size: 469 KiB |
4
demo/trajectory-playground/README.md
Normal file
4
demo/trajectory-playground/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Module trajectory-playground
|
||||||
|
|
||||||
|
|
||||||
|
|
25
demo/trajectory-playground/api/trajectory-playground.api
Normal file
25
demo/trajectory-playground/api/trajectory-playground.api
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
30
demo/trajectory-playground/build.gradle.kts
Normal file
30
demo/trajectory-playground/build.gradle.kts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
195
demo/trajectory-playground/src/jvmMain/kotlin/Main.kt
Normal file
195
demo/trajectory-playground/src/jvmMain/kotlin/Main.kt
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
docs/images/Screenshot 2023-01-12 110429.png
Normal file
BIN
docs/images/Screenshot 2023-01-12 110429.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 591 KiB |
17
docs/templates/ARTIFACT-TEMPLATE.md
vendored
Normal file
17
docs/templates/ARTIFACT-TEMPLATE.md
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
## 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}")
|
||||||
|
}
|
||||||
|
```
|
9
docs/templates/README-TEMPLATE.md
vendored
Normal file
9
docs/templates/README-TEMPLATE.md
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# 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}
|
@ -1,5 +1,10 @@
|
|||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
kotlin.version=1.6.10
|
|
||||||
compose.version=1.1.1
|
compose.version=1.4.0
|
||||||
agp.version=4.2.2
|
agp.version=7.4.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
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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-7.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
2213
gradle/yarn.lock
Normal file
2213
gradle/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
33
maps-kt-compose/README.md
Normal file
33
maps-kt-compose/README.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# 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")
|
||||||
|
}
|
||||||
|
```
|
@ -1,30 +1,43 @@
|
|||||||
import org.jetbrains.compose.compose
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("multiplatform")
|
id("space.kscience.gradle.mpp")
|
||||||
id("org.jetbrains.compose")
|
id("org.jetbrains.compose")
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
}
|
}
|
||||||
|
|
||||||
val ktorVersion: String by rootProject.extra
|
kscience{
|
||||||
|
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("io.ktor:ktor-client-core:$ktorVersion")
|
api(project.dependencies.platform(spclibs.ktor.bom))
|
||||||
|
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 jvmMain by getting
|
val jvmTest by getting {
|
||||||
val jvmTest by getting
|
dependencies {
|
||||||
|
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." }
|
||||||
|
}
|
7
maps-kt-compose/docs/README-TEMPLATE.md
Normal file
7
maps-kt-compose/docs/README-TEMPLATE.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Module kmath-core
|
||||||
|
|
||||||
|
The core interfaces of KMath.
|
||||||
|
|
||||||
|
${features}
|
||||||
|
|
||||||
|
${artifact}
|
@ -0,0 +1,74 @@
|
|||||||
|
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 {
|
||||||
|
//
|
||||||
|
//}
|
@ -1,174 +0,0 @@
|
|||||||
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.*
|
|
||||||
import kotlin.math.floor
|
|
||||||
|
|
||||||
public interface MapFeature {
|
|
||||||
public val zoomRange: IntRange
|
|
||||||
public fun getBoundingBox(zoom: Double): GmcRectangle?
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface DraggableMapFeature : MapFeature {
|
|
||||||
public fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun Iterable<MapFeature>.computeBoundingBox(zoom: Double): GmcRectangle? =
|
|
||||||
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: Double): GmcRectangle? = selector(floor(zoom).toInt()).getBoundingBox(zoom)
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MapDrawFeature(
|
|
||||||
public val position: GeodeticMapCoordinates,
|
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
|
||||||
public val drawFeature: DrawScope.() -> Unit,
|
|
||||||
) : DraggableMapFeature {
|
|
||||||
override fun getBoundingBox(zoom: Double): GmcRectangle {
|
|
||||||
//TODO add box computation
|
|
||||||
return GmcRectangle(position, position)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
|
||||||
MapDrawFeature(newCoordinates, zoomRange, drawFeature)
|
|
||||||
}
|
|
||||||
|
|
||||||
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: Double): GmcRectangle {
|
|
||||||
return GmcRectangle(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,
|
|
||||||
) : DraggableMapFeature {
|
|
||||||
override fun getBoundingBox(zoom: Double): GmcRectangle {
|
|
||||||
val scale = WebMercatorProjection.scaleFactor(zoom)
|
|
||||||
return GmcRectangle.square(center, (size/scale).radians, (size/scale).radians)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
|
||||||
MapCircleFeature(newCoordinates, zoomRange, size, color)
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
) : DraggableMapFeature {
|
|
||||||
override fun getBoundingBox(zoom: Double): GmcRectangle {
|
|
||||||
val scale = WebMercatorProjection.scaleFactor(zoom)
|
|
||||||
return GmcRectangle.square(center, (size.height.value/scale).radians, (size.width.value/scale).radians)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
|
||||||
MapRectangleFeature(newCoordinates, zoomRange, size, color)
|
|
||||||
}
|
|
||||||
|
|
||||||
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: Double): GmcRectangle = GmcRectangle(a, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MapArcFeature(
|
|
||||||
public val oval: GmcRectangle,
|
|
||||||
public val startAngle: Float,
|
|
||||||
public val endAngle: Float,
|
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
|
||||||
public val color: Color = Color.Red,
|
|
||||||
) : MapFeature {
|
|
||||||
override fun getBoundingBox(zoom: Double): GmcRectangle = oval
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MapBitmapImageFeature(
|
|
||||||
public val position: GeodeticMapCoordinates,
|
|
||||||
public val image: ImageBitmap,
|
|
||||||
public val size: IntSize = IntSize(15, 15),
|
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
|
||||||
) : DraggableMapFeature {
|
|
||||||
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position)
|
|
||||||
|
|
||||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
|
||||||
MapBitmapImageFeature(newCoordinates, image, size, zoomRange)
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MapVectorImageFeature(
|
|
||||||
public val position: GeodeticMapCoordinates,
|
|
||||||
public val painter: Painter,
|
|
||||||
public val size: DpSize,
|
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
|
||||||
) : DraggableMapFeature{
|
|
||||||
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position)
|
|
||||||
|
|
||||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
|
||||||
MapVectorImageFeature(newCoordinates,painter, size, zoomRange)
|
|
||||||
}
|
|
||||||
|
|
||||||
@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: Double): GmcRectangle? =
|
|
||||||
children.values.mapNotNull { it.getBoundingBox(zoom) }.wrapAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MapTextFeature(
|
|
||||||
public val position: GeodeticMapCoordinates,
|
|
||||||
public val text: String,
|
|
||||||
override val zoomRange: IntRange = defaultZoomRange,
|
|
||||||
public val color: Color,
|
|
||||||
public val fontConfig: MapTextFeatureFont.() -> Unit,
|
|
||||||
) : DraggableMapFeature{
|
|
||||||
override fun getBoundingBox(zoom: Double): GmcRectangle = GmcRectangle(position, position)
|
|
||||||
|
|
||||||
override fun withCoordinates(newCoordinates: GeodeticMapCoordinates): MapFeature =
|
|
||||||
MapTextFeature(newCoordinates, text, zoomRange, color, fontConfig)
|
|
||||||
}
|
|
@ -1,190 +0,0 @@
|
|||||||
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.GmcRectangle
|
|
||||||
|
|
||||||
public typealias FeatureId = String
|
|
||||||
|
|
||||||
public interface MapFeatureAttributeKey<T>
|
|
||||||
|
|
||||||
|
|
||||||
public class MapFeatureAttributeSet(private val map: Map<MapFeatureAttributeKey<*>, *>) {
|
|
||||||
public operator fun <T> get(key: MapFeatureAttributeKey<*>): T? = map[key]?.let {
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
it as T
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface MapFeatureBuilder {
|
|
||||||
public fun addFeature(id: FeatureId?, feature: MapFeature): FeatureId
|
|
||||||
|
|
||||||
public fun <T> setAttribute(id: FeatureId, key: MapFeatureAttributeKey<T>, value: T)
|
|
||||||
|
|
||||||
public val features: MutableMap<FeatureId, MapFeature>
|
|
||||||
|
|
||||||
public fun attributes(): Map<FeatureId, MapFeatureAttributeSet>
|
|
||||||
|
|
||||||
//TODO use context receiver for that
|
|
||||||
public fun FeatureId.draggable(enabled: Boolean = true) {
|
|
||||||
setAttribute(this, DraggableAttribute, enabled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class MapFeatureBuilderImpl(
|
|
||||||
override val features: SnapshotStateMap<FeatureId, MapFeature>,
|
|
||||||
) : MapFeatureBuilder {
|
|
||||||
|
|
||||||
private val attributes = SnapshotStateMap<FeatureId, SnapshotStateMap<MapFeatureAttributeKey<out Any?>, in Any?>>()
|
|
||||||
|
|
||||||
|
|
||||||
private fun generateID(feature: MapFeature): FeatureId = "@feature[${feature.hashCode().toUInt()}]"
|
|
||||||
|
|
||||||
override fun addFeature(id: FeatureId?, feature: MapFeature): FeatureId {
|
|
||||||
val safeId = id ?: generateID(feature)
|
|
||||||
features[id ?: generateID(feature)] = feature
|
|
||||||
return safeId
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T> setAttribute(id: FeatureId, key: MapFeatureAttributeKey<T>, value: T) {
|
|
||||||
attributes.getOrPut(id) { SnapshotStateMap() }[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun attributes(): Map<FeatureId, MapFeatureAttributeSet> =
|
|
||||||
attributes.mapValues { MapFeatureAttributeSet(it.value) }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
drawFeature: DrawScope.() -> Unit,
|
|
||||||
): FeatureId = addFeature(id, MapDrawFeature(position.toCoordinates(), zoomRange, 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: GmcRectangle,
|
|
||||||
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(
|
|
||||||
GmcRectangle.square(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(mutableStateMapOf()).apply(builder).features
|
|
||||||
val feature = MapFeatureGroup(map, zoomRange)
|
|
||||||
return addFeature(id, feature)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun MapFeatureBuilder.text(
|
|
||||||
position: GeodeticMapCoordinates,
|
|
||||||
text: String,
|
|
||||||
zoomRange: IntRange = defaultZoomRange,
|
|
||||||
color: Color = Color.Red,
|
|
||||||
font: MapTextFeatureFont.() -> 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: MapTextFeatureFont.() -> Unit = { size = 16f },
|
|
||||||
id: FeatureId? = null,
|
|
||||||
): FeatureId = addFeature(id, MapTextFeature(position.toCoordinates(), text, zoomRange, color, font))
|
|
@ -1,5 +0,0 @@
|
|||||||
package center.sciprog.maps.compose
|
|
||||||
|
|
||||||
public expect class MapTextFeatureFont {
|
|
||||||
public var size: Float
|
|
||||||
}
|
|
@ -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: ImageBitmap,
|
val image: Image,
|
||||||
)
|
)
|
||||||
|
|
||||||
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: Double): Int = floor(d / tileSize).toInt()
|
public fun toIndex(d: Float): Int = floor(d / tileSize).toInt()
|
||||||
|
|
||||||
public fun toCoordinate(i: Int): Double = (i * tileSize).toDouble()
|
public fun toCoordinate(i: Int): Float = (i * tileSize).toFloat()
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public const val DEFAULT_TILE_SIZE: Int = 256
|
public const val DEFAULT_TILE_SIZE: Int = 256
|
||||||
|
@ -1,155 +1,70 @@
|
|||||||
package center.sciprog.maps.compose
|
package center.sciprog.maps.compose
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.pointer.PointerEvent
|
import center.sciprog.maps.coordinates.Gmc
|
||||||
import androidx.compose.ui.input.pointer.isPrimaryPressed
|
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
|
|
||||||
|
|
||||||
|
|
||||||
public fun interface DragHandle {
|
|
||||||
/**
|
|
||||||
* @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: MapViewPoint, end: MapViewPoint): Boolean
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
public val BYPASS: DragHandle = DragHandle { _, _, _ -> true }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process only events with primary button pressed
|
|
||||||
*/
|
|
||||||
public fun withPrimaryButton(
|
|
||||||
block: (event: PointerEvent, start: MapViewPoint, end: MapViewPoint) -> Boolean,
|
|
||||||
): DragHandle = DragHandle { event, start, end ->
|
|
||||||
if (event.buttons.isPrimaryPressed) {
|
|
||||||
block(event, start, end)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Combine several handles into one
|
|
||||||
*/
|
|
||||||
public fun combine(vararg handles: DragHandle): DragHandle = DragHandle { event, start, end ->
|
|
||||||
handles.forEach {
|
|
||||||
if (!it.handle(event, start, end)) return@DragHandle false
|
|
||||||
}
|
|
||||||
return@DragHandle true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO consider replacing by modifier
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
public data class MapViewConfig(
|
|
||||||
val zoomSpeed: Double = 1.0 / 3.0,
|
|
||||||
val inferViewBoxFromFeatures: Boolean = false,
|
|
||||||
val onClick: MapViewPoint.(PointerEvent) -> Unit = {},
|
|
||||||
val dragHandle: DragHandle = DragHandle.BYPASS,
|
|
||||||
val onViewChange: MapViewPoint.() -> Unit = {},
|
|
||||||
val onSelect: (GmcRectangle) -> Unit = {},
|
|
||||||
val zoomOnSelect: Boolean = true,
|
|
||||||
val resetViewPoint: Boolean = false,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
public expect fun MapView(
|
public expect fun MapView(
|
||||||
mapTileProvider: MapTileProvider,
|
viewScope: MapViewScope,
|
||||||
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
|
features: FeatureGroup<Gmc>,
|
||||||
features: Map<FeatureId, MapFeature>,
|
|
||||||
config: MapViewConfig = MapViewConfig(),
|
|
||||||
modifier: Modifier = Modifier.fillMaxSize(),
|
modifier: Modifier = Modifier.fillMaxSize(),
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun prepareConfig(initialConfig: MapViewConfig, featureBuilder: MapFeatureBuilder): MapViewConfig {
|
/**
|
||||||
val draggableFeatureIds: Set<FeatureId> = featureBuilder.attributes().filterValues {
|
* A builder for a Map with static features.
|
||||||
it[DraggableAttribute] ?: false
|
*/
|
||||||
}.keys
|
|
||||||
|
|
||||||
val features = featureBuilder.features
|
|
||||||
|
|
||||||
val featureDrag = DragHandle.withPrimaryButton { _, start, end ->
|
|
||||||
val zoom = start.zoom
|
|
||||||
draggableFeatureIds.forEach { id ->
|
|
||||||
val feature = features[id] as? DraggableMapFeature ?: return@forEach
|
|
||||||
//val border = WebMercatorProjection.scaleFactor(zoom)
|
|
||||||
val boundingBox = feature.getBoundingBox(zoom) ?: return@forEach
|
|
||||||
if (start.focus in boundingBox) {
|
|
||||||
features[id] = feature.withCoordinates(end.focus)
|
|
||||||
return@withPrimaryButton false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return@withPrimaryButton true
|
|
||||||
}
|
|
||||||
return initialConfig.copy(
|
|
||||||
dragHandle = DragHandle.combine(featureDrag, initialConfig.dragHandle),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
public fun MapView(
|
public fun MapView(
|
||||||
mapTileProvider: MapTileProvider,
|
mapTileProvider: MapTileProvider,
|
||||||
initialViewPoint: MapViewPoint,
|
features: FeatureGroup<Gmc>,
|
||||||
config: MapViewConfig = MapViewConfig(),
|
initialViewPoint: ViewPoint<Gmc>? = null,
|
||||||
|
initialRectangle: Rectangle<Gmc>? = null,
|
||||||
|
config: ViewConfig<Gmc> = ViewConfig(),
|
||||||
modifier: Modifier = Modifier.fillMaxSize(),
|
modifier: Modifier = Modifier.fillMaxSize(),
|
||||||
buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
|
|
||||||
) {
|
) {
|
||||||
val featuresBuilder = MapFeatureBuilderImpl(mutableStateMapOf())
|
|
||||||
featuresBuilder.buildFeatures()
|
|
||||||
val features = remember { featuresBuilder.features }
|
|
||||||
|
|
||||||
val newConfig = remember(features) {
|
val mapState: MapViewScope = MapViewScope.remember(
|
||||||
prepareConfig(config, featuresBuilder)
|
|
||||||
}
|
|
||||||
|
|
||||||
MapView(
|
|
||||||
mapTileProvider,
|
mapTileProvider,
|
||||||
{ initialViewPoint },
|
config,
|
||||||
features,
|
initialViewPoint = initialViewPoint,
|
||||||
newConfig,
|
initialRectangle = initialRectangle ?: features.getBoundingBox(Float.MAX_VALUE),
|
||||||
modifier
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
MapView(mapState, features, modifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun GmcRectangle.computeViewPoint(
|
/**
|
||||||
|
* 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,
|
mapTileProvider: MapTileProvider,
|
||||||
): (canvasSize: DpSize) -> MapViewPoint = { canvasSize ->
|
initialViewPoint: ViewPoint<Gmc>? = null,
|
||||||
val zoom = log2(
|
initialRectangle: Rectangle<Gmc>? = null,
|
||||||
min(
|
config: ViewConfig<Gmc> = ViewConfig(),
|
||||||
canvasSize.width.value / longitudeDelta.radians.value,
|
modifier: Modifier = Modifier.fillMaxSize(),
|
||||||
canvasSize.height.value / latitudeDelta.radians.value
|
buildFeatures: FeatureGroup<Gmc>.() -> Unit = {},
|
||||||
) * PI / mapTileProvider.tileSize
|
) {
|
||||||
|
|
||||||
|
val featureState = FeatureGroup.remember(WebMercatorSpace, buildFeatures)
|
||||||
|
|
||||||
|
val mapState: MapViewScope = MapViewScope.remember(
|
||||||
|
mapTileProvider,
|
||||||
|
config,
|
||||||
|
initialViewPoint = initialViewPoint,
|
||||||
|
initialRectangle = initialRectangle ?: featureState.features.computeBoundingBox(
|
||||||
|
WebMercatorSpace,
|
||||||
|
Float.MAX_VALUE
|
||||||
|
),
|
||||||
)
|
)
|
||||||
MapViewPoint(center, zoom)
|
|
||||||
|
MapView(mapState, featureState, modifier)
|
||||||
}
|
}
|
||||||
//
|
|
||||||
//@Composable
|
|
||||||
//public fun MapView(
|
|
||||||
// mapTileProvider: MapTileProvider,
|
|
||||||
// box: GmcRectangle,
|
|
||||||
// config: MapViewConfig = MapViewConfig(),
|
|
||||||
// modifier: Modifier = Modifier.fillMaxSize(),
|
|
||||||
// buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
|
|
||||||
//) {
|
|
||||||
// val builder by derivedStateOf { MapFeatureBuilderImpl().apply(buildFeatures) }
|
|
||||||
//
|
|
||||||
// MapView(
|
|
||||||
// mapTileProvider,
|
|
||||||
// box.computeViewPoint(mapTileProvider),
|
|
||||||
// builder.features,
|
|
||||||
// prepareConfig(config, builder),
|
|
||||||
// modifier
|
|
||||||
// )
|
|
||||||
//}
|
|
@ -0,0 +1,20 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,100 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
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)
|
||||||
|
}
|
@ -1,3 +0,0 @@
|
|||||||
package center.sciprog.maps.compose
|
|
||||||
|
|
||||||
public object DraggableAttribute: MapFeatureAttributeKey<Boolean>
|
|
@ -0,0 +1,143 @@
|
|||||||
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
@ -1,5 +0,0 @@
|
|||||||
package center.sciprog.maps.compose
|
|
||||||
|
|
||||||
import org.jetbrains.skia.Font
|
|
||||||
|
|
||||||
public actual typealias MapTextFeatureFont = Font
|
|
@ -1,371 +0,0 @@
|
|||||||
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.runtime.snapshots.SnapshotStateMap
|
|
||||||
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 org.jetbrains.skia.Font
|
|
||||||
import org.jetbrains.skia.Paint
|
|
||||||
import kotlin.math.*
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
internal fun MapViewPoint.move(deltaX: Double, deltaY: Double): MapViewPoint {
|
|
||||||
val newCoordinates = GeodeticMapCoordinates(
|
|
||||||
(focus.latitude + (deltaY / scaleFactor).radians).coerceIn(
|
|
||||||
-MercatorProjection.MAXIMUM_LATITUDE,
|
|
||||||
MercatorProjection.MAXIMUM_LATITUDE
|
|
||||||
),
|
|
||||||
focus.longitude + (deltaX / scaleFactor).radians
|
|
||||||
)
|
|
||||||
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(
|
|
||||||
mapTileProvider: MapTileProvider,
|
|
||||||
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
|
|
||||||
features: Map<FeatureId, MapFeature>,
|
|
||||||
config: MapViewConfig,
|
|
||||||
modifier: Modifier,
|
|
||||||
) {
|
|
||||||
var canvasSize by remember { mutableStateOf(DpSize(512.dp, 512.dp)) }
|
|
||||||
|
|
||||||
var viewPointInternal: MapViewPoint? by remember {
|
|
||||||
mutableStateOf(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.resetViewPoint) {
|
|
||||||
viewPointInternal = null
|
|
||||||
}
|
|
||||||
|
|
||||||
val viewPoint: MapViewPoint by derivedStateOf {
|
|
||||||
viewPointInternal ?: if (config.inferViewBoxFromFeatures) {
|
|
||||||
features.values.computeBoundingBox(1.0)?.let { box ->
|
|
||||||
val zoom = log2(
|
|
||||||
min(
|
|
||||||
canvasSize.width.value / box.longitudeDelta.radians.value,
|
|
||||||
canvasSize.height.value / box.latitudeDelta.radians.value
|
|
||||||
) * PI / mapTileProvider.tileSize
|
|
||||||
)
|
|
||||||
MapViewPoint(box.center, zoom)
|
|
||||||
} ?: computeViewPoint(canvasSize)
|
|
||||||
} else {
|
|
||||||
computeViewPoint(canvasSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val zoom: Int by derivedStateOf {
|
|
||||||
require(viewPoint.zoom in 1.0..18.0) { "Zoom value of ${viewPoint.zoom} is not valid" }
|
|
||||||
floor(viewPoint.zoom).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
val tileScale: Double by derivedStateOf {
|
|
||||||
2.0.pow(viewPoint.zoom - zoom)
|
|
||||||
}
|
|
||||||
|
|
||||||
val mapTiles = remember { mutableStateListOf<MapTile>() }
|
|
||||||
|
|
||||||
val centerCoordinates by derivedStateOf { WebMercatorProjection.toMercator(viewPoint.focus, zoom) }
|
|
||||||
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
fun DpOffset.toGeodetic() = WebMercatorProjection.toGeodetic(toMercator())
|
|
||||||
|
|
||||||
// Selection rectangle. If null - no selection
|
|
||||||
var selectRect by remember { mutableStateOf<Rect?>(null) }
|
|
||||||
|
|
||||||
@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 ->
|
|
||||||
val dragStart = change.position
|
|
||||||
val dpPos = DpOffset(dragStart.x.toDp(), dragStart.y.toDp())
|
|
||||||
|
|
||||||
//start selection
|
|
||||||
if (event.buttons.isPrimaryPressed && event.keyboardModifiers.isShiftPressed) {
|
|
||||||
selectRect = Rect(change.position, change.position)
|
|
||||||
}
|
|
||||||
|
|
||||||
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())
|
|
||||||
|
|
||||||
//apply drag handle and check if it prohibits the drag even propagation
|
|
||||||
if (
|
|
||||||
!config.dragHandle.handle(
|
|
||||||
event,
|
|
||||||
MapViewPoint(dpStart.toGeodetic(), viewPoint.zoom),
|
|
||||||
MapViewPoint(dpEnd.toGeodetic(), viewPoint.zoom)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
//clear selection just in case
|
|
||||||
selectRect = null
|
|
||||||
return@drag
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.buttons.isPrimaryPressed) {
|
|
||||||
//Evaluating selection frame
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
return@drag
|
|
||||||
}
|
|
||||||
|
|
||||||
config.onClick(MapViewPoint(dpPos.toGeodetic(), viewPoint.zoom), event)
|
|
||||||
|
|
||||||
val newViewPoint = viewPoint.move(
|
|
||||||
-dragAmount.x.toDp().value / tileScale,
|
|
||||||
+dragAmount.y.toDp().value / tileScale
|
|
||||||
)
|
|
||||||
config.onViewChange(newViewPoint)
|
|
||||||
viewPointInternal = newViewPoint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// evaluate selection
|
|
||||||
selectRect?.let { rect ->
|
|
||||||
//Use selection override if it is defined
|
|
||||||
val gmcBox = GmcRectangle(
|
|
||||||
rect.topLeft.toDpOffset().toGeodetic(),
|
|
||||||
rect.bottomRight.toDpOffset().toGeodetic()
|
|
||||||
)
|
|
||||||
config.onSelect(gmcBox)
|
|
||||||
if (config.zoomOnSelect) {
|
|
||||||
val newViewPoint = gmcBox.computeViewPoint(mapTileProvider).invoke(canvasSize)
|
|
||||||
|
|
||||||
config.onViewChange(newViewPoint)
|
|
||||||
viewPointInternal = newViewPoint
|
|
||||||
}
|
|
||||||
selectRect = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.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() * config.zoomSpeed, invariant)
|
|
||||||
config.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)
|
|
||||||
try {
|
|
||||||
//start all
|
|
||||||
val deferred = loadTileAsync(id)
|
|
||||||
//wait asynchronously for it to finish
|
|
||||||
launch {
|
|
||||||
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) {
|
|
||||||
fun WebMercatorCoordinates.toOffset(): Offset = 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
|
|
||||||
fun GeodeticMapCoordinates.toOffset(): Offset = WebMercatorProjection.toMercator(this, zoom).toOffset()
|
|
||||||
|
|
||||||
|
|
||||||
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()
|
|
||||||
)
|
|
||||||
|
|
||||||
is MapRectangleFeature -> drawRect(
|
|
||||||
feature.color,
|
|
||||||
topLeft = feature.center.toOffset() - Offset(
|
|
||||||
feature.size.width.toPx() / 2,
|
|
||||||
feature.size.height.toPx() / 2
|
|
||||||
),
|
|
||||||
size = feature.size.toSize()
|
|
||||||
)
|
|
||||||
|
|
||||||
is MapLineFeature -> drawLine(feature.color, feature.a.toOffset(), feature.b.toOffset())
|
|
||||||
is MapArcFeature -> {
|
|
||||||
val topLeft = feature.oval.topLeft.toOffset()
|
|
||||||
val bottomRight = feature.oval.bottomRight.toOffset()
|
|
||||||
|
|
||||||
val path = Path().apply {
|
|
||||||
addArcRad(Rect(topLeft, bottomRight), feature.startAngle, feature.endAngle - feature.startAngle)
|
|
||||||
}
|
|
||||||
|
|
||||||
drawPath(path, color = feature.color, style = Stroke())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
is MapBitmapImageFeature -> drawImage(feature.image, feature.position.toOffset())
|
|
||||||
is MapVectorImageFeature -> {
|
|
||||||
val offset = feature.position.toOffset()
|
|
||||||
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()
|
|
||||||
canvas.nativeCanvas.drawString(
|
|
||||||
feature.text,
|
|
||||||
offset.x + 5,
|
|
||||||
offset.y - 5,
|
|
||||||
Font().apply(feature.fontConfig),
|
|
||||||
feature.color.toPaint()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is MapDrawFeature -> {
|
|
||||||
val offset = feature.position.toOffset()
|
|
||||||
translate(offset.x, offset.y) {
|
|
||||||
feature.drawFeature(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is MapFeatureGroup -> {
|
|
||||||
feature.children.values.forEach {
|
|
||||||
drawFeature(zoom, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is MapPointsFeature -> {
|
|
||||||
val points = feature.points.map { it.toOffset() }
|
|
||||||
drawPoints(
|
|
||||||
points = points,
|
|
||||||
color = feature.color,
|
|
||||||
strokeWidth = feature.stroke,
|
|
||||||
pointMode = feature.pointMode
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
logger.error { "Unrecognized feature type: ${feature::class}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canvasSize != size.toDpSize()) {
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,8 @@
|
|||||||
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
|
||||||
@ -26,23 +23,24 @@ public class OpenStreetMapTileProvider(
|
|||||||
private val cacheDirectory: Path,
|
private val cacheDirectory: Path,
|
||||||
parallelism: Int = 4,
|
parallelism: Int = 4,
|
||||||
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<ImageBitmap>>(cacheCapacity)
|
private val cache = LruCache<TileId, Deferred<Image>>(cacheCapacity)
|
||||||
|
|
||||||
private fun TileId.osmUrl() = URL("https://tile.openstreetmap.org/${zoom}/${i}/${j}.png")
|
private fun TileId.osmUrl() = URL("$osmBaseUrl/${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<ImageBitmap> = async(Dispatchers.IO) {
|
private fun CoroutineScope.downloadImageAsync(id: TileId): Deferred<Image> = 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()).toComposeImageBitmap()
|
return@async Image.makeFromEncoded(path.readBytes())
|
||||||
} 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()
|
||||||
@ -62,7 +60,7 @@ public class OpenStreetMapTileProvider(
|
|||||||
path.writeBytes(byteArray)
|
path.writeBytes(byteArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
Image.makeFromEncoded(byteArray).toComposeImageBitmap()
|
Image.makeFromEncoded(byteArray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,21 +69,17 @@ public class OpenStreetMapTileProvider(
|
|||||||
): Deferred<MapTile> {
|
): Deferred<MapTile> {
|
||||||
|
|
||||||
//start image download
|
//start image download
|
||||||
val imageDeferred = cache.getOrPut(tileId) {
|
val imageDeferred: Deferred<Image> = cache.getOrPut(tileId) {
|
||||||
downloadImageAsync(tileId)
|
downloadImageAsync(tileId)
|
||||||
}
|
}
|
||||||
|
|
||||||
//collect the result asynchronously
|
//collect the result asynchronously
|
||||||
return async {
|
return async {
|
||||||
val image: ImageBitmap = try {
|
val image: Image = runCatching { imageDeferred.await() }.onFailure {
|
||||||
imageDeferred.await()
|
logger.error(it) { "Failed to load tile image with id=$tileId" }
|
||||||
} catch (ex: Exception) {
|
|
||||||
cache.remove(tileId)
|
cache.remove(tileId)
|
||||||
if (ex !is CancellationException) {
|
}.getOrThrow()
|
||||||
logger.error(ex) { "Failed to load tile image with id=$tileId" }
|
|
||||||
}
|
|
||||||
throw ex
|
|
||||||
}
|
|
||||||
MapTile(tileId, image)
|
MapTile(tileId, image)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,146 @@
|
|||||||
|
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)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
35
maps-kt-core/README.md
Normal file
35
maps-kt-core/README.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# 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")
|
||||||
|
}
|
||||||
|
```
|
250
maps-kt-core/api/maps-kt-core.api
Normal file
250
maps-kt-core/api/maps-kt-core.api
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
@ -1,26 +1,34 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("multiplatform")
|
id("space.kscience.gradle.mpp")
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
}
|
}
|
||||||
|
|
||||||
val ktorVersion: String by rootProject.extra
|
val kmathVersion: String by rootProject.extra
|
||||||
|
|
||||||
kotlin {
|
kscience{
|
||||||
explicitApi = org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode.Warning
|
jvm()
|
||||||
jvm {
|
js()
|
||||||
compilations.all {
|
useSerialization()
|
||||||
kotlinOptions.jvmTarget = "11"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
js(IR) {
|
|
||||||
browser()
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets{
|
|
||||||
commonTest{
|
|
||||||
dependencies{
|
dependencies{
|
||||||
implementation(kotlin("test"))
|
api(projects.trajectoryKt)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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" }
|
||||||
|
}
|
7
maps-kt-core/docs/README-TEMPLATE.md
Normal file
7
maps-kt-core/docs/README-TEMPLATE.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Module kmath-core
|
||||||
|
|
||||||
|
The core interfaces of KMath.
|
||||||
|
|
||||||
|
${features}
|
||||||
|
|
||||||
|
${artifact}
|
@ -1,12 +1,17 @@
|
|||||||
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(public val kilometers: Double) : Comparable<Distance> {
|
public value class Distance internal constructor(public val kilometers: Double) : Comparable<Distance> {
|
||||||
override fun compareTo(other: Distance): Int = this.kilometers.compareTo(other.kilometers)
|
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
|
||||||
|
|
||||||
public operator fun Distance.plus(other: Distance): Distance = Distance(kilometers + other.kilometers)
|
public operator fun Distance.plus(other: Distance): Distance = Distance(kilometers + other.kilometers)
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package center.sciprog.maps.coordinates
|
package center.sciprog.maps.coordinates
|
||||||
|
|
||||||
import kotlin.math.acos
|
import kotlinx.serialization.Serializable
|
||||||
|
import space.kscience.kmath.geometry.Angle
|
||||||
|
import space.kscience.kmath.geometry.tan
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
@Serializable
|
||||||
public class GeoEllipsoid(public val equatorRadius: Distance, public val polarRadius: Distance) {
|
public class GeoEllipsoid(public val equatorRadius: Distance, public val polarRadius: Distance) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,8 +19,13 @@ public class GeoEllipsoid(public val equatorRadius: Distance, public val polarRa
|
|||||||
*/
|
*/
|
||||||
public val inverseF: Double = equatorRadius.kilometers / (equatorRadius.kilometers - polarRadius.kilometers)
|
public val inverseF: Double = equatorRadius.kilometers / (equatorRadius.kilometers - polarRadius.kilometers)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eccentricity squared
|
||||||
|
*/
|
||||||
public val eSquared: Double = 2 * f - f * f
|
public val eSquared: Double = 2 * f - f * f
|
||||||
|
|
||||||
|
public val eccentricity: Double = sqrt(eSquared)
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
|
|
||||||
public val WGS84: GeoEllipsoid = GeoEllipsoid(
|
public val WGS84: GeoEllipsoid = GeoEllipsoid(
|
||||||
@ -48,7 +55,7 @@ public class GeoEllipsoid(public val equatorRadius: Distance, public val polarRa
|
|||||||
/**
|
/**
|
||||||
* A radius of circle normal to the axis of the ellipsoid at given latitude
|
* A radius of circle normal to the axis of the ellipsoid at given latitude
|
||||||
*/
|
*/
|
||||||
internal fun GeoEllipsoid.reducedRadius(latitude: Angle): Distance {
|
public fun GeoEllipsoid.reducedRadius(latitude: Angle): Distance {
|
||||||
val reducedLatitudeTan = (1 - f) * tan(latitude)
|
val reducedLatitudeTan = (1 - f) * tan(latitude)
|
||||||
return equatorRadius / sqrt(1.0 + reducedLatitudeTan.pow(2))
|
return equatorRadius / sqrt(1.0 + reducedLatitudeTan.pow(2))
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,31 @@
|
|||||||
package center.sciprog.maps.coordinates
|
package center.sciprog.maps.coordinates
|
||||||
|
|
||||||
import kotlin.math.PI
|
import kotlinx.serialization.Serializable
|
||||||
|
import space.kscience.kmath.geometry.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Geodetic coordinated
|
* Geodetic coordinated
|
||||||
|
*
|
||||||
|
* @param elevation is optional
|
||||||
*/
|
*/
|
||||||
|
@Serializable
|
||||||
public class GeodeticMapCoordinates(
|
public class GeodeticMapCoordinates(
|
||||||
public val latitude: Angle,
|
public val latitude: Angle,
|
||||||
longitude: Angle,
|
public val longitude: Angle,
|
||||||
) {
|
public val elevation: Distance? = null,
|
||||||
public val longitude: Radians = longitude.radians.value.rem(PI / 2).radians
|
) : Vector2D<Angle> {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(latitude.radians.value in (-PI / 2)..(PI / 2)) { "Latitude $latitude is not in (-PI/2)..(PI/2)" }
|
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
|
||||||
@ -21,10 +33,7 @@ public class GeodeticMapCoordinates(
|
|||||||
|
|
||||||
other as GeodeticMapCoordinates
|
other as GeodeticMapCoordinates
|
||||||
|
|
||||||
if (latitude != other.latitude) return false
|
return latitude == other.latitude && longitude == other.longitude
|
||||||
if (longitude != other.longitude) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
@ -34,19 +43,34 @@ public class GeodeticMapCoordinates(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "GMC(latitude=${latitude.degrees.value} deg, longitude=${longitude.degrees.value} deg)"
|
return "GMC(latitude=${latitude.degrees} deg, longitude=${longitude.degrees} deg)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public fun ofRadians(latitude: Double, longitude: Double): GeodeticMapCoordinates =
|
public fun normalized(
|
||||||
GeodeticMapCoordinates(latitude.radians, longitude.radians)
|
latitude: Angle,
|
||||||
|
longitude: Angle,
|
||||||
|
elevation: Distance? = null,
|
||||||
|
): GeodeticMapCoordinates = GeodeticMapCoordinates(
|
||||||
|
latitude.coerceIn(-Angle.piDiv2..Angle.piDiv2), longitude.normalized(Angle.zero), elevation
|
||||||
|
)
|
||||||
|
|
||||||
public fun ofDegrees(latitude: Double, longitude: Double): GeodeticMapCoordinates =
|
public fun ofRadians(
|
||||||
GeodeticMapCoordinates(latitude.degrees.radians, longitude.degrees.radians)
|
latitude: Double,
|
||||||
|
longitude: Double,
|
||||||
|
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
|
* Short name for GeodeticMapCoordinates
|
||||||
*/
|
*/
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package center.sciprog.maps.coordinates
|
package center.sciprog.maps.coordinates
|
||||||
|
|
||||||
import center.sciprog.maps.coordinates.Angle.Companion.pi
|
import space.kscience.kmath.geometry.*
|
||||||
import center.sciprog.maps.coordinates.Angle.Companion.piDiv2
|
|
||||||
import center.sciprog.maps.coordinates.Angle.Companion.zero
|
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -10,13 +8,22 @@ import kotlin.math.*
|
|||||||
* @param forward coordinate of a start point with forward direction
|
* @param forward coordinate of a start point with forward direction
|
||||||
* @param backward coordinate of an end point with backward direction
|
* @param backward coordinate of an end point with backward direction
|
||||||
*/
|
*/
|
||||||
public class GmcCurve internal constructor(
|
public class GmcCurve(
|
||||||
public val forward: GmcPose,
|
public val forward: GmcPose,
|
||||||
public val backward: GmcPose,
|
public val backward: GmcPose,
|
||||||
public val distance: Distance,
|
public val distance: Distance,
|
||||||
)
|
){
|
||||||
|
override fun toString(): String {
|
||||||
|
return "GmcCurve(from: ${forward.coordinates}, to: ${backward.coordinates})"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public fun GmcCurve.reversed(): GmcCurve = GmcCurve(backward, forward, distance)
|
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
|
* Compute a curve alongside a meridian
|
||||||
@ -27,8 +34,8 @@ public fun GeoEllipsoid.meridianCurve(
|
|||||||
toLatitude: Angle,
|
toLatitude: Angle,
|
||||||
step: Radians = 0.015.radians,
|
step: Radians = 0.015.radians,
|
||||||
): GmcCurve {
|
): GmcCurve {
|
||||||
require(fromLatitude in (-piDiv2)..(piDiv2)) { "Latitude must be in (-90, 90) degrees range" }
|
require(fromLatitude in (-Angle.piDiv2)..(Angle.piDiv2)) { "Latitude must be in (-90, 90) degrees range" }
|
||||||
require(toLatitude in (-piDiv2)..(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 *
|
fun smallDistance(from: Radians, to: Radians): Distance = equatorRadius *
|
||||||
(1 - eSquared) *
|
(1 - eSquared) *
|
||||||
@ -41,14 +48,14 @@ public fun GeoEllipsoid.meridianCurve(
|
|||||||
val integrateTo: Radians
|
val integrateTo: Radians
|
||||||
|
|
||||||
if (up) {
|
if (up) {
|
||||||
integrateFrom = fromLatitude.radians
|
integrateFrom = fromLatitude.toRadians()
|
||||||
integrateTo = toLatitude.radians
|
integrateTo = toLatitude.toRadians()
|
||||||
} else {
|
} else {
|
||||||
integrateTo = fromLatitude.radians
|
integrateTo = fromLatitude.toRadians()
|
||||||
integrateFrom = toLatitude.radians
|
integrateFrom = toLatitude.toRadians()
|
||||||
}
|
}
|
||||||
|
|
||||||
var current = integrateFrom
|
var current: Radians = integrateFrom
|
||||||
var s = Distance(0.0)
|
var s = Distance(0.0)
|
||||||
while (current < integrateTo) {
|
while (current < integrateTo) {
|
||||||
val next = minOf(current + step, integrateTo)
|
val next = minOf(current + step, integrateTo)
|
||||||
@ -57,8 +64,8 @@ public fun GeoEllipsoid.meridianCurve(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return GmcCurve(
|
return GmcCurve(
|
||||||
forward = GmcPose(Gmc(fromLatitude, longitude), if (up) zero else pi),
|
forward = GmcPose(Gmc.normalized(fromLatitude, longitude), if (up) Angle.zero else Angle.pi),
|
||||||
backward = GmcPose(Gmc(toLatitude, longitude), if (up) pi else zero),
|
backward = GmcPose(Gmc.normalized(toLatitude, longitude), if (up) Angle.pi else Angle.zero),
|
||||||
distance = s
|
distance = s
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -67,12 +74,12 @@ public fun GeoEllipsoid.meridianCurve(
|
|||||||
* Compute a curve alongside a parallel
|
* Compute a curve alongside a parallel
|
||||||
*/
|
*/
|
||||||
public fun GeoEllipsoid.parallelCurve(latitude: Angle, fromLongitude: Angle, toLongitude: Angle): GmcCurve {
|
public fun GeoEllipsoid.parallelCurve(latitude: Angle, fromLongitude: Angle, toLongitude: Angle): GmcCurve {
|
||||||
require(latitude in (-piDiv2)..(piDiv2)) { "Latitude must be in (-90, 90) degrees range" }
|
require(latitude in (-Angle.piDiv2)..(Angle.piDiv2)) { "Latitude must be in (-90, 90) degrees range" }
|
||||||
val right = toLongitude > fromLongitude
|
val right = toLongitude > fromLongitude
|
||||||
return GmcCurve(
|
return GmcCurve(
|
||||||
forward = GmcPose(Gmc(latitude, fromLongitude), if (right) piDiv2.radians else -piDiv2.radians),
|
forward = GmcPose(Gmc.normalized(latitude, fromLongitude), if (right) Angle.piDiv2 else -Angle.piDiv2),
|
||||||
backward = GmcPose(Gmc(latitude, toLongitude), if (right) -piDiv2.radians else piDiv2.radians),
|
backward = GmcPose(Gmc.normalized(latitude, toLongitude), if (right) -Angle.piDiv2 else Angle.piDiv2),
|
||||||
distance = reducedRadius(latitude) * abs((fromLongitude - toLongitude).radians.value)
|
distance = reducedRadius(latitude) * abs((fromLongitude - toLongitude).radians)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +193,7 @@ public fun GeoEllipsoid.curveInDirection(
|
|||||||
val L = lambda - (1 - C) * f * sinAlpha *
|
val L = lambda - (1 - C) * f * sinAlpha *
|
||||||
(sigma.value + C * sinSigma * (cosSigmaM2 + C * cosSigma * (-1 + 2 * cos2SigmaM2)))
|
(sigma.value + C * sinSigma * (cosSigmaM2 + C * cosSigma * (-1 + 2 * cos2SigmaM2)))
|
||||||
|
|
||||||
val endPoint = Gmc(phi2, L.radians)
|
val endPoint = Gmc.normalized(phi2, start.longitude + L.radians)
|
||||||
|
|
||||||
// eq. 12
|
// eq. 12
|
||||||
|
|
||||||
@ -222,6 +229,8 @@ public fun GeoEllipsoid.curveBetween(start: Gmc, end: Gmc, precision: Double = 1
|
|||||||
val a = equatorRadius
|
val a = equatorRadius
|
||||||
val b = polarRadius
|
val b = polarRadius
|
||||||
|
|
||||||
|
if(start == end) error("Can't compute a curve because start and end coincide at $start")
|
||||||
|
|
||||||
// get parameters as radians
|
// get parameters as radians
|
||||||
val phi1 = start.latitude
|
val phi1 = start.latitude
|
||||||
val lambda1 = start.longitude
|
val lambda1 = start.longitude
|
||||||
@ -232,7 +241,7 @@ public fun GeoEllipsoid.curveBetween(start: Gmc, end: Gmc, precision: Double = 1
|
|||||||
val a2 = a.kilometers * a.kilometers
|
val a2 = a.kilometers * a.kilometers
|
||||||
val b2 = b.kilometers * b.kilometers
|
val b2 = b.kilometers * b.kilometers
|
||||||
val a2b2b2 = (a2 - b2) / b2
|
val a2b2b2 = (a2 - b2) / b2
|
||||||
val omega: Radians = lambda2 - lambda1
|
val omega: Angle = lambda2 - lambda1
|
||||||
val tanphi1: Double = tan(phi1)
|
val tanphi1: Double = tan(phi1)
|
||||||
val tanU1 = (1.0 - f) * tanphi1
|
val tanU1 = (1.0 - f) * tanphi1
|
||||||
val U1: Double = atan(tanU1)
|
val U1: Double = atan(tanU1)
|
||||||
@ -249,14 +258,14 @@ public fun GeoEllipsoid.curveBetween(start: Gmc, end: Gmc, precision: Double = 1
|
|||||||
val cosU1cosU2 = cosU1 * cosU2
|
val cosU1cosU2 = cosU1 * cosU2
|
||||||
|
|
||||||
// eq. 13
|
// eq. 13
|
||||||
var lambda = omega
|
var lambda: Angle = omega
|
||||||
|
|
||||||
// intermediates we'll need to compute 's'
|
// intermediates we'll need to compute 's'
|
||||||
var A = 0.0
|
var A = 0.0
|
||||||
|
|
||||||
var sigma = 0.0
|
var sigma = 0.0
|
||||||
var deltasigma = 0.0
|
var deltasigma = 0.0
|
||||||
var lambda0: Radians
|
var lambda0: Angle
|
||||||
var converged = false
|
var converged = false
|
||||||
for (i in 0..19) {
|
for (i in 0..19) {
|
||||||
lambda0 = lambda
|
lambda0 = lambda
|
||||||
@ -320,14 +329,13 @@ public fun GeoEllipsoid.curveBetween(start: Gmc, end: Gmc, precision: Double = 1
|
|||||||
// didn't converge? must be N/S
|
// didn't converge? must be N/S
|
||||||
if (!converged) {
|
if (!converged) {
|
||||||
if (phi1 > phi2) {
|
if (phi1 > phi2) {
|
||||||
alpha1 = pi.radians
|
alpha1 = Angle.pi.toRadians()
|
||||||
alpha2 = 0.0.radians
|
alpha2 = 0.0.radians
|
||||||
} else if (phi1 < phi2) {
|
} else if (phi1 < phi2) {
|
||||||
alpha1 = 0.0.radians
|
alpha1 = 0.0.radians
|
||||||
alpha2 = pi.radians
|
alpha2 = Angle.pi.toRadians()
|
||||||
} else {
|
} else {
|
||||||
alpha1 = Double.NaN.radians
|
error("Start and end point coinside.")
|
||||||
alpha2 = Double.NaN.radians
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// eq. 20
|
// eq. 20
|
||||||
@ -340,7 +348,7 @@ public fun GeoEllipsoid.curveBetween(start: Gmc, end: Gmc, precision: Double = 1
|
|||||||
alpha2 = atan2(
|
alpha2 = atan2(
|
||||||
cosU1 * sin(lambda),
|
cosU1 * sin(lambda),
|
||||||
-sinU1cosU2 + cosU1sinU2 * cos(lambda)
|
-sinU1cosU2 + cosU1sinU2 * cos(lambda)
|
||||||
).radians + pi
|
).radians + Angle.pi
|
||||||
}
|
}
|
||||||
return GmcCurve(
|
return GmcCurve(
|
||||||
GmcPose(start, alpha1),
|
GmcPose(start, alpha1),
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
package center.sciprog.maps.coordinates
|
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
|
* A coordinate-bearing pair
|
||||||
*/
|
*/
|
||||||
|
@Serializable
|
||||||
public data class GmcPose(val coordinates: GeodeticMapCoordinates, val bearing: Angle) {
|
public data class GmcPose(val coordinates: GeodeticMapCoordinates, val bearing: Angle) {
|
||||||
val latitude: Angle get() = coordinates.latitude
|
val latitude: Angle get() = coordinates.latitude
|
||||||
val longitude: Angle get() = coordinates.longitude
|
val longitude: Angle get() = coordinates.longitude
|
||||||
|
@ -1,116 +0,0 @@
|
|||||||
package center.sciprog.maps.coordinates
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
public data class GmcRectangle(
|
|
||||||
public val a: GeodeticMapCoordinates,
|
|
||||||
public val b: GeodeticMapCoordinates,
|
|
||||||
) {
|
|
||||||
public companion object {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A quasi-square section.
|
|
||||||
*/
|
|
||||||
public fun square(
|
|
||||||
center: GeodeticMapCoordinates,
|
|
||||||
height: Angle,
|
|
||||||
width: Angle,
|
|
||||||
): GmcRectangle {
|
|
||||||
val a = GeodeticMapCoordinates(
|
|
||||||
center.latitude - (height / 2),
|
|
||||||
center.longitude - (width / 2)
|
|
||||||
)
|
|
||||||
val b = GeodeticMapCoordinates(
|
|
||||||
center.latitude + (height / 2),
|
|
||||||
center.longitude + (width / 2)
|
|
||||||
)
|
|
||||||
return GmcRectangle(a, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A quasi-square section. Note that latitudinal distance could be imprecise for large distances
|
|
||||||
*/
|
|
||||||
public fun square(
|
|
||||||
center: GeodeticMapCoordinates,
|
|
||||||
height: Distance,
|
|
||||||
width: Distance,
|
|
||||||
ellipsoid: GeoEllipsoid = GeoEllipsoid.WGS84,
|
|
||||||
): GmcRectangle {
|
|
||||||
val reducedRadius = ellipsoid.reducedRadius(center.latitude)
|
|
||||||
return square(center, (height / ellipsoid.polarRadius).radians, (width / reducedRadius).radians)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public val GmcRectangle.center: GeodeticMapCoordinates
|
|
||||||
get() = GeodeticMapCoordinates(
|
|
||||||
(a.latitude + b.latitude) / 2,
|
|
||||||
(a.longitude + b.longitude) / 2
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum longitude
|
|
||||||
*/
|
|
||||||
public val GmcRectangle.left: Angle get() = minOf(a.longitude, b.longitude)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* maximum longitude
|
|
||||||
*/
|
|
||||||
public val GmcRectangle.right: Angle get() = maxOf(a.longitude, b.longitude)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum latitude
|
|
||||||
*/
|
|
||||||
public val GmcRectangle.top: Angle get() = maxOf(a.latitude, b.latitude)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum latitude
|
|
||||||
*/
|
|
||||||
public val GmcRectangle.bottom: Angle get() = minOf(a.latitude, b.latitude)
|
|
||||||
|
|
||||||
public val GmcRectangle.longitudeDelta: Angle get() = abs(a.longitude - b.longitude)
|
|
||||||
public val GmcRectangle.latitudeDelta: Angle get() = abs(a.latitude - b.latitude)
|
|
||||||
|
|
||||||
public val GmcRectangle.topLeft: GeodeticMapCoordinates get() = GeodeticMapCoordinates(top, left)
|
|
||||||
public val GmcRectangle.bottomRight: GeodeticMapCoordinates get() = GeodeticMapCoordinates(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 {
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if coordinate is inside the box
|
|
||||||
*/
|
|
||||||
public operator fun GmcRectangle.contains(coordinate: Gmc): Boolean =
|
|
||||||
coordinate.latitude in (bottom..top) && coordinate.longitude in (left..right)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute a minimal bounding box including all given boxes. Return null if collection is empty
|
|
||||||
*/
|
|
||||||
public fun Collection<GmcRectangle>.wrapAll(): GmcRectangle? {
|
|
||||||
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(GeodeticMapCoordinates(minLat, minLong), GeodeticMapCoordinates(maxLat, maxLong))
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
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(
|
|
||||||
(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(
|
|
||||||
focus.latitude + (invariant.latitude - focus.latitude) * difScale,
|
|
||||||
focus.longitude + (invariant.longitude - focus.longitude) * difScale
|
|
||||||
)
|
|
||||||
MapViewPoint(newCenter, (zoom + zoomDelta).coerceIn(2.0, 18.0))
|
|
||||||
}
|
|
@ -5,54 +5,92 @@
|
|||||||
|
|
||||||
package center.sciprog.maps.coordinates
|
package center.sciprog.maps.coordinates
|
||||||
|
|
||||||
import center.sciprog.maps.coordinates.Angle.Companion.pi
|
import kotlinx.serialization.Serializable
|
||||||
import kotlin.math.atan
|
import space.kscience.kmath.geometry.*
|
||||||
import kotlin.math.ln
|
import kotlin.math.*
|
||||||
import kotlin.math.sinh
|
|
||||||
|
public data class ProjectionCoordinates(val x: Distance, val y: Distance)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public data class MercatorCoordinates(val x: Distance, val y: Distance)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param baseLongitude the longitude offset in radians
|
* @param baseLongitude the longitude offset in radians
|
||||||
* @param radius the average radius of the Earth
|
* @param ellipsoid - a [GeoEllipsoid] to be used for conversion
|
||||||
* @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: Angle = Angle.zero,
|
||||||
protected val radius: Distance = DEFAULT_EARTH_RADIUS,
|
public val ellipsoid: GeoEllipsoid = GeoEllipsoid.sphere,
|
||||||
private val correctedRadius: ((GeodeticMapCoordinates) -> Distance)? = null,
|
) : MapProjection<ProjectionCoordinates> {
|
||||||
) {
|
|
||||||
|
|
||||||
public fun toGeodetic(mc: MercatorCoordinates): GeodeticMapCoordinates {
|
|
||||||
val res = GeodeticMapCoordinates.ofRadians(
|
/**
|
||||||
atan(sinh(mc.y / radius)),
|
* Taken from https://github.com/geotools/geotools/blob/main/modules/library/referencing/src/main/java/org/geotools/referencing/operation/projection/Mercator.java#L164
|
||||||
baseLongitude.radians.value + (mc.x / radius),
|
*/
|
||||||
)
|
private fun cphi2(ts: Double): Double {
|
||||||
return if (correctedRadius != null) {
|
val eccnth: Double = 0.5 * ellipsoid.eccentricity
|
||||||
val r = correctedRadius.invoke(res)
|
var phi: Double = PI / 2 - 2.0 * atan(ts)
|
||||||
|
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(mc.y / r)),
|
atan(sinh(pc.y / ellipsoid.equatorRadius)),
|
||||||
baseLongitude.radians.value + mc.x / r,
|
baseLongitude.radians + (pc.x / ellipsoid.equatorRadius),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
res
|
GeodeticMapCoordinates.ofRadians(
|
||||||
|
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
|
||||||
*/
|
*/
|
||||||
public fun toMercator(gmc: GeodeticMapCoordinates): MercatorCoordinates {
|
override fun toProjection(gmc: GeodeticMapCoordinates): ProjectionCoordinates {
|
||||||
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 r: Distance = correctedRadius?.invoke(gmc) ?: radius
|
val e = sqrt(ellipsoid.eSquared)
|
||||||
return MercatorCoordinates(
|
|
||||||
x = r * (gmc.longitude - baseLongitude).radians.value,
|
return if (ellipsoid === GeoEllipsoid.sphere) {
|
||||||
y = r * ln(tan(pi / 4 + gmc.latitude / 2))
|
ProjectionCoordinates(
|
||||||
|
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 : MercatorProjection(Angle.zero, Distance(6378.137)) {
|
public companion object {
|
||||||
public val MAXIMUM_LATITUDE: Angle = 85.05113.degrees
|
public val MAXIMUM_LATITUDE: Angle = 85.05113.degrees
|
||||||
public val DEFAULT_EARTH_RADIUS: Distance = radius
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,19 +5,21 @@
|
|||||||
|
|
||||||
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: Double, val y: Double)
|
public data class WebMercatorCoordinates(val zoom: Int, val x: Float, val y: Float)
|
||||||
|
|
||||||
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: Double): Double = 256.0 / 2 / PI * 2.0.pow(zoom)
|
public fun scaleFactor(zoom: Float): Float = (256.0 / 2 / PI * 2f.pow(zoom)).toFloat()
|
||||||
|
|
||||||
public fun toGeodetic(mercator: WebMercatorCoordinates): GeodeticMapCoordinates {
|
public fun toGeodetic(mercator: WebMercatorCoordinates): GeodeticMapCoordinates {
|
||||||
val scaleFactor = scaleFactor(mercator.zoom.toDouble())
|
val scaleFactor = scaleFactor(mercator.zoom.toFloat())
|
||||||
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)
|
||||||
@ -25,15 +27,17 @@ 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? {
|
||||||
require(abs(gmc.latitude) <= MercatorProjection.MAXIMUM_LATITUDE) { "Latitude exceeds the maximum latitude for mercator coordinates" }
|
if (abs(gmc.latitude) > MercatorProjection.MAXIMUM_LATITUDE) return null
|
||||||
|
|
||||||
val scaleFactor = scaleFactor(zoom.toDouble())
|
val scaleFactor = scaleFactor(zoom.toFloat())
|
||||||
return WebMercatorCoordinates(
|
return WebMercatorCoordinates(
|
||||||
zoom = zoom,
|
zoom = zoom,
|
||||||
x = scaleFactor * (gmc.longitude.radians.value + PI),
|
x = scaleFactor * (gmc.longitude.radians + PI).toFloat(),
|
||||||
y = scaleFactor * (PI - ln(tan(PI / 4 + gmc.latitude.radians.value / 2)))
|
y = scaleFactor * (PI - ln(tan(PI / 4 + gmc.latitude.radians / 2))).toFloat()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2018-2021 KMath contributors.
|
|
||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package center.sciprog.maps.coordinates
|
|
||||||
|
|
||||||
import kotlin.jvm.JvmInline
|
|
||||||
import kotlin.math.PI
|
|
||||||
|
|
||||||
// Taken from KMath dev version, to be used directly in the future
|
|
||||||
|
|
||||||
|
|
||||||
public sealed interface Angle : Comparable<Angle> {
|
|
||||||
public val radians: Radians
|
|
||||||
public val degrees: Degrees
|
|
||||||
|
|
||||||
public operator fun plus(other: Angle): Angle
|
|
||||||
public operator fun minus(other: Angle): Angle
|
|
||||||
|
|
||||||
public operator fun times(other: Number): Angle
|
|
||||||
public operator fun div(other: Number): Angle
|
|
||||||
public operator fun div(other: Angle): Double
|
|
||||||
public operator fun unaryMinus(): Angle
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
public val zero: Angle = 0.radians
|
|
||||||
public val pi: Angle = PI.radians
|
|
||||||
public val piTimes2: Angle = (2 * PI).radians
|
|
||||||
public val piDiv2: Angle = (PI / 2).radians
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type safe radians
|
|
||||||
*/
|
|
||||||
@JvmInline
|
|
||||||
public value class Radians(public val value: Double) : Angle {
|
|
||||||
override val radians: Radians
|
|
||||||
get() = this
|
|
||||||
override val degrees: Degrees
|
|
||||||
get() = Degrees(value * 180 / PI)
|
|
||||||
|
|
||||||
public override fun plus(other: Angle): Radians = Radians(value + other.radians.value)
|
|
||||||
public override fun minus(other: Angle): Radians = Radians(value - other.radians.value)
|
|
||||||
|
|
||||||
public override fun times(other: Number): Radians = Radians(value * other.toDouble())
|
|
||||||
public override fun div(other: Number): Radians = Radians(value / other.toDouble())
|
|
||||||
override fun div(other: Angle): Double = value / other.radians.value
|
|
||||||
public override fun unaryMinus(): Radians = Radians(-value)
|
|
||||||
|
|
||||||
override fun compareTo(other: Angle): Int = value.compareTo(other.radians.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun sin(angle: Angle): Double = kotlin.math.sin(angle.radians.value)
|
|
||||||
public fun cos(angle: Angle): Double = kotlin.math.cos(angle.radians.value)
|
|
||||||
public fun tan(angle: Angle): Double = kotlin.math.tan(angle.radians.value)
|
|
||||||
|
|
||||||
public val Number.radians: Radians get() = Radians(toDouble())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type safe degrees
|
|
||||||
*/
|
|
||||||
@JvmInline
|
|
||||||
public value class Degrees(public val value: Double) : Angle {
|
|
||||||
override val radians: Radians
|
|
||||||
get() = Radians(value * PI / 180)
|
|
||||||
override val degrees: Degrees
|
|
||||||
get() = this
|
|
||||||
|
|
||||||
public override fun plus(other: Angle): Degrees = Degrees(value + other.degrees.value)
|
|
||||||
public override fun minus(other: Angle): Degrees = Degrees(value - other.degrees.value)
|
|
||||||
|
|
||||||
public override fun times(other: Number): Degrees = Degrees(value * other.toDouble())
|
|
||||||
public override fun div(other: Number): Degrees = Degrees(value / other.toDouble())
|
|
||||||
override fun div(other: Angle): Double = value / other.degrees.value
|
|
||||||
public override fun unaryMinus(): Degrees = Degrees(-value)
|
|
||||||
|
|
||||||
override fun compareTo(other: Angle): Int = value.compareTo(other.degrees.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
public val Number.degrees: Degrees get() = Degrees(toDouble())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalized angle to (0, 2PI) for radians or (0, 360) for degrees.
|
|
||||||
*/
|
|
||||||
public fun Angle.normalized(): Angle = when (this) {
|
|
||||||
is Degrees -> (value + 180.0).rem(360.0).degrees
|
|
||||||
is Radians -> (value + PI).rem(PI * 2).radians
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun abs(angle: Angle): Angle = if (angle < Angle.zero) -angle else angle
|
|
@ -1,13 +1,13 @@
|
|||||||
package center.sciprog.maps.coordinates
|
package center.sciprog.maps.coordinates
|
||||||
|
|
||||||
import kotlin.test.Ignore
|
import space.kscience.kmath.geometry.radians
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
internal class DistanceTest {
|
internal class DistanceTest {
|
||||||
companion object {
|
companion object {
|
||||||
val moscow = GMC.ofDegrees(55.76058287719673, 37.60358622841869)
|
val moscow = Gmc.ofDegrees(55.76058287719673, 37.60358622841869)
|
||||||
val spb = GMC.ofDegrees(59.926686023580444, 30.36038109122013)
|
val spb = Gmc.ofDegrees(59.926686023580444, 30.36038109122013)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -21,6 +21,7 @@ internal class DistanceTest {
|
|||||||
val distance = curve.distance
|
val distance = curve.distance
|
||||||
|
|
||||||
assertEquals(632.035426877, distance.kilometers, 0.0001)
|
assertEquals(632.035426877, distance.kilometers, 0.0001)
|
||||||
|
assertEquals(-0.6947937116552751, curve.forward.bearing.radians, 0.0001)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -29,6 +30,7 @@ internal class DistanceTest {
|
|||||||
GmcPose(moscow, (-0.6947937116552751).radians), Distance(632.035426877)
|
GmcPose(moscow, (-0.6947937116552751).radians), Distance(632.035426877)
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(spb.latitude.radians.value,curve.backward.latitude.radians.value, 0.0001)
|
assertEquals(spb.latitude.radians, curve.backward.latitude.radians, 0.0001)
|
||||||
|
assertEquals(spb.longitude.radians, curve.backward.longitude.radians, 0.0001)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
32
maps-kt-features/README.md
Normal file
32
maps-kt-features/README.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# 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")
|
||||||
|
}
|
||||||
|
```
|
765
maps-kt-features/api/maps-kt-features.api
Normal file
765
maps-kt-features/api/maps-kt-features.api
Normal file
@ -0,0 +1,765 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
30
maps-kt-features/build.gradle.kts
Normal file
30
maps-kt-features/build.gradle.kts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
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())
|
||||||
|
|
@ -0,0 +1,74 @@
|
|||||||
|
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
|
||||||
|
// }
|
@ -0,0 +1,47 @@
|
|||||||
|
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()
|
@ -0,0 +1,55 @@
|
|||||||
|
@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)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
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)
|
@ -0,0 +1,63 @@
|
|||||||
|
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)
|
@ -0,0 +1,56 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,364 @@
|
|||||||
|
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))
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package center.sciprog.maps.features
|
||||||
|
|
||||||
|
public expect class FeatureFont {
|
||||||
|
public var size: Float
|
||||||
|
}
|
@ -0,0 +1,305 @@
|
|||||||
|
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)
|
||||||
|
)
|
@ -0,0 +1,25 @@
|
|||||||
|
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,
|
||||||
|
//)
|
@ -0,0 +1,14 @@
|
|||||||
|
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
|
||||||
|
)
|
@ -0,0 +1,9 @@
|
|||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
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)
|
||||||
|
}
|
@ -0,0 +1,172 @@
|
|||||||
|
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)
|
@ -0,0 +1,87 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package center.sciprog.maps.features
|
||||||
|
|
||||||
|
public actual class FeatureFont {
|
||||||
|
public actual var size: Float = 16f
|
||||||
|
}
|
@ -0,0 +1,306 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,189 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
@ -0,0 +1,5 @@
|
|||||||
|
package center.sciprog.maps.features
|
||||||
|
|
||||||
|
import org.jetbrains.skia.Font
|
||||||
|
|
||||||
|
public actual typealias FeatureFont = Font
|
@ -0,0 +1,198 @@
|
|||||||
|
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}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
maps-kt-geojson/README.md
Normal file
32
maps-kt-geojson/README.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# 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")
|
||||||
|
}
|
||||||
|
```
|
224
maps-kt-geojson/api/maps-kt-geojson.api
Normal file
224
maps-kt-geojson/api/maps-kt-geojson.api
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
18
maps-kt-geojson/build.gradle.kts
Normal file
18
maps-kt-geojson/build.gradle.kts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
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)
|
||||||
|
}
|
@ -0,0 +1,226 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
)
|
@ -0,0 +1,7 @@
|
|||||||
|
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())
|
@ -0,0 +1,21 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
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)
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
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)
|
||||||
|
}
|
17
maps-kt-geotiff/build.gradle.kts
Normal file
17
maps-kt-geotiff/build.gradle.kts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
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)
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user