1 Commits

Author SHA1 Message Date
a.kalmakhanov
cd64ecd817 onClick does not occur on onDrag start, and onRelease added that occur on onDrag end 2022-08-26 16:25:21 +06:00
151 changed files with 1900 additions and 89131 deletions

View File

@@ -1,22 +0,0 @@
name: Gradle build
on:
push:
branches: [ dev ]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4.1.7
- name: Set up JDK 17
uses: actions/setup-java@v4.2.1
with:
java-version: 17
distribution: liberica
- name: execute build
uses: gradle/gradle-build-action@v3.4.2
with:
arguments: build

View File

@@ -1,39 +0,0 @@
name: Dokka publication
on:
push:
branches: [ main ]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: liberica
- name: execute build
uses: gradle/gradle-build-action@v3
with:
arguments: dokkaHtmlMultiModule
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: 'build/dokka/htmlMultiModule'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

View File

@@ -1,27 +0,0 @@
name: Gradle publish
on:
workflow_dispatch:
release:
types: [ created ]
jobs:
publish:
environment:
name: publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.7
- name: Set up JDK 17
uses: actions/setup-java@v4.2.1
with:
java-version: 17
distribution: liberica
- name: execute build
uses: gradle/gradle-build-action@v3.4.2
- name: Publish
shell: bash
run: >
./gradlew release --no-daemon --build-cache -Ppublishing.enabled=true
-Ppublishing.space.user=${{ secrets.SPACE_APP_ID }}
-Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }}

1
.gitignore vendored
View File

