Compare commits

..

179 Commits

Author SHA1 Message Date
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
f218853544
add deployments 2022-07-17 10:31:45 +03:00
2e8de0b183
add deployments 2022-07-17 10:21:11 +03:00
a8b304bd20
add deployments 2022-07-17 10:05:06 +03:00
7e0acaeca9
Merge remote-tracking branch 'space/main' 2022-07-16 21:51:13 +03:00
f49ff34a18
Fix tile loading lock 2022-07-16 21:50:47 +03:00
9f6386a8c2
Project splitting and deployment 2022-07-16 21:34:19 +03:00
Alexander Nozik
3787e990ae
Merge pull request #1 from ArystanK/main
Added function to draw custom markers with Kotlin DSL Canvas API
2022-07-16 19:28:06 +03:00
7cfc76f2c7 Merge TAVRIDA-MR-6: limit-parallel-requests 2022-07-16 16:24:39 +00:00
7a243780d1
Refactor papallel doanwload 2022-07-16 19:22:22 +03:00
a.kalmakhanov
6b7f0d1015 onFeatureClick removed completely 2022-07-16 21:44:05 +06:00
a.kalmakhanov
ad058e14b5 onFeatureClick removed 2022-07-16 21:40:38 +06:00
Alexander Nozik
dd53662038
Create LICENSE 2022-07-16 15:57:41 +03:00
a.kalmakhanov
b520d2f93a Added function to draw custom markers with Kotlin DSL Canvas API. FeatureId can be extended to hold info about a feature??? I added unused onFeatureClick, which can be latter implemented with pointer input, where it will check coordinates with offset? Features need to hold info about their coordinates? 2022-07-16 14:58:56 +06:00
1ebde3a7a6 update README 2022-07-15 13:52:12 +00:00
Lev Shagalov
96844cd526 LruCache internal 2022-07-15 10:52:26 +03:00
Lev Shagalov
e56ed96fdb Merge branch 'main' into limit-parallel-requests
# Conflicts:
#	src/jvmMain/kotlin/Main.kt
#	src/jvmMain/kotlin/centre/sciprog/maps/compose/MapViewJvm.kt
2022-07-15 10:41:03 +03:00
Lev Shagalov
5d3db81c4f loadTileAsync refactor
loadTileAsync consumes all tileIds
2022-07-15 10:39:04 +03:00
Lev Shagalov
fc0f223766 LruCache with linkedMapOf 2022-07-15 10:17:42 +03:00
ec2dcdccaa
OnClick returns MapViewPoint 2022-07-15 09:31:51 +03:00
Lev Shagalov
0694cd6a07 limit parallel requests 2022-07-15 09:30:36 +03:00
Lev Shagalov
416328e320 Merge branch 'main' into limit-parallel-requests 2022-07-15 09:28:07 +03:00
5984de70b4 Merge TAVRIDA-MR-4: feature/boundbox 2022-07-15 06:26:40 +00:00
Lev Shagalov
7dd59dbf2a LruCache 2022-07-15 09:05:57 +03:00
d720470ea2 Merge TAVRIDA-MR-5: Async load 2022-07-14 18:27:11 +00:00
f92ccb4838
Async load 2022-07-14 20:53:41 +03:00
d3809aca8d
Working box by features 2022-07-14 20:32:31 +03:00
9392c0f991
Working box by features (mostly) 2022-07-14 20:19:57 +03:00
1541fb4f39
[WIP] bounding boxes 2022-07-14 10:36:16 +03:00
134 changed files with 89179 additions and 817 deletions

2
.gitignore vendored
View File

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

45
.space.kts Normal file
View File

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

15
CHANGELOG.md Normal file
View File

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

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

73
README.md Normal file
View File

@ -0,0 +1,73 @@
# 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/polygon-editor](demo/polygon-editor)
>
>
> **Maturity**: EXPERIMENTAL
### [demo/scheme](demo/scheme)
>
>
> **Maturity**: EXPERIMENTAL
### [demo/trajectory-playground](demo/trajectory-playground)
>
>
> **Maturity**: EXPERIMENTAL

View File

@ -1,58 +1,51 @@
import org.jetbrains.compose.compose import space.kscience.gradle.isInDevelopment
import org.jetbrains.compose.desktop.application.dsl.TargetFormat import space.kscience.gradle.useApache2Licence
import space.kscience.gradle.useSPCTeam
plugins { plugins {
kotlin("multiplatform") id("space.kscience.gradle.project")
id("org.jetbrains.compose")
`maven-publish`
} }
group = "center.sciptog" val kmathVersion: String by extra("0.3.1-dev-RC")
version = "1.0-SNAPSHOT"
repositories { allprojects {
google() group = "center.sciprog"
mavenCentral() version = "0.2.2"
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
val ktorVersion by extra("2.0.3") repositories {
mavenLocal()
kotlin { maven("https://repo.kotlin.link")
jvm { maven("https://maven.pkg.jetbrains.space/spc/p/sci/dev")
compilations.all {
kotlinOptions.jvmTarget = "11"
}
withJava()
}
sourceSets {
commonMain{
dependencies{
api(compose.runtime)
api(compose.foundation)
api(compose.material)
api("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("io.github.microutils:kotlin-logging:2.1.23")
}
}
val jvmMain by getting {
dependencies {
implementation(compose.desktop.currentOs)
implementation("ch.qos.logback:logback-classic:1.2.11")
}
}
val jvmTest by getting
} }
} }
compose.desktop { ksciencePublish {
application { pom("https://github.com/SciProgCentre/maps-kt") {
mainClass = "MainKt" useApache2Licence()
nativeDistributions { useSPCTeam()
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) }
packageName = "maps-kt-compose" github("SciProgCentre", "maps-kt")
packageVersion = "1.0.0" space(
if (isInDevelopment) {
"https://maven.pkg.jetbrains.space/spc/p/sci/dev"
} else {
"https://maven.pkg.jetbrains.space/spc/p/sci/maven"
} }
)
sonatype()
}
subprojects {
repositories {
maven("https://maven.pkg.jetbrains.space/mipt-npm/p/sci/dev")
google()
mavenCentral()
maven("https://repo.kotlin.link")
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
} }
} }
readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md")

4
demo/README.md Normal file
View File

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

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,38 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
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("ch.qos.logback:logback-classic:1.2.11")
}
}
val jvmTest by getting
}
}
compose {
desktop {
application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "maps-compose-demo"
packageVersion = "1.0.0"
}
}
}
}

