├── .github └── workflows │ ├── publish-to-maven-central.yml │ └── publish-to-sonatype-snapshots.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── Releasing.main.kts ├── build.gradle ├── core ├── build.gradle └── src │ ├── commonMain │ └── kotlin │ │ └── io │ │ └── data2viz │ │ └── geojson │ │ └── GeoJson.kt │ ├── commonTest │ ├── kotlin │ │ └── io │ │ │ └── data2viz │ │ │ └── geojson │ │ │ ├── FeatureParsingTests.kt │ │ │ └── GeoJsonCommonParsingTests.kt │ └── resources │ │ ├── ny.json │ │ ├── us-10m.json │ │ ├── world-110m.json │ │ └── world-50m.json │ ├── iosMain │ └── kotlin │ │ └── geojson │ │ └── toGeoJsonObjectIOS.kt │ ├── jsMain │ └── kotlin │ │ └── io │ │ └── data2viz │ │ └── geojson │ │ ├── js │ │ └── GeoJsonJs.kt │ │ └── toGeoJsonObject.kt │ ├── jsTest │ └── kotlin │ │ └── io │ │ └── data2viz │ │ └── geojson │ │ └── js │ │ └── GeoJsonSerializationTests.kt │ ├── jvmMain │ └── kotlin │ │ └── io │ │ └── data2viz │ │ └── geojson │ │ ├── jackson │ │ ├── Crs.kt │ │ ├── Feature.kt │ │ ├── FeatureCollection.kt │ │ ├── GeoJsonObject.kt │ │ ├── GeoJsonObjectVisitor.kt │ │ ├── Geometry.kt │ │ ├── GeometryCollection.kt │ │ ├── LineString.kt │ │ ├── LngLatAlt.kt │ │ ├── MultiLineString.kt │ │ ├── MultiPoint.kt │ │ ├── MultiPolygon.kt │ │ ├── Point.kt │ │ ├── Polygon.kt │ │ └── jackson │ │ │ ├── CrsType.kt │ │ │ ├── LngLatAltDeserializer.kt │ │ │ └── LngLatAltSerializer.kt │ │ └── toGeoJsonObject.kt │ └── jvmTest │ └── kotlin │ └── io │ └── data2viz │ └── geojson │ └── jackson │ ├── FeatureTest.kt │ ├── GeoJsonObjectVisitorTest.kt │ ├── LngLatAltTest.kt │ ├── LoadingTest.kt │ ├── ToStringTest.kt │ └── jackson │ ├── CrsTest.kt │ ├── GeoJsonObjectTest.kt │ ├── GeometryCollectionTest.kt │ ├── LineStringTest.kt │ ├── LngLatAltDeserializerTest.kt │ ├── LngLatAltSerializerTest.kt │ ├── MockData.kt │ ├── MultiLineStringTest.kt │ ├── MultiPointTest.kt │ ├── MultiPoligonTest.kt │ ├── PointTest.kt │ └── PolygonTest.kt ├── gradle.properties ├── gradle ├── mpp-common.gradle ├── mpp-ios.gradle ├── mpp-js.gradle ├── mpp-jvm.gradle ├── publish.gradle ├── test-mocha-js.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libraries_version.txt ├── license ├── LICENSE.txt └── README.md └── settings.gradle /.github/workflows/publish-to-maven-central.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions 2 | name: Publish to Maven Central 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | types: [opened] 7 | paths: 8 | - 'libraries_version.txt' 9 | branches: 10 | - release 11 | jobs: 12 | create-staging-repository: 13 | name: Create staging repository 14 | runs-on: ubuntu-latest 15 | outputs: 16 | repository_id: ${{ steps.create.outputs.repository_id }} 17 | steps: 18 | - id: create 19 | uses: nexus-actions/create-nexus-staging-repo@v1.2 20 | with: 21 | username: ${{ secrets.SONATYPE_USERNAME }} 22 | password: ${{ secrets.SONATYPE_PASSWORD }} 23 | staging_profile_id: ${{ secrets.SONATYPE_PROFILE_ID }} 24 | description: ${{ github.repository }}/${{ github.workflow }}#${{ github.run_number }} 25 | linux-upload: 26 | runs-on: ubuntu-latest 27 | needs: [create-staging-repository] 28 | steps: 29 | - name: Checkout the repo 30 | uses: actions/checkout@v3 31 | - name: Configure JDK 32 | uses: actions/setup-java@v3 33 | with: 34 | distribution: 'temurin' 35 | java-version: 17 36 | - name: Upload all artifacts Linux can build 37 | run: >- 38 | ./gradlew --console=plain 39 | publishAllPublicationsToMavenCentralStagingRepository 40 | env: 41 | sonatype_staging_repo_id: ${{ needs.create-staging-repository.outputs.repository_id }} 42 | macos-upload: 43 | runs-on: macos-latest 44 | needs: [create-staging-repository] 45 | steps: 46 | - name: Checkout the repo 47 | uses: actions/checkout@v3 48 | - name: Configure JDK 49 | uses: actions/setup-java@v3 50 | with: 51 | distribution: 'temurin' 52 | java-version: 17 53 | - name: Upload Darwin artifacts 54 | run: >- 55 | ./gradlew --console=plain 56 | publishIosArm64PublicationToMavenCentralStagingRepository 57 | publishIosX64PublicationToMavenCentralStagingRepository 58 | publishIosSimulatorArm64PublicationToMavenCentralStagingRepository 59 | env: 60 | sonatype_staging_repo_id: ${{ needs.create-staging-repository.outputs.repository_id }} 61 | finalize: 62 | runs-on: ubuntu-latest 63 | needs: [create-staging-repository, macos-upload, linux-upload] 64 | if: ${{ always() && needs.create-staging-repository.result == 'success' }} 65 | steps: 66 | - name: Discard staging repository 67 | if: ${{ needs.macos-upload.result != 'success' || needs.linux-upload.result != 'success' }} 68 | uses: nexus-actions/drop-nexus-staging-repo@v1 69 | with: 70 | username: ${{ secrets.SONATYPE_USERNAME }} 71 | password: ${{ secrets.SONATYPE_PASSWORD }} 72 | staging_repository_id: ${{ needs.create-staging-repository.outputs.repository_id }} 73 | - name: Release 74 | if: ${{ needs.macos-upload.result == 'success' && needs.linux-upload.result == 'success' }} 75 | uses: nexus-actions/release-nexus-staging-repo@v1.2 76 | with: 77 | username: ${{ secrets.SONATYPE_USERNAME }} 78 | password: ${{ secrets.SONATYPE_PASSWORD }} 79 | staging_repository_id: ${{ needs.create-staging-repository.outputs.repository_id }} 80 | env: 81 | GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx5g -XX:+UseParallelGC -Dfile.encoding=UTF-8" 82 | sonatype_username: ${{ secrets.SONATYPE_USERNAME }} 83 | sonatype_password: ${{ secrets.SONATYPE_PASSWORD }} 84 | GPG_key_id: ${{ secrets.GPG_KEY_ID }} 85 | GPG_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 86 | GPG_private_password: ${{ secrets.GPG_PRIVATE_PASSWORD }} 87 | -------------------------------------------------------------------------------- /.github/workflows/publish-to-sonatype-snapshots.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions 2 | name: Publish to Sonatype Snapshots 3 | on: 4 | workflow_dispatch: 5 | jobs: 6 | linux-upload: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout the repo 10 | uses: actions/checkout@v3 11 | - name: Configure JDK 12 | uses: actions/setup-java@v3 13 | with: 14 | distribution: 'temurin' 15 | java-version: 17 16 | - name: Upload all artifacts Linux can build 17 | run: >- 18 | ./gradlew --console=plain 19 | publishAllPublicationsToSonatypeSnapshotsRepository 20 | macos-upload: 21 | runs-on: macos-latest 22 | steps: 23 | - name: Checkout the repo 24 | uses: actions/checkout@v3 25 | - name: Configure JDK 26 | uses: actions/setup-java@v3 27 | with: 28 | distribution: 'temurin' 29 | java-version: 17 30 | - name: Upload Darwin artifacts 31 | run: >- 32 | ./gradlew --console=plain 33 | publishIosArm64PublicationToSonatypeSnapshotsRepository 34 | publishIosX64PublicationToSonatypeSnapshotsRepository 35 | publishIosSimulatorArm64PublicationToSonatypeSnapshotsRepository 36 | env: 37 | GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx5g -XX:+UseParallelGC -Dfile.encoding=UTF-8" 38 | sonatype_username: ${{ secrets.SONATYPE_USERNAME }} 39 | sonatype_password: ${{ secrets.SONATYPE_PASSWORD }} 40 | GPG_key_id: ${{ secrets.GPG_KEY_ID }} 41 | GPG_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 42 | GPG_private_password: ${{ secrets.GPG_PRIVATE_PASSWORD }} 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | 3 | .project 4 | .classpath 5 | .settings 6 | *.iml 7 | .idea 8 | .gradle 9 | build/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | dist: trusty # needs Ubuntu Trusty 4 | # Note: if you switch to sudo: false, you'll need to launch chrome with --no-sandbox. 5 | # See https://github.com/travis-ci/travis-ci/issues/8836 6 | sudo: required 7 | addons: 8 | chrome: stable # have Travis install chrome stable. 9 | cache: 10 | yarn: true 11 | directories: 12 | - $HOME/.m2 13 | - $HOME/.gradle 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log for geojson-kotlin 2 | 3 | ## 0.6.5 4 | 5 | > Published 18 Feb 2023 6 | * Kotlin 1.8.10 7 | * Fix a publication issue that prevented the proper resolution of Kotlin/Native artifacts because of a case mismatch. 8 | 9 | ## 0.6.5 10 | 11 | > Published 17 Feb 2023 12 | * Kotlin 1.8.0 13 | * Remove unneeded `Typed` class introduced in version 0.6.4 that broke iOS consumers with the following error: `e: Compilation failed: external function Typed. must have @TypedIntrinsic, @SymbolName, @GCUnsafeCall or @ObjCMethod annotation`. 14 | 15 | ## 0.6.2 16 | > Published 01 Dec 2020 17 | * Kotlin 1.4.20 18 | * publish both IR and Legacy version for JS platform. 19 | 20 | 21 | ## 0.6.1-RC3 22 | 23 | > Published 27 Jan 2020 24 | * release on npm, new project structure and artifact name. 25 | 26 | 27 | ## 0.6.1-RC2 28 | 29 | > Published 23 Jan 2020 30 | * rename `intProp`, `booleanProp`, `stringProp` to 31 | `intProperty`, `booleanProperty`, `stringProperty` 32 | 33 | ## 0.6.1-RC1 34 | 35 | > Published 22 Jan 2020 36 | * extract Feature properties (#5) 37 | * the project uses now the new MPP structure 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GeoJson Kotlin 2 | ========================= 3 | 4 | [![Build Status](https://travis-ci.org/data2viz/geojson-kotlin.svg?branch=master)](https://travis-ci.org/data2viz/geojson-kotlin) 5 | [![GitHub License](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0) 6 | 7 | 8 | This project goal is to provide [GeoJson](https://tools.ietf.org/html/rfc7946) deserialization for kotlin multiplatform (JVM, JS). 9 | 10 | 11 | 12 | 13 | 14 | The specific format of GeoJson files does not allow the use of kotlinx.serialization. JS and 15 | JVM implementations are completely distinct but they share the same base objects and interfaces. 16 | 17 | JVM implementation is based on the project [GeoJson-Jackson](https://github.com/opendatalab-de/geojson-jackson). 18 | 19 | ## Using in your projects 20 | 21 | The library is published to data2viz space repository. 22 | 23 | 24 | 25 | ### Gradle 26 | 27 | - Add the data2viz maven repository: 28 | 29 | ```kotlin 30 | repositories { 31 | maven { url = uri("https://maven.pkg.jetbrains.space/data2viz/p/maven/public") } 32 | } 33 | ``` 34 | 35 | The project is deployed using Gradle metadata. You can use the dependency 36 | on Gradle Metadata. Depending on your platform (JS or JVM) the correct 37 | artifact will be imported. 38 | 39 | ```groovy 40 | compile 'io.data2viz.geojson:core:0.6.6' 41 | ``` 42 | 43 | The JS version is available in [both modes](https://kotlinlang.org/docs/reference/js-ir-compiler.html), `Legacy` and `IR`. 44 | 45 | You can then use the String extension toGeoJsonObject to transform any String into a GeoJsonObject: 46 | 47 | ```kotlin 48 | val featureCollection = json.toGeoJsonObject() as FeatureCollection 49 | ``` 50 | 51 | If you deserialize a FeatureCollection that have properties (main use case) you 52 | need to pass a function that transform the properties in a specific domain object. 53 | 54 | ```kotlin 55 | class CountryProperties(val name: String, val id: Int) 56 | 57 | val countries = countriesGeoJson.toFeatures { 58 | CountryProperties(stringProperty("name"), intProperty("id")) 59 | } 60 | ``` 61 | 62 | You then retrieve a list of `Pair` 63 | 64 | -------------------------------------------------------------------------------- /Releasing.main.kts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env kotlin 2 | 3 | @file:Repository("https://repo.maven.apache.org/maven2/") 4 | //@file:Repository("https://oss.sonatype.org/content/repositories/snapshots") 5 | //@file:Repository("file:///Users/me/.m2/repository") 6 | @file:DependsOn("com.louiscad.incubator:lib-publishing-helpers:0.2.3") 7 | 8 | import Releasing_main.CiReleaseFailureCause.* 9 | import java.io.File 10 | import Releasing_main.ReleaseStep.* 11 | import lib_publisher_tools.cli.CliUi 12 | import lib_publisher_tools.cli.defaultImpl 13 | import lib_publisher_tools.cli.runUntilSuccessWithErrorPrintingOrCancel 14 | import lib_publisher_tools.vcs.* 15 | import lib_publisher_tools.versioning.StabilityLevel 16 | import lib_publisher_tools.versioning.Version 17 | import lib_publisher_tools.versioning.checkIsValidVersionString 18 | import lib_publisher_tools.versioning.stabilityLevel 19 | 20 | val gitHubRepoUrl = "https://github.com/data2viz/geojson-kotlin" 21 | 22 | val buildAndPublishExpectedToTake = "less than 10 minutes" 23 | val suggestionsIfBuildTakesLonger = listOf( 24 | "We recommend to set a timer for 10 minutes to not forget to check the status.", 25 | "Suggestion: In case it's not complete after that time, set a 5min timer to check again, until completion." 26 | ) 27 | 28 | val dir = File(".") 29 | 30 | val publishToMavenCentralWorkflowFilename = "publish-to-maven-central.yml".also { 31 | check(dir.resolve(".github").resolve("workflows").resolve(it).exists()) { 32 | "The $it file expected in the `.github/workflows dir wasn't found!\n" + 33 | "The filename is required to be correct.\n" + 34 | "If the release workflow needs to be retried, it will be used to make a valid link." 35 | } 36 | } 37 | 38 | val publishToMavenCentralWorkflowLink = "$gitHubRepoUrl/actions/workflows/$publishToMavenCentralWorkflowFilename" 39 | 40 | val cliUi = CliUi.defaultImpl 41 | val git = Vcs.git 42 | 43 | fun File.checkChanged() = check(git.didFileChange(this)) { 44 | "Expected changes in the following file: $this" 45 | } 46 | 47 | fun checkOnMainBranch() { 48 | check(git.isOnMainBranch()) { "Please, checkout the `main` branch first." } 49 | } 50 | 51 | @Suppress("EnumEntryName") 52 | enum class ReleaseStep { // Order of the steps, must be kept right. 53 | `Update release branch`, 54 | `Update main branch from release`, 55 | `Change this library version`, 56 | `Request doc update confirmation`, 57 | `Request CHANGELOG update confirmation`, 58 | `Commit 'prepare for release' and tag`, 59 | `Push release to origin`, 60 | `Request PR submission`, 61 | `Wait for successful release by CI`, 62 | `Push tags to origin`, 63 | `Request PR merge`, 64 | `Request GitHub release publication`, 65 | `Change this library version back to a SNAPSHOT`, 66 | `Commit 'prepare next dev version'`, 67 | `Push, at last`; 68 | } 69 | 70 | sealed interface CiReleaseFailureCause { 71 | enum class RequiresNewCommits : CiReleaseFailureCause { BuildFailure, PublishingRejection } 72 | enum class RequiresRetrying : CiReleaseFailureCause { ThirdPartyOutage, NetworkOutage } 73 | } 74 | 75 | val ongoingReleaseFile = dir.resolve("ongoing_release.tmp.properties") 76 | val versionsFile = dir.resolve("libraries_version.txt") 77 | 78 | inner class OngoingReleaseImpl { 79 | fun load() = properties.load(ongoingReleaseFile.inputStream()) 80 | fun write() = properties.store(ongoingReleaseFile.outputStream(), null) 81 | fun clear() = ongoingReleaseFile.delete() 82 | 83 | private val properties = java.util.Properties() 84 | 85 | var versionBeforeRelease: String by properties 86 | var newVersion: String by properties 87 | 88 | var currentStepName: String by properties 89 | } 90 | 91 | //TODO: Make OngoingRelease and object again when https://youtrack.jetbrains.com/issue/KT-19423 is fixed. 92 | @Suppress("PropertyName") 93 | val OngoingRelease = OngoingReleaseImpl() 94 | 95 | var startAtStep: ReleaseStep //TODO: Make a val again when https://youtrack.jetbrains.com/issue/KT-20059 is fixed 96 | 97 | val versionTagPrefix = "v" 98 | 99 | fun tagOfVersionBeingReleased(): String = "$versionTagPrefix${OngoingRelease.newVersion}" 100 | 101 | if (ongoingReleaseFile.exists()) { 102 | OngoingRelease.load() 103 | startAtStep = ReleaseStep.valueOf(OngoingRelease.currentStepName) 104 | } else { 105 | checkOnMainBranch() 106 | with(OngoingRelease) { 107 | versionBeforeRelease = versionsFile.bufferedReader().use { it.readLine() }.also { 108 | check(it.contains("-dev-") || it.endsWith("-SNAPSHOT")) { 109 | "The current version needs to be a SNAPSHOT or a dev version, but we got: $it" 110 | } 111 | } 112 | newVersion = askNewVersionInput( 113 | currentVersion = versionBeforeRelease, 114 | tagPrefix = versionTagPrefix 115 | ) 116 | } 117 | startAtStep = ReleaseStep.values().first() 118 | } 119 | 120 | fun askNewVersionInput( 121 | currentVersion: String, 122 | tagPrefix: String 123 | ): String = cliUi.runUntilSuccessWithErrorPrintingOrCancel { 124 | cliUi.printInfo("Current version: $currentVersion") 125 | cliUi.printQuestion("Please enter the name of the new version you want to release:") 126 | val input = readLine()?.trimEnd() ?: error("Empty input!") 127 | input.checkIsValidVersionString() 128 | when { 129 | "-dev-" in input -> error("Dev versions not allowed") 130 | "-SNAPSHOT" in input -> error("Snapshots not allowed") 131 | } 132 | val existingVersions = git.getTags().filter { 133 | it.startsWith(tagPrefix) && it.getOrElse(tagPrefix.length) { ' ' }.isDigit() 134 | }.sorted().toList() 135 | check("$tagPrefix$input" !in existingVersions) { "This version already exists!" } 136 | input 137 | } 138 | 139 | 140 | fun CliUi.runReleaseStep(step: ReleaseStep): Unit = when (step) { 141 | `Update release branch` -> { 142 | printInfo("Before proceeding to the release, we will ensure we merge changes from the release branch into the main branch.") 143 | printInfo("Will now checkout the `release` branch and pull from GitHub (origin) to update the local `release` branch.") 144 | requestUserConfirmation("Continue?") 145 | if (git.hasBranch("release")) { 146 | git.checkoutBranch("release") 147 | git.pullFromOrigin() 148 | } else { 149 | printInfo("The branch release doesn't exist locally. Fetching from remote…") 150 | git.fetch() 151 | if (git.hasRemoteBranch(remoteName = "origin", branchName = "release")) { 152 | printInfo("The branch exists on the origin remote. Checking out.") 153 | git.checkoutAndTrackRemoteBranch("origin", "release") 154 | } else { 155 | printInfo("Creating and checking out the release branch") 156 | git.createAndCheckoutBranch("release") 157 | printInfo("Pushing the new release branch…") 158 | git.push(repository = "origin", setUpstream = true, branchName = "release") 159 | } 160 | } 161 | } 162 | `Update main branch from release` -> { 163 | printInfo("About to checkout the main branch (and update it from release for merge commits).") 164 | requestUserConfirmation("Continue?") 165 | git.checkoutMain() 166 | git.mergeBranchIntoCurrent("release") 167 | } 168 | `Change this library version` -> { 169 | checkOnMainBranch() 170 | OngoingRelease.newVersion.let { newVersion -> 171 | printInfo("data2viz new version: \"$newVersion\"") 172 | requestUserConfirmation("Confirm?") 173 | versionsFile.writeText(newVersion) 174 | } 175 | } 176 | `Request doc update confirmation` -> { 177 | arrayOf( 178 | "README.md" 179 | ).forEach { relativePath -> 180 | do { 181 | requestManualAction( 182 | instructions = "Update the `$relativePath` file with the new version (if needed)," + 183 | " and any other changes needed for this release." 184 | ) 185 | if (git.didFileChange(dir.resolve(relativePath))) { 186 | break 187 | } 188 | if (askIfYes( 189 | yesNoQuestion = "Are you sure the $relativePath file doesn't need to be updated?" 190 | ) 191 | ) { 192 | break 193 | } 194 | } while (true) 195 | }.also { 196 | if (askIfYes( 197 | yesNoQuestion = "Apart from the changelog, are there any other files that " + 198 | "need to be updated for this new release?" 199 | ) 200 | ) { 201 | requestManualAction( 202 | instructions = "Let's ensure all these other files are updated." 203 | ) 204 | } 205 | } 206 | } 207 | `Request CHANGELOG update confirmation` -> { 208 | requestManualAction("Update the `CHANGELOG.md` for the impending release.") 209 | dir.resolve("CHANGELOG.md").checkChanged() 210 | } 211 | `Commit 'prepare for release' and tag` -> with(OngoingRelease) { 212 | git.commitAllFiles(commitMessage = "Prepare for release $newVersion") 213 | git.tagAnnotated(tag = tagOfVersionBeingReleased(), annotationMessage = "Version $newVersion") 214 | } 215 | `Push release to origin` -> { 216 | printInfo("Will now push to origin repository") 217 | requestUserConfirmation("Continue?") 218 | git.pushToOrigin() 219 | } 220 | `Request PR submission` -> { 221 | printInfo("You now need to create a pull request from the `main` to the `release` branch on GitHub for the new version,") 222 | printInfo("if not already done.") 223 | printInfo("You can do so by heading over to the following url:") 224 | printInfo("$gitHubRepoUrl/compare/release...main") 225 | printInfo("Here's a title suggestion which you can copy/paste:") 226 | printInfo("Prepare for release ${OngoingRelease.newVersion}") 227 | printInfo("Once submitted, GitHub should kick-off the release GitHub Action that will perform the publishing.") 228 | requestManualAction("PR submitted?") 229 | } 230 | `Wait for successful release by CI` -> { 231 | printInfo("To perform this step, we need to wait for the artifacts building and uploading.") 232 | do { 233 | printInfo("The build and publishing workflow is expected to take $buildAndPublishExpectedToTake.") 234 | printInfo("") 235 | suggestionsIfBuildTakesLonger.forEach { printInfo(it) } 236 | val succeeded = askIfYes("Did the publishing/release Github Action complete successfully?") 237 | if (succeeded.not()) { 238 | printQuestion("What was the cause of failure?") 239 | val failureCause: CiReleaseFailureCause = askChoice( 240 | optionsWithValues = listOf( 241 | "Outage of a third party service (GitHub actions, Sonatype, MavenCentral, Google Maven…)" to RequiresRetrying.ThirdPartyOutage, 242 | "Network outage" to RequiresRetrying.NetworkOutage, 243 | "Build failure that requires new commits to fix" to RequiresNewCommits.BuildFailure, 244 | "Publication was rejected because of misconfiguration" to RequiresNewCommits.PublishingRejection 245 | ) 246 | ) 247 | when (failureCause) { 248 | is RequiresRetrying -> { 249 | printInfo("The outage will most likely be temporary.") 250 | when (failureCause) { 251 | RequiresRetrying.ThirdPartyOutage -> "You can search for the status page of the affected service and check it periodically." 252 | RequiresRetrying.NetworkOutage -> "You can retry when you feel or know it might be resolved." 253 | }.let { infoMessage -> 254 | printInfo(infoMessage) 255 | } 256 | printInfo("Once the outage is resolved, head to the following url to run the workflow again, on the right branch:") 257 | printInfo(publishToMavenCentralWorkflowLink) 258 | requestManualAction("Click the `Run workflow` button, select the `main` branch and confirm.") 259 | } 260 | is RequiresNewCommits -> { 261 | if (git.hasTag(tagOfVersionBeingReleased())) { 262 | printInfo("Removing the version tag (will be put back later on)") 263 | git.deleteTag(tag = tagOfVersionBeingReleased()) 264 | printInfo("tag removed") 265 | } 266 | printInfo("Recovering from that is going to require new fixing commits to be pushed to the main branch.") 267 | printInfo("Note: you can keep this script waiting while you're resolving the build issue.") 268 | requestManualAction("Fix the issues and commit the changes") 269 | printInfo("Will now push the new commits") 270 | requestUserConfirmation("Continue?") 271 | git.pushToOrigin() 272 | printInfo("Now, head to the following url to run the workflow again, on the right branch:") 273 | printInfo(publishToMavenCentralWorkflowLink) 274 | requestManualAction("Click the `Run workflow` button, select the `main` branch and confirm.") 275 | } 276 | } 277 | } 278 | } while (succeeded.not()) 279 | printInfo("Alright, we take your word.") 280 | } 281 | `Push tags to origin` -> { 282 | printInfo("Will now push with tags.") 283 | requestUserConfirmation("Continue?") 284 | if (git.hasTag(tagOfVersionBeingReleased()).not()) with(OngoingRelease) { 285 | printInfo("The tag for the impeding release is missing, so we're putting it back too.") 286 | git.tagAnnotated(tag = tagOfVersionBeingReleased(), annotationMessage = "Version $newVersion") 287 | } 288 | git.pushToOrigin(withTags = true) 289 | } 290 | `Request PR merge` -> { 291 | requestManualAction("Merge the pull request for the new version on GitHub.") 292 | printInfo("Now that the pull request has been merged into the release branch on GitHub,") 293 | printInfo("we are going to update our local release branch") 294 | requestUserConfirmation("Ready?") 295 | git.updateBranchFromOrigin(targetBranch = "release") 296 | } 297 | `Request GitHub release publication` -> { 298 | printInfo("It's now time to publish the release on GitHub, so people get notified.") 299 | printInfo("Copy the section of this release from the CHANGELOG file, and head over to the following url to proceed:") 300 | printInfo("$gitHubRepoUrl/releases/new") 301 | requestManualAction("Publish the release ${OngoingRelease.newVersion} on GitHub with the changelog.") 302 | } 303 | `Change this library version back to a SNAPSHOT` -> { 304 | val newVersion = Version(OngoingRelease.newVersion) 305 | 306 | val isNewVersionStable: Boolean = newVersion.stabilityLevel().let { level -> 307 | if (level == StabilityLevel.Stable) true 308 | else level == StabilityLevel.Unknown && askIfYes( 309 | yesNoQuestion = "The stabilityLevel of the new release is unknown. Is it a stable one?" 310 | ) 311 | } 312 | val nextDevVersion: String = if (isNewVersionStable) { 313 | printInfo("Congratulations for this new stable release!") 314 | printInfo("Let's update the library for next development version.") 315 | runUntilSuccessWithErrorPrintingOrCancel { 316 | printInfo("Enter the name of the next target version (`-SNAPSHOT` will be added automatically)") 317 | val input = readLine() 318 | input.checkIsValidVersionString() 319 | when (Version(input).stabilityLevel()) { 320 | StabilityLevel.Unknown, StabilityLevel.Stable -> Unit 321 | else -> error("You need to enter a stable target version") 322 | } 323 | "$input-SNAPSHOT" 324 | } 325 | } else OngoingRelease.versionBeforeRelease.let { 326 | if (it.endsWith("-SNAPSHOT")) it 327 | else "${it.substringBefore("-dev-")}-SNAPSHOT" 328 | } 329 | versionsFile.writeText(nextDevVersion) 330 | printInfo("${versionsFile.path} has been edited with next development version ($nextDevVersion).") 331 | } 332 | `Commit 'prepare next dev version'` -> git.commitAllFiles( 333 | commitMessage = "Prepare next development version.".also { 334 | requestUserConfirmation( 335 | yesNoQuestion = """Will commit all files with message "$it" Continue?""" 336 | ) 337 | } 338 | ) 339 | `Push, at last` -> { 340 | requestUserConfirmation("Finally the last step: the last push. Continue?") 341 | git.pushToOrigin() 342 | } 343 | } 344 | 345 | fun performRelease() { 346 | var stepIndex = startAtStep.ordinal 347 | val enumValues = enumValues().toList() 348 | while (stepIndex < enumValues.size) { 349 | val step = enumValues[stepIndex] 350 | OngoingRelease.currentStepName = step.name 351 | OngoingRelease.write() 352 | cliUi.runReleaseStep(step) 353 | stepIndex++ 354 | } 355 | OngoingRelease.clear() 356 | cliUi.printQuestion("All Done! Let's brag about this new release!!") 357 | } 358 | 359 | performRelease() 360 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | dependencies { 3 | classpath "org.jetbrains.kotlinx:binary-compatibility-validator:0.12.1" 4 | } 5 | } 6 | plugins { 7 | id "org.jetbrains.kotlin.multiplatform" version "1.8.10" apply(false) 8 | } 9 | 10 | apply plugin: "binary-compatibility-validator" 11 | 12 | final String thisLibraryVersion = file("libraries_version.txt").readLines().first() 13 | 14 | allprojects { 15 | 16 | group = "io.data2viz.geojson" 17 | version = thisLibraryVersion 18 | 19 | repositories { 20 | mavenLocal() 21 | google() 22 | mavenCentral() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "org.jetbrains.kotlin.multiplatform" 3 | } 4 | 5 | apply from: rootProject.file('gradle/mpp-common.gradle') 6 | apply from: rootProject.file('gradle/mpp-ios.gradle') 7 | apply from: rootProject.file('gradle/mpp-jvm.gradle') 8 | apply from: rootProject.file('gradle/mpp-js.gradle') 9 | apply from: rootProject.file('gradle/publish.gradle') 10 | 11 | kotlin { 12 | sourceSets { 13 | jvmMain { 14 | dependencies { 15 | implementation kotlin('stdlib') 16 | implementation "com.fasterxml.jackson.core:jackson-core:2.7.3" 17 | implementation "com.fasterxml.jackson.core:jackson-databind:2.7.3" 18 | implementation "com.fasterxml.jackson.core:jackson-annotations:2.7.0" 19 | implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.0" 20 | } 21 | } 22 | jvmTest { 23 | dependencies { 24 | implementation "org.mockito:mockito-core:1.10.19" 25 | implementation "junit:junit:4.12" 26 | implementation kotlin('test') 27 | implementation kotlin('test-junit') 28 | } 29 | } 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /core/src/commonMain/kotlin/io/data2viz/geojson/GeoJson.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson 2 | 3 | 4 | /** 5 | * Marker interface to indicate a GeoJson object. It can be 6 | * a Geometry, a Feature or a FeatureCollection 7 | */ 8 | interface GeoJsonObject 9 | 10 | /** 11 | * A feature contains a Geometry, an optional id, and optional properties. 12 | */ 13 | data class Feature( 14 | val geometry: Geometry, 15 | val id:Any? = null, 16 | val properties: Any? = null 17 | ) : GeoJsonObject 18 | 19 | /** 20 | * A feature collection is an array of features. 21 | */ 22 | data class FeatureCollection(val features: Array): GeoJsonObject 23 | 24 | 25 | interface Geometry : GeoJsonObject 26 | 27 | 28 | data class Point (val coordinates: Position) : Geometry 29 | data class MultiPoint (val coordinates: Positions) : Geometry 30 | data class LineString (val coordinates: Line) : Geometry 31 | data class MultiLineString (val coordinates: Lines) : Geometry 32 | data class Polygon (val coordinates: Lines) : Geometry { 33 | val hasHoles = coordinates.size > 1 34 | } 35 | data class MultiPolygon (val coordinates: Surface) :Geometry 36 | data class GeometryCollection(val geometries: Array): Geometry 37 | 38 | 39 | /** 40 | * Position is an alias on a DoubleArray that represents the coordinates 41 | * in degrees for longitude (index = 0), latitude (index = 1) and altitude (meters). 42 | * The altitude is not a mandatory information. The position can be 2 length array or 43 | * a 3 length array (with altitude) 44 | */ 45 | typealias Position = DoubleArray 46 | 47 | /** 48 | * Type alias of an array of Positions 49 | */ 50 | typealias Positions = Array 51 | 52 | /** 53 | * Type alias for an array of Positions 54 | */ 55 | typealias Line = Positions 56 | 57 | /** 58 | * Type alias of an array of lines. 59 | */ 60 | typealias Lines = Array 61 | 62 | 63 | typealias Surface = Array 64 | 65 | 66 | val Position.lon: Double 67 | get() = this[0] 68 | 69 | val Position.lat: Double 70 | get() = this[1] 71 | 72 | val Position.alt: Double? 73 | get() = if (size > 2) this[2] else null 74 | 75 | 76 | /** 77 | * Parse the String as a GeoJsonObject. The implementation depends on 78 | * the platform 79 | */ 80 | expect fun String.toGeoJsonObject():GeoJsonObject 81 | 82 | /** 83 | * Retrieve a list of Feature (FeatureCollection) which have properties. 84 | * You need to pass an extractionFunction to transform the extracted properties 85 | * to a specific Properties class. 86 | * 87 | * For instance: 88 | * ``` 89 | * json.toFeaturesAndProperties { City( intProp("id"), stringProp("name") } 90 | * ``` 91 | * @return a list of Pair 92 | */ 93 | expect fun String.toFeaturesAndProperties(extractFunction: FeatureProperties.() -> T): List> 94 | //expect fun String.toFeature(extract: FeatureProperties.() -> T): Pair 95 | 96 | /** 97 | * This class simplifies the access to feature properties. It should only 98 | * be used as a way to deserialize the properties of a feature using 99 | * the String.toFeaturesAndProperties function. 100 | */ 101 | expect class FeatureProperties { 102 | fun stringProperty(name: String): String 103 | fun intProperty(name: String): Int 104 | fun booleanProperty(name: String): Boolean 105 | } 106 | 107 | -------------------------------------------------------------------------------- /core/src/commonTest/kotlin/io/data2viz/geojson/FeatureParsingTests.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson 2 | 3 | 4 | import kotlin.test.* 5 | 6 | 7 | class FeatureParsingTests { 8 | 9 | @Test 10 | fun feature() { 11 | //language=JSON 12 | val json = """ 13 | { 14 | "type": "Feature", 15 | "geometry": { 16 | "type": "LineString", 17 | "coordinates": [ 18 | [102.0, 0.0], 19 | [105.0, 1.0] 20 | ] 21 | } 22 | } 23 | """.trimIndent() 24 | 25 | val feature = json.toGeoJsonObject() as Feature 26 | assertEquals(2, (feature.geometry as? LineString)?.coordinates?.size) 27 | assertNull(feature.id) 28 | } 29 | 30 | @Test 31 | fun featureWithStringId() { 32 | //language=JSON 33 | val json = """ 34 | { 35 | "type": "Feature", 36 | "id": "1234", 37 | "geometry": { 38 | "type": "LineString", 39 | "coordinates": [ 40 | [0.0, 0.0], 41 | [1.0, 1.0] 42 | ] 43 | } 44 | } 45 | """.trimIndent() 46 | val feature = json.toGeoJsonObject() as Feature 47 | assertEquals("1234", feature.id) 48 | assertTrue { feature.geometry is LineString } 49 | } 50 | 51 | @Test 52 | fun featureWithIntId() { 53 | //language=JSON 54 | val json = """ 55 | { 56 | "type": "Feature", 57 | "id": 1234, 58 | "geometry": { 59 | "type": "LineString", 60 | "coordinates": [ 61 | [0.0, 0.0], 62 | [1.0, 1.0] 63 | ] 64 | } 65 | } 66 | """.trimIndent() 67 | val feature = json.toGeoJsonObject() as Feature 68 | assertEquals(1234, feature.id) 69 | assertTrue { feature.geometry is LineString } 70 | 71 | } 72 | 73 | @Test 74 | fun featureWithProperties() { 75 | //language=JSON 76 | val json = """ 77 | { 78 | "type": "Feature", 79 | "geometry": { "type": "LineString", "coordinates": [ [102.0, 0.0], [105.0, 1.0] ] }, 80 | "properties": { "name" : "Archamps", "code" : 74016} 81 | } 82 | """.trimIndent() 83 | 84 | val feature = json.toGeoJsonObject() as Feature 85 | assertNotNull(feature.properties) 86 | } 87 | 88 | @Test 89 | fun featuresWithProperties() { 90 | //language=JSON 91 | val json = """ 92 | {"type":"FeatureCollection","features":[ 93 | { 94 | "type": "Feature", 95 | "geometry": { "type": "LineString", "coordinates": [ [102.0, 0.0], [105.0, 1.0] ] }, 96 | "properties": { "name" : "Archamps", "code" : 74016} 97 | }, 98 | { 99 | "type": "Feature", 100 | "geometry": { "type": "LineString", "coordinates": [ [102.0, 0.0], [105.0, 1.0] ] }, 101 | "properties": { "name" : "Collonges", "code" : 74017} 102 | } 103 | ]} 104 | """.trimIndent() 105 | 106 | data class Commune(val name: String, val code: Int) 107 | 108 | val features: List> = json.toFeaturesAndProperties { 109 | Commune(stringProperty("name"), intProperty("code")) 110 | } 111 | assertEquals(2, features.size) 112 | val archamps = features[0] 113 | assertEquals("Archamps", archamps.second.name) 114 | } 115 | 116 | @Test 117 | fun loadWorld110m() { 118 | 119 | val json = """ 120 | {"type":"FeatureCollection", "features": [ 121 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-57.147436,5.97315],[-55.949318,5.772878],[-55.03325,6.025291],[-53.958045,5.756548],[-54.478633,4.896756],[-54.399542,4.212611],[-54.006931,3.620038],[-54.524754,2.311849],[-55.973322,2.510364],[-56.539386,1.899523],[-58.044694,4.060864],[-57.914289,4.812626],[-57.307246,5.073567],[-57.147436,5.97315]]]},"properties":{"name":"Suriname"},"id":"SUR"}, 122 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[18.853144,49.49623],[19.825023,49.217125],[21.607808,49.470107],[22.558138,49.085738],[22.085608,48.422264],[20.801294,48.623854],[20.239054,48.327567],[17.857133,47.758429],[16.979667,48.123497],[16.960288,48.596982],[18.853144,49.49623]]]},"properties":{"name":"Slovakia"},"id":"SVK"}, 123 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[13.806475,46.509306],[14.632472,46.431817],[16.202298,46.852386],[16.564808,46.503751],[15.768733,46.238108],[15.327675,45.452316],[13.71506,45.500324],[13.93763,45.591016],[13.806475,46.509306]]]},"properties":{"name":"Slovenia"},"id":"SVN"}, 124 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[11.027369,58.856149],[12.300366,60.117933],[12.631147,61.293572],[11.992064,61.800362],[11.930569,63.128318],[12.579935,64.066219],[13.571916,64.049114],[13.55569,64.787028],[15.108411,66.193867],[16.768879,68.013937],[17.729182,68.010552],[17.993868,68.567391],[19.87856,68.407194],[20.645593,69.106247],[23.539473,67.936009],[23.56588,66.396051],[23.903379,66.006927],[22.183173,65.723741],[21.213517,65.026005],[21.369631,64.413588],[17.847779,62.7494],[17.119555,61.341166],[18.787722,60.081914],[17.869225,58.953766],[16.829185,58.719827],[16.44771,57.041118],[15.879786,56.104302],[14.666681,56.200885],[14.100721,55.407781],[12.942911,55.361737],[12.625101,56.30708],[11.787942,57.441817],[11.027369,58.856149]]]},"properties":{"name":"Sweden"},"id":"SWE"}, 125 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[32.071665,-26.73382],[31.282773,-27.285879],[30.685962,-26.743845],[31.04408,-25.731452],[31.837778,-25.843332],[32.071665,-26.73382]]]},"properties":{"name":"Swaziland"},"id":"SWZ"}, 126 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[38.792341,33.378686],[36.834062,32.312938],[35.719918,32.709192],[35.821101,33.277426],[36.61175,34.201789],[35.998403,34.644914],[36.149763,35.821535],[37.066761,36.623036],[38.167727,36.90121],[39.52258,36.716054],[40.673259,37.091276],[42.349591,37.229873],[41.289707,36.358815],[41.383965,35.628317],[41.006159,34.419372],[38.792341,33.378686]]]},"properties":{"name":"Syria"},"id":"SYR"}, 127 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[14.495787,12.859396],[13.954477,13.353449],[13.540394,14.367134],[13.97217,15.68437],[15.247731,16.627306],[15.300441,17.92795],[15.903247,20.387619],[15.096888,21.308519],[14.8513,22.86295],[15.86085,23.40972],[23.83766,19.58047],[23.88689,15.61084],[23.02459,15.68072],[22.56795,14.94429],[22.03759,12.95546],[22.864165,11.142395],[21.723822,10.567056],[21.000868,9.475985],[20.059685,9.012706],[19.094008,9.074847],[17.96493,7.890914],[16.705988,7.508328],[15.27946,7.421925],[14.979996,8.796104],[13.954218,9.549495],[14.171466,10.021378],[15.467873,9.982337],[14.923565,10.891325],[14.89336,12.21905],[14.495787,12.859396]]]},"properties":{"name":"Chad"},"id":"TCD"}, 128 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[1.865241,6.142158],[1.060122,5.928837],[0.490957,7.411744],[0.36758,10.191213],[0.023803,11.018682],[0.899563,10.997339],[0.772336,10.470808],[1.664478,9.12859],[1.618951,6.832038],[1.865241,6.142158]]]},"properties":{"name":"Togo"},"id":"TGO"}, 129 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[102.584932,12.186595],[101.687158,12.64574],[100.83181,12.627085],[100.978467,13.412722],[100.097797,13.406856],[100.018733,12.307001],[99.153772,9.963061],[99.222399,9.239255],[99.873832,9.207862],[100.459274,7.429573],[102.141187,6.221636],[101.814282,5.810808],[100.085757,6.464489],[99.519642,7.343454],[98.503786,8.382305],[98.553551,9.93296],[99.587286,11.892763],[99.196354,12.804748],[99.097755,13.827503],[98.430819,14.622028],[98.903348,16.177824],[97.375896,18.445438],[97.797783,18.62708],[98.253724,19.708203],[98.959676,19.752981],[100.115988,20.41785],[100.606294,19.508344],[101.282015,19.462585],[101.059548,17.512497],[102.113592,18.109102],[102.998706,17.961695],[103.956477,18.240954],[104.716947,17.428859],[104.779321,16.441865],[105.589039,15.570316],[105.218777,14.273212],[104.281418,14.416743],[102.988422,14.225721],[102.348099,13.394247],[102.584932,12.186595]]]},"properties":{"name":"Thailand"},"id":"THA"}, 130 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[71.014198,40.244366],[70.648019,39.935754],[69.55961,40.103211],[69.464887,39.526683],[70.549162,39.604198],[71.784694,39.279463],[73.675379,39.431237],[73.928852,38.505815],[74.864816,38.378846],[74.980002,37.41999],[73.260056,37.495257],[71.844638,36.738171],[71.448693,37.065645],[71.541918,37.905774],[70.806821,38.486282],[70.116578,37.588223],[69.518785,37.608997],[68.135562,37.023115],[67.83,37.144994],[68.392033,38.157025],[68.176025,38.901553],[69.329495,40.727824],[70.666622,40.960213],[71.014198,40.244366]]]},"properties":{"name":"Tajikistan"},"id":"TJK"}, 131 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[61.210817,35.650072],[61.123071,36.491597],[60.377638,36.527383],[59.234762,37.412988],[57.330434,38.029229],[55.511578,37.964117],[54.800304,37.392421],[53.921598,37.198918],[53.880929,38.952093],[53.101028,39.290574],[52.693973,40.033629],[52.915251,40.876523],[53.858139,40.631034],[54.736845,40.951015],[53.721713,42.123191],[52.50246,41.783316],[54.079418,42.324109],[54.755345,42.043971],[55.455251,41.259859],[55.968191,41.308642],[58.629011,42.751551],[59.976422,42.223082],[60.083341,41.425146],[61.882714,41.084857],[62.37426,40.053886],[64.170223,38.892407],[66.54615,37.974685],[66.518607,37.362784],[64.746105,37.111818],[64.546479,36.312073],[63.193538,35.857166],[62.230651,35.270664],[61.210817,35.650072]]]},"properties":{"name":"Turkmenistan"},"id":"TKM"}, 132 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[124.968682,-8.89279],[126.644704,-8.398247],[126.967992,-8.668256],[125.08852,-9.393173],[124.968682,-8.89279]]]},"properties":{"name":"East Timor"},"id":"TLS"}, 133 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-61.68,10.76],[-60.895,10.855],[-60.935,10.11],[-61.77,10],[-61.68,10.76]]]},"properties":{"name":"Trinidad and Tobago"},"id":"TTO"}, 134 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[9.48214,30.307556],[9.055603,32.102692],[7.612642,33.344115],[7.524482,34.097376],[8.140981,34.655146],[8.376368,35.479876],[8.420964,36.946427],[9.509994,37.349994],[10.210002,37.230002],[10.939519,35.698984],[10.807847,34.833507],[10.149593,34.330773],[11.488787,33.136996],[11.432253,32.368903],[9.950225,31.37607],[9.970017,30.539325],[9.48214,30.307556]]]},"properties":{"name":"Tunisia"},"id":"TUN"}, 135 | {"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[41.554084,41.535656],[42.619549,41.583173],[43.582746,41.092143],[43.656436,40.253564],[44.79399,39.713003],[44.109225,39.428136],[44.77267,37.17045],[42.779126,37.385264],[42.349591,37.229873],[40.673259,37.091276],[39.52258,36.716054],[38.167727,36.90121],[37.066761,36.623036],[36.149763,35.821535],[35.550936,36.565443],[34.714553,36.795532],[34.026895,36.21996],[32.509158,36.107564],[31.699595,36.644275],[30.621625,36.677865],[29.699976,36.144357],[28.732903,36.676831],[27.641187,36.658822],[27.048768,37.653361],[26.318218,38.208133],[26.8047,38.98576],[26.170785,39.463612],[27.28002,40.420014],[28.819978,40.460011],[29.240004,41.219991],[31.145934,41.087622],[32.347979,41.736264],[33.513283,42.01896],[35.167704,42.040225],[36.913127,41.335358],[38.347665,40.948586],[40.373433,41.013673],[41.554084,41.535656]]],[[[26.056942,40.824123],[26.117042,41.826905],[27.135739,42.141485],[27.99672,42.007359],[28.806438,41.054962],[27.619017,40.999823],[26.358009,40.151994],[26.056942,40.824123]]]]},"properties":{"name":"Turkey"},"id":"TUR"}, 136 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[121.777818,24.394274],[120.74708,21.970571],[120.106189,23.556263],[120.69468,24.538451],[121.495044,25.295459],[121.777818,24.394274]]]},"properties":{"name":"Taiwan"},"id":"TWN"}, 137 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[31.78597,52.10168],[33.7527,52.335075],[35.022183,51.207572],[35.356116,50.577197],[36.626168,50.225591],[37.39346,50.383953],[38.010631,49.915662],[40.06904,49.60105],[39.67465,48.78382],[39.738278,47.898937],[38.77057,47.82562],[38.223538,47.10219],[36.759855,46.6987],[35.823685,46.645964],[34.962342,46.273197],[35.239999,44.939996],[33.882511,44.361479],[33.546924,45.034771],[32.630804,45.519186],[33.298567,46.080598],[30.748749,46.5831],[29.603289,45.293308],[28.233554,45.488283],[28.933717,46.25883],[29.759972,46.349988],[29.122698,47.849095],[27.522537,48.467119],[26.619337,48.220726],[24.866317,47.737526],[23.142236,48.096341],[22.710531,47.882194],[22.085608,48.422264],[22.558138,49.085738],[22.51845,49.476774],[24.029986,50.705407],[23.527071,51.578454],[24.553106,51.888461],[26.337959,51.832289],[29.254938,51.368234],[30.555117,51.319503],[30.927549,52.042353],[31.78597,52.10168]]]},"properties":{"name":"Ukraine"},"id":"UKR"}, 138 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-57.625133,-30.216295],[-56.976026,-30.109686],[-53.787952,-32.047243],[-53.209589,-32.727666],[-53.373662,-33.768378],[-53.806426,-34.396815],[-54.935866,-34.952647],[-56.215297,-34.859836],[-57.139685,-34.430456],[-57.817861,-34.462547],[-58.427074,-33.909454],[-58.14244,-32.044504],[-57.625133,-30.216295]]]},"properties":{"name":"Uruguay"},"id":"URY"}, 139 | {"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[-155.54211,19.08348],[-156.07347,19.70294],[-155.78505,20.2487],[-154.83147,19.45328],[-155.54211,19.08348]]],[[[-67.13741,45.13753],[-68.03252,44.3252],[-70.11617,43.68405],[-70.81489,42.8653],[-70.64,41.475],[-71.86,41.32],[-71.945,40.93],[-73.982,40.628],[-74.17838,39.70926],[-75.72205,37.93705],[-75.9718,36.89726],[-75.72749,35.55074],[-76.36318,34.80854],[-77.397635,34.51201],[-78.55435,33.86133],[-79.20357,33.15839],[-80.301325,32.509355],[-81.33629,31.44049],[-81.49042,30.72999],[-80.056539,26.88],[-80.38103,25.20616],[-81.17213,25.20126],[-81.71,25.87],[-82.85526,27.88624],[-82.65,28.55],[-83.70959,29.93656],[-85.10882,29.63615],[-86.4,30.4],[-89.18049,30.31598],[-89.40823,29.15961],[-90.880225,29.148535],[-91.626785,29.677],[-93.22637,29.78375],[-94.69,29.48],[-95.60026,28.73863],[-96.59404,28.30748],[-97.37,27.38],[-97.140008,25.869997],[-99.02,26.37],[-99.52,27.54],[-100.9576,29.38071],[-101.6624,29.7793],[-102.48,29.76],[-103.11,28.97],[-104.45697,29.57196],[-105.03737,30.64402],[-106.50759,31.75452],[-108.24,31.754854],[-108.24194,31.34222],[-111.02361,31.33472],[-114.815,32.52528],[-117.12776,32.53534],[-117.295938,33.046225],[-118.519895,34.027782],[-120.36778,34.44711],[-120.74433,35.15686],[-121.71457,36.16153],[-122.54747,37.55176],[-123.7272,38.95166],[-123.86517,39.76699],[-124.39807,40.3132],[-124.2137,41.99964],[-124.53284,42.76599],[-124.14214,43.70838],[-123.89893,45.52341],[-124.079635,46.86475],[-124.566101,48.379715],[-123.12,48.04],[-122.84,49],[-95.15907,49],[-94.32914,48.67074],[-92.61,48.45],[-91.64,48.14],[-90.83,48.27],[-89.6,48.01],[-88.378114,48.302918],[-84.87608,46.900083],[-83.592851,45.816894],[-82.550925,45.347517],[-82.137642,43.571088],[-83.12,42.08],[-82.439278,41.675105],[-81.277747,42.209026],[-80.247448,42.3662],[-78.939362,42.863611],[-78.72028,43.625089],[-76.820034,43.628784],[-76.5,44.018459],[-74.867,45.00048],[-71.50506,45.0082],[-70.66,45.46],[-69.99997,46.69307],[-69.237216,47.447781],[-67.79046,47.06636],[-67.79134,45.70281],[-67.13741,45.13753]]],[[[-153.006314,57.115842],[-154.00509,56.734677],[-154.670993,57.461196],[-153.228729,57.968968],[-153.006314,57.115842]]],[[[-140.986,69.712],[-140.99778,60.30639],[-139.039,60.000007],[-137.4525,58.905],[-135.47583,59.78778],[-134.945,59.27056],[-133.35556,58.41028],[-131.70781,56.55212],[-130.00778,55.91583],[-129.98,55.285],[-130.53611,54.80278],[-131.967211,55.497776],[-132.250011,56.369996],[-133.539181,57.178887],[-134.078063,58.123068],[-136.628062,58.212209],[-137.800006,58.499995],[-139.867787,59.537762],[-142.574444,60.084447],[-143.958881,59.99918],[-147.114374,60.884656],[-148.224306,60.672989],[-148.018066,59.978329],[-149.727858,59.705658],[-151.716393,59.155821],[-151.895839,60.727198],[-152.57833,60.061657],[-154.019172,59.350279],[-153.287511,58.864728],[-154.232492,58.146374],[-156.308335,57.422774],[-156.556097,56.979985],[-158.117217,56.463608],[-158.433321,55.994154],[-159.603327,55.566686],[-160.28972,55.643581],[-163.069447,54.689737],[-162.870001,55.348043],[-161.804175,55.894986],[-160.563605,56.008055],[-160.07056,56.418055],[-157.72277,57.570001],[-157.550274,58.328326],[-158.517218,58.787781],[-159.058606,58.424186],[-160.355271,59.071123],[-161.968894,58.671665],[-161.874171,59.633621],[-162.518059,59.989724],[-163.818341,59.798056],[-165.346388,60.507496],[-165.350832,61.073895],[-166.121379,61.500019],[-164.562508,63.146378],[-163.067224,63.059459],[-162.260555,63.541936],[-160.772507,63.766108],[-161.391926,64.777235],[-162.757786,64.338605],[-163.546394,64.55916],[-164.96083,64.446945],[-166.425288,64.686672],[-168.11056,65.669997],[-164.47471,66.57666],[-163.788602,66.077207],[-161.677774,66.11612],[-162.489715,66.735565],[-163.719717,67.116395],[-165.390287,68.042772],[-166.764441,68.358877],[-166.204707,68.883031],[-164.430811,68.915535],[-163.168614,69.371115],[-162.930566,69.858062],[-161.908897,70.33333],[-159.039176,70.891642],[-158.119723,70.824721],[-156.580825,71.357764],[-153.900006,70.889989],[-152.210006,70.829992],[-150.739992,70.430017],[-149.720003,70.53001],[-147.613362,70.214035],[-144.920011,69.989992],[-143.589446,70.152514],[-140.986,69.712]]]]},"properties":{"name":"USA"},"id":"USA"}, 140 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[31.191409,-22.25151],[29.432188,-22.091313],[28.02137,-21.485975],[27.724747,-20.499059],[27.296505,-20.39152],[26.164791,-19.293086],[25.264226,-17.73654],[27.044427,-17.938026],[27.598243,-17.290831],[29.516834,-15.644678],[30.274256,-15.507787],[32.847639,-16.713398],[32.772708,-19.715592],[32.244988,-21.116489],[31.191409,-22.25151]]]},"properties":{"name":"Zimbabwe"},"id":"ZWE"} 141 | ]} 142 | """.trimIndent() 143 | 144 | val features: List> = json.toFeaturesAndProperties { stringProperty("name") } 145 | val country = features.first { it.first.id == "ZWE" } 146 | assertEquals("Zimbabwe", country.second) 147 | 148 | } 149 | 150 | 151 | } 152 | 153 | -------------------------------------------------------------------------------- /core/src/commonTest/kotlin/io/data2viz/geojson/GeoJsonCommonParsingTests.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson 2 | 3 | 4 | import kotlin.test.* 5 | 6 | 7 | class GeoJsonCommonParsingTests { 8 | 9 | @Test 10 | fun pointJson() { 11 | //language=JSON 12 | val json = """{"type":"Point", "coordinates":[1.0, 2.0]}""" 13 | val p = json.toGeoJsonObject() as Point 14 | assertEquals(p.coordinates.lon, 1.0) 15 | assertEquals(p.coordinates.lat, 2.0) 16 | } 17 | 18 | @Test 19 | fun lineString() { 20 | //language=JSON 21 | val json = """{"type":"LineString", "coordinates":[[1.0, 2.0],[3.0,4.0]]}""" 22 | val lineString = json.toGeoJsonObject() as LineString 23 | assertEquals(1.0, lineString.coordinates[0].lon) 24 | } 25 | 26 | @Test 27 | fun multiPoint() { 28 | //language=JSON 29 | val json = """{"type":"MultiPoint", "coordinates":[[1.0, 2.0],[3.0,4.0]]}""" 30 | val lineString = json.toGeoJsonObject() as MultiPoint 31 | assertEquals(1.0, lineString.coordinates[0].lon) 32 | } 33 | 34 | @Test 35 | fun multiLineString() { 36 | //language=JSON 37 | val json = """{ 38 | "type": "MultiLineString", 39 | "coordinates": [ 40 | [ 41 | [100.0, 0.0], 42 | [101.0, 1.0] 43 | ], 44 | [ 45 | [102.0, 2.0], 46 | [103.0, 3.0] 47 | ] 48 | ] 49 | }""" 50 | val multiLineString = json.toGeoJsonObject() as MultiLineString 51 | assertEquals(101.0, multiLineString.coordinates[0][1].lon) 52 | } 53 | 54 | @Test 55 | fun polygonWithoutHole() { 56 | //language=JSON 57 | val json = """ { 58 | "type": "Polygon", 59 | "coordinates": [ 60 | [ 61 | [100.0, 0.0], 62 | [101.0, 0.0], 63 | [101.0, 1.0], 64 | [100.0, 1.0], 65 | [100.0, 0.0] 66 | ] 67 | ] 68 | }""" 69 | 70 | val polygon = json.toGeoJsonObject() as Polygon 71 | assertFalse(polygon.hasHoles) 72 | } 73 | 74 | @Test 75 | fun polygonWithHoles() { 76 | //language=JSON 77 | val json = """ { 78 | "type": "Polygon", 79 | "coordinates": [ 80 | [ 81 | [100.0, 0.0], 82 | [101.0, 0.0], 83 | [101.0, 1.0], 84 | [100.0, 1.0], 85 | [100.0, 0.0] 86 | ], 87 | [ 88 | [100.8, 0.8], 89 | [100.8, 0.2], 90 | [100.2, 0.2], 91 | [100.2, 0.8], 92 | [100.8, 0.8] 93 | ] 94 | ] 95 | }""" 96 | 97 | val polygon = json.toGeoJsonObject() as Polygon 98 | assertTrue(polygon.hasHoles) 99 | } 100 | 101 | @Test 102 | fun geometryCollection() { 103 | //language=JSON 104 | val json = """ 105 | { 106 | "type": "GeometryCollection", 107 | "geometries": [{ 108 | "type": "Point", 109 | "coordinates": [100.0, 0.0] 110 | }, { 111 | "type": "LineString", 112 | "coordinates": [ 113 | [101.0, 0.0], 114 | [102.0, 1.0] 115 | ] 116 | }] 117 | } 118 | 119 | """.trimIndent() 120 | val geometryCollection = json.toGeoJsonObject() as GeometryCollection 121 | } 122 | 123 | 124 | @Test 125 | fun featureCollection() { 126 | //language=JSON 127 | val json = """ 128 | { 129 | "type": "FeatureCollection", 130 | "features": [{ 131 | "type": "Feature", 132 | "geometry": { 133 | "type": "Point", 134 | "coordinates": [102.0, 0.5] 135 | }, 136 | "properties": { 137 | "prop0": "value0" 138 | } 139 | }, { 140 | "type": "Feature", 141 | "geometry": { 142 | "type": "LineString", 143 | "coordinates": [ 144 | [102.0, 0.0], 145 | [103.0, 1.0], 146 | [104.0, 0.0], 147 | [105.0, 1.0] 148 | ] 149 | }, 150 | "properties": { 151 | "prop0": "value0", 152 | "prop1": 0.0 153 | } 154 | }, { 155 | "type": "Feature", 156 | "geometry": { 157 | "type": "Polygon", 158 | "coordinates": [ 159 | [ 160 | [100.0, 0.0], 161 | [101.0, 0.0], 162 | [101.0, 1.0], 163 | [100.0, 1.0], 164 | [100.0, 0.0] 165 | ] 166 | ] 167 | }, 168 | "properties": { 169 | "prop0": "value0", 170 | "prop1": { 171 | "this": "that" 172 | } 173 | } 174 | }] 175 | } 176 | """.trimIndent() 177 | 178 | 179 | val featureCollection = json.toGeoJsonObject() as FeatureCollection 180 | assertEquals(3, featureCollection.features.size) 181 | val feature = featureCollection.features[2] 182 | 183 | // val props = feature.properties as PolygonProps 184 | // assertEquals("value0", props.prop0) 185 | 186 | val polygon: Polygon = feature.geometry as Polygon 187 | 188 | assertEquals(100.0, polygon.coordinates[0][0].lon) 189 | } 190 | } 191 | 192 | -------------------------------------------------------------------------------- /core/src/commonTest/resources/world-110m.json: -------------------------------------------------------------------------------- 1 | {"type":"FeatureCollection", "features": [ 2 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-57.147436,5.97315],[-55.949318,5.772878],[-55.03325,6.025291],[-53.958045,5.756548],[-54.478633,4.896756],[-54.399542,4.212611],[-54.006931,3.620038],[-54.524754,2.311849],[-55.973322,2.510364],[-56.539386,1.899523],[-58.044694,4.060864],[-57.914289,4.812626],[-57.307246,5.073567],[-57.147436,5.97315]]]},"properties":{"name":"Suriname"},"id":"SUR"}, 3 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[18.853144,49.49623],[19.825023,49.217125],[21.607808,49.470107],[22.558138,49.085738],[22.085608,48.422264],[20.801294,48.623854],[20.239054,48.327567],[17.857133,47.758429],[16.979667,48.123497],[16.960288,48.596982],[18.853144,49.49623]]]},"properties":{"name":"Slovakia"},"id":"SVK"}, 4 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[13.806475,46.509306],[14.632472,46.431817],[16.202298,46.852386],[16.564808,46.503751],[15.768733,46.238108],[15.327675,45.452316],[13.71506,45.500324],[13.93763,45.591016],[13.806475,46.509306]]]},"properties":{"name":"Slovenia"},"id":"SVN"}, 5 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[11.027369,58.856149],[12.300366,60.117933],[12.631147,61.293572],[11.992064,61.800362],[11.930569,63.128318],[12.579935,64.066219],[13.571916,64.049114],[13.55569,64.787028],[15.108411,66.193867],[16.768879,68.013937],[17.729182,68.010552],[17.993868,68.567391],[19.87856,68.407194],[20.645593,69.106247],[23.539473,67.936009],[23.56588,66.396051],[23.903379,66.006927],[22.183173,65.723741],[21.213517,65.026005],[21.369631,64.413588],[17.847779,62.7494],[17.119555,61.341166],[18.787722,60.081914],[17.869225,58.953766],[16.829185,58.719827],[16.44771,57.041118],[15.879786,56.104302],[14.666681,56.200885],[14.100721,55.407781],[12.942911,55.361737],[12.625101,56.30708],[11.787942,57.441817],[11.027369,58.856149]]]},"properties":{"name":"Sweden"},"id":"SWE"}, 6 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[32.071665,-26.73382],[31.282773,-27.285879],[30.685962,-26.743845],[31.04408,-25.731452],[31.837778,-25.843332],[32.071665,-26.73382]]]},"properties":{"name":"Swaziland"},"id":"SWZ"}, 7 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[38.792341,33.378686],[36.834062,32.312938],[35.719918,32.709192],[35.821101,33.277426],[36.61175,34.201789],[35.998403,34.644914],[36.149763,35.821535],[37.066761,36.623036],[38.167727,36.90121],[39.52258,36.716054],[40.673259,37.091276],[42.349591,37.229873],[41.289707,36.358815],[41.383965,35.628317],[41.006159,34.419372],[38.792341,33.378686]]]},"properties":{"name":"Syria"},"id":"SYR"}, 8 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[14.495787,12.859396],[13.954477,13.353449],[13.540394,14.367134],[13.97217,15.68437],[15.247731,16.627306],[15.300441,17.92795],[15.903247,20.387619],[15.096888,21.308519],[14.8513,22.86295],[15.86085,23.40972],[23.83766,19.58047],[23.88689,15.61084],[23.02459,15.68072],[22.56795,14.94429],[22.03759,12.95546],[22.864165,11.142395],[21.723822,10.567056],[21.000868,9.475985],[20.059685,9.012706],[19.094008,9.074847],[17.96493,7.890914],[16.705988,7.508328],[15.27946,7.421925],[14.979996,8.796104],[13.954218,9.549495],[14.171466,10.021378],[15.467873,9.982337],[14.923565,10.891325],[14.89336,12.21905],[14.495787,12.859396]]]},"properties":{"name":"Chad"},"id":"TCD"}, 9 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[1.865241,6.142158],[1.060122,5.928837],[0.490957,7.411744],[0.36758,10.191213],[0.023803,11.018682],[0.899563,10.997339],[0.772336,10.470808],[1.664478,9.12859],[1.618951,6.832038],[1.865241,6.142158]]]},"properties":{"name":"Togo"},"id":"TGO"}, 10 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[102.584932,12.186595],[101.687158,12.64574],[100.83181,12.627085],[100.978467,13.412722],[100.097797,13.406856],[100.018733,12.307001],[99.153772,9.963061],[99.222399,9.239255],[99.873832,9.207862],[100.459274,7.429573],[102.141187,6.221636],[101.814282,5.810808],[100.085757,6.464489],[99.519642,7.343454],[98.503786,8.382305],[98.553551,9.93296],[99.587286,11.892763],[99.196354,12.804748],[99.097755,13.827503],[98.430819,14.622028],[98.903348,16.177824],[97.375896,18.445438],[97.797783,18.62708],[98.253724,19.708203],[98.959676,19.752981],[100.115988,20.41785],[100.606294,19.508344],[101.282015,19.462585],[101.059548,17.512497],[102.113592,18.109102],[102.998706,17.961695],[103.956477,18.240954],[104.716947,17.428859],[104.779321,16.441865],[105.589039,15.570316],[105.218777,14.273212],[104.281418,14.416743],[102.988422,14.225721],[102.348099,13.394247],[102.584932,12.186595]]]},"properties":{"name":"Thailand"},"id":"THA"}, 11 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[71.014198,40.244366],[70.648019,39.935754],[69.55961,40.103211],[69.464887,39.526683],[70.549162,39.604198],[71.784694,39.279463],[73.675379,39.431237],[73.928852,38.505815],[74.864816,38.378846],[74.980002,37.41999],[73.260056,37.495257],[71.844638,36.738171],[71.448693,37.065645],[71.541918,37.905774],[70.806821,38.486282],[70.116578,37.588223],[69.518785,37.608997],[68.135562,37.023115],[67.83,37.144994],[68.392033,38.157025],[68.176025,38.901553],[69.329495,40.727824],[70.666622,40.960213],[71.014198,40.244366]]]},"properties":{"name":"Tajikistan"},"id":"TJK"}, 12 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[61.210817,35.650072],[61.123071,36.491597],[60.377638,36.527383],[59.234762,37.412988],[57.330434,38.029229],[55.511578,37.964117],[54.800304,37.392421],[53.921598,37.198918],[53.880929,38.952093],[53.101028,39.290574],[52.693973,40.033629],[52.915251,40.876523],[53.858139,40.631034],[54.736845,40.951015],[53.721713,42.123191],[52.50246,41.783316],[54.079418,42.324109],[54.755345,42.043971],[55.455251,41.259859],[55.968191,41.308642],[58.629011,42.751551],[59.976422,42.223082],[60.083341,41.425146],[61.882714,41.084857],[62.37426,40.053886],[64.170223,38.892407],[66.54615,37.974685],[66.518607,37.362784],[64.746105,37.111818],[64.546479,36.312073],[63.193538,35.857166],[62.230651,35.270664],[61.210817,35.650072]]]},"properties":{"name":"Turkmenistan"},"id":"TKM"}, 13 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[124.968682,-8.89279],[126.644704,-8.398247],[126.967992,-8.668256],[125.08852,-9.393173],[124.968682,-8.89279]]]},"properties":{"name":"East Timor"},"id":"TLS"}, 14 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-61.68,10.76],[-60.895,10.855],[-60.935,10.11],[-61.77,10],[-61.68,10.76]]]},"properties":{"name":"Trinidad and Tobago"},"id":"TTO"}, 15 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[9.48214,30.307556],[9.055603,32.102692],[7.612642,33.344115],[7.524482,34.097376],[8.140981,34.655146],[8.376368,35.479876],[8.420964,36.946427],[9.509994,37.349994],[10.210002,37.230002],[10.939519,35.698984],[10.807847,34.833507],[10.149593,34.330773],[11.488787,33.136996],[11.432253,32.368903],[9.950225,31.37607],[9.970017,30.539325],[9.48214,30.307556]]]},"properties":{"name":"Tunisia"},"id":"TUN"}, 16 | {"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[41.554084,41.535656],[42.619549,41.583173],[43.582746,41.092143],[43.656436,40.253564],[44.79399,39.713003],[44.109225,39.428136],[44.77267,37.17045],[42.779126,37.385264],[42.349591,37.229873],[40.673259,37.091276],[39.52258,36.716054],[38.167727,36.90121],[37.066761,36.623036],[36.149763,35.821535],[35.550936,36.565443],[34.714553,36.795532],[34.026895,36.21996],[32.509158,36.107564],[31.699595,36.644275],[30.621625,36.677865],[29.699976,36.144357],[28.732903,36.676831],[27.641187,36.658822],[27.048768,37.653361],[26.318218,38.208133],[26.8047,38.98576],[26.170785,39.463612],[27.28002,40.420014],[28.819978,40.460011],[29.240004,41.219991],[31.145934,41.087622],[32.347979,41.736264],[33.513283,42.01896],[35.167704,42.040225],[36.913127,41.335358],[38.347665,40.948586],[40.373433,41.013673],[41.554084,41.535656]]],[[[26.056942,40.824123],[26.117042,41.826905],[27.135739,42.141485],[27.99672,42.007359],[28.806438,41.054962],[27.619017,40.999823],[26.358009,40.151994],[26.056942,40.824123]]]]},"properties":{"name":"Turkey"},"id":"TUR"}, 17 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[121.777818,24.394274],[120.74708,21.970571],[120.106189,23.556263],[120.69468,24.538451],[121.495044,25.295459],[121.777818,24.394274]]]},"properties":{"name":"Taiwan"},"id":"TWN"}, 18 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[31.78597,52.10168],[33.7527,52.335075],[35.022183,51.207572],[35.356116,50.577197],[36.626168,50.225591],[37.39346,50.383953],[38.010631,49.915662],[40.06904,49.60105],[39.67465,48.78382],[39.738278,47.898937],[38.77057,47.82562],[38.223538,47.10219],[36.759855,46.6987],[35.823685,46.645964],[34.962342,46.273197],[35.239999,44.939996],[33.882511,44.361479],[33.546924,45.034771],[32.630804,45.519186],[33.298567,46.080598],[30.748749,46.5831],[29.603289,45.293308],[28.233554,45.488283],[28.933717,46.25883],[29.759972,46.349988],[29.122698,47.849095],[27.522537,48.467119],[26.619337,48.220726],[24.866317,47.737526],[23.142236,48.096341],[22.710531,47.882194],[22.085608,48.422264],[22.558138,49.085738],[22.51845,49.476774],[24.029986,50.705407],[23.527071,51.578454],[24.553106,51.888461],[26.337959,51.832289],[29.254938,51.368234],[30.555117,51.319503],[30.927549,52.042353],[31.78597,52.10168]]]},"properties":{"name":"Ukraine"},"id":"UKR"}, 19 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-57.625133,-30.216295],[-56.976026,-30.109686],[-53.787952,-32.047243],[-53.209589,-32.727666],[-53.373662,-33.768378],[-53.806426,-34.396815],[-54.935866,-34.952647],[-56.215297,-34.859836],[-57.139685,-34.430456],[-57.817861,-34.462547],[-58.427074,-33.909454],[-58.14244,-32.044504],[-57.625133,-30.216295]]]},"properties":{"name":"Uruguay"},"id":"URY"}, 20 | {"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[-155.54211,19.08348],[-156.07347,19.70294],[-155.78505,20.2487],[-154.83147,19.45328],[-155.54211,19.08348]]],[[[-67.13741,45.13753],[-68.03252,44.3252],[-70.11617,43.68405],[-70.81489,42.8653],[-70.64,41.475],[-71.86,41.32],[-71.945,40.93],[-73.982,40.628],[-74.17838,39.70926],[-75.72205,37.93705],[-75.9718,36.89726],[-75.72749,35.55074],[-76.36318,34.80854],[-77.397635,34.51201],[-78.55435,33.86133],[-79.20357,33.15839],[-80.301325,32.509355],[-81.33629,31.44049],[-81.49042,30.72999],[-80.056539,26.88],[-80.38103,25.20616],[-81.17213,25.20126],[-81.71,25.87],[-82.85526,27.88624],[-82.65,28.55],[-83.70959,29.93656],[-85.10882,29.63615],[-86.4,30.4],[-89.18049,30.31598],[-89.40823,29.15961],[-90.880225,29.148535],[-91.626785,29.677],[-93.22637,29.78375],[-94.69,29.48],[-95.60026,28.73863],[-96.59404,28.30748],[-97.37,27.38],[-97.140008,25.869997],[-99.02,26.37],[-99.52,27.54],[-100.9576,29.38071],[-101.6624,29.7793],[-102.48,29.76],[-103.11,28.97],[-104.45697,29.57196],[-105.03737,30.64402],[-106.50759,31.75452],[-108.24,31.754854],[-108.24194,31.34222],[-111.02361,31.33472],[-114.815,32.52528],[-117.12776,32.53534],[-117.295938,33.046225],[-118.519895,34.027782],[-120.36778,34.44711],[-120.74433,35.15686],[-121.71457,36.16153],[-122.54747,37.55176],[-123.7272,38.95166],[-123.86517,39.76699],[-124.39807,40.3132],[-124.2137,41.99964],[-124.53284,42.76599],[-124.14214,43.70838],[-123.89893,45.52341],[-124.079635,46.86475],[-124.566101,48.379715],[-123.12,48.04],[-122.84,49],[-95.15907,49],[-94.32914,48.67074],[-92.61,48.45],[-91.64,48.14],[-90.83,48.27],[-89.6,48.01],[-88.378114,48.302918],[-84.87608,46.900083],[-83.592851,45.816894],[-82.550925,45.347517],[-82.137642,43.571088],[-83.12,42.08],[-82.439278,41.675105],[-81.277747,42.209026],[-80.247448,42.3662],[-78.939362,42.863611],[-78.72028,43.625089],[-76.820034,43.628784],[-76.5,44.018459],[-74.867,45.00048],[-71.50506,45.0082],[-70.66,45.46],[-69.99997,46.69307],[-69.237216,47.447781],[-67.79046,47.06636],[-67.79134,45.70281],[-67.13741,45.13753]]],[[[-153.006314,57.115842],[-154.00509,56.734677],[-154.670993,57.461196],[-153.228729,57.968968],[-153.006314,57.115842]]],[[[-140.986,69.712],[-140.99778,60.30639],[-139.039,60.000007],[-137.4525,58.905],[-135.47583,59.78778],[-134.945,59.27056],[-133.35556,58.41028],[-131.70781,56.55212],[-130.00778,55.91583],[-129.98,55.285],[-130.53611,54.80278],[-131.967211,55.497776],[-132.250011,56.369996],[-133.539181,57.178887],[-134.078063,58.123068],[-136.628062,58.212209],[-137.800006,58.499995],[-139.867787,59.537762],[-142.574444,60.084447],[-143.958881,59.99918],[-147.114374,60.884656],[-148.224306,60.672989],[-148.018066,59.978329],[-149.727858,59.705658],[-151.716393,59.155821],[-151.895839,60.727198],[-152.57833,60.061657],[-154.019172,59.350279],[-153.287511,58.864728],[-154.232492,58.146374],[-156.308335,57.422774],[-156.556097,56.979985],[-158.117217,56.463608],[-158.433321,55.994154],[-159.603327,55.566686],[-160.28972,55.643581],[-163.069447,54.689737],[-162.870001,55.348043],[-161.804175,55.894986],[-160.563605,56.008055],[-160.07056,56.418055],[-157.72277,57.570001],[-157.550274,58.328326],[-158.517218,58.787781],[-159.058606,58.424186],[-160.355271,59.071123],[-161.968894,58.671665],[-161.874171,59.633621],[-162.518059,59.989724],[-163.818341,59.798056],[-165.346388,60.507496],[-165.350832,61.073895],[-166.121379,61.500019],[-164.562508,63.146378],[-163.067224,63.059459],[-162.260555,63.541936],[-160.772507,63.766108],[-161.391926,64.777235],[-162.757786,64.338605],[-163.546394,64.55916],[-164.96083,64.446945],[-166.425288,64.686672],[-168.11056,65.669997],[-164.47471,66.57666],[-163.788602,66.077207],[-161.677774,66.11612],[-162.489715,66.735565],[-163.719717,67.116395],[-165.390287,68.042772],[-166.764441,68.358877],[-166.204707,68.883031],[-164.430811,68.915535],[-163.168614,69.371115],[-162.930566,69.858062],[-161.908897,70.33333],[-159.039176,70.891642],[-158.119723,70.824721],[-156.580825,71.357764],[-153.900006,70.889989],[-152.210006,70.829992],[-150.739992,70.430017],[-149.720003,70.53001],[-147.613362,70.214035],[-144.920011,69.989992],[-143.589446,70.152514],[-140.986,69.712]]]]},"properties":{"name":"USA"},"id":"USA"}, 21 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[31.191409,-22.25151],[29.432188,-22.091313],[28.02137,-21.485975],[27.724747,-20.499059],[27.296505,-20.39152],[26.164791,-19.293086],[25.264226,-17.73654],[27.044427,-17.938026],[27.598243,-17.290831],[29.516834,-15.644678],[30.274256,-15.507787],[32.847639,-16.713398],[32.772708,-19.715592],[32.244988,-21.116489],[31.191409,-22.25151]]]},"properties":{"name":"Zimbabwe"},"id":"ZWE"} 22 | ]} -------------------------------------------------------------------------------- /core/src/iosMain/kotlin/geojson/toGeoJsonObjectIOS.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson 2 | 3 | /** 4 | * Parse the String as a GeoJsonObject 5 | */ 6 | actual fun String.toGeoJsonObject(): GeoJsonObject = Point(doubleArrayOf(.0, .0)) 7 | 8 | actual class FeatureProperties { 9 | actual fun stringProperty(name: String): String = "" 10 | actual fun intProperty(name: String): Int = 0 11 | actual fun booleanProperty(name: String): Boolean = true 12 | } 13 | 14 | actual fun String.toFeaturesAndProperties(extractFunction: FeatureProperties.() -> T): List> { 15 | val features = listOf(Feature(Point(doubleArrayOf()))) 16 | val properties = FeatureProperties() 17 | return features.map { feature -> 18 | Pair(feature, extractFunction(properties)) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /core/src/jsMain/kotlin/io/data2viz/geojson/js/GeoJsonJs.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.js 2 | 3 | import io.data2viz.geojson.* 4 | 5 | 6 | 7 | external interface Typed { 8 | val type:String 9 | } 10 | 11 | 12 | fun Typed.asGeoJsonObject():GeoJsonObject = 13 | when (type) { 14 | "Point" -> Point(asDynamic().coordinates) 15 | "MultiPoint" -> MultiPoint(asDynamic().coordinates) 16 | "LineString" -> LineString(asDynamic().coordinates) 17 | "MultiLineString" -> MultiLineString(asDynamic().coordinates) 18 | "Polygon" -> Polygon(asDynamic().coordinates) 19 | "MultiPolygon" -> MultiPolygon(asDynamic().coordinates) 20 | "GeometryCollection" -> { 21 | val types:Array = asDynamic().geometries 22 | val geometries:Array = types.map {it.asGeoJsonObject() as Geometry}.toTypedArray() 23 | GeometryCollection(geometries) 24 | } 25 | "Feature" -> { 26 | val dyn: dynamic = asDynamic() 27 | val geometry:Typed = dyn.geometry 28 | Feature(geometry.asGeoJsonObject() as Geometry, dyn.id, dyn.properties) 29 | } 30 | "FeatureCollection" -> asFeatureCollection() 31 | else -> throw IllegalStateException("${type} is not known") 32 | } 33 | 34 | 35 | 36 | private fun Typed.asFeatureCollection(): FeatureCollection { 37 | val dyn:dynamic = this 38 | val featureJs:dynamic = dyn.features 39 | val features:dynamic = Array(0, {Point(doubleArrayOf())}) 40 | val size:Int = featureJs.length 41 | for (i in 0 until size) { 42 | val feature = featureJs[i] 43 | val typed:Typed = feature.geometry 44 | features[i] = Feature(typed.asGeoJsonObject() as Geometry, feature.id, feature.properties) 45 | } 46 | return FeatureCollection(features) 47 | } 48 | -------------------------------------------------------------------------------- /core/src/jsMain/kotlin/io/data2viz/geojson/toGeoJsonObject.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson 2 | 3 | import io.data2viz.geojson.js.Typed 4 | import io.data2viz.geojson.js.asGeoJsonObject 5 | 6 | /** 7 | * Parse the String as a GeoJsonObject 8 | */ 9 | actual fun String.toGeoJsonObject(): GeoJsonObject = JSON.parse(this).asGeoJsonObject() 10 | 11 | actual class FeatureProperties { 12 | 13 | var properties: dynamic = null 14 | 15 | actual fun stringProperty(name: String): String = properties[name] as String 16 | actual fun intProperty(name: String): Int = properties[name] as Int 17 | actual fun booleanProperty(name: String): Boolean = properties[name] as Boolean 18 | } 19 | 20 | actual fun String.toFeaturesAndProperties(extractFunction: FeatureProperties.() -> T): List> { 21 | val features = JSON.parse(this).asGeoJsonObject() as FeatureCollection 22 | val properties = FeatureProperties() 23 | return features.features.map { feature -> 24 | properties.properties = feature.properties 25 | Pair(feature, extractFunction(properties)) 26 | } 27 | } -------------------------------------------------------------------------------- /core/src/jsTest/kotlin/io/data2viz/geojson/js/GeoJsonSerializationTests.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.js 2 | 3 | import io.data2viz.geojson.* 4 | //import kotlinx.coroutines.experimental.* 5 | import org.w3c.fetch.Request 6 | import kotlin.browser.window 7 | import kotlin.js.Date 8 | import kotlin.test.* 9 | 10 | val browserEnabled: Boolean = js("typeof document !== 'undefined'") as Boolean 11 | 12 | class GeoJsonJsParsingTests { 13 | 14 | // @Test 15 | // fun loadBigJson() = promise { 16 | // if (browserEnabled) { 17 | // val request = window.fetch(Request("base/build/classes/kotlin/test/ny.json")) 18 | // val response = request.await() 19 | // val text = response.text().await() 20 | // val start = Date.now() 21 | // val featureCollection = text.toGeoJsonObject() as FeatureCollection 22 | // val multi = featureCollection.features 23 | // .filter { it.geometry is MultiPolygon } 24 | // .map { it.geometry as MultiPolygon } 25 | // val polygons = multi.flatMap { it.coordinates.toList() } 26 | // println("Parsing in ${Date.now() - start} ms") 27 | // assertEquals(104, polygons.size) 28 | // } else { 29 | // println("Not in a browser environment => skip loadBigJson test.") 30 | // } 31 | // 32 | // } 33 | } -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/Crs.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | import io.data2viz.geojson.jackson.jackson.CrsType 4 | 5 | import java.io.Serializable 6 | import java.util.HashMap 7 | 8 | 9 | 10 | class Crs : Serializable { 11 | 12 | var type: CrsType? = CrsType.NAME 13 | var properties: Map? = HashMap() 14 | 15 | override fun equals(o: Any?): Boolean { 16 | if (this === o) return true 17 | if (o !is Crs) return false 18 | val crs = o as Crs? 19 | return if (if (properties != null) properties != crs!!.properties else crs!!.properties != null) { 20 | false 21 | } else !if (type != null) type != crs.type else crs.type != null 22 | } 23 | 24 | override fun hashCode(): Int = 31 * (type?.hashCode() ?: 0) + (properties?.hashCode() ?: 0) 25 | 26 | override fun toString(): String = "Crs{type='$type', properties=$properties}" 27 | } 28 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/Feature.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude 4 | 5 | import java.util.HashMap 6 | 7 | class Feature : GeoJsonObject() { 8 | 9 | @JsonInclude(JsonInclude.Include.ALWAYS) 10 | private var properties: MutableMap? = HashMap() 11 | 12 | @JsonInclude(JsonInclude.Include.ALWAYS) 13 | var geometry: GeoJsonObject? = null 14 | 15 | 16 | var id: Any? = null 17 | 18 | fun setProperty(key: String, value: Any) { 19 | properties!![key] = value 20 | } 21 | 22 | fun getProperty(key: String): T = properties!![key] as T 23 | 24 | fun getProperties(): Map? = properties 25 | 26 | fun setProperties(properties: MutableMap) { 27 | this.properties = properties 28 | } 29 | 30 | override fun accept(geoJsonObjectVisitor: GeoJsonObjectVisitor): T = geoJsonObjectVisitor.visit(this)!! 31 | 32 | override fun equals(o: Any?): Boolean { 33 | if (this === o) return true 34 | if (o == null || javaClass != o.javaClass) return false 35 | if (!super.equals(o)) return false 36 | val feature = o as Feature 37 | if ( if (properties != null) properties != feature.properties else feature.properties != null) 38 | return false 39 | return if (if (geometry != null) geometry != feature.geometry else feature.geometry != null) false else !if (id != null) id != feature.id else feature.id != null 40 | } 41 | 42 | override fun hashCode(): Int { 43 | var result = super.hashCode() 44 | result = 31 * result + (properties?.hashCode() ?: 0) 45 | result = 31 * result + (geometry?.hashCode() ?: 0) 46 | result = 31 * result + (id?.hashCode() ?: 0) 47 | return result 48 | } 49 | 50 | override fun toString(): String = "Feature{properties=$properties, geometry=$geometry, id='$id'}" 51 | } 52 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/FeatureCollection.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | import java.util.ArrayList 4 | 5 | class FeatureCollection : GeoJsonObject(), Iterable { 6 | 7 | private var features: MutableList = ArrayList() 8 | 9 | fun getFeatures(): List = features 10 | 11 | fun setFeatures(features: MutableList) { 12 | this.features = features 13 | } 14 | 15 | fun add(feature: Feature): FeatureCollection = this.apply { features.add(feature) } 16 | 17 | fun addAll(features: Collection) { 18 | this.features.addAll(features) 19 | } 20 | 21 | override fun iterator(): Iterator = features.iterator() 22 | 23 | override fun accept(geoJsonObjectVisitor: GeoJsonObjectVisitor): T = geoJsonObjectVisitor.visit(this)!! 24 | 25 | override fun equals(o: Any?): Boolean { 26 | if (this === o) 27 | return true 28 | if (o !is FeatureCollection) 29 | return false 30 | val features1 = o as FeatureCollection? 31 | return features == features1!!.features 32 | } 33 | 34 | override fun hashCode(): Int = features.hashCode() 35 | 36 | override fun toString(): String = "FeatureCollection{features=$features}" 37 | } 38 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/GeoJsonObject.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude 4 | import com.fasterxml.jackson.annotation.JsonInclude.Include 5 | import com.fasterxml.jackson.annotation.JsonSubTypes 6 | import com.fasterxml.jackson.annotation.JsonTypeInfo 7 | import com.fasterxml.jackson.annotation.JsonTypeInfo.Id 8 | 9 | import java.io.Serializable 10 | import java.util.Arrays 11 | 12 | @JsonTypeInfo(property = "type", use = Id.NAME) 13 | @JsonSubTypes( 14 | JsonSubTypes.Type(FeatureCollection::class), 15 | JsonSubTypes.Type(Feature::class), 16 | JsonSubTypes.Type(MultiPolygon::class), 17 | JsonSubTypes.Type(Polygon::class), 18 | JsonSubTypes.Type(GeometryCollection::class), 19 | JsonSubTypes.Type(MultiLineString::class), 20 | JsonSubTypes.Type(MultiPoint::class), 21 | JsonSubTypes.Type(LineString::class), 22 | JsonSubTypes.Type(Point::class) 23 | ) 24 | @JsonInclude(Include.NON_NULL) 25 | abstract class GeoJsonObject: Serializable { 26 | 27 | var crs: Crs? = null 28 | var bbox: DoubleArray? = null 29 | 30 | abstract fun accept(geoJsonObjectVisitor: GeoJsonObjectVisitor): T 31 | 32 | override fun equals(o: Any?): Boolean { 33 | if (this === o) 34 | return true 35 | if (o == null || this::class != o::class) 36 | return false 37 | val that = o as GeoJsonObject 38 | return if (if (crs != null) crs != that.crs else that.crs != null) false else Arrays.equals(bbox, that.bbox) 39 | } 40 | 41 | override fun hashCode(): Int { 42 | var result = crs?.hashCode() ?: 0 43 | result = 31 * result + if (bbox != null) Arrays.hashCode(bbox) else 0 44 | return result 45 | } 46 | 47 | override fun toString(): String = "GeoJsonObject{}" 48 | } 49 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/GeoJsonObjectVisitor.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | /** 4 | * Visitor to handle all different types of [GeoJsonObject]. 5 | * 6 | * @param 7 | * return type of the visitor. 8 | */ 9 | interface GeoJsonObjectVisitor { 10 | 11 | fun visit(geoJsonObject: GeometryCollection): T? 12 | 13 | fun visit(geoJsonObject: FeatureCollection): T? 14 | 15 | fun visit(geoJsonObject: Point): T? 16 | 17 | fun visit(geoJsonObject: Feature): T? 18 | 19 | fun visit(geoJsonObject: MultiLineString): T? 20 | 21 | fun visit(geoJsonObject: Polygon): T? 22 | 23 | fun visit(geoJsonObject: MultiPolygon): T? 24 | 25 | fun visit(geoJsonObject: MultiPoint): T? 26 | 27 | fun visit(geoJsonObject: LineString): T? 28 | 29 | /** 30 | * An abstract adapter class for visiting GeoJson objects. 31 | * The methods in this class are empty. 32 | * This class exists as convenience for creating listener objects. 33 | * 34 | * @param Return type of the visitor 35 | */ 36 | class Adapter : GeoJsonObjectVisitor { 37 | 38 | override fun visit(geoJsonObject: GeometryCollection): T? = GeometryCollection() as T 39 | override fun visit(geoJsonObject: FeatureCollection): T? = FeatureCollection() as T 40 | override fun visit(geoJsonObject: Point): T? = null 41 | override fun visit(geoJsonObject: Feature): T? = null 42 | override fun visit(geoJsonObject: MultiLineString): T? = null 43 | override fun visit(geoJsonObject: Polygon): T? = null 44 | override fun visit(geoJsonObject: MultiPolygon): T? = null 45 | override fun visit(geoJsonObject: MultiPoint): T? = null 46 | override fun visit(geoJsonObject: LineString): T? = null 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/Geometry.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | 4 | abstract class Geometry : GeoJsonObject { 5 | 6 | val coordinates: MutableList = mutableListOf() 7 | 8 | constructor() 9 | 10 | constructor(vararg elements: T) { 11 | elements.forEach { coordinate -> coordinates.add(coordinate) } 12 | } 13 | 14 | fun add(elements: T): Geometry = apply { coordinates.add(elements) } 15 | 16 | override fun equals(o: Any?): Boolean { 17 | if (this === o) return true 18 | if (o !is Geometry<*>) return false 19 | if (!super.equals(o)) return false 20 | return coordinates == o.coordinates 21 | } 22 | 23 | override fun hashCode(): Int = 31 * super.hashCode() + coordinates.hashCode() 24 | 25 | override fun toString(): String = "Geometry{coordinates=$coordinates} ${super.toString()}" 26 | } 27 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/GeometryCollection.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | 4 | class GeometryCollection : GeoJsonObject(), Iterable { 5 | 6 | private var geometries: MutableList = mutableListOf() 7 | 8 | fun getGeometries(): List = geometries 9 | 10 | fun setGeometries(geometries: MutableList) { 11 | this.geometries = geometries 12 | } 13 | 14 | override fun iterator(): Iterator = geometries!!.iterator() 15 | 16 | fun add(geometry: GeoJsonObject): GeometryCollection = this.apply { geometries!!.add(geometry)} 17 | 18 | override fun accept(geoJsonObjectVisitor: GeoJsonObjectVisitor): T { 19 | return geoJsonObjectVisitor.visit(this)!! 20 | } 21 | 22 | override fun equals(o: Any?): Boolean { 23 | if (this === o) 24 | return true 25 | if (o !is GeometryCollection) 26 | return false 27 | if (!super.equals(o)) 28 | return false 29 | val that = o as GeometryCollection? 30 | return !if (geometries != null) geometries != that!!.geometries else that!!.geometries != null 31 | } 32 | 33 | override fun hashCode(): Int = 31 * super.hashCode() + (geometries?.hashCode() ?:0) 34 | 35 | override fun toString(): String = "GeometryCollection{geometries=$geometries} ${super.toString()}" 36 | } 37 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/LineString.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | class LineString : MultiPoint { 4 | 5 | constructor() 6 | 7 | constructor(vararg points: LngLatAlt) : super(*points) 8 | 9 | override fun accept(geoJsonObjectVisitor: GeoJsonObjectVisitor): T = geoJsonObjectVisitor.visit(this)!! 10 | 11 | override fun toString(): String = "LineString{} ${super.toString()}" 12 | } 13 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/LngLatAlt.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize 4 | import com.fasterxml.jackson.databind.annotation.JsonSerialize 5 | import io.data2viz.geojson.jackson.jackson.LngLatAltDeserializer 6 | import io.data2viz.geojson.jackson.jackson.LngLatAltSerializer 7 | 8 | import java.io.Serializable 9 | import java.util.Arrays 10 | 11 | /** 12 | * Construct a LngLatAlt with additional elements. 13 | * The specification allows for any number of additional elements in a position, after lng, lat, alt. 14 | * http://geojson.org/geojson-spec.html#positions 15 | */ 16 | @JsonDeserialize(using = LngLatAltDeserializer::class) 17 | @JsonSerialize(using = LngLatAltSerializer::class) 18 | class LngLatAlt 19 | ( 20 | var longitude: Double, 21 | var latitude: Double, 22 | private var altitude: Double = Double.NaN, 23 | vararg additionalElements: Double 24 | ) : Serializable { 25 | 26 | private var additionalElements = DoubleArray(0) 27 | 28 | fun hasAltitude(): Boolean = !java.lang.Double.isNaN(altitude) 29 | 30 | private fun hasAdditionalElements(): Boolean = additionalElements.isNotEmpty() 31 | 32 | fun getAltitude(): Double = altitude 33 | 34 | fun setAltitude(altitude: Double) { 35 | this.altitude = altitude 36 | checkAltitudeAndAdditionalElements() 37 | } 38 | 39 | fun getAdditionalElements(): DoubleArray { 40 | return additionalElements 41 | } 42 | 43 | fun setAdditionalElements(vararg additionalElements: Double) { 44 | require(additionalElements.none { java.lang.Double.isNaN(it) }) { "No additional elements may be NaN." } 45 | require(additionalElements.none { java.lang.Double.isInfinite(it) }) { "No additional elements may be infinite." } 46 | this.additionalElements = additionalElements 47 | checkAltitudeAndAdditionalElements() 48 | } 49 | 50 | override fun equals(other: Any?): Boolean { 51 | if (this === other) return true 52 | if (other !is LngLatAlt) return false 53 | val lngLatAlt = other as LngLatAlt? 54 | return (java.lang.Double.compare( 55 | lngLatAlt!!.latitude, 56 | latitude 57 | ) == 0 && java.lang.Double.compare(lngLatAlt.longitude, longitude) == 0 58 | && java.lang.Double.compare(lngLatAlt.altitude, altitude) == 0 && 59 | Arrays.equals(lngLatAlt.getAdditionalElements(), additionalElements)) 60 | } 61 | 62 | override fun hashCode(): Int { 63 | var temp = java.lang.Double.doubleToLongBits(longitude) 64 | var result = (temp xor temp.ushr(32)).toInt() 65 | temp = java.lang.Double.doubleToLongBits(latitude) 66 | result = 31 * result + (temp xor temp.ushr(32)).toInt() 67 | temp = java.lang.Double.doubleToLongBits(altitude) 68 | result = 31 * result + (temp xor temp.ushr(32)).toInt() 69 | for (element in additionalElements) { 70 | temp = java.lang.Double.doubleToLongBits(element) 71 | result = 31 * result + (temp xor temp.ushr(32)).toInt() 72 | } 73 | return result 74 | } 75 | 76 | override fun toString(): String { 77 | var s = "LngLatAlt{longitude=$longitude, latitude=$latitude, altitude=$altitude" 78 | s += if (additionalElements.isNotEmpty()) additionalElements.joinToString(prefix = ", additionalElements=[", postfix = "]}") else "}" 79 | return s 80 | } 81 | 82 | private fun checkAltitudeAndAdditionalElements() { 83 | require(hasAltitude() || !hasAdditionalElements()) { "Additional Elements are only valid if Altitude is also provided." } 84 | } 85 | 86 | init { 87 | setAdditionalElements(*additionalElements) 88 | checkAltitudeAndAdditionalElements() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/MultiLineString.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | class MultiLineString : Geometry> { 4 | 5 | constructor() 6 | 7 | constructor(line: List) { 8 | add(line) 9 | } 10 | 11 | override fun accept(geoJsonObjectVisitor: GeoJsonObjectVisitor): T = geoJsonObjectVisitor.visit(this)!! 12 | 13 | override fun toString(): String = "MultiLineString{} ${super.toString()}" 14 | } 15 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/MultiPoint.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | open class MultiPoint : Geometry { 4 | 5 | constructor() 6 | 7 | constructor(vararg points: LngLatAlt) : super(*points) 8 | 9 | override fun accept(geoJsonObjectVisitor: GeoJsonObjectVisitor): T = geoJsonObjectVisitor.visit(this)!! 10 | 11 | override fun toString(): String = "MultiPoint{} ${super.toString()}" 12 | } 13 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/MultiPolygon.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | class MultiPolygon : Geometry>> { 4 | 5 | constructor() 6 | 7 | constructor(polygon: Polygon) { 8 | add(polygon) 9 | } 10 | 11 | fun add(polygon: Polygon): MultiPolygon = this.apply { coordinates.add(polygon.coordinates) } 12 | 13 | override fun accept(geoJsonObjectVisitor: GeoJsonObjectVisitor): T = geoJsonObjectVisitor.visit(this)!! 14 | override fun toString(): String = "MultiPolygon{} ${super.toString()}" 15 | } 16 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/Point.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | class Point : GeoJsonObject { 4 | 5 | var coordinates: LngLatAlt = LngLatAlt(Double.NaN, Double.NaN) 6 | 7 | constructor() {} 8 | 9 | constructor(coordinates: LngLatAlt) { 10 | this.coordinates = coordinates 11 | } 12 | 13 | constructor(longitude: Double, latitude: Double) { 14 | coordinates = LngLatAlt(longitude, latitude) 15 | } 16 | 17 | constructor(longitude: Double, latitude: Double, altitude: Double) { 18 | coordinates = LngLatAlt(longitude, latitude, altitude) 19 | } 20 | 21 | constructor(longitude: Double, latitude: Double, altitude: Double, vararg additionalElements: Double) { 22 | coordinates = LngLatAlt(longitude, latitude, altitude, *additionalElements) 23 | } 24 | 25 | override fun accept(geoJsonObjectVisitor: GeoJsonObjectVisitor): T { 26 | return geoJsonObjectVisitor.visit(this)!! 27 | } 28 | 29 | override fun equals(o: Any?): Boolean { 30 | if (this === o) { 31 | return true 32 | } 33 | if (o !is Point) { 34 | return false 35 | } 36 | if (!super.equals(o)) { 37 | return false 38 | } 39 | val point = o as Point? 40 | return !if (coordinates != null) coordinates != point!!.coordinates else point!!.coordinates != null 41 | } 42 | 43 | override fun hashCode(): Int = 31 * super.hashCode() + (coordinates?.hashCode() ?: 0) 44 | 45 | override fun toString(): String = "Point{coordinates=$coordinates} ${super.toString()}" 46 | } 47 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/Polygon.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonIgnore 5 | 6 | class Polygon : Geometry> { 7 | 8 | var exteriorRing: List 9 | @JsonIgnore 10 | get() { 11 | assertExteriorRing() 12 | return coordinates[0] 13 | } 14 | set(points) = coordinates.add(0, points) 15 | 16 | val interiorRings: List> 17 | @JsonIgnore 18 | get() { 19 | assertExteriorRing() 20 | return coordinates.subList(1, coordinates.size) 21 | } 22 | 23 | constructor() 24 | 25 | constructor(polygon: List) { 26 | add(polygon) 27 | } 28 | 29 | constructor(vararg polygon: LngLatAlt) { 30 | add(polygon.asList()) 31 | } 32 | 33 | fun getInteriorRing(index: Int): List { 34 | assertExteriorRing() 35 | return coordinates[1 + index] 36 | } 37 | 38 | fun addInteriorRing(points: List) { 39 | assertExteriorRing() 40 | coordinates.add(points) 41 | } 42 | 43 | fun addInteriorRing(vararg points: LngLatAlt) { 44 | assertExteriorRing() 45 | coordinates.add(points.asList()) 46 | } 47 | 48 | private fun assertExteriorRing() = check(coordinates.isNotEmpty()) {"No exterior ring defined"} 49 | 50 | override fun accept(geoJsonObjectVisitor: GeoJsonObjectVisitor): T { 51 | return geoJsonObjectVisitor.visit(this) as T 52 | } 53 | 54 | override fun toString(): String = "Polygon{} ${super.toString()}" 55 | } 56 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/jackson/CrsType.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson.jackson 2 | 3 | import com.fasterxml.jackson.annotation.JsonValue 4 | import com.fasterxml.jackson.annotation.JsonCreator 5 | 6 | 7 | 8 | enum class CrsType { 9 | NAME, LINK; 10 | 11 | companion object { 12 | @JvmStatic 13 | @JsonCreator 14 | fun forValue(value: String): CrsType { 15 | return valueOf(value.toUpperCase()) 16 | } 17 | 18 | } 19 | 20 | @JsonValue 21 | fun toValue(): String = name.toLowerCase() 22 | 23 | } 24 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/jackson/LngLatAltDeserializer.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson.jackson 2 | 3 | import com.fasterxml.jackson.core.JsonParser 4 | import com.fasterxml.jackson.core.JsonToken 5 | import com.fasterxml.jackson.databind.DeserializationContext 6 | import com.fasterxml.jackson.databind.JsonDeserializer 7 | import io.data2viz.geojson.jackson.LngLatAlt 8 | 9 | import java.io.IOException 10 | import java.util.ArrayList 11 | 12 | class LngLatAltDeserializer : JsonDeserializer() { 13 | 14 | fun DeserializationContext.handle( 15 | clazz: Class<*>, 16 | jp: JsonParser 17 | ):Nothing { 18 | this.handleUnexpectedToken(clazz, jp) 19 | throw IllegalArgumentException("Exception should have been thrown before!!") 20 | } 21 | 22 | @Throws(IOException::class) 23 | override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): LngLatAlt { 24 | return if (jp.isExpectedStartArrayToken) deserializeArray(jp, ctxt) else ctxt.handle(LngLatAlt::class.java, jp) 25 | } 26 | 27 | @Throws(IOException::class) 28 | protected fun deserializeArray(jp: JsonParser, ctxt: DeserializationContext): LngLatAlt { 29 | val node = LngLatAlt(extractDouble(jp, ctxt, false), extractDouble(jp, ctxt, false)) 30 | node.setAltitude(extractDouble(jp, ctxt, true)) 31 | val additionalElementsList = ArrayList() 32 | while (jp.hasCurrentToken() && jp.currentToken != JsonToken.END_ARRAY) { 33 | val element = extractDouble(jp, ctxt, true) 34 | if (!java.lang.Double.isNaN(element)) { 35 | additionalElementsList.add(element) 36 | } 37 | } 38 | val additionalElements = DoubleArray(additionalElementsList.size) 39 | for (i in additionalElements.indices) { 40 | additionalElements[i] = additionalElementsList[i] 41 | } 42 | node.setAdditionalElements(*additionalElements) 43 | return node 44 | } 45 | 46 | @Throws(IOException::class) 47 | private fun extractDouble(jp: JsonParser, ctxt: DeserializationContext, optional: Boolean): Double { 48 | val token = jp.nextToken() 49 | return if (token == null) { 50 | if (optional) 51 | java.lang.Double.NaN 52 | else 53 | throw ctxt.mappingException("Unexpected end-of-input when binding data into LngLatAlt") 54 | } else { 55 | when (token) { 56 | JsonToken.END_ARRAY -> if (optional) 57 | java.lang.Double.NaN 58 | else 59 | throw ctxt.mappingException("Unexpected end-of-input when binding data into LngLatAlt") 60 | JsonToken.VALUE_NUMBER_FLOAT -> jp.doubleValue 61 | JsonToken.VALUE_NUMBER_INT -> jp.longValue.toDouble() 62 | JsonToken.VALUE_STRING -> jp.valueAsDouble 63 | else -> throw ctxt.mappingException( 64 | "Unexpected token (" + token.name + ") when binding data into LngLatAlt" 65 | ) 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/jackson/jackson/LngLatAltSerializer.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson.jackson 2 | 3 | import java.io.IOException 4 | 5 | import io.data2viz.geojson.jackson.LngLatAlt 6 | 7 | import com.fasterxml.jackson.core.JsonGenerator 8 | import com.fasterxml.jackson.databind.JsonSerializer 9 | import com.fasterxml.jackson.databind.SerializerProvider 10 | 11 | class LngLatAltSerializer : JsonSerializer() { 12 | 13 | @Throws(IOException::class) 14 | override fun serialize(value: LngLatAlt, jgen: JsonGenerator, provider: SerializerProvider) { 15 | jgen.writeStartArray() 16 | jgen.writeNumber(value.longitude) 17 | jgen.writeNumber(value.latitude) 18 | if (value.hasAltitude()) { 19 | jgen.writeNumber(value.getAltitude()) 20 | 21 | for (d in value.getAdditionalElements()) { 22 | jgen.writeNumber(d) 23 | } 24 | } 25 | jgen.writeEndArray() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/jvmMain/kotlin/io/data2viz/geojson/toGeoJsonObject.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import io.data2viz.geojson.jackson.LngLatAlt 5 | 6 | 7 | typealias JacksonGeoJsonObject = io.data2viz.geojson.jackson.GeoJsonObject 8 | typealias JacksonPoint = io.data2viz.geojson.jackson.Point 9 | typealias JacksonMultiPoint = io.data2viz.geojson.jackson.MultiPoint 10 | typealias JacksonLineString = io.data2viz.geojson.jackson.LineString 11 | typealias JacksonMultiLineString = io.data2viz.geojson.jackson.MultiLineString 12 | typealias JacksonPolygon = io.data2viz.geojson.jackson.Polygon 13 | typealias JacksonMultiPolygon = io.data2viz.geojson.jackson.MultiPolygon 14 | typealias JacksonGeometryCollection = io.data2viz.geojson.jackson.GeometryCollection 15 | typealias JacksonFeature = io.data2viz.geojson.jackson.Feature 16 | typealias JacksonFeatureCollection = io.data2viz.geojson.jackson.FeatureCollection 17 | 18 | /** 19 | * Parse the String as a GeoJsonObject 20 | */ 21 | actual fun String.toGeoJsonObject(): GeoJsonObject = 22 | ObjectMapper().readValue(this, JacksonGeoJsonObject::class.java).toGeoJsonObject() 23 | 24 | fun io.data2viz.geojson.jackson.GeoJsonObject.toGeoJsonObject(): GeoJsonObject = when (this) { 25 | is JacksonPoint -> this.toPoint() 26 | is JacksonLineString -> this.toLineString() //be carefull to keep this order (LineString is a MultiPoint) 27 | is JacksonMultiPoint -> this.toMultiPoint() 28 | is JacksonMultiLineString -> this.toMultiLineString() 29 | is JacksonPolygon -> this.toPolygon() 30 | is JacksonMultiPolygon -> this.toMultiPolygon() 31 | is JacksonGeometryCollection -> this.toGeometryCollection() 32 | is JacksonFeature -> this.toFeature() 33 | is JacksonFeatureCollection -> this.toFeatureCollection() 34 | 35 | else -> { 36 | throw IllegalStateException("Unknown GeoJson type:: ${this.javaClass}") 37 | } 38 | } 39 | 40 | private fun JacksonPoint.toPoint() = Point(this.coordinates.toPosition()) 41 | private fun io.data2viz.geojson.jackson.MultiPoint.toMultiPoint() = MultiPoint(this.coordinates.toLine()) 42 | private fun io.data2viz.geojson.jackson.LineString.toLineString() = LineString(this.coordinates.toLine()) 43 | private fun JacksonMultiLineString.toMultiLineString() = MultiLineString(this.coordinates.toSurface()) 44 | private fun JacksonPolygon.toPolygon() = Polygon(this.coordinates.toSurface()) 45 | private fun JacksonMultiPolygon.toMultiPolygon() = MultiPolygon(this.coordinates.toSurfaces()) 46 | private fun JacksonGeometryCollection.toGeometryCollection() = GeometryCollection(this.getGeometries().map { it.toGeoJsonObject() as Geometry }.toTypedArray()) 47 | private fun JacksonFeature.toFeature() = Feature(this.geometry!!.toGeoJsonObject() as Geometry, this.id, this.getProperties()) 48 | private fun JacksonFeatureCollection.toFeatureCollection() = FeatureCollection(this.getFeatures().map { it.toFeature() }.toTypedArray()) 49 | 50 | 51 | private fun LngLatAlt.toPosition():Position = 52 | if (hasAltitude()) doubleArrayOf(this.longitude, this.latitude, this.getAltitude()) 53 | else doubleArrayOf(this.longitude, this.latitude) 54 | 55 | 56 | fun Collection.toLine(): Array = map { it.toPosition() }.toTypedArray() 57 | fun Collection>.toSurface(): Array = map { it.toLine() }.toTypedArray() 58 | fun Collection>>.toSurfaces(): Array = map { it.toSurface() }.toTypedArray() 59 | 60 | actual class FeatureProperties { 61 | var properties: Map = mapOf() 62 | actual fun stringProperty(name: String): String = properties[name] as String 63 | actual fun intProperty(name: String): Int = properties[name] as Int 64 | actual fun booleanProperty(name: String): Boolean = properties[name] as Boolean 65 | } 66 | 67 | actual fun String.toFeaturesAndProperties(extractFunction: FeatureProperties.() -> T): List> { 68 | val features = ObjectMapper().readValue(this, JacksonFeatureCollection::class.java) 69 | val properties = FeatureProperties() 70 | return features.getFeatures().map { feature -> 71 | properties.properties = feature.getProperties()!! 72 | Pair(feature.toFeature(), extractFunction(properties)) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/FeatureTest.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import org.junit.Test 5 | 6 | import org.junit.Assert.assertEquals 7 | import org.junit.Assert.assertNotNull 8 | 9 | class FeatureTest { 10 | 11 | private val testObject = Feature().apply { 12 | geometry = Point(100.0, 0.0) 13 | } 14 | private val mapper = ObjectMapper() 15 | 16 | @Test 17 | @Throws(Exception::class) 18 | fun itShouldHaveProperties() { 19 | assertNotNull(testObject.getProperties()) 20 | } 21 | 22 | @Test 23 | @Throws(Exception::class) 24 | fun itShouldSerializeFeature() { 25 | // http://geojson.org/geojson-spec.html#feature-objects 26 | // A feature object must have a member with the name "properties". 27 | // The value of the properties member is an object (any JSON object or a JSON null value). 28 | 29 | //language=JSON 30 | assertEquals( 31 | """{"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[100.0,0.0]}}""", 32 | mapper.writeValueAsString(testObject) 33 | ) 34 | } 35 | } -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/GeoJsonObjectVisitorTest.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | import org.junit.runner.RunWith 6 | import org.junit.runners.Parameterized 7 | 8 | import java.util.Arrays 9 | 10 | @RunWith(Parameterized::class) 11 | class GeoJsonObjectVisitorTest(private val geoJsonObject: GeoJsonObject) { 12 | private val instance = object : 13 | GeoJsonObjectVisitor { 14 | 15 | override fun visit(geoJsonObject: GeometryCollection): GeoJsonObject? { 16 | Assert.assertEquals(GeometryCollection::class.java, geoJsonObject.javaClass) 17 | return geoJsonObject 18 | } 19 | 20 | override fun visit(geoJsonObject: FeatureCollection): GeoJsonObject? { 21 | Assert.assertEquals(FeatureCollection::class.java, geoJsonObject.javaClass) 22 | return geoJsonObject 23 | } 24 | 25 | override fun visit(geoJsonObject: Point): GeoJsonObject? { 26 | Assert.assertEquals(Point::class.java, geoJsonObject.javaClass) 27 | return geoJsonObject 28 | } 29 | 30 | override fun visit(geoJsonObject: Feature): GeoJsonObject? { 31 | Assert.assertEquals(Feature::class.java, geoJsonObject.javaClass) 32 | return geoJsonObject 33 | } 34 | 35 | override fun visit(geoJsonObject: MultiLineString): GeoJsonObject? { 36 | Assert.assertEquals(MultiLineString::class.java, geoJsonObject.javaClass) 37 | return geoJsonObject 38 | } 39 | 40 | override fun visit(geoJsonObject: Polygon): GeoJsonObject? { 41 | Assert.assertEquals(Polygon::class.java, geoJsonObject.javaClass) 42 | return geoJsonObject 43 | } 44 | 45 | override fun visit(geoJsonObject: MultiPolygon): GeoJsonObject? { 46 | Assert.assertEquals(MultiPolygon::class.java, geoJsonObject.javaClass) 47 | return geoJsonObject 48 | } 49 | 50 | override fun visit(geoJsonObject: MultiPoint): GeoJsonObject? { 51 | Assert.assertEquals(MultiPoint::class.java, geoJsonObject.javaClass) 52 | return geoJsonObject 53 | } 54 | 55 | override fun visit(geoJsonObject: LineString): GeoJsonObject? { 56 | Assert.assertEquals(LineString::class.java, geoJsonObject.javaClass) 57 | return geoJsonObject 58 | } 59 | } 60 | 61 | @Test 62 | fun should_visit_right_class() { 63 | // When 64 | val result = geoJsonObject.accept(this.instance) 65 | // Then 66 | Assert.assertEquals(geoJsonObject, result) 67 | } 68 | 69 | companion object { 70 | 71 | @JvmStatic 72 | @Parameterized.Parameters 73 | fun data(): Collection> { 74 | return listOf( 75 | arrayOf(GeometryCollection()), 76 | arrayOf(FeatureCollection()), 77 | arrayOf(Point(12.0, 13.0)), 78 | arrayOf(Feature()), 79 | arrayOf( 80 | MultiLineString( 81 | Arrays.asList( 82 | LngLatAlt( 83 | 12.0, 84 | 13.0 85 | ) 86 | ) 87 | ) 88 | ), 89 | arrayOf(Polygon()), 90 | arrayOf(MultiPolygon()), 91 | arrayOf(MultiPoint()), 92 | arrayOf(LineString()) 93 | ) 94 | } 95 | } 96 | 97 | // @Test 98 | // public void itShouldAdapter() throws Exception { 99 | // Assert.assertNull(geoJsonObject.accept(new GeoJsonObjectVisitor.Adapter())); 100 | // } 101 | } 102 | -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/LngLatAltTest.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | 6 | class LngLatAltTest { 7 | 8 | @Test 9 | fun should_LngLatAlt_equals_without_alt() { 10 | val first = LngLatAlt(14.0, 13.0) 11 | val second = LngLatAlt(14.0, 13.0) 12 | Assert.assertEquals(second, first) 13 | } 14 | 15 | @Test 16 | fun should_LngLatAlt_equals_with_alt() { 17 | val first = LngLatAlt(14.0, 13.0, 15.0) 18 | val second = LngLatAlt(14.0, 13.0, 15.0) 19 | Assert.assertEquals(second, first) 20 | } 21 | 22 | @Test 23 | fun should_not_LngLatAlt_equals_with_alt() { 24 | val first = LngLatAlt(14.0, 13.0, 15.0) 25 | val second = LngLatAlt(14.0, 13.0, 16.0) 26 | Assert.assertNotEquals(second, first) 27 | } 28 | 29 | @Test 30 | fun should_not_LngLatAlt_equals_without_alt() { 31 | val first = LngLatAlt(14.0, 14.0, 15.0) 32 | val second = LngLatAlt(14.0, 13.0, 16.0) 33 | Assert.assertNotEquals(second, first) 34 | } 35 | 36 | @Test 37 | fun should_LngLatAlt_equals_with_additional_elements() { 38 | val first = LngLatAlt(14.0, 14.0, 15.0, 16.0, 17.0) 39 | val second = LngLatAlt(14.0, 14.0, 15.0, 16.0, 17.0) 40 | Assert.assertEquals(second, first) 41 | Assert.assertEquals(second.hashCode().toLong(), first.hashCode().toLong()) 42 | } 43 | 44 | @Test 45 | fun should_LngLatAlt_equals_with_additional_elements_and_null() { 46 | val first = LngLatAlt(14.0, 14.0, 15.0, 16.0, 17.0) 47 | val second = LngLatAlt(14.0, 14.0, 15.0, 16.0, 17.0) 48 | Assert.assertEquals(second, first) 49 | Assert.assertEquals(second.hashCode().toLong(), first.hashCode().toLong()) 50 | } 51 | 52 | @Test 53 | fun should_not_LngLatAlt_equals_without_additional_elements() { 54 | val first = LngLatAlt(14.0, 14.0, 15.0, 16.0, 17.0) 55 | val second = LngLatAlt(14.0, 14.0, 15.0) 56 | Assert.assertNotEquals(second, first) 57 | Assert.assertNotEquals(second.hashCode().toLong(), first.hashCode().toLong()) 58 | } 59 | 60 | @Test 61 | fun should_not_LngLatAlt_equals_with_additional_elements_in_different_order() { 62 | val first = LngLatAlt(14.0, 14.0, 15.0, 16.0, 17.0) 63 | val second = LngLatAlt(14.0, 14.0, 15.0, 17.0, 16.0) 64 | Assert.assertNotEquals(second, first) 65 | Assert.assertNotEquals(second.hashCode().toLong(), first.hashCode().toLong()) 66 | } 67 | 68 | @Test 69 | fun should_not_LngLatAlt_equals_with_additional_elements_and_different_size() { 70 | val first = LngLatAlt(14.0, 14.0, 15.0, 16.0, 17.0) 71 | val second = LngLatAlt(14.0, 14.0, 15.0, 16.0, 17.0, 18.0) 72 | Assert.assertNotEquals(second, first) 73 | Assert.assertNotEquals(second.hashCode().toLong(), first.hashCode().toLong()) 74 | } 75 | 76 | @Test 77 | fun should_LngLatAlt_throw_if_alt_not_specified_in_constructor() { 78 | try { 79 | LngLatAlt(14.0, 14.0, Double.NaN, 16.0, 17.0) 80 | Assert.fail("Additional elements are not allowed if altitude is Nan.") 81 | } catch (e: IllegalArgumentException) { 82 | Assert.assertTrue("Expected exception.", true) 83 | } 84 | 85 | } 86 | 87 | @Test 88 | fun should_LngLatAlt_throw_if_alt_set_to_Nan_with_additional_elements() { 89 | val lngLatAlt = LngLatAlt(14.0, 14.0, 15.0, 16.0, 17.0) 90 | 91 | try { 92 | lngLatAlt.setAltitude(java.lang.Double.NaN) 93 | Assert.fail("Additional elements are not allowed if altitude is Nan.") 94 | } catch (e: IllegalArgumentException) { 95 | Assert.assertTrue("Expected exception.", true) 96 | } 97 | 98 | } 99 | 100 | @Test 101 | fun should_LngLatAlt_throw_if_additional_elements_set_with_missing_alt() { 102 | val lngLatAlt = LngLatAlt(14.0, 14.0) 103 | 104 | try { 105 | lngLatAlt.setAdditionalElements(42.0) 106 | Assert.fail("Additional elements are not allowed if altitude is Nan.") 107 | } catch (e: IllegalArgumentException) { 108 | Assert.assertTrue("Expected exception.", true) 109 | } 110 | 111 | } 112 | 113 | @Test 114 | fun should_LngLatAlt_throw_if_additional_elements_set_with_Nan_alt() { 115 | val lngLatAlt = LngLatAlt(14.0, 14.0, Double.NaN) 116 | 117 | try { 118 | lngLatAlt.setAdditionalElements(42.0) 119 | Assert.fail("Additional elements are not allowed if altitude is Nan.") 120 | } catch (e: IllegalArgumentException) { 121 | Assert.assertTrue("Expected exception.", true) 122 | } 123 | 124 | } 125 | 126 | @Test 127 | fun should_LngLatAlt_throw_if_any_additional_elements_constructed_to_Nan() { 128 | try { 129 | LngLatAlt(14.0, 14.0, 15.0, 16.0, Double.NaN, 17.0) 130 | Assert.fail("Additional elements are not allowed to be Nan.") 131 | } catch (e: IllegalArgumentException) { 132 | Assert.assertTrue("Expected exception.", true) 133 | } 134 | 135 | } 136 | 137 | @Test 138 | fun should_LngLatAlt_throw_if_any_additional_elements_constructed_to_Positive_Infinity() { 139 | try { 140 | LngLatAlt(14.0, 14.0, 15.0, 16.0, Double.POSITIVE_INFINITY, 17.0) 141 | Assert.fail("Additional elements are not allowed to be positive infinity.") 142 | } catch (e: IllegalArgumentException) { 143 | Assert.assertTrue("Expected exception.", true) 144 | } 145 | 146 | } 147 | 148 | @Test 149 | fun should_LngLatAlt_throw_if_any_additional_elements_constructed_to_Negative_Infinity() { 150 | try { 151 | LngLatAlt(14.0, 14.0, 15.0, 16.0, Double.NEGATIVE_INFINITY, 17.0) 152 | Assert.fail("Additional elements are not allowed to be positive infinity.") 153 | } catch (e: IllegalArgumentException) { 154 | Assert.assertTrue("Expected exception.", true) 155 | } 156 | 157 | } 158 | 159 | @Test 160 | fun should_LngLatAlt_throw_if_any_additional_elements_set_to_Nan() { 161 | val lngLatAlt = LngLatAlt(14.0, 14.0, 15.0) 162 | try { 163 | lngLatAlt.setAdditionalElements(16.0, java.lang.Double.NaN, 17.0) 164 | Assert.fail("Additional elements are not allowed to be Nan.") 165 | } catch (e: IllegalArgumentException) { 166 | Assert.assertTrue("Expected exception.", true) 167 | } 168 | 169 | } 170 | 171 | @Test 172 | fun should_LngLatAlt_throw_if_any_additional_elements_set_to_Positive_Infinity() { 173 | val lngLatAlt = LngLatAlt(14.0, 14.0, 15.0) 174 | try { 175 | lngLatAlt.setAdditionalElements(16.0, java.lang.Double.POSITIVE_INFINITY, 17.0) 176 | Assert.fail("Additional elements are not allowed to be positive infinity.") 177 | } catch (e: IllegalArgumentException) { 178 | Assert.assertTrue("Expected exception.", true) 179 | } 180 | 181 | } 182 | 183 | @Test 184 | fun should_LngLatAlt_throw_if_any_additional_elements_set_to_Negative_Infinity() { 185 | val lngLatAlt = LngLatAlt(14.0, 14.0, 15.0) 186 | try { 187 | lngLatAlt.setAdditionalElements(16.0, java.lang.Double.NEGATIVE_INFINITY, 17.0) 188 | Assert.fail("Additional elements are not allowed to be positive infinity.") 189 | } catch (e: IllegalArgumentException) { 190 | Assert.assertTrue("Expected exception.", true) 191 | } 192 | 193 | } 194 | } -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/LoadingTest.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import com.fasterxml.jackson.module.kotlin.readValue 5 | import io.data2viz.geojson.JacksonGeoJsonObject 6 | import io.data2viz.geojson.toGeoJsonObject 7 | import org.junit.Test 8 | 9 | import org.junit.Assert.assertEquals 10 | import org.junit.Assert.assertNotNull 11 | 12 | class LoadingTest { 13 | 14 | 15 | @Test 16 | @Throws(Exception::class) 17 | fun loadNyGeoJson() { 18 | val input = this.javaClass.getResourceAsStream("/ny.json") 19 | val time = System.currentTimeMillis() 20 | val geojson = ObjectMapper().readValue(input, JacksonGeoJsonObject::class.java) 21 | val geoJsonObject = geojson.toGeoJsonObject() 22 | println("loading in ${System.currentTimeMillis() -time} ms.") 23 | 24 | } 25 | } -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/ToStringTest.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson 2 | 3 | import org.junit.Test 4 | 5 | import java.util.Arrays 6 | 7 | import org.junit.Assert.assertEquals 8 | 9 | class ToStringTest { 10 | 11 | @Test 12 | @Throws(Exception::class) 13 | fun itShouldToStringCrs() { 14 | assertEquals("Crs{type='NAME', properties={}}", Crs().toString()) 15 | } 16 | 17 | @Test 18 | @Throws(Exception::class) 19 | fun itShouldToStringFeature() { 20 | assertEquals("Feature{properties={}, geometry=null, id='null'}", Feature().toString()) 21 | } 22 | 23 | @Test 24 | @Throws(Exception::class) 25 | fun itShouldToStringFeatureCollection() { 26 | assertEquals("FeatureCollection{features=[]}", FeatureCollection().toString()) 27 | } 28 | 29 | @Test 30 | @Throws(Exception::class) 31 | fun itShouldToStringPoint() { 32 | val geometry = Point(10.0, 20.0) 33 | assertEquals( 34 | "Point{coordinates=LngLatAlt{longitude=10.0, latitude=20.0, altitude=NaN}} GeoJsonObject{}", 35 | geometry.toString() 36 | ) 37 | } 38 | 39 | @Test 40 | fun itShouldToStringPointWithAdditionalElements() { 41 | val geometry = Point(10.0, 20.0, 30.0, 40.0, 50.0) 42 | assertEquals( 43 | "Point{coordinates=LngLatAlt{longitude=10.0, latitude=20.0, altitude=30.0, additionalElements=[40.0, 50.0]}} GeoJsonObject{}", 44 | geometry.toString() 45 | ) 46 | } 47 | 48 | @Test 49 | fun itShouldToStringPointWithAdditionalElementsAndIgnoreNulls() { 50 | val geometry = Point(10.0, 20.0, 30.0, 40.0, 50.0) 51 | assertEquals( 52 | "Point{coordinates=LngLatAlt{longitude=10.0, latitude=20.0, altitude=30.0, additionalElements=[40.0, 50.0]}} GeoJsonObject{}", 53 | geometry.toString() 54 | ) 55 | } 56 | 57 | @Test 58 | @Throws(Exception::class) 59 | fun itShouldToStringPolygon() { 60 | val geometry = Polygon( 61 | LngLatAlt(10.0, 20.0), 62 | LngLatAlt(30.0, 40.0), 63 | LngLatAlt(10.0, 40.0), 64 | LngLatAlt(10.0, 20.0) 65 | ) 66 | assertEquals( 67 | "Polygon{} Geometry{coordinates=[[LngLatAlt{longitude=10.0, latitude=20.0, altitude=NaN}, " 68 | + "LngLatAlt{longitude=30.0, latitude=40.0, altitude=NaN}, LngLatAlt{longitude=10.0, latitude=40.0, altitude=NaN}, " 69 | + "LngLatAlt{longitude=10.0, latitude=20.0, altitude=NaN}]]} GeoJsonObject{}", 70 | geometry.toString() 71 | ) 72 | } 73 | 74 | @Test 75 | @Throws(Exception::class) 76 | fun itShouldToStringMultiPolygon() { 77 | val geometry = MultiPolygon( 78 | Polygon( 79 | LngLatAlt(10.0, 20.0), LngLatAlt(30.0, 40.0), 80 | LngLatAlt(10.0, 40.0), LngLatAlt(10.0, 20.0) 81 | ) 82 | ) 83 | geometry.add( 84 | Polygon( 85 | LngLatAlt(5.0, 20.0), 86 | LngLatAlt(30.0, 40.0), 87 | LngLatAlt(10.0, 40.0), 88 | LngLatAlt( 89 | 5.0, 90 | 20.0 91 | ) 92 | ) 93 | ) 94 | assertEquals( 95 | "MultiPolygon{} Geometry{coordinates=[[[LngLatAlt{longitude=10.0, latitude=20.0, altitude=NaN}, " 96 | + "LngLatAlt{longitude=30.0, latitude=40.0, altitude=NaN}, " 97 | + "LngLatAlt{longitude=10.0, latitude=40.0, altitude=NaN}, " 98 | + "LngLatAlt{longitude=10.0, latitude=20.0, altitude=NaN}]], " 99 | + "[[LngLatAlt{longitude=5.0, latitude=20.0, altitude=NaN}, " 100 | + "LngLatAlt{longitude=30.0, latitude=40.0, altitude=NaN}, " 101 | + "LngLatAlt{longitude=10.0, latitude=40.0, altitude=NaN}, " 102 | + "LngLatAlt{longitude=5.0, latitude=20.0, altitude=NaN}]]]} GeoJsonObject{}", 103 | geometry.toString() 104 | ) 105 | } 106 | 107 | @Test 108 | @Throws(Exception::class) 109 | fun itShouldToStringLineString() { 110 | val geometry = LineString( 111 | LngLatAlt(49.0, 9.0), 112 | LngLatAlt(41.0, 1.0) 113 | ) 114 | assertEquals( 115 | "LineString{} MultiPoint{} Geometry{coordinates=[" 116 | + "LngLatAlt{longitude=49.0, latitude=9.0, altitude=NaN}, " 117 | + "LngLatAlt{longitude=41.0, latitude=1.0, altitude=NaN}]} GeoJsonObject{}", 118 | geometry.toString() 119 | ) 120 | } 121 | 122 | @Test 123 | @Throws(Exception::class) 124 | fun itShouldToStringMultiLineString() { 125 | val geometry = MultiLineString( 126 | Arrays.asList( 127 | LngLatAlt(49.0, 9.0), 128 | LngLatAlt(41.0, 1.0) 129 | ) 130 | ) 131 | geometry.add(Arrays.asList( 132 | LngLatAlt(10.0, 20.0), 133 | LngLatAlt(30.0, 40.0) 134 | )) 135 | assertEquals( 136 | "MultiLineString{} Geometry{coordinates=[[LngLatAlt{longitude=49.0, latitude=9.0, altitude=NaN}, " 137 | + "LngLatAlt{longitude=41.0, latitude=1.0, altitude=NaN}], " 138 | + "[LngLatAlt{longitude=10.0, latitude=20.0, altitude=NaN}, " 139 | + "LngLatAlt{longitude=30.0, latitude=40.0, altitude=NaN}]]} GeoJsonObject{}", 140 | geometry.toString() 141 | ) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/jackson/CrsTest.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson.jackson 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import io.data2viz.geojson.jackson.Crs 5 | import io.data2viz.geojson.jackson.GeoJsonObject 6 | import io.data2viz.geojson.jackson.Point 7 | import org.intellij.lang.annotations.Language 8 | import org.junit.Test 9 | 10 | import org.junit.Assert.assertEquals 11 | import org.junit.Assert.assertNotNull 12 | 13 | class CrsTest { 14 | 15 | private val mapper = ObjectMapper() 16 | 17 | @Test 18 | @Throws(Exception::class) 19 | fun itShouldParseCrsWithLink() { 20 | 21 | @Language("JSON") 22 | val value = mapper.readValue( 23 | """{"crs": { 24 | "type": "link", 25 | "properties": { 26 | "href": "http://example.com/crs/42", 27 | "type": "proj4" 28 | } 29 | },"type":"Point", 30 | "coordinates":[100.0,5.0] 31 | }""".trimIndent(), 32 | GeoJsonObject::class.java 33 | ) 34 | assertNotNull(value) 35 | assertEquals(CrsType.LINK, value.crs!!.type) 36 | } 37 | 38 | @Test 39 | @Throws(Exception::class) 40 | fun itShouldSerializeCrsWithLink() { 41 | val point = Point(100.0, 1.0) 42 | val crs = Crs() 43 | crs.type = CrsType.LINK 44 | point.crs = crs 45 | val value = mapper.writeValueAsString(point) 46 | //language=JSON 47 | assertEquals("""{"type":"Point","crs":{"type":"link","properties":{}},"coordinates":[100.0,1.0]}""", value) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/jackson/GeoJsonObjectTest.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson.jackson 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import io.data2viz.geojson.jackson.GeoJsonObject 5 | import io.data2viz.geojson.jackson.GeoJsonObjectVisitor 6 | 7 | class GeoJsonObjectTest { 8 | 9 | private val mapper = ObjectMapper() 10 | 11 | 12 | private inner class TestGeoJsonObject : GeoJsonObject() { 13 | 14 | override fun accept(geoJsonObjectVisitor: GeoJsonObjectVisitor): T { 15 | throw RuntimeException("not implemented") 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/jackson/GeometryCollectionTest.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson.jackson 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import io.data2viz.geojson.jackson.* 5 | import org.junit.Test 6 | 7 | import org.junit.Assert.* 8 | 9 | class GeometryCollectionTest { 10 | 11 | private val mapper = ObjectMapper() 12 | 13 | @Test 14 | @Throws(Exception::class) 15 | fun itShouldSerialize() { 16 | val gc = GeometryCollection() 17 | gc.add(Point(100.0, 0.0)) 18 | gc.add( 19 | LineString( 20 | LngLatAlt(101.0, 0.0), 21 | LngLatAlt(102.0, 1.0) 22 | ) 23 | ) 24 | assertEquals( 25 | "{\"type\":\"GeometryCollection\"," 26 | + "\"geometries\":[{\"type\":\"Point\",\"coordinates\":[100.0,0.0]}," 27 | + "{\"type\":\"LineString\",\"coordinates\":[[101.0,0.0],[102.0,1.0]]}]}", 28 | mapper.writeValueAsString(gc) 29 | ) 30 | } 31 | 32 | @Test 33 | @Throws(Exception::class) 34 | fun itShouldDeserialize() { 35 | val geometryCollection = mapper 36 | .readValue( 37 | "{\"type\":\"GeometryCollection\"," 38 | + "\"geometries\":[{\"type\":\"Point\",\"coordinates\":[100.0,0.0]}," 39 | + "{\"type\":\"LineString\",\"coordinates\":[[101.0,0.0],[102.0,1.0]]}]}", 40 | GeometryCollection::class.java 41 | ) 42 | assertNotNull(geometryCollection) 43 | } 44 | 45 | @Test 46 | @Throws(Exception::class) 47 | fun itShouldDeserializeSubtype() { 48 | val collection = mapper 49 | .readValue( 50 | "{\"type\": \"FeatureCollection\"," 51 | + " \"features\": [" 52 | + " {" 53 | + " \"type\": \"Feature\"," 54 | + " \"geometry\": {" 55 | + " \"type\": \"GeometryCollection\"," 56 | + " \"geometries\": [" 57 | + " {" 58 | + " \"type\": \"Point\"," 59 | + " \"coordinates\": [100.0, 0.0]" 60 | + " }" 61 | + " ]" 62 | + " }" 63 | + " }" 64 | + " ]" 65 | + "}", 66 | FeatureCollection::class.java 67 | ) 68 | assertNotNull(collection) 69 | assertTrue(collection.getFeatures()[0].geometry is GeometryCollection) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/jackson/LineStringTest.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson.jackson 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import io.data2viz.geojson.jackson.LineString 5 | import io.data2viz.geojson.jackson.LngLatAlt 6 | import org.intellij.lang.annotations.Language 7 | import org.junit.Test 8 | 9 | import org.junit.Assert.assertEquals 10 | import org.junit.Assert.assertNotNull 11 | 12 | class LineStringTest { 13 | 14 | private val mapper = ObjectMapper() 15 | 16 | @Test 17 | @Throws(Exception::class) 18 | fun itShouldSerializeMultiPoint() { 19 | val lineString = LineString( 20 | LngLatAlt(100.0, 0.0), 21 | LngLatAlt(101.0, 1.0) 22 | ) 23 | //language=JSON 24 | assertEquals( 25 | """{"type":"LineString","coordinates":[[100.0,0.0],[101.0,1.0]]}""", 26 | mapper.writeValueAsString(lineString) 27 | ) 28 | } 29 | 30 | @Test 31 | @Throws(Exception::class) 32 | fun itShouldDeserializeLineString() { 33 | @Language("JSON") 34 | val lineString = mapper.readValue( 35 | """{"type":"LineString","coordinates":[[100.0,0.0],[101.0,1.0]]}""", 36 | LineString::class.java 37 | ) 38 | assertNotNull(lineString) 39 | val coordinates = lineString.coordinates 40 | PointTest.assertLngLatAlt(100.0, 0.0, java.lang.Double.NaN, coordinates[0]) 41 | PointTest.assertLngLatAlt(101.0, 1.0, java.lang.Double.NaN, coordinates[1]) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/jackson/LngLatAltDeserializerTest.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson.jackson 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import io.data2viz.geojson.jackson.LngLatAlt 5 | import org.junit.Assert 6 | import org.junit.Test 7 | 8 | /** 9 | * Created by babbleshack on 27/11/16. 10 | */ 11 | class LngLatAltDeserializerTest { 12 | @Test 13 | @Throws(Exception::class) 14 | fun deserializeMongoLngLatAlt() { 15 | val lngLatAlt = LngLatAlt(10.0, 15.0, 5.0) 16 | val lngLatAltJson = ObjectMapper().writeValueAsString(lngLatAlt) 17 | lngLatAltJson.replace("10.0", "\"10.0\"") 18 | lngLatAltJson.replace("15.0", "\"15.0\"") 19 | val lngLatAlt1 = ObjectMapper().readValue(lngLatAltJson, LngLatAlt::class.java) 20 | Assert.assertTrue(lngLatAlt == lngLatAlt) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/jackson/LngLatAltSerializerTest.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson.jackson 2 | 3 | import io.data2viz.geojson.jackson.LngLatAlt 4 | import org.junit.Assert 5 | import org.junit.Test 6 | 7 | import com.fasterxml.jackson.databind.ObjectMapper 8 | 9 | class LngLatAltSerializerTest { 10 | 11 | @Test 12 | @Throws(Exception::class) 13 | fun testSerialization() { 14 | val position = LngLatAlt(49.43245, 52.42345, 120.34626) 15 | val correctJson = "[49.43245,52.42345,120.34626]" 16 | val producedJson = ObjectMapper().writeValueAsString(position) 17 | Assert.assertEquals(correctJson, producedJson) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/jackson/MockData.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson.jackson 2 | 3 | import java.util.Arrays 4 | 5 | import io.data2viz.geojson.jackson.LngLatAlt 6 | 7 | object MockData { 8 | 9 | val EXTERNAL = Arrays.asList( 10 | LngLatAlt(100.0, 0.0), 11 | LngLatAlt(101.0, 0.0), 12 | LngLatAlt(101.0, 1.0), 13 | LngLatAlt(100.0, 1.0), 14 | LngLatAlt(100.0, 0.0) 15 | ) 16 | val INTERNAL = Arrays.asList( 17 | LngLatAlt(100.2, 0.2), 18 | LngLatAlt(100.8, 0.2), 19 | LngLatAlt(100.8, 0.8), 20 | LngLatAlt(100.2, 0.8), 21 | LngLatAlt(100.2, 0.2) 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/jackson/MultiLineStringTest.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson.jackson 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import io.data2viz.geojson.jackson.LngLatAlt 5 | import io.data2viz.geojson.jackson.MultiLineString 6 | import org.junit.Test 7 | 8 | import java.util.Arrays 9 | 10 | import org.junit.Assert.assertEquals 11 | 12 | class MultiLineStringTest { 13 | 14 | private val mapper = ObjectMapper() 15 | 16 | @Test 17 | @Throws(Exception::class) 18 | fun itShouldSerialize() { 19 | val multiLineString = MultiLineString() 20 | multiLineString.add(Arrays.asList( 21 | LngLatAlt(100.0, 0.0), 22 | LngLatAlt(101.0, 1.0) 23 | )) 24 | multiLineString.add(Arrays.asList( 25 | LngLatAlt(102.0, 2.0), 26 | LngLatAlt(103.0, 3.0) 27 | )) 28 | //language=JSON 29 | assertEquals( 30 | """{"type":"MultiLineString","coordinates":[[[100.0,0.0],[101.0,1.0]],[[102.0,2.0],[103.0,3.0]]]}""", 31 | mapper.writeValueAsString(multiLineString) 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/jackson/MultiPointTest.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson.jackson 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import io.data2viz.geojson.jackson.LngLatAlt 5 | import io.data2viz.geojson.jackson.MultiPoint 6 | import org.intellij.lang.annotations.Language 7 | import org.junit.Test 8 | 9 | import org.junit.Assert.assertEquals 10 | import org.junit.Assert.assertNotNull 11 | 12 | class MultiPointTest { 13 | 14 | private val mapper = ObjectMapper() 15 | 16 | @Test 17 | @Throws(Exception::class) 18 | fun itShouldSerializeMultiPoint() { 19 | val multiPoint = MultiPoint( 20 | LngLatAlt(100.0, 0.0), 21 | LngLatAlt(101.0, 1.0) 22 | ) 23 | //language=JSON 24 | assertEquals( 25 | """{"type":"MultiPoint","coordinates":[[100.0,0.0],[101.0,1.0]]}""", 26 | mapper.writeValueAsString(multiPoint) 27 | ) 28 | } 29 | 30 | @Test 31 | @Throws(Exception::class) 32 | fun itShouldDeserializeMultiPoint() { 33 | @Language("JSON") 34 | val multiPoint = mapper 35 | .readValue( 36 | """{"type":"MultiPoint","coordinates":[[100.0,0.0],[101.0,1.0]]}""", 37 | MultiPoint::class.java 38 | ) 39 | assertNotNull(multiPoint) 40 | val coordinates = multiPoint.coordinates 41 | PointTest.assertLngLatAlt(100.0, 0.0, java.lang.Double.NaN, coordinates[0]) 42 | PointTest.assertLngLatAlt(101.0, 1.0, java.lang.Double.NaN, coordinates[1]) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/jackson/MultiPoligonTest.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson.jackson 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import io.data2viz.geojson.jackson.LngLatAlt 5 | import io.data2viz.geojson.jackson.MultiPolygon 6 | import io.data2viz.geojson.jackson.Polygon 7 | import org.intellij.lang.annotations.Language 8 | import org.junit.Test 9 | 10 | import org.junit.Assert.assertEquals 11 | 12 | class MultiPoligonTest { 13 | 14 | private val mapper = ObjectMapper() 15 | 16 | @Test 17 | @Throws(Exception::class) 18 | fun itShouldSerialize() { 19 | val multiPolygon = MultiPolygon() 20 | multiPolygon.add( 21 | Polygon( 22 | LngLatAlt(102.0, 2.0), 23 | LngLatAlt(103.0, 2.0), 24 | LngLatAlt(103.0, 3.0), 25 | LngLatAlt(102.0, 3.0), 26 | LngLatAlt(102.0, 2.0) 27 | ) 28 | ) 29 | val polygon = Polygon(MockData.EXTERNAL) 30 | polygon.addInteriorRing(MockData.INTERNAL) 31 | multiPolygon.add(polygon) 32 | //language=JSON 33 | assertEquals( 34 | """{"type":"MultiPolygon","coordinates":[[[[102.0,2.0],[103.0,2.0],[103.0,3.0],[102.0,3.0],[102.0,2.0]]],[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]]}""", 35 | mapper.writeValueAsString(multiPolygon) 36 | ) 37 | } 38 | 39 | @Test 40 | @Throws(Exception::class) 41 | fun itShouldDeserialize() { 42 | @Language("JSON") 43 | val multiPolygon = mapper.readValue( 44 | """{"type":"MultiPolygon", 45 | "coordinates":[ 46 | [[[102.0,2.0],[103.0,2.0],[103.0,3.0],[102.0,3.0],[102.0,2.0]]], 47 | [[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]] 48 | ] 49 | }""".trimIndent(), MultiPolygon::class.java 50 | ) 51 | assertEquals(2, multiPolygon.coordinates.size.toLong()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/jackson/PointTest.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson.jackson 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException 4 | import com.fasterxml.jackson.databind.ObjectMapper 5 | import io.data2viz.geojson.jackson.GeoJsonObject 6 | import io.data2viz.geojson.jackson.LngLatAlt 7 | import io.data2viz.geojson.jackson.Point 8 | import org.intellij.lang.annotations.Language 9 | import org.junit.Test 10 | 11 | import java.io.IOException 12 | import java.util.Arrays 13 | 14 | import org.junit.Assert.* 15 | 16 | class PointTest { 17 | 18 | private val mapper = ObjectMapper() 19 | 20 | @Test 21 | @Throws(Exception::class) 22 | fun itShouldSerializeAPoint() { 23 | val point = Point(100.0, 0.0) 24 | //language=JSON 25 | assertEquals( 26 | """{"type":"Point","coordinates":[100.0,0.0]}""", 27 | mapper.writeValueAsString(point) 28 | ) 29 | } 30 | 31 | @Test 32 | @Throws(Exception::class) 33 | fun itShouldDeserializeAPoint() { 34 | val value = mapper 35 | .readValue("{\"type\":\"Point\",\"coordinates\":[100.0,5.0]}", GeoJsonObject::class.java) 36 | assertNotNull(value) 37 | assertTrue(value is Point) 38 | val point = value as Point 39 | assertLngLatAlt(100.0, 5.0, java.lang.Double.NaN, point.coordinates) 40 | } 41 | 42 | @Test 43 | @Throws(Exception::class) 44 | fun itShouldDeserializeAPointWithAltitude() { 45 | @Language("JSON") 46 | val value = mapper.readValue( 47 | """{"type":"Point","coordinates":[100.0,5.0,123]}""", 48 | GeoJsonObject::class.java 49 | ) 50 | val point = value as Point 51 | assertLngLatAlt(100.0, 5.0, 123.0, point.coordinates) 52 | } 53 | 54 | @Test 55 | @Throws(Exception::class) 56 | fun itShouldSerializeAPointWithAltitude() { 57 | val point = Point(100.0, 0.0, 256.0) 58 | //language=JSON 59 | assertEquals( 60 | """{"type":"Point","coordinates":[100.0,0.0,256.0]}""", 61 | mapper.writeValueAsString(point) 62 | ) 63 | } 64 | 65 | @Test 66 | @Throws(IOException::class) 67 | fun itShouldDeserializeAPointWithAdditionalAttributes() { 68 | @Language("JSON") 69 | val value = mapper.readValue( 70 | """{"type":"Point","coordinates":[100.0,5.0,123,456,789.2]}""", 71 | GeoJsonObject::class.java 72 | ) 73 | val point = value as Point 74 | assertLngLatAlt(100.0, 5.0, 123.0, doubleArrayOf(456.0, 789.2), point.coordinates) 75 | } 76 | 77 | @Test 78 | @Throws(JsonProcessingException::class) 79 | fun itShouldSerializeAPointWithAdditionalAttributes() { 80 | val point = Point(100.0, 0.0, 256.0, 345.0, 678.0) 81 | //language=JSON 82 | assertEquals( 83 | """{"type":"Point","coordinates":[100.0,0.0,256.0,345.0,678.0]}""", 84 | mapper.writeValueAsString(point) 85 | ) 86 | } 87 | 88 | @Test 89 | @Throws(JsonProcessingException::class) 90 | fun itShouldSerializeAPointWithAdditionalAttributesAndNull() { 91 | val point = Point(100.0, 0.0, 256.0, 345.0, 678.0) 92 | //language=JSON 93 | assertEquals( 94 | """{"type":"Point","coordinates":[100.0,0.0,256.0,345.0,678.0]}""", 95 | mapper.writeValueAsString(point) 96 | ) 97 | } 98 | 99 | companion object { 100 | 101 | fun assertLngLatAlt( 102 | expectedLongitude: Double, expectedLatitude: Double, expectedAltitude: Double, 103 | point: LngLatAlt? 104 | ) { 105 | assertLngLatAlt(expectedLongitude, expectedLatitude, expectedAltitude, DoubleArray(0), point!!) 106 | } 107 | 108 | fun assertLngLatAlt( 109 | expectedLongitude: Double, expectedLatitude: Double, expectedAltitude: Double, 110 | expectedAdditionalElements: DoubleArray, point: LngLatAlt 111 | ) { 112 | assertEquals(expectedLongitude, point.longitude, 0.00001) 113 | assertEquals(expectedLatitude, point.latitude, 0.00001) 114 | if (java.lang.Double.isNaN(expectedAltitude)) { 115 | assertFalse(point.hasAltitude()) 116 | } else { 117 | assertEquals(expectedAltitude, point.getAltitude(), 0.00001) 118 | assertTrue(Arrays.equals(expectedAdditionalElements, point.getAdditionalElements())) 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /core/src/jvmTest/kotlin/io/data2viz/geojson/jackson/jackson/PolygonTest.kt: -------------------------------------------------------------------------------- 1 | package io.data2viz.geojson.jackson.jackson 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import io.data2viz.geojson.jackson.LngLatAlt 5 | import io.data2viz.geojson.jackson.Polygon 6 | import org.intellij.lang.annotations.Language 7 | import org.junit.Test 8 | 9 | import org.junit.Assert.assertEquals 10 | 11 | class PolygonTest { 12 | 13 | private val mapper = ObjectMapper() 14 | 15 | @Test 16 | @Throws(Exception::class) 17 | fun itShouldSerialize() { 18 | val polygon = Polygon(MockData.EXTERNAL) 19 | //language=JSON 20 | assertEquals( 21 | """{"type":"Polygon","coordinates":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]]]}""", 22 | mapper.writeValueAsString(polygon) 23 | ) 24 | } 25 | 26 | @Test 27 | @Throws(Exception::class) 28 | fun itShouldSerializeWithHole() { 29 | val polygon = Polygon(MockData.EXTERNAL) 30 | polygon.addInteriorRing(MockData.INTERNAL) 31 | //language=JSON 32 | assertEquals( 33 | """{"type":"Polygon","coordinates":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}""", 34 | mapper.writeValueAsString(polygon) 35 | ) 36 | } 37 | 38 | @Test(expected = RuntimeException::class) 39 | @Throws(Exception::class) 40 | fun itShouldFailOnAddInteriorRingWithoutExteriorRing() { 41 | val polygon = Polygon() 42 | polygon.addInteriorRing(MockData.EXTERNAL) 43 | } 44 | 45 | @Test 46 | @Throws(Exception::class) 47 | fun itShouldDeserialize() { 48 | @Language("JSON") 49 | val polygon = mapper.readValue( 50 | """{"type":"Polygon","coordinates":[[[100.0,0.0],[101.0,0.0],[101.0,1.0],[100.0,1.0],[100.0,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}""", Polygon::class.java 51 | ) 52 | assertListEquals(MockData.EXTERNAL, polygon.exteriorRing) 53 | assertListEquals(MockData.INTERNAL, polygon.getInteriorRing(0)) 54 | assertListEquals(MockData.INTERNAL, polygon.interiorRings[0]) 55 | } 56 | 57 | private fun assertListEquals(expectedList: List, actualList: List) { 58 | for (x in actualList.indices) { 59 | val expected = expectedList[x] 60 | val actual = actualList[x] 61 | PointTest.assertLngLatAlt(expected.longitude, expected.latitude, expected.getAltitude(), actual) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group=io.data2viz.geojson 2 | 3 | include_ios=true 4 | 5 | 6 | kotlin.native.ignoreDisabledTargets=true 7 | kotlin.mpp.stability.nowarn=true 8 | kotlin.js.compiler=both 9 | 10 | coroutines_version = 1.6.4 11 | 12 | junit_version = 4.13 13 | -------------------------------------------------------------------------------- /gradle/mpp-common.gradle: -------------------------------------------------------------------------------- 1 | sourceCompatibility = 1.8 2 | 3 | kotlin { 4 | 5 | sourceSets { 6 | commonTest { 7 | dependencies { 8 | implementation kotlin('test') 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /gradle/mpp-ios.gradle: -------------------------------------------------------------------------------- 1 | 2 | 3 | if (include_ios.toBoolean()) { 4 | 5 | apply plugin: 'org.jetbrains.kotlin.multiplatform' 6 | 7 | kotlin { 8 | 9 | ios { 10 | binaries { 11 | // framework { 12 | // baseName = "d2v-$project.name" 13 | // } 14 | } 15 | } 16 | 17 | iosSimulatorArm64() 18 | 19 | sourceSets { 20 | 21 | iosSimulatorArm64Main { 22 | dependsOn(iosMain) 23 | } 24 | 25 | iosSimulatorArm64Test { 26 | dependsOn(iosTest) 27 | } 28 | iosTest { 29 | dependencies { 30 | } 31 | } 32 | } 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /gradle/mpp-js.gradle: -------------------------------------------------------------------------------- 1 | 2 | kotlin { 3 | js { 4 | moduleName = "geojson-kotlin-$project.name" 5 | browser {} 6 | } 7 | 8 | sourceSets { 9 | jsMain { 10 | dependencies { 11 | implementation kotlin('stdlib-js') 12 | } 13 | } 14 | jsTest { 15 | dependencies { 16 | if (project.name != "tests") { 17 | implementation kotlin('test-js') 18 | } 19 | } 20 | } 21 | } 22 | } 23 | 24 | def npmDeployDir = file("$buildDir/npm") 25 | 26 | 27 | task preparePublishNpm(type: Copy, dependsOn: compileKotlinJsLegacy) { 28 | npmDeployDir.mkdirs() 29 | from(kotlin.targets.jsLegacy.compilations.main.output.allOutputs) 30 | from("../build/js/packages/geojson-kotlin-$project.name/package.json") 31 | into npmDeployDir 32 | 33 | doLast { 34 | def jsonFile = file("$buildDir/npm/package.json") 35 | def parsedJson = new groovy.json.JsonSlurper().parseText(jsonFile.text) 36 | def newFile = new File( npmDeployDir, "package.json") 37 | newFile.write(generatePackageJson( 38 | version, 39 | project.name, 40 | parsedJson.dependencies)) 41 | } 42 | 43 | } 44 | 45 | task publishNpm(type: Exec, dependsOn: [preparePublishNpm]) { 46 | if (!project.hasProperty("npmToken")) { 47 | logger.warn 'warn: to publish on npm the build needs a npmToken property.' 48 | return 49 | } 50 | 51 | workingDir = npmDeployDir 52 | // commandLine 'npm', 'publish', '--dry-run', "--//registry.npmjs.org/:_authToken=$npmToken" //test 53 | commandLine 'npm', 'publish', '--access=public', "--//registry.npmjs.org/:_authToken=$npmToken" 54 | } 55 | 56 | static def generatePackageJson(String version, String projectName, Map dependencies) { 57 | return """ 58 | { 59 | "name": "@data2viz/geojson-kotlin", 60 | "version" : "$version", 61 | "description" : "Geojson Kotlin module", 62 | "author": "Data2viz", 63 | "main": "geojson-kotlin.js", 64 | "license": "Apache-2.0", 65 | "homepage": "https://data2viz.io", 66 | "bugs": {"url": "https://github.com/data2viz/data2viz/issues"}, 67 | "repository": { 68 | "type": "git", 69 | "url": "git+https://github.com/data2viz/data2viz.git" 70 | }, 71 | "keywords": [ 72 | "Kotlin", 73 | "JavaScript", 74 | "data2viz" 75 | ] 76 | } 77 | """.toString() 78 | } 79 | -------------------------------------------------------------------------------- /gradle/mpp-jvm.gradle: -------------------------------------------------------------------------------- 1 | 2 | 3 | kotlin { 4 | jvm() 5 | sourceSets { 6 | jvmTest { 7 | dependencies { 8 | // if (project.name != "tests") { 9 | // implementation project(":tests") 10 | // } 11 | implementation kotlin('test') 12 | implementation kotlin('test-junit') 13 | } 14 | } 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /gradle/publish.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'base' 2 | apply plugin: 'maven-publish' 3 | apply plugin: 'signing' 4 | 5 | tasks.register('javadocJar', Jar) { 6 | archiveClassifier.set('javadoc') 7 | } 8 | 9 | static String propertyOrEnvOrNull(Project project, String key) { 10 | final String property = project.findProperty(key) 11 | if (property == null) return System.getenv(key) 12 | return property 13 | } 14 | 15 | afterEvaluate { 16 | publishing { 17 | 18 | String sonatypeUsername = propertyOrEnvOrNull(project, "sonatype_username") 19 | String sonatypePassword = propertyOrEnvOrNull(project, "sonatype_password") 20 | String repositoryId = System.getenv("sonatype_staging_repo_id") 21 | 22 | repositories { 23 | maven { 24 | name = "MavenCentralStaging" 25 | if (repositoryId == null) { 26 | url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/") 27 | } else { 28 | url = uri("https://oss.sonatype.org/service/local/staging/deployByRepositoryId/$repositoryId/") 29 | } 30 | credentials { 31 | username = sonatypeUsername 32 | password = sonatypePassword 33 | } 34 | } 35 | maven { 36 | name = "SonatypeSnapshots" 37 | url = uri("https://oss.sonatype.org/content/repositories/snapshots/") 38 | credentials { 39 | username = sonatypeUsername 40 | password = sonatypePassword 41 | } 42 | } 43 | } 44 | 45 | if (project.hasProperty("d2v_service_app_id")) { 46 | repositories { 47 | maven { 48 | name = "data2viz-public" 49 | url = uri("https://maven.pkg.jetbrains.space/data2viz/p/maven/public") 50 | credentials { 51 | username = d2v_service_app_id 52 | password = d2v_service_app_secret 53 | } 54 | } 55 | } 56 | repositories { 57 | maven { 58 | name = "data2viz-dev" 59 | url = uri("https://maven.pkg.jetbrains.space/data2viz/p/maven/dev") 60 | credentials { 61 | username = d2v_service_app_id 62 | password = d2v_service_app_secret 63 | } 64 | } 65 | } 66 | } 67 | 68 | publications { 69 | all { publication -> 70 | pom.withXml(configureMavenCentralMetadata) 71 | artifact(javadocJar) 72 | 73 | final String type = publication.name 74 | switch (type) { 75 | case 'kotlinMultiplatform': 76 | publication.artifactId = "$project.name" 77 | break 78 | case 'metadata': 79 | publication.artifactId = "$project.name-common" 80 | break 81 | case 'androidRelease': 82 | publication.artifactId = "$project.name-android" 83 | break 84 | case 'androidDebug': 85 | publication.artifactId = "$project.name-android-debug" 86 | break 87 | default: 88 | String typeLowercase = type.toLowerCase(Locale.ROOT) 89 | publication.artifactId = "$project.name-$typeLowercase" 90 | break 91 | } 92 | } 93 | } 94 | 95 | signing { 96 | String keyId = propertyOrEnvOrNull(project, "GPG_key_id") 97 | String privateKey = propertyOrEnvOrNull(project, "GPG_private_key") 98 | if (privateKey != null) { 99 | useInMemoryPgpKeys( 100 | keyId, 101 | privateKey, 102 | propertyOrEnvOrNull(project, "GPG_private_password") 103 | ) 104 | sign(publishing.publications) 105 | } 106 | } 107 | } 108 | } 109 | 110 | project.ext.configureMavenCentralMetadata = { pom -> 111 | def root = asNode() 112 | root.appendNode('name', project.name) 113 | root.appendNode('description', "Data2viz ${project.name} library") 114 | root.appendNode('url', 'https://github.com/data2viz/geojson-kotlin') 115 | root.children().last() + pomConfig 116 | } 117 | 118 | project.ext.pomConfig = { 119 | licenses { 120 | license { 121 | name "The Apache Software License, Version 2.0" 122 | url "http://www.apache.org/licenses/LICENSE-2.0.txt" 123 | distribution "repo" 124 | } 125 | } 126 | developers { 127 | developer { 128 | id "Data2viz" 129 | name "Data2viz team" 130 | organization "Data2viz" 131 | organizationUrl "https://data2viz.io" 132 | } 133 | } 134 | 135 | scm { 136 | url "https://github.com/data2viz/geojson-kotlin" 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /gradle/test-mocha-js.gradle: -------------------------------------------------------------------------------- 1 | // Configures testing for JS modules 2 | 3 | apply plugin: 'com.moowork.node' 4 | 5 | task populateNodeModules(type: Copy, dependsOn: compileKotlin2Js) { 6 | from compileKotlin2Js.destinationDir 7 | into "${buildDir}/node_modules" 8 | 9 | afterEvaluate { 10 | configurations.testCompile.each { 11 | from zipTree(it.absolutePath).matching { 12 | include '*.js' 13 | include '*.js.map' 14 | } 15 | } 16 | } 17 | } 18 | 19 | node { 20 | version = "$node_version" 21 | npmVersion = "$npm_version" 22 | download = true 23 | } 24 | 25 | task installDependencies(type: YarnTask) { 26 | args = ['install'] 27 | } 28 | 29 | task prepareMocha(dependsOn: [compileTestKotlin2Js, populateNodeModules, installDependencies]) 30 | 31 | task runJsTests(type: YarnTask, dependsOn: prepareMocha) { 32 | args = ['test']} 33 | 34 | test.dependsOn runJsTests -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data2viz/geojson-kotlin/0a2e9f27dbcebda32dac3499d1e85b05eac7f2db/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /libraries_version.txt: -------------------------------------------------------------------------------- 1 | 0.6.7-SNAPSHOT -------------------------------------------------------------------------------- /license/LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Data2viz.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ -------------------------------------------------------------------------------- /license/README.md: -------------------------------------------------------------------------------- 1 | ## geojson-kotlin code 2 | 3 | - The JVM implementation is based on a port of [geojson-jackson](https://github.com/opendatalab-de/geojson-jackson). 4 | 5 | - Licence: Apache 2 -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "geojson-kotlin" 2 | 3 | include ":core" 4 | 5 | --------------------------------------------------------------------------------