@@ -1,7 +1,6 @@
build/
.gradle/
.idea/
.kotlin
/*.iml
mapCache/

View File

@@ -1,25 +1,25 @@
import kotlin.io.path.readText
job("Build") {
gradlew("spc.registry.jetbrains.space/p/sci/containers/kotlin-ci:1.0.3", "build")
gradlew("openjdk:11", "build")
}
job("Publish") {
startOn {
gitPush { enabled = false }
}
container("spc.registry.jetbrains.space/p/sci/containers/kotlin-ci:1.0.3") {
env["SPACE_USER"] = "{{ project:space_user }}"
env["SPACE_TOKEN"] = "{{ project:space_token }}"
container("openjdk:11") {
env["SPACE_USER"] = Secrets("space_user")
env["SPACE_TOKEN"] = Secrets("space_token")
kotlinScript { api ->
val spaceUser = System.getenv("SPACE_USER")
val spaceToken = System.getenv("SPACE_TOKEN")
// write the version to the build directory
// write version to the build directory
api.gradlew("version")
//read the version from build file
//read version from build file
val version = java.nio.file.Path.of("build/project-version.txt").readText()
val revisionSuffix = if (version.endsWith("SNAPSHOT")) {
@@ -32,7 +32,7 @@ job("Publish") {
project = api.projectIdentifier(),
targetIdentifier = TargetIdentifier.Key("maps-kt"),
version = version+revisionSuffix,
// automatically update deployment status based on the status of a job
// automatically update deployment status based on a status of a job
syncWithAutomationJob = true
)
api.gradlew(

View File

@@ -1,32 +0,0 @@
# Changelog
## Unreleased
### Added
- `alpha` extension for feature attribute builder
- PNG export
### Changed
- avoid drawing features with VisibleAttribute false
- Move SVG export to `features` and make it usable for maps as well
### Deprecated
### Removed
### Fixed
- Add alpha attribute comprehension for all standard features.
- Package name for SerializeableAttribute
### Security
## 0.3.0 - 2024-06-04
### Changed
- Package changed to `space.kscience`
- Kotlin 2.0
### Fixed
- Use of generated resources for Wasm

View File

@@ -1,69 +1,13 @@
# 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)
## [maps-kt-core](maps-kt-core)
A multiplatform coordinates representation and conversion.
## Modules
## [maps-kt-compose](maps-kt-compose)
A compose multiplatform (currently desktop only, contributions of android target are welcome) implementation of a map component, features and builder.
## [maps-kt-scheme](maps-kt-scheme)
An alternative component used for the same functionality on 2D schemes. Not all features from maps could be ported because it requires some code duplication (ideas for common API are welcome).
### [demo](demo)
>
> **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/maps-wasm](demo/maps-wasm)
>
> **Maturity**: EXPERIMENTAL
### [demo/polygon-editor](demo/polygon-editor)
>
> **Maturity**: EXPERIMENTAL
### [demo/scheme](demo/scheme)
>
> **Maturity**: EXPERIMENTAL
### [demo/trajectory-playground](demo/trajectory-playground)
>
> **Maturity**: EXPERIMENTAL
## [demo](demo)
Demonstration projects for different features

View File

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

View File

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

View File

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

View File

@@ -1,37 +0,0 @@
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
plugins {
kotlin("multiplatform")
alias(spclibs.plugins.compose.compiler)
alias(spclibs.plugins.compose.jb)
}
//val ktorVersion: String by rootProject.extra
kotlin {
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser()
binaries.executable()
}
sourceSets {
commonMain {
dependencies {
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
api(compose.components.resources)
}
}
wasmJsMain {
dependencies {
implementation(projects.mapsKtScheme)
}
}
}
}
compose {
web {
}
}

View File

@@ -1,82 +0,0 @@
@file:OptIn(ExperimentalResourceApi::class, ExperimentalComposeUiApi::class)
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.window.CanvasBasedWindow
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.painterResource
import space.kscience.kmath.geometry.Angle
import space.kscience.maps.features.FeatureStore
import space.kscience.maps.features.ViewConfig
import space.kscience.maps.features.ViewPoint
import space.kscience.maps.features.color
import space.kscience.maps.scheme.*
import space.kscience.maps_wasm.generated.resources.Res
import space.kscience.maps_wasm.generated.resources.middle_earth
@Composable
fun App() {
val scope = rememberCoroutineScope()
val features = FeatureStore.remember(XYCoordinateSpace) {
background(1600f, 1200f) {
painterResource(Res.drawable.middle_earth)
}
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)
//circle(410.52737 to 868.7676, id = "hobbit")
scope.launch {
var t = 0.0
while (isActive) {
val x = 410.52737 + t * (1132.0881 - 410.52737)
val y = 868.7676 + t * (394.99127 - 868.7676)
circle(x to y, id = "hobbit").color(Color.Green)
delay(100)
t += 0.005
if (t >= 1.0) t = 0.0
}
}
}
val initialViewPoint: ViewPoint<XY> = remember {
features.getBoundingBox(1f)?.computeViewPoint() ?: XYViewPoint(XY(0f, 0f))
}
var viewPoint: ViewPoint<XY> by remember { mutableStateOf(initialViewPoint) }
val mapState: XYCanvasState = XYCanvasState.remember(
ViewConfig(
onClick = { _, click ->
println("${click.focus.x}, ${click.focus.y}")
},
onViewChange = { viewPoint = this }
),
initialViewPoint = initialViewPoint,
)
SchemeView(
mapState,
features,
)
}
fun main() {
// renderComposable(rootElementId = "root") {
CanvasBasedWindow("Maps demo", canvasElementId = "ComposeTarget") {
App()
}
}

View File

@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Compose App</title>
<script type="application/javascript" src="skiko.js"></script>
<script type="application/javascript" src="maps-wasm.js"></script>
</head>
<body>
<canvas id="ComposeTarget"></canvas>
</body>
</html>

View File

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

View File

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

View File

@@ -1,39 +1,40 @@
import org.jetbrains.compose.compose
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
kotlin("multiplatform")
alias(spclibs.plugins.compose.compiler)
alias(spclibs.plugins.compose.jb)
id("org.jetbrains.compose")
}
val ktorVersion: String by rootProject.extra
kotlin {
jvmToolchain(11)
jvm()
jvm {
compilations.all {
kotlinOptions.jvmTarget = "11"
}
withJava()
}
sourceSets {
val jvmMain by getting {
dependencies {
implementation(projects.mapsKtCompose)
implementation(projects.mapsKtGeojson)
implementation(compose.desktop.currentOs)
implementation("io.ktor:ktor-client-cio")
implementation(spclibs.logback.classic)
implementation("io.ktor:ktor-client-cio:$ktorVersion")
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 = "maps-compose-demo"
packageVersion = "1.0.0"
}
compose.desktop {
application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "maps-compose-demo"
packageVersion = "1.0.0"
}
}
}

View File

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

View File

@@ -1,53 +1,44 @@
// 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.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.PointerMatcher
import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Home
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.*
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.graphics.PointMode
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import center.sciprog.maps.compose.*
import center.sciprog.maps.coordinates.Distance
import center.sciprog.maps.coordinates.GeodeticMapCoordinates
import center.sciprog.maps.coordinates.MapViewPoint
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
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.launch
import space.kscience.attributes.Attributes
import space.kscience.kmath.geometry.Angle
import space.kscience.kmath.geometry.degrees
import space.kscience.kmath.geometry.radians
import space.kscience.maps.compose.*
import space.kscience.maps.coordinates.GeodeticMapCoordinates
import space.kscience.maps.coordinates.Gmc
import space.kscience.maps.coordinates.kilometers
import space.kscience.maps.features.*
import space.kscience.maps.geojson.geoJson
import java.nio.file.Path
import kotlin.math.PI
import kotlin.random.Random
public fun GeodeticMapCoordinates.toShortString(): String =
"${(latitude.toDegrees().value).toString().take(6)}:${(longitude.toDegrees().value).toString().take(6)}"
private fun GeodeticMapCoordinates.toShortString(): String =
"${(latitude * 180.0 / PI).toString().take(6)}:${(longitude * 180.0 / PI).toString().take(6)}"
@OptIn(ExperimentalFoundationApi::class)
@Composable
@Preview
fun App() {
MaterialTheme {
//create a view point
val viewPoint = remember {
MapViewPoint(
GeodeticMapCoordinates.ofDegrees(55.7558, 37.6173),
8.0
)
}
val scope = rememberCoroutineScope()
val mapTileProvider = remember {
OpenStreetMapTileProvider(
client = HttpClient(CIO),
@@ -55,47 +46,39 @@ fun App() {
)
}
val centerCoordinates = MutableStateFlow<Gmc?>(null)
var centerCoordinates by remember { mutableStateOf<GeodeticMapCoordinates?>(null) }
val pointOne = 55.568548 to 37.568604
val pointTwo = 55.929444 to 37.518434
// val pointThree = 60.929444 to 37.518434
var pointTwo by remember { mutableStateOf(55.929444 to 37.518434) }
val pointThree = 60.929444 to 37.518434
MapView(
mapTileProvider = mapTileProvider,
config = ViewConfig(
onViewChange = { centerCoordinates.value = focus },
onClick = { _, viewPoint ->
println(viewPoint)
initialViewPoint = viewPoint,
config = MapViewConfig(
inferViewBoxFromFeatures = true,
onViewChange = { centerCoordinates = focus },
onDrag = { start, end ->
if (start.focus.latitude.toDegrees() in (pointTwo.first - 0.05)..(pointTwo.first + 0.05) &&
start.focus.longitude.toDegrees() in (pointTwo.second - 0.05)..(pointTwo.second + 0.05)
) {
pointTwo = pointTwo.first + (end.focus.latitude - start.focus.latitude).toDegrees() to
pointTwo.second + (end.focus.longitude - start.focus.longitude).toDegrees()
false// returning false, because when we are dragging circle we don't want to drag map
} else true
},
onRelease = {
println("On drag ended $this")
},
onClick = {
println("On CLick $this")
}
)
) {
geoJson(javaClass.getResource("/moscow.geo.json")!!)
.color(Color.Blue)
.alpha(0.4f)
image(pointOne, Icons.Filled.Home)
icon(pointOne, Icons.Filled.Home)
val marker1 = rectangle(55.744 to 38.614, size = DpSize(10.dp, 10.dp))
.color(Color.Magenta)
val marker2 = rectangle(55.8 to 38.5, size = DpSize(10.dp, 10.dp))
.color(Color.Magenta)
val marker3 = rectangle(56.0 to 38.5, size = DpSize(10.dp, 10.dp))
.color(Color.Magenta)
draggableLine(marker1, marker2, id = "line 1").color(Color.Red).onClick {
println("line 1 clicked")
}
draggableLine(marker2, marker3, id = "line 2").color(Color.DarkGray).onClick {
println("line 2 clicked")
}
draggableLine(marker3, marker1, id = "line 3").color(Color.Blue).onClick {
println("line 3 clicked")
}
multiLine(
points(
points = listOf(
55.742465 to 37.615812,
55.742713 to 37.616370,
@@ -104,89 +87,48 @@ fun App() {
55.742086 to 37.616566,
55.741715 to 37.616716
),
pointMode = PointMode.Polygon
)
// points(
// points = listOf(
// 55.744 to 38.614,
// 55.8 to 38.5,
// 56.0 to 38.5,
// )
// ).pointSize(5f)
// geodeticLine(Gmc.ofDegrees(40.7128, -74.0060), Gmc.ofDegrees(55.742465, 37.615812)).color(Color.Blue)
// line(Gmc.ofDegrees(40.7128, -74.0060), Gmc.ofDegrees(55.742465, 37.615812))
//remember feature ref
val circleId = circle(
//remember feature Id
val circleId: FeatureId = circle(
centerCoordinates = pointTwo,
)
scope.launch {
while (isActive) {
delay(200)
circleId.color(Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat()))
}
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)
}
// 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)
arc(pointOne, Distance(10.0), 0f, PI)
line(pointOne, pointTwo, id = "line")
text(pointOne, "Home", font = { size = 32f })
pixelMap(
space.Rectangle(
Gmc(latitude = 55.58461879539754.degrees, longitude = 37.8746197303493.degrees),
Gmc(latitude = 55.442792937592415.degrees, longitude = 38.132240805463844.degrees)
),
0.005.degrees,
0.005.degrees
) { gmc ->
Color(
red = ((gmc.latitude + Angle.piDiv2).toDegrees().value * 10 % 1f).toFloat(),
green = ((gmc.longitude + Angle.pi).toDegrees().value * 10 % 1f).toFloat(),
blue = 0f,
alpha = 0.3f
)
}
centerCoordinates.filterNotNull().onEach {
centerCoordinates?.let {
group(id = "center") {
circle(center = it, id = "circle", size = 1.dp).color(Color.Blue)
text(position = it, it.toShortString(), id = "text").color(Color.Blue)
}
}.launchIn(scope)
//Add click listeners for all polygons
forEachWithType<Gmc, PolygonFeature<Gmc>> { ref, polygon: PolygonFeature<Gmc> ->
ref.onClick(PointerMatcher.Primary) {
println("Click on $ref")
//draw in top-level scope
with(this@MapView) {
multiLine(
polygon.points,
attributes = Attributes(ZAttribute, 10f),
id = "selected",
).modifyAttribute(StrokeAttribute, 4f).color(Color.Magenta)
}
circle(center = it, color = Color.Blue, size = 1f)
text(position = it, it.toShortString(), color = Color.Blue)
}
}
scope.launch {
while (isActive) {
delay(200)
//Overwrite a feature with new color
circle(
pointTwo,
id = circleId,
color = Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat())
)
}
}
// println(toPrettyString())
}
}
}
fun main() = application {
Window(onCloseRequest = ::exitApplication, title = "Maps-kt demo", icon = painterResource("SPC-logo.png")) {
Window(onCloseRequest = ::exitApplication) {
App()
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -1,81 +0,0 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.material.CursorDropdownMenu
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.input.pointer.isSecondaryPressed
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import space.kscience.maps.features.*
import space.kscience.maps.scheme.SchemeView
import space.kscience.maps.scheme.XY
import space.kscience.maps.scheme.XYCanvasState
import space.kscience.maps.scheme.XYCoordinateSpace
@Composable
@Preview
fun App() {
MaterialTheme {
var clickPoint by remember { mutableStateOf<XY?>(null) }
val myPolygon: SnapshotStateList<XY> = remember { mutableStateListOf<XY>() }
val featureState = FeatureStore.remember(XYCoordinateSpace) {
multiLine(
listOf(XY(0f, 0f), XY(0f, 1f), XY(1f, 1f), XY(1f, 0f), XY(0f, 0f)),
id = "frame"
)
}
val mapState: XYCanvasState = XYCanvasState.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(),
"line"
)
}
}
clickPoint = null
}) {
Text("Create node")
}
}
}
SchemeView(
mapState,
featureState,
)
}
}
fun main() = application {
Window(title = "Polygon editor demo", onCloseRequest = ::exitApplication) {
App()
}
}

View File

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

View File

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

View File

@@ -1,16 +1,20 @@
import org.jetbrains.compose.compose
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
kotlin("multiplatform")
alias(spclibs.plugins.compose.compiler)
alias(spclibs.plugins.compose.jb)
id("org.jetbrains.compose")
}
val ktorVersion: String by rootProject.extra
kotlin {
jvm()
jvmToolchain(11)
jvm {
compilations.all {
kotlinOptions.jvmTarget = "11"
}
withJava()
}
sourceSets {
val jvmMain by getting {
dependencies {
@@ -23,16 +27,13 @@ kotlin {
}
}
compose{
desktop {
application {
mainClass = "MainKt"
//mainClass = "Joker2023Kt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "scheme-compose-demo"
packageVersion = "1.0.0"
}
compose.desktop {
application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "scheme-compose-demo"
packageVersion = "1.0.0"
}
}
}

View File

@@ -1,110 +1,67 @@
// 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.foundation.ContextMenuArea
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.ui.geometry.Size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import center.sciprog.maps.scheme.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import space.kscience.kmath.geometry.Angle
import space.kscience.maps.features.*
import space.kscience.maps.scheme.*
import space.kscience.maps.svg.exportToPng
import space.kscience.maps.svg.exportToSvg
import java.awt.Desktop
import java.nio.file.Files
@Composable
@Preview
fun App() {
MaterialTheme {
//create a view point
val viewPoint = remember {
SchemeViewPoint(
SchemeCoordinates(0f, 0f),
1f
)
}
val scope = rememberCoroutineScope()
val features = FeatureStore.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)
//circle(410.52737 to 868.7676, id = "hobbit")
SchemeView(
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 {
var t = 0.0
while (isActive) {
val x = 410.52737 + t * (1132.0881 - 410.52737)
val y = 868.7676 + t * (394.99127 - 868.7676)
circle(x to y, id = "hobbit").color(Color.Green)
circle(x to y, color = Color.Green, id = hobbitId)
delay(100)
t += 0.005
if (t >= 1.0) t = 0.0
}
}
}
val initialViewPoint: ViewPoint<XY> = remember {
features.getBoundingBox(1f)?.computeViewPoint() ?: XYViewPoint(XY(0f, 0f))
}
var viewPoint: ViewPoint<XY> by remember { mutableStateOf(initialViewPoint) }
val painterCache = features.pointerCache()
val textMeasurer = rememberTextMeasurer()
ContextMenuArea(
items = {
listOf(
ContextMenuItem("Export to SVG") {
val path = Files.createTempFile("scheme-kt-", ".svg")
features.exportToSvg(viewPoint, painterCache, Size(800f, 800f), path)
println(path.toFile())
Desktop.getDesktop().browse(path.toFile().toURI())
},
ContextMenuItem("Export to PNG") {
val path = Files.createTempFile("scheme-kt-", ".png")
features.exportToPng(
viewPoint,
painterCache,
textMeasurer,
Size(800f, 800f),
path
)
println(path.toFile())
Desktop.getDesktop().browse(path.toFile().toURI())
}
)
}
) {
val mapState: XYCanvasState = XYCanvasState.remember(
ViewConfig(
onClick = { _, click ->
println("${click.focus.x}, ${click.focus.y}")
},
onViewChange = { viewPoint = this }
),
initialViewPoint = initialViewPoint,
)
SchemeView(
mapState,
features,
)
}
}
}
fun main() = application {
Window(title = "Scheme demo", onCloseRequest = ::exitApplication) {
Window(onCloseRequest = ::exitApplication) {
App()
}
}

View File

@@ -1,76 +0,0 @@
import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Face
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import space.kscience.maps.features.*
import space.kscience.maps.scheme.*
import space.kscience.maps.scheme.XYCoordinateSpace.Rectangle
fun main() = application {
Window(onCloseRequest = ::exitApplication, title = "Joker2023 demo", icon = painterResource("SPC-logo.png")) {
MaterialTheme {
SchemeView(
initialRectangle = Rectangle(XY(0f, 0f), XY(1734f, 724f)),
config = ViewConfig(
onClick = { _, pointer ->
println("(${pointer.focus.x}, ${pointer.focus.y})")
}
)
) {
background(1734f, 724f, id = "background") { painterResource("joker2023.png") }
group(id = "hall_1") {
polygon(
listOf(
XY(1582.0042, 210.29636),
XY(1433.7021, 127.79796),
XY(1370.7639, 127.79796),
XY(1315.293, 222.73865),
XY(1314.2262, 476.625),
XY(1364.3635, 570.4984),
XY(1434.7689, 570.4984),
XY(1579.8469, 493.69244),
)
).modifyAttributes {
ColorAttribute(Color.Blue)
AlphaAttribute(0.4f)
}.onClick {
println("hall_1")
}
}
group(id = "hall_2") {
rectanglePolygon(
left = 893, right = 1103,
bottom = 223, top = 406,
).modifyAttributes {
ColorAttribute(Color.Blue)
AlphaAttribute(0.4f)
}
}
group(id = "hall_3") {
rectanglePolygon(
Rectangle(XY(460f, 374f), width = 140f, height = 122f),
).modifyAttributes {
ColorAttribute(Color.Blue)
AlphaAttribute(0.4f)
}
}
group(id = "people") {
icon(XY(815.60535, 342.71313), Icons.Default.Face).color(Color.Red)
icon(XY(743.751, 381.09064), Icons.Default.Face).color(Color.Red)
icon(XY(1349.6648, 417.36014), Icons.Default.Face).color(Color.Red)
icon(XY (1362.4658, 287.21667), Icons.Default.Face).color(Color.Red)
icon(XY(208.24274, 317.08566), Icons.Default.Face).color(Color.Red)
icon(XY (293.5827, 319.21915), Icons.Default.Face).color(Color.Red)
}
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 968 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 KiB

View File

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

View File

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

View File

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

View File

@@ -1,205 +0,0 @@
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import space.kscience.kmath.geometry.Angle
import space.kscience.kmath.geometry.Vector2D
import space.kscience.kmath.geometry.euclidean2d.Circle2D
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
import space.kscience.maps.features.*
import space.kscience.maps.scheme.SchemeView
import space.kscience.maps.scheme.XY
import space.kscience.trajectory.*
import kotlin.random.Random
private fun Vector2D<out Number>.toXY() = XY(x.toFloat(), y.toFloat())
private val random = Random(123)
fun FeatureBuilder<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(Float64Space2D) {
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 FeatureBuilder<XY>.obstacle(obstacle: Obstacle, colorPicker: (Trajectory2D) -> Color = { Color.Red }) {
trajectory(obstacle.circumvention, colorPicker)
polygon(obstacle.arcs.map { it.center.toXY() }).color(Color.Gray)
}
fun FeatureBuilder<XY>.pose(pose2D: Pose2D) = with(Float64Space2D) {
line(pose2D.toXY(), (pose2D + Pose2D.bearingToVector(pose2D.bearing)).toXY())
}
@Composable
@Preview
fun closePoints() = with(Float64Space2D){
SchemeView {
val obstacle = Obstacle(
Circle2D(vector(0.0, 0.0), 1.0),
Circle2D(vector(0.0, 1.0), 1.0),
Circle2D(vector(1.0, 1.0), 1.0),
Circle2D(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(Float64Space2D.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() = with(Float64Space2D){
SchemeView {
val obstacles = arrayOf(
Obstacle(
Circle2D(vector(1.0, 6.5), 0.5),
Circle2D(vector(2.0, 1.0), 0.5),
Circle2D(vector(6.0, 0.0), 0.5),
Circle2D(vector(5.0, 5.0), 0.5)
), Obstacle(
Circle2D(vector(10.0, 1.0), 0.5),
Circle2D(vector(16.0, 0.0), 0.5),
Circle2D(vector(14.0, 6.0), 0.5),
Circle2D(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 singleElement() {
SchemeView {
points(listOf(XY(1f,1f)))
}
}
@Composable
@Preview
fun playground() {
val examples = listOf(
"Close starting points",
"Single obstacle",
"Two obstacles",
"Single element"
)
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()
examples[3] -> singleElement()
}
}
}
fun main() = application {
Window(title = "Trajectory-playground", onCloseRequest = ::exitApplication) {
MaterialTheme {
playground()
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 KiB

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
kotlin.code.style=official
org.gradle.jvmargs=-Xmx4096m
toolsVersion=0.15.4-kotlin-2.0.0
kotlin.version=1.6.10
compose.version=1.1.1
agp.version=4.2.2
android.useAndroidX=true

View File

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

File diff suppressed because it is too large Load Diff

0
gradlew vendored Executable file → Normal file
View File

View File

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

View File

@@ -1,45 +1,30 @@
import org.jetbrains.compose.compose
plugins {
id("space.kscience.gradle.mpp")
alias(spclibs.plugins.compose.compiler)
alias(spclibs.plugins.compose.jb)
// id("com.android.library")
kotlin("multiplatform")
id("org.jetbrains.compose")
`maven-publish`
}
kscience {
jvm()
wasm()
val ktorVersion: String by rootProject.extra
useCoroutines()
commonMain{
api(projects.mapsKtCore)
api(projects.mapsKtFeatures)
api(compose.foundation)
api(project.dependencies.platform(spclibs.ktor.bom))
kotlin {
explicitApi = org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode.Warning
jvm {
compilations.all {
kotlinOptions.jvmTarget = "11"
}
}
jvmMain{
api("io.ktor:ktor-client-cio")
sourceSets {
commonMain {
dependencies {
api(projects.mapsKtCore)
api(compose.foundation)
api("io.ktor:ktor-client-core:$ktorVersion")
api("io.github.microutils:kotlin-logging:2.1.23")
}
}
val jvmMain by getting
val jvmTest by getting
}
jvmTest{
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." }
}
//tasks.getByName<Copy>("downloadWix"){
// duplicatesStrategy = DuplicatesStrategy.WARN
//}
}

View File

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

View File

@@ -1,6 +1,4 @@
@file:Suppress("DEPRECATION")
package space.kscience.maps.compose
package center.sciprog.maps.compose
import kotlin.jvm.Synchronized

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,87 @@
package center.sciprog.maps.compose
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.DpSize
import center.sciprog.maps.coordinates.*
import kotlin.math.PI
import kotlin.math.log2
import kotlin.math.min
//TODO consider replacing by modifier
/**
* @param onDrag - returns true if you want to drag a map and false, if you want to make map stationary.
* start - is a point where drag begins, end is a point where drag ends
*/
public data class MapViewConfig(
val zoomSpeed: Double = 1.0 / 3.0,
val inferViewBoxFromFeatures: Boolean = false,
val onClick: MapViewPoint.() -> Unit = {},
val onDrag: (start: MapViewPoint, end: MapViewPoint) -> Boolean = { _, _ -> true },
val onViewChange: MapViewPoint.() -> Unit = {},
val onSelect: (GmcBox) -> Unit = {},
val onRelease: MapViewPoint.() -> Unit = {},
val zoomOnSelect: Boolean = true,
val resetViewPoint: Boolean = false,
)
@Composable
public expect fun MapView(
mapTileProvider: MapTileProvider,
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
features: Map<FeatureId, MapFeature>,
config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
)
@Composable
public fun MapView(
mapTileProvider: MapTileProvider,
initialViewPoint: MapViewPoint,
features: Map<FeatureId, MapFeature> = emptyMap(),
config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
) {
val featuresBuilder = MapFeatureBuilderImpl(features)
featuresBuilder.buildFeatures()
MapView(
mapTileProvider,
{ initialViewPoint },
featuresBuilder.build(),
config,
modifier
)
}
internal fun GmcBox.computeViewPoint(mapTileProvider: MapTileProvider): (canvasSize: DpSize) -> MapViewPoint = { canvasSize ->
val zoom = log2(
min(
canvasSize.width.value / width,
canvasSize.height.value / height
) * PI / mapTileProvider.tileSize
)
MapViewPoint(center, zoom)
}
@Composable
public fun MapView(
mapTileProvider: MapTileProvider,
box: GmcBox,
features: Map<FeatureId, MapFeature> = emptyMap(),
config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
buildFeatures: @Composable (MapFeatureBuilder.() -> Unit) = {},
) {
val featuresBuilder = MapFeatureBuilderImpl(features)
featuresBuilder.buildFeatures()
MapView(
mapTileProvider,
box.computeViewPoint(mapTileProvider),
featuresBuilder.build(),
config,
modifier
)
}