View File

@ -0,0 +1,171 @@
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 center.sciprog.attributes.Attributes
import center.sciprog.maps.compose.*
import center.sciprog.maps.coordinates.*
import center.sciprog.maps.features.*
import center.sciprog.maps.geojson.geoJson
import io.ktor.client.HttpClient
import io.ktor.client.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.kmath.geometry.Angle
import space.kscience.kmath.geometry.degrees
import space.kscience.kmath.geometry.radians
import java.nio.file.Path
import kotlin.math.PI
import kotlin.random.Random
public fun GeodeticMapCoordinates.toShortString(): String =
"${(latitude.degrees).toString().take(6)}:${(longitude.degrees).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")!!)
.modifyAttribute(ColorAttribute, Color.Blue)
.modifyAttribute(AlphaAttribute, 0.4f)
icon(pointOne, Icons.Filled.Home)
val marker1 = rectangle(55.744 to 38.614, size = DpSize(10.dp, 10.dp)).color(Color.Magenta)
val marker2 = rectangle(55.8 to 38.5, size = DpSize(10.dp, 10.dp)).color(Color.Magenta)
val marker3 = rectangle(56.0 to 38.5, size = DpSize(10.dp, 10.dp)).color(Color.Magenta)
draggableLine(marker1, marker2, id = "line 1").color(Color.Red).onClick {
println("line 1 clicked")
}
draggableLine(marker2, marker3, id = "line 2").color(Color.DarkGray).onClick {
println("line 2 clicked")
}
draggableLine(marker3, marker1, id = "line 3").color(Color.Blue).onClick {
println("line 3 clicked")
}
multiLine(
points = listOf(
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
),
)
//remember feature ID
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).degrees*10 % 1f).toFloat(),
green = ((gmc.longitude + Angle.pi).degrees*10 % 1f).toFloat(),
blue = 0f
).copy(alpha = 0.3f)
}
centerCoordinates.filterNotNull().onEach {
group(id = "center") {
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 ->
ref.onClick(PointerMatcher.Primary) {
println("Click on ${ref.id}")
//draw in top-level scope
with(this@MapView) {
multiLine(
ref.resolve().points,
attributes = Attributes(ZAttribute, 10f),
id = "selected",
).modifyAttribute(StrokeAttribute, 4f).color(Color.Magenta)
}
}
}
}
}
}
fun main() = application {
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,36 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
val ktorVersion: String by rootProject.extra
kotlin {
jvm()
jvmToolchain(11)
sourceSets {
val jvmMain by getting {
dependencies {
implementation(projects.mapsKtScheme)
implementation(compose.desktop.currentOs)
implementation("ch.qos.logback:logback-classic:1.2.11")
}
}
val jvmTest by getting
}
}
compose{
desktop {
application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "polygon-editor-demo"
packageVersion = "1.0.0"
}
}
}
}

View File

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

4
demo/scheme/README.md Normal file
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,36 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
val ktorVersion: String by rootProject.extra
kotlin {
jvm()
jvmToolchain(11)
sourceSets {
val jvmMain by getting {
dependencies {
implementation(projects.mapsKtScheme)
implementation(compose.desktop.currentOs)
implementation("ch.qos.logback:logback-classic:1.2.11")
}
}
val jvmTest by getting
}
}
compose{
desktop {
application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "scheme-compose-demo"
packageVersion = "1.0.0"
}
}
}
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 KiB

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

View File

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

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,4 +1,10 @@
kotlin.code.style=official kotlin.code.style=official
kotlin.version=1.6.10
agp.version=4.2.2 compose.version=1.4.0
compose.version=1.1.1 agp.version=7.4.2
android.useAndroidX=true
org.jetbrains.compose.experimental.jscanvas.enabled=true
org.gradle.jvmargs=-Xmx4096m
toolsVersion=0.14.6-kotlin-1.8.20

View File

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

