192 Commits
routes ... main

Author SHA1 Message Date
fa5393a0f0 Merge pull request '0.3.0' (!23) from dev into main
Reviewed-on: #23
2025-03-09 10:03:55 +03:00
4e08680b22 PNG export 2024-10-03 09:22:42 +03:00
bd2804d772 Move svg to features 2024-10-01 20:26:00 +03:00
e3b5ad0df4 Add proper serialization for trajectory 2024-08-13 20:17:01 +03:00
3ddced5c4a fix missing default modifier for MapView 2024-08-13 16:41:43 +03:00
2d46a0ad98 Fix package names in Trajectory 2024-08-13 16:26:31 +03:00
29a0fb743c Use data objects for trajectory directions 2024-07-21 18:40:17 +03:00
df800f05f0 Merge pull request 'Add requirement on non-emptiness of composite trajectory parts list' (!25) from lounres/maps-kt:fix/nonemptiness-check-for-composite-trajectories into dev
Reviewed-on: #25
Reviewed-by: Alexander Nozik <altavir@gmail.com>
2024-07-08 18:12:56 +03:00
Gleb Minaev
498db37a7c Add requirement on non-emptiness of composite trajectory parts list. 2024-07-08 18:08:54 +03:00
e913874ace Merge pull request 'immutable_features' (!24) from immutable_features into dev 2024-07-08 11:56:03 +03:00
4e76a25a15 Merge remote-tracking branch 'spc/immutable_features' into immutable_features 2024-07-08 11:55:20 +03:00
5da7ee7944 Remove unnecessary argument 2024-07-08 11:55:11 +03:00
1119d593a2 Revert "extract GroupAttributesCalculator"
This reverts commit bf128a3eb9.
2024-07-08 14:29:43 +06:00
bf128a3eb9 extract GroupAttributesCalculator 2024-07-08 14:03:59 +06:00
3a4c9133c6 Add bulk set for features 2024-07-08 09:13:42 +03:00
29074a9624 Fix feature update 2024-07-07 13:20:57 +03:00
62196fc6f5 Refactored to use flow instead of snapshot maps 2024-07-06 09:54:06 +03:00
0f5dcf9979 add github CI 2024-06-19 18:58:58 +03:00
601a16e420 Merge remote-tracking branch 'refs/remotes/github/main' into dev 2024-06-19 18:38:30 +03:00
SPC-code
7d0dcd1b91 Update pages.yml 2024-06-19 18:35:26 +03:00
SPC-code
b4b3ecc8d7 Update publish.yml 2024-06-19 18:23:36 +03:00
SPC-code
79f4d0eba5 Update pages.yml 2024-06-19 18:22:51 +03:00
SPC-code
390a896e0a Update build.yml 2024-06-19 18:19:10 +03:00
234d4715b6 add github CI 2024-06-19 18:14:59 +03:00
6eafb5ec26 make ./gradlew executable 2024-06-19 17:34:44 +06:00
SPC-code
bd6d8e2f8e Merge pull request #24 from SciProgCentre/dev
0.3.0
2024-06-12 14:26:56 +03:00
f62f8181ce avoid drawing invisible features 2024-06-07 19:28:38 +03:00
c30f586120 add alpha for remaining features 2024-06-07 12:07:49 +03:00
07ea73a87a add .kotlin to ignore 2024-06-04 15:16:25 +03:00
7ca4bba1b7 Version 0.3.0 2024-06-04 14:38:21 +03:00
7c7a788d2e change package to space.kscience 2024-06-04 11:06:50 +03:00
327cef9ea9 Update dependencies. Add wasm demo 2024-02-23 12:11:43 +03:00
ea7869e39d First preview of 0.3.0 2023-11-15 16:43:13 +03:00
d21d6ebb2a Add (not working yet) JS implementation 2023-10-01 14:05:03 +03:00
f05f6e137c Add Js targets 2023-10-01 11:22:02 +03:00
7d3b219d70 Move files around 2023-10-01 11:12:15 +03:00
aebf4af24f Fix svg export 2023-09-10 21:13:54 +03:00
75b5a69a27 Full refactor of map state 2023-09-10 13:12:45 +03:00
921aff4685 Merge remote-tracking branch 'space/dev' into dev 2023-05-26 15:49:31 +03:00
1caf141d27 Fix for #22 (default hairline size for points) 2023-05-26 15:49:14 +03:00
2bc595d97d Fix for #20 - auto-scale for single point 2023-05-11 09:39:08 +03:00
a059697f5c Merge remote-tracking branch 'space/dev' into dev 2023-05-06 15:46:16 +03:00
1ccd35f152 Prepare for 0.2.2 release 2023-05-06 15:39:58 +03:00
Alexander Nozik
7981422f94 change container version for CI 2023-05-06 11:26:57 +00:00
d8548ab162 Differentiate obstacles 2023-05-06 14:22:09 +03:00
5d0e0f054e Merge remote-tracking branch 'space/main' into dev 2023-05-06 13:57:23 +03:00
Alexander Nozik
869aee887a update container image reference 2023-05-06 10:56:28 +00:00
11b11118fa Fix circle line intersection and add a special case for a single-point obstacle 2023-05-06 12:50:44 +03:00
05ef3aa4dd Fix segment-circle intersection 2023-05-02 16:17:04 +03:00
8947998e0c Fix propagation of a finished route 2023-05-02 13:52:06 +03:00
74b86dbc59 Add possibility to skip some paths if target is close to the obstacle 2023-05-02 13:30:41 +03:00
35efce087e remove unnecessary elvis. He lives, but not needed here. 2023-05-01 21:29:37 +03:00
fcf0600d0c Seems to be working... finally 2023-05-01 21:25:38 +03:00
f0da3efd27 [WIP] A lot of bugfixes 2023-05-01 16:56:42 +03:00
614ca8d6f3 [WIP] refactor CircleTrajectory2D logic to be more correct 2023-05-01 09:14:26 +03:00
8bc1987acf [WIP] full rewrite of obstacle avoidance 2023-04-30 21:20:10 +03:00
b06fc5c87a Merge remote-tracking branch 'space/dev' into dev 2023-04-28 20:33:01 +03:00
Artyom Degtyarev
dde5b2c9f7 intersection of lineSegment and Circle fixed 2023-04-28 13:07:18 +03:00
5e35ce8b5f Rename ObstacleNode to ObstacleConnection 2023-04-26 10:44:13 +03:00
a6a5baa352 Obstacle avoidance refactoring 2023-04-23 21:20:32 +03:00
4e7ef35280 Obstacle avoidance refactoring 2023-04-23 13:44:07 +03:00
cac5841401 Obstacle avoidance refactoring 2023-04-23 11:01:08 +03:00
cdaa17a3b9 Change trajectory package 2023-04-16 20:50:51 +03:00
3fc52dd60f Fixed zero tangents 2023-04-16 20:28:58 +03:00
375de71ca6 Fixed zero tangents 2023-04-16 20:08:04 +03:00
df675c8d45 Fix Trajectory2D type name 2023-04-12 20:46:45 +03:00
e553e33d4c [WIP] disentangling obstacles phase 2 2023-04-11 17:08:28 +03:00
dbfe61b949 Update kmath version and gradle 2023-04-10 11:33:13 +03:00
3ab875d87c Add trajectory-kt 2023-04-06 10:03:38 +03:00
dcee205b7c Remove tile flicker on move 2023-03-16 10:54:40 +03:00
4658418517 Add builder for pixelMap to scheme 2023-03-16 10:10:53 +03:00
3b43446f82 Add pixel map implementation 2023-03-15 15:24:23 +03:00
2ed9d72029 Fix circle clickable radius 2023-02-22 14:57:25 +03:00
1bcae1f362 Fix autoscale for maps 2023-02-22 14:00:41 +03:00
94a91e9ff9 Add remaining attributes arguments to feature builders 2023-02-22 12:20:32 +03:00
1ca1b3cd43 Fix line distance 2023-02-22 11:48:44 +03:00
a89a5cf36a up version 2023-02-22 11:10:37 +03:00
1a7646f311 Add ellipsoid mercator backward transformation 2023-02-22 10:34:59 +03:00
8f489ea0f9 Fix line clickability region 2023-02-22 09:48:18 +03:00
7623e5f622 Add ability to remove a feature by key 2023-02-13 17:32:48 +03:00
dab9784089 Optional zoom on double click 2023-02-13 17:01:16 +03:00
90eb7b4575 Split points and multiline 2023-02-13 16:49:36 +03:00
3219e13fa7 Add double click navigation 2023-02-13 13:32:25 +03:00
ce25690dfb fix arc rendering 2023-02-13 11:13:00 +03:00
e7784c2960 Fix attribute serialization tests 2023-02-12 12:49:55 +03:00
e80c9406c9 Fix attribute serialization tests 2023-02-11 19:09:27 +03:00
2343f37655 Fix attribute serializer 2023-02-11 17:14:00 +03:00
50ccfeab70 Type safe angles 2023-02-06 17:19:51 +03:00
a23b9954cd FeatureId -> FeatureRef 2023-02-06 10:37:22 +03:00
82a1260e3f Make elevation optional for Gmc 2023-01-14 20:09:58 +03:00
9a7e086591 GeoJson builders 2023-01-12 22:58:33 +03:00
b6a3ce0fe7 GeoJson builders 2023-01-12 14:50:10 +03:00
0f00abb1b2 Attribute serialization 2023-01-12 11:52:54 +03:00
190834634f API fixes 2023-01-06 22:16:46 +03:00
6278235b51 Polygon editor demo 2023-01-06 14:15:06 +03:00
5a3a4b059e Detect taps manually 2023-01-06 12:36:02 +03:00
f288a17243 fix featureGroup remember 2023-01-06 10:47:24 +03:00
ad938a614a Attributes cleanup 2023-01-06 10:40:21 +03:00
ffc77dc611 Introduce Attribute builders 2023-01-06 10:26:29 +03:00
69e7b058d2 Fix scheme 2023-01-05 17:24:27 +03:00
70fe9cf97a Update title and logo 2023-01-05 17:12:37 +03:00
cb160110e7 Optimize attributes working 2023-01-05 12:15:52 +03:00
ea8c5571bb Proper polygon contains check 2023-01-04 22:43:51 +03:00
ba962acf5c Change the working of groups 2023-01-02 20:28:47 +03:00
a8a3da7e70 Change the working of groups 2023-01-02 14:08:20 +03:00
26c3e589da Immutable attributes 2023-01-01 09:10:01 +03:00
7643968d39 Fix view rectangle computation 2022-12-28 21:02:23 +03:00
d34c5099f6 Fix view change listener 2022-12-28 20:03:08 +03:00
15ad690129 Refactor drag. Again. 2022-12-28 19:49:09 +03:00
9e3eec9533 Full refacor of map state and arguments 2022-12-28 14:41:46 +03:00
56ccd66db5 Make attributeMap val 2022-12-27 12:46:27 +03:00
33cadb1d15 Add js support for features 2022-12-27 12:34:24 +03:00
8969b2b094 Add geojson demo 2022-12-27 11:51:14 +03:00
5da23b83c1 Add GeoJson bindings 2022-12-26 23:22:21 +03:00
7e7cb0a260 Multi-listener drag 2022-12-26 17:44:01 +03:00
572adf041f Cleanup drag 2022-12-26 11:19:08 +03:00
42dbbeea58 Add depth attribute 2022-12-25 14:51:34 +03:00
5ed46b278c Generalize feature draw logic for schemes 2022-12-25 14:33:31 +03:00
8a94438dfd Generalize feature draw logic 2022-12-25 11:29:31 +03:00
5b95adc649 Generalize feature draw logic 2022-12-25 11:07:45 +03:00
fb13fa1431 Generalize map control logic 2022-12-24 22:59:33 +03:00
9b8ba884e1 Generic features 2022-12-23 22:16:16 +03:00
c2157c8351 [WIP] generic features 2022-12-23 11:47:34 +03:00
7735d667bc [WIP] Generic features 2022-12-17 23:12:00 +03:00
0d9efadcb8 New attributes 2022-12-17 21:36:05 +03:00
8451cc3aa1 [WIP] attributes refactoring 2022-12-10 13:41:54 +03:00
edeb422335 Typed features for Map 2022-12-09 22:21:24 +03:00
5448929d31 Fixed multi-drag issue. Added demo for bounded drag. 2022-12-07 18:02:20 +03:00
5561a4188c More robust drag API and implementation 2022-11-28 07:58:22 +03:00
1ebcb02f0a More robust drag API and implementation 2022-11-28 07:56:15 +03:00
e7cb006a3c 0.1.0 release with Kotlin 1.7.20 2022-10-12 15:03:02 +03:00
5ab7862660 Use exact shapes for SVG 2022-10-09 20:25:21 +03:00
737bbbde6a Fix SVG rendering for scheme 2022-10-09 17:11:03 +03:00
77e27c7eb6 Add string export 2022-10-09 13:48:19 +03:00
28663a010d Add SVG render for Scheme 2022-10-09 13:03:36 +03:00
c679680cf0 Fix curve reverse 2022-10-08 14:16:12 +03:00
6a4531fe1d Fix repos 2022-10-08 13:44:21 +03:00
c8c6aef24e Fix curveInDirection 2022-10-08 13:39:52 +03:00
3e31effa80 Fix failing test 2022-10-04 15:06:00 +03:00
e106ceef9a Fix the problem with failed downloads... again 2022-09-20 13:57:41 +03:00
c82f47a786 Fix scheme rectangle bug 2022-09-17 13:11:49 +03:00
b68fa02fa4 Add key to SchemeView 2022-09-17 12:44:44 +03:00
9c5c4b5747 line builder for scheme 2022-09-17 11:49:39 +03:00
354cd8c9a2 Add arc builders to scheme 2022-09-17 11:44:34 +03:00
409d749054 Update schemes to match Maps features 2022-09-17 10:32:28 +03:00
6decba8e83 Add toString to curves and correct rounding normalization for angles 2022-09-16 12:59:58 +03:00
2b01b8e316 Add separate builders for feature state 2022-09-14 15:09:02 +03:00
8c79c913e6 Fix draggable 2022-09-14 12:11:15 +03:00
693f608b14 Reorder some API 2022-09-13 22:53:56 +03:00
4c71b8e7bb Change state propagation to better support dynamic features. Drag not working for now 2022-09-13 20:30:49 +03:00
fa51fc03db Change state propagation and rectangle selection 2022-09-13 19:01:23 +03:00
6b0a2566bd Change state propagation and rectangle selection 2022-09-13 18:27:57 +03:00
4cabfcf16f Add rectangle override for map component builder 2022-09-13 13:44:38 +03:00
1808a9b7d3 Change notation for arc angles. WARNING meaning changed without API change 2022-09-12 14:05:01 +03:00
890a1bceca Fix arc builder 2022-09-11 22:24:53 +03:00
8e143eb81c Fix Angle inversion 2022-09-11 21:16:48 +03:00
8429577fd8 Add extension builders for distance 2022-09-10 18:47:03 +03:00
25e9eed88d Correct mercator projection. Use epsg naming 2022-09-10 18:04:42 +03:00
42e0a4c46d Add abstraction for coordinate projections 2022-09-10 16:14:30 +03:00
cda8d8e76f Update feature builder AP 2022-09-10 15:20:01 +03:00
7939677e5a Remove unnecessary state duplication for viewPoint 2022-09-09 21:23:38 +03:00
48e425bbae Use null viewPoint to infer it from features 2022-09-09 21:16:54 +03:00
74e878e7e5 Switch to kscience project configuration. 2022-09-06 13:32:13 +03:00
491a4e6000 Drag done 2022-08-31 22:48:52 +03:00
5a1d3d701f Refactored drag processing 2022-08-30 10:29:56 +03:00
26aaac2ecd Fix after merge 2022-08-29 22:24:39 +03:00
d490b3f37c Merge remote-tracking branch 'space/main'
# Conflicts:
#	maps-kt-compose/src/commonMain/kotlin/center/sciprog/maps/compose/MapFeature.kt
#	maps-kt-compose/src/jvmMain/kotlin/center/sciprog/maps/compose/MapTextFeature.kt
2022-08-29 22:23:24 +03:00
9ffc9d3d67 Add geodetic distance measurement. 2022-08-29 22:12:03 +03:00
e0cc0bc60b Tests for curve calculation 2022-08-29 18:37:42 +03:00
4614b1f7bc Type safe angles and distances 2022-08-29 13:38:46 +03:00
Alexander Nozik
6459246eb5 Merge pull request #16 from SciProgCentre/connect_timeout_exception_handling
Connect timeout exception handling
2022-08-26 14:23:51 +03:00
3467a6dbe0 Refactor error management 2022-08-26 14:22:17 +03:00
a.kalmakhanov
b110ab2b4c Merge remote-tracking branch 'origin/main' 2022-08-21 22:37:13 +06:00
a.kalmakhanov
4b512d0967 ConnectTimeoutException handling enabled
TextFeature multiplatform capability enabled
2022-08-21 22:36:56 +06:00
11b278fc81 Frodo goes to Mordor (directly) 2022-08-18 18:47:06 +03:00
Alexander Nozik
190877c10e Merge pull request #11 from mipt-npm/drag
Ability to customise onDrag functionality
2022-08-08 20:28:17 +03:00
a.kalmakhanov
954cc26bfd Ability to customise onDrag functionality 2022-07-28 16:47:14 +06:00
Alexander Nozik
22cbeddaf0 Merge pull request #10 from mipt-npm/points
Added new feature to draw points on the map
2022-07-27 16:44:11 +03:00
a.kalmakhanov
3e2c8d2db2 Added new feature to draw points on the map 2022-07-27 17:48:59 +06:00
14acd88358 Add arc feature. Add (approximate) ellipsoid 2022-07-23 18:37:14 +03:00
4c3aefcfae package refactoring 2022-07-23 13:49:47 +03:00
8867388e85 package refactoring 2022-07-23 11:30:33 +03:00
2c87ba7638 package refactoring 2022-07-23 11:24:37 +03:00
cdee88573d Fix package naming 2022-07-23 10:58:16 +03:00
09dfdcc84a Add explicit API for core 2022-07-23 10:40:36 +03:00
14b3142f43 Add schemes in a separate module 2022-07-23 10:27:58 +03:00
7ada7f85f2 small autozoom fix 2022-07-23 09:59:14 +03:00
c7d1797617 Refactor map arguments 2022-07-19 12:31:09 +03:00
2fdec494fb Use Dp in features 2022-07-19 10:34:21 +03:00
5e548fcc65 change logic of drawFeature to draw relative to offset 2022-07-19 10:16:53 +03:00
307e42eac2 Add feature group 2022-07-19 10:12:52 +03:00
52d0d959de refactor custom feature -> draw feature 2022-07-19 09:34:00 +03:00
146 changed files with 89338 additions and 1061 deletions