View File

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

View File

@@ -1,101 +0,0 @@
package space.kscience.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 space.kscience.kmath.geometry.radians
import space.kscience.maps.coordinates.Gmc
import space.kscience.maps.coordinates.MercatorProjection
import space.kscience.maps.coordinates.WebMercatorCoordinates
import space.kscience.maps.coordinates.WebMercatorProjection
import space.kscience.maps.features.*
import kotlin.math.*
public class MapCanvasState internal constructor(
public val mapTileProvider: MapTileProvider,
config: ViewConfig<Gmc>,
) : CanvasState<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.toRadians().value,
canvasSize.height.value / rectangle.latitudeDelta.toRadians().value
) * 2 * PI / mapTileProvider.tileSize
).coerceIn(0.0..22.0)
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,
): MapCanvasState = remember {
MapCanvasState(mapTileProvider, config).apply {
if (initialViewPoint != null) {
viewPoint = initialViewPoint
} else if (initialRectangle != null) {
viewPoint = computeViewPoint(initialRectangle)
}
}
}
}
}

View File

@@ -1,149 +0,0 @@
package space.kscience.maps.compose
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.drawscope.clipRect
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 io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import org.jetbrains.skia.Image
import space.kscience.maps.coordinates.Gmc
import space.kscience.maps.features.*
import kotlin.math.ceil
import kotlin.math.pow
private fun IntRange.intersect(other: IntRange) = kotlin.math.max(first, other.first)..kotlin.math.min(last, other.last)
private val logger = KotlinLogging.logger("MapView")
/**
* A component that renders map and provides basic map manipulation capabilities
*/
@Composable
public fun MapView(
mapState: MapCanvasState,
mapTileProvider: MapTileProvider,
featureStore: FeatureStore<Gmc>,
modifier: Modifier,
) {
val mapTiles = remember(mapTileProvider) {
mutableStateMapOf<TileId, Image>()
}
with(mapState) {
// 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
if (ex !is CancellationException) {
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)
}
}
}
}
}
}
FeatureCanvas(mapState, featureStore.featureFlow, modifier = modifier.canvasControls(mapState, featureStore)) {
val tileScale = mapState.tileScale
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(
(mapState.canvasSize.width / 2 + (mapTileProvider.toCoordinate(id.i).dp - mapState.centerCoordinates.x.dp) * tileScale).roundToPx(),
(mapState.canvasSize.height / 2 + (mapTileProvider.toCoordinate(id.j).dp - mapState.centerCoordinates.y.dp) * tileScale).roundToPx()
)
drawImage(
image = image.toComposeImageBitmap(),
dstOffset = offset,
dstSize = tileSize
)
}
}
}
}
/**
* Create a [MapView] with given [featureStore] group.
*/
@Composable
public fun MapView(
mapTileProvider: MapTileProvider,
config: ViewConfig<Gmc>,
featureStore: FeatureStore<Gmc>,
initialViewPoint: ViewPoint<Gmc>? = null,
initialRectangle: Rectangle<Gmc>? = null,
modifier: Modifier = Modifier.fillMaxSize(),
) {
val mapState = MapCanvasState.remember(mapTileProvider, config, initialViewPoint, initialRectangle)
MapView(mapState, mapTileProvider, featureStore, modifier)
}
/**
* Draw a map using convenient parameters. If neither [initialViewPoint], noe [initialRectangle] is defined,
* use map features to infer the 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,
config: ViewConfig<Gmc> = ViewConfig(),
initialViewPoint: ViewPoint<Gmc>? = null,
initialRectangle: Rectangle<Gmc>? = null,
modifier: Modifier = Modifier.fillMaxSize(),
buildFeatures: FeatureStore<Gmc>.() -> Unit = {},
) {
val featureState = FeatureStore.remember(WebMercatorSpace, buildFeatures)
val computedRectangle = initialRectangle ?: featureState.getBoundingBox()
MapView(mapTileProvider, config, featureState, initialViewPoint, computedRectangle, modifier)
}

View File

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

View File

@@ -1,143 +0,0 @@
package space.kscience.maps.compose
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import space.kscience.kmath.geometry.Angle
import space.kscience.kmath.geometry.radians
import space.kscience.maps.coordinates.*
import space.kscience.maps.features.CoordinateSpace
import space.kscience.maps.features.Rectangle
import space.kscience.maps.features.ViewPoint
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> =
space.kscience.maps.compose.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 space.kscience.maps.compose.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 space.kscience.maps.compose.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 space.kscience.maps.compose.GmcRectangle(a, b)
}

View File

@@ -1,173 +0,0 @@
package space.kscience.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 org.jetbrains.skia.Font
import space.kscience.kmath.geometry.Angle
import space.kscience.maps.coordinates.*
import space.kscience.maps.features.*
import kotlin.math.ceil
internal fun FeatureBuilder<Gmc>.coordinatesOf(pair: Pair<Number, Number>) =
GeodeticMapCoordinates.ofDegrees(pair.first.toDouble(), pair.second.toDouble())
public typealias MapFeature = Feature<Gmc>
public fun FeatureBuilder<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 FeatureBuilder<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 FeatureBuilder<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 FeatureBuilder<Gmc>.line(
curve: GmcCurve,
id: String? = null,
): FeatureRef<Gmc, LineFeature<Gmc>> = feature(
id,
LineFeature(space, curve.forward.coordinates, curve.backward.coordinates)
)
/**
* A segmented geodetic curve
*/
public fun FeatureBuilder<Gmc>.geodeticLine(
curve: GmcCurve,
ellipsoid: GeoEllipsoid = GeoEllipsoid.WGS84,
maxLineDistance: Distance = 100.kilometers,
id: String? = null,
): FeatureRef<Gmc, Feature<Gmc>> = if (curve.distance < maxLineDistance) {
feature(
id,
LineFeature(space, curve.forward.coordinates, curve.backward.coordinates)
)
} else {
val segments = ceil(curve.distance / maxLineDistance).toInt()
val segmentSize = curve.distance / segments
val points = buildList<GmcPose> {
add(curve.forward)
repeat(segments) {
val segment = ellipsoid.curveInDirection(this.last(), segmentSize, 1e-2)
add(segment.backward)
}
}
multiLine(points.map { it.coordinates }, id = id)
}
public fun FeatureBuilder<Gmc>.geodeticLine(
from: Gmc,
to: Gmc,
ellipsoid: GeoEllipsoid = GeoEllipsoid.WGS84,
maxLineDistance: Distance = 100.kilometers,
id: String? = null,
): FeatureRef<Gmc, Feature<Gmc>> = geodeticLine(ellipsoid.curveBetween(from, to), ellipsoid, maxLineDistance, id)
public fun FeatureBuilder<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 FeatureBuilder<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 FeatureBuilder<Gmc>.points(
points: List<Pair<Double, Double>>,
id: String? = null,
): FeatureRef<Gmc, PointsFeature<Gmc>> = feature(id, PointsFeature(space, points.map(::coordinatesOf)))
public fun FeatureBuilder<Gmc>.multiLine(
points: List<Pair<Double, Double>>,
id: String? = null,
): FeatureRef<Gmc, MultiLineFeature<Gmc>> = feature(id, MultiLineFeature(space, points.map(::coordinatesOf)))
public fun FeatureBuilder<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 FeatureBuilder<Gmc>.text(
position: Pair<Double, Double>,
text: String,
font: Font.() -> Unit = { size = 16f },
id: String? = null,
): FeatureRef<Gmc, TextFeature<Gmc>> = feature(
id,
TextFeature(space, coordinatesOf(position), text, fontConfig = font)
)
public fun FeatureBuilder<Gmc>.pixelMap(
rectangle: Rectangle<Gmc>,
latitudeDelta: Angle,
longitudeDelta: Angle,
id: String? = null,
builder: (Gmc) -> Color?,
): FeatureRef<Gmc, PixelMapFeature<Gmc>> = feature(
id,
PixelMapFeature(
space,
rectangle,
Structure2D(
ceil(rectangle.longitudeDelta / latitudeDelta).toInt(),
ceil(rectangle.latitudeDelta / longitudeDelta).toInt()
) { (i, j) ->
val longitude = rectangle.left + longitudeDelta * i
val latitude = rectangle.bottom + latitudeDelta * j
builder(
Gmc(latitude, longitude)
)
}
)
)

