├── .gitignore ├── .gitlab-ci.yml ├── LICENCE.txt ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lib ├── build.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── geojson │ │ ├── BoundingBox.kt │ │ ├── Exception.kt │ │ ├── Feature.kt │ │ ├── FeatureCollection.kt │ │ ├── GeoJsonObject.kt │ │ ├── GeometryCollection.kt │ │ ├── LinearRing.kt │ │ ├── Position.kt │ │ ├── Validation.kt │ │ ├── geometry │ │ ├── Geometry.kt │ │ ├── MultiGeometry.kt │ │ ├── impl │ │ │ ├── LineString.kt │ │ │ ├── MultiLineString.kt │ │ │ ├── MultiPoint.kt │ │ │ ├── MultiPolygon.kt │ │ │ ├── Point.kt │ │ │ └── Polygon.kt │ │ └── package-info.kt │ │ ├── gson │ │ ├── GeoJsonDeserializer.kt │ │ ├── GeoJsonSerializer.kt │ │ ├── GeoJsonType.kt │ │ ├── GeoJsonTypeAdapterFactory.kt │ │ ├── GsonBuilder.kt │ │ └── package-info.kt │ │ └── package-info.kt │ └── test │ └── java │ ├── ValidGeometryCommuteTests.kt │ └── ValidTypeAliasTests.kt ├── local.properties └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | build 24 | *.iml 25 | .gradle 26 | 27 | .idea 28 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | 2 | stages: 3 | - build 4 | - test 5 | # - deploy 6 | 7 | before_script: 8 | # - echo `pwd` # debug 9 | # - echo "$CI_BUILD_NAME, $CI_BUILD_REF_NAME $CI_BUILD_STAGE" # debug 10 | - export GRADLE_USER_HOME=`pwd`/.gradle 11 | 12 | cache: 13 | paths: 14 | - .gradle/wrapper 15 | - .gradle/caches 16 | 17 | build: 18 | stage: build 19 | script: 20 | - ./gradlew assemble 21 | artifacts: 22 | paths: 23 | - build/libs/*.jar 24 | expire_in: 1 week 25 | only: 26 | - master 27 | 28 | test: 29 | stage: test 30 | script: 31 | - ./gradlew check 32 | 33 | #deploy: 34 | # stage: deploy 35 | # only: 36 | # - master 37 | # script: 38 | # - ./gradlew bintrayUpload 39 | 40 | after_script: 41 | - echo "End CI" -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KGeoGson 2 | 3 | An implementation of GeoJSON / RFC7946 for Kotlin, using GSON. 4 | 5 | ## Download 6 | 7 | [ ![Download](https://api.bintray.com/packages/chris-hatton/maven/geojson-kotlin/images/download.svg) ](https://bintray.com/chris-hatton/maven/KGeoGson/_latestVersion) 8 | 9 | ```groovy 10 | repositories { 11 | jcenter() 12 | } 13 | 14 | dependencies { 15 | implementation 'org.chrishatton.lib:geojson-kotlin:0.0.1' 16 | } 17 | 18 | ``` 19 | 20 | ## Usage 21 | 22 | - Create a `Point` 23 | 24 | ```kotlin 25 | val point = Point(latitude = 1.0, longitude = 2.0) 26 | ``` 27 | 28 | - Incorporate point into a `Feature` 29 | 30 | ```kotlin 31 | val feature = Feature(geometry = point) 32 | ``` 33 | 34 | - Then incorporate feature into a `FeatureCollection` 35 | 36 | ```kotlin 37 | val featureCollection = FeatureCollection(1, listOf(feature)) 38 | ``` 39 | 40 | - For each *GeoJson* object, you can call `toJson()` 41 | 42 | ```kotlin 43 | val geoJsonText = featureCollection.toJson() 44 | ``` 45 | 46 | - Parsing : 47 | 48 | ```kotlin 49 | val geoJsonObjectOut: FeatureCollection = FeatureCollection.fromJson(geoJsonText) 50 | ``` 51 | 52 | ### GsonBuilder 53 | 54 | To serialize and parse GeoJson you need to register special adapter to GSon 55 | 56 | ```kotlin 57 | val gson = GsonBuilder() 58 | // registering KGeoGson adapter here 59 | .registerGeoJsonTypeAdapters() 60 | .create() 61 | ``` 62 | 63 | 64 | ## License 65 | 66 | Licensed under the Apache License, Version 2.0 (the "License"); 67 | you may not use this file except in compliance with the License. 68 | You may obtain a copy of the License at 69 | 70 | http://www.apache.org/licenses/LICENSE-2.0 71 | 72 | Unless required by applicable law or agreed to in writing, software 73 | distributed under the License is distributed on an "AS IS" BASIS, 74 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 75 | See the License for the specific language governing permissions and 76 | limitations under the License. 77 | 78 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | val clean by tasks.creating(Delete::class) { 3 | delete(rootProject.buildDir) 4 | } 5 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.caching=true 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-hatton/geojson-kotlin/b29aaae33af19451f0e054b7fb408345c1671c7e/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.0-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS='"-Xmx64m"' 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /lib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.util.Properties 2 | import java.util.Date 3 | 4 | plugins { 5 | kotlin("jvm") version "1.5.0" 6 | id("com.github.ben-manes.versions") version "0.38.0" 7 | id("fr.coppernic.versioning") version "3.2.1" 8 | id("org.jetbrains.dokka") version "1.4.32" 9 | id("com.jfrog.bintray") version "1.8.5" 10 | `maven-publish` 11 | } 12 | 13 | repositories { 14 | jcenter() 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation("com.google.code.gson:gson:2.8.6") 20 | 21 | testImplementation("junit:junit:4.13.2") 22 | } 23 | 24 | tasks { 25 | // dokka { 26 | // outputFormat = "html" 27 | // outputDirectory = "$buildDir/javadoc" 28 | // moduleName = rootProject.name 29 | // } 30 | 31 | publishToMavenLocal { 32 | dependsOn(build) 33 | } 34 | } 35 | 36 | // val dokkaJar by tasks.creating(Jar::class) { 37 | // group = JavaBasePlugin.DOCUMENTATION_GROUP 38 | // description = "Assembles Kotlin docs with Dokka" 39 | // archiveClassifier.set("javadoc") 40 | // from(tasks.dokka) 41 | // dependsOn(tasks.dokka) 42 | // } 43 | 44 | val sourcesJar by tasks.creating(Jar::class) { 45 | archiveClassifier.set("sources") 46 | from(sourceSets.getByName("main").allSource) 47 | } 48 | 49 | val artifactName = "geojson-kotlin" 50 | val artifactGroup = "org.chrishatton.lib" 51 | 52 | val pomUrl = "https://github.com/chris-hatton/geojson-kotlin" 53 | val pomScmUrl = "https://github.com/chris-hatton/geojson-kotlin" 54 | val pomIssueUrl = "https://github.com/chris-hatton/geojson-kotlin/issues" 55 | val pomDesc = "https://github.com/chris-hatton/geojson-kotlin" 56 | 57 | val githubRepo = "chris-hatton/geojson-kotlin" 58 | val githubReadme = "README.md" 59 | 60 | val pomLicenseName = "The Apache Software License, Version 2.0" 61 | val pomLicenseUrl = "http://www.apache.org/licenses/LICENSE-2.0.txt" 62 | val pomLicenseDist = "repo" 63 | 64 | val pomDeveloperId = "chris-hatton" 65 | val pomDeveloperName = "Chris Hatton" 66 | 67 | versioning { 68 | releaseMode = "snapshot" 69 | } 70 | 71 | publishing { 72 | publications { 73 | create("lib") { 74 | groupId = artifactGroup 75 | artifactId = artifactName 76 | version = project.versioning.info.full 77 | from(components["java"]) 78 | // artifact(dokkaJar) 79 | artifact(sourcesJar) 80 | 81 | pom.withXml { 82 | asNode().apply { 83 | appendNode("description", pomDesc) 84 | appendNode("name", rootProject.name) 85 | appendNode("url", pomUrl) 86 | appendNode("licenses").appendNode("license").apply { 87 | appendNode("name", pomLicenseName) 88 | appendNode("url", pomLicenseUrl) 89 | appendNode("distribution", pomLicenseDist) 90 | } 91 | appendNode("developers").appendNode("developer").apply { 92 | appendNode("id", pomDeveloperId) 93 | appendNode("name", pomDeveloperName) 94 | } 95 | appendNode("scm").apply { 96 | appendNode("url", pomScmUrl) 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | bintray { 105 | 106 | val properties = Properties() 107 | val inputStream = project.rootProject.file("local.properties").inputStream() 108 | properties.load(inputStream) 109 | 110 | user = System.getenv("bintrayUser") ?: properties.getProperty("bintrayUser") 111 | key = System.getenv("bintrayKey") ?: properties.getProperty("bintrayKey") 112 | publish = true 113 | 114 | setPublications("lib") 115 | 116 | pkg.apply { 117 | repo = "lib" 118 | name = rootProject.name 119 | setLicenses("Apache-2.0") 120 | setLabels("Gson", "json", "GeoJson", "GPS", "Kotlin") 121 | vcsUrl = pomScmUrl 122 | websiteUrl = pomUrl 123 | issueTrackerUrl = pomIssueUrl 124 | githubRepo = githubRepo 125 | githubReleaseNotesFile = githubReadme 126 | 127 | version.apply { 128 | name = project.versioning.info.full 129 | desc = pomDesc 130 | released = Date().toString() 131 | vcsTag = project.versioning.info.tag 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/BoundingBox.kt: -------------------------------------------------------------------------------- 1 | package geojson 2 | 3 | import geojson.geometry.impl.Point 4 | 5 | /** 6 | * Although in broad conceptual terms, a 'Bounding Box' is a geometry, under the GeoJSON 7 | * specification a bounding box is not classified as a such. 8 | * 9 | * A bounding box in GeoJSON is used only to refine requests for geo-spatial data to a targeted 10 | * region, rather than in specifying features having box-like geometry. 11 | * 12 | * https://tools.ietf.org/html/rfc7946#section-5 13 | */ 14 | data class BoundingBox( 15 | val southWest: Point, 16 | val northEast: Point 17 | ) { 18 | constructor(west: Double, south: Double, east: Double, north: Double) : this(Point(west, south), Point(east, north)) 19 | 20 | override fun toString(): String { 21 | // left,lower,right,upper,crs 22 | return "${southWest.longitude},${southWest.latitude},${northEast.longitude},${northEast.latitude}" 23 | } 24 | 25 | companion object { 26 | 27 | /* A bounding-box which encompasses the entire globe, from the most south-westerly to the most north-easterly point. */ 28 | var Global: BoundingBox = BoundingBox(southWest = Point.minimum, northEast = Point.maximum) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/Exception.kt: -------------------------------------------------------------------------------- 1 | package geojson 2 | 3 | import kotlin.reflect.KClass 4 | 5 | sealed class Exception : kotlin.Exception() { 6 | data class IllegalFormat(val reason: String? = null) : Exception() 7 | data class UnsupportedType(val type: KClass<*>) : Exception() 8 | data class UnknownTypeName(val name: String) : Exception() 9 | object InconsistentPositionDimensions : Exception() 10 | } 11 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/Feature.kt: -------------------------------------------------------------------------------- 1 | package geojson 2 | 3 | import geojson.geometry.Geometry 4 | 5 | data class Feature( 6 | val id: String? = null, 7 | val geometry: Geometry<*>, 8 | val properties: Map = mapOf() 9 | ) : GeoJsonObject() 10 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/FeatureCollection.kt: -------------------------------------------------------------------------------- 1 | package geojson 2 | 3 | data class FeatureCollection( 4 | val totalFeatures: Int, 5 | val features: List 6 | ) : GeoJsonObject() 7 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/GeoJsonObject.kt: -------------------------------------------------------------------------------- 1 | package geojson 2 | 3 | import com.google.gson.GsonBuilder 4 | import geojson.gson.registerGeoJsonTypeAdapters 5 | 6 | /** 7 | * https://tools.ietf.org/html/rfc7946 8 | * Section 3 9 | */ 10 | abstract class GeoJsonObject { 11 | 12 | fun toJson(): String { 13 | return GsonBuilder().registerGeoJsonTypeAdapters().create().toJson(this) 14 | } 15 | 16 | companion object { 17 | inline fun fromJson(text: String): T { 18 | return GsonBuilder().registerGeoJsonTypeAdapters().create().fromJson(text, T::class.java) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/GeometryCollection.kt: -------------------------------------------------------------------------------- 1 | package geojson 2 | 3 | import geojson.geometry.Geometry 4 | 5 | class GeometryCollection(val geometries: List>) : GeoJsonObject() 6 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/LinearRing.kt: -------------------------------------------------------------------------------- 1 | package geojson 2 | 3 | /** 4 | * RF7646 discusses the concept of a Linear Ring in section 3.1.6: 5 | * https://tools.ietf.org/html/rfc7946#section-3.1.6 6 | * 7 | * A Linear Ring is not explicitly represented as a GeoJSON geometry type, 8 | * instead being a sequence of positions with certain properties. 9 | * 10 | * Maintaining consistency with RF7646, this library will also avoid an object 11 | * representation of LinearRing, instead providing functions to manipulate 12 | * sequences of positions with respect to the Linear Ring definition. 13 | */ 14 | object LinearRing { 15 | 16 | /** 17 | * Determine if this sequence of Positions meets the criteria of being a Linear Ring. 18 | */ 19 | fun isLinearRing(positions: List): Boolean { 20 | return with(positions) { 21 | // Position-count and start-end sameness checked. TODO: Check winding? 22 | (count() >= 4) && (first() == last()) 23 | } 24 | } 25 | 26 | fun fromVertices(positions: List): List { 27 | return with(positions) { 28 | if (count() >= 3) { 29 | this + first() 30 | } else { 31 | throw Exception.InsufficientPositions 32 | } 33 | } 34 | } 35 | 36 | sealed class Exception : kotlin.Exception() { 37 | object InsufficientPositions : Exception() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/Position.kt: -------------------------------------------------------------------------------- 1 | package geojson 2 | 3 | /** 4 | * https://tools.ietf.org/html/rfc7946#section-3.1.1 5 | */ 6 | data class Position(val longitude: Double, val latitude: Double, val altitude: Double? = null) { 7 | fun validate() { 8 | if (longitude < -180.0 || longitude > 180.0) { 9 | throw Exception.IllegalFormat("Longitude '$longitude' is out of range -180 to 180") 10 | } 11 | if (latitude < -90.0 || latitude > 90.0) { 12 | throw Exception.IllegalFormat("Latitude '$latitude' is out of range -90 to 90") 13 | } 14 | } 15 | 16 | val hasAltitude: Boolean get() = altitude != null 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/Validation.kt: -------------------------------------------------------------------------------- 1 | package geojson 2 | 3 | fun List.checkAltitudeConsistent() { 4 | if (isEmpty()) { 5 | return 6 | } 7 | val hasAltitude = this[0].hasAltitude 8 | val isAltitudeConsistent = this.drop(1).all { it.hasAltitude == hasAltitude } 9 | if (!isAltitudeConsistent) { 10 | throw Exception.InconsistentPositionDimensions 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/geometry/Geometry.kt: -------------------------------------------------------------------------------- 1 | package geojson.geometry 2 | 3 | import geojson.GeoJsonObject 4 | import geojson.Position 5 | 6 | /** 7 | * https://tools.ietf.org/html/rfc7946#section-3.1 8 | */ 9 | abstract class Geometry(val coordinates: C) : GeoJsonObject() { 10 | 11 | interface Companion, C> { 12 | 13 | /** 14 | * If the coordinates are valid for Geometry G, this function has no effect. 15 | * If they are invalid, an exception will be thrown. 16 | */ 17 | fun validateCoordinates(coordinates: C) 18 | 19 | fun fromVertices(vertexPositions: List): G 20 | 21 | fun fromPositions(positions: List): G 22 | 23 | fun fromCoordinates(coordinates: C): G 24 | 25 | fun fromVertexPairs(vararg vertexPairs: Pair): G { 26 | val vertices: List = vertexPairs.map { Position(it.first, it.second) } 27 | return fromVertices(vertices) 28 | } 29 | 30 | interface Closed, C> : Companion { 31 | override fun fromVertices(vertexPositions: List): G { 32 | val positions: List = if (vertexPositions.isEmpty()) emptyList() else vertexPositions + vertexPositions[0] 33 | return fromPositions(positions) 34 | } 35 | } 36 | 37 | interface Open, C> : Companion { 38 | override fun fromVertices(vertexPositions: List): G = fromPositions(vertexPositions) 39 | } 40 | } 41 | 42 | // internal interface InternalCompanion,C> { 43 | // fun fromUnsafeCoordinates( coordinates: C ) : G 44 | // } 45 | 46 | override fun equals(other: Any?): Boolean { 47 | if (this === other) return true 48 | if (other?.javaClass != javaClass) return false 49 | 50 | other as Geometry<*> 51 | 52 | if (coordinates != other.coordinates) return false 53 | 54 | return true 55 | } 56 | 57 | override fun hashCode(): Int { 58 | return coordinates?.hashCode() ?: 0 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/geometry/MultiGeometry.kt: -------------------------------------------------------------------------------- 1 | package geojson.geometry 2 | 3 | /** 4 | * GeoJSON (RFC7946) specifies several 'Multi-' geometry types whose coordinates are a 5 | * straightforward collection of the coordinates of their associated, singular type. These are: 6 | * 7 | * MultiPoint -> Point 8 | * MultiLineString -> LineString 9 | * MultiPolygon -> Polygon 10 | * 11 | * For convenience sake; this library identifies these types with the MultiGeometry interface, 12 | * which guarantees the provision of a function to split out a collection of base types from the 13 | * Multi type. 14 | * 15 | * @param SG Singular geometry type 16 | */ 17 | interface MultiGeometry { 18 | 19 | /** 20 | * Return a collection of singular geometry instances, split from this multi-geometry instance. 21 | */ 22 | fun split(): List 23 | 24 | /** 25 | * Interface which, by convention alone, the 'companion object' of multi-geometry classes 26 | * should implement. 27 | * 28 | * @param SG Singular geometry type 29 | * @param SGC Singular geometry coordinates type 30 | * @param MG Multi-geometry type 31 | */ 32 | interface Companion { 33 | 34 | fun join(geometries: List): MG 35 | 36 | fun join(vararg geometries: SG) = join(geometries.toList()) 37 | 38 | fun validateCoordinates(coordinates: List) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/geometry/impl/LineString.kt: -------------------------------------------------------------------------------- 1 | package geojson.geometry.impl 2 | 3 | import geojson.Exception 4 | import geojson.Position 5 | import geojson.geometry.Geometry 6 | 7 | typealias LineStringCoordinates = List 8 | 9 | /** 10 | * https://tools.ietf.org/html/rfc7946#section-3.1.4 11 | */ 12 | class LineString(coordinates: LineStringCoordinates) : Geometry(coordinates) { 13 | 14 | companion object : Geometry.Companion.Open { 15 | 16 | override fun fromCoordinates(coordinates: LineStringCoordinates): LineString = LineString(coordinates) 17 | 18 | override fun fromPositions(positions: List): LineString = fromCoordinates(positions) 19 | 20 | override fun validateCoordinates(coordinates: List) { 21 | if (coordinates.count() < 2) { 22 | throw Exception.IllegalFormat("A LineString must have at least two positions.") 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/geometry/impl/MultiLineString.kt: -------------------------------------------------------------------------------- 1 | package geojson.geometry.impl 2 | 3 | import geojson.geometry.Geometry 4 | import geojson.geometry.MultiGeometry 5 | 6 | /** 7 | * https://tools.ietf.org/html/rfc7946#section-3.1.7 8 | */ 9 | class MultiLineString(coordinates: List) : Geometry>(coordinates), MultiGeometry { 10 | 11 | override fun split(): List = coordinates.map(::LineString) 12 | 13 | companion object : MultiGeometry.Companion { 14 | 15 | override fun join(geometries: List): MultiLineString = 16 | MultiLineString(coordinates = geometries.map { it.coordinates }) 17 | 18 | /** Each child component of the [MultiLineString] must be [LineString] compliant. */ 19 | override fun validateCoordinates(coordinates: List) { 20 | coordinates.forEach(LineString.Companion::validateCoordinates) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/geometry/impl/MultiPoint.kt: -------------------------------------------------------------------------------- 1 | package geojson.geometry.impl 2 | 3 | import geojson.Position 4 | import geojson.checkAltitudeConsistent 5 | import geojson.geometry.Geometry 6 | import geojson.geometry.MultiGeometry 7 | 8 | /** 9 | * https://tools.ietf.org/html/rfc7946#section-3.1.3 10 | */ 11 | class MultiPoint(coordinates: List) : Geometry>(coordinates), MultiGeometry { 12 | 13 | override fun split(): List = coordinates.map(::Point) 14 | 15 | companion object : MultiGeometry.Companion { 16 | 17 | override fun join(geometries: List): MultiPoint = 18 | MultiPoint(coordinates = geometries.map { it.coordinates }) 19 | 20 | override fun validateCoordinates(coordinates: List) { 21 | coordinates.checkAltitudeConsistent() 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/geometry/impl/MultiPolygon.kt: -------------------------------------------------------------------------------- 1 | package geojson.geometry.impl 2 | 3 | import geojson.geometry.Geometry 4 | import geojson.geometry.MultiGeometry 5 | 6 | /** 7 | * https://tools.ietf.org/html/rfc7946#section-3.1.7 8 | */ 9 | class MultiPolygon(coordinates: List) : Geometry>(coordinates), MultiGeometry { 10 | 11 | override fun split(): List = coordinates.map(::Polygon) 12 | 13 | companion object : MultiGeometry.Companion { 14 | 15 | override fun join(geometries: List): MultiPolygon = 16 | MultiPolygon(coordinates = geometries.map { it.coordinates }) 17 | 18 | override fun validateCoordinates(coordinates: List) { 19 | coordinates.forEach(Polygon.Companion::validateCoordinates) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/geometry/impl/Point.kt: -------------------------------------------------------------------------------- 1 | package geojson.geometry.impl 2 | 3 | import geojson.Exception 4 | import geojson.Position 5 | import geojson.geometry.Geometry 6 | 7 | typealias PointCoordinates = Position 8 | 9 | /** 10 | * https://tools.ietf.org/html/rfc7946#section-3.1.2 11 | */ 12 | class Point(coordinates: PointCoordinates) : Geometry(coordinates) { 13 | 14 | /** 15 | * Convenience constructor, forming the mandated 'single Position' coordinate from Latitude 16 | * and Longitude components. 17 | */ 18 | constructor(longitude: Double, latitude: Double) : this(Position(longitude = longitude, latitude = latitude)) 19 | 20 | /** Convenience accessor for the Longitude component of this [Point]s coordinate. */ 21 | val longitude: Double = coordinates.longitude 22 | 23 | /** Convenience accessor for the Latitude component of this [Point]s coordinate. */ 24 | val latitude: Double = coordinates.latitude 25 | 26 | override fun toString(): String = "POINT(${coordinates.latitude}%20$${coordinates.longitude})" 27 | 28 | companion object : Geometry.Companion.Open { 29 | 30 | override fun fromPositions(positions: List): Point { 31 | return when (positions.count()) { 32 | 1 -> Point(coordinates = positions[0]) 33 | else -> throw Exception.IllegalFormat("When creating a Point from a vertex list, only ONE element is allowed.") 34 | } 35 | } 36 | 37 | override fun fromCoordinates(coordinates: Position): Point = Point(coordinates) 38 | 39 | override fun validateCoordinates(coordinates: Position) { 40 | coordinates.validate() 41 | } 42 | 43 | /* The most South-Westerly point. */ 44 | val minimum = Point(longitude = -180.0, latitude = -90.0) 45 | 46 | /* The most North-Easterly point. */ 47 | val maximum = Point(longitude = 180.0, latitude = 90.0) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/geometry/impl/Polygon.kt: -------------------------------------------------------------------------------- 1 | package geojson.geometry.impl 2 | 3 | import geojson.Exception 4 | import geojson.LinearRing 5 | import geojson.Position 6 | import geojson.geometry.Geometry 7 | 8 | typealias PolygonCoordinates = List> 9 | 10 | /** 11 | * https://tools.ietf.org/html/rfc7946#section-3.1.6 12 | */ 13 | class Polygon internal constructor(coordinates: PolygonCoordinates) : Geometry(coordinates) { 14 | 15 | companion object : Geometry.Companion.Closed { 16 | 17 | override fun fromCoordinates(coordinates: PolygonCoordinates): Polygon = Polygon(coordinates) 18 | 19 | override fun fromPositions(positions: List): Polygon = fromCoordinates(listOf(positions)) 20 | 21 | override fun validateCoordinates(coordinates: PolygonCoordinates) { 22 | 23 | coordinates.withIndex().find { (_, positions) -> !LinearRing.isLinearRing(positions) }?.let { (index, positions) -> 24 | throw Exception.IllegalFormat("The top-level element at index [$index] of this Polygon's coordinates does not meet Linear Ring criteria, they are: '$positions'") 25 | } 26 | 27 | // TODO: Check that first Linear Ring is outer, encompassing all subsequent rings. 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/geometry/package-info.kt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-hatton/geojson-kotlin/b29aaae33af19451f0e054b7fb408345c1671c7e/lib/src/main/kotlin/geojson/geometry/package-info.kt -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/gson/GeoJsonDeserializer.kt: -------------------------------------------------------------------------------- 1 | package geojson.gson 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.JsonArray 5 | import com.google.gson.JsonDeserializationContext 6 | import com.google.gson.JsonDeserializer 7 | import com.google.gson.JsonElement 8 | import com.google.gson.JsonObject 9 | import com.google.gson.JsonParseException 10 | import com.google.gson.reflect.TypeToken 11 | import geojson.Feature 12 | import geojson.FeatureCollection 13 | import geojson.GeoJsonObject 14 | import geojson.Position 15 | import geojson.geometry.Geometry 16 | import geojson.geometry.impl.LineString 17 | import geojson.geometry.impl.MultiLineString 18 | import geojson.geometry.impl.MultiPoint 19 | import geojson.geometry.impl.MultiPolygon 20 | import geojson.geometry.impl.Point 21 | import geojson.geometry.impl.Polygon 22 | import java.lang.reflect.Type 23 | 24 | @Suppress("UNCHECKED_CAST") 25 | class GeoJsonDeserializer(private val lenient: Boolean = false) : JsonDeserializer { 26 | 27 | @Throws(JsonParseException::class) 28 | override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): T { 29 | 30 | val geoJsonType: GeoJsonType = readType(json.asJsonObject) 31 | 32 | return when (geoJsonType) { 33 | GeoJsonType.Feature -> readFeature(json) as T 34 | GeoJsonType.FeatureCollection -> readFeatureCollection(json) as T 35 | is GeoJsonType.Geometry -> readGeometry(json, geoJsonType) as T 36 | } 37 | } 38 | 39 | private fun readType(jsonObject: JsonObject): GeoJsonType { 40 | val typeName: String? = jsonObject.get(GeoJsonType.typeKey)?.asString 41 | return GeoJsonType.forString(typeName ?: "") 42 | ?: throw JsonParseException("Unrecognised geometry type '$typeName'") 43 | } 44 | 45 | private fun readPosition(element: JsonElement): Position { 46 | val array = element.asJsonArray ?: throw geojson.Exception.IllegalFormat() 47 | if (array.size() < 2) throw geojson.Exception.IllegalFormat("A GeoJSON Position must have at least two elements") 48 | val longitude: Double = array.get(0)?.asNumber?.toDouble() ?: throw geojson.Exception.IllegalFormat() 49 | val latitude: Double = array.get(1)?.asNumber?.toDouble() ?: throw geojson.Exception.IllegalFormat() 50 | val altitude: Double? = if (array.size() >= 3) array.get(2).asNumber?.toDouble() else null 51 | return Position(longitude, latitude, altitude) 52 | } 53 | 54 | private fun readListOfPositions(element: JsonElement): List { 55 | val array = element.asJsonArray ?: throw geojson.Exception.IllegalFormat() 56 | return array.map { readPosition(it) } 57 | } 58 | 59 | private fun readListOfListOfPositions(element: JsonElement): List> { 60 | val array = element.asJsonArray ?: throw geojson.Exception.IllegalFormat() 61 | return array.map { readListOfPositions(it) } 62 | } 63 | 64 | private fun readListOfListOfListOfPositions(element: JsonElement): List>> { 65 | val array = element.asJsonArray ?: throw geojson.Exception.IllegalFormat() 66 | return array.map { readListOfListOfPositions(it) } 67 | } 68 | 69 | private fun readCoordinates(element: JsonElement): JsonElement = 70 | (element as? JsonObject)?.get(GeoJsonType.Geometry.coordinatesKey) 71 | ?: throw geojson.Exception.IllegalFormat() 72 | 73 | private fun readPoint(element: JsonElement): Point = Point(coordinates = readPosition(readCoordinates(element))) 74 | private fun readMultiPoint(element: JsonElement): MultiPoint = MultiPoint(coordinates = readListOfPositions(readCoordinates(element))) 75 | private fun readLineString(element: JsonElement): LineString = LineString(coordinates = readListOfPositions(readCoordinates(element))) 76 | private fun readMultiLineString(element: JsonElement): MultiLineString = MultiLineString(coordinates = readListOfListOfPositions(readCoordinates(element))) 77 | private fun readPolygon(element: JsonElement): Polygon = Polygon(coordinates = readListOfListOfPositions(readCoordinates(element))) 78 | private fun readMultiPolygon(element: JsonElement): MultiPolygon = MultiPolygon(coordinates = readListOfListOfListOfPositions(readCoordinates(element))) 79 | 80 | private fun readGeometry( 81 | element: JsonElement, 82 | geometryType: GeoJsonType.Geometry = readType(element.asJsonObject) as? GeoJsonType.Geometry 83 | ?: throw Exception() 84 | ): Geometry<*> = when (geometryType) { 85 | GeoJsonType.Geometry.Point -> readPoint(element) 86 | GeoJsonType.Geometry.MultiPoint -> readMultiPoint(element) 87 | GeoJsonType.Geometry.LineString -> readLineString(element) 88 | GeoJsonType.Geometry.MultiLineString -> readMultiLineString(element) 89 | GeoJsonType.Geometry.Polygon -> readPolygon(element) 90 | GeoJsonType.Geometry.MultiPolygon -> readMultiPolygon(element) 91 | } 92 | 93 | private fun readFeature(element: JsonElement): Feature { 94 | val jsonObject = element.asJsonObject ?: throw geojson.Exception.IllegalFormat() 95 | val id: String? = jsonObject.getAsJsonPrimitive(GeoJsonType.Feature.idKey)?.asString 96 | val geometryObject: JsonObject = jsonObject.getAsJsonObject(GeoJsonType.Feature.geometryKey) 97 | val geometry: Geometry<*> = readGeometry(geometryObject) 98 | 99 | val propertiesTypeToken = object : TypeToken>() {} 100 | val propertiesJson = jsonObject.getAsJsonObject(GeoJsonType.Feature.propertiesKey) 101 | val properties = Gson().fromJson>(propertiesJson, propertiesTypeToken.type) 102 | 103 | return Feature(id, geometry, properties) 104 | } 105 | 106 | private fun readFeatureCollection(element: JsonElement): FeatureCollection { 107 | val jsonObject: JsonObject = element.asJsonObject ?: throw geojson.Exception.IllegalFormat() 108 | val featuresArray: JsonArray = jsonObject.get(GeoJsonType.FeatureCollection.featuresKey)?.asJsonArray 109 | ?: throw geojson.Exception.IllegalFormat() 110 | val features = featuresArray.map { readFeature(it) } 111 | val totalFeatures: Int = if (lenient) { 112 | jsonObject.get(GeoJsonType.FeatureCollection.totalFeaturesKey)?.asInt ?: features.size 113 | } else { 114 | jsonObject.get(GeoJsonType.FeatureCollection.totalFeaturesKey)?.asInt 115 | ?: throw geojson.Exception.IllegalFormat() 116 | } 117 | return FeatureCollection(totalFeatures = totalFeatures, features = features) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/gson/GeoJsonSerializer.kt: -------------------------------------------------------------------------------- 1 | package geojson.gson 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.JsonArray 5 | import com.google.gson.JsonElement 6 | import com.google.gson.JsonObject 7 | import com.google.gson.JsonSerializationContext 8 | import com.google.gson.JsonSerializer 9 | import geojson.Exception 10 | import geojson.Feature 11 | import geojson.FeatureCollection 12 | import geojson.GeoJsonObject 13 | import geojson.Position 14 | import geojson.geometry.Geometry 15 | import geojson.geometry.impl.LineString 16 | import geojson.geometry.impl.MultiLineString 17 | import geojson.geometry.impl.MultiPoint 18 | import geojson.geometry.impl.MultiPolygon 19 | import geojson.geometry.impl.Point 20 | import geojson.geometry.impl.Polygon 21 | import java.lang.reflect.Type 22 | 23 | // @Suppress("UNCHECKED_CAST") 24 | class GeoJsonSerializer(private val lenient: Boolean = false) : JsonSerializer { 25 | 26 | override fun serialize(src: T, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { 27 | return when (src) { 28 | is Feature -> writeFeature(src) 29 | is FeatureCollection -> writeFeatureCollection(src) 30 | is Geometry<*> -> writeGeometry(src) 31 | else -> throw Exception.UnsupportedType(src::class) 32 | } 33 | } 34 | 35 | private fun writePosition(coordinates: Position): JsonArray { 36 | return JsonArray().apply { 37 | add(coordinates.longitude) 38 | add(coordinates.latitude) 39 | coordinates.altitude?.let(this::add) 40 | } 41 | } 42 | 43 | private fun writeListOfPositions(coordinates: List): JsonArray = 44 | JsonArray().apply { coordinates.map(this@GeoJsonSerializer::writePosition).forEach(this::add) } 45 | 46 | private fun writeListOfListOfPositions(coordinates: List>): JsonArray = 47 | JsonArray().apply { coordinates.map(this@GeoJsonSerializer::writeListOfPositions).forEach(this::add) } 48 | 49 | private fun writeListOfListOfListOfPositions(coordinates: List>>): JsonArray = 50 | JsonArray().apply { coordinates.map(this@GeoJsonSerializer::writeListOfListOfPositions).forEach(this::add) } 51 | 52 | private fun JsonObject.addType(src: GeoJsonObject) { 53 | val geoJsonType: GeoJsonType = GeoJsonType.forObject(src) ?: throw geojson.Exception.UnsupportedType(src::class) 54 | addProperty(GeoJsonType.typeKey, geoJsonType.typeValue) 55 | } 56 | 57 | private fun writeGeometry(geometry: Geometry<*>): JsonObject { 58 | 59 | val coordinatesArray: JsonArray = when (geometry) { 60 | is Point -> writePosition(geometry.coordinates) 61 | is MultiPoint -> writeListOfPositions(geometry.coordinates) 62 | is LineString -> writeListOfPositions(geometry.coordinates) 63 | is MultiLineString -> writeListOfListOfPositions(geometry.coordinates) 64 | is Polygon -> writeListOfListOfPositions(geometry.coordinates) 65 | is MultiPolygon -> writeListOfListOfListOfPositions(geometry.coordinates) 66 | else -> throw geojson.Exception.UnsupportedType(geometry::class) 67 | } 68 | 69 | return JsonObject().apply { 70 | add(GeoJsonType.Geometry.coordinatesKey, coordinatesArray) 71 | addType(geometry) 72 | } 73 | } 74 | 75 | private fun writeFeature(feature: Feature): JsonObject = JsonObject().apply { 76 | addProperty(GeoJsonType.Feature.idKey, feature.id) 77 | add(GeoJsonType.Feature.geometryKey, writeGeometry(feature.geometry)) 78 | 79 | val jsonTree = Gson().toJsonTree(feature.properties) 80 | 81 | add(GeoJsonType.Feature.propertiesKey, jsonTree) 82 | 83 | addType(feature) 84 | } 85 | 86 | private fun writeFeatureCollection(featureCollection: FeatureCollection): JsonObject { 87 | 88 | val featuresArray: JsonArray = JsonArray().apply { 89 | featureCollection.features.map(this@GeoJsonSerializer::writeFeature).forEach(this::add) 90 | } 91 | 92 | return JsonObject().apply { 93 | add(GeoJsonType.FeatureCollection.featuresKey, featuresArray) 94 | addProperty(GeoJsonType.FeatureCollection.totalFeaturesKey, featureCollection.totalFeatures) 95 | addType(featureCollection) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/gson/GeoJsonType.kt: -------------------------------------------------------------------------------- 1 | package geojson.gson 2 | 3 | import geojson.GeoJsonObject 4 | import kotlin.reflect.KClass 5 | 6 | /** 7 | * Codifies a mapping between GeoJSON types and their Kotlin-class representations. 8 | * Used by [GeoJsonSerializer] and [GeoJsonDeserializer]. 9 | */ 10 | sealed class GeoJsonType(val typeValue: String, val `class`: KClass<*>) { 11 | 12 | sealed class Geometry(typeValue: String, `class`: KClass<*>) : GeoJsonType(typeValue, `class`) { 13 | 14 | companion object { 15 | const val coordinatesKey = "coordinates" 16 | } 17 | 18 | object Point : Geometry(typeValue = "Point", `class` = geojson.geometry.impl.Point::class) 19 | object MultiPoint : Geometry(typeValue = "MultiPoint", `class` = geojson.geometry.impl.MultiPoint::class) 20 | object LineString : Geometry(typeValue = "LineString", `class` = geojson.geometry.impl.LineString::class) 21 | object MultiLineString : Geometry(typeValue = "MultiLineString", `class` = geojson.geometry.impl.MultiLineString::class) 22 | object Polygon : Geometry(typeValue = "Polygon", `class` = geojson.geometry.impl.Polygon::class) 23 | object MultiPolygon : Geometry(typeValue = "MultiPolygon", `class` = geojson.geometry.impl.MultiPolygon::class) 24 | } 25 | 26 | object Feature : GeoJsonType(typeValue = "Feature", `class` = geojson.Feature::class) { 27 | const val idKey = "id" 28 | const val propertiesKey = "properties" 29 | const val geometryKey = "geometry" 30 | } 31 | 32 | object FeatureCollection : GeoJsonType(typeValue = "FeatureCollection", `class` = geojson.FeatureCollection::class) { 33 | const val featuresKey = "features" 34 | const val totalFeaturesKey = "totalFeatures" 35 | } 36 | 37 | companion object { 38 | 39 | const val typeKey = "type" 40 | 41 | private val allTypes: MutableList = mutableListOf( 42 | GeoJsonType.Geometry.Point, 43 | GeoJsonType.Geometry.MultiPoint, 44 | GeoJsonType.Geometry.LineString, 45 | GeoJsonType.Geometry.MultiLineString, 46 | GeoJsonType.Geometry.Polygon, 47 | GeoJsonType.Geometry.MultiPolygon, 48 | GeoJsonType.Feature, 49 | GeoJsonType.FeatureCollection 50 | ) 51 | 52 | fun forString(typeName: String): GeoJsonType? = allTypes.find { it.typeValue == typeName } 53 | fun forObject(obj: GeoJsonObject): GeoJsonType? = allTypes.find { it.`class` == obj::class } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/gson/GeoJsonTypeAdapterFactory.kt: -------------------------------------------------------------------------------- 1 | package geojson.gson 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.JsonDeserializer 5 | import com.google.gson.JsonSerializer 6 | import com.google.gson.TypeAdapter 7 | import com.google.gson.TypeAdapterFactory 8 | import com.google.gson.internal.bind.TreeTypeAdapter 9 | import com.google.gson.reflect.TypeToken 10 | import geojson.GeoJsonObject 11 | 12 | class GeoJsonTypeAdapterFactory(private val lenient: Boolean = false) : TypeAdapterFactory { 13 | 14 | companion object { 15 | val geoJsonObjectTypeToken: TypeToken = TypeToken.get(GeoJsonObject::class.java) 16 | } 17 | 18 | override fun create(gson: Gson, type: TypeToken): TypeAdapter? { 19 | 20 | val isGeoJsonObjectType = GeoJsonObject::class.java.isAssignableFrom(type.rawType) 21 | 22 | if (!isGeoJsonObjectType) { 23 | return null 24 | } 25 | 26 | val serializer: JsonSerializer = GeoJsonSerializer(lenient) 27 | val deserializer: JsonDeserializer = GeoJsonDeserializer(lenient) 28 | 29 | @Suppress("UNCHECKED_CAST") 30 | val adapter = TreeTypeAdapter(serializer, deserializer, gson, geoJsonObjectTypeToken, this) as TypeAdapter 31 | 32 | return adapter 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/gson/GsonBuilder.kt: -------------------------------------------------------------------------------- 1 | package geojson.gson 2 | 3 | import com.google.gson.GsonBuilder 4 | 5 | fun GsonBuilder.registerGeoJsonTypeAdapters(lenient: Boolean = false): GsonBuilder { 6 | this.registerTypeAdapterFactory(GeoJsonTypeAdapterFactory(lenient)) 7 | return this 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/gson/package-info.kt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-hatton/geojson-kotlin/b29aaae33af19451f0e054b7fb408345c1671c7e/lib/src/main/kotlin/geojson/gson/package-info.kt -------------------------------------------------------------------------------- /lib/src/main/kotlin/geojson/package-info.kt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris-hatton/geojson-kotlin/b29aaae33af19451f0e054b7fb408345c1671c7e/lib/src/main/kotlin/geojson/package-info.kt -------------------------------------------------------------------------------- /lib/src/test/java/ValidGeometryCommuteTests.kt: -------------------------------------------------------------------------------- 1 | import geojson.GeoJsonObject 2 | import geojson.geometry.impl.LineString 3 | import geojson.geometry.impl.MultiLineString 4 | import geojson.geometry.impl.MultiPoint 5 | import geojson.geometry.impl.MultiPolygon 6 | import geojson.geometry.impl.Point 7 | import geojson.geometry.impl.Polygon 8 | import geojson.gson.GeoJsonType 9 | import org.junit.Assert 10 | import org.junit.Test 11 | 12 | /** 13 | * Created by Chris on 14/08/2017. 14 | */ 15 | class ValidGeometryCommuteTests { 16 | 17 | @Test 18 | fun testCommutePoint() { 19 | val point = Point(latitude = 1.0, longitude = 2.0) 20 | testCommuteJson(geoJsonObject = point) 21 | } 22 | 23 | @Test 24 | fun testCommuteMultiPoint() { 25 | val multiPoint: MultiPoint = MultiPoint.join( 26 | Point.fromVertexPairs(1.1 to 1.2), 27 | Point.fromVertexPairs(1.3 to 1.4), 28 | Point.fromVertexPairs(1.5 to 1.6) 29 | ) 30 | testCommuteJson(geoJsonObject = multiPoint) 31 | } 32 | 33 | @Test 34 | fun testCommuteLineString() { 35 | val lineString: LineString = LineString.fromVertexPairs( 36 | (0.0 to 0.0), 37 | (1.0 to 0.0), 38 | (1.0 to 1.0) 39 | ) 40 | testCommuteJson(geoJsonObject = lineString) 41 | } 42 | 43 | @Test 44 | fun testCommuteMultiLineString() { 45 | val lineString1: LineString = LineString.fromVertexPairs( 46 | (0.0 to 0.0), 47 | (1.0 to 0.0), 48 | (1.0 to 1.0) 49 | ) 50 | val lineString2: LineString = LineString.fromVertexPairs( 51 | (3.0 to 4.0), 52 | (4.0 to 2.0), 53 | (5.0 to 5.0) 54 | ) 55 | val multiLineString: MultiLineString = MultiLineString.join(lineString1, lineString2) 56 | testCommuteJson(geoJsonObject = multiLineString) 57 | } 58 | 59 | @Test 60 | fun testCommutePolygon() { 61 | val polygon: Polygon = Polygon.fromVertexPairs( 62 | (0.0 to 0.0), 63 | (1.0 to 0.0), 64 | (1.0 to 1.0), 65 | (0.0 to 1.0) 66 | ) 67 | testCommuteJson(geoJsonObject = polygon) 68 | } 69 | 70 | @Test 71 | fun testCommuteMultiPolygon() { 72 | val polygon1: Polygon = Polygon.fromVertexPairs( 73 | (0.0 to 0.0), 74 | (1.0 to 0.0), 75 | (1.0 to 1.0), 76 | (0.0 to 1.0) 77 | ) 78 | val polygon2: Polygon = Polygon.fromVertexPairs( 79 | (2.0 to 2.0), 80 | (3.0 to 2.0), 81 | (3.0 to 3.0), 82 | (2.0 to 3.0) 83 | ) 84 | val multiPolygon = MultiPolygon.join(polygon1, polygon2) 85 | testCommuteJson(geoJsonObject = multiPolygon) 86 | } 87 | 88 | private inline fun testCommuteJson(geoJsonObject: T) { 89 | val name = GeoJsonType.forObject(geoJsonObject)?.typeValue 90 | println("Converting $name to JSON...") 91 | val geoJsonText = geoJsonObject.toJson() 92 | println(geoJsonText) 93 | println("Converting JSON to $name...") 94 | val geoJsonObjectOut: T = GeoJsonObject.fromJson(geoJsonText) 95 | println("Comparing...") 96 | Assert.assertEquals(geoJsonObject, geoJsonObjectOut) 97 | println("Success") 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/src/test/java/ValidTypeAliasTests.kt: -------------------------------------------------------------------------------- 1 | import geojson.Feature 2 | import geojson.FeatureCollection 3 | import geojson.GeoJsonObject 4 | import geojson.geometry.impl.Point 5 | import geojson.gson.GeoJsonType 6 | import org.junit.Assert 7 | import org.junit.Test 8 | 9 | typealias Where = FeatureCollection 10 | 11 | /** 12 | * Created by Chris on 14/08/2017. 13 | */ 14 | class ValidTypeAliasTests { 15 | 16 | @Test 17 | fun testFeatureCollection() { 18 | val point = Point(latitude = 1.0, longitude = 2.0) 19 | val feature = Feature(geometry = point) 20 | val collection = FeatureCollection(1, listOf(feature)) 21 | 22 | testCommuteJson(geoJsonObject = collection) 23 | } 24 | 25 | @Test 26 | fun testWhere() { 27 | val point = Point(latitude = 1.0, longitude = 2.0) 28 | val feature = Feature(geometry = point) 29 | val collection = Where(1, listOf(feature)) 30 | 31 | testCommuteJson(geoJsonObject = collection) 32 | } 33 | 34 | private inline fun testCommuteJson(geoJsonObject: T) { 35 | val name = GeoJsonType.forObject(geoJsonObject)?.typeValue 36 | println("Converting $name to JSON...") 37 | val geoJsonText = geoJsonObject.toJson() 38 | println(geoJsonText) 39 | println("Converting JSON to $name...") 40 | val geoJsonObjectOut: T = GeoJsonObject.fromJson(geoJsonText) 41 | println("Comparing...") 42 | Assert.assertEquals(geoJsonObject, geoJsonObjectOut) 43 | println("Success") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /local.properties: -------------------------------------------------------------------------------- 1 | bintrayUser=chris-hatton 2 | bintrayKey=24efc2c8609d23c745b2c38e74e537417216d99e -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | include("lib") 3 | --------------------------------------------------------------------------------