22
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
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

39
.github/workflows/pages.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
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

27
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
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,6 +1,7 @@
build/
.gradle/
.idea/
.kotlin
/*.iml
mapCache/

View File

@@ -1,25 +1,25 @@
import kotlin.io.path.readText
job("Build") {
gradlew("openjdk:11", "build")
gradlew("spc.registry.jetbrains.space/p/sci/containers/kotlin-ci:1.0.3", "build")
}
job("Publish") {
startOn {
gitPush { enabled = false }
}
container("openjdk:11") {
env["SPACE_USER"] = Secrets("space_user")
env["SPACE_TOKEN"] = Secrets("space_token")
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 }}"
kotlinScript { api ->
val spaceUser = System.getenv("SPACE_USER")
val spaceToken = System.getenv("SPACE_TOKEN")
// write version to the build directory
// write the version to the build directory
api.gradlew("version")
//read version from build file
//read the 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 a status of a job
// automatically update deployment status based on the status of a job
syncWithAutomationJob = true
)
api.gradlew(

32
CHANGELOG.md Normal file
View File

@@ -0,0 +1,32 @@
# 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 +1,69 @@
This repository is a work-in-progress implementation of Map-with-markers component for Compose-Multiplatform
# 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
### [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

View File

@@ -1,90 +1,40 @@
import space.kscience.gradle.useApache2Licence
import space.kscience.gradle.useSPCTeam
plugins {
base
id("space.kscience.gradle.project")
}
val ktorVersion by extra("2.0.3")
val kmathVersion: String by extra("0.4.0")
allprojects {
group = "center.sciprog"
version = "0.1.0-SNAPSHOT"
group = "space.kscience"
version = "0.4.0-dev-3"
repositories {
mavenLocal()
maven("https://repo.kotlin.link")
}
}
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)
ksciencePublish {
pom("https://github.com/SciProgCentre/maps-kt") {
useApache2Licence()
useSPCTeam()
}
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")

4
demo/README.md Normal file
View File

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

View File

@@ -1,40 +0,0 @@
import org.jetbrains.compose.compose
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
val ktorVersion: String by rootProject.extra
kotlin {
jvm {
compilations.all {
kotlinOptions.jvmTarget = "11"
}
withJava()
}
sourceSets {
val jvmMain by getting {
dependencies {
implementation(projects.mapsKtCompose)
implementation(compose.desktop.currentOs)
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-kt-compose"
packageVersion = "1.0.0"
}
}
}

4
demo/maps-wasm/README.md Normal file
View File

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

View File

@@ -0,0 +1,37 @@
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 {
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 KiB

View File

@@ -0,0 +1,82 @@
@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

@@ -0,0 +1,12 @@
<!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>

4
demo/maps/README.md Normal file
View File

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

18
demo/maps/api/maps.api Normal file
View File

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

View File

@@ -0,0 +1,39 @@
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 {
jvmToolchain(11)
jvm()
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)
}
}
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"
}
}
}
}

View File

@@ -0,0 +1,192 @@
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.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.window.Window
import androidx.compose.ui.window.application
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)}"
@OptIn(ExperimentalFoundationApi::class)
@Composable
@Preview
fun App() {
MaterialTheme {
val scope = rememberCoroutineScope()
val mapTileProvider = remember {
OpenStreetMapTileProvider(
client = HttpClient(CIO),
cacheDirectory = Path.of("mapCache")
)
}
val centerCoordinates = MutableStateFlow<Gmc?>(null)
val pointOne = 55.568548 to 37.568604
val pointTwo = 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)
}
)
) {
geoJson(javaClass.getResource("/moscow.geo.json")!!)
.color(Color.Blue)
.alpha(0.4f)
icon(pointOne, Icons.Filled.Home)
val marker1 = rectangle(55.744 to 38.614, size = DpSize(10.dp, 10.dp))
.color(Color.Magenta)
val marker2 = rectangle(55.8 to 38.5, size = DpSize(10.dp, 10.dp))
.color(Color.Magenta)
val marker3 = rectangle(56.0 to 38.5, size = DpSize(10.dp, 10.dp))
.color(Color.Magenta)
draggableLine(marker1, marker2, id = "line 1").color(Color.Red).onClick {
println("line 1 clicked")
}
draggableLine(marker2, marker3, id = "line 2").color(Color.DarkGray).onClick {
println("line 2 clicked")
}
draggableLine(marker3, marker1, id = "line 3").color(Color.Blue).onClick {
println("line 3 clicked")
}
multiLine(
points = listOf(
55.742465 to 37.615812,
55.742713 to 37.616370,
55.742815 to 37.616659,
55.742320 to 37.617132,
55.742086 to 37.616566,
55.741715 to 37.616716
),
)
// 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(
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)
// }
arc(pointOne, 10.0.kilometers, (PI / 4).radians, -Angle.pi / 2)
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 {
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)
}
}
}
// println(toPrettyString())
}
}
}
fun main() = application {
Window(onCloseRequest = ::exitApplication, title = "Maps-kt demo", icon = painterResource("SPC-logo.png")) {
App()
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -0,0 +1,37 @@
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

@@ -0,0 +1,81 @@
// 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()
}
}

4
demo/scheme/README.md Normal file
View File

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

View File

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

View File

@@ -0,0 +1,38 @@
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"
//mainClass = "Joker2023Kt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "scheme-compose-demo"
packageVersion = "1.0.0"
}
}
}
}

View File

@@ -0,0 +1,110 @@
// 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.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 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 {
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")
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 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) {
App()
}
}

View File

@@ -0,0 +1,76 @@
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.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 KiB

View File

@@ -1,96 +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.foundation.layout.Column
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Home
import androidx.compose.runtime.*
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import centre.sciprog.maps.GeodeticMapCoordinates
import centre.sciprog.maps.MapViewPoint
import centre.sciprog.maps.compose.*
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import java.nio.file.Path
import kotlin.random.Random
@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),
cacheDirectory = Path.of("mapCache")
)
}
var coordinates by remember { mutableStateOf<GeodeticMapCoordinates?>(null) }
Column {
//display click coordinates
Text(coordinates?.toString() ?: "")
MapView(
mapTileProvider = mapTileProvider,
initialViewPoint = viewPoint,
onClick = { coordinates = focus },
config = MapViewConfig(inferViewBoxFromFeatures = true)
) {
val pointOne = 55.568548 to 37.568604
val pointTwo = 55.929444 to 37.518434
val pointThree = 60.929444 to 37.518434
image(pointOne, Icons.Filled.Home)
//remember feature Id
val circleId: FeatureId = circle(
centerCoordinates = pointTwo,
)
custom(position = pointThree) {
drawRect(
color = Color.Red,
topLeft = it,
size = Size(20f, 20f)
)
}
line(pointOne, pointTwo)
text(pointOne, "Home")
scope.launch {
while (isActive) {
delay(200)
//Overwrite a feature with new color
circle(
pointTwo,
id = circleId,
color = Color(Random.nextFloat(), Random.nextFloat(), Random.nextFloat())
)
}
}
}
}
}
}
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
App()
}
}

View File

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

View File

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

View File

@@ -0,0 +1,31 @@
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

@@ -0,0 +1,205 @@
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.

After

Width:  |  Height:  |  Size: 591 KiB

17
docs/templates/ARTIFACT-TEMPLATE.md vendored Normal file
View File

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

9
docs/templates/README-TEMPLATE.md vendored Normal file
View File

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

View File

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

View File

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

2213
gradle/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

0
gradlew vendored Normal file → Executable file
View File

22
maps-kt-compose/README.md Normal file
View File

@@ -0,0 +1,22 @@
# 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,29 +1,45 @@
import org.jetbrains.compose.compose
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
id("space.kscience.gradle.mpp")
alias(spclibs.plugins.compose.compiler)
alias(spclibs.plugins.compose.jb)
// id("com.android.library")
`maven-publish`
}
val ktorVersion: String by rootProject.extra
kscience {
jvm()
wasm()
kotlin {
jvm {
compilations.all {
kotlinOptions.jvmTarget = "11"
}
useCoroutines()
commonMain{
api(projects.mapsKtCore)
api(projects.mapsKtFeatures)
api(compose.foundation)
api(project.dependencies.platform(spclibs.ktor.bom))
}
sourceSets {
commonMain{
dependencies{
api(projects.mapsKtCore)
api(compose.foundation)
api("io.ktor:ktor-client-core:$ktorVersion")
}
}
val jvmMain by getting
val jvmTest by getting
jvmMain{
api("io.ktor:ktor-client-cio")
}
}
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

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

View File

@@ -1,85 +0,0 @@
package centre.sciprog.maps.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.vector.ImageVector
import centre.sciprog.maps.GmcBox
typealias FeatureId = String
interface FeatureBuilder {
fun addFeature(id: FeatureId?, feature: MapFeature): FeatureId
fun build(): SnapshotStateMap<FeatureId, MapFeature>
}
internal class MapFeatureBuilder(initialFeatures: Map<FeatureId, MapFeature>) : FeatureBuilder {
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
}
fun FeatureBuilder.circle(
centerCoordinates: Pair<Double, Double>,
zoomRange: IntRange = defaultZoomRange,
size: Float = 5f,
color: Color = Color.Red,
id: FeatureId? = null,
) = addFeature(
id, MapCircleFeature(centerCoordinates.toCoordinates(), zoomRange, size, color)
)
fun FeatureBuilder.custom(
position: Pair<Double, Double>,
id: FeatureId? = null,
customFeatureBuilder: DrawScope.(Offset) -> Unit,
) = addFeature(id, object : MapCustomFeature(position = position.toCoordinates()) {
override fun drawFeature(drawScope: DrawScope, offset: Offset) {
customFeatureBuilder(drawScope, offset)
}
override fun getBoundingBox(zoom: Int): GmcBox {
return GmcBox(position.toCoordinates(), position.toCoordinates())
}
})
fun FeatureBuilder.line(
aCoordinates: Pair<Double, Double>,
bCoordinates: Pair<Double, Double>,
zoomRange: IntRange = defaultZoomRange,
color: Color = Color.Red,
id: FeatureId? = null,
) = addFeature(id, MapLineFeature(aCoordinates.toCoordinates(), bCoordinates.toCoordinates(), zoomRange, color))
fun FeatureBuilder.text(
position: Pair<Double, Double>,
text: String,
zoomRange: IntRange = defaultZoomRange,
color: Color = Color.Red,
id: FeatureId? = null,
) = addFeature(id, MapTextFeature(position.toCoordinates(), text, zoomRange, color))
@Composable
fun FeatureBuilder.image(
position: Pair<Double, Double>,
image: ImageVector,
size: Size = Size(20f, 20f),
zoomRange: IntRange = defaultZoomRange,
id: FeatureId? = null,
) = addFeature(id, MapVectorImageFeature(position.toCoordinates(), image, size, zoomRange))

View File

@@ -1,95 +0,0 @@
package centre.sciprog.maps.compose
import androidx.compose.runtime.Composable
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
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.IntSize
import centre.sciprog.maps.GeodeticMapCoordinates
import centre.sciprog.maps.GmcBox
import centre.sciprog.maps.wrapAll
//TODO replace zoom range with zoom-based representation change
sealed class MapFeature(val zoomRange: IntRange) {
abstract fun getBoundingBox(zoom: Int): GmcBox
}
fun Iterable<MapFeature>.computeBoundingBox(zoom: Int): GmcBox? = map { 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)
*/
class MapFeatureSelector(val selector: (zoom: Int) -> MapFeature) : MapFeature(defaultZoomRange) {
override fun getBoundingBox(zoom: Int): GmcBox = selector(zoom).getBoundingBox(zoom)
}
abstract class MapCustomFeature(
zoomRange: IntRange = defaultZoomRange,
val position: GeodeticMapCoordinates
) : MapFeature(zoomRange) {
abstract fun drawFeature(drawScope: DrawScope, offset: Offset)
}
class MapCircleFeature(
val center: GeodeticMapCoordinates,
zoomRange: IntRange = defaultZoomRange,
val size: Float = 5f,
val color: Color = Color.Red,
) : MapFeature(zoomRange) {
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(center, center)
}
class MapLineFeature(
val a: GeodeticMapCoordinates,
val b: GeodeticMapCoordinates,
zoomRange: IntRange = defaultZoomRange,
val color: Color = Color.Red,
) : MapFeature(zoomRange) {
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(a, b)
}
class MapTextFeature(
val position: GeodeticMapCoordinates,
val text: String,
zoomRange: IntRange = defaultZoomRange,
val color: Color = Color.Red,
) : MapFeature(zoomRange) {
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position)
}
class MapBitmapImageFeature(
val position: GeodeticMapCoordinates,
val image: ImageBitmap,
val size: IntSize = IntSize(15, 15),
zoomRange: IntRange = defaultZoomRange,
) : MapFeature(zoomRange) {
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position)
}
class MapVectorImageFeature(
val position: GeodeticMapCoordinates,
val painter: Painter,
val size: Size,
zoomRange: IntRange = defaultZoomRange,
) : MapFeature(zoomRange) {
override fun getBoundingBox(zoom: Int): GmcBox = GmcBox(position, position)
}
@Composable
fun MapVectorImageFeature(
position: GeodeticMapCoordinates,
image: ImageVector,
size: Size = Size(20f, 20f),
zoomRange: IntRange = defaultZoomRange,
): MapVectorImageFeature = MapVectorImageFeature(position, rememberVectorPainter(image), size, zoomRange)