2213
gradle/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

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

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,42 @@
package center.sciprog.maps.compose
import kotlin.jvm.Synchronized
internal class LruCache<K, V>(
private var capacity: Int,
) {
private val cache = linkedMapOf<K, V>()
@Synchronized
fun put(key: K, value: V){
if (cache.size >= capacity) {
cache.remove(cache.iterator().next().key)
}
cache[key] = value
}
operator fun get(key: K): V? {
val value = cache[key]
if (value != null) {
cache.remove(key)
cache[key] = value
}
return value
}
@Synchronized
fun remove(key: K) {
cache.remove(key)
}
@Synchronized
fun getOrPut(key: K, factory: () -> V): V = get(key) ?: factory().also { put(key, it) }
@Synchronized
fun reset(newCapacity: Int? = null) {
cache.clear()
capacity = newCapacity ?: capacity
}
}

View File

@ -0,0 +1,31 @@
package center.sciprog.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,70 @@
package center.sciprog.maps.compose
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import center.sciprog.maps.coordinates.Gmc
import center.sciprog.maps.features.*
@Composable
public expect fun MapView(
viewScope: MapViewScope,
features: FeatureGroup<Gmc>,
modifier: Modifier = Modifier.fillMaxSize(),
)
/**
* A builder for a Map with static features.
*/
@Composable
public fun MapView(
mapTileProvider: MapTileProvider,
features: FeatureGroup<Gmc>,
initialViewPoint: ViewPoint<Gmc>? = null,
initialRectangle: Rectangle<Gmc>? = null,
config: ViewConfig<Gmc> = ViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
) {
val mapState: MapViewScope = MapViewScope.remember(
mapTileProvider,
config,
initialViewPoint = initialViewPoint,
initialRectangle = initialRectangle ?: features.getBoundingBox(Float.MAX_VALUE),
)
MapView(mapState, features, modifier)
}
/**
* Draw a map using convenient parameters. If neither [initialViewPoint], noe [initialRectangle] is defined,
* use map features to infer view region.
* @param initialViewPoint The view point of the map using center and zoom. Is used if provided
* @param initialRectangle The rectangle to be used for view point computation. Used if [initialViewPoint] is not defined.
* @param buildFeatures - a builder for features
*/
@Composable
public fun MapView(
mapTileProvider: MapTileProvider,
initialViewPoint: ViewPoint<Gmc>? = null,
initialRectangle: Rectangle<Gmc>? = null,
config: ViewConfig<Gmc> = ViewConfig(),
modifier: Modifier = Modifier.fillMaxSize(),
buildFeatures: FeatureGroup<Gmc>.() -> Unit = {},
) {
val featureState = FeatureGroup.remember(WebMercatorSpace, buildFeatures)
val mapState: MapViewScope = MapViewScope.remember(
mapTileProvider,
config,
initialViewPoint = initialViewPoint,
initialRectangle = initialRectangle ?: featureState.features.computeBoundingBox(
WebMercatorSpace,
Float.MAX_VALUE
),
)
MapView(mapState, featureState, modifier)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,91 @@
package center.sciprog.maps.compose
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.client.statement.readBytes
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import mu.KotlinLogging
import org.jetbrains.skia.Image
import java.net.URL
import java.nio.file.Path
import kotlin.io.path.*
/**
* A [MapTileProvider] based on Open Street Map API. With in-memory and file cache
*/
public class OpenStreetMapTileProvider(
private val client: HttpClient,
private val cacheDirectory: Path,
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.cacheFilePath() = cacheDirectory.resolve("${zoom}/${i}/${j}.png")
/**
* Download and cache the tile image
*/
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())
} catch (ex: Exception) {
logger.debug { "Failed to load image from $path" }
path.deleteIfExists()
}
}
}
//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" }
id.cacheFilePath()?.let { path ->
logger.debug { "Caching map tile $id to $path" }
path.parent.createDirectories()
path.writeBytes(byteArray)
}
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 {
logger.error(it) { "Failed to load tile image with id=$tileId" }
cache.remove(tileId)
}.getOrThrow()
MapTile(tileId, image)
}
}
public companion object {
private val logger = KotlinLogging.logger("OpenStreetMapCache")
}
}

View File