View File

@@ -1,91 +0,0 @@
package center.sciprog.maps.compose
import io.github.oshai.kotlinlogging.KotlinLogging
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.client.statement.readBytes
import io.ktor.http.Url
import io.ktor.util.decodeBase64Bytes
import io.ktor.util.encodeBase64
import kotlinx.browser.window
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import org.jetbrains.skia.Image
import org.w3c.dom.Storage
/**
* A [MapTileProvider] based on Open Street Map API. With in-memory and file cache
*/
public class OpenStreetMapTileProvider(
private val client: HttpClient,
private val storage: Storage = window.localStorage,
parallelism: Int = 4,
cacheCapacity: Int = 200,
private val osmBaseUrl: String = "https://tile.openstreetmap.org",
) : MapTileProvider {
private val semaphore = Semaphore(parallelism)
private val cache = LruCache<TileId, Deferred<Image>>(cacheCapacity)
private fun TileId.osmUrl() = Url("$osmBaseUrl/${zoom}/${i}/${j}.png")
private fun TileId.imageName() = "${zoom}/${i}/${j}.png"
private fun TileId.readImage() = storage.getItem(imageName())
/**
* Download and cache the tile image
*/
private fun CoroutineScope.downloadImageAsync(id: TileId): Deferred<Image> = async {
id.readImage()?.let { imageString ->
try {
return@async Image.makeFromEncoded(imageString.decodeBase64Bytes())
} catch (ex: Exception) {
logger.debug { "Failed to load image from $imageString" }
storage.removeItem(id.imageName())
}
}
//semaphore works only for actual download
semaphore.withPermit {
val url = id.osmUrl()
val byteArray = client.get(url).readBytes()
logger.debug { "Finished downloading map tile with id $id from $url" }
val imageName = id.imageName()
logger.debug { "Caching map tile $id to $imageName" }
storage.setItem(imageName, byteArray.encodeBase64())
Image.makeFromEncoded(byteArray)
}
}
override fun CoroutineScope.loadTileAsync(
tileId: TileId,
): Deferred<MapTile> {
//start image download
val imageDeferred: Deferred<Image> = cache.getOrPut(tileId) {
downloadImageAsync(tileId)
}
//collect the result asynchronously
return async {
val image: Image = runCatching { imageDeferred.await() }.onFailure {
if (it !is CancellationException) {
logger.error(it) { "Failed to load tile image with id=$tileId" }
}
cache.remove(tileId)
}.getOrThrow()
MapTile(tileId, image)
}
}
public companion object {
private val logger = KotlinLogging.logger("OpenStreetMapCache")
}
}

View File

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

View File