View File

@@ -1,31 +0,0 @@
package centre.sciprog.maps.compose
import androidx.compose.ui.graphics.ImageBitmap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlin.math.floor
data class TileId(
val zoom: Int,
val i: Int,
val j: Int,
)
data class MapTile(
val id: TileId,
val image: ImageBitmap,
)
interface MapTileProvider {
fun CoroutineScope.loadTileAsync(tileId: TileId): Deferred<MapTile>
val tileSize: Int get() = DEFAULT_TILE_SIZE
fun toIndex(d: Double): Int = floor(d / tileSize).toInt()
fun toCoordinate(i: Int): Double = (i * tileSize).toDouble()
companion object {
const val DEFAULT_TILE_SIZE = 256
}
}

View File

@@ -1,66 +0,0 @@
package centre.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 centre.sciprog.maps.*
import kotlin.math.PI
import kotlin.math.log2
import kotlin.math.min
data class MapViewConfig(
val zoomSpeed: Double = 1.0 / 3.0,
val inferViewBoxFromFeatures: Boolean = false
)
@Composable
expect fun MapView(
mapTileProvider: MapTileProvider,
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
features: Map<FeatureId, MapFeature>,
onClick: MapViewPoint.() -> Unit = {},
//TODO consider replacing by modifier
config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
)
@Composable
fun MapView(
mapTileProvider: MapTileProvider,
initialViewPoint: MapViewPoint,
features: Map<FeatureId, MapFeature> = emptyMap(),
onClick: MapViewPoint.() -> Unit = {},
config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
buildFeatures: @Composable (FeatureBuilder.() -> Unit) = {},
) {
val featuresBuilder = MapFeatureBuilder(features)
featuresBuilder.buildFeatures()
MapView(mapTileProvider, { initialViewPoint }, featuresBuilder.build(), onClick, config, modifier)
}
@Composable
fun MapView(
mapTileProvider: MapTileProvider,
box: GmcBox,
features: Map<FeatureId, MapFeature> = emptyMap(),
onClick: MapViewPoint.() -> Unit = {},
config: MapViewConfig = MapViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
buildFeatures: @Composable (FeatureBuilder.() -> Unit) = {},
) {
val featuresBuilder = MapFeatureBuilder(features)
featuresBuilder.buildFeatures()
val computeViewPoint: (canvasSize: DpSize) -> MapViewPoint = { canvasSize ->
val zoom = log2(
min(
canvasSize.width.value / box.width,
canvasSize.height.value / box.height
) * PI / mapTileProvider.tileSize
)
MapViewPoint(box.center, zoom)
}
MapView(mapTileProvider, computeViewPoint, featuresBuilder.build(), onClick, config, modifier)
}