@ -0,0 +1,146 @@
package center.sciprog.maps.compose
import androidx.compose.foundation.Canvas
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.clipRect
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.graphics.toComposeImageBitmap
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import center.sciprog.attributes.z
import center.sciprog.maps.coordinates.Gmc
import center.sciprog.maps.features.FeatureGroup
import center.sciprog.maps.features.PainterFeature
import center.sciprog.maps.features.drawFeature
import center.sciprog.maps.features.zoomRange
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import mu.KotlinLogging
import org.jetbrains.skia.Image
import org.jetbrains.skia.Paint
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
private fun Color.toPaint(): Paint = Paint().apply {
isAntiAlias = true
color = toArgb()
}
private fun IntRange.intersect(other: IntRange) = max(first, other.first)..min(last, other.last)
private val logger = KotlinLogging.logger("MapView")
/**
* A component that renders map and provides basic map manipulation capabilities
*/
@Composable
public actual fun MapView(
viewScope: MapViewScope,
features: FeatureGroup<Gmc>,
modifier: Modifier,
): Unit = with(viewScope) {
val mapTiles = remember(mapTileProvider) { mutableStateMapOf<TileId, Image>() }
// Load tiles asynchronously
LaunchedEffect(viewPoint, canvasSize) {
with(mapTileProvider) {
val indexRange = 0 until 2.0.pow(intZoom).toInt()
val left = centerCoordinates.x - canvasSize.width.value / 2 / tileScale
val right = centerCoordinates.x + canvasSize.width.value / 2 / tileScale
val horizontalIndices: IntRange = (toIndex(left)..toIndex(right)).intersect(indexRange)
val top = (centerCoordinates.y + canvasSize.height.value / 2 / tileScale)
val bottom = (centerCoordinates.y - canvasSize.height.value / 2 / tileScale)
val verticalIndices: IntRange = (toIndex(bottom)..toIndex(top)).intersect(indexRange)
for (j in verticalIndices) {
for (i in horizontalIndices) {
val id = TileId(intZoom, i, j)
//ensure that failed tiles do not fail the application
supervisorScope {
//start all
val deferred = loadTileAsync(id)
//wait asynchronously for it to finish
launch {
try {
val tile = deferred.await()
mapTiles[tile.id] = tile.image
} catch (ex: Exception) {
//displaying the error is maps responsibility
logger.error(ex) { "Failed to load tile with id=$id" }
}
}
}
mapTiles.keys.filter {
it.zoom != intZoom || it.j !in verticalIndices || it.i !in horizontalIndices
}.forEach {
mapTiles.remove(it)
}
}
}
}
}
key(viewScope, features) {
val painterCache: Map<PainterFeature<Gmc>, Painter> =
features.features.filterIsInstance<PainterFeature<Gmc>>().associateWith { it.getPainter() }
Canvas(modifier = modifier.mapControls(viewScope, features)) {
if (canvasSize != size.toDpSize()) {
logger.debug { "Recalculate canvas. Size: $size" }
config.onCanvasSizeChange(canvasSize)
canvasSize = size.toDpSize()
}
clipRect {
val tileSize = IntSize(
ceil((mapTileProvider.tileSize.dp * tileScale).toPx()).toInt(),
ceil((mapTileProvider.tileSize.dp * tileScale).toPx()).toInt()
)
mapTiles.forEach { (id, image) ->
//converting back from tile index to screen offset
val offset = IntOffset(
(canvasSize.width / 2 + (mapTileProvider.toCoordinate(id.i).dp - centerCoordinates.x.dp) * tileScale).roundToPx(),
(canvasSize.height / 2 + (mapTileProvider.toCoordinate(id.j).dp - centerCoordinates.y.dp) * tileScale).roundToPx()
)
drawImage(
image = image.toComposeImageBitmap(),
dstOffset = offset,
dstSize = tileSize
)
}
features.featureMap.values.sortedBy { it.z }
.filter { viewPoint.zoom in it.zoomRange }
.forEach { feature ->
drawFeature(viewScope, painterCache, feature)
}
}
selectRect?.let { dpRect ->
val rect = dpRect.toRect()
drawRect(
color = Color.Blue,
topLeft = rect.topLeft,
size = rect.size,
alpha = 0.5f,
style = Stroke(
width = 2f,
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)
)
)
}
}
}
}

View File

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

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

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

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

@ -0,0 +1,34 @@
plugins {
id("space.kscience.gradle.mpp")
`maven-publish`
}
val kmathVersion: String by rootProject.extra
kscience{
jvm()
js()
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

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

View File

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

View File

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

View File

@ -0,0 +1,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 center.sciprog.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.radians + (pc.x / ellipsoid.equatorRadius),
)
} else {
GeodeticMapCoordinates.ofRadians(
cphi2(exp(-(pc.y / ellipsoid.equatorRadius))),
baseLongitude.radians + (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).radians,
y = ellipsoid.equatorRadius * ln(tan(Angle.pi / 4 + gmc.latitude / 2))
)
} else {
val sinPhi = sin(gmc.latitude)
ProjectionCoordinates(
x = ellipsoid.equatorRadius * (gmc.longitude - baseLongitude).radians,
y = ellipsoid.equatorRadius * ln(
tan(Angle.pi / 4 + gmc.latitude / 2) * ((1 - e * sinPhi) / (1 + e * sinPhi)).pow(e / 2)
)
)
}
}
public companion object {
public val MAXIMUM_LATITUDE: Angle = 85.05113.degrees
}
}

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

32
maps-kt-geojson/README.md Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

32
maps-kt-scheme/README.md Normal file
View File

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

View File