@@ -0,0 +1,345 @@
package center.sciprog.maps.compose
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.drag
import androidx.compose.foundation.gestures.forEachGesture
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.drawscope.*
import androidx.compose.ui.input.pointer.*
import androidx.compose.ui.unit.*
import center.sciprog.maps.coordinates.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
import mu.KotlinLogging
import 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.ofRadians(
(focus.latitude + deltaY / scaleFactor).coerceIn(
-MercatorProjection.MAXIMUM_LATITUDE,
MercatorProjection.MAXIMUM_LATITUDE
),
focus.longitude + deltaX / scaleFactor
)
return MapViewPoint(newCoordinates, zoom)
}
private val logger = KotlinLogging.logger("MapView")
/**
* A component that renders map and provides basic map manipulation capabilities
*/
@Composable
public actual fun MapView(
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)?.let { box ->
val zoom = log2(
min(
canvasSize.width.value / box.width,
canvasSize.height.value / box.height
) * PI / mapTileProvider.tileSize
)
MapViewPoint(box.center, zoom)
} ?: computeViewPoint(canvasSize)
} else {
computeViewPoint(canvasSize)
}
}
val zoom: Int by derivedStateOf { 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 ->
if (event.buttons.isPrimaryPressed) {
//Evaluating selection frame
if (event.keyboardModifiers.isShiftPressed) {
selectRect = Rect(change.position, change.position)
drag(change.id) { dragChange ->
selectRect?.let { rect ->
val offset = dragChange.position
selectRect = Rect(
min(offset.x, rect.left),
min(offset.y, rect.top),
max(offset.x, rect.right),
max(offset.y, rect.bottom)
)
}
}
selectRect?.let { rect ->
//Use selection override if it is defined
val gmcBox = GmcBox(
rect.topLeft.toDpOffset().toGeodetic(),
rect.bottomRight.toDpOffset().toGeodetic()
)
config.onSelect(gmcBox)
if (config.zoomOnSelect) {
val newViewPoint = gmcBox.computeViewPoint(mapTileProvider).invoke(canvasSize)
config.onViewChange(newViewPoint)
viewPointInternal = newViewPoint
}
selectRect = null
}
} else {
drag(change.id) { dragChange ->
val dragAmount = dragChange.position - dragChange.previousPosition
val dpStart =
DpOffset(dragChange.previousPosition.x.toDp(), dragChange.previousPosition.y.toDp())
val dpEnd = DpOffset(dragChange.position.x.toDp(), dragChange.position.y.toDp())
if (!config.onDrag(
MapViewPoint(dpStart.toGeodetic(), viewPoint.zoom),
MapViewPoint(dpEnd.toGeodetic(), viewPoint.zoom)
)
) return@drag
val newViewPoint = viewPoint.move(
-dragAmount.x.toDp().value / tileScale,
+dragAmount.y.toDp().value / tileScale
)
config.onViewChange(newViewPoint)
viewPointInternal = newViewPoint
}
}
}
}
}
}
}.onPointerEvent(PointerEventType.Scroll) {
val change = it.changes.first()
val (xPos, yPos) = change.position
//compute invariant point of translation
val invariant = DpOffset(xPos.toDp(), yPos.toDp()).toGeodetic()
val newViewPoint = viewPoint.zoom(-change.scrollDelta.y.toDouble() * config.zoomSpeed, invariant)
config.onViewChange(newViewPoint)
viewPointInternal = newViewPoint
}.onPointerEvent(PointerEventType.Release) {
val change = it.changes.first()
val (xPos, yPos) = change.position
val dpOffset = DpOffset(xPos.toDp(), yPos.toDp())
config.onRelease(MapViewPoint(dpOffset.toGeodetic(), viewPoint.zoom))
}.onPointerEvent(PointerEventType.Press) {
val dragStart = it.changes.first().position
val dpPos = DpOffset(dragStart.x.toDp(), dragStart.y.toDp())
config.onClick(MapViewPoint(dpPos.toGeodetic(), viewPoint.zoom))
}.fillMaxSize()
// Load tiles asynchronously
LaunchedEffect(viewPoint, canvasSize) {
with(mapTileProvider) {
val indexRange = 0 until 2.0.pow(zoom).toInt()
val left = centerCoordinates.x - canvasSize.width.value / 2 / tileScale
val right = centerCoordinates.x + canvasSize.width.value / 2 / tileScale
val horizontalIndices: IntRange = (toIndex(left)..toIndex(right)).intersect(indexRange)
val top = (centerCoordinates.y + canvasSize.height.value / 2 / tileScale)
val bottom = (centerCoordinates.y - canvasSize.height.value / 2 / tileScale)
val verticalIndices: IntRange = (toIndex(bottom)..toIndex(top)).intersect(indexRange)
mapTiles.clear()
for (j in verticalIndices) {
for (i in horizontalIndices) {
val id = TileId(zoom, i, j)
//start all
val deferred = loadTileAsync(id)
//wait asynchronously for it to finish
launch {
try {
mapTiles += deferred.await()
} catch (ex: Exception) {
if (ex !is CancellationException) {
//displaying the error is maps responsibility
logger.error(ex) { "Failed to load tile with id=$id" }
}
}
}
}
}
}
}
Canvas(canvasModifier) {
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)
)
)
}
}
}

View File