View File

@@ -0,0 +1,74 @@
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,4 +1,6 @@
package centre.sciprog.maps.compose
@file:Suppress("DEPRECATION")
package space.kscience.maps.compose
import kotlin.jvm.Synchronized

View File

@@ -0,0 +1,101 @@
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

@@ -0,0 +1,31 @@
package space.kscience.maps.compose
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import org.jetbrains.skia.Image
import kotlin.math.floor
public data class TileId(
val zoom: Int,
val i: Int,
val j: Int,
)
public data class MapTile(
val id: TileId,
val image: Image,
)
public interface MapTileProvider {
public fun CoroutineScope.loadTileAsync(tileId: TileId): Deferred<MapTile>
public val tileSize: Int get() = DEFAULT_TILE_SIZE
public fun toIndex(d: Float): Int = floor(d / tileSize).toInt()
public fun toCoordinate(i: Int): Float = (i * tileSize).toFloat()
public companion object {
public const val DEFAULT_TILE_SIZE: Int = 256
}
}

View File

@@ -0,0 +1,149 @@
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

@@ -0,0 +1,20 @@
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

@@ -0,0 +1,143 @@
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

@@ -0,0 +1,173 @@
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

@@ -0,0 +1,91 @@
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

@@ -1,284 +0,0 @@
package centre.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.Color
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.drawscope.*
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.*
import androidx.compose.ui.unit.*
import centre.sciprog.maps.*
import io.ktor.utils.io.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
*/
@OptIn(ExperimentalComposeUiApi::class)
@Composable
actual fun MapView(
mapTileProvider: MapTileProvider,
computeViewPoint: (canvasSize: DpSize) -> MapViewPoint,
features: Map<FeatureId, MapFeature>,
onClick: MapViewPoint.() -> Unit,
config: MapViewConfig,
modifier: Modifier,
) {
var canvasSize by remember { mutableStateOf(DpSize(512.dp, 512.dp)) }
var viewPointOverride: MapViewPoint? by remember {
mutableStateOf(
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)
}
} else {
null
}
)
}
val viewPoint by derivedStateOf { viewPointOverride ?: 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) }
val canvasModifier = modifier.pointerInput(Unit) {
forEachGesture {
awaitPointerEventScope {
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 ->
val (centerX, centerY) = rect.center
val centerGmc = DpOffset(centerX.toDp(), centerY.toDp()).toGeodetic()
val horizontalZoom: Float = log2(canvasSize.width.toPx() / rect.width)
val verticalZoom: Float = log2(canvasSize.height.toPx() / rect.height)
viewPointOverride = MapViewPoint(
centerGmc,
viewPoint.zoom + min(verticalZoom, horizontalZoom)
)
selectRect = null
}
} else {
val dragStart = change.position
val dpPos = DpOffset(dragStart.x.toDp(), dragStart.y.toDp())
onClick(MapViewPoint(dpPos.toGeodetic(), viewPoint.zoom))
drag(change.id) { dragChange ->
val dragAmount = dragChange.position - dragChange.previousPosition
viewPointOverride = viewPoint.move(
-dragAmount.x.toDp().value / tileScale,
+dragAmount.y.toDp().value / tileScale
)
}
}
}
}
}
}
}.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()
viewPointOverride = viewPoint.zoom(-change.scrollDelta.y.toDouble() * config.zoomSpeed, invariant)
}.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 MapLineFeature -> drawLine(feature.color, feature.a.toOffset(), feature.b.toOffset())
is MapBitmapImageFeature -> drawImage(feature.image, feature.position.toOffset())
is MapVectorImageFeature -> {
val offset = feature.position.toOffset()
translate(offset.x - feature.size.width / 2, offset.y - feature.size.height / 2) {
with(feature.painter) {
draw(feature.size)
}
}
}
is MapTextFeature -> drawIntoCanvas { canvas ->
val offset = feature.position.toOffset()
canvas.nativeCanvas.drawString(
feature.text,
offset.x + 5,
offset.y - 5,
Font().apply { size = 16f },
feature.color.toPaint()
)
}
is MapCustomFeature -> drawIntoCanvas { canvas ->
val offset = feature.position.toOffset()
feature.drawFeature(this, offset)
}
}
}
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,18 +1,12 @@
package centre.sciprog.maps.compose
package space.kscience.maps.compose
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toComposeImageBitmap
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.utils.io.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.*
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
@@ -21,28 +15,29 @@ import kotlin.io.path.*
/**
* A [MapTileProvider] based on Open Street Map API. With in-memory and file cache
*/
class OpenStreetMapTileProvider(
public class OpenStreetMapTileProvider(
private val client: HttpClient,
private val cacheDirectory: Path,
parallelism: Int = 1,
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<ImageBitmap>>(cacheCapacity)
private val cache = LruCache<TileId, Deferred<Image>>(cacheCapacity)
private fun TileId.osmUrl() = URL("https://tile.openstreetmap.org/${zoom}/${i}/${j}.png")
private fun TileId.osmUrl() = URL("$osmBaseUrl/${zoom}/${i}/${j}.png")
private fun TileId.cacheFilePath() = cacheDirectory.resolve("${zoom}/${i}/${j}.png")
/**
* Download and cache the tile image
*/
private fun CoroutineScope.downloadImageAsync(id: TileId) = async(Dispatchers.IO) {
private fun CoroutineScope.downloadImageAsync(id: TileId): Deferred<Image> = async(Dispatchers.IO) {
id.cacheFilePath()?.let { path ->
if (path.exists()) {
try {
return@async Image.makeFromEncoded(path.readBytes()).toComposeImageBitmap()
return@async Image.makeFromEncoded(path.readBytes())
} catch (ex: Exception) {
logger.debug { "Failed to load image from $path" }
path.deleteIfExists()
@@ -54,9 +49,7 @@ 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" }
@@ -64,7 +57,7 @@ class OpenStreetMapTileProvider(
path.writeBytes(byteArray)
}
Image.makeFromEncoded(byteArray).toComposeImageBitmap()
Image.makeFromEncoded(byteArray)
}
}
@@ -73,27 +66,25 @@ class OpenStreetMapTileProvider(
): Deferred<MapTile> {
//start image download
val imageDeferred = cache.getOrPut(tileId) {
val imageDeferred: Deferred<Image> = cache.getOrPut(tileId) {
downloadImageAsync(tileId)
}
//collect the result asynchronously
return async {
val image = try {
imageDeferred.await()
} catch (ex: Exception) {
cache.remove(tileId)
if(ex !is CancellationException) {
logger.error(ex) { "Failed to load tile image with id=$tileId" }
val image: Image = runCatching { imageDeferred.await() }.onFailure {
if(it !is CancellationException) {
logger.error(it) { "Failed to load tile image with id=$tileId" }
}
throw ex
}
cache.remove(tileId)
}.getOrThrow()
MapTile(tileId, image)
}
}
companion object {
public companion object {
private val logger = KotlinLogging.logger("OpenStreetMapCache")
}
}
}

View File

@@ -0,0 +1,55 @@
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

@@ -0,0 +1,43 @@
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()
}
}
}
}
}

24
maps-kt-core/README.md Normal file
View File

@@ -0,0 +1,24 @@
# 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

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

View File

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

View File

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

View File

@@ -1,62 +0,0 @@
package centre.sciprog.maps
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))
}
}
}
internal 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,39 +0,0 @@
package centre.sciprog.maps
import kotlin.math.*
class GmcBox(val a: GeodeticMapCoordinates, val b: GeodeticMapCoordinates)
fun GmcBox(latitudes: ClosedFloatingPointRange<Double>, longitudes: ClosedFloatingPointRange<Double>) = GmcBox(
GeodeticMapCoordinates.ofRadians(latitudes.start, longitudes.start),
GeodeticMapCoordinates.ofRadians(latitudes.endInclusive, longitudes.endInclusive)
)
val GmcBox.center
get() = GeodeticMapCoordinates.ofRadians(
(a.latitude + b.latitude) / 2,
(a.longitude + b.longitude) / 2
)
val GmcBox.left get() = min(a.longitude, b.longitude)
val GmcBox.right get() = max(a.longitude, b.longitude)
val GmcBox.top get() = max(a.latitude, b.latitude)
val GmcBox.bottom get() = min(a.latitude, b.latitude)
//TODO take curvature into account
val GmcBox.width get() = abs(a.longitude - b.longitude)
val GmcBox.height get() = abs(a.latitude - b.latitude)
/**
* Compute a minimal bounding box including all given boxes. Return null if collection is empty
*/
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(minLat..maxLat, minLong..maxLong)
}