@ -0,0 +1,156 @@
public final class center/sciprog/maps/scheme/SchemeFeaturesKt {
public static final fun arc (Lcenter/sciprog/maps/features/FeatureGroup;Lkotlin/Pair;FLspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun arc$default (Lcenter/sciprog/maps/features/FeatureGroup;Lkotlin/Pair;FLspace/kscience/kmath/geometry/Angle;Lspace/kscience/kmath/geometry/Angle;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun background (Lcenter/sciprog/maps/features/FeatureGroup;FFLcenter/sciprog/maps/scheme/XY;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun background$default (Lcenter/sciprog/maps/features/FeatureGroup;FFLcenter/sciprog/maps/scheme/XY;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun circle-d8LSEHM (Lcenter/sciprog/maps/features/FeatureGroup;Lkotlin/Pair;FLjava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun circle-d8LSEHM$default (Lcenter/sciprog/maps/features/FeatureGroup;Lkotlin/Pair;FLjava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun draw (Lcenter/sciprog/maps/features/FeatureGroup;Lkotlin/Pair;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun draw$default (Lcenter/sciprog/maps/features/FeatureGroup;Lkotlin/Pair;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun image-J7Fpp20 (Lcenter/sciprog/maps/features/FeatureGroup;Lkotlin/Pair;Landroidx/compose/ui/graphics/vector/ImageVector;JLjava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun image-J7Fpp20$default (Lcenter/sciprog/maps/features/FeatureGroup;Lkotlin/Pair;Landroidx/compose/ui/graphics/vector/ImageVector;JLjava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun line (Lcenter/sciprog/maps/features/FeatureGroup;Lkotlin/Pair;Lkotlin/Pair;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun line$default (Lcenter/sciprog/maps/features/FeatureGroup;Lkotlin/Pair;Lkotlin/Pair;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun pixelMap (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/maps/features/Rectangle;FFLjava/lang/String;Lkotlin/jvm/functions/Function1;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun pixelMap$default (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/maps/features/Rectangle;FFLjava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
public static final fun text (Lcenter/sciprog/maps/features/FeatureGroup;Lkotlin/Pair;Ljava/lang/String;Ljava/lang/String;)Lcenter/sciprog/maps/features/FeatureRef;
public static synthetic fun text$default (Lcenter/sciprog/maps/features/FeatureGroup;Lkotlin/Pair;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcenter/sciprog/maps/features/FeatureRef;
}
public final class center/sciprog/maps/scheme/SchemeViewKt {
public static final fun SchemeView (Lcenter/sciprog/maps/features/FeatureGroup;Lcenter/sciprog/maps/features/ViewPoint;Lcenter/sciprog/maps/features/Rectangle;Lcenter/sciprog/maps/features/ViewConfig;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V
public static final fun SchemeView (Lcenter/sciprog/maps/features/ViewPoint;Lcenter/sciprog/maps/features/Rectangle;Lcenter/sciprog/maps/features/ViewConfig;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V
public static final fun SchemeView (Lcenter/sciprog/maps/scheme/XYViewScope;Lcenter/sciprog/maps/features/FeatureGroup;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V
public static final fun computeViewPoint-6HolHcs (Lcenter/sciprog/maps/features/Rectangle;J)Lcenter/sciprog/maps/features/ViewPoint;
public static synthetic fun computeViewPoint-6HolHcs$default (Lcenter/sciprog/maps/features/Rectangle;JILjava/lang/Object;)Lcenter/sciprog/maps/features/ViewPoint;
}
public final class center/sciprog/maps/scheme/XY : space/kscience/kmath/geometry/Vector2D {
public static final field $stable I
public fun <init> (FF)V
public final fun component1 ()F
public final fun component2 ()F
public final fun copy (FF)Lcenter/sciprog/maps/scheme/XY;
public static synthetic fun copy$default (Lcenter/sciprog/maps/scheme/XY;FFILjava/lang/Object;)Lcenter/sciprog/maps/scheme/XY;
public fun equals (Ljava/lang/Object;)Z
public fun getX ()Ljava/lang/Float;
public synthetic fun getX ()Ljava/lang/Object;
public fun getY ()Ljava/lang/Float;
public synthetic fun getY ()Ljava/lang/Object;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class center/sciprog/maps/scheme/XYCoordinateSpace : center/sciprog/maps/features/CoordinateSpace {
public static final field $stable I
public static final field INSTANCE Lcenter/sciprog/maps/scheme/XYCoordinateSpace;
public fun Rectangle (Lcenter/sciprog/maps/scheme/XY;Lcenter/sciprog/maps/scheme/XY;)Lcenter/sciprog/maps/features/Rectangle;
public synthetic fun Rectangle (Ljava/lang/Object;Ljava/lang/Object;)Lcenter/sciprog/maps/features/Rectangle;
public fun Rectangle-Iw8sGQE (Lcenter/sciprog/maps/scheme/XY;FJ)Lcenter/sciprog/maps/features/Rectangle;
public synthetic fun Rectangle-Iw8sGQE (Ljava/lang/Object;FJ)Lcenter/sciprog/maps/features/Rectangle;
public fun ViewPoint (Lcenter/sciprog/maps/scheme/XY;F)Lcenter/sciprog/maps/features/ViewPoint;
public synthetic fun ViewPoint (Ljava/lang/Object;F)Lcenter/sciprog/maps/features/ViewPoint;
public fun getDefaultViewPoint ()Lcenter/sciprog/maps/features/ViewPoint;
public fun isInsidePolygon (Lcenter/sciprog/maps/scheme/XY;Ljava/util/List;)Z
public synthetic fun isInsidePolygon (Ljava/lang/Object;Ljava/util/List;)Z
public fun moveBy (Lcenter/sciprog/maps/features/ViewPoint;Lcenter/sciprog/maps/scheme/XY;)Lcenter/sciprog/maps/features/ViewPoint;
public synthetic fun moveBy (Lcenter/sciprog/maps/features/ViewPoint;Ljava/lang/Object;)Lcenter/sciprog/maps/features/ViewPoint;
public fun offsetTo-LQHvzoY (Lcenter/sciprog/maps/scheme/XY;Lcenter/sciprog/maps/scheme/XY;F)J
public synthetic fun offsetTo-LQHvzoY (Ljava/lang/Object;Ljava/lang/Object;F)J
public fun withCenter (Lcenter/sciprog/maps/features/Rectangle;Lcenter/sciprog/maps/scheme/XY;)Lcenter/sciprog/maps/features/Rectangle;
public synthetic 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;FLcenter/sciprog/maps/scheme/XY;)Lcenter/sciprog/maps/features/ViewPoint;
public synthetic fun zoomBy (Lcenter/sciprog/maps/features/ViewPoint;FLjava/lang/Object;)Lcenter/sciprog/maps/features/ViewPoint;
}
public final class center/sciprog/maps/scheme/XYKt {
public static final fun Rectangle (Lcenter/sciprog/maps/features/CoordinateSpace;Lcenter/sciprog/maps/scheme/XY;FF)Lcenter/sciprog/maps/features/Rectangle;
public static final fun getBottom (Lcenter/sciprog/maps/features/Rectangle;)F
public static final fun getHeight (Lcenter/sciprog/maps/features/Rectangle;)F
public static final fun getLeft (Lcenter/sciprog/maps/features/Rectangle;)F
public static final fun getLeftTop (Lcenter/sciprog/maps/features/Rectangle;)Lcenter/sciprog/maps/scheme/XY;
public static final fun getRight (Lcenter/sciprog/maps/features/Rectangle;)F
public static final fun getRightBottom (Lcenter/sciprog/maps/features/Rectangle;)Lcenter/sciprog/maps/scheme/XY;
public static final fun getTop (Lcenter/sciprog/maps/features/Rectangle;)F
public static final fun getWidth (Lcenter/sciprog/maps/features/Rectangle;)F
}
public final class center/sciprog/maps/scheme/XYViewPoint : center/sciprog/maps/features/ViewPoint {
public static final field $stable I
public fun <init> (Lcenter/sciprog/maps/scheme/XY;F)V
public synthetic fun <init> (Lcenter/sciprog/maps/scheme/XY;FILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcenter/sciprog/maps/scheme/XY;
public final fun component2 ()F
public final fun copy (Lcenter/sciprog/maps/scheme/XY;F)Lcenter/sciprog/maps/scheme/XYViewPoint;
public static synthetic fun copy$default (Lcenter/sciprog/maps/scheme/XYViewPoint;Lcenter/sciprog/maps/scheme/XY;FILjava/lang/Object;)Lcenter/sciprog/maps/scheme/XYViewPoint;
public fun equals (Ljava/lang/Object;)Z
public fun getFocus ()Lcenter/sciprog/maps/scheme/XY;
public synthetic fun getFocus ()Ljava/lang/Object;
public fun getZoom ()F
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class center/sciprog/maps/scheme/XYViewScope : center/sciprog/maps/features/CoordinateViewScope {
public static final field $stable I
public static final field Companion Lcenter/sciprog/maps/scheme/XYViewScope$Companion;
public fun <init> (Lcenter/sciprog/maps/features/ViewConfig;)V
public fun computeViewPoint (Lcenter/sciprog/maps/features/Rectangle;)Lcenter/sciprog/maps/features/ViewPoint;
public fun getSpace ()Lcenter/sciprog/maps/features/CoordinateSpace;
public fun moveBy-VpY3zN4 (Lcenter/sciprog/maps/features/ViewPoint;FF)Lcenter/sciprog/maps/features/ViewPoint;
public fun toCoordinates-jo-Fl9I (J)Lcenter/sciprog/maps/scheme/XY;
public synthetic fun toCoordinates-jo-Fl9I (J)Ljava/lang/Object;
public fun toDpOffset-gVRvYmI (Lcenter/sciprog/maps/scheme/XY;)J
public synthetic fun toDpOffset-gVRvYmI (Ljava/lang/Object;)J
public fun toDpRect (Lcenter/sciprog/maps/features/Rectangle;)Landroidx/compose/ui/unit/DpRect;
}
public final class center/sciprog/maps/scheme/XYViewScope$Companion {
public final fun remember (Lcenter/sciprog/maps/features/ViewConfig;Lcenter/sciprog/maps/features/ViewPoint;Lcenter/sciprog/maps/features/Rectangle;Landroidx/compose/runtime/Composer;II)Lcenter/sciprog/maps/scheme/XYViewScope;
}
public final class center/sciprog/maps/svg/ExportToSvgKt {
public static final fun exportToSvg (Lcenter/sciprog/maps/svg/FeatureStateSnapshot;Lcenter/sciprog/maps/features/ViewPoint;DDLjava/nio/file/Path;)V
public static final fun generateSvg (Lcenter/sciprog/maps/svg/FeatureStateSnapshot;Lcenter/sciprog/maps/features/ViewPoint;DDLjava/lang/String;)Ljava/lang/String;
public static synthetic fun generateSvg$default (Lcenter/sciprog/maps/svg/FeatureStateSnapshot;Lcenter/sciprog/maps/features/ViewPoint;DDLjava/lang/String;ILjava/lang/Object;)Ljava/lang/String;
public static final fun snapshot (Lcenter/sciprog/maps/features/FeatureGroup;Landroidx/compose/runtime/Composer;I)Lcenter/sciprog/maps/svg/FeatureStateSnapshot;
}
public final class center/sciprog/maps/svg/FeatureStateSnapshot {
public static final field $stable I
public fun <init> (Ljava/util/Map;Ljava/util/Map;)V
public final fun getFeatures ()Ljava/util/Map;
}
public final class center/sciprog/maps/svg/SvgDrawScope : androidx/compose/ui/graphics/drawscope/DrawScope {
public static final field $stable I
public synthetic fun <init> (Lorg/jfree/svg/SVGGraphics2D;JFILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lorg/jfree/svg/SVGGraphics2D;JFLkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun drawArc-illE91I (Landroidx/compose/ui/graphics/Brush;FFZJJFLandroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawArc-yD3GUKo (JFFZJJFLandroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawCircle-V9BoPsw (Landroidx/compose/ui/graphics/Brush;FJFLandroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawCircle-VaOC9Bg (JFJFLandroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawImage-9jGpkUE (Landroidx/compose/ui/graphics/ImageBitmap;JJJJFLandroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawImage-AZ2fEMs (Landroidx/compose/ui/graphics/ImageBitmap;JJJJFLandroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/graphics/ColorFilter;II)V
public fun drawImage-gbVJVH8 (Landroidx/compose/ui/graphics/ImageBitmap;JFLandroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawLine-1RTmtNc (Landroidx/compose/ui/graphics/Brush;JJFILandroidx/compose/ui/graphics/PathEffect;FLandroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawLine-NGM6Ib0 (JJJFILandroidx/compose/ui/graphics/PathEffect;FLandroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawOval-AsUm42w (Landroidx/compose/ui/graphics/Brush;JJFLandroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawOval-n-J9OG0 (JJJFLandroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawPath-GBMwjPU (Landroidx/compose/ui/graphics/Path;Landroidx/compose/ui/graphics/Brush;FLandroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawPath-LG529CI (Landroidx/compose/ui/graphics/Path;JFLandroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawPoints-F8ZwMP8 (Ljava/util/List;IJFILandroidx/compose/ui/graphics/PathEffect;FLandroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawPoints-Gsft0Ws (Ljava/util/List;ILandroidx/compose/ui/graphics/Brush;FILandroidx/compose/ui/graphics/PathEffect;FLandroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawRect-AsUm42w (Landroidx/compose/ui/graphics/Brush;JJFLandroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawRect-n-J9OG0 (JJJFLandroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawRoundRect-ZuiqVtQ (Landroidx/compose/ui/graphics/Brush;JJJFLandroidx/compose/ui/graphics/drawscope/DrawStyle;Landroidx/compose/ui/graphics/ColorFilter;I)V
public fun drawRoundRect-u-Aw5IA (JJJJLandroidx/compose/ui/graphics/drawscope/DrawStyle;FLandroidx/compose/ui/graphics/ColorFilter;I)V
public final fun drawText-xwkQ0AY (Ljava/lang/String;FFLjava/awt/Font;J)V
public fun getDensity ()F
public fun getDrawContext ()Landroidx/compose/ui/graphics/drawscope/DrawContext;
public fun getFontScale ()F
public fun getLayoutDirection ()Landroidx/compose/ui/unit/LayoutDirection;
}

View File

@ -0,0 +1,31 @@
plugins {
id("space.kscience.gradle.mpp")
id("org.jetbrains.compose")
`maven-publish`
}
kscience{
jvm()
}
kotlin {
sourceSets {
commonMain {
dependencies {
api(projects.mapsKtFeatures)
api("io.github.microutils:kotlin-logging:2.1.23")
api(compose.foundation)
}
}
val jvmMain by getting {
dependencies {
implementation("org.jfree:org.jfree.svg:5.0.4")
api(compose.desktop.currentOs)
}
}
}
}
//java {
// targetCompatibility = JVM_TARGET
//}

View File

@ -0,0 +1,65 @@
package center.sciprog.maps.scheme
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import center.sciprog.maps.features.CoordinateSpace
import center.sciprog.maps.features.Rectangle
import center.sciprog.maps.features.ViewPoint
import space.kscience.kmath.geometry.Vector2D
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
public data class XY(override val x: Float, override val y: Float): Vector2D<Float>
internal data class XYRectangle(
override val a: XY,
override val b: XY,
) : Rectangle<XY> {
override fun contains(point: XY): Boolean = point.x in a.x..b.x && point.y in a.y..b.y
override val center get() = XY((a.x + b.x) / 2, (a.y + b.y) / 2)
// companion object {
// fun square(center: XY, height: Float, width: Float): XYRectangle = XYRectangle(
// XY(center.x - width / 2, center.y + height / 2),
// XY(center.x + width / 2, center.y - height / 2),
// )
// }
}
public val Rectangle<XY>.top: Float get() = max(a.y, b.y)
public val Rectangle<XY>.bottom: Float get() = min(a.y, b.y)
public val Rectangle<XY>.right: Float get() = max(a.x, b.x)
public val Rectangle<XY>.left: Float get() = min(a.x, b.x)
public val Rectangle<XY>.width: Float get() = abs(a.x - b.x)
public val Rectangle<XY>.height: Float get() = abs(a.y - b.y)
public val Rectangle<XY>.leftTop: XY get() = XY(left, top)
public val Rectangle<XY>.rightBottom: XY get() = XY(right, bottom)
internal val defaultCanvasSize = DpSize(512.dp, 512.dp)
public data class XYViewPoint(
override val focus: XY,
override val zoom: Float = 1f,
) : ViewPoint<XY>
public fun CoordinateSpace<XY>.Rectangle(
center: XY,
height: Float,
width: Float,
): Rectangle<XY> {
val a = XY(
center.x - (width / 2),
center.y - (height / 2)
)
val b = XY(
center.x + (width / 2),
center.y + (height / 2)
)
return XYRectangle(a, b)
}

View File

@ -0,0 +1,89 @@
package center.sciprog.maps.scheme
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import center.sciprog.maps.features.CoordinateSpace
import center.sciprog.maps.features.Rectangle
import center.sciprog.maps.features.ViewPoint
import kotlin.math.abs
import kotlin.math.pow
public object XYCoordinateSpace : CoordinateSpace<XY> {
override fun Rectangle(first: XY, second: XY): Rectangle<XY> =
XYRectangle(first, second)
override fun Rectangle(center: XY, zoom: Float, size: DpSize): Rectangle<XY> =
Rectangle(center, (size.width.value / zoom), (size.height.value / zoom))
override fun ViewPoint(center: XY, zoom: Float): ViewPoint<XY> =
XYViewPoint(center, zoom)
override fun ViewPoint<XY>.moveBy(delta: XY): ViewPoint<XY> =
XYViewPoint(XY(focus.x + delta.x, focus.y + delta.y))
override fun ViewPoint<XY>.zoomBy(
zoomDelta: Float,
invariant: XY,
): ViewPoint<XY> = if (invariant == focus) {
XYViewPoint(focus, zoom = zoom * 2f.pow(zoomDelta))
} else {
val difScale = (1 - 2f.pow(-zoomDelta))
val newCenter = XY(
focus.x + (invariant.x - focus.x) * difScale,
focus.y + (invariant.y - focus.y) * difScale
)
XYViewPoint(newCenter, zoom * 2f.pow(zoomDelta))
}
override fun Rectangle<XY>.withCenter(center: XY): Rectangle<XY> =
Rectangle(center, width, height)
override fun Collection<Rectangle<XY>>.wrapRectangles(): Rectangle<XY>? {
if (isEmpty()) return null
val minX = minOf { it.left }
val maxX = maxOf { it.right }
val minY = minOf { it.bottom }
val maxY = maxOf { it.top }
return XYRectangle(
XY(minX, minY),
XY(maxX, maxY)
)
}
override fun Collection<XY>.wrapPoints(): Rectangle<XY>? {
if (isEmpty()) return null
val minX = minOf { it.x }
val maxX = maxOf { it.x }
val minY = minOf { it.y }
val maxY = maxOf { it.y }
return XYRectangle(
XY(minX, minY),
XY(maxX, maxY)
)
}
override val defaultViewPoint: ViewPoint<XY> = XYViewPoint(XY(0f, 0f), 1f)
override fun XY.offsetTo(b: XY, zoom: Float): DpOffset = DpOffset(
(b.x - x).dp * zoom,
(b.y - y).dp * zoom
)
override fun XY.isInsidePolygon(points: List<XY>): Boolean = points.zipWithNext().count { (left, right) ->
val yRange = if(right.x >= left.x) {
left.y..right.y
} else {
right.y..left.y
}
if(y !in yRange) return@count false
val longitudeDelta = right.y - left.y
left.x * abs((right.y - y) / longitudeDelta) +
right.x * abs((y - left.y) / longitudeDelta) >= x
} % 2 == 1
}

View File

@ -0,0 +1,65 @@
package center.sciprog.maps.scheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.DpRect
import androidx.compose.ui.unit.dp
import center.sciprog.maps.features.*
import kotlin.math.min
public class XYViewScope(
config: ViewConfig<XY>,
) : CoordinateViewScope<XY>(config) {
override val space: CoordinateSpace<XY>
get() = XYCoordinateSpace
override fun DpOffset.toCoordinates(): XY = XY(
(x - canvasSize.width / 2).value / viewPoint.zoom + viewPoint.focus.x,
(canvasSize.height / 2 - y).value / viewPoint.zoom + viewPoint.focus.y
)
override fun XY.toDpOffset(): DpOffset = DpOffset(
(canvasSize.width / 2 + (x.dp - viewPoint.focus.x.dp) * viewPoint.zoom),
(canvasSize.height / 2 + (viewPoint.focus.y.dp - y.dp) * viewPoint.zoom)
)
override fun computeViewPoint(rectangle: Rectangle<XY>): ViewPoint<XY> {
val scale = min(
canvasSize.width.value / rectangle.width,
canvasSize.height.value / rectangle.height
)
return XYViewPoint(rectangle.center, scale)
}
override fun ViewPoint<XY>.moveBy(x: Dp, y: Dp): ViewPoint<XY> {
val newCoordinates = XY(focus.x + x.value / zoom, focus.y + y.value / zoom)
return XYViewPoint(newCoordinates, zoom)
}
override fun Rectangle<XY>.toDpRect(): DpRect {
val topLeft = leftTop.toDpOffset()
val bottomRight = rightBottom.toDpOffset()
return DpRect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y)
}
public companion object{
@Composable
public fun remember(
config: ViewConfig<XY> = ViewConfig(),
initialViewPoint: ViewPoint<XY>? = null,
initialRectangle: Rectangle<XY>? = null,
): XYViewScope = remember {
XYViewScope(config).also { mapState->
if (initialViewPoint != null) {
mapState.viewPoint = initialViewPoint
} else if (initialRectangle != null) {
mapState.viewPoint = mapState.computeViewPoint(initialRectangle)
}
}
}
}
}

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