@@ -1,12 +1,18 @@
package space.kscience.maps.compose
package center.sciprog.maps.compose
import io.github.oshai.kotlinlogging.KotlinLogging
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toComposeImageBitmap
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.client.statement.readBytes
import kotlinx.coroutines.*
import io.ktor.utils.io.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import mu.KotlinLogging
import org.jetbrains.skia.Image
import java.net.URL
import java.nio.file.Path
@@ -18,26 +24,25 @@ import kotlin.io.path.*
public class OpenStreetMapTileProvider(
private val client: HttpClient,
private val cacheDirectory: Path,
parallelism: Int = 4,
parallelism: Int = 1,
cacheCapacity: Int = 200,
private val osmBaseUrl: String = "https://tile.openstreetmap.org",
) : MapTileProvider {
private val semaphore = Semaphore(parallelism)
private val cache = LruCache<TileId, Deferred<Image>>(cacheCapacity)
private val cache = LruCache<TileId, Deferred<ImageBitmap>>(cacheCapacity)
private fun TileId.osmUrl() = URL("$osmBaseUrl/${zoom}/${i}/${j}.png")
private fun TileId.osmUrl() = URL("https://tile.openstreetmap.org/${zoom}/${i}/${j}.png")
private fun TileId.cacheFilePath() = cacheDirectory.resolve("${zoom}/${i}/${j}.png")
/**
* Download and cache the tile image
*/
private fun CoroutineScope.downloadImageAsync(id: TileId): Deferred<Image> = async(Dispatchers.IO) {
private fun CoroutineScope.downloadImageAsync(id: TileId) = async(Dispatchers.IO) {
id.cacheFilePath()?.let { path ->
if (path.exists()) {
try {
return@async Image.makeFromEncoded(path.readBytes())
return@async Image.makeFromEncoded(path.readBytes()).toComposeImageBitmap()
} catch (ex: Exception) {
logger.debug { "Failed to load image from $path" }
path.deleteIfExists()
@@ -49,7 +54,9 @@ public class OpenStreetMapTileProvider(
semaphore.withPermit {
val url = id.osmUrl()
val byteArray = client.get(url).readBytes()
logger.debug { "Finished downloading map tile with id $id from $url" }
id.cacheFilePath()?.let { path ->
logger.debug { "Caching map tile $id to $path" }
@@ -57,7 +64,7 @@ public class OpenStreetMapTileProvider(
path.writeBytes(byteArray)
}
Image.makeFromEncoded(byteArray)
Image.makeFromEncoded(byteArray).toComposeImageBitmap()
}
}
@@ -66,19 +73,21 @@ public class OpenStreetMapTileProvider(
): Deferred<MapTile> {
//start image download
val imageDeferred: Deferred<Image> = cache.getOrPut(tileId) {
val imageDeferred = cache.getOrPut(tileId) {
downloadImageAsync(tileId)
}
//collect the result asynchronously
return async {
val image: Image = runCatching { imageDeferred.await() }.onFailure {
if(it !is CancellationException) {
logger.error(it) { "Failed to load tile image with id=$tileId" }
}
val image = try {
imageDeferred.await()
} catch (ex: Exception) {
cache.remove(tileId)
}.getOrThrow()
if(ex !is CancellationException) {
logger.error(ex) { "Failed to load tile image with id=$tileId" }
}
throw ex
}
MapTile(tileId, image)
}
}
@@ -87,4 +96,4 @@ public class OpenStreetMapTileProvider(
public companion object {
private val logger = KotlinLogging.logger("OpenStreetMapCache")
}
}
}

View File

@@ -1,55 +0,0 @@
package space.kscience.maps.compose
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.asSkiaBitmap
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.text.TextMeasurer
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import org.jetbrains.skia.Image
import org.jfree.svg.SVGUtils
import space.kscience.maps.coordinates.Gmc
import space.kscience.maps.features.FeatureSet
import space.kscience.maps.features.PainterFeature
import space.kscience.maps.features.ViewConfig
import space.kscience.maps.features.ViewPoint
import space.kscience.maps.svg.generateBitmap
import space.kscience.maps.svg.generateSvg
import java.nio.file.Path
import kotlin.io.path.writeBytes
public fun FeatureSet<Gmc>.exportToSvg(
mapTileProvider: MapTileProvider,
viewPoint: ViewPoint<Gmc>,
painterCache: Map<PainterFeature<Gmc>, Painter>,
size: Size,
path: Path,
) {
val mapCanvasState: MapCanvasState = MapCanvasState(mapTileProvider, ViewConfig()).apply {
this.viewPoint = viewPoint
this.canvasSize = DpSize(size.width.dp, size.height.dp)
}
val svgString: String = generateSvg(mapCanvasState, painterCache)
SVGUtils.writeToSVG(path.toFile(), svgString)
}
public fun FeatureSet<Gmc>.exportToPng(
mapTileProvider: MapTileProvider,
viewPoint: ViewPoint<Gmc>,
painterCache: Map<PainterFeature<Gmc>, Painter>,
textMeasurer: TextMeasurer,
size: Size,
path: Path,
) {
val mapCanvasState: MapCanvasState = MapCanvasState(mapTileProvider, ViewConfig()).apply {
this.viewPoint = viewPoint
this.canvasSize = DpSize(size.width.dp, size.height.dp)
}
val bitmap = generateBitmap(mapCanvasState, painterCache, textMeasurer, size)
Image.makeFromBitmap(bitmap.asSkiaBitmap()).encodeToData()?.bytes?.let {
path.writeBytes(it)
}
}

View File

@@ -1,43 +0,0 @@
package space.kscience.maps.compose
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import java.nio.file.Files
import kotlin.test.assertFails
class OsmTileProviderTest {
// @get:Rule
// val rule = createComposeRule()
@Test
fun testCorrectOsm() = runTest {
val provider = OpenStreetMapTileProvider(HttpClient(CIO), Files.createTempDirectory("mapCache"))
val tileId = TileId(3, 1, 1)
with(provider) {
loadTileAsync(tileId).await()
}
}
@Test
fun testFailedOsm() = runTest {
val provider = OpenStreetMapTileProvider(
HttpClient(CIO),
Files.createTempDirectory("mapCache"),
osmBaseUrl = "https://tile.openstreetmap1.org"
)
val tileId = TileId(3, 1, 1)
supervisorScope {
with(provider) {
val deferred = loadTileAsync(tileId)
assertFails {
deferred.await()
}
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,62 @@
package center.sciprog.maps.coordinates
import kotlin.math.PI
/**
* Geodetic coordinated
*/
public class GeodeticMapCoordinates private constructor(
public val latitude: Double,
public val longitude: Double,
){
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as GeodeticMapCoordinates
if (latitude != other.latitude) return false
if (longitude != other.longitude) return false
return true
}
override fun hashCode(): Int {
var result = latitude.hashCode()
result = 31 * result + longitude.hashCode()
return result
}
override fun toString(): String {
return "GeodeticCoordinates(latitude=${latitude / PI * 180} deg, longitude=${longitude / PI * 180} deg)"
}
public companion object {
public fun ofRadians(latitude: Double, longitude: Double): GeodeticMapCoordinates {
require(latitude in (-PI/2)..(PI/2)) { "Latitude $latitude is not in (-PI/2)..(PI/2)" }
return GeodeticMapCoordinates(latitude, longitude.rem(PI / 2))
}
public fun ofDegrees(latitude: Double, longitude: Double): GeodeticMapCoordinates {
require(latitude in (-90.0)..(90.0)) { "Latitude $latitude is not in -90..90" }
return GeodeticMapCoordinates(latitude * PI / 180, (longitude.rem(180) * PI / 180))
}
}
}
//public interface GeoToScreenConversion {
// public fun getScreenX(gmc: GeodeticMapCoordinates): Double
// public fun getScreenY(gmc: GeodeticMapCoordinates): Double
//
// public fun invalidationFlow(): Flow<Unit>
//}
//
//public interface ScreenMapCoordinates {
// public val gmc: GeodeticMapCoordinates
// public val converter: GeoToScreenConversion
//
// public val x: Double get() = converter.getScreenX(gmc)
// public val y: Double get() = converter.getScreenX(gmc)
//}

View File

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

View File

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

View File

@@ -0,0 +1,55 @@
/*
* 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.math.*
public data class MercatorCoordinates(val x: Double, val y: Double)
/**
* @param baseLongitude the longitude offset in radians
* @param radius the average radius of the Earth
* @param correctedRadius optional radius correction to account for ellipsoid model
*/
public open class MercatorProjection(
public val baseLongitude: Double = 0.0,
protected val radius: Double = DEFAULT_EARTH_RADIUS,
private val correctedRadius: ((GeodeticMapCoordinates) -> Double)? = null,
) {
public fun toGeodetic(mc: MercatorCoordinates): GeodeticMapCoordinates {
val res = GeodeticMapCoordinates.ofRadians(
atan(sinh(mc.y / radius)),
baseLongitude + mc.x / radius,
)
return if (correctedRadius != null) {
val r = correctedRadius.invoke(res)
GeodeticMapCoordinates.ofRadians(
atan(sinh(mc.y / r)),
baseLongitude + mc.x / r,
)
} else {
res
}
}
/**
* https://en.wikipedia.org/wiki/Web_Mercator_projection#Formulas
*/
public fun toMercator(gmc: GeodeticMapCoordinates): MercatorCoordinates {
require(abs(gmc.latitude) <= MAXIMUM_LATITUDE) { "Latitude exceeds the maximum latitude for mercator coordinates" }
val r = correctedRadius?.invoke(gmc) ?: radius
return MercatorCoordinates(
x = r * (gmc.longitude - baseLongitude),
y = r * ln(tan(PI / 4 + gmc.latitude / 2))
)
}
public companion object : MercatorProjection(0.0, 6378137.0) {
public const val MAXIMUM_LATITUDE: Double = 85.05113
public val DEFAULT_EARTH_RADIUS: Double = radius
}
}

View File

@@ -3,22 +3,21 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package space.kscience.maps.coordinates
package center.sciprog.maps.coordinates
import space.kscience.kmath.geometry.abs
import kotlin.math.*
public data class WebMercatorCoordinates(val zoom: Int, val x: Float, val y: Float)
public data class WebMercatorCoordinates(val zoom: Int, val x: Double, val y: Double)
public object WebMercatorProjection {
public object WebMercatorProjection {
/**
* Compute radians to projection coordinates ratio for given [zoom] factor
*/
public fun scaleFactor(zoom: Float): Float = (256.0 / 2 / PI * 2f.pow(zoom)).toFloat()
public fun scaleFactor(zoom: Double): Double = 256.0 / 2 / PI * 2.0.pow(zoom)
public fun toGeodetic(mercator: WebMercatorCoordinates): GeodeticMapCoordinates {
val scaleFactor = scaleFactor(mercator.zoom.toFloat())
val scaleFactor = scaleFactor(mercator.zoom.toDouble())
val longitude = mercator.x / scaleFactor - PI
val latitude = (atan(exp(PI - mercator.y / scaleFactor)) - PI / 4) * 2
return GeodeticMapCoordinates.ofRadians(latitude, longitude)
@@ -26,17 +25,15 @@ public object WebMercatorProjection {
/**
* 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? {
if (abs(gmc.latitude) > MercatorProjection.MAXIMUM_LATITUDE) return null
public fun toMercator(gmc: GeodeticMapCoordinates, zoom: Int): WebMercatorCoordinates {
require(abs(gmc.latitude) <= MercatorProjection.MAXIMUM_LATITUDE) { "Latitude exceeds the maximum latitude for mercator coordinates" }
val scaleFactor = scaleFactor(zoom.toFloat())
val scaleFactor = scaleFactor(zoom.toDouble())
return WebMercatorCoordinates(
zoom = zoom,
x = scaleFactor * (gmc.longitude.toRadians().value + PI).toFloat(),
y = scaleFactor * (PI - ln(tan(PI / 4 + gmc.latitude.toRadians().value / 2))).toFloat()
x = scaleFactor * (gmc.longitude + PI),
y = scaleFactor * (PI - ln(tan(PI / 4 + gmc.latitude / 2)))
)
}

View File

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

View File

@@ -1,91 +0,0 @@
package space.kscience.maps.coordinates
import kotlinx.serialization.Serializable
import space.kscience.kmath.geometry.*
/**
* Geodetic coordinated
*
* @param elevation is optional
*/
@Serializable
public class GeodeticMapCoordinates(
public val latitude: Angle,
public val longitude: Angle,
public val elevation: Distance? = null,
) : Vector2D<Angle> {
init {
require(latitude in (-Angle.piDiv2)..(Angle.piDiv2)) {
"Latitude $latitude is not in (-PI/2)..(PI/2)"
}
require(longitude in (-Angle.pi..Angle.pi)) {
"Longitude $longitude is not in (-PI..PI) range"
}
}
override val x: Angle get() = longitude
override val y: Angle get() = latitude
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as GeodeticMapCoordinates
return latitude == other.latitude && longitude == other.longitude
}
override fun hashCode(): Int {
var result = latitude.hashCode()
result = 31 * result + longitude.hashCode()
return result
}
override fun toString(): String {
return "GMC(latitude=${latitude.toDegrees().value} deg, longitude=${longitude.toDegrees().value} deg)"
}
public companion object {
public fun normalized(
latitude: Angle,
longitude: Angle,
elevation: Distance? = null,
): GeodeticMapCoordinates = GeodeticMapCoordinates(
latitude.coerceIn(-Angle.piDiv2..Angle.piDiv2), longitude.normalized(Angle.zero), elevation
)
public fun ofRadians(
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
*/
public typealias Gmc = GeodeticMapCoordinates
//public interface GeoToScreenConversion {
// public fun getScreenX(gmc: GeodeticMapCoordinates): Double
// public fun getScreenY(gmc: GeodeticMapCoordinates): Double
//
// public fun invalidationFlow(): Flow<Unit>
//}
//
//public interface ScreenMapCoordinates {
// public val gmc: GeodeticMapCoordinates
// public val converter: GeoToScreenConversion
//
// public val x: Double get() = converter.getScreenX(gmc)
// public val y: Double get() = converter.getScreenX(gmc)
//}

View File

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

View File

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

View File

@@ -1,96 +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 space.kscience.maps.coordinates
import kotlinx.serialization.Serializable
import space.kscience.kmath.geometry.*
import kotlin.math.*
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()
}
}
/**
* @param baseLongitude the longitude offset in radians
* @param ellipsoid - a [GeoEllipsoid] to be used for conversion
*/
@Serializable
public open class MercatorProjection(
public val baseLongitude: Angle = Angle.zero,
public val ellipsoid: GeoEllipsoid = GeoEllipsoid.sphere,
) : MapProjection<ProjectionCoordinates> {
/**
* Taken from https://github.com/geotools/geotools/blob/main/modules/library/referencing/src/main/java/org/geotools/referencing/operation/projection/Mercator.java#L164
*/
private fun cphi2(ts: Double): Double {
val eccnth: Double = 0.5 * ellipsoid.eccentricity
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(
atan(sinh(pc.y / ellipsoid.equatorRadius)),
baseLongitude.toRadians().value + (pc.x / ellipsoid.equatorRadius),
)
} else {
GeodeticMapCoordinates.ofRadians(
cphi2(exp(-(pc.y / ellipsoid.equatorRadius))),
baseLongitude.toRadians().value + (pc.x / ellipsoid.equatorRadius)
)
}
}
/**
* https://en.wikipedia.org/wiki/Web_Mercator_projection#Formulas
*/
override fun toProjection(gmc: GeodeticMapCoordinates): ProjectionCoordinates {
require(abs(gmc.latitude) <= MAXIMUM_LATITUDE) { "Latitude exceeds the maximum latitude for mercator coordinates" }
val e = sqrt(ellipsoid.eSquared)
return if (ellipsoid === GeoEllipsoid.sphere) {
ProjectionCoordinates(
x = ellipsoid.equatorRadius * (gmc.longitude - baseLongitude).toRadians().value,
y = ellipsoid.equatorRadius * ln(tan(Angle.pi / 4 + gmc.latitude / 2))
)
} else {
val sinPhi = sin(gmc.latitude)
ProjectionCoordinates(
x = ellipsoid.equatorRadius * (gmc.longitude - baseLongitude).toRadians().value,
y = ellipsoid.equatorRadius * ln(
tan(Angle.pi / 4 + gmc.latitude / 2) * ((1 - e * sinPhi) / (1 + e * sinPhi)).pow(e / 2)
)
)
}
}
public companion object {
public val MAXIMUM_LATITUDE: Angle = 85.05113.degrees
}
}

View File

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

View File

@@ -1,28 +0,0 @@
package space.kscience.maps.coordinates
import kotlin.test.Test
import kotlin.test.assertEquals
class MercatorTest {
@Test
fun sphereForwardBackward(){
val moscow = GeodeticMapCoordinates.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.toDegrees().value, backwards.latitude.toDegrees().value, 1e-6)
assertEquals(moscow.longitude.toDegrees().value, backwards.longitude.toDegrees().value, 1e-6)
}
@Test
fun ellipseForwardBackward(){
val moscow = GeodeticMapCoordinates.ofDegrees(55.76058287719673, 37.60358622841869)
val projection = MercatorProjection(ellipsoid = GeoEllipsoid.WGS84)
val mercator = projection.toProjection(moscow)
val backwards = projection.toGeodetic(mercator)
assertEquals(moscow.latitude.toDegrees().value, backwards.latitude.toDegrees().value, 1e-6)
assertEquals(moscow.longitude.toDegrees().value, backwards.longitude.toDegrees().value, 1e-6)
}
}

View File

@@ -1,21 +0,0 @@
# Module maps-kt-features
## Usage
## Artifact:
The Maven coordinates of this project are `space.kscience:maps-kt-features:0.3.0`.
**Gradle Kotlin DSL:**
```kotlin
repositories {
maven("https://repo.kotlin.link")
mavenCentral()
}
dependencies {
implementation("space.kscience:maps-kt-features:0.3.0")
}
```

View File

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

View File

@@ -1,44 +0,0 @@
plugins {
id("space.kscience.gradle.mpp")
alias(spclibs.plugins.compose.compiler)
alias(spclibs.plugins.compose.jb)
`maven-publish`
}
val kmathVersion: String by rootProject.extra
kscience {
jvm()
// js()
wasm{
browser {
testTask {
enabled = false
}
}
}
useCoroutines()
useSerialization {
json()
}
useSerialization(sourceSet = space.kscience.gradle.DependencySourceSet.TEST) {
protobuf()
}
commonMain{
api(projects.trajectoryKt)
api(compose.runtime)
api(compose.foundation)
api(compose.material)
api(compose.ui)
api("io.github.oshai:kotlin-logging:6.0.3")
api("com.benasher44:uuid:0.8.4")
}
jvmMain{
api("org.jfree:org.jfree.svg:5.0.4")
}
}

View File

@@ -1,189 +0,0 @@
package space.kscience.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 space.kscience.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.canvasControls(
state: CanvasState<T>,
features: FeatureStore<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 = toCoordinates(event.changes.first().position, this)
val point = state.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 (viewConfig.zoomOnDoubleClick) {
{ event ->
val invariant = toCoordinates(event.position, 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 = toCoordinates(event.position, this)
val point = space.ViewPoint(coordinates, zoom)
viewConfig.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 * viewConfig.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(
toCoordinates(dragChange.previousPosition, this),
zoom
)
val dragEnd = space.ViewPoint(
toCoordinates(dragChange.position, this),
zoom
)
val dragResult = viewConfig.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 the 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()
)
viewConfig.onSelect(coordinateRect)
if (viewConfig.zoomOnSelect) {
viewPoint = computeViewPoint(coordinateRect)
}
selectRect = null
}
}
}
}
}
}
/*
.pointerInput(Unit) {
allTapable.forEach { (feature, listeners) ->
listeners.forEach { listener ->
detectTapGestures(listener.pointerMatcher, listener.keyboardFilter) { offset ->
val point = space.ViewPoint(offset.toCoordinates(this@pointerInput), zoom)
if (point in feature as DomainFeature) {
listener.onTap(point)
}
}
}
}
}
*/

View File

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

View File

@@ -1,66 +0,0 @@
package space.kscience.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.*
/**
* A state holder for current canvas size and view point. Allows transformation from coordinates to pixels and back
*/
public abstract class CanvasState<T: Any>(
public val viewConfig: 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
viewConfig.onCanvasSizeChange(value)
}
public var viewPoint: ViewPoint<T>
get() = viewPointState.value ?: space.defaultViewPoint
set(value) {
viewPointState.value = value
viewConfig.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 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 abstract fun DpOffset.toCoordinates(): T
public abstract fun T.toDpOffset(): DpOffset
public fun toCoordinates(offset: Offset, density: Density): T = with(density){
val dpOffset = DpOffset(offset.x.toDp(), offset.y.toDp())
dpOffset.toCoordinates()
}
public fun toOffset(coordinates: T, density: Density): Offset = with(density){
val dpOffset = coordinates.toDpOffset()
return Offset(dpOffset.x.toPx(), dpOffset.y.toPx())
}
}
public val DpRect.topLeft: DpOffset get() = DpOffset(left, top)
public val DpRect.bottomRight: DpOffset get() = DpOffset(right, bottom)

View File

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

View File

@@ -1,56 +0,0 @@
package space.kscience.maps.features
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.isPrimaryPressed
/**
* @param result - the endpoint of the drag to perform constrained drag
* @param handleNext - if false do not evaluate subsequent drag handles
*/
public data class DragResult<T : Any>(val result: ViewPoint<T>, val handleNext: Boolean = true)
public fun interface DragListener<in T : Any> {
public fun handle(event: PointerEvent, from: ViewPoint<T>, to: ViewPoint<T>)
}
public fun interface DragHandle<T : Any> {
/**
* @param event - qualifiers of the event used for drag
* @param start - is a point where drag begins, end is a point where drag ends
* @param end - end point of the drag
*
* @return true if default event processors should be used after this one
*/
public fun handle(event: PointerEvent, start: ViewPoint<T>, end: ViewPoint<T>): DragResult<T>
public companion object {
public fun <T : Any> bypass(): DragHandle<T> = DragHandle<T> { _, _, end -> DragResult(end) }
/**
* Process only events with primary button pressed
*/
public fun <T : Any> withPrimaryButton(
block: (event: PointerEvent, start: ViewPoint<T>, end: ViewPoint<T>) -> DragResult<T>,
): DragHandle<T> = DragHandle { event, start, end ->
if (event.buttons.isPrimaryPressed) {
block(event, start, end)
} else {
DragResult(end)
}
}
/**
* Combine several handles into one
*/
public fun <T : Any> combine(vararg handles: DragHandle<T>): DragHandle<T> = DragHandle { event, start, end ->
var current: ViewPoint<T> = end
handles.forEach {
val result = it.handle(event, start, current)
if (!result.handleNext) return@DragHandle result else {
current = result.result
}
}
return@DragHandle DragResult(current)
}
}
}

View File

@@ -1,365 +0,0 @@
package space.kscience.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 org.jetbrains.skia.Font
import space.kscience.attributes.Attributes
import space.kscience.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: Font.() -> Unit,
) : DraggableFeature<T> {
override fun getBoundingBox(zoom: Float): Rectangle<T> = space.Rectangle(position, position)
override fun withCoordinates(newCoordinates: T): Feature<T> = copy(position = newCoordinates)
override fun withAttributes(modify: (Attributes) -> Attributes): Feature<T> = copy(attributes = modify(attributes))
}
/**
* A pixel map representation on the map/scheme.
* [rectangle] describes the boundary of the pixel map.
* [pixelMap] contained indexed color of pixels. Minimal indices correspond to bottom-left corner of the rectangle.
* Maximal indices - top-right.
*/
public data class PixelMapFeature<T : Any>(
override val space: CoordinateSpace<T>,
val rectangle: Rectangle<T>,
val pixelMap: Structure2D<Color?>,
override val attributes: Attributes = Attributes.EMPTY,
) : Feature<T> {
init {
require(pixelMap.shape[0] > 0) { "Empty dimensions in pixel map are not allowed" }
require(pixelMap.shape[1] > 0) { "Empty dimensions in pixel map are not allowed" }
}
override fun getBoundingBox(zoom: Float): Rectangle<T> = rectangle
override fun withAttributes(modify: Attributes.() -> Attributes): Feature<T> = copy(attributes = modify(attributes))
}

View File

@@ -1,147 +0,0 @@
package space.kscience.maps.features
import androidx.compose.foundation.Canvas
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.DrawScopeMarker
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.text.TextMeasurer
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.DpRect
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.sample
import space.kscience.attributes.Attributes
import space.kscience.attributes.plus
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
/**
* An extension of [DrawScope] to include map-specific features
*/
@DrawScopeMarker
public abstract class FeatureDrawScope<T : Any>(
public val state: CanvasState<T>,
) : DrawScope {
public fun Offset.toCoordinates(): T = with(state) {
toCoordinates(this@toCoordinates, this@FeatureDrawScope)
}
public open fun T.toOffset(): Offset = with(state) {
toOffset(this@toOffset, this@FeatureDrawScope)
}
public fun Rectangle<T>.toDpRect(): DpRect = with(state) { toDpRect() }
public abstract fun painterFor(feature: PainterFeature<T>): Painter
public abstract fun drawText(text: String, position: Offset, attributes: Attributes)
}
/**
* Default implementation of FeatureDrawScope to be used in Compose (both schemes and Maps)
*/
@DrawScopeMarker
public class ComposeFeatureDrawScope<T : Any>(
drawScope: DrawScope,
state: CanvasState<T>,
private val painterCache: Map<PainterFeature<T>, Painter>,
private val textMeasurer: TextMeasurer?,
) : FeatureDrawScope<T>(state), DrawScope by drawScope {
override fun drawText(text: String, position: Offset, attributes: Attributes) {
try {
//TODO don't draw text that is not on screen
drawText(textMeasurer ?: error("Text measurer not defined"), text, position)
} catch (ex: Exception) {
logger.error(ex) { "Failed to measure text" }
}
}
override fun painterFor(feature: PainterFeature<T>): Painter =
painterCache[feature] ?: error("Can't resolve painter for $feature")
public companion object {
private val logger = KotlinLogging.logger("ComposeFeatureDrawScope")
}
}
@Composable
public fun <T: Any> FeatureSet<T>.pointerCache(): Map<PainterFeature<T>, Painter> = key(features) {
features.values.filterIsInstance<PainterFeature<T>>().associateWith { it.getPainter() }
}
/**
* Create a canvas with extended functionality (e.g., drawing text)
*/
@OptIn(FlowPreview::class)
@Composable
public fun <T : Any> FeatureCanvas(
state: CanvasState<T>,
featureFlow: StateFlow<Map<String, Feature<T>>>,
modifier: Modifier = Modifier,
sampleDuration: Duration = 20.milliseconds,
draw: FeatureDrawScope<T>.() -> Unit = {},
) {
val textMeasurer = rememberTextMeasurer(0)
val features: Map<String, Feature<T>> by featureFlow.sample(sampleDuration).collectAsState(featureFlow.value)
val painterCache = features.values
.filterIsInstance<PainterFeature<T>>()
.associateWith { it.getPainter() }
Canvas(modifier) {
if (state.canvasSize != size.toDpSize()) {
state.canvasSize = size.toDpSize()
}
clipRect {
ComposeFeatureDrawScope(this, state, painterCache, textMeasurer).apply(draw).apply {
val attributesCache = mutableMapOf<List<String>, Attributes>()
fun computeGroupAttributes(path: List<String>): Attributes = attributesCache.getOrPut(path) {
if (path.isEmpty()) return Attributes.EMPTY
else if (path.size == 1) {
features[path.first()]?.attributes ?: Attributes.EMPTY
} else {
computeGroupAttributes(path.dropLast(1)) + (features[path.first()]?.attributes
?: Attributes.EMPTY)
}
}
features.entries.sortedBy { it.value.z }
.filter { state.viewPoint.zoom in it.value.zoomRange }
.forEach { (id, feature) ->
val path = id.split("/")
drawFeature(feature, computeGroupAttributes(path.dropLast(1)))
}
}
}
state.selectRect?.let { dpRect ->
val rect = dpRect.toRect()
drawRect(
color = Color.Blue,
topLeft = rect.topLeft,
size = rect.size,
alpha = 0.5f,
style = Stroke(
width = 2f,
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)
)
)
}
}
}

View File

@@ -1,406 +0,0 @@
package space.kscience.maps.features
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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 com.benasher44.uuid.Uuid
import com.benasher44.uuid.uuid4
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.jetbrains.skia.Font
import space.kscience.attributes.Attribute
import space.kscience.attributes.Attributes
import space.kscience.kmath.geometry.Angle
import space.kscience.kmath.nd.*
import space.kscience.kmath.structures.Buffer
import space.kscience.maps.features.FeatureStore.Companion.generateFeatureId
//@JvmInline
//public value class FeatureId<out F : Feature<*>>(public val id: String)
/**
* A reference to a feature inside a [FeatureStore]
*/
public class FeatureRef<T : Any, out F : Feature<T>> internal constructor(
internal val store: FeatureStore<T>,
internal val id: String,
) {
override fun toString(): String = "FeatureRef($id)"
}
@Suppress("UNCHECKED_CAST")
public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.resolve(): F =
store.features[id]?.let { it as F } ?: error("Feature with ref $this not found")
public val <T : Any, F : Feature<T>> FeatureRef<T, F>.attributes: Attributes get() = resolve().attributes
public fun Uuid.toIndex(): String = leastSignificantBits.toString(16)
public interface FeatureBuilder<T : Any> {
public val space: CoordinateSpace<T>
/**
* Add or replace feature. If [id] is null, then a unique id is generated
*/
public fun <F : Feature<T>> feature(id: String?, feature: F): FeatureRef<T, F>
public fun putFeatures(features: Map<String, Feature<T>?>)
/**
* Update existing feature if it is present and is of type [F]
*/
public fun <F : Feature<T>> updateFeature(id: String, block: (F?) -> F): FeatureRef<T, F>
public fun group(
id: String? = null,
attributes: Attributes = Attributes.EMPTY,
builder: FeatureGroup<T>.() -> Unit,
): FeatureRef<T, FeatureGroup<T>>
public fun removeFeature(id: String)
}
public interface FeatureSet<T : Any> {
public val features: Map<String, Feature<T>>
/**
* Create a reference
*/
public fun <F : Feature<T>> ref(id: String): FeatureRef<T, F>
}
public class FeatureStore<T : Any>(
override val space: CoordinateSpace<T>,
) : CoordinateSpace<T> by space, FeatureBuilder<T>, FeatureSet<T> {
private val _featureFlow = MutableStateFlow<Map<String, Feature<T>>>(emptyMap())
public val featureFlow: StateFlow<Map<String, Feature<T>>> get() = _featureFlow
override val features: Map<String, Feature<T>> get() = featureFlow.value
override fun <F : Feature<T>> feature(id: String?, feature: F): FeatureRef<T, F> {
val safeId = id ?: generateFeatureId(feature)
_featureFlow.value += (safeId to feature)
return FeatureRef(this, safeId)
}
public override fun putFeatures(features: Map<String, Feature<T>?>) {
_featureFlow.value = _featureFlow.value.toMutableMap().apply {
features.forEach { (key, value) ->
if (value == null) {
remove(key)
} else {
put(key, value)
}
}
}
}
@Suppress("UNCHECKED_CAST")
override fun <F : Feature<T>> updateFeature(id: String, block: (F?) -> F): FeatureRef<T, F> =
feature(id, block(features[id] as? F))
override fun group(
id: String?,
attributes: Attributes,
builder: FeatureGroup<T>.() -> Unit,
): FeatureRef<T, FeatureGroup<T>> {
val safeId: String = id ?: generateFeatureId<FeatureGroup<*>>()
return feature(safeId, FeatureGroup(this, safeId, attributes).apply(builder))
}
override fun removeFeature(id: String) {
_featureFlow.value -= id
}
override fun <F : Feature<T>> ref(id: String): FeatureRef<T, F> = FeatureRef(this, id)
public fun getBoundingBox(zoom: Float = Float.MAX_VALUE): Rectangle<T>? = with(space) {
features.values.mapNotNull { it.getBoundingBox(zoom) }.wrapRectangles()
}
public companion object {
internal fun generateFeatureId(prefix: String): String =
"$prefix[${uuid4().toIndex()}]"
internal fun generateFeatureId(feature: Feature<*>): String =
generateFeatureId(feature::class.simpleName ?: "undefined")
internal inline fun <reified F : Feature<*>> generateFeatureId(): String =
generateFeatureId(F::class.simpleName ?: "undefined")
/**
* Build, but do not remember map feature state
*/
public fun <T : Any> build(
coordinateSpace: CoordinateSpace<T>,
builder: FeatureStore<T>.() -> Unit = {},
): FeatureStore<T> = FeatureStore(coordinateSpace).apply(builder)
/**
* Build and remember map feature state
*/
@Composable
public fun <T : Any> remember(
coordinateSpace: CoordinateSpace<T>,
builder: FeatureStore<T>.() -> Unit = {},
): FeatureStore<T> = remember {
build(coordinateSpace, builder)
}
}
}
/**
* A group of other features
*/
public data class FeatureGroup<T : Any> internal constructor(
val store: FeatureStore<T>,
val groupId: String,
override val attributes: Attributes,
) : CoordinateSpace<T> by store.space, Feature<T>, FeatureBuilder<T>, FeatureSet<T> {
override val space: CoordinateSpace<T> get() = store.space
override fun withAttributes(modify: Attributes.() -> Attributes): FeatureGroup<T> =
FeatureGroup(store, groupId, modify(attributes))
override fun <F : Feature<T>> feature(id: String?, feature: F): FeatureRef<T, F> =
store.feature("$groupId/${id ?: generateFeatureId(feature)}", feature)
public override fun putFeatures(features: Map<String, Feature<T>?>) {
store.putFeatures(features.mapKeys { "$groupId/${it.key}" })
}
override fun <F : Feature<T>> updateFeature(id: String, block: (F?) -> F): FeatureRef<T, F> =
store.updateFeature("$groupId/$id", block)
override fun group(
id: String?,
attributes: Attributes,
builder: FeatureGroup<T>.() -> Unit,
): FeatureRef<T, FeatureGroup<T>> {
val safeId = id ?: generateFeatureId<FeatureGroup<*>>()
return feature(safeId, FeatureGroup(store, "$groupId/$safeId", attributes).apply(builder))
}
override fun removeFeature(id: String) {
store.removeFeature("$groupId/$id")
}
override val features: Map<String, Feature<T>>
get() = store.featureFlow.value
.filterKeys { it.startsWith("$groupId/") }
.mapKeys { it.key.removePrefix("$groupId/") }
.toMap()
override fun getBoundingBox(zoom: Float): Rectangle<T>? = with(space) {
features.values.mapNotNull { it.getBoundingBox(zoom) }.wrapRectangles()
}
override fun <F : Feature<T>> ref(id: String): FeatureRef<T, F> = FeatureRef(store, "$groupId/$id")
}
/**
* Recursively search for feature until function returns true
*/
public fun <T : Any> FeatureSet<T>.forEachUntil(block: FeatureSet<T>.(ref: FeatureRef<T, *>, feature: Feature<T>) -> Boolean) {
features.entries.sortedByDescending { it.value.z }.forEach { (key, feature) ->
if (!block(ref<Feature<T>>(key), feature)) return@forEachUntil
}
}
/**
* Process all features with a given attribute from the one with highest [z] to lowest
*/
public inline fun <T : Any, A> FeatureSet<T>.forEachWithAttribute(
key: Attribute<A>,
block: FeatureSet<T>.(ref: FeatureRef<T, *>, feature: Feature<T>, attribute: A) -> Unit,
) {
features.forEach { (id, feature) ->
feature.attributes[key]?.let {
block(ref<Feature<T>>(id), feature, it)
}
}
}
public inline fun <T : Any, A> FeatureSet<T>.forEachWithAttributeUntil(
key: Attribute<A>,
block: FeatureSet<T>.(ref: FeatureRef<T, *>, feature: Feature<T>, attribute: A) -> Boolean,
) {
features.forEach { (id, feature) ->
feature.attributes[key]?.let {
if (!block(ref<Feature<T>>(id), feature, it)) return@forEachWithAttributeUntil
}
}
}
public inline fun <T : Any, reified F : Feature<T>> FeatureSet<T>.forEachWithType(
crossinline block: FeatureSet<T>.(ref: FeatureRef<T, F>, feature: F) -> Unit,
) {
features.forEach { (id, feature) ->
if (feature is F) block(ref(id), feature)
}
}
public fun <T : Any> FeatureBuilder<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> FeatureBuilder<T>.rectangle(
centerCoordinates: T,
size: DpSize = DpSize(5.dp, 5.dp),
attributes: Attributes = Attributes.EMPTY,