├── .github └── workflows │ └── build.yml ├── .gitignore ├── CHANGES.md ├── CONTRIBUTING.md ├── FORMATS.md ├── README.md ├── about.md ├── asl-v20.txt ├── devnotes.md ├── eclipse ├── notice.md ├── pom.xml └── src ├── main └── java │ ├── org │ └── locationtech │ │ └── spatial4j │ │ ├── SpatialPredicate.java │ │ ├── context │ │ ├── SpatialContext.java │ │ ├── SpatialContextFactory.java │ │ ├── jts │ │ │ ├── DatelineRule.java │ │ │ ├── JtsSpatialContext.java │ │ │ ├── JtsSpatialContextFactory.java │ │ │ └── ValidationRule.java │ │ └── package-info.java │ │ ├── distance │ │ ├── AbstractDistanceCalculator.java │ │ ├── CartesianDistCalc.java │ │ ├── DistanceCalculator.java │ │ ├── DistanceUtils.java │ │ ├── GeodesicSphereDistCalc.java │ │ └── package-info.java │ │ ├── exception │ │ ├── InvalidShapeException.java │ │ └── UnsupportedSpatialPredicate.java │ │ ├── io │ │ ├── BinaryCodec.java │ │ ├── GeoJSONReader.java │ │ ├── GeoJSONWriter.java │ │ ├── GeohashUtils.java │ │ ├── LegacyShapeReader.java │ │ ├── LegacyShapeWriter.java │ │ ├── OnePointsBuilder.java │ │ ├── ParseUtils.java │ │ ├── PolyshapeReader.java │ │ ├── PolyshapeWriter.java │ │ ├── ShapeIO.java │ │ ├── ShapeReader.java │ │ ├── ShapeWriter.java │ │ ├── SupportedFormats.java │ │ ├── WKTReader.java │ │ ├── WKTWriter.java │ │ ├── WktShapeParser.java │ │ ├── jackson │ │ │ ├── GeometryAsGeoJSONSerializer.java │ │ │ ├── GeometryAsWKTSerializer.java │ │ │ ├── GeometryDeserializer.java │ │ │ ├── PackageVersion.java │ │ │ ├── PackageVersion.java.in │ │ │ ├── ShapeAsGeoJSONSerializer.java │ │ │ ├── ShapeAsWKTSerializer.java │ │ │ ├── ShapeDeserializer.java │ │ │ ├── ShapesAsGeoJSONModule.java │ │ │ ├── ShapesAsWKTModule.java │ │ │ └── package-info.java │ │ ├── jts │ │ │ ├── JtsBinaryCodec.java │ │ │ ├── JtsGeoJSONWriter.java │ │ │ ├── JtsPolyshapeWriter.java │ │ │ ├── JtsWKTReaderShapeParser.java │ │ │ └── JtsWKTWriter.java │ │ └── package-info.java │ │ ├── package-info.java │ │ └── shape │ │ ├── BaseShape.java │ │ ├── Circle.java │ │ ├── Point.java │ │ ├── Rectangle.java │ │ ├── Shape.java │ │ ├── ShapeCollection.java │ │ ├── ShapeFactory.java │ │ ├── SpatialRelation.java │ │ ├── impl │ │ ├── BBoxCalculator.java │ │ ├── BufferedLine.java │ │ ├── BufferedLineString.java │ │ ├── CircleImpl.java │ │ ├── GeoCircle.java │ │ ├── InfBufLine.java │ │ ├── PointImpl.java │ │ ├── Range.java │ │ ├── RectangleImpl.java │ │ └── ShapeFactoryImpl.java │ │ ├── jts │ │ ├── JtsGeometry.java │ │ ├── JtsPoint.java │ │ └── JtsShapeFactory.java │ │ └── package-info.java │ └── overview.html └── test ├── java └── org │ └── locationtech │ └── spatial4j │ ├── TestLog.java │ ├── context │ ├── SpatialContextFactoryTest.java │ └── jts │ │ └── JtsSpatialContextTest.java │ ├── distance │ ├── CompareRadiansSnippet.java │ └── TestDistances.java │ ├── io │ ├── BaseRoundTripTest.java │ ├── BinaryCodecTest.java │ ├── GeneralGeoJSONTest.java │ ├── GeneralPolyshapeTest.java │ ├── GeneralReadWriteShapeTest.java │ ├── GeneralWktTest.java │ ├── JtsBinaryCodecTest.java │ ├── JtsPolyshapeParserTest.java │ ├── JtsWKTReaderShapeParserTest.java │ ├── JtsWktShapeParserTest.java │ ├── LegacyShapeReadWriterTest.java │ ├── ShapeFormatTest.java │ ├── TestGeohashUtils.java │ ├── WKTWriterTest.java │ ├── WktCustomShapeParserTest.java │ ├── WktShapeParserTest.java │ ├── benchmark │ │ └── ShapeBenchmarks.java │ └── jackson │ │ ├── JacksonGeoJSONReaderTest.java │ │ ├── JacksonGeoJSONWriterTest.java │ │ ├── JacksonShapeReader.java │ │ ├── JacksonShapeWriter.java │ │ ├── ObjectWithGeometry.java │ │ └── SimpleJacksonTest.java │ ├── shape │ ├── AbstractTestShapes.java │ ├── BufferedLineStringTest.java │ ├── BufferedLineTest.java │ ├── JtsGeometryTest.java │ ├── RandomizedShapeTest.java │ ├── RectIntersectionTestHelper.java │ ├── RoundingDistCalc.java │ ├── ShapeCollectionTest.java │ ├── TestShapes2D.java │ ├── TestShapesGeo.java │ ├── impl │ │ └── BBoxCalculatorTest.java │ └── jts │ │ └── JtsShapeFactoryTest.java │ └── util │ ├── Geom.java │ └── GeomBuilder.java └── resources ├── fiji.wkt.txt ├── russia.wkt.txt └── samples.txt /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ main, master ] 9 | pull_request: 10 | branches: [ main, master ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | java: [ '8', '11' , '17', '21' ] 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Set up JDK ${{ matrix.java }} 23 | uses: actions/setup-java@v4 24 | with: 25 | java-version: ${{ matrix.java }} 26 | distribution: 'temurin' 27 | cache: maven 28 | - name: Build with Maven 29 | run: mvn --batch-mode --update-snapshots verify 30 | 31 | - name: Publish Test Results 32 | if: always() 33 | uses: EnricoMi/publish-unit-test-result-action@v2 34 | with: 35 | files: target/surefire-reports/*.xml 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /*.ipr 2 | /.idea/ 3 | *.iml 4 | target/ 5 | 6 | .classpath 7 | .project 8 | .settings/ 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Spatial4j 2 | 3 | The Spatial4j project is always excited to accept contributions from the community. This document 4 | contains some guidelines to help users and developers contribute to the project. 5 | 6 | - [Code Style](#code) 7 | - [Issues and Bugs](#bugs) 8 | - [Discussion Forum](#discuss) 9 | - [Submitting Patches](#patches) 10 | 11 | ## Code Style 12 | 13 | Spatial4j adheres to (as much as possible) the 14 | [Google Java Style](https://google.github.io/styleguide/javaguide.html) conventions. If a patch 15 | or commit deviates from these guidelines a reviewer will likely ask for it to be reformatted. 16 | 17 | ## Issues, Bugs, and Feature Requests 18 | 19 | Spatial4j utilizes Github for issue tracking. Bugs, issues, and feature requests should be 20 | filed [here](https://github.com/locationtech/spatial4j/issues). 21 | 22 | ## Discussion Forum 23 | 24 | Often communication can be carried out through comments on an issue or pull request directly but 25 | for larger discussions that are more general in nature it is recommended that the project 26 | [mailing list](https://locationtech.org/mailman/listinfo/spatial4j-dev) be used. 27 | 28 | ## Submitting Patches 29 | 30 | The best way to submit a patch or add a new feature to the code is to submit a [ 31 | pull request](https://help.github.com/articles/using-pull-requests/). Below are some guidelines to 32 | follow when developing code intended to be submitted via pull request. 33 | 34 | This [guide](http://people.redhat.com/rjones/how-to-supply-code-to-open-source-projects/) contains 35 | some useful guidelines for contributing to open source projects in general. Below are some additionally 36 | stressed points. 37 | 38 | ### Send email first 39 | 40 | It is never a bad idea to email the mailing list with thoughts about a change you intend to make 41 | before you make it. This allows the committers to weigh in with thoughts and suggestions that will 42 | help you make the change and ultimately ensure your successful contribution to the project. 43 | 44 | ### Filing an ECA 45 | 46 | Contributors must electronically sign the Eclipse Contributor Agreement (ECA). 47 | This is a one-time event, and is quick & easy. 48 | 49 | * http://www.eclipse.org/legal/ECA.php 50 | 51 | For more information, please see the Eclipse Committer Handbook: 52 | https://www.eclipse.org/projects/handbook/#resources-commit 53 | 54 | ### One patch per one bug/feature 55 | 56 | Avoid submitting patches that mix together multiple features and/or bug fixes into a single changeset. 57 | It is much easier to review and understand a patch that is dedicated to a single purpose. 58 | 59 | ### No cruft 60 | 61 | While working on a patch often developers can't resist the urge to reformat code that is unrelated 62 | to the patch. This adds unnecessary "noise" that makes the job of the reviewer more difficult. It 63 | also makes the history of a change harder to analyze after the fact. If a patch contains unnecessary 64 | whitespace or other formatting changes a reviewer will ask for them to be removed. 65 | 66 | ### Viewing the entire project history 67 | 68 | (Optional) We recommended that developers do this step to be able to view the 69 | pre-LocationTech history of the project. This can be achieved with the following 70 | command after the repository has been cloned: 71 | 72 | git fetch origin refs/replace/*:refs/replace/* 73 | 74 | -------------------------------------------------------------------------------- /about.md: -------------------------------------------------------------------------------- 1 | ## About This Content 2 | 3 | May 22, 2015 4 | 5 | ### License 6 | 7 | The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the 8 | Content is provided to you under the terms and conditions of the Apache License, Version 2.0. A copy of the Apache 9 | License, Version 2.0 is available at 10 | [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) 11 | 12 | If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another 13 | party ("Redistributor") and different terms and conditions may apply to your use of any object code in the Content. 14 | Check the Redistributor’s license that was provided with the Content. If no such license exists, contact the 15 | Redistributor. Unless otherwise indicated below, the terms and conditions of the Apache License, Version 2.0 still apply 16 | to any source code in the Content and such source code may be obtained at 17 | [http://www.eclipse.org](http://www.eclipse.org). 18 | -------------------------------------------------------------------------------- /devnotes.md: -------------------------------------------------------------------------------- 1 | This file has notes for committers. 2 | 3 | # Making a snapshot release 4 | 5 | Note: depends on having access to the Sonatype repo described further below 6 | 7 | mvn deploy -Prelease 8 | 9 | # Making a new release 10 | 11 | First, understand that LocationTech projects undergo releases using an official process described here: 12 | https://www.eclipse.org/projects/handbook/#release with complete and thorough details here: 13 | https://wiki.eclipse.org/Development_Resources/HOWTO/Release_Reviews 14 | 15 | Note: 16 | * See https://projects.eclipse.org/projects/locationtech.spatial4j and "Committer Tools" panel at right, including 17 | * "Create a new release" 18 | * "Generate IP Log" 19 | * References to the "PMC" (Project Management Committee) in Spatial4j's case is the 20 | LocationTech Technology PMC. There aren't 21 | project-specific PMCs. 22 | 23 | *TODO distill the process here.* 24 | 25 | Those steps can be concurrent with following some of the earlier technical steps below. Deploying/releasing any 26 | jars must wait until the release date assuming the release review is successful. 27 | 28 | ## Review files... 29 | 30 | * Review CHANGES.md — up to date? 31 | 32 | * Review README.md — up to date? 33 | 34 | * Review pom — up to date? Run display-plugin-updates & display-dependency-updates. Do *not* remove the SNAPSHOT; 35 | that'll be handled later. 36 | 37 | 38 | ## Build, Tag, and Deploy to Sonatype 39 | 40 | Optional: create a release branch if there will be release-specific changes. Probably not. 41 | 42 | **Prepare** the release: https://maven.apache.org/maven-release/maven-release-plugin/examples/prepare-release.html 43 | 44 | ``` 45 | mvn release:prepare 46 | ``` 47 | 48 | This will create a tag in git named spatial4j-0.6 (or whatever the version is) with the pom updated. 49 | If something goes wrong, you'll have to do `release:rollback` and then possibly remove the tag (pushing to GitHub) 50 | so that next time it can succeed. 51 | 52 | Some "release.properties" file and "pom.xml.releaseBackup" files will be produced. They will be removed later at the `clean` phase 53 | 54 | Note that org.locationtech.spatial4j.io.jackson.PackageVersion includes a hard-coded version. 55 | It's overwritten in the build process. You should manually update it to the next snapshot release and commit. 56 | It's okay that the Maven release plugin, when setting the final version, did so only in the POM but not this 57 | souce code file because the jars that get created include the modified file (both compiled and source). 58 | 59 | **Perform** the release: https://maven.apache.org/maven-release/maven-release-plugin/examples/perform-release.html 60 | 61 | mvn release:perform 62 | 63 | This should build, GPG sign, and deploy artifacts to Sonatype. 64 | When I last edited these instructions, there was previously a different process. So... something will probably go wrong. 65 | It is intentional that there remains a manual step further below at Sonatype to send the binaries to Maven Central. 66 | Further info: https://central.sonatype.org/pages/apache-maven.html#performing-a-release-deployment-with-the-maven-release-plugin 67 | 68 | **Clean** 69 | 70 | mvn release:clean 71 | 72 | 73 | ## Release deployed artifacts to Maven Central 74 | 75 | http://central.sonatype.org/pages/releasing-the-deployment.html 76 | 77 | ## Publish the Maven site (includes Javadoc) 78 | 79 | We publish the Maven "site" HTML on GitHub, and we link to it from the readme and others might too. The site 80 | includes the javadoc API. 81 | 82 | Instructions: 83 | http://blog.progs.be/517/publishing-javadoc-to-github-using-maven 84 | 85 | Summary: 86 | 87 | First checkout the release tag (e.g. spatial4j-0.5) or modify pom.xml temporarily to have this version. The site 88 | reports reference the version, so this is why. 89 | 90 | mvn clean site 91 | mvn scm-publish:publish-scm 92 | 93 | When site completes, open the target/site/index.html to view it to see if it's reasonable. Then continue to the publish 94 | step. The publish step will require your username & password for GitHub. Observe the final published content online: 95 | 96 | https://locationtech.github.io/spatial4j/ 97 | 98 | ## GitHub Release 99 | 100 | On the project's Git based homepage, navigate to the "Tags": https://github.com/locationtech/spatial4j/tags 101 | 102 | Find the tag for 0.8 (or whatever the version is), click the "..." menu next to it, and choose "Create a Release". 103 | 104 | Put a brief characterization of the release at the top, and then paste in the content from CHANGES.md. Add the release binaries as well (all JAR files). 105 | 106 | ## Update the Eclipse CMS 107 | 108 | At the Eclipse project site for Spatial4j, hit the Edit button: 109 | https://projects.eclipse.org/projects/locationtech.spatial4j 110 | Then on the "Download" section, update the version. 111 | 112 | ## Send announcement 113 | -------------------------------------------------------------------------------- /eclipse: -------------------------------------------------------------------------------- 1 | rm */.classpath 2 | rm */.project 3 | rm */*/.classpath 4 | rm */*/.project 5 | rm */*/*/.classpath 6 | rm */*/*/.project 7 | rm */*/*/*/.classpath 8 | rm */*/*/*/.project 9 | mvn eclipse:eclipse -P updateLucene 10 | 11 | -------------------------------------------------------------------------------- /notice.md: -------------------------------------------------------------------------------- 1 | # Notices for Spatial4j 2 | 3 | This content is produced and maintained by the Eclipse Spatial4j project. 4 | 5 | * Project home: https://projects.eclipse.org/projects/locationtech.spatial4j 6 | 7 | ## Trademarks 8 | 9 | LocationTech Spatial4j, and Spatial4j are trademarks of the Eclipse Foundation. 10 | 11 | ## Declared Project Licenses 12 | 13 | This program and the accompanying materials are made available under the terms 14 | of the Apache License, Version 2.0 which is available at 15 | https://www.apache.org/licenses/LICENSE-2.0. 16 | 17 | SPDX-License-Identifier: Apache-2.0 18 | 19 | ## Source Code 20 | 21 | The project maintains the following source code repositories: 22 | 23 | * https://github.com/locationtech/spatial4j 24 | 25 | ## Third-party Content 26 | 27 | (none) 28 | 29 | ## Cryptography 30 | 31 | (none) -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/context/jts/DatelineRule.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.context.jts; 10 | 11 | /** 12 | * Indicates the algorithm used to process JTS Polygons and JTS LineStrings for detecting dateline 13 | * (aka anti-meridian) crossings. It only applies when geo=true. 14 | */ 15 | public enum DatelineRule { 16 | /** No polygon will cross the dateline. */ 17 | none, 18 | 19 | /** 20 | * Adjacent points with an x (longitude) difference that spans more than half way around the 21 | * globe will be interpreted as going the other (shorter) way, and thus cross the dateline. 22 | */ 23 | width180, // TODO is there a better name that doesn't have '180' in it? 24 | 25 | /** 26 | * For rectangular polygons, the point order is interpreted as being counter-clockwise (CCW). 27 | * However, non-rectangular polygons or other shapes aren't processed this way; they use the 28 | * {@link #width180} rule instead. The CCW rule is specified by OGC Simple Features 29 | * Specification v. 1.2.0 section 6.1.11.1. 30 | */ 31 | ccwRect 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/context/jts/JtsSpatialContextFactory.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.context.jts; 10 | 11 | import org.locationtech.spatial4j.context.SpatialContextFactory; 12 | import org.locationtech.spatial4j.io.GeoJSONReader; 13 | import org.locationtech.spatial4j.io.LegacyShapeReader; 14 | import org.locationtech.spatial4j.io.LegacyShapeWriter; 15 | import org.locationtech.spatial4j.io.PolyshapeReader; 16 | import org.locationtech.spatial4j.io.WKTReader; 17 | import org.locationtech.spatial4j.io.jts.*; 18 | import org.locationtech.spatial4j.shape.jts.JtsShapeFactory; 19 | import org.locationtech.jts.geom.CoordinateSequenceFactory; 20 | import org.locationtech.jts.geom.GeometryFactory; 21 | import org.locationtech.jts.geom.PrecisionModel; 22 | import org.locationtech.jts.geom.impl.CoordinateArraySequenceFactory; 23 | 24 | import java.util.Map; 25 | 26 | /** 27 | * See {@link SpatialContextFactory#makeSpatialContext(java.util.Map, ClassLoader)}. 28 | *

29 | * The following keys are looked up in the args map, in addition to those in the 30 | * superclass: 31 | *

32 | *
datelineRule
33 | *
width180(default)|ccwRect|none 34 | * -- see {@link DatelineRule}
35 | *
validationRule
36 | *
error(default)|none|repairConvexHull|repairBuffer0 37 | * -- see {@link ValidationRule}
38 | *
autoIndex
39 | *
true|false(default) -- see {@link JtsShapeFactory#isAutoIndex()}
40 | *
allowMultiOverlap
41 | *
true|false(default) -- see {@link JtsSpatialContext#isAllowMultiOverlap()}
42 | *
precisionModel
43 | *
floating(default) | floating_single | fixed 44 | * -- see {@link org.locationtech.jts.geom.PrecisionModel}. 45 | * If {@code fixed} then you must also provide {@code precisionScale} 46 | * -- see {@link org.locationtech.jts.geom.PrecisionModel#getScale()}
47 | *
useJtsPoint, useJtsLineString, useJtsMulti
48 | *
All default to true. See corresponding methods on {@link JtsShapeFactory}.
49 | *
50 | */ 51 | public class JtsSpatialContextFactory extends SpatialContextFactory { 52 | 53 | protected static final PrecisionModel defaultPrecisionModel = new PrecisionModel();//floating 54 | 55 | //These 3 are JTS defaults for new GeometryFactory() 56 | public PrecisionModel precisionModel = defaultPrecisionModel; 57 | public int srid = 0; 58 | public CoordinateSequenceFactory coordinateSequenceFactory = CoordinateArraySequenceFactory.instance(); 59 | 60 | //ignored if geo=false 61 | public DatelineRule datelineRule = DatelineRule.width180; 62 | 63 | public ValidationRule validationRule = ValidationRule.error; 64 | public boolean autoIndex = false; 65 | public boolean allowMultiOverlap = false;//ignored if geo=false 66 | 67 | //kinda advanced options: 68 | public boolean useJtsPoint = true; 69 | public boolean useJtsLineString = true; 70 | public boolean useJtsMulti = true; 71 | 72 | public JtsSpatialContextFactory() { 73 | super.shapeFactoryClass = JtsShapeFactory.class; 74 | super.binaryCodecClass = JtsBinaryCodec.class; 75 | } 76 | 77 | @Override 78 | protected void checkDefaultFormats() { 79 | if (readers.isEmpty() ) { 80 | addReaderIfNoggitExists(GeoJSONReader.class); 81 | readers.add(WKTReader.class); 82 | readers.add(PolyshapeReader.class); 83 | readers.add(LegacyShapeReader.class); 84 | } 85 | if (writers.isEmpty()) { 86 | writers.add(JtsGeoJSONWriter.class); 87 | writers.add(JtsWKTWriter.class); 88 | writers.add(JtsPolyshapeWriter.class); 89 | writers.add(LegacyShapeWriter.class); 90 | } 91 | } 92 | 93 | @Override 94 | protected void init(Map args, ClassLoader classLoader) { 95 | super.init(args, classLoader); 96 | 97 | initField("datelineRule"); 98 | initField("validationRule"); 99 | initField("autoIndex"); 100 | initField("allowMultiOverlap"); 101 | initField("useJtsPoint"); 102 | initField("useJtsLineString"); 103 | initField("useJtsMulti"); 104 | 105 | String scaleStr = args.get("precisionScale"); 106 | String modelStr = args.get("precisionModel"); 107 | 108 | if (scaleStr != null) { 109 | if (modelStr != null && !modelStr.equals("fixed")) 110 | throw new RuntimeException("Since precisionScale was specified; precisionModel must be 'fixed' but got: "+modelStr); 111 | precisionModel = new PrecisionModel(Double.parseDouble(scaleStr)); 112 | } else if (modelStr != null) { 113 | if (modelStr.equals("floating")) { 114 | precisionModel = new PrecisionModel(PrecisionModel.FLOATING); 115 | } else if (modelStr.equals("floating_single")) { 116 | precisionModel = new PrecisionModel(PrecisionModel.FLOATING_SINGLE); 117 | } else if (modelStr.equals("fixed")) { 118 | throw new RuntimeException("For fixed model, must specifiy 'precisionScale'"); 119 | } else { 120 | throw new RuntimeException("Unknown precisionModel: "+modelStr); 121 | } 122 | } 123 | } 124 | 125 | public GeometryFactory getGeometryFactory() { 126 | if (precisionModel == null || coordinateSequenceFactory == null) 127 | throw new IllegalStateException("precision model or coord seq factory can't be null"); 128 | return new GeometryFactory(precisionModel, srid, coordinateSequenceFactory); 129 | } 130 | 131 | @Override 132 | public JtsSpatialContext newSpatialContext() { 133 | return new JtsSpatialContext(this); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/context/jts/ValidationRule.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.context.jts; 10 | 11 | import org.locationtech.spatial4j.io.ShapeReader; 12 | 13 | /** 14 | * Indicates how JTS geometries (notably polygons but applies to other geometries too) are 15 | * validated (if at all) and repaired (if at all). This setting usually only applies to 16 | * {@link ShapeReader}. 17 | */ 18 | public enum ValidationRule { 19 | /** 20 | * Geometries will not be validated (because it's kinda expensive to calculate). You may or may 21 | * not ultimately get an error at some point; results are undefined. However, note that 22 | * coordinates will still be validated for falling within the world boundaries. 23 | * 24 | * @see org.locationtech.jts.geom.Geometry#isValid() 25 | */ 26 | none, 27 | 28 | /** 29 | * Geometries will be explicitly validated on creation, possibly resulting in an exception: 30 | * {@link org.locationtech.spatial4j.exception.InvalidShapeException}. 31 | */ 32 | error, 33 | 34 | /** 35 | * Invalid Geometries are repaired by taking the convex hull. The result will very likely be a 36 | * larger shape that matches false-positives, but no false-negatives. See 37 | * {@link org.locationtech.jts.geom.Geometry#convexHull()}. 38 | */ 39 | repairConvexHull, 40 | 41 | /** 42 | * Invalid polygons are repaired using the {@code buffer(0)} technique. From the JTS FAQ: 44 | *

45 | * The buffer operation is fairly insensitive to topological invalidity, and the act of 46 | * computing the buffer can often resolve minor issues such as self-intersecting rings. However, 47 | * in some situations the computed result may not be what is desired (i.e. the buffer operation 48 | * may be "confused" by certain topologies, and fail to produce a result which is close to the 49 | * original. An example where this can happen is a "bow-tie: or "figure-8" polygon, with one 50 | * very small lobe and one large one. Depending on the orientations of the lobes, the buffer(0) 51 | * operation may keep the small lobe and discard the "valid" large lobe). 52 | *

53 | */ 54 | repairBuffer0 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/context/package-info.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | /** SpatialContext implementations are the facade to the Spatial4j API. */ 10 | package org.locationtech.spatial4j.context; -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/distance/AbstractDistanceCalculator.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 MITRE and VoyagerSearch 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.distance; 10 | 11 | import org.locationtech.spatial4j.shape.Point; 12 | 13 | /** 14 | */ 15 | public abstract class AbstractDistanceCalculator implements DistanceCalculator { 16 | 17 | @Override 18 | public double distance(Point from, Point to) { 19 | return distance(from, to.getX(), to.getY()); 20 | } 21 | 22 | @Override 23 | public boolean within(Point from, double toX, double toY, double distance) { 24 | return distance(from, toX, toY) <= distance; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return getClass().getSimpleName(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/distance/CartesianDistCalc.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.distance; 10 | 11 | import org.locationtech.spatial4j.context.SpatialContext; 12 | import org.locationtech.spatial4j.shape.Circle; 13 | import org.locationtech.spatial4j.shape.Point; 14 | import org.locationtech.spatial4j.shape.Rectangle; 15 | 16 | /** 17 | * Calculates based on Euclidean / Cartesian 2d plane. 18 | */ 19 | public class CartesianDistCalc extends AbstractDistanceCalculator { 20 | 21 | public static final CartesianDistCalc INSTANCE = new CartesianDistCalc(); 22 | public static final CartesianDistCalc INSTANCE_SQUARED = new CartesianDistCalc(true); 23 | 24 | private final boolean squared; 25 | 26 | public CartesianDistCalc() { 27 | this.squared = false; 28 | } 29 | 30 | /** 31 | * @param squared Set to true to have {@link #distance(org.locationtech.spatial4j.shape.Point, org.locationtech.spatial4j.shape.Point)} 32 | * return the square of the correct answer. This is a 33 | * performance optimization used when sorting in which the 34 | * actual distance doesn't matter so long as the sort order is 35 | * consistent. 36 | */ 37 | public CartesianDistCalc(boolean squared) { 38 | this.squared = squared; 39 | } 40 | 41 | @Override 42 | public double distance(Point from, double toX, double toY) { 43 | double xSquaredPlusYSquared = distanceSquared(from.getX(), from.getY(), toX, toY); 44 | if (squared) 45 | return xSquaredPlusYSquared; 46 | 47 | return Math.sqrt(xSquaredPlusYSquared); 48 | } 49 | 50 | private static double distanceSquared(double fromX, double fromY, double toX, double toY) { 51 | double deltaX = fromX - toX; 52 | double deltaY = fromY - toY; 53 | return deltaX*deltaX + deltaY*deltaY; 54 | } 55 | 56 | /** 57 | * Distance from point to a line segment formed between points 'v' and 'w'. 58 | * It respects the "squared" option. 59 | */ 60 | // TODO add to generic DistanceCalculator and develop geo versions. 61 | public double distanceToLineSegment(Point point, double vX, double vY, double wX, double wY) { 62 | // Translated from: http://bl.ocks.org/mbostock/4218871 63 | double d = distanceSquared(vX, vY, wX, wY); 64 | double toX; 65 | double toY; 66 | if (d <= 0) { 67 | toX = vX; 68 | toY = vY; 69 | } else { 70 | // t = ((point[0] - v[0]) * (w[0] - v[0]) + (point[1] - v[1]) * (w[1] - v[1])) / d 71 | double t = ((point.getX() - vX) * (wX - vX) + (point.getY() - vY) * (wY - vY)) / d; 72 | if (t < 0) { 73 | toX = vX; 74 | toY = vY; 75 | } else if (t > 1) { 76 | toX = wX; 77 | toY = wY; 78 | } else { 79 | toX = vX + t * (wX - vX); 80 | toY = vY + t * (wY - vY); 81 | } 82 | } 83 | return distance(point, toX, toY); 84 | } 85 | 86 | @Override 87 | public boolean within(Point from, double toX, double toY, double distance) { 88 | double deltaX = from.getX() - toX; 89 | double deltaY = from.getY() - toY; 90 | return deltaX*deltaX + deltaY*deltaY <= distance*distance; 91 | } 92 | 93 | @Override 94 | public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse) { 95 | if (distDEG == 0) { 96 | if (reuse == null) 97 | return from; 98 | reuse.reset(from.getX(), from.getY()); 99 | return reuse; 100 | } 101 | double bearingRAD = DistanceUtils.toRadians(bearingDEG); 102 | double x = from.getX() + Math.sin(bearingRAD) * distDEG; 103 | double y = from.getY() + Math.cos(bearingRAD) * distDEG; 104 | if (reuse == null) { 105 | return ctx.makePoint(x, y); 106 | } else { 107 | reuse.reset(x, y); 108 | return reuse; 109 | } 110 | } 111 | 112 | @Override 113 | public Rectangle calcBoxByDistFromPt(Point from, double distDEG, SpatialContext ctx, Rectangle reuse) { 114 | double minX = from.getX() - distDEG; 115 | double maxX = from.getX() + distDEG; 116 | double minY = from.getY() - distDEG; 117 | double maxY = from.getY() + distDEG; 118 | if (reuse == null) { 119 | return ctx.makeRectangle(minX, maxX, minY, maxY); 120 | } else { 121 | reuse.reset(minX, maxX, minY, maxY); 122 | return reuse; 123 | } 124 | } 125 | 126 | @Override 127 | public double calcBoxByDistFromPt_yHorizAxisDEG(Point from, double distDEG, SpatialContext ctx) { 128 | return from.getY(); 129 | } 130 | 131 | @Override 132 | public double area(Rectangle rect) { 133 | return rect.getArea(null); 134 | } 135 | 136 | @Override 137 | public double area(Circle circle) { 138 | return circle.getArea(null); 139 | } 140 | 141 | @Override 142 | public boolean equals(Object o) { 143 | if (this == o) return true; 144 | if (o == null || getClass() != o.getClass()) return false; 145 | 146 | CartesianDistCalc that = (CartesianDistCalc) o; 147 | 148 | if (squared != that.squared) return false; 149 | 150 | return true; 151 | } 152 | 153 | @Override 154 | public int hashCode() { 155 | return (squared ? 1 : 0); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/distance/DistanceCalculator.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.distance; 10 | 11 | import org.locationtech.spatial4j.context.SpatialContext; 12 | import org.locationtech.spatial4j.shape.Circle; 13 | import org.locationtech.spatial4j.shape.Point; 14 | import org.locationtech.spatial4j.shape.Rectangle; 15 | 16 | /** 17 | * Performs calculations relating to distance, such as the distance between a pair of points. A 18 | * calculator might be based on Euclidean space, or a spherical model, or theoretically something 19 | * else like an ellipsoid. 20 | */ 21 | public interface DistanceCalculator { 22 | 23 | /** The distance between from and to. */ 24 | public double distance(Point from, Point to); 25 | 26 | /** The distance between from and Point(toX,toY). */ 27 | public double distance(Point from, double toX, double toY); 28 | 29 | /** Returns true if the distance between from and to is <= distance. */ 30 | public boolean within(Point from, double toX, double toY, double distance); 31 | 32 | /** 33 | * Calculates where a destination point is given an origin (from) 34 | * distance, and bearing (given in degrees -- 0-360). If reuse is given, then 35 | * this method may reset() it and return it. 36 | */ 37 | public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse); 38 | 39 | /** 40 | * Calculates the bounding box of a circle, as specified by its center point 41 | * and distance. 42 | */ 43 | public Rectangle calcBoxByDistFromPt(Point from, double distDEG, SpatialContext ctx, Rectangle reuse); 44 | 45 | /** 46 | * The Y coordinate of the horizontal axis of a circle that has maximum width. On a 47 | * 2D plane, this result is always from.getY() but, perhaps surprisingly, on a sphere 48 | * it is going to be slightly different. 49 | */ 50 | public double calcBoxByDistFromPt_yHorizAxisDEG(Point from, double distDEG, SpatialContext ctx); 51 | 52 | public double area(Rectangle rect); 53 | 54 | public double area(Circle circle); 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/distance/GeodesicSphereDistCalc.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.distance; 10 | 11 | import org.locationtech.spatial4j.context.SpatialContext; 12 | import org.locationtech.spatial4j.shape.Circle; 13 | import org.locationtech.spatial4j.shape.Point; 14 | import org.locationtech.spatial4j.shape.Rectangle; 15 | 16 | import static org.locationtech.spatial4j.distance.DistanceUtils.toDegrees; 17 | import static org.locationtech.spatial4j.distance.DistanceUtils.toRadians; 18 | 19 | /** 20 | * A base class for a Distance Calculator that assumes a spherical earth model. 21 | */ 22 | public abstract class GeodesicSphereDistCalc extends AbstractDistanceCalculator { 23 | 24 | private static final double radiusDEG = DistanceUtils.toDegrees(1);//in degrees 25 | 26 | @Override 27 | public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse) { 28 | if (distDEG == 0) { 29 | if (reuse == null) 30 | return from; 31 | reuse.reset(from.getX(), from.getY()); 32 | return reuse; 33 | } 34 | Point result = DistanceUtils.pointOnBearingRAD( 35 | toRadians(from.getY()), toRadians(from.getX()), 36 | toRadians(distDEG), 37 | toRadians(bearingDEG), ctx, reuse);//output result is in radians 38 | result.reset(toDegrees(result.getX()), toDegrees(result.getY())); 39 | return result; 40 | } 41 | 42 | @Override 43 | public Rectangle calcBoxByDistFromPt(Point from, double distDEG, SpatialContext ctx, Rectangle reuse) { 44 | return DistanceUtils.calcBoxByDistFromPtDEG(from.getY(), from.getX(), distDEG, ctx, reuse); 45 | } 46 | 47 | @Override 48 | public double calcBoxByDistFromPt_yHorizAxisDEG(Point from, double distDEG, SpatialContext ctx) { 49 | return DistanceUtils.calcBoxByDistFromPt_latHorizAxisDEG(from.getY(), from.getX(), distDEG); 50 | } 51 | 52 | @Override 53 | public double area(Rectangle rect) { 54 | //From http://mathforum.org/library/drmath/view/63767.html 55 | double lat1 = toRadians(rect.getMinY()); 56 | double lat2 = toRadians(rect.getMaxY()); 57 | return Math.PI / 180 * radiusDEG * radiusDEG * 58 | Math.abs(Math.sin(lat1) - Math.sin(lat2)) * 59 | rect.getWidth(); 60 | } 61 | 62 | @Override 63 | public double area(Circle circle) { 64 | //formula is a simplified case of area(rect). 65 | double lat = toRadians(90 - circle.getRadius()); 66 | return 2 * Math.PI * radiusDEG * radiusDEG * (1 - Math.sin(lat)); 67 | } 68 | 69 | @Override 70 | public boolean equals(Object obj) { 71 | if (obj == null) 72 | return false; 73 | return getClass().equals(obj.getClass()); 74 | } 75 | 76 | @Override 77 | public int hashCode() { 78 | return getClass().hashCode(); 79 | } 80 | 81 | @Override 82 | public final double distance(Point from, double toX, double toY) { 83 | return toDegrees(distanceLatLonRAD(toRadians(from.getY()), toRadians(from.getX()), toRadians(toY), toRadians(toX))); 84 | } 85 | 86 | protected abstract double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2); 87 | 88 | public static class Haversine extends GeodesicSphereDistCalc { 89 | 90 | @Override 91 | protected double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2) { 92 | return DistanceUtils.distHaversineRAD(lat1,lon1,lat2,lon2); 93 | } 94 | 95 | } 96 | 97 | public static class LawOfCosines extends GeodesicSphereDistCalc { 98 | 99 | @Override 100 | protected double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2) { 101 | return DistanceUtils.distLawOfCosinesRAD(lat1, lon1, lat2, lon2); 102 | } 103 | 104 | } 105 | 106 | public static class Vincenty extends GeodesicSphereDistCalc { 107 | 108 | @Override 109 | protected double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2) { 110 | return DistanceUtils.distVincentyRAD(lat1, lon1, lat2, lon2); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/distance/package-info.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | /** 10 | * Ways to calculate distance. 11 | */ 12 | package org.locationtech.spatial4j.distance; 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/exception/InvalidShapeException.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.exception; 10 | 11 | /** 12 | * A shape was constructed but failed because, based on the given parts, it's invalid. For example 13 | * a rectangle's minimum Y was specified as greater than the maximum Y. This class is not used for 14 | * parsing exceptions; that's usually {@link java.text.ParseException}. 15 | */ 16 | public class InvalidShapeException extends RuntimeException { 17 | 18 | public InvalidShapeException(String reason, Throwable cause) { 19 | super(reason, cause); 20 | } 21 | 22 | public InvalidShapeException(String reason) { 23 | super(reason); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/exception/UnsupportedSpatialPredicate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | // NOTE: we keep the header as it came from ASF; it did not originate in Spatial4j 19 | 20 | package org.locationtech.spatial4j.exception; 21 | 22 | import org.locationtech.spatial4j.SpatialPredicate; 23 | 24 | /** 25 | * Exception thrown when something cannot implement the {@link SpatialPredicate}. 26 | */ 27 | public class UnsupportedSpatialPredicate extends UnsupportedOperationException { 28 | 29 | public UnsupportedSpatialPredicate(SpatialPredicate op) { 30 | super(op.getName()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/LegacyShapeReader.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io; 10 | 11 | import java.io.IOException; 12 | import java.io.Reader; 13 | import java.text.ParseException; 14 | import java.util.StringTokenizer; 15 | 16 | import org.locationtech.spatial4j.context.SpatialContext; 17 | import org.locationtech.spatial4j.context.SpatialContextFactory; 18 | import org.locationtech.spatial4j.exception.InvalidShapeException; 19 | import org.locationtech.spatial4j.shape.Point; 20 | import org.locationtech.spatial4j.shape.Shape; 21 | 22 | /** 23 | * Reads a shape from the old format. 24 | * 37 | */ 38 | @Deprecated 39 | public class LegacyShapeReader implements ShapeReader { 40 | 41 | final SpatialContext ctx; 42 | 43 | public LegacyShapeReader(SpatialContext ctx, SpatialContextFactory factory) { 44 | this.ctx = ctx; 45 | } 46 | 47 | 48 | /** Reads the shape specification as defined in the class javadocs. If the first character is 49 | * a letter but it doesn't complete out "Circle" or "CIRCLE" then this method returns null, 50 | * offering the caller the opportunity to potentially try additional parsing. 51 | * If the first character is not a letter then it's assumed to be a point or rectangle. If that 52 | * doesn't work out then an {@link org.locationtech.spatial4j.exception.InvalidShapeException} is thrown. 53 | */ 54 | public static Shape readShapeOrNull(String str, SpatialContext ctx) throws InvalidShapeException { 55 | if (str == null || str.length() == 0) { 56 | throw new InvalidShapeException(str); 57 | } 58 | 59 | if (Character.isLetter(str.charAt(0))) { 60 | if (str.startsWith("Circle(") || str.startsWith("CIRCLE(")) { 61 | int idx = str.lastIndexOf(')'); 62 | if (idx > 0) { 63 | String body = str.substring("Circle(".length(), idx); 64 | StringTokenizer st = new StringTokenizer(body, " "); 65 | String token = st.nextToken(); 66 | Point pt; 67 | if (token.indexOf(',') != -1) { 68 | pt = readLatCommaLonPoint(token, ctx); 69 | } else { 70 | double x = Double.parseDouble(token); 71 | double y = Double.parseDouble(st.nextToken()); 72 | pt = ctx.makePoint(x, y); 73 | } 74 | Double d = null; 75 | 76 | String arg = st.nextToken(); 77 | idx = arg.indexOf('='); 78 | if (idx > 0) { 79 | String k = arg.substring(0, idx); 80 | if (k.equals("d") || k.equals("distance")) { 81 | d = Double.parseDouble(arg.substring(idx + 1)); 82 | } else { 83 | throw new InvalidShapeException("unknown arg: " + k + " :: " + str); 84 | } 85 | } else { 86 | d = Double.parseDouble(arg); 87 | } 88 | if (st.hasMoreTokens()) { 89 | throw new InvalidShapeException("Extra arguments: " + st.nextToken() + " :: " + str); 90 | } 91 | if (d == null) { 92 | throw new InvalidShapeException("Missing Distance: " + str); 93 | } 94 | //NOTE: we are assuming the units of 'd' is the same as that of the spatial context. 95 | return ctx.makeCircle(pt, d); 96 | } 97 | } 98 | return null;//caller has opportunity to try other parsing 99 | } 100 | 101 | if (str.indexOf(',') != -1) 102 | return readLatCommaLonPoint(str, ctx); 103 | StringTokenizer st = new StringTokenizer(str, " "); 104 | double p0 = Double.parseDouble(st.nextToken()); 105 | double p1 = Double.parseDouble(st.nextToken()); 106 | if (st.hasMoreTokens()) { 107 | double p2 = Double.parseDouble(st.nextToken()); 108 | double p3 = Double.parseDouble(st.nextToken()); 109 | if (st.hasMoreTokens()) 110 | throw new InvalidShapeException("Only 4 numbers supported (rect) but found more: " + str); 111 | return ctx.makeRectangle(p0, p2, p1, p3); 112 | } 113 | return ctx.makePoint(p0, p1); 114 | } 115 | 116 | /** Reads geospatial latitude then a comma then longitude. */ 117 | private static Point readLatCommaLonPoint(String value, SpatialContext ctx) throws InvalidShapeException { 118 | double[] latLon = ParseUtils.parseLatitudeLongitude(value); 119 | return ctx.makePoint(latLon[1], latLon[0]); 120 | } 121 | 122 | //------- 123 | 124 | @Override 125 | public String getFormatName() { 126 | return ShapeIO.LEGACY; 127 | } 128 | 129 | @Override 130 | public Shape read(Object value) throws IOException, ParseException, InvalidShapeException { 131 | Shape shape = readShapeOrNull(value.toString(), ctx); 132 | if(shape==null) { 133 | throw new ParseException("unable to read shape: "+value, 0); 134 | } 135 | return readShapeOrNull(value.toString(), ctx); 136 | } 137 | 138 | @Override 139 | public Shape readIfSupported(Object value) throws InvalidShapeException { 140 | return readShapeOrNull(value.toString(), ctx); 141 | } 142 | 143 | @Override 144 | public Shape read(Reader reader) throws IOException, ParseException, InvalidShapeException { 145 | return read(WKTReader.readString(reader)); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/LegacyShapeWriter.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io; 10 | 11 | import java.io.IOException; 12 | import java.io.Writer; 13 | import java.text.NumberFormat; 14 | import java.util.Locale; 15 | 16 | import org.locationtech.spatial4j.context.SpatialContext; 17 | import org.locationtech.spatial4j.context.SpatialContextFactory; 18 | import org.locationtech.spatial4j.shape.Circle; 19 | import org.locationtech.spatial4j.shape.Point; 20 | import org.locationtech.spatial4j.shape.Rectangle; 21 | import org.locationtech.spatial4j.shape.Shape; 22 | 23 | /** 24 | * Writes a shape in the old format. 25 | * 38 | */ 39 | @Deprecated 40 | public class LegacyShapeWriter implements ShapeWriter { 41 | 42 | final SpatialContext ctx; 43 | 44 | public LegacyShapeWriter(SpatialContext ctx, SpatialContextFactory factory) { 45 | this.ctx = ctx; 46 | } 47 | 48 | /** 49 | * Writes a shape to a String, in a format that can be read by 50 | * {@link LegacyShapeReader#readShapeOrNull(String, SpatialContext)} 51 | * @param shape Not null. 52 | * @return Not null. 53 | */ 54 | public static String writeShape(Shape shape) { 55 | return writeShape(shape, makeNumberFormat(6)); 56 | } 57 | 58 | /** Overloaded to provide a number format. */ 59 | public static String writeShape(Shape shape, NumberFormat nf) { 60 | if (shape instanceof Point) { 61 | Point point = (Point) shape; 62 | return nf.format(point.getX()) + " " + nf.format(point.getY()); 63 | } 64 | else if (shape instanceof Rectangle) { 65 | Rectangle rect = (Rectangle)shape; 66 | return 67 | nf.format(rect.getMinX()) + " " + 68 | nf.format(rect.getMinY()) + " " + 69 | nf.format(rect.getMaxX()) + " " + 70 | nf.format(rect.getMaxY()); 71 | } 72 | else if (shape instanceof Circle) { 73 | Circle c = (Circle) shape; 74 | return "Circle(" + 75 | nf.format(c.getCenter().getX()) + " " + 76 | nf.format(c.getCenter().getY()) + " " + 77 | "d=" + nf.format(c.getRadius()) + 78 | ")"; 79 | } 80 | return shape.toString(); 81 | } 82 | 83 | /** 84 | * A convenience method to create a suitable NumberFormat for writing numbers. 85 | */ 86 | public static NumberFormat makeNumberFormat(int fractionDigits) { 87 | NumberFormat nf = NumberFormat.getInstance(Locale.ROOT);//not thread-safe 88 | nf.setGroupingUsed(false); 89 | nf.setMaximumFractionDigits(fractionDigits); 90 | nf.setMinimumFractionDigits(0); 91 | return nf; 92 | } 93 | 94 | @Override 95 | public String getFormatName() { 96 | return ShapeIO.LEGACY; 97 | } 98 | 99 | @Override 100 | public void write(Writer output, Shape shape) throws IOException { 101 | output.append(writeShape(shape)); 102 | } 103 | 104 | @Override 105 | public String toString(Shape shape) { 106 | return writeShape(shape); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/OnePointsBuilder.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2016 David Smiley 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io; 10 | 11 | import org.locationtech.spatial4j.shape.Point; 12 | import org.locationtech.spatial4j.shape.ShapeFactory; 13 | 14 | /** INTERNAL class used by some {@link ShapeReader}s. */ 15 | public class OnePointsBuilder implements ShapeFactory.PointsBuilder { 16 | private ShapeFactory shapeFactory; 17 | private Point point; 18 | 19 | public OnePointsBuilder(ShapeFactory shapeFactory) { 20 | this.shapeFactory = shapeFactory; 21 | } 22 | 23 | @Override 24 | public OnePointsBuilder pointXY(double x, double y) { 25 | assert point == null; 26 | point = shapeFactory.pointXY(x, y); 27 | return this; 28 | } 29 | 30 | @Override 31 | public OnePointsBuilder pointXYZ(double x, double y, double z) { 32 | assert point == null; 33 | point = shapeFactory.pointXYZ(x, y, z); 34 | return this; 35 | } 36 | 37 | @Override 38 | public OnePointsBuilder pointLatLon(double latitude, double longitude) { 39 | assert point == null; 40 | point = shapeFactory.pointLatLon(latitude, longitude); 41 | return this; 42 | } 43 | 44 | public Point getPoint() { 45 | return point; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/ShapeIO.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io; 10 | 11 | 12 | public interface ShapeIO { 13 | public static final String WKT = "WKT"; 14 | public static final String GeoJSON = "GeoJSON"; 15 | public static final String POLY = "POLY"; 16 | public static final String LEGACY = "LEGACY"; 17 | 18 | /** 19 | * @return the format name 20 | */ 21 | public String getFormatName(); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/ShapeReader.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io; 10 | 11 | import java.io.IOException; 12 | import java.io.Reader; 13 | import java.text.ParseException; 14 | 15 | import org.locationtech.spatial4j.exception.InvalidShapeException; 16 | import org.locationtech.spatial4j.shape.Shape; 17 | 18 | /** 19 | * Implementations are expected to be thread safe 20 | */ 21 | public interface ShapeReader extends ShapeIO { 22 | 23 | /** 24 | * @param value -- the input value, could be a String or other object 25 | * @return a shape valid shape (not null) 26 | */ 27 | public Shape read(Object value) throws IOException, ParseException, InvalidShapeException; 28 | 29 | /** 30 | * @param value -- the input value, could be a String or other object 31 | * @return a shape or null, if the input was un readable. 32 | * 33 | * This will throw {@link InvalidShapeException} when we could read a shape, but it was 34 | * invalid 35 | */ 36 | public Shape readIfSupported(Object value) throws InvalidShapeException; 37 | 38 | /** 39 | * Read a {@link Shape} from the reader. 40 | * 41 | * @param reader -- the input. Note, it will not be closed by this function 42 | * @return a valid Shape (never null) 43 | */ 44 | public Shape read(Reader reader) throws IOException, ParseException, InvalidShapeException; 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/ShapeWriter.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io; 10 | 11 | import java.io.IOException; 12 | import java.io.Writer; 13 | import org.locationtech.spatial4j.shape.Shape; 14 | 15 | /** 16 | * Implementations are expected to be thread safe 17 | */ 18 | public interface ShapeWriter extends ShapeIO { 19 | 20 | /** 21 | * Write a shape to the output writer 22 | */ 23 | public void write(Writer output, Shape shape) throws IOException; 24 | 25 | /** 26 | * Write a shape to String 27 | */ 28 | public String toString(Shape shape); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/SupportedFormats.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io; 10 | 11 | import java.util.List; 12 | 13 | import org.locationtech.spatial4j.context.SpatialContext; 14 | import org.locationtech.spatial4j.shape.Shape; 15 | 16 | /** 17 | * Information about the formats a {@link SpatialContext} can read/write 18 | */ 19 | public class SupportedFormats { 20 | 21 | private final List readers; 22 | private final List writers; 23 | 24 | private final ShapeReader wktReader; 25 | private final ShapeWriter wktWriter; 26 | 27 | private final ShapeReader geoJsonReader; 28 | private final ShapeWriter geoJsonWriter; 29 | 30 | public SupportedFormats(List readers, List writers) { 31 | this.readers = readers; 32 | this.writers = writers; 33 | 34 | wktReader = getReader(ShapeIO.WKT); 35 | wktWriter = getWriter(ShapeIO.WKT); 36 | 37 | geoJsonReader = getReader(ShapeIO.GeoJSON); 38 | geoJsonWriter = getWriter(ShapeIO.GeoJSON); 39 | } 40 | 41 | public List getReaders() { 42 | return readers; 43 | } 44 | 45 | public List getWriters() { 46 | return writers; 47 | } 48 | 49 | public ShapeReader getReader(String fmt) { 50 | for(ShapeReader f : readers) { 51 | if(fmt.equals(f.getFormatName())) { 52 | return f; 53 | } 54 | } 55 | return null; 56 | } 57 | 58 | public ShapeWriter getWriter(String fmt) { 59 | for(ShapeWriter f : writers) { 60 | if(fmt.equals(f.getFormatName())) { 61 | return f; 62 | } 63 | } 64 | return null; 65 | } 66 | 67 | public ShapeReader getWktReader() { 68 | return wktReader; 69 | } 70 | 71 | public ShapeWriter getWktWriter() { 72 | return wktWriter; 73 | } 74 | 75 | public ShapeReader getGeoJsonReader() { 76 | return geoJsonReader; 77 | } 78 | 79 | public ShapeWriter getGeoJsonWriter() { 80 | return geoJsonWriter; 81 | } 82 | 83 | public Shape read(String value) { 84 | for(ShapeReader format : readers) { 85 | Shape v = format.readIfSupported(value); 86 | if(v!=null) { 87 | return v; 88 | } 89 | } 90 | return null; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/WKTWriter.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io; 10 | 11 | import java.io.IOException; 12 | import java.io.Writer; 13 | import java.math.RoundingMode; 14 | import java.text.NumberFormat; 15 | import java.util.Iterator; 16 | import org.locationtech.spatial4j.shape.*; 17 | import org.locationtech.spatial4j.shape.impl.BufferedLine; 18 | import org.locationtech.spatial4j.shape.impl.BufferedLineString; 19 | 20 | public class WKTWriter implements ShapeWriter { 21 | 22 | @Override 23 | public String getFormatName() { 24 | return ShapeIO.WKT; 25 | } 26 | 27 | 28 | protected StringBuilder append(StringBuilder buffer, Point p, NumberFormat nf) { 29 | return buffer.append(nf.format(p.getX())).append(' ').append( nf.format(p.getY())); 30 | } 31 | 32 | protected NumberFormat getNumberFormat() { 33 | return LegacyShapeWriter.makeNumberFormat(6); 34 | } 35 | 36 | @Override 37 | public String toString(Shape shape) { 38 | NumberFormat nf = getNumberFormat(); 39 | if (shape instanceof Point) { 40 | Point point = (Point)shape; 41 | if (point.isEmpty()) { 42 | return "POINT EMPTY"; 43 | } 44 | 45 | StringBuilder buffer = new StringBuilder(); 46 | return append(buffer.append("POINT ("), point, nf).append(")").toString(); 47 | } 48 | if (shape instanceof Rectangle) { 49 | NumberFormat nfMIN = nf; 50 | NumberFormat nfMAX = LegacyShapeWriter.makeNumberFormat(6); 51 | 52 | nfMIN.setRoundingMode( RoundingMode.FLOOR ); 53 | nfMAX.setRoundingMode( RoundingMode.CEILING ); 54 | 55 | Rectangle rect = (Rectangle)shape; 56 | return "ENVELOPE (" + 57 | // '(' x1 ',' x2 ',' y2 ',' y1 ')' 58 | nfMIN.format(rect.getMinX()) + ", " + nfMAX.format(rect.getMaxX()) + ", "+ 59 | nfMAX.format(rect.getMaxY()) + ", " + nfMIN.format(rect.getMinY()) + ")"; 60 | // 61 | // return "POLYGON(( "+ 62 | // nf.format(rect.getMinX()) + " " + nf.format(rect.getMinY()) + ", "+ 63 | // nf.format(rect.getMinX()) + " " + nf.format(rect.getMaxY()) + ", "+ 64 | // nf.format(rect.getMaxX()) + " " + nf.format(rect.getMaxY()) + ", "+ 65 | // nf.format(rect.getMaxX()) + " " + nf.format(rect.getMinY()) + ", "+ 66 | // nf.format(rect.getMinX()) + " " + nf.format(rect.getMinY()) + "))"; 67 | } 68 | if (shape instanceof Circle) { 69 | Circle c = (Circle) shape; 70 | 71 | StringBuilder str = new StringBuilder(); 72 | str.append("BUFFER (POINT (") 73 | .append(nf.format(c.getCenter().getX())).append(" ") 74 | .append(nf.format(c.getCenter().getY())) 75 | .append("), ") 76 | .append(nf.format(c.getRadius())) 77 | .append(")"); 78 | return str.toString(); 79 | } 80 | if (shape instanceof BufferedLineString) { 81 | BufferedLineString line = (BufferedLineString) shape; 82 | StringBuilder str = new StringBuilder(); 83 | 84 | double buf = line.getBuf(); 85 | if (buf > 0d) { 86 | str.append("BUFFER ("); 87 | } 88 | 89 | str.append("LINESTRING ("); 90 | Iterator iter = line.getSegments().iterator(); 91 | while(iter.hasNext()) { 92 | BufferedLine seg = iter.next(); 93 | append(str,seg.getA(),nf).append(", "); 94 | if(!iter.hasNext()) { 95 | append(str,seg.getB(),nf); 96 | } 97 | } 98 | str.append(")"); 99 | 100 | if (buf > 0d) { 101 | str.append(", ").append(nf.format(buf)).append(")"); 102 | } 103 | return str.toString(); 104 | } 105 | if(shape instanceof ShapeCollection) { 106 | @SuppressWarnings("unchecked") 107 | ShapeCollection collection = (ShapeCollection) shape; 108 | 109 | if (collection.isEmpty()) { 110 | return "GEOMETRYCOLLECTION EMPTY"; 111 | } 112 | 113 | StringBuilder buffer = new StringBuilder(); 114 | buffer.append("GEOMETRYCOLLECTION ("); 115 | boolean first = true; 116 | for (Shape sub : collection.getShapes()) { 117 | if(!first) { 118 | buffer.append(","); 119 | } 120 | buffer.append(toString(sub)); 121 | first = false; 122 | } 123 | buffer.append(")"); 124 | return buffer.toString(); 125 | } 126 | return LegacyShapeWriter.writeShape(shape, nf); 127 | } 128 | 129 | @Override 130 | public void write(Writer output, Shape shape) throws IOException { 131 | output.append( toString(shape) ); 132 | } 133 | } -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/WktShapeParser.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 ElasticSearch and MITRE, and others 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | // A derivative of commit 14bc4dee08355048d6a94e33834b919a3999a06e 10 | // at https://github.com/chrismale/elasticsearch 11 | 12 | package org.locationtech.spatial4j.io; 13 | 14 | import org.locationtech.spatial4j.context.SpatialContext; 15 | import org.locationtech.spatial4j.context.SpatialContextFactory; 16 | 17 | @Deprecated 18 | public class WktShapeParser extends WKTReader { 19 | 20 | public WktShapeParser(SpatialContext ctx, SpatialContextFactory factory) { 21 | super(ctx,factory); 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/jackson/GeometryAsGeoJSONSerializer.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch and others 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jackson; 10 | 11 | import java.io.IOException; 12 | import com.fasterxml.jackson.core.JsonGenerator; 13 | import com.fasterxml.jackson.core.JsonProcessingException; 14 | import com.fasterxml.jackson.databind.JsonSerializer; 15 | import com.fasterxml.jackson.databind.SerializerProvider; 16 | import org.locationtech.jts.geom.Coordinate; 17 | import org.locationtech.jts.geom.CoordinateSequence; 18 | import org.locationtech.jts.geom.Geometry; 19 | import org.locationtech.jts.geom.GeometryCollection; 20 | import org.locationtech.jts.geom.LineString; 21 | import org.locationtech.jts.geom.MultiLineString; 22 | import org.locationtech.jts.geom.MultiPoint; 23 | import org.locationtech.jts.geom.MultiPolygon; 24 | import org.locationtech.jts.geom.Point; 25 | import org.locationtech.jts.geom.Polygon; 26 | 27 | public class GeometryAsGeoJSONSerializer extends JsonSerializer 28 | { 29 | // -------------------------------------------------------------- 30 | // Write JTS To GeoJSON 31 | // -------------------------------------------------------------- 32 | 33 | protected void write(JsonGenerator gen, Coordinate coord) throws IOException { 34 | gen.writeStartArray(); 35 | gen.writeNumber(coord.x); 36 | gen.writeNumber(coord.y); 37 | gen.writeEndArray(); 38 | } 39 | 40 | protected void write(JsonGenerator gen, CoordinateSequence coordseq) throws IOException { 41 | gen.writeStartArray(); 42 | int dim = coordseq.getDimension(); 43 | for (int i = 0; i < coordseq.size(); i++) { 44 | gen.writeStartArray(); 45 | gen.writeNumber(coordseq.getOrdinate(i, 0)); 46 | gen.writeNumber(coordseq.getOrdinate(i, 1)); 47 | if (dim > 2) { 48 | double v = coordseq.getOrdinate(i, 2); 49 | if (!Double.isNaN(v)) { 50 | gen.writeNumber(v); 51 | } 52 | } 53 | gen.writeEndArray(); 54 | } 55 | gen.writeEndArray(); 56 | } 57 | 58 | protected void write(JsonGenerator gen, Coordinate[] coord) throws IOException { 59 | gen.writeStartArray(); 60 | for (int i = 0; i < coord.length; i++) { 61 | write(gen, coord[i]); 62 | } 63 | gen.writeEndArray(); 64 | } 65 | 66 | protected void write(JsonGenerator gen, Polygon p) throws IOException { 67 | gen.writeStartArray(); 68 | write(gen, p.getExteriorRing().getCoordinateSequence()); 69 | for (int i = 0; i < p.getNumInteriorRing(); i++) { 70 | write(gen, p.getInteriorRingN(i).getCoordinateSequence()); 71 | } 72 | gen.writeEndArray(); 73 | } 74 | 75 | @Override 76 | public void serialize(Geometry geom, JsonGenerator gen, SerializerProvider serializers) 77 | throws IOException, JsonProcessingException 78 | { 79 | gen.writeStartObject(); 80 | gen.writeFieldName("type"); 81 | gen.writeString(geom.getClass().getSimpleName()); 82 | 83 | if (geom instanceof Point) { 84 | Point v = (Point) geom; 85 | gen.writeFieldName("coordinates"); 86 | write(gen, v.getCoordinate()); 87 | } else if (geom instanceof Polygon) { 88 | gen.writeFieldName("coordinates"); 89 | write(gen, (Polygon) geom); 90 | } else if (geom instanceof LineString) { 91 | LineString v = (LineString) geom; 92 | gen.writeFieldName("coordinates"); 93 | write(gen, v.getCoordinateSequence()); 94 | } else if (geom instanceof MultiPoint) { 95 | MultiPoint v = (MultiPoint) geom; 96 | gen.writeFieldName("coordinates"); 97 | write(gen, v.getCoordinates()); 98 | } else if (geom instanceof MultiLineString) { 99 | MultiLineString v = (MultiLineString) geom; 100 | gen.writeFieldName("coordinates"); 101 | gen.writeStartArray(); 102 | for (int i = 0; i < v.getNumGeometries(); i++) { 103 | write(gen, v.getGeometryN(i).getCoordinates()); 104 | } 105 | gen.writeEndArray(); 106 | } else if (geom instanceof MultiPolygon) { 107 | MultiPolygon v = (MultiPolygon) geom; 108 | gen.writeFieldName("coordinates"); 109 | gen.writeStartArray(); 110 | for (int i = 0; i < v.getNumGeometries(); i++) { 111 | write(gen, (Polygon) v.getGeometryN(i)); 112 | } 113 | gen.writeEndArray(); 114 | } else if (geom instanceof GeometryCollection) { 115 | GeometryCollection v = (GeometryCollection) geom; 116 | gen.writeFieldName("geometries"); 117 | gen.writeStartArray(); 118 | for (int i = 0; i < v.getNumGeometries(); i++) { 119 | serialize(v.getGeometryN(i), gen, serializers); 120 | } 121 | gen.writeEndArray(); 122 | } else { 123 | throw new UnsupportedOperationException("unknown: " + geom); 124 | } 125 | gen.writeEndObject(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/jackson/GeometryAsWKTSerializer.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch and others 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jackson; 10 | 11 | import java.io.IOException; 12 | 13 | import com.fasterxml.jackson.core.JsonGenerator; 14 | import com.fasterxml.jackson.core.JsonProcessingException; 15 | import com.fasterxml.jackson.databind.JsonSerializer; 16 | import com.fasterxml.jackson.databind.SerializerProvider; 17 | import org.locationtech.jts.geom.Geometry; 18 | 19 | public class GeometryAsWKTSerializer extends JsonSerializer 20 | { 21 | @Override 22 | public void serialize(Geometry value, JsonGenerator gen, 23 | SerializerProvider serializers) throws IOException, 24 | JsonProcessingException { 25 | 26 | gen.writeString(value.toText()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/jackson/GeometryDeserializer.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch and others 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jackson; 10 | 11 | import java.io.IOException; 12 | 13 | import org.locationtech.spatial4j.context.jts.JtsSpatialContext; 14 | import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; 15 | import org.locationtech.spatial4j.shape.Shape; 16 | 17 | import com.fasterxml.jackson.core.JsonParser; 18 | import com.fasterxml.jackson.core.JsonProcessingException; 19 | import com.fasterxml.jackson.databind.DeserializationContext; 20 | import com.fasterxml.jackson.databind.JsonDeserializer; 21 | import org.locationtech.jts.geom.Geometry; 22 | 23 | public class GeometryDeserializer extends JsonDeserializer 24 | { 25 | // Create a context that will allow any JTS shape 26 | static final JtsSpatialContext JTS; 27 | static { 28 | JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); 29 | factory.geo = false; 30 | factory.useJtsLineString = true; 31 | factory.useJtsMulti = true; 32 | factory.useJtsPoint = true; 33 | JTS = new JtsSpatialContext(factory); 34 | } 35 | 36 | final ShapeDeserializer dser; 37 | 38 | public GeometryDeserializer() { 39 | dser = new ShapeDeserializer(JTS); 40 | } 41 | 42 | @Override 43 | public Geometry deserialize(JsonParser jp, DeserializationContext ctxt) 44 | throws IOException, JsonProcessingException { 45 | Shape shape = dser.deserialize(jp, ctxt); 46 | if(shape!=null) { 47 | return JTS.getShapeFactory().getGeometryFrom(shape); 48 | } 49 | return null; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/jackson/PackageVersion.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch and others 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jackson; 10 | 11 | import com.fasterxml.jackson.core.Version; 12 | import com.fasterxml.jackson.core.Versioned; 13 | import com.fasterxml.jackson.core.util.VersionUtil; 14 | 15 | /** 16 | * Automatically generated from PackageVersion.java.in during 17 | * packageVersion-generate execution of maven-replacer-plugin in 18 | * pom.xml. 19 | */ 20 | public final class PackageVersion implements Versioned { 21 | public final static Version VERSION = VersionUtil.parseVersion( 22 | "0.9-SNAPSHOT", "org.locationtech.spatial4j", "spatial4j"); 23 | 24 | @Override 25 | public Version version() { 26 | return VERSION; 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/jackson/PackageVersion.java.in: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch and others 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jackson; 10 | 11 | import com.fasterxml.jackson.core.Version; 12 | import com.fasterxml.jackson.core.Versioned; 13 | import com.fasterxml.jackson.core.util.VersionUtil; 14 | 15 | /** 16 | * Automatically generated from PackageVersion.java.in during 17 | * packageVersion-generate execution of maven-replacer-plugin in 18 | * pom.xml. 19 | */ 20 | public final class PackageVersion implements Versioned { 21 | public final static Version VERSION = VersionUtil.parseVersion( 22 | "@projectversion@", "@projectgroupid@", "@projectartifactid@"); 23 | 24 | @Override 25 | public Version version() { 26 | return VERSION; 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/jackson/ShapeAsWKTSerializer.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch and others 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jackson; 10 | 11 | import java.io.IOException; 12 | import java.io.StringWriter; 13 | 14 | import org.locationtech.spatial4j.shape.Shape; 15 | 16 | import com.fasterxml.jackson.core.JsonGenerator; 17 | import com.fasterxml.jackson.core.JsonProcessingException; 18 | import com.fasterxml.jackson.databind.JsonSerializer; 19 | import com.fasterxml.jackson.databind.SerializerProvider; 20 | 21 | public class ShapeAsWKTSerializer extends JsonSerializer 22 | { 23 | @Override 24 | public void serialize(Shape value, JsonGenerator gen, 25 | SerializerProvider serializers) throws IOException, 26 | JsonProcessingException { 27 | 28 | StringWriter str = new StringWriter(); 29 | value.getContext().getFormats().getWktWriter().write(str, value); 30 | gen.writeString(str.toString()); 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/jackson/ShapesAsGeoJSONModule.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch and others 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jackson; 10 | 11 | import org.locationtech.spatial4j.shape.Shape; 12 | 13 | import com.fasterxml.jackson.databind.module.SimpleModule; 14 | import org.locationtech.jts.geom.Geometry; 15 | 16 | public class ShapesAsGeoJSONModule extends SimpleModule 17 | { 18 | private static final long serialVersionUID = 1L; 19 | 20 | public ShapesAsGeoJSONModule() 21 | { 22 | super(PackageVersion.VERSION); 23 | // first deserializers 24 | addDeserializer(Geometry.class, new GeometryDeserializer()); 25 | addDeserializer(Shape.class, new ShapeDeserializer()); 26 | 27 | // then serializers: 28 | addSerializer(Geometry.class, new GeometryAsGeoJSONSerializer()); 29 | addSerializer(Shape.class, new ShapeAsGeoJSONSerializer()); 30 | } 31 | 32 | // will try to avoid duplicate registations (if MapperFeature enabled) 33 | @Override 34 | public String getModuleName() { 35 | return getClass().getSimpleName(); 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return getClass().hashCode(); 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | return this == o; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/jackson/ShapesAsWKTModule.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch and others 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jackson; 10 | 11 | import org.locationtech.spatial4j.shape.Shape; 12 | 13 | import com.fasterxml.jackson.databind.module.SimpleModule; 14 | import org.locationtech.jts.geom.Geometry; 15 | 16 | public class ShapesAsWKTModule extends SimpleModule 17 | { 18 | private static final long serialVersionUID = 1L; 19 | 20 | public ShapesAsWKTModule() 21 | { 22 | super(PackageVersion.VERSION); 23 | // first deserializers 24 | addDeserializer(Geometry.class, new GeometryDeserializer()); 25 | addDeserializer(Shape.class, new ShapeDeserializer()); 26 | 27 | // then serializers: 28 | addSerializer(Geometry.class, new GeometryAsWKTSerializer()); 29 | addSerializer(Shape.class, new ShapeAsWKTSerializer()); 30 | } 31 | 32 | // will try to avoid duplicate registations (if MapperFeature enabled) 33 | @Override 34 | public String getModuleName() { 35 | return getClass().getSimpleName(); 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return getClass().hashCode(); 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | return this == o; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/jackson/package-info.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch and others 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | /** 10 | * Optional support to read/write Shapes and Geometry using Jackson 11 | */ 12 | package org.locationtech.spatial4j.io.jackson; 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/jts/JtsBinaryCodec.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jts; 10 | 11 | import org.locationtech.spatial4j.context.jts.JtsSpatialContext; 12 | import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; 13 | import org.locationtech.spatial4j.exception.InvalidShapeException; 14 | import org.locationtech.spatial4j.io.BinaryCodec; 15 | import org.locationtech.spatial4j.shape.Shape; 16 | import org.locationtech.jts.geom.Geometry; 17 | import org.locationtech.jts.geom.PrecisionModel; 18 | import org.locationtech.jts.io.InStream; 19 | import org.locationtech.jts.io.OutStream; 20 | import org.locationtech.jts.io.ParseException; 21 | import org.locationtech.jts.io.WKBConstants; 22 | import org.locationtech.jts.io.WKBReader; 23 | import org.locationtech.jts.io.WKBWriter; 24 | 25 | import java.io.DataInput; 26 | import java.io.DataOutput; 27 | import java.io.IOException; 28 | 29 | /** 30 | * Writes shapes in WKB, if it isn't otherwise supported by the superclass. 31 | */ 32 | public class JtsBinaryCodec extends BinaryCodec { 33 | 34 | protected final boolean useFloat;//instead of double 35 | 36 | public JtsBinaryCodec(JtsSpatialContext ctx, JtsSpatialContextFactory factory) { 37 | super(ctx, factory); 38 | //note: ctx.geometryFactory hasn't been set yet 39 | useFloat = (factory.precisionModel.getType() == PrecisionModel.FLOATING_SINGLE); 40 | } 41 | 42 | @Override 43 | protected double readDim(DataInput dataInput) throws IOException { 44 | if (useFloat) 45 | return dataInput.readFloat(); 46 | return super.readDim(dataInput); 47 | } 48 | 49 | @Override 50 | protected void writeDim(DataOutput dataOutput, double v) throws IOException { 51 | if (useFloat) 52 | dataOutput.writeFloat((float) v); 53 | else 54 | super.writeDim(dataOutput, v); 55 | } 56 | 57 | @Override 58 | protected byte typeForShape(Shape s) { 59 | byte type = super.typeForShape(s); 60 | if (type == 0) { 61 | type = TYPE_GEOM;//handles everything 62 | } 63 | return type; 64 | } 65 | 66 | @Override 67 | protected Shape readShapeByTypeIfSupported(final DataInput dataInput, byte type) throws IOException { 68 | if (type != TYPE_GEOM) 69 | return super.readShapeByTypeIfSupported(dataInput, type); 70 | return readJtsGeom(dataInput); 71 | } 72 | 73 | @Override 74 | protected boolean writeShapeByTypeIfSupported(DataOutput dataOutput, Shape s, byte type) throws IOException { 75 | if (type != TYPE_GEOM) 76 | return super.writeShapeByTypeIfSupported(dataOutput, s, type); 77 | writeJtsGeom(dataOutput, s); 78 | return true; 79 | } 80 | 81 | public Shape readJtsGeom(final DataInput dataInput) throws IOException { 82 | JtsSpatialContext ctx = (JtsSpatialContext)super.ctx; 83 | WKBReader reader = new WKBReader(ctx.getGeometryFactory()); 84 | try { 85 | InStream inStream = new InStream() {//a strange JTS abstraction 86 | boolean first = true; 87 | @Override 88 | public int read(byte[] buf) throws IOException { 89 | if (first) {//we don't write JTS's leading BOM so synthesize reading it 90 | if (buf.length != 1) 91 | throw new IllegalStateException("Expected initial read of one byte, not: " + buf.length); 92 | buf[0] = WKBConstants.wkbXDR;//0 93 | first = false; 94 | return 1; 95 | } else { 96 | //TODO for performance, specialize for common array lengths: 1, 4, 8 97 | dataInput.readFully(buf); 98 | return buf.length; 99 | } 100 | } 101 | }; 102 | Geometry geom = reader.read(inStream); 103 | //false: don't check for dateline-180 cross or multi-polygon overlaps; this won't happen 104 | // once it gets written, and we're reading it now 105 | return ctx.makeShape(geom, false, false); 106 | } catch (ParseException ex) { 107 | throw new InvalidShapeException("error reading WKT", ex); 108 | } 109 | } 110 | 111 | public void writeJtsGeom(final DataOutput dataOutput, Shape s) throws IOException { 112 | JtsSpatialContext ctx = (JtsSpatialContext)super.ctx; 113 | Geometry geom = ctx.getGeometryFrom(s);//might even translate it 114 | new WKBWriter().write(geom, new OutStream() {//a strange JTS abstraction 115 | boolean first = true; 116 | @Override 117 | public void write(byte[] buf, int len) throws IOException { 118 | if (first) { 119 | first = false; 120 | //skip byte order mark 121 | if (len != 1 || buf[0] != WKBConstants.wkbXDR)//the default 122 | throw new IllegalStateException("Unexpected WKB byte order mark"); 123 | return; 124 | } 125 | dataOutput.write(buf, 0, len); 126 | } 127 | }); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/jts/JtsPolyshapeWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.locationtech.spatial4j.io.jts; 19 | 20 | import java.io.IOException; 21 | 22 | import org.locationtech.spatial4j.context.SpatialContextFactory; 23 | import org.locationtech.spatial4j.context.jts.JtsSpatialContext; 24 | import org.locationtech.spatial4j.io.PolyshapeWriter; 25 | import org.locationtech.spatial4j.shape.Shape; 26 | import org.locationtech.spatial4j.shape.jts.JtsGeometry; 27 | import org.locationtech.jts.geom.Coordinate; 28 | import org.locationtech.jts.geom.CoordinateSequence; 29 | import org.locationtech.jts.geom.Geometry; 30 | import org.locationtech.jts.geom.GeometryCollection; 31 | import org.locationtech.jts.geom.LineString; 32 | import org.locationtech.jts.geom.MultiPoint; 33 | import org.locationtech.jts.geom.Point; 34 | import org.locationtech.jts.geom.Polygon; 35 | 36 | public class JtsPolyshapeWriter extends PolyshapeWriter { 37 | 38 | protected final JtsSpatialContext ctx; 39 | 40 | public JtsPolyshapeWriter(JtsSpatialContext ctx, SpatialContextFactory factory) { 41 | super(ctx, factory); 42 | this.ctx = ctx; 43 | } 44 | 45 | // -------------------------------------------------------------- 46 | // Write JTS To GeoJSON 47 | // -------------------------------------------------------------- 48 | 49 | protected void write(Encoder output, CoordinateSequence coordseq) throws IOException { 50 | // int dim = coordseq.getDimension(); 51 | // if(dim>2) { 52 | // throw new IllegalArgumentException("only supports 2d geometry now ("+dim+")"); 53 | // } 54 | for (int i = 0; i < coordseq.size(); i++) { 55 | output.write(coordseq.getOrdinate(i, 0), 56 | coordseq.getOrdinate(i, 1)); 57 | } 58 | } 59 | 60 | protected void write(Encoder output, Coordinate[] coord) throws IOException { 61 | for (int i = 0; i < coord.length; i++) { 62 | output.write(coord[i].x, coord[i].y); 63 | } 64 | } 65 | 66 | protected void write(Encoder output, Polygon p) throws IOException { 67 | output.write(PolyshapeWriter.KEY_POLYGON); 68 | write(output, p.getExteriorRing().getCoordinateSequence()); 69 | for (int i = 0; i < p.getNumInteriorRing(); i++) { 70 | output.startRing(); 71 | write(output, p.getInteriorRingN(i).getCoordinateSequence()); 72 | } 73 | } 74 | 75 | public void write(Encoder output, Geometry geom) throws IOException { 76 | if (geom instanceof Point) { 77 | Point v = (Point) geom; 78 | output.write(PolyshapeWriter.KEY_POINT); 79 | write(output, v.getCoordinateSequence()); 80 | } else if (geom instanceof Polygon) { 81 | write(output, (Polygon) geom); 82 | } else if (geom instanceof LineString) { 83 | LineString v = (LineString) geom; 84 | output.write(PolyshapeWriter.KEY_LINE); 85 | write(output, v.getCoordinateSequence()); 86 | } else if (geom instanceof MultiPoint) { 87 | MultiPoint v = (MultiPoint) geom; 88 | output.write(PolyshapeWriter.KEY_MULTIPOINT); 89 | write(output, v.getCoordinates()); 90 | } else if (geom instanceof GeometryCollection) { 91 | GeometryCollection v = (GeometryCollection) geom; 92 | for (int i = 0; i < v.getNumGeometries(); i++) { 93 | if (i > 0) { 94 | output.seperator(); 95 | } 96 | write(output, v.getGeometryN(i)); 97 | } 98 | } else { 99 | throw new UnsupportedOperationException("unknown: " + geom); 100 | } 101 | } 102 | 103 | @Override 104 | public void write(Encoder enc, Shape shape) throws IOException { 105 | if (shape == null) { 106 | throw new NullPointerException("Shape can not be null"); 107 | } 108 | if (shape instanceof JtsGeometry) { 109 | write(enc, ((JtsGeometry) shape).getGeom()); 110 | return; 111 | } 112 | super.write(enc, shape); 113 | } 114 | } -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/jts/JtsWKTReaderShapeParser.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jts; 10 | 11 | import org.locationtech.spatial4j.context.jts.JtsSpatialContext; 12 | import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; 13 | import org.locationtech.spatial4j.distance.DistanceUtils; 14 | import org.locationtech.spatial4j.exception.InvalidShapeException; 15 | import org.locationtech.spatial4j.shape.Shape; 16 | import org.locationtech.spatial4j.shape.jts.JtsPoint; 17 | import org.locationtech.spatial4j.shape.jts.JtsShapeFactory; 18 | import org.locationtech.jts.geom.CoordinateSequence; 19 | import org.locationtech.jts.geom.CoordinateSequenceFilter; 20 | import org.locationtech.jts.geom.Geometry; 21 | import org.locationtech.jts.io.WKTReader; 22 | 23 | import java.text.ParseException; 24 | 25 | /** 26 | * This is an extension of Spatial4j's {@link org.locationtech.spatial4j.io.WKTReader} that processes the entire 27 | * string with JTS's {@link org.locationtech.jts.io.WKTReader}. Some differences: 28 | *
    29 | *
  • No support for ENVELOPE and BUFFER
  • 30 | *
  • MULTI* shapes use JTS's {@link org.locationtech.jts.geom.GeometryCollection} subclasses, 31 | * not {@link org.locationtech.spatial4j.shape.ShapeCollection}
  • 32 | *
  • 'Z' coordinates are saved into the geometry
  • 33 | *
34 | * 35 | */ 36 | @Deprecated 37 | public class JtsWKTReaderShapeParser extends org.locationtech.spatial4j.io.WKTReader { 38 | 39 | //Note: Historically, the code here originated from the defunct JtsShapeReadWriter. 40 | 41 | public JtsWKTReaderShapeParser(JtsSpatialContext ctx, JtsSpatialContextFactory factory) { 42 | super(ctx, factory); 43 | } 44 | 45 | @Override 46 | public Shape parseIfSupported(String wktString) throws ParseException { 47 | return parseIfSupported(wktString, new WKTReader(getShapeFactory().getGeometryFactory())); 48 | } 49 | 50 | private JtsShapeFactory getShapeFactory() { 51 | return ((JtsShapeFactory)shapeFactory); 52 | } 53 | 54 | /** 55 | * Reads WKT from the {@code str} via JTS's {@link org.locationtech.jts.io.WKTReader}. 56 | * 57 | * @param reader
new WKTReader(ctx.getGeometryFactory()))
58 | * @return Non-Null 59 | */ 60 | protected Shape parseIfSupported(String str, WKTReader reader) throws ParseException { 61 | try { 62 | Geometry geom = reader.read(str); 63 | 64 | //Normalizes & verifies coordinates 65 | checkCoordinates(geom); 66 | 67 | if (geom instanceof org.locationtech.jts.geom.Point) { 68 | org.locationtech.jts.geom.Point ptGeom = (org.locationtech.jts.geom.Point) geom; 69 | if (getShapeFactory().useJtsPoint()) 70 | return new JtsPoint(ptGeom, (JtsSpatialContext) ctx); 71 | else 72 | return getShapeFactory().pointXY(ptGeom.getX(), ptGeom.getY()); 73 | } else if (geom.isRectangle()) { 74 | return getShapeFactory().makeRectFromRectangularPoly(geom); 75 | } else { 76 | return getShapeFactory().makeShapeFromGeometry(geom); 77 | } 78 | } catch (InvalidShapeException e) { 79 | throw e; 80 | } catch (Exception e) { 81 | throw new InvalidShapeException("error reading WKT: "+e.toString(), e); 82 | } 83 | } 84 | 85 | 86 | protected void checkCoordinates(Geometry geom) { 87 | // note: JTS WKTReader has already normalized coords with the JTS PrecisionModel. 88 | geom.apply(new CoordinateSequenceFilter() { 89 | boolean changed = false; 90 | @Override 91 | public void filter(CoordinateSequence seq, int i) { 92 | double x = seq.getX(i); 93 | double y = seq.getY(i); 94 | 95 | //Note: we don't simply call ctx.normX & normY because 96 | // those methods use the precisionModel, but WKTReader already 97 | // used the precisionModel. It's be nice to turn that off somehow but alas. 98 | if (ctx.isGeo() && ctx.isNormWrapLongitude()) { 99 | double xNorm = DistanceUtils.normLonDEG(x); 100 | if (Double.compare(x, xNorm) != 0) {//handles NaN 101 | changed = true; 102 | seq.setOrdinate(i, CoordinateSequence.X, xNorm); 103 | } 104 | // double yNorm = DistanceUtils.normLatDEG(y); 105 | // if (y != yNorm) { 106 | // changed = true; 107 | // seq.setOrdinate(i,CoordinateSequence.Y,yNorm); 108 | // } 109 | } 110 | ctx.verifyX(x); 111 | ctx.verifyY(y); 112 | } 113 | 114 | @Override 115 | public boolean isDone() { return false; } 116 | 117 | @Override 118 | public boolean isGeometryChanged() { return changed; } 119 | }); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/jts/JtsWKTWriter.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jts; 10 | 11 | import org.locationtech.spatial4j.context.jts.JtsSpatialContext; 12 | import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; 13 | import org.locationtech.spatial4j.io.WKTWriter; 14 | import org.locationtech.spatial4j.shape.Shape; 15 | import org.locationtech.spatial4j.shape.jts.JtsGeometry; 16 | 17 | /** 18 | * Writes the WKT using JTS directly 19 | */ 20 | public class JtsWKTWriter extends WKTWriter { 21 | 22 | public JtsWKTWriter(JtsSpatialContext ctx, JtsSpatialContextFactory factory) { 23 | 24 | } 25 | 26 | @Override 27 | public String toString(Shape shape) { 28 | if (shape instanceof JtsGeometry) { 29 | return ((JtsGeometry) shape).getGeom().toText(); 30 | } 31 | return super.toString(shape); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/io/package-info.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | /** Reading & writing shapes in various forms. */ 10 | package org.locationtech.spatial4j.io; -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/package-info.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | /** 10 | * This is the base package for Spatial4j from which the rest of it is organized. 11 | */ 12 | package org.locationtech.spatial4j; 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/shape/BaseShape.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.shape; 10 | 11 | import org.locationtech.spatial4j.context.SpatialContext; 12 | 13 | public abstract class BaseShape implements Shape { 14 | 15 | protected final T ctx; 16 | 17 | public BaseShape(T ctx) { 18 | this.ctx = ctx; 19 | } 20 | 21 | @Override 22 | public T getContext() { 23 | return ctx; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/shape/Circle.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.shape; 10 | 11 | /** 12 | * A circle, also known as a point-radius since that is what it is comprised of. 13 | */ 14 | public interface Circle extends Shape { 15 | 16 | /** 17 | * Expert: Resets the state of this shape given the arguments. This is a 18 | * performance feature to avoid excessive Shape object allocation as well as 19 | * some argument error checking. Mutable shapes is error-prone so use with 20 | * care. 21 | */ 22 | void reset(double x, double y, double radiusDEG); 23 | 24 | /** 25 | * The distance from the point's center to its edge, measured in the same 26 | * units as x & y (e.g. degrees if WGS84). 27 | */ 28 | double getRadius(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/shape/Point.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.shape; 10 | 11 | /** 12 | * A Point with X & Y coordinates. 13 | */ 14 | public interface Point extends Shape { 15 | 16 | /** 17 | * Expert: Resets the state of this shape given the arguments. This is a 18 | * performance feature to avoid excessive Shape object allocation as well as 19 | * some argument error checking. Mutable shapes is error-prone so use with 20 | * care. 21 | */ 22 | public void reset(double x, double y); 23 | 24 | /** The X coordinate, or Longitude in geospatial contexts. */ 25 | public double getX(); 26 | 27 | /** The Y coordinate, or Latitude in geospatial contexts. */ 28 | public double getY(); 29 | 30 | /** Convenience method that usually maps on {@link org.locationtech.spatial4j.shape.Point#getY()} */ 31 | public default double getLat() { 32 | return getY(); 33 | } 34 | 35 | /** Convenience method that usually maps on {@link org.locationtech.spatial4j.shape.Point#getX()} */ 36 | public default double getLon() { 37 | return getX(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/shape/Rectangle.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.shape; 10 | 11 | /** 12 | * A rectangle aligned with the axis (i.e. it is not at an angle). 13 | *

14 | * In geospatial contexts, it may cross the international date line (-180 15 | * longitude) if {@link #getCrossesDateLine()} however it cannot pass the poles 16 | * although it may span the globe. It spans the globe if the X coordinate 17 | * (Longitude) goes from -180 to 180 as seen from {@link #getMinX()} and {@link 18 | * #getMaxX()}. 19 | */ 20 | public interface Rectangle extends Shape { 21 | 22 | /** 23 | * Expert: Resets the state of this shape given the arguments. This is a 24 | * performance feature to avoid excessive Shape object allocation as well as 25 | * some argument error checking. Mutable shapes is error-prone so use with 26 | * care. 27 | */ 28 | public void reset(double minX, double maxX, double minY, double maxY); 29 | 30 | /** 31 | * The width. In geospatial contexts, this is generally in degrees longitude 32 | * and is aware of the dateline (aka anti-meridian). It will always be >= 0. 33 | */ 34 | public double getWidth(); 35 | 36 | /** 37 | * The height. In geospatial contexts, this is in degrees latitude. It will 38 | * always be >= 0. 39 | */ 40 | public double getHeight(); 41 | 42 | /** The left edge of the X coordinate. */ 43 | public double getMinX(); 44 | 45 | /** The bottom edge of the Y coordinate. */ 46 | public double getMinY(); 47 | 48 | /** The right edge of the X coordinate. */ 49 | public double getMaxX(); 50 | 51 | /** The top edge of the Y coordinate. */ 52 | public double getMaxY(); 53 | 54 | /** Only meaningful for geospatial contexts. */ 55 | public boolean getCrossesDateLine(); 56 | 57 | /** 58 | * A specialization of {@link Shape#relate(Shape)} 59 | * for a vertical line. 60 | */ 61 | public SpatialRelation relateYRange(double minY, double maxY); 62 | 63 | /** 64 | * A specialization of {@link Shape#relate(Shape)} 65 | * for a horizontal line. 66 | */ 67 | public SpatialRelation relateXRange(double minX, double maxX); 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/shape/Shape.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.shape; 10 | 11 | import org.locationtech.spatial4j.context.SpatialContext; 12 | 13 | /** 14 | * The base interface defining a geometric shape. Shape instances should be 15 | * instantiated via one of the create* methods on a {@link SpatialContext} or 16 | * by reading WKT which calls those methods; they should not be 17 | * created directly. 18 | *

19 | * Shapes are generally immutable and thread-safe. If a particular shape has a 20 | * reset(...) method then its use means the shape is actually 21 | * mutable. Mutating shape state is considered expert and should be done with care. 22 | */ 23 | public interface Shape { 24 | 25 | /** 26 | * Describe the relationship between the two objects. For example 27 | *

    28 | *
  • this is WITHIN other
  • 29 | *
  • this CONTAINS other
  • 30 | *
  • this is DISJOINT other
  • 31 | *
  • this INTERSECTS other
  • 32 | *
33 | * Note that a Shape implementation may choose to return INTERSECTS when the 34 | * true answer is WITHIN or CONTAINS for performance reasons. If a shape does 35 | * this then it must document when it does. Ideally the shape will not 36 | * do this approximation in all circumstances, just sometimes. 37 | *

38 | * If the shapes are equal then the result is CONTAINS (preferred) or WITHIN. 39 | */ 40 | SpatialRelation relate(Shape other); 41 | 42 | /** 43 | * Get the bounding box for this Shape. This means the shape is within the 44 | * bounding box and that it touches each side of the rectangle. 45 | *

46 | * Postcondition: this.getBoundingBox().relate(this) == CONTAINS 47 | */ 48 | Rectangle getBoundingBox(); 49 | 50 | /** 51 | * Does the shape have area? This will be false for points and lines. It will 52 | * also be false for shapes that normally have area but are constructed in a 53 | * degenerate case as to not have area (e.g. a circle with 0 radius or 54 | * rectangle with no height or no width). 55 | */ 56 | boolean hasArea(); 57 | 58 | /** 59 | * Calculates the area of the shape, in square-degrees. If ctx is null then 60 | * simple Euclidean calculations will be used. This figure can be an 61 | * estimate. 62 | */ 63 | double getArea(SpatialContext ctx); 64 | 65 | /** 66 | * Returns the center point of this shape. This is usually the same as 67 | * getBoundingBox().getCenter() but it doesn't have to be. 68 | *

69 | * Postcondition: this.relate(this.getCenter()) == CONTAINS 70 | */ 71 | Point getCenter(); 72 | 73 | /** 74 | * Returns a buffered version of this shape. The buffer is usually a 75 | * rounded-corner buffer, although some shapes might buffer differently. This 76 | * is an optional operation. 77 | * 78 | * @return Not null, and the returned shape should contain the current shape. 79 | */ 80 | Shape getBuffered(double distance, SpatialContext ctx); 81 | 82 | /** 83 | * Shapes can be "empty", which is to say it exists nowhere. The underlying coordinates are 84 | * typically NaN. 85 | */ 86 | boolean isEmpty(); 87 | 88 | /** The sub-classes of Shape generally implement the 89 | * same contract for {@link Object#equals(Object)} and {@link Object#hashCode()} 90 | * amongst the same sub-interface type. This means, for example, that multiple 91 | * Point implementations of different classes are equal if they share the same x 92 | * & y. */ 93 | @Override 94 | public boolean equals(Object other); 95 | 96 | 97 | /** 98 | * Get the SpatialContext that created the Shape 99 | */ 100 | public SpatialContext getContext(); 101 | } 102 | 103 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/shape/SpatialRelation.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.shape; 10 | 11 | /** 12 | * The set of spatial relationships. Naming is somewhat consistent with OGC spec 13 | * conventions as seen in SQL/MM and others. 14 | *

15 | * There is no equality case. If two Shape instances are equal then the result 16 | * might be CONTAINS (preferred) or WITHIN. Client logic may have to be aware 17 | * of this edge condition; Spatial4j testing certainly does. 18 | *

19 | * The "CONTAINS" and "WITHIN" wording here is inconsistent with OGC; these here map to OGC 20 | * "COVERS" and "COVERED BY", respectively. The distinction is in the boundaries; in Spatial4j 21 | * there is no boundary distinction -- boundaries are part of the shape as if it was an "interior", 22 | * with respect to OGC's terminology. 23 | */ 24 | public enum SpatialRelation { 25 | //see http://docs.geotools.org/latest/userguide/library/jts/dim9.html#preparedgeometry 26 | 27 | /** 28 | * The shape is within the target geometry. It's the converse of {@link #CONTAINS}. 29 | * Boundaries of shapes count too. OGC specs refer to this relation as "COVERED BY"; 30 | * WITHIN is differentiated there by not including boundaries. 31 | */ 32 | WITHIN, 33 | 34 | /** 35 | * The shape contains the target geometry. It's the converse of {@link #WITHIN}. 36 | * Boundaries of shapes count too. OGC specs refer to this relation as "COVERS"; 37 | * CONTAINS is differentiated there by not including boundaries. 38 | */ 39 | CONTAINS, 40 | 41 | /** 42 | * The shape shares no point in common with the target shape. 43 | */ 44 | DISJOINT, 45 | 46 | /** 47 | * The shape shares some points/overlap with the target shape, and the relation is 48 | * not more specifically {@link #WITHIN} or {@link #CONTAINS}. 49 | */ 50 | INTERSECTS; 51 | //Don't have these: TOUCHES, CROSSES, OVERLAPS, nor distinction between CONTAINS/COVERS 52 | 53 | /** 54 | * Given the result of shapeA.relate(shapeB), transposing that 55 | * result should yield the result of shapeB.relate(shapeA). There 56 | * is a corner case is when the shapes are equal, in which case actually 57 | * flipping the relate() call will result in the same value -- either CONTAINS 58 | * or WITHIN; this method can't possible check for that so the caller might 59 | * have to. 60 | */ 61 | public SpatialRelation transpose() { 62 | switch(this) { 63 | case CONTAINS: return SpatialRelation.WITHIN; 64 | case WITHIN: return SpatialRelation.CONTAINS; 65 | default: return this; 66 | } 67 | } 68 | 69 | /** 70 | * If you were to call aShape.relate(bShape) and aShape.relate(cShape), you 71 | * could call this to merge the intersect results as if bShape & cShape were 72 | * combined into {@link ShapeCollection}. If {@code other} is null then the 73 | * result is "this". 74 | */ 75 | public SpatialRelation combine(SpatialRelation other) { 76 | // You can think of this algorithm as a state transition / automata. 77 | // 1. The answer must be the same no matter what the order is. 78 | // 2. If any INTERSECTS, then the result is INTERSECTS (done). 79 | // 3. A DISJOINT + WITHIN == INTERSECTS (done). 80 | // 4. A DISJOINT + CONTAINS == CONTAINS. 81 | // 5. A CONTAINS + WITHIN == INTERSECTS (done). (weird scenario) 82 | // 6. X + X == X.) 83 | // 7. X + null == X; 84 | if (other == this || other == null) 85 | return this; 86 | if (this == DISJOINT && other == CONTAINS 87 | || this == CONTAINS && other == DISJOINT) 88 | return CONTAINS; 89 | return INTERSECTS; 90 | } 91 | 92 | /** Not DISJOINT, i.e. there is some sort of intersection. */ 93 | public boolean intersects() { 94 | return this != DISJOINT; 95 | } 96 | 97 | /** 98 | * If aShape.relate(bShape) is r, then r.inverse() 99 | * is inverse(aShape).relate(bShape) whereas 100 | * inverse(shape) is theoretically the opposite area covered by a 101 | * shape, i.e. everywhere but where the shape is. 102 | *

103 | * Note that it's not commutative! WITHIN.inverse().inverse() != 104 | * WITHIN. 105 | */ 106 | public SpatialRelation inverse() { 107 | switch(this) { 108 | case DISJOINT: return CONTAINS; 109 | case CONTAINS: return DISJOINT; 110 | case WITHIN: return INTERSECTS;//not commutative! 111 | } 112 | return INTERSECTS; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/shape/impl/InfBufLine.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.shape.impl; 10 | 11 | import org.locationtech.spatial4j.shape.Point; 12 | import org.locationtech.spatial4j.shape.Rectangle; 13 | import org.locationtech.spatial4j.shape.SpatialRelation; 14 | 15 | import static org.locationtech.spatial4j.shape.SpatialRelation.*; 16 | 17 | /** 18 | * INERNAL: A buffered line of infinite length. 19 | * Public for test access. 20 | */ 21 | public class InfBufLine { 22 | 23 | /** Error epsilon. */ 24 | private static final double EPS = 10e-14; 25 | 26 | //TODO consider removing support for vertical line -- let caller 27 | // do something else. BufferedLine could have a factory method 28 | // that returns a rectangle, for example. 29 | 30 | // line: y = slope * x + intercept 31 | 32 | private final double slope;//can be infinite for vertical line 33 | //if slope is infinite, this is x intercept, otherwise y intercept 34 | private final double intercept; 35 | 36 | private final double buf; 37 | 38 | private final double distDenomInv;//cached: 1 / Math.sqrt(slope * slope + 1) 39 | 40 | InfBufLine(double slope, Point point, double buf) { 41 | assert !Double.isNaN(slope); 42 | this.slope = slope; 43 | if (Double.isInfinite(slope)) { 44 | intercept = point.getX(); 45 | distDenomInv = Double.NaN; 46 | } else { 47 | intercept = point.getY() - slope * point.getX(); 48 | distDenomInv = 1 / Math.sqrt(slope * slope + 1); 49 | } 50 | this.buf = buf; 51 | } 52 | 53 | SpatialRelation relate(Rectangle r, Point prC, Point scratch) { 54 | assert r.getCenter().equals(prC); 55 | 56 | int cQuad = quadrant(prC); 57 | 58 | Point nearestP = scratch; 59 | cornerByQuadrant(r, oppositeQuad[cQuad], nearestP); 60 | boolean nearestContains = contains(nearestP); 61 | 62 | if (nearestContains) { 63 | Point farthestP = scratch; 64 | nearestP = null;//just to be safe (same scratch object) 65 | cornerByQuadrant(r, cQuad, farthestP); 66 | boolean farthestContains = contains(farthestP); 67 | if (farthestContains) 68 | return CONTAINS; 69 | return INTERSECTS; 70 | } else {// not nearestContains 71 | if (quadrant(nearestP) == cQuad) 72 | return DISJOINT;//out of buffer on same side as center 73 | return INTERSECTS;//nearest & farthest points straddle the line 74 | } 75 | } 76 | 77 | boolean contains(Point p) { 78 | return (distanceUnbuffered(p) <= buf + EPS); 79 | } 80 | 81 | /** INTERNAL AKA lineToPointDistance */ 82 | public double distanceUnbuffered(Point c) { 83 | if (Double.isInfinite(slope)) 84 | return Math.abs(c.getX() - intercept); 85 | // http://math.ucsd.edu/~wgarner/math4c/derivations/distance/distptline.htm 86 | double num = Math.abs(c.getY() - slope * c.getX() - intercept); 87 | return num * distDenomInv; 88 | } 89 | 90 | // /** Amount to add or subtract to intercept to indicate where the 91 | // * buffered line edges cross the y axis. 92 | // * @return 93 | // */ 94 | // double interceptBuffOffset() { 95 | // if (Double.isInfinite(slope)) 96 | // return slope; 97 | // if (buf == 0) 98 | // return 0; 99 | // double slopeDivBuf = slope / buf; 100 | // return Math.sqrt(buf*buf + slopeDivBuf*slopeDivBuf); 101 | // } 102 | 103 | /** INTERNAL: AKA lineToPointQuadrant */ 104 | public int quadrant(Point c) { 105 | //check vertical line case 1st 106 | if (Double.isInfinite(slope)) { 107 | //when slope is infinite, intercept is x intercept instead of y 108 | return c.getX() > intercept ? 1 : 2; //4 : 3 would work too 109 | } 110 | //(below will work for slope==0 horizontal line too) 111 | //is c above or below the line 112 | double yAtCinLine = slope * c.getX() + intercept; 113 | boolean above = c.getY() >= yAtCinLine; 114 | if (slope > 0) { 115 | //if slope is a forward slash, then result is 2 | 4 116 | return above ? 2 : 4; 117 | } else { 118 | //if slope is a backward slash, then result is 1 | 3 119 | return above ? 1 : 3; 120 | } 121 | } 122 | 123 | //TODO ? Use an Enum for quadrant? 124 | 125 | /* quadrants 1-4: NE, NW, SW, SE. */ 126 | private static final int[] oppositeQuad= {-1,3,4,1,2}; 127 | 128 | public static void cornerByQuadrant(Rectangle r, int cornerQuad, Point out) { 129 | double x = (cornerQuad == 1 || cornerQuad == 4) ? r.getMaxX() : r.getMinX(); 130 | double y = (cornerQuad == 1 || cornerQuad == 2) ? r.getMaxY() : r.getMinY(); 131 | out.reset(x, y); 132 | } 133 | 134 | public double getSlope() { 135 | return slope; 136 | } 137 | 138 | public double getIntercept() { 139 | return intercept; 140 | } 141 | 142 | public double getBuf() { 143 | return buf; 144 | } 145 | 146 | /** 1 / Math.sqrt(slope * slope + 1) */ 147 | public double getDistDenomInv() { 148 | return distDenomInv; 149 | } 150 | 151 | @Override 152 | public String toString() { 153 | return "InfBufLine{" + 154 | "buf=" + buf + 155 | ", intercept=" + intercept + 156 | ", slope=" + slope + 157 | '}'; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/shape/impl/PointImpl.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.shape.impl; 10 | 11 | import org.locationtech.spatial4j.context.SpatialContext; 12 | import org.locationtech.spatial4j.shape.BaseShape; 13 | import org.locationtech.spatial4j.shape.Circle; 14 | import org.locationtech.spatial4j.shape.Point; 15 | import org.locationtech.spatial4j.shape.Rectangle; 16 | import org.locationtech.spatial4j.shape.Shape; 17 | import org.locationtech.spatial4j.shape.SpatialRelation; 18 | 19 | /** A basic 2D implementation of a Point. */ 20 | public class PointImpl extends BaseShape implements Point { 21 | 22 | private double x; 23 | private double y; 24 | 25 | /** A simple constructor without normalization / validation. */ 26 | public PointImpl(double x, double y, SpatialContext ctx) { 27 | super(ctx); 28 | reset(x, y); 29 | } 30 | 31 | @Override 32 | public boolean isEmpty() { 33 | return Double.isNaN(x); 34 | } 35 | 36 | @Override 37 | public void reset(double x, double y) { 38 | assert ! isEmpty(); 39 | this.x = x; 40 | this.y = y; 41 | } 42 | 43 | @Override 44 | public double getX() { 45 | return x; 46 | } 47 | 48 | @Override 49 | public double getY() { 50 | return y; 51 | } 52 | 53 | @Override 54 | public double getLat() { 55 | return getY(); 56 | } 57 | 58 | @Override 59 | public double getLon() { 60 | return getX(); 61 | } 62 | 63 | @Override 64 | public Rectangle getBoundingBox() { 65 | return ctx.makeRectangle(this, this); 66 | } 67 | 68 | @Override 69 | public PointImpl getCenter() { 70 | return this; 71 | } 72 | 73 | @Override 74 | public Circle getBuffered(double distance, SpatialContext ctx) { 75 | return ctx.makeCircle(this, distance); 76 | } 77 | 78 | @Override 79 | public SpatialRelation relate(Shape other) { 80 | if (isEmpty() || other.isEmpty()) 81 | return SpatialRelation.DISJOINT; 82 | if (other instanceof Point) 83 | return this.equals(other) ? SpatialRelation.INTERSECTS : SpatialRelation.DISJOINT; 84 | return other.relate(this).transpose(); 85 | } 86 | 87 | @Override 88 | public boolean hasArea() { 89 | return false; 90 | } 91 | 92 | @Override 93 | public double getArea(SpatialContext ctx) { 94 | return 0; 95 | } 96 | 97 | @Override 98 | public String toString() { 99 | return "Pt(x="+x+",y="+y+")"; 100 | } 101 | 102 | @Override 103 | public boolean equals(Object o) { 104 | return equals(this,o); 105 | } 106 | 107 | /** 108 | * All {@link Point} implementations should use this definition of {@link Object#equals(Object)}. 109 | */ 110 | public static boolean equals(Point thiz, Object o) { 111 | assert thiz != null; 112 | if (thiz == o) return true; 113 | if (!(o instanceof Point)) return false; 114 | 115 | Point point = (Point) o; 116 | 117 | if (Double.compare(point.getX(), thiz.getX()) != 0) return false; 118 | if (Double.compare(point.getY(), thiz.getY()) != 0) return false; 119 | 120 | return true; 121 | } 122 | 123 | @Override 124 | public int hashCode() { 125 | return hashCode(this); 126 | } 127 | 128 | /** 129 | * All {@link Point} implementations should use this definition of {@link Object#hashCode()}. 130 | */ 131 | public static int hashCode(Point thiz) { 132 | int result; 133 | long temp; 134 | temp = thiz.getX() != +0.0d ? Double.doubleToLongBits(thiz.getX()) : 0L; 135 | result = (int) (temp ^ (temp >>> 32)); 136 | temp = thiz.getY() != +0.0d ? Double.doubleToLongBits(thiz.getY()) : 0L; 137 | result = 31 * result + (int) (temp ^ (temp >>> 32)); 138 | return result; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/shape/impl/Range.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.shape.impl; 10 | 11 | import org.locationtech.spatial4j.context.SpatialContext; 12 | import org.locationtech.spatial4j.shape.Rectangle; 13 | 14 | /** 15 | * INTERNAL: A numeric range between a pair of numbers. 16 | * Perhaps this class could become 1st class citizen extending Shape but not now. 17 | * Only public so is accessible from tests in another package. 18 | */ 19 | @Deprecated // See BBoxCalculator 20 | public class Range { 21 | protected final double min, max; 22 | 23 | public static Range xRange(Rectangle rect, SpatialContext ctx) { 24 | if (ctx.isGeo()) 25 | return new LongitudeRange(rect.getMinX(), rect.getMaxX()); 26 | else 27 | return new Range(rect.getMinX(), rect.getMaxX()); 28 | } 29 | 30 | public static Range yRange(Rectangle rect, SpatialContext ctx) { 31 | return new Range(rect.getMinY(), rect.getMaxY()); 32 | } 33 | 34 | public Range(double min, double max) { 35 | this.min = min; 36 | this.max = max; 37 | } 38 | 39 | public double getMin() { 40 | return min; 41 | } 42 | 43 | public double getMax() { 44 | return max; 45 | } 46 | 47 | @Override 48 | public boolean equals(Object o) { 49 | if (this == o) return true; 50 | if (o == null || getClass() != o.getClass()) return false; 51 | 52 | Range range = (Range) o; 53 | 54 | if (Double.compare(range.max, max) != 0) return false; 55 | if (Double.compare(range.min, min) != 0) return false; 56 | 57 | return true; 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | int result; 63 | long temp; 64 | temp = min != +0.0d ? Double.doubleToLongBits(min) : 0L; 65 | result = (int) (temp ^ (temp >>> 32)); 66 | temp = max != +0.0d ? Double.doubleToLongBits(max) : 0L; 67 | result = 31 * result + (int) (temp ^ (temp >>> 32)); 68 | return result; 69 | } 70 | 71 | @Override 72 | public String toString() { 73 | return "Range{" + min + " TO " + max + '}'; 74 | } 75 | 76 | public double getWidth() { 77 | return max - min; 78 | } 79 | 80 | public boolean contains(double v) { 81 | return v >= min && v <= max; 82 | } 83 | 84 | public double getCenter() { 85 | return min + getWidth()/2; 86 | } 87 | 88 | public Range expandTo(Range other) { 89 | assert this.getClass() == other.getClass(); 90 | return new Range(Math.min(min, other.min), Math.max(max, other.max)); 91 | } 92 | 93 | public double deltaLen(Range other) { 94 | double min3 = Math.max(min, other.min); 95 | double max3 = Math.min(max, other.max); 96 | return max3 - min3; 97 | } 98 | 99 | @Deprecated // See BBoxCalculator 100 | public static class LongitudeRange extends Range { 101 | 102 | public static final LongitudeRange WORLD_180E180W = new LongitudeRange(-180, 180); 103 | 104 | public LongitudeRange(double min, double max) { 105 | super(min, max); 106 | } 107 | 108 | public LongitudeRange(Rectangle r) { 109 | super(r.getMinX(), r.getMaxX()); 110 | } 111 | 112 | @Override 113 | public double getWidth() { 114 | double w = super.getWidth(); 115 | if (w < 0) 116 | w += 360; 117 | return w; 118 | } 119 | 120 | @Override 121 | public boolean contains(double v) { 122 | if (!crossesDateline()) 123 | return super.contains(v); 124 | return v >= min || v <= max;// the OR is the distinction from non-dateline cross 125 | } 126 | 127 | public boolean crossesDateline() { 128 | return min > max; 129 | } 130 | 131 | @Override 132 | public double getCenter() { 133 | double ctr = super.getCenter(); 134 | if (ctr > 180) 135 | ctr -= 360; 136 | return ctr; 137 | } 138 | 139 | public double compareTo(LongitudeRange b) { 140 | return diff(getCenter(), b.getCenter()); 141 | } 142 | 143 | /** a - b (compareTo order). < 0 if a < b */ 144 | private static double diff(double a, double b) { 145 | double diff = a - b; 146 | if (diff <= 180) { 147 | if (diff >= -180) 148 | return diff; 149 | return diff + 360; 150 | } else { 151 | return diff - 360; 152 | } 153 | } 154 | 155 | @Override 156 | public Range expandTo(Range other) { 157 | return expandTo((LongitudeRange) other); 158 | } 159 | 160 | public LongitudeRange expandTo(LongitudeRange other) { 161 | LongitudeRange a, b;// a.ctr <= b.ctr 162 | if (this.compareTo(other) <= 0) { 163 | a = this; 164 | b = other; 165 | } else { 166 | a = other; 167 | b = this; 168 | } 169 | LongitudeRange newMin = b.contains(a.min) ? b : a;//usually 'a' 170 | LongitudeRange newMax = a.contains(b.max) ? a : b;//usually 'b' 171 | if (newMin == newMax) 172 | return newMin; 173 | if (newMin == b && newMax == a) 174 | return WORLD_180E180W; 175 | return new LongitudeRange(newMin.min, newMax.max); 176 | } 177 | } 178 | } 179 | 180 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/shape/jts/JtsPoint.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.shape.jts; 10 | 11 | 12 | import org.locationtech.spatial4j.context.SpatialContext; 13 | import org.locationtech.spatial4j.context.jts.JtsSpatialContext; 14 | import org.locationtech.spatial4j.shape.BaseShape; 15 | import org.locationtech.spatial4j.shape.Circle; 16 | import org.locationtech.spatial4j.shape.Point; 17 | import org.locationtech.spatial4j.shape.Rectangle; 18 | import org.locationtech.spatial4j.shape.Shape; 19 | import org.locationtech.spatial4j.shape.SpatialRelation; 20 | import org.locationtech.spatial4j.shape.impl.PointImpl; 21 | import org.locationtech.jts.geom.CoordinateSequence; 22 | 23 | /** Wraps a {@link org.locationtech.jts.geom.Point}. */ 24 | public class JtsPoint extends BaseShape implements Point { 25 | 26 | private org.locationtech.jts.geom.Point pointGeom; 27 | private final boolean empty;//cached 28 | 29 | /** A simple constructor without normalization / validation. */ 30 | public JtsPoint(org.locationtech.jts.geom.Point pointGeom, JtsSpatialContext ctx) { 31 | super(ctx); 32 | this.pointGeom = pointGeom; 33 | this.empty = pointGeom.isEmpty(); 34 | } 35 | 36 | public org.locationtech.jts.geom.Point getGeom() { 37 | return pointGeom; 38 | } 39 | 40 | @Override 41 | public boolean isEmpty() { 42 | return empty; 43 | } 44 | 45 | @Override 46 | public org.locationtech.spatial4j.shape.Point getCenter() { 47 | return this; 48 | } 49 | 50 | @Override 51 | public boolean hasArea() { 52 | return false; 53 | } 54 | 55 | @Override 56 | public double getArea(SpatialContext ctx) { 57 | return 0; 58 | } 59 | 60 | @Override 61 | public Rectangle getBoundingBox() { 62 | return ctx.makeRectangle(this, this); 63 | } 64 | 65 | @Override 66 | public Circle getBuffered(double distance, SpatialContext ctx) { 67 | return ctx.makeCircle(this, distance); 68 | } 69 | 70 | @Override 71 | public SpatialRelation relate(Shape other) { 72 | // ** NOTE ** the overall order of logic is kept consistent here with simple.PointImpl. 73 | if (isEmpty() || other.isEmpty()) 74 | return SpatialRelation.DISJOINT; 75 | if (other instanceof org.locationtech.spatial4j.shape.Point) 76 | return this.equals(other) ? SpatialRelation.INTERSECTS : SpatialRelation.DISJOINT; 77 | return other.relate(this).transpose(); 78 | } 79 | 80 | @Override 81 | public double getX() { 82 | return isEmpty() ? Double.NaN : pointGeom.getX(); 83 | } 84 | 85 | @Override 86 | public double getY() { 87 | return isEmpty() ? Double.NaN : pointGeom.getY(); 88 | } 89 | 90 | @Override 91 | public double getLat() { 92 | return getY(); 93 | } 94 | 95 | @Override 96 | public double getLon() { 97 | return getX(); 98 | } 99 | 100 | @Override 101 | public void reset(double x, double y) { 102 | assert ! isEmpty(); 103 | CoordinateSequence cSeq = pointGeom.getCoordinateSequence(); 104 | cSeq.setOrdinate(0, CoordinateSequence.X, x); 105 | cSeq.setOrdinate(0, CoordinateSequence.Y, y); 106 | } 107 | 108 | @Override 109 | public String toString() { 110 | return "Pt(x="+getX()+",y="+getY()+")"; 111 | } 112 | 113 | @Override 114 | public boolean equals(Object o) { 115 | return PointImpl.equals(this,o); 116 | } 117 | 118 | @Override 119 | public int hashCode() { 120 | return PointImpl.hashCode(this); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/org/locationtech/spatial4j/shape/package-info.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | /** Shapes are the core geometry objects that Spatial4j provides. The types 10 | * exposed in this package should be considered public. Remember not to 11 | * instantiate them directly, use the SpatialContext which acts as a 12 | * factory for shapes. */ 13 | package org.locationtech.spatial4j.shape; -------------------------------------------------------------------------------- /src/main/java/overview.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | Spatial4j - Core Spatial Library 11 | 12 | 13 | The central class to the API is the SpatialContext, from which most of the rest 14 | of the API is accessed. 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/TestLog.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j; 10 | 11 | import com.carrotsearch.randomizedtesting.rules.TestRuleAdapter; 12 | import org.slf4j.helpers.MessageFormatter; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * A utility logger for tests in which log statements are logged following 19 | * test failure only. Add this to a JUnit based test class with a {@link org.junit.Rule} 20 | * annotation. 21 | */ 22 | public class TestLog extends TestRuleAdapter { 23 | 24 | //TODO does this need to be threadsafe (such as via thread-local state)? 25 | private static ArrayList logStack = new ArrayList<>(); 26 | private static final int MAX_LOGS = 1000; 27 | 28 | public static final TestLog instance = new TestLog(); 29 | 30 | private TestLog() {} 31 | 32 | @Override 33 | protected void before() throws Throwable { 34 | logStack.clear(); 35 | } 36 | 37 | @Override 38 | protected void afterAlways(List errors) throws Throwable { 39 | if (!errors.isEmpty()) 40 | logThenClear(); 41 | } 42 | 43 | private void logThenClear() { 44 | for (LogEntry entry : logStack) { 45 | System.out.println(MessageFormatter.arrayFormat(entry.msg, entry.args).getMessage()); 46 | } 47 | logStack.clear(); 48 | } 49 | 50 | public static void clear() { 51 | logStack.clear(); 52 | } 53 | 54 | /** 55 | * Enqueues a log message with substitution arguments ala SLF4J (i.e. {} syntax). 56 | * If the test fails then it'll be logged then, otherwise it'll be forgotten. 57 | */ 58 | public static void log(String msg, Object... args) { 59 | if (logStack.size() > MAX_LOGS) { 60 | throw new RuntimeException("Too many log statements: "+logStack.size() + " > "+MAX_LOGS); 61 | } 62 | LogEntry entry = new LogEntry(); 63 | entry.msg = msg; 64 | entry.args = args; 65 | logStack.add(entry); 66 | } 67 | 68 | private static class LogEntry { String msg; Object[] args; } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/context/SpatialContextFactoryTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.context; 10 | 11 | import org.locationtech.spatial4j.context.jts.DatelineRule; 12 | import org.locationtech.spatial4j.context.jts.JtsSpatialContext; 13 | import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; 14 | import org.locationtech.spatial4j.context.jts.ValidationRule; 15 | import org.locationtech.spatial4j.distance.CartesianDistCalc; 16 | import org.locationtech.spatial4j.distance.GeodesicSphereDistCalc; 17 | import org.locationtech.spatial4j.io.ShapeIO; 18 | import org.locationtech.spatial4j.io.WKTReader; 19 | import org.locationtech.spatial4j.shape.impl.RectangleImpl; 20 | import org.junit.After; 21 | import org.junit.Test; 22 | 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | 26 | import static org.junit.Assert.assertEquals; 27 | import static org.junit.Assert.assertTrue; 28 | 29 | 30 | public class SpatialContextFactoryTest { 31 | public static final String PROP = "SpatialContextFactory"; 32 | 33 | @After 34 | public void tearDown() { 35 | System.getProperties().remove(PROP); 36 | } 37 | 38 | private SpatialContext call(String... argsStr) { 39 | Map args = new HashMap<>(); 40 | for (int i = 0; i < argsStr.length; i+=2) { 41 | String key = argsStr[i]; 42 | String val = argsStr[i+1]; 43 | args.put(key,val); 44 | } 45 | return SpatialContextFactory.makeSpatialContext(args, getClass().getClassLoader()); 46 | } 47 | 48 | @Test 49 | public void testDefault() { 50 | SpatialContext ctx = SpatialContext.GEO; 51 | SpatialContext ctx2 = call();//default 52 | assertEquals(ctx.getClass(), ctx2.getClass()); 53 | assertEquals(ctx.isGeo(), ctx2.isGeo()); 54 | assertEquals(ctx.getDistCalc(),ctx2.getDistCalc()); 55 | assertEquals(ctx.getWorldBounds(), ctx2.getWorldBounds()); 56 | } 57 | 58 | @Test 59 | public void testCustom() { 60 | SpatialContext ctx = call("geo","false"); 61 | assertTrue(!ctx.isGeo()); 62 | assertEquals(new CartesianDistCalc(), ctx.getDistCalc()); 63 | 64 | ctx = call("geo","false", 65 | "distCalculator","cartesian^2", 66 | "worldBounds","ENVELOPE(-100, 75, 200, 0)");//xMin, xMax, yMax, yMin 67 | assertEquals(new CartesianDistCalc(true),ctx.getDistCalc()); 68 | assertEquals(new RectangleImpl(-100, 75, 0, 200, ctx), ctx.getWorldBounds()); 69 | 70 | ctx = call("geo","true", 71 | "distCalculator","lawOfCosines"); 72 | assertTrue(ctx.isGeo()); 73 | assertEquals(new GeodesicSphereDistCalc.LawOfCosines(), 74 | ctx.getDistCalc()); 75 | } 76 | 77 | @Test 78 | public void testJtsContextFactory() { 79 | JtsSpatialContext ctx = (JtsSpatialContext) call( 80 | "spatialContextFactory", JtsSpatialContextFactory.class.getName(), 81 | "geo", "true", 82 | "normWrapLongitude", "true", 83 | "precisionScale", "2.0", 84 | "wktShapeParserClass", CustomWktShapeParser.class.getName(), 85 | "datelineRule", "ccwRect", 86 | "validationRule", "repairConvexHull", 87 | "autoIndex", "true"); 88 | assertTrue(ctx.isNormWrapLongitude()); 89 | assertEquals(2.0, ctx.getGeometryFactory().getPrecisionModel().getScale(), 0.0); 90 | assertTrue(CustomWktShapeParser.once);//cheap way to test it was created 91 | assertEquals(DatelineRule.ccwRect, 92 | ctx.getDatelineRule()); 93 | assertEquals(ValidationRule.repairConvexHull, 94 | ctx.getValidationRule()); 95 | 96 | //ensure geo=false with worldbounds works -- fixes #72 97 | ctx = (JtsSpatialContext) call( 98 | "spatialContextFactory", JtsSpatialContextFactory.class.getName(), 99 | "geo", "false",//set to false 100 | "worldBounds", "ENVELOPE(-500,500,300,-300)", 101 | "normWrapLongitude", "true", 102 | "precisionScale", "2.0", 103 | "wktShapeParserClass", CustomWktShapeParser.class.getName(), 104 | "datelineRule", "ccwRect", 105 | "validationRule", "repairConvexHull", 106 | "autoIndex", "true"); 107 | assertEquals(300, ctx.getWorldBounds().getMaxY(), 0.0); 108 | } 109 | 110 | 111 | @Test 112 | public void testFormatsConfig() { 113 | JtsSpatialContext ctx = (JtsSpatialContext) call( 114 | "spatialContextFactory", JtsSpatialContextFactory.class.getName(), 115 | "readers", CustomWktShapeParser.class.getName()); 116 | 117 | assertTrue( ctx.getFormats().getReader(ShapeIO.WKT) instanceof CustomWktShapeParser ); 118 | } 119 | 120 | @Test 121 | public void testSystemPropertyLookup() { 122 | System.setProperty(PROP,DSCF.class.getName()); 123 | assertTrue(!call().isGeo());//DSCF returns this 124 | } 125 | 126 | public static class DSCF extends SpatialContextFactory { 127 | 128 | @Override 129 | public SpatialContext newSpatialContext() { 130 | geo = false; 131 | return new SpatialContext(this); 132 | } 133 | } 134 | 135 | public static class CustomWktShapeParser extends WKTReader { 136 | static boolean once = false;//cheap way to test it was created 137 | public CustomWktShapeParser(JtsSpatialContext ctx, JtsSpatialContextFactory factory) { 138 | super(ctx, factory); 139 | once = true; 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/context/jts/JtsSpatialContextTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.context.jts; 10 | 11 | import org.locationtech.jts.geom.Geometry; 12 | import org.locationtech.jts.geom.GeometryFactory; 13 | import org.locationtech.spatial4j.shape.jts.JtsGeometry; 14 | import org.locationtech.jts.geom.GeometryCollection; 15 | import org.locationtech.jts.geom.Polygon; 16 | import org.locationtech.jts.geom.MultiPolygon; 17 | import org.junit.Test; 18 | import org.locationtech.spatial4j.shape.jts.JtsShapeFactory; 19 | import org.locationtech.spatial4j.util.Geom; 20 | 21 | import static org.junit.Assert.assertEquals; 22 | import static org.junit.Assert.assertTrue; 23 | 24 | public class JtsSpatialContextTest { 25 | 26 | @Test 27 | public void testDatelineRule() { 28 | // rectangle enclosing the dateline 29 | Polygon polygon = Geom.build().points(-179, -90, 179, -90, 179, 90, -179, 90).toPolygon(); 30 | 31 | JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); 32 | factory.datelineRule = DatelineRule.width180; 33 | JtsSpatialContext ctx = factory.newSpatialContext(); 34 | final Polygon polygonCloned = (Polygon) polygon.copy(); 35 | JtsGeometry shp = ctx.makeShape(polygonCloned); 36 | assertEquals("shouldn't be modified after calling makeShape", polygon, polygonCloned); 37 | assertTrue(shp.getGeom() instanceof GeometryCollection); 38 | 39 | factory.datelineRule = DatelineRule.none; 40 | ctx = factory.newSpatialContext(); 41 | shp = ctx.makeShape(polygon); 42 | assertTrue(shp.getGeom() instanceof Polygon); 43 | } 44 | 45 | @Test 46 | public void testDatelineRuleWithMultiPolygon() { 47 | JtsSpatialContext ctx = new JtsSpatialContextFactory().newSpatialContext(); 48 | JtsShapeFactory shapeFactory = ctx.getShapeFactory(); 49 | GeometryFactory geomFactory = shapeFactory.getGeometryFactory(); 50 | 51 | // rectangle enclosing the dateline 52 | Polygon poly1Geom = Geom.build().points(-179, -90, 179, -90, 179, 90, -179, 90).toPolygon(); 53 | // simple triangle 54 | Polygon poly2Geom = Geom.build().points(0, 0, 1, 1, 1, 0, 0, 0).toPolygon(); 55 | 56 | GeometryCollection geomColl = geomFactory.createGeometryCollection( 57 | new Geometry[]{poly1Geom, poly2Geom}); 58 | JtsGeometry jtsGeometry = shapeFactory.makeShape(geomColl); 59 | // one of them is split; other is unchanged 60 | assertEquals("MULTIPOLYGON (" + 61 | "((-180 -90, -180 90, -179 90, -179 -90, -180 -90)), " + 62 | "((179 90, 180 90, 180 -90, 179 -90, 179 90)), " + 63 | "((0 0, 1 1, 1 0, 0 0))" + 64 | ")", jtsGeometry.toString()); 65 | } 66 | 67 | @Test 68 | public void testMultiDatelineWrap() { 69 | // polygon crosses the dateline twice 70 | Polygon polygon = Geom.build().points(-179, 45, 179, 44, 1, 35, -179, 25, 179, 24, 179, 19, -179, 20, 1, 30, 179, 39, -179, 40).toPolygon(); 71 | 72 | JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); 73 | factory.datelineRule = DatelineRule.width180; 74 | JtsSpatialContext ctx = factory.newSpatialContext(); 75 | JtsShapeFactory shapeFactory = ctx.getShapeFactory(); 76 | JtsGeometry jtsGeometry = shapeFactory.makeShape(polygon); 77 | Geometry geometry = jtsGeometry.getGeom(); 78 | 79 | assertTrue(geometry.isValid()); 80 | assertTrue(geometry instanceof MultiPolygon); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/distance/CompareRadiansSnippet.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.distance; 10 | 11 | 12 | //import static org.locationtech.spatial4j.distance.DistanceUtils.toRadians; 13 | import static java.lang.Math.toRadians; 14 | 15 | /** 16 | * On my machine, using 17 | * Math.toRadians: 2090 18 | * DistanceUtils: 626 19 | */ 20 | public class CompareRadiansSnippet { 21 | 22 | public static void main(String[] args) { 23 | long start = System.currentTimeMillis(); 24 | double x = 1.12345; 25 | for (int i=0; i<100000000; i++) { 26 | x += toRadians(x) - toRadians(x+1); 27 | } 28 | System.out.println(x); // need to use the result... otherwise JVM may skip everything 29 | System.out.println(System.currentTimeMillis()-start); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/BaseRoundTripTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io; 10 | 11 | import com.carrotsearch.randomizedtesting.RandomizedTest; 12 | import org.locationtech.spatial4j.context.SpatialContext; 13 | import org.locationtech.spatial4j.shape.Shape; 14 | import org.junit.Test; 15 | 16 | public abstract class BaseRoundTripTest extends RandomizedTest { 17 | 18 | protected T ctx; 19 | protected BinaryCodec binaryCodec; 20 | 21 | protected BaseRoundTripTest() { 22 | this.ctx = initContext(); 23 | binaryCodec = ctx.getBinaryCodec();//stateless 24 | } 25 | 26 | public abstract T initContext(); 27 | 28 | public boolean shouldBeEqualAfterRoundTrip() { 29 | return true; 30 | } 31 | 32 | //This test uses WKT to specify the shapes because the Jts based subclass tests will test 33 | // using floats instead of doubles, and WKT is normalized whereas ctx.makeXXX is not. 34 | 35 | @Test 36 | public void testPoint() throws Exception { 37 | assertRoundTrip(wkt("POINT(-10 80.3)")); 38 | } 39 | 40 | /** Convenience to read static data. */ 41 | protected Shape wkt(String wkt) { 42 | try { 43 | return ctx.getFormats().getWktReader().read(wkt); 44 | } catch (RuntimeException e) { 45 | throw e; 46 | } catch (Exception e) { 47 | throw new RuntimeException(e); 48 | } 49 | } 50 | 51 | protected Shape randomShape() { 52 | switch (randomInt(2)) {//inclusive 53 | case 0: return wkt("POINT(-10 80.3)"); 54 | case 1: return wkt("ENVELOPE(-10, 180, 42.3, 0)"); 55 | case 2: return wkt("BUFFER(POINT(-10 30), 5.2)"); 56 | default: throw new Error(); 57 | } 58 | } 59 | 60 | protected final void assertRoundTrip(Shape shape) throws Exception { 61 | assertRoundTrip(shape, shouldBeEqualAfterRoundTrip()); 62 | } 63 | 64 | protected abstract void assertRoundTrip(Shape shape, boolean andEquals) throws Exception; 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/BinaryCodecTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io; 10 | 11 | import org.locationtech.spatial4j.context.SpatialContext; 12 | import org.locationtech.spatial4j.shape.Shape; 13 | import org.locationtech.spatial4j.shape.ShapeCollection; 14 | import org.junit.Test; 15 | 16 | import java.io.*; 17 | import java.util.Arrays; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | 21 | public class BinaryCodecTest extends BaseRoundTripTest { 22 | 23 | @Override 24 | public SpatialContext initContext() { 25 | return SpatialContext.GEO; 26 | } 27 | 28 | @Test 29 | public void testRect() throws Exception { 30 | assertRoundTrip(wkt("ENVELOPE(-10, 180, 42.3, 0)")); 31 | } 32 | 33 | @Test 34 | public void testCircle() throws Exception { 35 | assertRoundTrip(wkt("BUFFER(POINT(-10 30), 5.2)")); 36 | } 37 | 38 | @Test 39 | public void testCollection() throws Exception { 40 | ShapeCollection s = ctx.makeCollection( 41 | Arrays.asList( 42 | randomShape(), 43 | randomShape(), 44 | randomShape() 45 | ) 46 | ); 47 | assertRoundTrip(s); 48 | } 49 | 50 | @Override 51 | protected void assertRoundTrip(Shape shape, boolean andEquals) throws IOException { 52 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 53 | binaryCodec.writeShape(new DataOutputStream(baos), shape); 54 | ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 55 | assertEquals(shape, binaryCodec.readShape(new DataInputStream(bais))); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/GeneralGeoJSONTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.locationtech.spatial4j.io; 19 | 20 | import org.locationtech.spatial4j.shape.Shape; 21 | import org.junit.Assert; 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | 25 | import static org.junit.Assert.assertEquals; 26 | import static org.junit.Assert.assertTrue; 27 | 28 | 29 | public class GeneralGeoJSONTest extends GeneralReadWriteShapeTest { 30 | 31 | protected ShapeReader reader; 32 | protected ShapeWriter writer; 33 | 34 | protected ShapeWriter writerForTests; 35 | 36 | @Before 37 | @Override 38 | public void setUp() { 39 | super.setUp(); 40 | 41 | reader = ctx.getFormats().getReader(ShapeIO.GeoJSON); 42 | writer = ctx.getFormats().getWriter(ShapeIO.GeoJSON); 43 | writerForTests = writer; //ctx.getFormats().getWriter(ShapeIO.GeoJSON); 44 | 45 | Assert.assertNotNull(reader); 46 | Assert.assertNotNull(writer); 47 | Assert.assertNotNull(writerForTests); 48 | } 49 | 50 | @Override 51 | protected ShapeReader getShapeReader() { 52 | return reader; 53 | } 54 | 55 | @Override 56 | protected ShapeWriter getShapeWriter() { 57 | return writer; 58 | } 59 | 60 | @Override 61 | protected ShapeWriter getShapeWriterForTests() { 62 | return writerForTests; 63 | } 64 | 65 | @Test @Override 66 | public void testWriteThenReadCircle() throws Exception { 67 | //don't test shape equality; rounding issue in 'km' conversion 68 | assertRoundTrip(circle(), false); 69 | } 70 | 71 | @Test @Override 72 | public void testWriteThenReadBufferedLine() throws Exception { 73 | //don't test shape equality; rounding issue in 'km' conversion 74 | assertRoundTrip(bufferedLine(), false); 75 | } 76 | 77 | // 78 | // Below ported from GeoJSONReadWriteTest: 79 | // 80 | 81 | @Test 82 | public void testParsePoint() throws Exception { 83 | Shape v = reader.read(pointText()); 84 | assertTrue(point().equals(v)); 85 | } 86 | 87 | @Test 88 | public void testEncodePoint() throws Exception { 89 | assertEquals(pointText(), writer.toString(point())); 90 | } 91 | 92 | @Test 93 | public void testParseLineString() throws Exception { 94 | assertEquals(line(), reader.read(lineText())); 95 | } 96 | 97 | @Test 98 | public void testEncodeLineString() throws Exception { 99 | assertEquals(lineText(), strip(writer.toString(line()))); 100 | } 101 | 102 | @Test 103 | public void testParsePolygon() throws Exception { 104 | assertEquals(polygon1(), reader.read(polygonText1())); 105 | assertEquals(polygon2(), reader.read(polygonText2())); 106 | } 107 | 108 | @Test 109 | public void testEncodePolygon() throws Exception { 110 | assertEquals(polygonText1(), writer.toString(polygon1())); 111 | assertEquals(polygonText2(), writer.toString(polygon2())); 112 | } 113 | 114 | @Test 115 | public void testParseMultiPoint() throws Exception { 116 | assertEquals(multiPoint(), reader.read(multiPointText())); 117 | } 118 | 119 | @Test 120 | public void testEncodeMultiPoint() throws Exception { 121 | assertEquals(multiPointText(), writer.toString(multiPoint())); 122 | } 123 | 124 | @Test 125 | public void testParseMultiLineString() throws Exception { 126 | assertEquals(multiLine(), reader.read(multiLineText())); 127 | } 128 | 129 | @Test 130 | public void testEncodeMultiLineString() throws Exception { 131 | assertEquals(multiLineText(), writer.toString(multiLine())); 132 | } 133 | 134 | @Test 135 | public void testParseMultiPolygon() throws Exception { 136 | assertEquals(multiPolygon(), reader.read(multiPolygonText())); 137 | } 138 | 139 | @Test 140 | public void testEncodeMultiPolygon() throws Exception { 141 | assertEquals(multiPolygonText(), writer.toString(multiPolygon())); 142 | } 143 | 144 | @Test 145 | public void testEncodeRectangle() throws Exception { 146 | assertEquals(rectangleText(), strip(writer.toString(rectangle()))); 147 | } 148 | 149 | @Test 150 | public void testParseGeometryCollection() throws Exception { 151 | assertEquals(collection(), reader.read(collectionText())); 152 | } 153 | 154 | @Test 155 | public void testEncodeGeometryCollection() throws Exception { 156 | assertEquals(collectionText(), strip(writer.toString(collection()))); 157 | } 158 | 159 | @Test 160 | public void testEncodeBufferedLineString() throws Exception { 161 | assertEquals(bufferedLineText(), strip(writer.toString(bufferedLine()))); 162 | } 163 | 164 | } -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/GeneralPolyshapeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.locationtech.spatial4j.io; 19 | 20 | import org.junit.Assert; 21 | import org.junit.Before; 22 | import org.junit.Ignore; 23 | 24 | public class GeneralPolyshapeTest extends GeneralReadWriteShapeTest { 25 | 26 | ShapeReader reader; 27 | ShapeWriter writer; 28 | 29 | ShapeWriter writerForTests; 30 | 31 | @Before 32 | @Override 33 | public void setUp() { 34 | super.setUp(); 35 | 36 | reader = ctx.getFormats().getReader(ShapeIO.POLY); 37 | writer = ctx.getFormats().getWriter(ShapeIO.POLY); 38 | writerForTests = writer; 39 | 40 | Assert.assertNotNull(reader); 41 | Assert.assertNotNull(writer); 42 | Assert.assertNotNull(writerForTests); 43 | } 44 | 45 | @Override 46 | protected ShapeReader getShapeReader() { 47 | return reader; 48 | } 49 | 50 | @Override 51 | protected ShapeWriter getShapeWriter() { 52 | return writer; 53 | } 54 | 55 | @Override 56 | protected ShapeWriter getShapeWriterForTests() { 57 | return writerForTests; 58 | } 59 | 60 | @Override 61 | public boolean shouldBeEqualAfterRoundTrip() { 62 | return false; // the polyline values will be off by a small fraction -- everything is rounded to: Math.round(value * 1e5) 63 | } 64 | 65 | @Override 66 | @Ignore 67 | public void testEmptyGeometryCollection() throws Exception { 68 | assumeTrue(false); // not supported 69 | } 70 | } -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/GeneralWktTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.locationtech.spatial4j.io; 19 | 20 | import org.junit.Assert; 21 | import org.junit.Before; 22 | import org.junit.Ignore; 23 | import org.junit.Test; 24 | 25 | public class GeneralWktTest extends GeneralReadWriteShapeTest { 26 | 27 | ShapeReader reader; 28 | ShapeWriter writer; 29 | 30 | ShapeWriter writerForTests; 31 | 32 | @Before 33 | @Override 34 | public void setUp() { 35 | super.setUp(); 36 | 37 | reader = ctx.getFormats().getReader(ShapeIO.WKT); 38 | writer = ctx.getFormats().getWriter(ShapeIO.WKT); 39 | writerForTests = writer; //ctx.getFormats().getWriter(ShapeIO.GeoJSON); 40 | 41 | Assert.assertNotNull(reader); 42 | Assert.assertNotNull(writer); 43 | Assert.assertNotNull(writerForTests); 44 | } 45 | 46 | @Override 47 | protected ShapeReader getShapeReader() { 48 | return reader; 49 | } 50 | 51 | @Override 52 | protected ShapeWriter getShapeWriter() { 53 | return writer; 54 | } 55 | 56 | @Override 57 | protected ShapeWriter getShapeWriterForTests() { 58 | return writerForTests; 59 | } 60 | 61 | //TODO: Either the WKT read/writer should try to flatten to the original type (not GeometryCollection) 62 | // and/or ShapeCollection needs to become typed. 63 | 64 | @Ignore @Test @Override 65 | public void testWriteThenReadMultiPoint() throws Exception { 66 | super.testWriteThenReadMultiPoint(); 67 | } 68 | 69 | @Ignore @Test @Override 70 | public void testWriteThenReadMultiLineString() throws Exception { 71 | super.testWriteThenReadMultiLineString(); 72 | } 73 | 74 | @Ignore @Test @Override 75 | public void testWriteThenReadMultiPolygon() throws Exception { 76 | super.testWriteThenReadMultiPolygon(); 77 | } 78 | } -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/JtsBinaryCodecTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io; 10 | 11 | import org.junit.Test; 12 | import org.locationtech.jts.geom.Coordinate; 13 | import org.locationtech.jts.geom.Geometry; 14 | import org.locationtech.jts.geom.PrecisionModel; 15 | import org.locationtech.jts.util.GeometricShapeFactory; 16 | import org.locationtech.spatial4j.context.SpatialContext; 17 | import org.locationtech.spatial4j.context.jts.JtsSpatialContext; 18 | import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; 19 | import org.locationtech.spatial4j.shape.Shape; 20 | import org.locationtech.spatial4j.shape.jts.JtsGeometry; 21 | 22 | public class JtsBinaryCodecTest extends BinaryCodecTest { 23 | 24 | @Override 25 | public SpatialContext initContext() { 26 | JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); 27 | factory.precisionModel = new PrecisionModel(PrecisionModel.FLOATING_SINGLE); 28 | return factory.newSpatialContext(); 29 | } 30 | 31 | @Test 32 | public void testPoly() throws Exception { 33 | JtsSpatialContext ctx = (JtsSpatialContext)super.ctx; 34 | final JtsGeometry shape = ctx.makeShape(randomGeometry(randomIntBetween(3, 20)), false, false); 35 | assertRoundTrip(shape); 36 | } 37 | 38 | @Override 39 | protected Shape randomShape() { 40 | if (randomInt(3) == 0) { 41 | JtsSpatialContext ctx = (JtsSpatialContext)super.ctx; 42 | return ctx.makeShape(randomGeometry(randomIntBetween(3, 20)), false, false); 43 | } else { 44 | return super.randomShape(); 45 | } 46 | } 47 | 48 | Geometry randomGeometry(int points) { 49 | //a circle 50 | JtsSpatialContext ctx = (JtsSpatialContext)super.ctx; 51 | GeometricShapeFactory gsf = new GeometricShapeFactory(ctx.getGeometryFactory()); 52 | gsf.setCentre(new Coordinate(0, 0)); 53 | gsf.setSize(180);//diameter 54 | gsf.setNumPoints(points); 55 | return gsf.createCircle(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/JtsPolyshapeParserTest.java: -------------------------------------------------------------------------------- 1 | package org.locationtech.spatial4j.io; 2 | 3 | import org.locationtech.spatial4j.context.jts.JtsSpatialContext; 4 | import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; 5 | import org.locationtech.spatial4j.shape.Shape; 6 | import org.locationtech.spatial4j.shape.ShapeCollection; 7 | import org.locationtech.spatial4j.shape.jts.JtsGeometry; 8 | import org.locationtech.jts.geom.Geometry; 9 | import org.locationtech.jts.geom.MultiPoint; 10 | import org.junit.Test; 11 | import org.locationtech.spatial4j.util.Geom; 12 | 13 | import java.io.IOException; 14 | import java.text.ParseException; 15 | 16 | import static org.junit.Assert.assertTrue; 17 | 18 | public class JtsPolyshapeParserTest { 19 | 20 | @Test 21 | public void testUseMulti() throws IOException, ParseException { 22 | String ps = write(Geom.build().point(0,0).point(0,0).toMultiPoint()); 23 | 24 | Shape shape = newContext(false).getFormats().getReader(ShapeIO.POLY).read(ps); 25 | assertTrue(shape instanceof ShapeCollection); 26 | 27 | shape = newContext(true).getFormats().getReader(ShapeIO.POLY).read(ps); 28 | assertTrue(shape instanceof JtsGeometry); 29 | assertTrue(((JtsGeometry)shape).getGeom() instanceof MultiPoint); 30 | } 31 | 32 | JtsSpatialContext newContext(boolean useMulti) { 33 | JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); 34 | factory.useJtsMulti = useMulti; 35 | return factory.newSpatialContext(); 36 | } 37 | 38 | String write(Geometry g) { 39 | Shape shp = JtsSpatialContext.GEO.getShapeFactory().makeShape(g); 40 | return JtsSpatialContext.GEO.getFormats().getWriter(ShapeIO.POLY).toString(shp); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/JtsWKTReaderShapeParserTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io; 10 | 11 | import com.carrotsearch.randomizedtesting.RandomizedTest; 12 | import org.locationtech.spatial4j.context.SpatialContext; 13 | import org.locationtech.spatial4j.context.jts.DatelineRule; 14 | import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; 15 | import org.locationtech.spatial4j.exception.InvalidShapeException; 16 | import org.locationtech.spatial4j.io.jts.JtsWKTReaderShapeParser; 17 | import org.locationtech.spatial4j.shape.Rectangle; 18 | import org.locationtech.spatial4j.shape.Shape; 19 | import org.junit.Test; 20 | 21 | import java.io.IOException; 22 | 23 | import static org.junit.Assert.assertEquals; 24 | import static org.junit.Assert.assertTrue; 25 | import static org.junit.Assert.fail; 26 | 27 | public class JtsWKTReaderShapeParserTest extends RandomizedTest { 28 | 29 | final SpatialContext ctx; 30 | { 31 | JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); 32 | factory.datelineRule = DatelineRule.ccwRect; 33 | factory.readers.clear(); 34 | factory.readers.add( JtsWKTReaderShapeParser.class ); 35 | ctx = factory.newSpatialContext(); 36 | } 37 | 38 | @Test 39 | public void wktGeoPt() throws IOException { 40 | Shape s = read("Point(-160 30)"); 41 | assertEquals(ctx.makePoint(-160,30),s); 42 | } 43 | 44 | private Shape read(String value) { 45 | return ctx.getFormats().read(value); 46 | } 47 | 48 | @Test 49 | public void wktGeoRect() throws IOException { 50 | //REMEMBER: Polygon WKT's outer ring is counter-clockwise order. If you accidentally give the other direction, 51 | // JtsSpatialContext will give the wrong result for a rectangle crossing the dateline. 52 | 53 | // In these two tests, we give the same set of points, one that does not cross the dateline, and the 2nd does. The 54 | // order is counter-clockwise in both cases as it should be. 55 | 56 | Shape sNoDL = read("Polygon((-170 30, -170 15, 160 15, 160 30, -170 30))"); 57 | Rectangle expectedNoDL = ctx.makeRectangle(-170, 160, 15, 30); 58 | assertTrue(!expectedNoDL.getCrossesDateLine()); 59 | assertEquals(expectedNoDL,sNoDL); 60 | 61 | Shape sYesDL = read("Polygon(( 160 30, 160 15, -170 15, -170 30, 160 30))"); 62 | Rectangle expectedYesDL = ctx.makeRectangle(160, -170, 15, 30); 63 | assertTrue(expectedYesDL.getCrossesDateLine()); 64 | assertEquals(expectedYesDL,sYesDL); 65 | 66 | } 67 | 68 | 69 | @Test 70 | public void testWrapTopologyException() { 71 | try { 72 | read("POLYGON((0 0, 10 0, 10 20))");//doesn't connect around 73 | fail(); 74 | } catch (InvalidShapeException e) { 75 | //expected 76 | } 77 | 78 | try { 79 | read("POLYGON((0 0, 10 0, 10 20, 5 -5, 0 20, 0 0))");//Topology self-intersect 80 | fail(); 81 | } catch (InvalidShapeException e) { 82 | //expected 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/LegacyShapeReadWriterTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io; 10 | 11 | import com.carrotsearch.randomizedtesting.RandomizedTest; 12 | import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; 13 | import org.locationtech.spatial4j.context.SpatialContext; 14 | import org.locationtech.spatial4j.context.jts.JtsSpatialContext; 15 | import org.locationtech.spatial4j.shape.Shape; 16 | import org.junit.Test; 17 | 18 | import java.io.IOException; 19 | import java.util.Arrays; 20 | 21 | import static org.junit.Assert.assertEquals; 22 | import static org.junit.Assert.assertFalse; 23 | import static org.junit.Assert.assertTrue; 24 | 25 | 26 | public class LegacyShapeReadWriterTest extends RandomizedTest { 27 | 28 | private final LegacyShapeReader reader; 29 | private final LegacyShapeWriter writer; 30 | 31 | @ParametersFactory 32 | public static Iterable parameters() { 33 | return Arrays.asList($$( 34 | $(SpatialContext.GEO), 35 | $(JtsSpatialContext.GEO) 36 | )); 37 | } 38 | 39 | private final SpatialContext ctx; 40 | 41 | public LegacyShapeReadWriterTest(SpatialContext ctx) { 42 | this.ctx = ctx; 43 | this.reader = new LegacyShapeReader(ctx, null); 44 | this.writer = new LegacyShapeWriter(ctx, null); 45 | } 46 | 47 | @SuppressWarnings("unchecked") 48 | private T writeThenRead(T s ) throws IOException { 49 | String buff = writer.toString( s ); 50 | return (T) read( buff ); 51 | } 52 | 53 | private Shape read(String value) { 54 | return reader.readIfSupported(value); 55 | } 56 | 57 | @Test 58 | public void testPoint() throws IOException { 59 | Shape s = read("10 20"); 60 | assertEquals(ctx.makePoint(10,20),s); 61 | assertEquals(s,writeThenRead(s)); 62 | assertEquals(s,read("20,10"));//check comma for y,x format 63 | assertEquals(s,read("20, 10"));//test space 64 | assertFalse(s.hasArea()); 65 | } 66 | 67 | @Test 68 | public void testRectangle() throws IOException { 69 | Shape s = read("-10 -20 10 20"); 70 | assertEquals(ctx.makeRectangle(-10, 10, -20, 20),s); 71 | assertEquals(s,writeThenRead(s)); 72 | assertTrue(s.hasArea()); 73 | } 74 | 75 | @Test 76 | public void testCircle() throws IOException { 77 | Shape s = read("Circle(1.23 4.56 distance=7.89)"); 78 | assertEquals(ctx.makeCircle(1.23, 4.56, 7.89),s); 79 | assertEquals(s,writeThenRead(s)); 80 | assertEquals(s,read("CIRCLE( 4.56,1.23 d=7.89 )")); // use lat,lon and use 'd' abbreviation 81 | assertTrue(s.hasArea()); 82 | } 83 | 84 | 85 | // Looking for more tests? Shapes are tested in TestShapes2D. 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/ShapeFormatTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 VoyagerSearch 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io; 10 | 11 | import org.locationtech.spatial4j.context.SpatialContext; 12 | import org.locationtech.spatial4j.context.jts.JtsSpatialContext; 13 | import org.locationtech.spatial4j.exception.InvalidShapeException; 14 | import org.locationtech.spatial4j.shape.Shape; 15 | import org.junit.Test; 16 | 17 | import java.io.*; 18 | import java.text.ParseException; 19 | import java.util.Arrays; 20 | 21 | import static org.junit.Assert.assertEquals; 22 | import static org.junit.Assert.assertNotNull; 23 | import static org.junit.Assert.fail; 24 | 25 | /** 26 | * Tests for {@link ShapeFormat} 27 | */ 28 | public class ShapeFormatTest { 29 | 30 | public Shape testReadAndWriteTheSame(Shape shape, ShapeReader reader,ShapeWriter writer) throws IOException, ParseException { 31 | assertNotNull(shape); 32 | 33 | StringWriter str = new StringWriter(); 34 | writer.write(str, shape); 35 | // System.out.println( "OUT: "+str.toString()); 36 | 37 | Shape out = reader.read(new StringReader(str.toString())); 38 | 39 | StringWriter copy = new StringWriter(); 40 | writer.write(copy, out); 41 | assertEquals(str.toString(), copy.toString()); 42 | return out; 43 | } 44 | 45 | public void testCommon(SpatialContext ctx, String name) throws Exception { 46 | ShapeReader reader = ctx.getFormats().getReader(name); 47 | ShapeWriter writer = ctx.getFormats().getWriter(name); 48 | assertNotNull(reader); 49 | assertNotNull(writer); 50 | testReadAndWriteTheSame(ctx.makePoint(10, 20),reader,writer); 51 | testReadAndWriteTheSame(ctx.makeLineString( 52 | Arrays.asList( 53 | ctx.makePoint(1, 2), 54 | ctx.makePoint(3, 4), 55 | ctx.makePoint(5, 6) 56 | )),reader,writer); 57 | 58 | // testReadAndWriteTheSame(ctx.makeRectangle(10, 20, 30, 40),format); 59 | } 60 | 61 | public void testJTS(JtsSpatialContext ctx, String name) throws Exception { 62 | ShapeReader reader = ctx.getFormats().getReader(name); 63 | ShapeWriter writer = ctx.getFormats().getWriter(name); 64 | Shape shape; 65 | 66 | // 67 | // wkt = readFirstLineFromRsrc("/russia.wkt.txt"); 68 | // shape = ctx.readShape(wkt); 69 | // // testReadAndWriteTheSame(shape,format); 70 | 71 | // Examples from Wikipedia 72 | shape = wkt(ctx,"LINESTRING (30 10, 10 30, 40 40)"); 73 | // testReadAndWriteTheSame(shape,format); 74 | 75 | shape = wkt(ctx,"POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10))"); 76 | testReadAndWriteTheSame(shape,reader,writer); 77 | 78 | shape = wkt(ctx,"POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30))"); 79 | testReadAndWriteTheSame(shape,reader,writer); 80 | 81 | shape = wkt(ctx,"MULTIPOINT ((10 40), (40 30), (20 20), (30 10))"); 82 | testReadAndWriteTheSame(shape,reader,writer); 83 | 84 | shape = wkt(ctx,"MULTIPOINT (10 40, 40 30, 20 20, 30 10)"); 85 | testReadAndWriteTheSame(shape,reader,writer); 86 | 87 | shape = wkt(ctx,"MULTILINESTRING ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10))"); 88 | testReadAndWriteTheSame(shape,reader,writer); 89 | 90 | shape = wkt(ctx,"MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)),((15 5, 40 10, 10 20, 5 10, 15 5)))"); 91 | testReadAndWriteTheSame(shape,reader,writer); 92 | } 93 | 94 | @Test 95 | public void testReadAndWriteTheSame() throws Exception { 96 | // GeoJSON 97 | String format = ShapeIO.GeoJSON; 98 | testCommon(SpatialContext.GEO, format); 99 | testCommon(JtsSpatialContext.GEO, format); 100 | testJTS(JtsSpatialContext.GEO, format); 101 | 102 | // WKT 103 | format = ShapeIO.WKT; 104 | testCommon(SpatialContext.GEO, format); 105 | testCommon(JtsSpatialContext.GEO, format); 106 | testJTS(JtsSpatialContext.GEO, format); 107 | } 108 | 109 | public void testParseVsInvalidExceptions(ShapeReader reader, boolean supportsPolygon) throws Exception { 110 | String txt = null; 111 | try { 112 | txt = "garbage"; 113 | reader.read(txt); 114 | fail("should throw invalid exception"); 115 | } catch(ParseException ex) { 116 | //expected 117 | } 118 | 119 | try { 120 | txt = "POINT(-1000 1000)"; 121 | reader.read(txt); 122 | fail("should throw invalid shape"); 123 | } catch(InvalidShapeException ex) { 124 | //expected 125 | } 126 | 127 | if(supportsPolygon) { 128 | try { 129 | txt = readFirstLineFromRsrc("/fiji.wkt.txt"); 130 | reader.read(txt); 131 | fail("should throw invalid exception"); 132 | } catch(InvalidShapeException ex) { 133 | //expected 134 | } 135 | } 136 | } 137 | 138 | @Test 139 | public void testParseVsInvalidExceptions() throws Exception { 140 | testParseVsInvalidExceptions(SpatialContext.GEO.getFormats().getWktReader(), false); 141 | testParseVsInvalidExceptions(JtsSpatialContext.GEO.getFormats().getWktReader(), true); 142 | } 143 | 144 | 145 | private String readFirstLineFromRsrc(String wktRsrcPath) throws IOException { 146 | InputStream is = getClass().getResourceAsStream(wktRsrcPath); 147 | assertNotNull(is); 148 | try { 149 | BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8")); 150 | return br.readLine(); 151 | } finally { 152 | is.close(); 153 | } 154 | } 155 | 156 | /** Convenience to read static data. */ 157 | protected Shape wkt(SpatialContext ctx, String wkt) throws IOException, ParseException { 158 | return ctx.getFormats().getWktReader().read(wkt); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/TestGeohashUtils.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io; 10 | 11 | import org.locationtech.spatial4j.context.SpatialContext; 12 | import org.locationtech.spatial4j.shape.Point; 13 | import org.junit.Test; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | 17 | /** 18 | * Tests for {@link GeohashUtils} 19 | */ 20 | public class TestGeohashUtils { 21 | SpatialContext ctx = SpatialContext.GEO; 22 | 23 | /** 24 | * Pass condition: lat=42.6, lng=-5.6 should be encoded as "ezs42e44yx96", 25 | * lat=57.64911 lng=10.40744 should be encoded as "u4pruydqqvj8" 26 | */ 27 | @Test 28 | public void testEncode() { 29 | String hash = GeohashUtils.encodeLatLon(42.6, -5.6); 30 | assertEquals("ezs42e44yx96", hash); 31 | 32 | hash = GeohashUtils.encodeLatLon(57.64911, 10.40744); 33 | assertEquals("u4pruydqqvj8", hash); 34 | } 35 | 36 | /** 37 | * Pass condition: lat=52.3738007, lng=4.8909347 should be encoded and then 38 | * decoded within 0.00001 of the original value 39 | */ 40 | @Test 41 | public void testDecodePreciseLongitudeLatitude() { 42 | String hash = GeohashUtils.encodeLatLon(52.3738007, 4.8909347); 43 | 44 | Point point = GeohashUtils.decode(hash,ctx); 45 | 46 | assertEquals(52.3738007, point.getY(), 0.00001D); 47 | assertEquals(4.8909347, point.getX(), 0.00001D); 48 | } 49 | 50 | /** 51 | * Pass condition: lat=84.6, lng=10.5 should be encoded and then decoded 52 | * within 0.00001 of the original value 53 | */ 54 | @Test 55 | public void testDecodeImpreciseLongitudeLatitude() { 56 | String hash = GeohashUtils.encodeLatLon(84.6, 10.5); 57 | 58 | Point point = GeohashUtils.decode(hash, ctx); 59 | 60 | assertEquals(84.6, point.getY(), 0.00001D); 61 | assertEquals(10.5, point.getX(), 0.00001D); 62 | } 63 | 64 | /* 65 | * see https://issues.apache.org/jira/browse/LUCENE-1815 for details 66 | */ 67 | @Test 68 | public void testDecodeEncode() { 69 | String geoHash = "u173zq37x014"; 70 | assertEquals(geoHash, GeohashUtils.encodeLatLon(52.3738007, 4.8909347)); 71 | Point point = GeohashUtils.decode(geoHash,ctx); 72 | assertEquals(52.37380061d, point.getY(), 0.000001d); 73 | assertEquals(4.8909343d, point.getX(), 0.000001d); 74 | 75 | assertEquals(geoHash, GeohashUtils.encodeLatLon(point.getY(), point.getX())); 76 | 77 | geoHash = "u173"; 78 | point = GeohashUtils.decode("u173",ctx); 79 | geoHash = GeohashUtils.encodeLatLon(point.getY(), point.getX()); 80 | final Point point2 = GeohashUtils.decode(geoHash, ctx); 81 | assertEquals(point.getY(), point2.getY(), 0.000001d); 82 | assertEquals(point.getX(), point2.getX(), 0.000001d); 83 | } 84 | 85 | /** see the table at http://en.wikipedia.org/wiki/Geohash */ 86 | @Test 87 | public void testHashLenToWidth() { 88 | //test odd & even len 89 | double[] boxOdd = GeohashUtils.lookupDegreesSizeForHashLen(3); 90 | assertEquals(1.40625,boxOdd[0],0.0001); 91 | assertEquals(1.40625,boxOdd[1],0.0001); 92 | double[] boxEven = GeohashUtils.lookupDegreesSizeForHashLen(4); 93 | assertEquals(0.1757,boxEven[0],0.0001); 94 | assertEquals(0.3515,boxEven[1],0.0001); 95 | } 96 | 97 | /** see the table at http://en.wikipedia.org/wiki/Geohash */ 98 | @Test 99 | public void testLookupHashLenForWidthHeight() { 100 | assertEquals(1, GeohashUtils.lookupHashLenForWidthHeight(999,999)); 101 | 102 | assertEquals(1, GeohashUtils.lookupHashLenForWidthHeight(999,46)); 103 | assertEquals(1, GeohashUtils.lookupHashLenForWidthHeight(46,999)); 104 | 105 | assertEquals(2, GeohashUtils.lookupHashLenForWidthHeight(44,999)); 106 | assertEquals(2, GeohashUtils.lookupHashLenForWidthHeight(999,44)); 107 | assertEquals(2, GeohashUtils.lookupHashLenForWidthHeight(999,5.7)); 108 | assertEquals(2, GeohashUtils.lookupHashLenForWidthHeight(11.3,999)); 109 | 110 | assertEquals(3, GeohashUtils.lookupHashLenForWidthHeight(999,5.5)); 111 | assertEquals(3, GeohashUtils.lookupHashLenForWidthHeight(11.1,999)); 112 | 113 | assertEquals(GeohashUtils.MAX_PRECISION, GeohashUtils.lookupHashLenForWidthHeight(10e-20,10e-20)); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/WKTWriterTest.java: -------------------------------------------------------------------------------- 1 | package org.locationtech.spatial4j.io; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import java.util.ArrayList; 5 | import org.junit.Test; 6 | import org.locationtech.spatial4j.context.SpatialContext; 7 | import org.locationtech.spatial4j.shape.Point; 8 | import org.locationtech.spatial4j.shape.ShapeCollection; 9 | 10 | public class WKTWriterTest { 11 | 12 | private SpatialContext ctx; 13 | 14 | protected WKTWriterTest(SpatialContext ctx) { 15 | this.ctx = ctx; 16 | } 17 | 18 | public WKTWriterTest() { 19 | this(SpatialContext.GEO); 20 | } 21 | 22 | @Test 23 | public void testToStringOnEmptyPoint() throws Exception { 24 | ShapeWriter writer = ctx.getFormats().getWktWriter(); 25 | Point emptyPoint = ctx.makePoint(Double.NaN, Double.NaN); 26 | 27 | assertEquals("POINT EMPTY", writer.toString(emptyPoint)); 28 | } 29 | 30 | @Test 31 | public void testToStringOnEmptyShapeCollection() throws Exception { 32 | ShapeWriter writer = ctx.getFormats().getWktWriter(); 33 | ShapeCollection emptyCollection = ctx.makeCollection(new ArrayList<>()); 34 | 35 | assertEquals("GEOMETRYCOLLECTION EMPTY", writer.toString(emptyCollection)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/WktCustomShapeParserTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io; 10 | 11 | import org.locationtech.spatial4j.context.SpatialContext; 12 | import org.locationtech.spatial4j.context.SpatialContextFactory; 13 | import org.locationtech.spatial4j.shape.Shape; 14 | import org.locationtech.spatial4j.shape.impl.PointImpl; 15 | import org.junit.Test; 16 | 17 | import java.text.ParseException; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | 21 | public class WktCustomShapeParserTest extends WktShapeParserTest { 22 | 23 | static class CustomShape extends PointImpl { 24 | 25 | private final String name; 26 | 27 | /** 28 | * A simple constructor without normalization / validation. 29 | */ 30 | public CustomShape(String name, SpatialContext ctx) { 31 | super(0, 0, ctx); 32 | this.name = name; 33 | } 34 | } 35 | 36 | public WktCustomShapeParserTest() { 37 | super(makeCtx()); 38 | } 39 | 40 | private static SpatialContext makeCtx() { 41 | SpatialContextFactory factory = new SpatialContextFactory(); 42 | factory.readers.clear(); 43 | factory.readers.add( MyWKTShapeParser.class ); 44 | return factory.newSpatialContext(); 45 | } 46 | 47 | @Test 48 | public void testCustomShape() throws ParseException { 49 | assertEquals("customShape", ((CustomShape) wkt("customShape()")).name); 50 | assertEquals("custom3d", ((CustomShape) wkt("custom3d ()")).name);//number supported 51 | } 52 | 53 | @Test 54 | public void testNextSubShapeString() throws ParseException { 55 | 56 | WKTReader.State state = ((WKTReader)ctx.getFormats().getWktReader()).newState("OUTER(INNER(3, 5))"); 57 | state.offset = 0; 58 | 59 | assertEquals("OUTER(INNER(3, 5))", state.nextSubShapeString()); 60 | assertEquals("OUTER(INNER(3, 5))".length(), state.offset); 61 | 62 | state.offset = "OUTER(".length(); 63 | assertEquals("INNER(3, 5)", state.nextSubShapeString()); 64 | assertEquals("OUTER(INNER(3, 5)".length(), state.offset); 65 | 66 | state.offset = "OUTER(INNER(".length(); 67 | assertEquals("3", state.nextSubShapeString()); 68 | assertEquals("OUTER(INNER(3".length(), state.offset); 69 | } 70 | 71 | public static class MyWKTShapeParser extends WKTReader { 72 | public MyWKTShapeParser(SpatialContext ctx, SpatialContextFactory factory) { 73 | super(ctx, factory); 74 | } 75 | 76 | @Override 77 | protected State newState(String wkt) { 78 | //First few lines compile, despite newState() being protected. Just proving extensibility. 79 | WKTReader other = null; 80 | if (false) 81 | other.newState(wkt); 82 | 83 | return new State(wkt); 84 | } 85 | 86 | @Override 87 | public Shape parseShapeByType(State state, String shapeType) throws ParseException { 88 | Shape result = super.parseShapeByType(state, shapeType); 89 | if (result == null && shapeType.contains("custom")) { 90 | state.nextExpect('('); 91 | state.nextExpect(')'); 92 | return new CustomShape(shapeType, ctx); 93 | } 94 | return result; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/benchmark/ShapeBenchmarks.java: -------------------------------------------------------------------------------- 1 | package org.locationtech.spatial4j.io.benchmark; 2 | 3 | import org.locationtech.spatial4j.context.jts.JtsSpatialContext; 4 | import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; 5 | import org.locationtech.spatial4j.io.LegacyShapeWriter; 6 | import org.locationtech.spatial4j.io.ShapeIO; 7 | import org.locationtech.spatial4j.io.ShapeWriter; 8 | import org.locationtech.spatial4j.shape.Shape; 9 | 10 | import java.io.*; 11 | import java.text.NumberFormat; 12 | 13 | public class ShapeBenchmarks { 14 | 15 | public static void main(String[] args) throws Exception { 16 | 17 | 18 | JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); 19 | factory.geo = true; 20 | factory.normWrapLongitude = true; 21 | JtsSpatialContext ctx = new JtsSpatialContext(factory); 22 | 23 | PrintStream out = System.out; 24 | 25 | NumberFormat nf = NumberFormat.getPercentInstance(); 26 | InputStreamReader in = new InputStreamReader(ShapeBenchmarks.class.getResourceAsStream("/samples.txt")); 27 | try (BufferedReader br = new BufferedReader(in)) { 28 | String line; 29 | while ((line = br.readLine()) != null) { 30 | line = line.trim(); 31 | if(line.startsWith("#") || line.length()==0) { 32 | continue; 33 | } 34 | 35 | Shape shape = ctx.getFormats().getWktReader().read(line); 36 | 37 | double poly = ctx.getFormats().getWriter(ShapeIO.POLY).toString(shape).getBytes().length; 38 | 39 | out.println("Format | bytes | %poly | encoded"); 40 | out.println("------ | ----- | ----- | -------"); 41 | 42 | 43 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 44 | ctx.getBinaryCodec().writeShape(new DataOutputStream(baos), shape); 45 | 46 | out.print(" binary | "); 47 | out.print(baos.size()); 48 | out.print(" | "); 49 | out.print(nf.format(poly/baos.size())); 50 | out.print(" | "); 51 | out.print("..."); 52 | out.println(); 53 | 54 | for(ShapeWriter writer : ctx.getFormats().getWriters()) { 55 | if(writer instanceof LegacyShapeWriter) { 56 | continue; 57 | } 58 | 59 | String str = writer.toString(shape); 60 | out.print(writer.getFormatName()); 61 | out.print(" | "); 62 | out.print(str.length()); 63 | out.print(" | "); 64 | out.print(nf.format(poly/str.getBytes().length)); 65 | out.print(" | "); 66 | out.print(str); 67 | out.println(); 68 | } 69 | out.println(); 70 | out.println(); 71 | } 72 | } 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/jackson/JacksonGeoJSONReaderTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2017 Voyager Search 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jackson; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | 13 | import org.junit.Assert; 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | import org.locationtech.spatial4j.context.jts.JtsSpatialContext; 17 | import org.locationtech.spatial4j.io.GeneralGeoJSONTest; 18 | import org.locationtech.spatial4j.io.ShapeIO; 19 | import org.locationtech.spatial4j.io.jackson.ShapesAsGeoJSONModule; 20 | import org.locationtech.spatial4j.shape.Shape; 21 | import com.fasterxml.jackson.databind.ObjectMapper; 22 | 23 | /** 24 | * This test compares the jackson JSONWriter to the standard GeoJSON Writer 25 | */ 26 | public class JacksonGeoJSONReaderTest extends GeneralGeoJSONTest { 27 | 28 | @Before 29 | @Override 30 | public void setUp() { 31 | super.setUp(); 32 | ctx = JtsSpatialContext.GEO; 33 | 34 | ObjectMapper mapper = new ObjectMapper(); 35 | mapper.registerModule(new ShapesAsGeoJSONModule()); 36 | 37 | reader = new JacksonShapeReader(mapper); 38 | writer = ctx.getFormats().getWriter(ShapeIO.GeoJSON); 39 | writerForTests = writer; 40 | 41 | Assert.assertNotNull(reader); 42 | Assert.assertNotNull(writer); 43 | Assert.assertNotNull(writerForTests); 44 | } 45 | 46 | @Override 47 | @Test 48 | public void testEncodeBufferedLineString() throws Exception { 49 | // the JTS buffered LineString becomes a polygon! 50 | Shape out = reader.read( bufferedLineText() ); 51 | 52 | assertEquals(out.getClass(), bufferedLine().getClass()); 53 | } 54 | 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/jackson/JacksonGeoJSONWriterTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2017 Voyager Search 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jackson; 10 | 11 | import com.fasterxml.jackson.databind.ObjectMapper; 12 | import org.junit.Assert; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | import org.locationtech.spatial4j.context.SpatialContext; 16 | import org.locationtech.spatial4j.io.GeneralGeoJSONTest; 17 | import org.locationtech.spatial4j.io.ShapeIO; 18 | import org.locationtech.spatial4j.shape.Point; 19 | import org.locationtech.spatial4j.shape.Rectangle; 20 | import org.locationtech.spatial4j.shape.Shape; 21 | import org.locationtech.spatial4j.shape.SpatialRelation; 22 | import org.locationtech.spatial4j.shape.jts.JtsGeometry; 23 | 24 | /** 25 | * This test compares the jackson JSONWriter to the standard GeoJSON Writer 26 | */ 27 | public class JacksonGeoJSONWriterTest extends GeneralGeoJSONTest { 28 | 29 | @Before 30 | @Override 31 | public void setUp() { 32 | super.setUp(); 33 | 34 | ObjectMapper mapper = new ObjectMapper(); 35 | mapper.registerModule(new ShapesAsGeoJSONModule()); 36 | JacksonShapeWriter w = new JacksonShapeWriter(mapper); 37 | 38 | reader = ctx.getFormats().getReader(ShapeIO.GeoJSON); 39 | writer = w; 40 | writerForTests = writer; 41 | 42 | Assert.assertNotNull(reader); 43 | Assert.assertNotNull(writer); 44 | Assert.assertNotNull(writerForTests); 45 | } 46 | 47 | 48 | @Test 49 | public void testWriteUnknownAsWKT() throws Exception { 50 | // some anonymous impl that doesn't do anything 51 | Shape shape = new Shape() { 52 | @Override 53 | public SpatialRelation relate(Shape other) { 54 | throw new UnsupportedOperationException("TODO unimplemented");//TODO 55 | } 56 | 57 | @Override 58 | public Rectangle getBoundingBox() { 59 | throw new UnsupportedOperationException("TODO unimplemented");//TODO 60 | } 61 | 62 | @Override 63 | public boolean hasArea() { 64 | throw new UnsupportedOperationException("TODO unimplemented");//TODO 65 | } 66 | 67 | @Override 68 | public double getArea(SpatialContext ctx) { 69 | throw new UnsupportedOperationException("TODO unimplemented");//TODO 70 | } 71 | 72 | @Override 73 | public Point getCenter() { 74 | throw new UnsupportedOperationException("TODO unimplemented");//TODO 75 | } 76 | 77 | @Override 78 | public Shape getBuffered(double distance, SpatialContext ctx) { 79 | throw new UnsupportedOperationException("TODO unimplemented");//TODO 80 | } 81 | 82 | @Override 83 | public boolean isEmpty() { 84 | throw new UnsupportedOperationException("TODO unimplemented");//TODO 85 | } 86 | 87 | @Override 88 | public SpatialContext getContext() { 89 | return SpatialContext.GEO; 90 | } 91 | }; 92 | 93 | String str = writer.toString(shape); 94 | Assert.assertTrue(str.indexOf("wkt")>0); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/jackson/JacksonShapeReader.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2017 Voyager Search 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jackson; 10 | 11 | import java.io.IOException; 12 | import java.io.Reader; 13 | import java.text.ParseException; 14 | 15 | import org.locationtech.spatial4j.exception.InvalidShapeException; 16 | import org.locationtech.spatial4j.io.ShapeReader; 17 | import org.locationtech.spatial4j.shape.Shape; 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | 21 | /** 22 | * This is really just a utility for testing 23 | */ 24 | public class JacksonShapeReader implements ShapeReader { 25 | 26 | final ObjectMapper mapper; 27 | 28 | public JacksonShapeReader(ObjectMapper m) { 29 | this.mapper = m; 30 | } 31 | 32 | @Override 33 | public String getFormatName() { 34 | return getClass().getSimpleName(); 35 | } 36 | 37 | @Override 38 | public Shape read(Object value) throws IOException, ParseException, InvalidShapeException { 39 | String str = value.toString(); 40 | return mapper.readValue(str, Shape.class); 41 | } 42 | 43 | @Override 44 | public Shape readIfSupported(Object value) throws InvalidShapeException { 45 | try { 46 | return read(value); 47 | } 48 | catch (IOException | ParseException e) { 49 | return null; 50 | } 51 | } 52 | 53 | @Override 54 | public Shape read(Reader reader) throws IOException, ParseException, InvalidShapeException { 55 | return mapper.readValue(reader, Shape.class); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/jackson/JacksonShapeWriter.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2017 Voyager Search 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jackson; 10 | 11 | import java.io.IOException; 12 | import java.io.Writer; 13 | 14 | import org.locationtech.spatial4j.io.ShapeWriter; 15 | import org.locationtech.spatial4j.shape.Shape; 16 | 17 | import com.fasterxml.jackson.core.JsonProcessingException; 18 | import com.fasterxml.jackson.databind.ObjectMapper; 19 | 20 | /** 21 | * This is really just a utility for testing 22 | */ 23 | public class JacksonShapeWriter implements ShapeWriter { 24 | 25 | final ObjectMapper mapper; 26 | 27 | public JacksonShapeWriter(ObjectMapper m) { 28 | this.mapper = m; 29 | } 30 | 31 | @Override 32 | public String getFormatName() { 33 | return getClass().getSimpleName(); 34 | } 35 | 36 | @Override 37 | public void write(Writer output, Shape shape) throws IOException { 38 | output.write(toString(shape)); 39 | } 40 | 41 | @Override 42 | public String toString(Shape shape) { 43 | try { 44 | return mapper.writeValueAsString(shape); 45 | } 46 | catch (JsonProcessingException e) { 47 | throw new RuntimeException(e); 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/jackson/ObjectWithGeometry.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2017 Voyager Search 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jackson; 10 | 11 | import org.locationtech.spatial4j.shape.Shape; 12 | 13 | import org.locationtech.jts.geom.Geometry; 14 | 15 | public class ObjectWithGeometry { 16 | public String name; 17 | public Geometry geo; 18 | public Shape shape; 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/io/jackson/SimpleJacksonTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2017 Voyager Search 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.io.jackson; 10 | 11 | import com.fasterxml.jackson.annotation.JsonInclude; 12 | import com.fasterxml.jackson.databind.ObjectMapper; 13 | import com.fasterxml.jackson.databind.SerializationFeature; 14 | import org.junit.Test; 15 | import org.locationtech.jts.geom.Coordinate; 16 | import org.locationtech.spatial4j.context.jts.JtsSpatialContext; 17 | import org.locationtech.spatial4j.shape.RandomizedShapeTest; 18 | import org.locationtech.spatial4j.shape.jts.JtsShapeFactory; 19 | 20 | import java.io.IOException; 21 | 22 | import static org.junit.Assert.assertEquals; 23 | 24 | public class SimpleJacksonTest extends RandomizedShapeTest { 25 | 26 | public SimpleJacksonTest() { 27 | super(JtsSpatialContext.GEO); 28 | } 29 | 30 | @Test 31 | public void testReadWriteShapeAsGeoJSON() throws IOException { 32 | ObjectWithGeometry obj = new ObjectWithGeometry(); 33 | obj.name = "Hello"; 34 | obj.shape = ctx.getShapeFactory().pointXY(11,12); // Spatial4j type 35 | obj.geo = null; // 36 | 37 | ObjectMapper mapper = new ObjectMapper(); 38 | mapper.enable(SerializationFeature.INDENT_OUTPUT); 39 | mapper.registerModule(new ShapesAsGeoJSONModule()); 40 | 41 | String json = mapper.writeValueAsString(obj); 42 | 43 | ObjectWithGeometry out = mapper.readValue(json, ObjectWithGeometry.class); 44 | assertEquals(obj.shape, out.shape); 45 | } 46 | 47 | @Test 48 | public void testReadWriteJtsAsWKT() throws IOException { 49 | final JtsShapeFactory shapeFactory = ((JtsSpatialContext) ctx).getShapeFactory(); 50 | 51 | ObjectWithGeometry obj = new ObjectWithGeometry(); 52 | obj.name = "Hello"; 53 | obj.shape = null; 54 | obj.geo = shapeFactory.getGeometryFactory().createPoint(new Coordinate(11, 12)); // JTS type 55 | 56 | ObjectMapper objectMapper = new ObjectMapper(); 57 | objectMapper.registerModule(new ShapesAsWKTModule()); 58 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 59 | 60 | String json = objectMapper.writeValueAsString(obj); 61 | 62 | assertEquals("{\"name\":\"Hello\",\"geo\":\"POINT (11 12)\"}", json); 63 | 64 | ObjectWithGeometry deserialized = objectMapper.readValue(json, ObjectWithGeometry.class); 65 | assertEquals(obj.geo, deserialized.geo); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/shape/BufferedLineStringTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Voyager Search and MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.shape; 10 | 11 | import com.carrotsearch.randomizedtesting.RandomizedTest; 12 | import org.locationtech.spatial4j.context.SpatialContext; 13 | import org.locationtech.spatial4j.context.SpatialContextFactory; 14 | import org.locationtech.spatial4j.shape.impl.BufferedLineString; 15 | import org.locationtech.spatial4j.shape.impl.RectangleImpl; 16 | import org.junit.Test; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | public class BufferedLineStringTest extends RandomizedTest { 22 | 23 | private final SpatialContext ctx = new SpatialContextFactory() 24 | {{geo = false; worldBounds = new RectangleImpl(-100, 100, -50, 50, null);}}.newSpatialContext(); 25 | 26 | 27 | @Test 28 | public void testRectIntersect() { 29 | new RectIntersectionTestHelper(ctx) { 30 | 31 | @Override 32 | protected BufferedLineString generateRandomShape(Point nearP) { 33 | Rectangle nearR = randomRectangle(nearP); 34 | int numPoints = 2 + randomInt(3);//2-5 points 35 | 36 | ArrayList points = new ArrayList<>(numPoints); 37 | while (points.size() < numPoints) { 38 | points.add(randomPointIn(nearR)); 39 | } 40 | double maxBuf = Math.max(nearR.getWidth(), nearR.getHeight()); 41 | double buf = Math.abs(randomGaussian()) * maxBuf / 4; 42 | buf = randomInt((int) divisible(buf)); 43 | return new BufferedLineString(points, buf, ctx); 44 | } 45 | 46 | protected Point randomPointInEmptyShape(BufferedLineString shape) { 47 | List points = shape.getPoints(); 48 | return points.get(randomInt(points.size() - 1)); 49 | } 50 | }.testRelateWithRectangle(); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/shape/RoundingDistCalc.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.shape; 10 | 11 | import org.locationtech.spatial4j.context.SpatialContext; 12 | import org.locationtech.spatial4j.distance.AbstractDistanceCalculator; 13 | import org.locationtech.spatial4j.distance.DistanceCalculator; 14 | 15 | /** Ameliorates some random tests cases in which shapes barely tough or barely not 16 | * touch. */ 17 | class RoundingDistCalc extends AbstractDistanceCalculator { 18 | DistanceCalculator delegate; 19 | 20 | RoundingDistCalc(DistanceCalculator delegate) { 21 | this.delegate = delegate; 22 | } 23 | 24 | double round(double val) { 25 | final double scale = Math.pow(10,10/*digits precision*/); 26 | return Math.round(val * scale) / scale; 27 | } 28 | 29 | @Override 30 | public double distance(Point from, double toX, double toY) { 31 | return round(delegate.distance(from, toX, toY)); 32 | } 33 | 34 | @Override 35 | public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse) { 36 | return delegate.pointOnBearing(from, distDEG, bearingDEG, ctx, reuse); 37 | } 38 | 39 | @Override 40 | public Rectangle calcBoxByDistFromPt(Point from, double distDEG, SpatialContext ctx, Rectangle reuse) { 41 | return delegate.calcBoxByDistFromPt(from, distDEG, ctx, reuse); 42 | } 43 | 44 | @Override 45 | public double calcBoxByDistFromPt_yHorizAxisDEG(Point from, double distDEG, SpatialContext ctx) { 46 | return delegate.calcBoxByDistFromPt_yHorizAxisDEG(from, distDEG, ctx); 47 | } 48 | 49 | @Override 50 | public double area(Rectangle rect) { 51 | return delegate.area(rect); 52 | } 53 | 54 | @Override 55 | public double area(Circle circle) { 56 | return delegate.area(circle); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/shape/ShapeCollectionTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 MITRE 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.shape; 10 | 11 | import org.locationtech.spatial4j.TestLog; 12 | import org.locationtech.spatial4j.context.SpatialContext; 13 | import org.locationtech.spatial4j.context.SpatialContextFactory; 14 | import org.locationtech.spatial4j.shape.impl.RectangleImpl; 15 | import org.junit.Rule; 16 | import org.junit.Test; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | 22 | import static org.junit.Assert.assertEquals; 23 | import static org.junit.Assert.fail; 24 | import static org.locationtech.spatial4j.shape.SpatialRelation.CONTAINS; 25 | 26 | public class ShapeCollectionTest extends RandomizedShapeTest { 27 | 28 | public static final String WORLD180 = getLonRangeString(SpatialContext.GEO.getWorldBounds()); 29 | 30 | protected static String getLonRangeString(Rectangle bbox) { 31 | return bbox.getMinX()+" "+bbox.getMaxX(); 32 | } 33 | 34 | @Rule 35 | public final TestLog testLog = TestLog.instance; 36 | 37 | @Test 38 | public void testBbox() { 39 | validateWorld(-180, 180, -180, 180); 40 | validateWorld(-180, 0, 0, +180); 41 | validateWorld(-90, +90, +90, -90); 42 | } 43 | 44 | @Test 45 | public void testBboxNotWorldWrap() { 46 | ctx = SpatialContext.GEO; 47 | //doesn't contain 102, thus shouldn't world-wrap 48 | Rectangle r1 = ctx.makeRectangle(-92, 90, -10, 10); 49 | Rectangle r2 = ctx.makeRectangle(130, 172, -10, 10); 50 | Rectangle r3 = ctx.makeRectangle(172, -60, -10, 10); 51 | ShapeCollection s = new ShapeCollection<>(Arrays.asList(r1, r2, r3), ctx); 52 | assertEquals("130.0 90.0", getLonRangeString(s.getBoundingBox())); 53 | // note: BBoxCalculatorTest thoroughly tests the longitude range 54 | } 55 | 56 | 57 | private void validateWorld(double r1MinX, double r1MaxX, double r2MinX, double r2MaxX) { 58 | ctx = SpatialContext.GEO; 59 | Rectangle r1 = ctx.makeRectangle(r1MinX, r1MaxX, -10, 10); 60 | Rectangle r2 = ctx.makeRectangle(r2MinX, r2MaxX, -10, 10); 61 | 62 | ShapeCollection s = new ShapeCollection<>(Arrays.asList(r1, r2), ctx); 63 | assertEquals(WORLD180, getLonRangeString(s.getBoundingBox())); 64 | 65 | //flip r1, r2 order 66 | s = new ShapeCollection<>(Arrays.asList(r2, r1), ctx); 67 | assertEquals(WORLD180, getLonRangeString(s.getBoundingBox())); 68 | } 69 | 70 | @Test 71 | public void testRectIntersect() { 72 | SpatialContext ctx = new SpatialContextFactory() 73 | {{geo = false; worldBounds = new RectangleImpl(-100, 100, -50, 50, null);}}.newSpatialContext(); 74 | 75 | new ShapeCollectionRectIntersectionTestHelper(ctx).testRelateWithRectangle(); 76 | } 77 | 78 | @Test 79 | public void testGeoRectIntersect() { 80 | ctx = SpatialContext.GEO; 81 | new ShapeCollectionRectIntersectionTestHelper(ctx).testRelateWithRectangle(); 82 | } 83 | 84 | private class ShapeCollectionRectIntersectionTestHelper extends RectIntersectionTestHelper { 85 | 86 | private ShapeCollectionRectIntersectionTestHelper(SpatialContext ctx) { 87 | super(ctx); 88 | } 89 | 90 | @Override 91 | protected ShapeCollection generateRandomShape(Point nearP) { 92 | testLog.log("Break on nearP.toString(): {}", nearP); 93 | List shapes = new ArrayList<>(); 94 | int count = randomIntBetween(1,4); 95 | for(int i = 0; i < count; i++) { 96 | //1st 2 are near nearP, the others are anywhere 97 | shapes.add(randomRectangle( i < 2 ? nearP : null)); 98 | } 99 | ShapeCollection shapeCollection = new ShapeCollection<>(shapes, ctx); 100 | 101 | //test shapeCollection.getBoundingBox(); 102 | Rectangle msBbox = shapeCollection.getBoundingBox(); 103 | if (shapes.size() == 1) { 104 | assertEquals(shapes.get(0), msBbox.getBoundingBox()); 105 | } else { 106 | for (Rectangle shape : shapes) { 107 | assertRelation("bbox contains shape", CONTAINS, msBbox, shape); 108 | } 109 | if (ctx.isGeo() && msBbox.getMinX() == -180 && msBbox.getMaxX() == 180) { 110 | int lonTest = randomIntBetween(-180, 180); 111 | boolean valid = false; 112 | for (Rectangle shape : shapes) { 113 | if (shape.relateXRange(lonTest, lonTest).intersects()) { 114 | valid = true; 115 | break; 116 | } 117 | } 118 | if (!valid) 119 | fail("ShapeCollection bbox world-wrap doesn't contain "+lonTest+" for shapes: "+shapes); 120 | } 121 | } 122 | return shapeCollection; 123 | } 124 | 125 | protected Point randomPointInEmptyShape(ShapeCollection shape) { 126 | Rectangle r = (Rectangle) shape.getShapes().get(0); 127 | return randomPointIn(r); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/test/java/org/locationtech/spatial4j/shape/impl/BBoxCalculatorTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 David Smiley 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Apache License, Version 2.0 which 5 | * accompanies this distribution and is available at 6 | * http://www.apache.org/licenses/LICENSE-2.0.txt 7 | ******************************************************************************/ 8 | 9 | package org.locationtech.spatial4j.shape.impl; 10 | 11 | import com.carrotsearch.randomizedtesting.annotations.Repeat; 12 | import org.locationtech.spatial4j.context.SpatialContext; 13 | import org.locationtech.spatial4j.shape.RandomizedShapeTest; 14 | import org.locationtech.spatial4j.shape.Rectangle; 15 | import org.locationtech.spatial4j.shape.SpatialRelation; 16 | import org.junit.Test; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import static org.junit.Assert.assertEquals; 22 | import static org.junit.Assert.assertFalse; 23 | import static org.junit.Assert.assertTrue; 24 | 25 | public class BBoxCalculatorTest extends RandomizedShapeTest { 26 | 27 | public BBoxCalculatorTest() { 28 | super(SpatialContext.GEO); 29 | } 30 | 31 | // note: testing latitude would be so simple that's effectively the same code as the code to be tested. So I don't. 32 | 33 | 34 | @Test @Repeat(iterations = 100) 35 | public void testGeoLongitude() { 36 | BBoxCalculator calc = new BBoxCalculator(ctx); 37 | final int numShapes = randomIntBetween(1, 4);//inclusive 38 | List rects = new ArrayList<>(numShapes); 39 | for (int i = 0; i < numShapes; i++) { 40 | Rectangle rect = randomRectangle(30);// divisible by 41 | rects.add(rect); 42 | calc.expandRange(rect); 43 | } 44 | Rectangle boundary = calc.getBoundary(); 45 | if (numShapes == 1) { 46 | assertEquals(rects.get(0), boundary); 47 | return; 48 | } 49 | 50 | // If the boundary is the world-bounds, check that it's right. 51 | if (boundary.getMinX() == -180 && boundary.getMaxX() == 180) { 52 | // each longitude should be present in at least one shape: 53 | for (int lon = -180; lon <= +180; lon++) { 54 | assertTrue(atLeastOneRectHasLon(rects, lon)); 55 | } 56 | return; 57 | } 58 | 59 | // Test that it contains all shapes: 60 | for (Rectangle rect : rects) { 61 | assertRelation(SpatialRelation.CONTAINS, boundary, rect); 62 | } 63 | 64 | // Test that the left & right are boundaries: 65 | assertTrue(atLeastOneRectHasLon(rects, boundary.getMinX())); 66 | assertFalse(atLeastOneRectHasLon(rects, normX(boundary.getMinX() - 0.5))); 67 | 68 | assertTrue(atLeastOneRectHasLon(rects, boundary.getMaxX())); 69 | assertFalse(atLeastOneRectHasLon(rects, normX(boundary.getMaxX() + 0.5))); 70 | 71 | // Test that this is the smallest enclosing boundary by ensuring the gap (opposite the bbox) is 72 | // the largest: 73 | if (boundary.getWidth() > 180) { // conversely if wider than 180 then no wider gap is possible 74 | double biggerGap = 360.0 - boundary.getWidth() + 0.5; 75 | for (Rectangle rect : rects) { 76 | // try to see if a bigger gap could lie to the right of this rect 77 | double gapRectLeft = rect.getMaxX() + 0.25; 78 | double gapRectRight = gapRectLeft + biggerGap; 79 | Rectangle testGap = makeNormRect(gapRectLeft, gapRectRight, -90, 90); 80 | boolean fits = true; 81 | for (Rectangle rect2 : rects) { 82 | if (rect2.relate(testGap).intersects()) { 83 | fits = false; 84 | break; 85 | } 86 | } 87 | assertFalse(fits);//should never fit because it's larger than the biggest gap 88 | } 89 | } 90 | } 91 | 92 | private boolean atLeastOneRectHasLon(List rects, double lon) { 93 | for (Rectangle rect : rects) { 94 | if (rect.relateXRange(lon, lon).intersects()) { 95 | return true; 96 | } 97 | } 98 | return false; 99 | } 100 | 101 | } -------------------------------------------------------------------------------- /src/test/resources/samples.txt: -------------------------------------------------------------------------------- 1 | POINT(100.1 0.1) 2 | 3 | LINESTRING (100.1 0.1, 101.1 1.1) 4 | 5 | POLYGON ((100.1 0.1, 101.1 0.1, 101.1 1.1, 100.1 1.1, 100.1 0.1)) 6 | 7 | POLYGON ((100.1 0.1, 101.1 0.1, 101.1 1.1, 100.1 1.1, 100.1 0.1), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2)) 8 | 9 | MULTILINESTRING ((100.1 0.1, 101.1 1.1), (102.1 2.1, 103.1 3.1)) 10 | 11 | MULTIPOINT ((100.1 0.1), (101.1 1.1)) 12 | 13 | ENVELOPE(100.1, 101.1, 1.1, 0.1) 14 | 15 | 16 | --------------------------------------------------------------------------------