View File

@@ -1,38 +0,0 @@
package centre.sciprog.maps
import kotlin.math.pow
/**
* Observable position on the map. Includes observation coordinate and [zoom] factor
*/
data class MapViewPoint(
val focus: GeodeticMapCoordinates,
val zoom: Double,
) {
val scaleFactor by lazy { WebMercatorProjection.scaleFactor(zoom) }
}
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)
}
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

@@ -1,55 +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 centre.sciprog.maps
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

@@ -0,0 +1,23 @@
package space.kscience.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 operator fun Distance.div(other: Distance): Double = kilometers / other.kilometers
public operator fun Distance.plus(other: Distance): Distance = Distance(kilometers + other.kilometers)
public operator fun Distance.minus(other: Distance): Distance = Distance(kilometers - other.kilometers)
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,86 @@
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

@@ -0,0 +1,91 @@
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

@@ -0,0 +1,358 @@
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

@@ -0,0 +1,17 @@
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

@@ -0,0 +1,96 @@
/*
* 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

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

View File

@@ -0,0 +1,36 @@
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

@@ -0,0 +1,28 @@
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

@@ -0,0 +1,21 @@
# 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

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

View File

@@ -0,0 +1,44 @@
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

@@ -0,0 +1,189 @@
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

@@ -0,0 +1,283 @@
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

@@ -0,0 +1,66 @@
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

@@ -0,0 +1,86 @@
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

@@ -0,0 +1,56 @@
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

@@ -0,0 +1,365 @@
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

@@ -0,0 +1,147 @@
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