pax_global_header00006660000000000000000000000064124176763350014530gustar00rootroot0000000000000052 comment=c4b76a92063750805d6905c0da776490968e9deb spatial4j-0.4-0.4.1/000077500000000000000000000000001241767633500137245ustar00rootroot00000000000000spatial4j-0.4-0.4.1/.gitignore000077500000000000000000000000351241767633500157150ustar00rootroot00000000000000/*.ipr /.idea/ *.iml target/ spatial4j-0.4-0.4.1/.travis.yml000066400000000000000000000002041241767633500160310ustar00rootroot00000000000000language: java script: mvn -Drandomized.multiplier=10 clean verify jdk: - openjdk6 notifications: email: - dev@spatial4j.comspatial4j-0.4-0.4.1/CHANGES.md000066400000000000000000000113561241767633500153240ustar00rootroot00000000000000## VERSION 0.4 DATE: 20 January 2014 ### User/API changes & Notes: * It used to be the case that rectangular polygons provided in WKT had to have its vertexes given in counter-clockwise order to indicate which way around the earth it went. The default is now the shorter width (less than 180 degrees). This setting can be changed via the “datelineRule” setting. To avoid ambiguity, just use the ENVELOPE syntax. * Unless you refer to the SpatialContext.GEO or JtsSpatialContext.GEO instances, the only true way to create a context is to use the SpatialContextFactory (and including the JTS based subclass), which have a host of settings that make Spatial4j very customizable. * SpatialContext.readShape(String) and toString(Shape) are still deprecated but will be removed in the next release. You should instead read WKT via the new method readShapeFromWkt(String). This also means the worldBounds initialization via SpatialContextFactory should be specified as an ENVELOPE WKT. Spatial4j will no longer provide a way to generate WKT from a shape although it’s pretty easy to use JTS for that. * In Spatial4j’s older JTS based implementation, it used to be the case that when reading WKT, latitudes would be normalized into the -90 to 90 range; it’s now an error. Longitudes outside of -180 to 180 were also normalized and this is now an error too. Longitude normalization can be enabled with the “normWrapLongitude” option. * Newly deprecated: ParseUtils, some methods and constants in DistanceUtils (which seemed only used by Solr), and SpatialContext constructors other than that which takes a SpatialContextFactory. In the "io" package, these classes were **deleted**: LineReader, SampleData..., Geoname... Copies of those were moved to the _Spatial Solr Sandbox_ project. ### Features: * New built-in WKT parser separate from JTS’s. JTS is no longer required for WKT but still is for certain shapes like polygons. It supports the ENVELOPE rectangle syntax seen in OGC’s CQL spec, and it has a custom BUFFER(shape, distance) that can be used to buffer a shape. A buffered point is a circle. Parse via SpatialContext.readShapeFromWkt(String). JTS’s WKT parser can be used as an alternative via the JtsWKTReaderShapeParser class. * New ShapeCollection shape. It’s similar to a JTS/OGC GeometryCollection and is used to hold multiple shapes of the same or different types. * New BufferedLine & BufferedLineString shapes. A 0 buffer results in effectively Line & LineString shapes. A non-0 buffer is buffered in a rectangular corner fashion (i.e. a rectangle on an angle). This new shape does not yet support geodesics (e.g. the dateline), and so JTS’s equivalents are used by default for now. * JtsGeometry can now be “indexed”. It builds an internal index to speed up subsequent calculations. It can be done automatically when read from WKT via the new “autoIndex” option. * JtsGeometry shapes when read via WKT have configurable validation and automatic repair via a couple algorithms. See the new “validationRule” setting. * Shapes can now be “empty”; see Shape.isEmpty(). * Configurable dateline crossing algorithm: none, width180, ccwRect * JTS’s PrecisionModel is configurable from the JtsSpatialContextFactory. * A new “BinaryCodec” is added which is a binary format for shapes; see SpatialContext.getBinaryCodec(). In this release it’s a pretty straight-forward implementation, but might get optimized for more compactness in the future. When using JTS and the “floating_single” PrecisionModel, it will use 4-byte floats instead of 8-byte doubles for shapes other than JtsGeometry (i.e. non-polygons). JtsGeometry is written in WKB format which is always 8-byte doubles. In the future, WKB will not likely be used. * New SpatialContext.calcDistance convenience methods to avoid referencing DistanceCalculator * New DistanceCalculator.withinDistance method used by Euclidean circle avoids a Math.sqrt call. * DistanceUtils.DEG_TO_KM & KM_TO_DEG convenience constants ### Bugs: * Geodetic circles sometimes computed the wrong relationship with a rectangle. * JtsGeometry now calculates the minimum geodetic bounding box (also used by ShapeCollection). This fixed an issue where Lucene-spatial would give Fiji a much courser grid granularity than it deserved. * JtsGeometry shapes that had > 180 width sometimes resulted in an exception. (#41 & SOLR-4879) * Converting a Euclidean circle to a polygonal geometry was wrong. (#44) * DistanceUtil.vectorDistance for Manhattan style was wrong. (LUCENE-3814) * More consistently wrap shape parsing errors with InvalidShapeException or ParseException as applicable. spatial4j-0.4-0.4.1/LICENSE.txt000066400000000000000000000261361241767633500155570ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. spatial4j-0.4-0.4.1/README.md000066400000000000000000000165241241767633500152130ustar00rootroot00000000000000# Spatial4j Spatial4j is a general purpose spatial / geospatial [ASL](http://www.apache.org/licenses/LICENSE-2.0.html) licensed open-source Java library. It's core capabilities are 3-fold: to provide common shapes that can work in Euclidean and geodesic (surface of sphere) world models, to provide distance calculations and other math, and to read shapes from [WKT](http://en.wikipedia.org/wiki/Well-known_text) formatted strings. Spatial4j is a [pending](http://www.locationtech.org/proposals/spatial4j) member of the [LocationTech](http://www.locationtech.org) Industry Working Group family of projects. If you are working with spatial grid-square indexing schemes, be it [Geohash](http://en.wikipedia.org/wiki/Geohash) or something custom, then you are likely to find especially high utility from Spatial4j -- doubly-so for Apache Software Foundation projects due to restrictions on use of LGPL software there. ## Shapes and Other Features The main part of Spatial4j is its collection of shapes. Shapes in Spatial4j have these features: * Compute its lat-lon bounding box. * Compute an area. For some shapes its more of an estimate. * Compute if it contains a provided point. * Compute the relationship to a lat-lon rectangle. Relationships are: CONTAINS, WITHIN, DISJOINT, INTERSECTS. Note that Spatial4j doesn't have a notion of "touching". Spatial4j has a variety of shapes that operate in Euclidean-space -- i.e. a flat 2D plane. Most shapes are augmented to support a wrap-around at `X` -180/+180 for compatibility with latitude & longitudes, which is effectively a cylindrical model. But the real bonus is its circle (i.e. point-radius shape that can operate on a surface-of-a-sphere model. See below for further info. I often use the term "geodetic" or "geodesic" or "geo" as synonymous with that model but technically those words have a more broad meaning. | Shape | Euclidean | Cylindrical | Spherical| | -----------|:---------:|:-----------:|:--------:| | **Point** | Y | Y | Y | | **Rectangle** | Y | Y | Y | | **Circle** | Y | N | Y | | **LineString** | Y | N | N | | **Buffered L/S** | Y | N | N | | **Polygon** | Y | Y | N | | **ShapeCollection** | Y | Y | Y | * The Rectangle shape exists in the spherical model as a lat-lon rectangle, which basically means it's math is no different than cylindrical. * Polygons don't support pole-wrap (sorry, no Antarctica polygon); just dateline-cross. Polygons are supported by wrapping JTS's `Geometry`, which is to say that most of the fundamental logic for that shape is implemented by JTS. ### Other Features * Parse shapes from strings in [WKT](http://en.wikipedia.org/wiki/Well-known_text) to include the ENVELOPE extension from CQL, plus a Spatial4j custom BUFFER operation. Buffering a point gets you a Circle. * 3 great-circle distance calculators: Law of Cosines, Haversine, Vincenty * The code is well tested and it's monitored via [Travis-CI](http://travis-ci.org/#!/spatial4j/spatial4j) continuous integration. * Spatial4j has no dependencies on other libraries except for JTS, which is only triggered if you use Polygons, or obviously if you use any of the classes prefixed with "Jts". ## Why not use JTS? Why should you use Spatial4j? Spatial4j was born out of an unmet need from other open-source Java software. [JTS](https://sourceforge.net/projects/jts-topo-suite/) is the most popular spatial library in Java. JTS is powerful but it only supports Euclidean geometry (no geodesics), it has no Circle shape, and it is LGPL licensed (which is planned to change soon). Spatial4j has a geodesic circle implementation, and it wraps JTS geometries to add dateline-wrap support (no pole wrap yet). A geodesic circle implementation (i.e. point-radius on surface of a sphere), has been non-trivial; see for yourself and look at the extensive testing. Presumably many applications will use a polygon substitute for a circle, however note that not only is it an approximation, but common algorithms *inscribe* instead of *circumscribe* the circle. The result is a polygon that doesn't quite completely cover the intended shape, potentially resulting in not finding desired data when applied to the information-retrieval domain (i.e. indexing/search in Apache Lucene) where it is usually better to find a false match versus not find a positive match when making approximations. Also, Spatial4j's implementation goes to some lengths to be efficient by only calculating the great-circle-distance a minimum number of times in order to find the intersection relationship with a rectangle. Even computing the bounding-box of this shape was non-obvious, as the initial algorithm lifted from the web at a popular site turned out to be false. ## Getting Started **[Javadoc API](https://spatial4j.github.io/spatial4j/apidocs/)** The facade to all of Spatial4j is the [`SpatialContext`](https://spatial4j.github.io/spatial4j/apidocs/com/spatial4j/core/context/SpatialContext.html). It acts as a factory for shapes and it holds references to most other classes you might use and/or it has convenience methods for them. For example you can get a [`DistanceCalculator`](https://spatial4j.github.io/spatial4j/apidocs/com/spatial4j/core/distance/DistanceCalculator.html) but if you just want to calculate the distance then the context has a method for that. To get a SpatialContext (or just "context" for short), you could use a global singleton `SpatialContext.GEO` or `JtsSpatialContext.GEO` with both use geodesic surface-of-sphere calculations (when available); the JTS one principally adds Polygon support. If you want a non-geodesic implementation or you want to customize one of many options, then instantiate a [`SpatialContextFactory`](https://spatial4j.github.io/spatial4j/apidocs/com/spatial4j/core/context/SpatialContextFactory.html) (or `JtsSpatialContextFactory`), set the options, then invoke `newSpatialContext()`. If you have a set of name-value string pairs, perhaps from a java properties file, then instead use the static `makeSpatialContext(map, classLoader)` method which adds a lot of flexibility to the configuration initialization versus hard-coding it. *You should generally avoid calling constructors for anything in Spatial4j except for the `SpatialContextFactory`.* Constructors aren't strictly forbidden but the factories are there to provide an extension point / abstraction, so don't side-step them unless there's a deliberate reason. ## Miscellaneous Discuss Spatial4j on our [mailing list](http://spatial4j.16575.n6.nabble.com/). View metadata about the project as generated by Maven: [maven site](https://spatial4j.github.io/spatial4j/). Spatial4j has been ported to .NET (C#) where it is appropriately named [Spatial4n](https://github.com/synhershko/Spatial4n). ### Future Road Map Ideas * Support for projections by incorporating Proj4j * More surface-of-sphere implemented shapes (LineString, Polygon) * Polygon pole wrap * Multi-dimensional? ### History Before Spatial4j, there was [Lucene Spatial Playground](http://code.google.com/p/lucene-spatial-playground/) (LSP) and from this work a generic core spatial library emerged, independent of Lucene: Spatial4j. The other parts of LSP were either merged into Lucene / Solr itself or were migrated to [Spatial Solr Sandbox](https://github.com/ryantxu/spatial-solr-sandbox).spatial4j-0.4-0.4.1/eclipse000077500000000000000000000003151241767633500152750ustar00rootroot00000000000000rm */.classpath rm */.project rm */*/.classpath rm */*/.project rm */*/*/.classpath rm */*/*/.project rm */*/*/*/.classpath rm */*/*/*/.project mvn eclipse:eclipse -P updateLucene spatial4j-0.4-0.4.1/pom.xml000066400000000000000000000235721241767633500152520ustar00rootroot00000000000000 4.0.0 org.sonatype.oss oss-parent 7 com.spatial4j spatial4j 0.4.1 bundle Spatial4J Spatial4j is a general purpose spatial / geospatial ASL licensed open-source Java library. It's core capabilities are 3-fold: to provide common geospatially-aware shapes, to provide distance calculations and other math, and to read shapes in WKT format. LocationTech http://locationtech.org https://github.com/spatial4j/spatial4j The Apache Software License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo GitHub https://github.com/spatial4j/spatial4j/issues Travis-CI https://travis-ci.org/spatial4j/spatial4j dev@lists.spatial4j.com http://spatial4j.16575.x6.nabble.com http://lists.spatial4j.com/pipermail/dev-spatial4j.com/ dev@lists.spatial4j.com scm:git:git@github.com:spatial4j/spatial4j.git scm:git:git@github.com:spatial4j/spatial4j.git https://github.com/spatial4j/spatial4j UTF-8 1.7.5 2.2.1 com.vividsolutions jts 1.13 true xerces xercesImpl junit junit 4.11 test org.slf4j slf4j-simple ${slf4j.version} test com.carrotsearch.randomizedtesting randomizedtesting-runner 2.0.15 test org.apache.maven.plugins maven-compiler-plugin 3.1 1.6 1.6 true true org.apache.maven.plugins maven-surefire-plugin 2.16 0 de.thetaphi forbiddenapis 1.4 check true jdk-unsafe jdk-deprecated false 1.6 org.apache.felix maven-bundle-plugin 2.4.0 com.spatial4j.core*;version=${project.version} true org.apache.maven.plugins maven-site-plugin 3.3 org.apache.maven.plugins maven-scm-publish-plugin 1.0-beta-2 ${project.build.directory}/scmpublish Publishing javadoc for ${project.artifactId}:${project.version} ${project.reporting.outputDirectory} true scm:git:file://${project.basedir} gh-pages org.apache.maven.plugins maven-project-info-reports-plugin 2.7 org.apache.maven.plugins maven-pmd-plugin 3.0.1 true 100 1.6 org.codehaus.mojo findbugs-maven-plugin 2.5.3 true org.apache.maven.plugins maven-jxr-plugin 2.4 jxr org.apache.maven.plugins maven-surefire-report-plugin 2.16 org.apache.maven.plugins maven-javadoc-plugin 2.9.1
Spatial4j, ${project.version}
Spatial4j, ${project.version}
Spatial4j, ${project.version} http://tsusiatsoftware.net/jts/javadoc/
javadoc
spatial4j-0.4-0.4.1/src/000077500000000000000000000000001241767633500145135ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/main/000077500000000000000000000000001241767633500154375ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/main/java/000077500000000000000000000000001241767633500163605ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/main/java/com/000077500000000000000000000000001241767633500171365ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/000077500000000000000000000000001241767633500210315ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/000077500000000000000000000000001241767633500217615ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/context/000077500000000000000000000000001241767633500234455ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/context/SpatialContext.java000066400000000000000000000323021241767633500272520ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.context; import com.spatial4j.core.distance.CartesianDistCalc; import com.spatial4j.core.distance.DistanceCalculator; import com.spatial4j.core.distance.DistanceUtils; import com.spatial4j.core.distance.GeodesicSphereDistCalc; import com.spatial4j.core.exception.InvalidShapeException; import com.spatial4j.core.io.BinaryCodec; import com.spatial4j.core.io.LegacyShapeReadWriterFormat; import com.spatial4j.core.io.WktShapeParser; import com.spatial4j.core.shape.Circle; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.ShapeCollection; import com.spatial4j.core.shape.impl.BufferedLineString; import com.spatial4j.core.shape.impl.CircleImpl; import com.spatial4j.core.shape.impl.GeoCircle; import com.spatial4j.core.shape.impl.PointImpl; import com.spatial4j.core.shape.impl.RectangleImpl; import java.text.ParseException; import java.util.List; /** * This is a facade to most of Spatial4j, holding things like {@link DistanceCalculator}, * {@link com.spatial4j.core.io.WktShapeParser}, and acting as a factory for the {@link Shape}s. *

* If you want a typical geodetic context, just reference {@link #GEO}. Otherwise, * You should either create and configure a {@link SpatialContextFactory} and then call * {@link SpatialContextFactory#newSpatialContext()}, OR, call * {@link com.spatial4j.core.context.SpatialContextFactory#makeSpatialContext(java.util.Map, ClassLoader)} * to do this via configuration data. *

* Thread-safe & immutable. */ public class SpatialContext { /** A popular default SpatialContext implementation for geospatial. */ public static final SpatialContext GEO = new SpatialContext(new SpatialContextFactory()); //These are non-null private final boolean geo; private final DistanceCalculator calculator; private final Rectangle worldBounds; private final WktShapeParser wktShapeParser; private final BinaryCodec binaryCodec; private final boolean normWrapLongitude; /** * Consider using {@link com.spatial4j.core.context.SpatialContextFactory} instead. * * @param geo Establishes geo vs cartesian / Euclidean. * @param calculator Optional; defaults to haversine or cartesian depending on {@code geo}. * @param worldBounds Optional; defaults to GEO_WORLDBOUNDS or MAX_WORLDBOUNDS depending on units. */ @Deprecated public SpatialContext(boolean geo, DistanceCalculator calculator, Rectangle worldBounds) { this(initFromLegacyConstructor(geo, calculator, worldBounds)); } private static SpatialContextFactory initFromLegacyConstructor(boolean geo, DistanceCalculator calculator, Rectangle worldBounds) { SpatialContextFactory factory = new SpatialContextFactory(); factory.geo = geo; factory.distCalc = calculator; factory.worldBounds = worldBounds; return factory; } @Deprecated public SpatialContext(boolean geo) { this(initFromLegacyConstructor(geo, null, null)); } /** * Called by {@link com.spatial4j.core.context.SpatialContextFactory#newSpatialContext()}. */ public SpatialContext(SpatialContextFactory factory) { this.geo = factory.geo; if (factory.distCalc == null) { this.calculator = isGeo() ? new GeodesicSphereDistCalc.Haversine() : new CartesianDistCalc(); } else { this.calculator = factory.distCalc; } //TODO remove worldBounds from Spatial4j: see Issue #55 Rectangle bounds = factory.worldBounds; if (bounds == null) { this.worldBounds = isGeo() ? new RectangleImpl(-180, 180, -90, 90, this) : new RectangleImpl(-Double.MAX_VALUE, Double.MAX_VALUE, -Double.MAX_VALUE, Double.MAX_VALUE, this); } else { if (isGeo() && !bounds.equals(new RectangleImpl(-180, 180, -90, 90, this))) throw new IllegalArgumentException("for geo (lat/lon), bounds must be " + GEO.getWorldBounds()); if (bounds.getMinX() > bounds.getMaxX()) throw new IllegalArgumentException("worldBounds minX should be <= maxX: "+ bounds); if (bounds.getMinY() > bounds.getMaxY()) throw new IllegalArgumentException("worldBounds minY should be <= maxY: "+ bounds); //hopefully worldBounds' rect implementation is compatible this.worldBounds = new RectangleImpl(bounds, this); } this.normWrapLongitude = factory.normWrapLongitude && this.isGeo(); this.wktShapeParser = factory.makeWktShapeParser(this); this.binaryCodec = factory.makeBinaryCodec(this); } public DistanceCalculator getDistCalc() { return calculator; } /** Convenience that uses {@link #getDistCalc()} */ public double calcDistance(Point p, double x2, double y2) { return getDistCalc().distance(p, x2, y2); } /** Convenience that uses {@link #getDistCalc()} */ public double calcDistance(Point p, Point p2) { return getDistCalc().distance(p, p2); } /** * The extent of x & y coordinates should fit within the return'ed rectangle. * Do *NOT* invoke reset() on this return type. */ public Rectangle getWorldBounds() { return worldBounds; } /** If true then {@link #normX(double)} will wrap longitudes outside of the standard * geodetic boundary into it. Example: 181 will become -179. */ public boolean isNormWrapLongitude() { return normWrapLongitude; } /** Is the mathematical world model based on a sphere, or is it a flat plane? The word * "geodetic" or "geodesic" is sometimes used to refer to the former, and the latter is sometimes * referred to as "Euclidean" or "cartesian". */ public boolean isGeo() { return geo; } /** Normalize the 'x' dimension. Might reduce precision or wrap it to be within the bounds. This * is called by {@link com.spatial4j.core.io.WktShapeParser} before creating a shape. */ public double normX(double x) { if (normWrapLongitude) x = DistanceUtils.normLonDEG(x); return x; } /** Normalize the 'y' dimension. Might reduce precision or wrap it to be within the bounds. This * is called by {@link com.spatial4j.core.io.WktShapeParser} before creating a shape. */ public double normY(double y) { return y; } /** Ensure fits in {@link #getWorldBounds()}. It's called by any shape factory method that * gets an 'x' dimension. */ public void verifyX(double x) { Rectangle bounds = getWorldBounds(); if (x < bounds.getMinX() || x > bounds.getMaxX())//NaN will pass throw new InvalidShapeException("Bad X value "+x+" is not in boundary "+bounds); } /** Ensure fits in {@link #getWorldBounds()}. It's called by any shape factory method that * gets a 'y' dimension. */ public void verifyY(double y) { Rectangle bounds = getWorldBounds(); if (y < bounds.getMinY() || y > bounds.getMaxY())//NaN will pass throw new InvalidShapeException("Bad Y value "+y+" is not in boundary "+bounds); } /** Construct a point. */ public Point makePoint(double x, double y) { verifyX(x); verifyY(y); return new PointImpl(x, y, this); } /** Construct a rectangle. */ public Rectangle makeRectangle(Point lowerLeft, Point upperRight) { return makeRectangle(lowerLeft.getX(), upperRight.getX(), lowerLeft.getY(), upperRight.getY()); } /** * Construct a rectangle. If just one longitude is on the dateline (+/- 180) * then potentially adjust its sign to ensure the rectangle does not cross the * dateline. */ public Rectangle makeRectangle(double minX, double maxX, double minY, double maxY) { Rectangle bounds = getWorldBounds(); // Y if (minY < bounds.getMinY() || maxY > bounds.getMaxY())//NaN will pass throw new InvalidShapeException("Y values ["+minY+" to "+maxY+"] not in boundary "+bounds); if (minY > maxY) throw new InvalidShapeException("maxY must be >= minY: " + minY + " to " + maxY); // X if (isGeo()) { verifyX(minX); verifyX(maxX); //TODO consider removing this logic so that there is no normalization here //if (minX != maxX) { USUALLY TRUE, inline check below //If an edge coincides with the dateline then don't make this rect cross it if (minX == 180 && minX != maxX) { minX = -180; } else if (maxX == -180 && minX != maxX) { maxX = 180; } //} } else { if (minX < bounds.getMinX() || maxX > bounds.getMaxX())//NaN will pass throw new InvalidShapeException("X values ["+minX+" to "+maxX+"] not in boundary "+bounds); if (minX > maxX) throw new InvalidShapeException("maxX must be >= minX: " + minX + " to " + maxX); } return new RectangleImpl(minX, maxX, minY, maxY, this); } /** Construct a circle. The units of "distance" should be the same as x & y. */ public Circle makeCircle(double x, double y, double distance) { return makeCircle(makePoint(x, y), distance); } /** Construct a circle. The units of "distance" should be the same as x & y. */ public Circle makeCircle(Point point, double distance) { if (distance < 0) throw new InvalidShapeException("distance must be >= 0; got " + distance); if (isGeo()) { if (distance > 180) { // (it's debatable whether to error or not) //throw new InvalidShapeException("distance must be <= 180; got " + distance); distance = 180; } return new GeoCircle(point, distance, this); } else { return new CircleImpl(point, distance, this); } } /** Constructs a line string. It's an ordered sequence of connected vertexes. There * is no official shape/interface for it yet so we just return Shape. */ public Shape makeLineString(List points) { return new BufferedLineString(points, 0, false, this); } /** Constructs a buffered line string. It's an ordered sequence of connected vertexes, * with a buffer distance along the line in all directions. There * is no official shape/interface for it so we just return Shape. */ public Shape makeBufferedLineString(List points, double buf) { return new BufferedLineString(points, buf, isGeo(), this); } /** Construct a ShapeCollection, analogous to an OGC GeometryCollection. */ public ShapeCollection makeCollection(List coll) { return new ShapeCollection(coll, this); } /** The {@link com.spatial4j.core.io.WktShapeParser} used by {@link #readShapeFromWkt(String)}. */ public WktShapeParser getWktShapeParser() { return wktShapeParser; } /** Reads a shape from the string formatted in WKT. * @see com.spatial4j.core.io.WktShapeParser * @param wkt non-null WKT. * @return non-null * @throws ParseException if it failed to parse. */ public Shape readShapeFromWkt(String wkt) throws ParseException { return wktShapeParser.parse(wkt); } public BinaryCodec getBinaryCodec() { return binaryCodec; } /** Reads the shape from a String using the old/deprecated * {@link com.spatial4j.core.io.LegacyShapeReadWriterFormat}. * Instead you should use standard WKT via {@link #readShapeFromWkt(String)}. This method falls * back on WKT if it's not in the legacy format. * @param value non-null * @return non-null */ @Deprecated public Shape readShape(String value) throws InvalidShapeException { Shape s = LegacyShapeReadWriterFormat.readShapeOrNull(value, this); if (s == null) { try { s = readShapeFromWkt(value); } catch (ParseException e) { if (e.getCause() instanceof InvalidShapeException) throw (InvalidShapeException) e.getCause(); throw new InvalidShapeException(e.toString(), e); } } return s; } /** Writes the shape to a String using the old/deprecated * {@link com.spatial4j.core.io.LegacyShapeReadWriterFormat}. The JTS based subclass will write it * to WKT if the legacy format doesn't support that shape. * Spatial4j in the near future won't support writing shapes to strings. * @param shape non-null * @return non-null */ @Deprecated public String toString(Shape shape) { return LegacyShapeReadWriterFormat.writeShape(shape); } @Override public String toString() { if (this.equals(GEO)) { return GEO.getClass().getSimpleName()+".GEO"; } else { return getClass().getSimpleName()+"{" + "geo=" + geo + ", calculator=" + calculator + ", worldBounds=" + worldBounds + '}'; } } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/context/SpatialContextFactory.java000066400000000000000000000203141241767633500306020ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.context; import com.spatial4j.core.distance.CartesianDistCalc; import com.spatial4j.core.distance.DistanceCalculator; import com.spatial4j.core.distance.GeodesicSphereDistCalc; import com.spatial4j.core.io.BinaryCodec; import com.spatial4j.core.io.WktShapeParser; import com.spatial4j.core.shape.Rectangle; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Map; /** * Factory for a {@link SpatialContext} based on configuration data. Call * {@link #makeSpatialContext(java.util.Map, ClassLoader)} to construct one via String name-value * pairs. To construct one via code then create a factory instance, set the fields, then call * {@link #newSpatialContext()}. *

* The following keys are looked up in the args map: *

*
spatialContextFactory
*
com.spatial4j.core.context.SpatialContext or * com.spatial4j.core.context.jts.JtsSpatialContext
*
geo
*
true (default)| false -- see {@link SpatialContext#isGeo()}
*
distCalculator
*
haversine | lawOfCosines | vincentySphere | cartesian | cartesian^2 * -- see {@link DistanceCalculator}
*
worldBounds
*
{@code ENVELOPE(xMin, xMax, yMax, yMin)} -- see {@link SpatialContext#getWorldBounds()}
*
normWrapLongitude
*
true | false (default) -- see {@link SpatialContext#isNormWrapLongitude()}
*
*/ public class SpatialContextFactory { /** Set by {@link #makeSpatialContext(java.util.Map, ClassLoader)}. */ protected Map args; /** Set by {@link #makeSpatialContext(java.util.Map, ClassLoader)}. */ protected ClassLoader classLoader; /* These fields are public to make it easy to set them without bothering with setters. */ public boolean geo = true; public DistanceCalculator distCalc;//defaults in SpatialContext c'tor based on geo public Rectangle worldBounds;//defaults in SpatialContext c'tor based on geo public boolean normWrapLongitude = false; public Class wktShapeParserClass = WktShapeParser.class; public Class binaryCodecClass = BinaryCodec.class; /** * Creates a new {@link SpatialContext} based on configuration in * args. See the class definition for what keys are looked up * in it. * The factory class is looked up via "spatialContextFactory" in args * then falling back to a Java system property (with initial caps). If neither are specified * then {@link SpatialContextFactory} is chosen. * * @param args Non-null map of name-value pairs. * @param classLoader Optional, except when a class name is provided to an * argument. */ public static SpatialContext makeSpatialContext(Map args, ClassLoader classLoader) { if (classLoader == null) classLoader = SpatialContextFactory.class.getClassLoader(); SpatialContextFactory instance; String cname = args.get("spatialContextFactory"); if (cname == null) cname = System.getProperty("SpatialContextFactory"); if (cname == null) instance = new SpatialContextFactory(); else { try { Class c = classLoader.loadClass(cname); instance = (SpatialContextFactory) c.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } instance.init(args,classLoader); return instance.newSpatialContext(); } protected void init(Map args, ClassLoader classLoader) { this.args = args; this.classLoader = classLoader; initField("geo"); initCalculator(); //init wktParser before worldBounds because WB needs to be parsed initField("wktShapeParserClass"); initWorldBounds(); initField("normWrapLongitude"); initField("binaryCodecClass"); } /** Gets {@code name} from args and populates a field by the same name with the value. */ protected void initField(String name) { // note: java.beans API is more verbose to use correctly (?) but would arguably be better Field field; try { field = getClass().getField(name); } catch (NoSuchFieldException e) { throw new Error(e); } String str = args.get(name); if (str != null) { try { Object o; if (field.getType() == Boolean.TYPE) { o = Boolean.valueOf(str); } else if (field.getType() == Class.class) { try { o = classLoader.loadClass(str); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } else if (field.getType().isEnum()) { o = Enum.valueOf(field.getType().asSubclass(Enum.class), str); } else { throw new Error("unsupported field type: "+field.getType());//not plausible at runtime unless developing } field.set(this, o); } catch (IllegalAccessException e) { throw new Error(e); } catch (Exception e) { throw new RuntimeException( "Invalid value '"+str+"' on field "+name+" of type "+field.getType(), e); } } } protected void initCalculator() { String calcStr = args.get("distCalculator"); if (calcStr == null) return; if (calcStr.equalsIgnoreCase("haversine")) { distCalc = new GeodesicSphereDistCalc.Haversine(); } else if (calcStr.equalsIgnoreCase("lawOfCosines")) { distCalc = new GeodesicSphereDistCalc.LawOfCosines(); } else if (calcStr.equalsIgnoreCase("vincentySphere")) { distCalc = new GeodesicSphereDistCalc.Vincenty(); } else if (calcStr.equalsIgnoreCase("cartesian")) { distCalc = new CartesianDistCalc(); } else if (calcStr.equalsIgnoreCase("cartesian^2")) { distCalc = new CartesianDistCalc(true); } else { throw new RuntimeException("Unknown calculator: "+calcStr); } } protected void initWorldBounds() { String worldBoundsStr = args.get("worldBounds"); if (worldBoundsStr == null) return; //kinda ugly we do this just to read a rectangle. TODO refactor final SpatialContext ctx = newSpatialContext(); worldBounds = (Rectangle) ctx.readShape(worldBoundsStr);//TODO use readShapeFromWkt } /** Subclasses should simply construct the instance from the initialized configuration. */ public SpatialContext newSpatialContext() { return new SpatialContext(this); } public WktShapeParser makeWktShapeParser(SpatialContext ctx) { return makeClassInstance(wktShapeParserClass, ctx, this); } public BinaryCodec makeBinaryCodec(SpatialContext ctx) { return makeClassInstance(binaryCodecClass, ctx, this); } @SuppressWarnings("unchecked") private T makeClassInstance(Class clazz, Object... ctorArgs) { try { //can't simply lookup constructor by arg type because might be subclass type ctorLoop: for (Constructor ctor : clazz.getConstructors()) { Class[] parameterTypes = ctor.getParameterTypes(); if (parameterTypes.length != ctorArgs.length) continue; for (int i = 0; i < ctorArgs.length; i++) { Object ctorArg = ctorArgs[i]; if (!parameterTypes[i].isAssignableFrom(ctorArg.getClass())) continue ctorLoop; } return clazz.cast(ctor.newInstance(ctorArgs)); } } catch (Exception e) { throw new RuntimeException(e); } throw new RuntimeException(clazz + " needs a constructor that takes: " + Arrays.toString(ctorArgs)); } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/context/jts/000077500000000000000000000000001241767633500242455ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/context/jts/JtsSpatialContext.java000077500000000000000000000207711241767633500305450ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.context.jts; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.exception.InvalidShapeException; import com.spatial4j.core.shape.Circle; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.jts.JtsGeometry; import com.spatial4j.core.shape.jts.JtsPoint; import com.vividsolutions.jts.geom.*; import com.vividsolutions.jts.util.GeometricShapeFactory; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * Enhances the default {@link SpatialContext} with support for Polygons (and * other geometries) using JTS. * To the extent possible, our {@link JtsGeometry} adds some amount of geodetic support over * vanilla JTS which only has a Euclidean (flat plane) model. */ public class JtsSpatialContext extends SpatialContext { public static final JtsSpatialContext GEO; static { JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.geo = true; GEO = new JtsSpatialContext(factory); } protected final GeometryFactory geometryFactory; protected final boolean allowMultiOverlap; protected final boolean useJtsPoint; protected final boolean useJtsLineString; /** * Called by {@link com.spatial4j.core.context.jts.JtsSpatialContextFactory#newSpatialContext()}. */ public JtsSpatialContext(JtsSpatialContextFactory factory) { super(factory); this.geometryFactory = factory.getGeometryFactory(); this.allowMultiOverlap = factory.allowMultiOverlap; this.useJtsPoint = factory.useJtsPoint; this.useJtsLineString = factory.useJtsLineString; } /** * If geom might be a multi geometry of some kind, then might multiple * component geometries overlap? Strict OGC says this is invalid but we * can accept it by computing the union. Note: Our ShapeCollection mostly * doesn't care but it has a method related to this * {@link com.spatial4j.core.shape.ShapeCollection#relateContainsShortCircuits()}. */ public boolean isAllowMultiOverlap() { return allowMultiOverlap; } @Override public double normX(double x) { x = super.normX(x); return geometryFactory.getPrecisionModel().makePrecise(x); } @Override public double normY(double y) { y = super.normY(y); return geometryFactory.getPrecisionModel().makePrecise(y); } @Override public String toString(Shape shape) { //Note: this logic is from the defunct JtsShapeReadWriter if (shape instanceof JtsGeometry) { JtsGeometry jtsGeom = (JtsGeometry) shape; return jtsGeom.getGeom().toText(); } //Note: doesn't handle ShapeCollection or BufferedLineString return super.toString(shape); } /** * Gets a JTS {@link Geometry} for the given {@link Shape}. Some shapes hold a * JTS geometry whereas new ones must be created for the rest. * @param shape Not null * @return Not null */ public Geometry getGeometryFrom(Shape shape) { if (shape instanceof JtsGeometry) { return ((JtsGeometry)shape).getGeom(); } if (shape instanceof JtsPoint) { return ((JtsPoint) shape).getGeom(); } if (shape instanceof Point) { Point point = (Point) shape; return geometryFactory.createPoint(new Coordinate(point.getX(),point.getY())); } if (shape instanceof Rectangle) { Rectangle r = (Rectangle)shape; if (r.getCrossesDateLine()) { Collection pair = new ArrayList(2); pair.add(geometryFactory.toGeometry(new Envelope( r.getMinX(), getWorldBounds().getMaxX(), r.getMinY(), r.getMaxY()))); pair.add(geometryFactory.toGeometry(new Envelope( getWorldBounds().getMinX(), r.getMaxX(), r.getMinY(), r.getMaxY()))); return geometryFactory.buildGeometry(pair);//a MultiPolygon or MultiLineString } else { return geometryFactory.toGeometry(new Envelope(r.getMinX(), r.getMaxX(), r.getMinY(), r.getMaxY())); } } if (shape instanceof Circle) { // FYI Some interesting code for this is here: // http://docs.codehaus.org/display/GEOTDOC/01+How+to+Create+a+Geometry#01HowtoCreateaGeometry-CreatingaCircle //TODO This should ideally have a geodetic version Circle circle = (Circle)shape; if (circle.getBoundingBox().getCrossesDateLine()) throw new IllegalArgumentException("Doesn't support dateline cross yet: "+circle);//TODO GeometricShapeFactory gsf = new GeometricShapeFactory(geometryFactory); gsf.setSize(circle.getBoundingBox().getWidth()); gsf.setNumPoints(4*25);//multiple of 4 is best gsf.setCentre(new Coordinate(circle.getCenter().getX(), circle.getCenter().getY())); return gsf.createCircle(); } //TODO add BufferedLineString throw new InvalidShapeException("can't make Geometry from: " + shape); } /** Should {@link #makePoint(double, double)} return {@link JtsPoint}? */ public boolean useJtsPoint() { return useJtsPoint; } @Override public Point makePoint(double x, double y) { if (!useJtsPoint()) return super.makePoint(x, y); //A Jts Point is fairly heavyweight! TODO could/should we optimize this? SingleCoordinateSequence verifyX(x); verifyY(y); Coordinate coord = Double.isNaN(x) ? null : new Coordinate(x, y); return new JtsPoint(geometryFactory.createPoint(coord), this); } /** Should {@link #makeLineString(java.util.List)} return {@link JtsGeometry}? */ public boolean useJtsLineString() { //BufferedLineString doesn't yet do dateline cross, and can't yet be relate()'ed with a // JTS geometry return useJtsLineString; } @Override public Shape makeLineString(List points) { if (!useJtsLineString()) return super.makeLineString(points); //convert List to Coordinate[] Coordinate[] coords = new Coordinate[points.size()]; for (int i = 0; i < coords.length; i++) { Point p = points.get(i); if (p instanceof JtsPoint) { JtsPoint jtsPoint = (JtsPoint) p; coords[i] = jtsPoint.getGeom().getCoordinate(); } else { coords[i] = new Coordinate(p.getX(), p.getY()); } } LineString lineString = geometryFactory.createLineString(coords); return makeShape(lineString); } /** * INTERNAL * @see #makeShape(com.vividsolutions.jts.geom.Geometry) * * @param geom Non-null * @param dateline180Check if both this is true and {@link #isGeo()}, then JtsGeometry will check * for adjacent coordinates greater than 180 degrees longitude apart, and * it will do tricks to make that line segment (and the shape as a whole) * cross the dateline even though JTS doesn't have geodetic support. * @param allowMultiOverlap See {@link #isAllowMultiOverlap()}. */ public JtsGeometry makeShape(Geometry geom, boolean dateline180Check, boolean allowMultiOverlap) { return new JtsGeometry(geom, this, dateline180Check, allowMultiOverlap); } /** * INTERNAL: Creates a {@link Shape} from a JTS {@link Geometry}. Generally, this shouldn't be * called when one of the other factory methods are available, such as for points. The caller * needs to have done some verification/normalization of the coordinates by now, if any. */ public JtsGeometry makeShape(Geometry geom) { return makeShape(geom, true/*dateline180Check*/, allowMultiOverlap); } public GeometryFactory getGeometryFactory() { return geometryFactory; } @Override public String toString() { if (this.equals(GEO)) { return GEO.getClass().getSimpleName()+".GEO"; } else { return super.toString(); } } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/context/jts/JtsSpatialContextFactory.java000066400000000000000000000114661241767633500320730ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.context.jts; import com.spatial4j.core.context.SpatialContextFactory; import com.spatial4j.core.io.jts.JtsBinaryCodec; import com.spatial4j.core.io.jts.JtsWktShapeParser; import com.vividsolutions.jts.geom.CoordinateSequenceFactory; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.PrecisionModel; import com.vividsolutions.jts.geom.impl.CoordinateArraySequenceFactory; import java.util.Map; /** * See {@link SpatialContextFactory#makeSpatialContext(java.util.Map, ClassLoader)}. *

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

*
datelineRule
*
width180(default)|ccwRect|none * -- see {@link com.spatial4j.core.io.jts.JtsWktShapeParser.DatelineRule}
*
validationRule
*
error(default)|none|repairConvexHull|repairBuffer0 * -- see {@link com.spatial4j.core.io.jts.JtsWktShapeParser.ValidationRule}
*
autoIndex
*
true|false(default) -- see {@link JtsWktShapeParser#isAutoIndex()}
*
allowMultiOverlap
*
true|false(default) -- see {@link JtsSpatialContext#isAllowMultiOverlap()}
*
precisionModel
*
floating(default) | floating_single | fixed * -- see {@link com.vividsolutions.jts.geom.PrecisionModel}. * If {@code fixed} then you must also provide {@code precisionScale} * -- see {@link com.vividsolutions.jts.geom.PrecisionModel#getScale()}
*
*/ public class JtsSpatialContextFactory extends SpatialContextFactory { protected static final PrecisionModel defaultPrecisionModel = new PrecisionModel();//floating //These 3 are JTS defaults for new GeometryFactory() public PrecisionModel precisionModel = defaultPrecisionModel; public int srid = 0; public CoordinateSequenceFactory coordinateSequenceFactory = CoordinateArraySequenceFactory.instance(); //ignored if geo=false public JtsWktShapeParser.DatelineRule datelineRule = JtsWktShapeParser.DatelineRule.width180; public JtsWktShapeParser.ValidationRule validationRule = JtsWktShapeParser.ValidationRule.error; public boolean autoIndex = false; public boolean allowMultiOverlap = false;//ignored if geo=false //kinda advanced options: public boolean useJtsPoint = true; public boolean useJtsLineString = true; public JtsSpatialContextFactory() { super.wktShapeParserClass = JtsWktShapeParser.class; super.binaryCodecClass = JtsBinaryCodec.class; } @Override protected void init(Map args, ClassLoader classLoader) { super.init(args, classLoader); initField("datelineRule"); initField("validationRule"); initField("autoIndex"); initField("allowMultiOverlap"); initField("useJtsPoint"); initField("useJtsLineString"); String scaleStr = args.get("precisionScale"); String modelStr = args.get("precisionModel"); if (scaleStr != null) { if (modelStr != null && !modelStr.equals("fixed")) throw new RuntimeException("Since precisionScale was specified; precisionModel must be 'fixed' but got: "+modelStr); precisionModel = new PrecisionModel(Double.parseDouble(scaleStr)); } else if (modelStr != null) { if (modelStr.equals("floating")) { precisionModel = new PrecisionModel(PrecisionModel.FLOATING); } else if (modelStr.equals("floating_single")) { precisionModel = new PrecisionModel(PrecisionModel.FLOATING_SINGLE); } else if (modelStr.equals("fixed")) { throw new RuntimeException("For fixed model, must specifiy 'precisionScale'"); } else { throw new RuntimeException("Unknown precisionModel: "+modelStr); } } } public GeometryFactory getGeometryFactory() { if (precisionModel == null || coordinateSequenceFactory == null) throw new IllegalStateException("precision model or coord seq factory can't be null"); return new GeometryFactory(precisionModel, srid, coordinateSequenceFactory); } @Override public JtsSpatialContext newSpatialContext() { return new JtsSpatialContext(this); } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/context/package-info.java000066400000000000000000000016211241767633500266340ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** SpatialContext implementations are the facade to the Spatial4j API. */ package com.spatial4j.core.context;spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/distance/000077500000000000000000000000001241767633500235535ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/distance/AbstractDistanceCalculator.java000066400000000000000000000024441241767633500316520ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.distance; import com.spatial4j.core.shape.Point; /** */ public abstract class AbstractDistanceCalculator implements DistanceCalculator { @Override public double distance(Point from, Point to) { return distance(from, to.getX(), to.getY()); } @Override public boolean within(Point from, double toX, double toY, double distance) { return distance(from, toX, toY) <= distance; } @Override public String toString() { return getClass().getSimpleName(); } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/distance/CartesianDistCalc.java000066400000000000000000000075621241767633500277500ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.distance; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.Circle; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; /** * Calculates based on Euclidean / Cartesian 2d plane. */ public class CartesianDistCalc extends AbstractDistanceCalculator { private final boolean squared; public CartesianDistCalc() { this.squared = false; } /** * @param squared Set to true to have {@link #distance(com.spatial4j.core.shape.Point, com.spatial4j.core.shape.Point)} * return the square of the correct answer. This is a * performance optimization used when sorting in which the * actual distance doesn't matter so long as the sort order is * consistent. */ public CartesianDistCalc(boolean squared) { this.squared = squared; } @Override public double distance(Point from, double toX, double toY) { double deltaX = from.getX() - toX; double deltaY = from.getY() - toY; double xSquaredPlusYSquared = deltaX*deltaX + deltaY*deltaY; if (squared) return xSquaredPlusYSquared; return Math.sqrt(xSquaredPlusYSquared); } @Override public boolean within(Point from, double toX, double toY, double distance) { double deltaX = from.getX() - toX; double deltaY = from.getY() - toY; return deltaX*deltaX + deltaY*deltaY <= distance*distance; } @Override public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse) { if (distDEG == 0) { if (reuse == null) return from; reuse.reset(from.getX(), from.getY()); return reuse; } double bearingRAD = DistanceUtils.toRadians(bearingDEG); double x = from.getX() + Math.sin(bearingRAD) * distDEG; double y = from.getY() + Math.cos(bearingRAD) * distDEG; if (reuse == null) { return ctx.makePoint(x, y); } else { reuse.reset(x, y); return reuse; } } @Override public Rectangle calcBoxByDistFromPt(Point from, double distDEG, SpatialContext ctx, Rectangle reuse) { double minX = from.getX() - distDEG; double maxX = from.getX() + distDEG; double minY = from.getY() - distDEG; double maxY = from.getY() + distDEG; if (reuse == null) { return ctx.makeRectangle(minX, maxX, minY, maxY); } else { reuse.reset(minX, maxX, minY, maxY); return reuse; } } @Override public double calcBoxByDistFromPt_yHorizAxisDEG(Point from, double distDEG, SpatialContext ctx) { return from.getY(); } @Override public double area(Rectangle rect) { return rect.getArea(null); } @Override public double area(Circle circle) { return circle.getArea(null); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CartesianDistCalc that = (CartesianDistCalc) o; if (squared != that.squared) return false; return true; } @Override public int hashCode() { return (squared ? 1 : 0); } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/distance/DistanceCalculator.java000066400000000000000000000051701241767633500301650ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.distance; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.Circle; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; /** * Performs calculations relating to distance, such as the distance between a pair of points. A * calculator might be based on Euclidean space, or a spherical model, or theoretically something * else like an ellipsoid. */ public interface DistanceCalculator { /** The distance between from and to. */ public double distance(Point from, Point to); /** The distance between from and Point(toX,toY). */ public double distance(Point from, double toX, double toY); /** Returns true if the distance between from and to is <= distance. */ public boolean within(Point from, double toX, double toY, double distance); /** * Calculates where a destination point is given an origin (from) * distance, and bearing (given in degrees -- 0-360). If reuse is given, then * this method may reset() it and return it. */ public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse); /** * Calculates the bounding box of a circle, as specified by its center point * and distance. */ public Rectangle calcBoxByDistFromPt(Point from, double distDEG, SpatialContext ctx, Rectangle reuse); /** * The Y coordinate of the horizontal axis of a circle that has maximum width. On a * 2D plane, this result is always from.getY() but, perhaps surprisingly, on a sphere * it is going to be slightly different. */ public double calcBoxByDistFromPt_yHorizAxisDEG(Point from, double distDEG, SpatialContext ctx); public double area(Rectangle rect); public double area(Circle circle); } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/distance/DistanceUtils.java000066400000000000000000000473271241767633500272060ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.distance; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; /** * Various distance calculations and constants. To the extent possible, a {@link * com.spatial4j.core.distance.DistanceCalculator}, retrieved from {@link * com.spatial4j.core.context.SpatialContext#getDistCalc()} should be used in preference to calling * these methods directly. *

* This code came from Apache * Lucene, LUCENE-1387, which in turn came from "LocalLucene". */ public class DistanceUtils { //pre-compute some angles that are commonly used @Deprecated public static final double DEG_45_AS_RADS = Math.PI / 4; @Deprecated public static final double SIN_45_AS_RADS = Math.sin(DEG_45_AS_RADS); public static final double DEG_90_AS_RADS = Math.PI / 2; public static final double DEG_180_AS_RADS = Math.PI; @Deprecated public static final double DEG_225_AS_RADS = 5 * DEG_45_AS_RADS; @Deprecated public static final double DEG_270_AS_RADS = 3 * DEG_90_AS_RADS; public static final double DEGREES_TO_RADIANS = Math.PI / 180; public static final double RADIANS_TO_DEGREES = 1 / DEGREES_TO_RADIANS; public static final double KM_TO_MILES = 0.621371192; public static final double MILES_TO_KM = 1 / KM_TO_MILES;//1.609 /** * The International Union of Geodesy and Geophysics says the Earth's mean radius in KM is: * * [1] http://en.wikipedia.org/wiki/Earth_radius */ public static final double EARTH_MEAN_RADIUS_KM = 6371.0087714; public static final double EARTH_EQUATORIAL_RADIUS_KM = 6378.1370; /** Equivalent to degrees2Dist(1, EARTH_MEAN_RADIUS_KM) */ public static final double DEG_TO_KM = DEGREES_TO_RADIANS * EARTH_MEAN_RADIUS_KM; public static final double KM_TO_DEG = 1 / DEG_TO_KM; public static final double EARTH_MEAN_RADIUS_MI = EARTH_MEAN_RADIUS_KM * KM_TO_MILES; public static final double EARTH_EQUATORIAL_RADIUS_MI = EARTH_EQUATORIAL_RADIUS_KM * KM_TO_MILES; private DistanceUtils() {} /** * Calculate the p-norm (i.e. length) between two vectors. *

* See Lp space * * @param vec1 The first vector * @param vec2 The second vector * @param power The power (2 for cartesian distance, 1 for manhattan, etc.) * @return The length. * * @see #vectorDistance(double[], double[], double, double) * */ @Deprecated public static double vectorDistance(double[] vec1, double[] vec2, double power) { //only calc oneOverPower if it's needed double oneOverPower = (power == 0 || power == 1.0 || power == 2.0) ? Double.NaN : 1.0 / power; return vectorDistance(vec1, vec2, power, oneOverPower); } /** * Calculate the p-norm (i.e. length) between two vectors. * * @param vec1 The first vector * @param vec2 The second vector * @param power The power (2 for cartesian distance, 1 for manhattan, etc.) * @param oneOverPower If you've pre-calculated oneOverPower and cached it, use this method to save * one division operation over {@link #vectorDistance(double[], double[], double)}. * @return The length. */ @Deprecated public static double vectorDistance(double[] vec1, double[] vec2, double power, double oneOverPower) { double result = 0; if (power == 0) { for (int i = 0; i < vec1.length; i++) { result += vec1[i] - vec2[i] == 0 ? 0 : 1; } } else if (power == 1.0) { // Manhattan for (int i = 0; i < vec1.length; i++) { result += Math.abs(vec1[i] - vec2[i]); } } else if (power == 2.0) { // Cartesian result = Math.sqrt(distSquaredCartesian(vec1, vec2)); } else if (power == Integer.MAX_VALUE || Double.isInfinite(power)) {//infinite norm? for (int i = 0; i < vec1.length; i++) { result = Math.max(result, Math.max(vec1[i], vec2[i])); } } else { for (int i = 0; i < vec1.length; i++) { result += Math.pow(vec1[i] - vec2[i], power); } result = Math.pow(result, oneOverPower); } return result; } /** * Return the coordinates of a vector that is the corner of a box (upper right or lower left), assuming a Rectangular * coordinate system. Note, this does not apply for points on a sphere or ellipse (although it could be used as an approximation). * * @param center The center point * @param result Holds the result, potentially resizing if needed. * @param distance The d from the center to the corner * @param upperRight If true, return the coords for the upper right corner, else return the lower left. * @return The point, either the upperLeft or the lower right */ @Deprecated public static double[] vectorBoxCorner(double[] center, double[] result, double distance, boolean upperRight) { if (result == null || result.length != center.length) { result = new double[center.length]; } if (upperRight == false) { distance = -distance; } //We don't care about the power here, // b/c we are always in a rectangular coordinate system, so any norm can be used by //using the definition of sine distance = SIN_45_AS_RADS * distance; // sin(Pi/4) == (2^0.5)/2 == opp/hyp == opp/distance, solve for opp, similarly for cosine for (int i = 0; i < center.length; i++) { result[i] = center[i] + distance; } return result; } /** * Given a start point (startLat, startLon) and a bearing on a sphere, return the destination point. * * @param startLat The starting point latitude, in radians * @param startLon The starting point longitude, in radians * @param distanceRAD The distance to travel along the bearing in radians. * @param bearingRAD The bearing, in radians. North is a 0, moving clockwise till radians(360). * @param ctx * @param reuse A preallocated object to hold the results. * @return The destination point, IN RADIANS. */ public static Point pointOnBearingRAD(double startLat, double startLon, double distanceRAD, double bearingRAD, SpatialContext ctx, Point reuse) { /* lat2 = asin(sin(lat1)*cos(d/R) + cos(lat1)*sin(d/R)*cos(θ)) lon2 = lon1 + atan2(sin(θ)*sin(d/R)*cos(lat1), cos(d/R)−sin(lat1)*sin(lat2)) */ double cosAngDist = Math.cos(distanceRAD); double cosStartLat = Math.cos(startLat); double sinAngDist = Math.sin(distanceRAD); double sinStartLat = Math.sin(startLat); double sinLat2 = sinStartLat * cosAngDist + cosStartLat * sinAngDist * Math.cos(bearingRAD); double lat2 = Math.asin(sinLat2); double lon2 = startLon + Math.atan2(Math.sin(bearingRAD) * sinAngDist * cosStartLat, cosAngDist - sinStartLat * sinLat2); // normalize lon first if (lon2 > DEG_180_AS_RADS) { lon2 = -1.0 * (DEG_180_AS_RADS - (lon2 - DEG_180_AS_RADS)); } else if (lon2 < -DEG_180_AS_RADS) { lon2 = (lon2 + DEG_180_AS_RADS) + DEG_180_AS_RADS; } // normalize lat - could flip poles if (lat2 > DEG_90_AS_RADS) { lat2 = DEG_90_AS_RADS - (lat2 - DEG_90_AS_RADS); if (lon2 < 0) { lon2 = lon2 + DEG_180_AS_RADS; } else { lon2 = lon2 - DEG_180_AS_RADS; } } else if (lat2 < -DEG_90_AS_RADS) { lat2 = -DEG_90_AS_RADS - (lat2 + DEG_90_AS_RADS); if (lon2 < 0) { lon2 = lon2 + DEG_180_AS_RADS; } else { lon2 = lon2 - DEG_180_AS_RADS; } } if (reuse == null) { return ctx.makePoint(lon2, lat2); } else { reuse.reset(lon2, lat2);//x y return reuse; } } /** * Puts in range -180 <= lon_deg <= +180. */ public static double normLonDEG(double lon_deg) { if (lon_deg >= -180 && lon_deg <= 180) return lon_deg;//common case, and avoids slight double precision shifting double off = (lon_deg + 180) % 360; if (off < 0) return 180 + off; else if (off == 0 && lon_deg > 0) return 180; else return -180 + off; } /** * Puts in range -90 <= lat_deg <= 90. */ public static double normLatDEG(double lat_deg) { if (lat_deg >= -90 && lat_deg <= 90) return lat_deg;//common case, and avoids slight double precision shifting double off = Math.abs((lat_deg + 90) % 360); return (off <= 180 ? off : 360-off) - 90; } /** * Calculates the bounding box of a circle, as specified by its center point * and distance. reuse is an optional argument to store the * results to avoid object creation. */ public static Rectangle calcBoxByDistFromPtDEG(double lat, double lon, double distDEG, SpatialContext ctx, Rectangle reuse) { //See http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates Section 3.1, 3.2 and 3.3 double minX; double maxX; double minY; double maxY; if (distDEG == 0) { minX = lon; maxX = lon; minY = lat; maxY = lat; } else if (distDEG >= 180) {//distance is >= opposite side of the globe minX = -180; maxX = 180; minY = -90; maxY = 90; } else { //--calc latitude bounds maxY = lat + distDEG; minY = lat - distDEG; if (maxY >= 90 || minY <= -90) {//touches either pole //we have special logic for longitude minX = -180; maxX = 180;//world wrap: 360 deg if (maxY <= 90 && minY >= -90) {//doesn't pass either pole: 180 deg minX = normLonDEG(lon - 90); maxX = normLonDEG(lon + 90); } if (maxY > 90) maxY = 90; if (minY < -90) minY = -90; } else { //--calc longitude bounds double lon_delta_deg = calcBoxByDistFromPt_deltaLonDEG(lat, lon, distDEG); minX = normLonDEG(lon - lon_delta_deg); maxX = normLonDEG(lon + lon_delta_deg); } } if (reuse == null) { return ctx.makeRectangle(minX, maxX, minY, maxY); } else { reuse.reset(minX, maxX, minY, maxY); return reuse; } } /** * The delta longitude of a point-distance. In other words, half the width of * the bounding box of a circle. */ public static double calcBoxByDistFromPt_deltaLonDEG(double lat, double lon, double distDEG) { //http://gis.stackexchange.com/questions/19221/find-tangent-point-on-circle-furthest-east-or-west if (distDEG == 0) return 0; double lat_rad = toRadians(lat); double dist_rad = toRadians(distDEG); double result_rad = Math.asin(Math.sin(dist_rad) / Math.cos(lat_rad)); if (!Double.isNaN(result_rad)) return toDegrees(result_rad); return 90; } /** * The latitude of the horizontal axis (e.g. left-right line) * of a circle. The horizontal axis of a circle passes through its furthest * left-most and right-most edges. On a 2D plane, this result is always * from.getY() but, perhaps surprisingly, on a sphere it is going * to be slightly different. */ public static double calcBoxByDistFromPt_latHorizAxisDEG(double lat, double lon, double distDEG) { //http://gis.stackexchange.com/questions/19221/find-tangent-point-on-circle-furthest-east-or-west if (distDEG == 0) return lat; // if we don't do this when == 90 or -90, computed result can be (+/-)89.9999 when at pole. // No biggie but more accurate. else if (lat + distDEG >= 90) return 90; else if (lat - distDEG <= -90) return -90; double lat_rad = toRadians(lat); double dist_rad = toRadians(distDEG); double result_rad = Math.asin( Math.sin(lat_rad) / Math.cos(dist_rad)); if (!Double.isNaN(result_rad)) return toDegrees(result_rad); //handle NaN (shouldn't happen due to checks earlier) if (lat > 0) return 90; if (lat < 0) return -90; return lat;//0 } /** * Calculates the degrees longitude distance at latitude {@code lat} to cover * a distance {@code dist}. *

* Used to calculate a new expanded buffer distance to account for skewing * effects for shapes that use the lat-lon space as a 2D plane instead of a * sphere. The expanded buffer will be sure to cover the intended area, but * the shape is still skewed and so it will cover a larger area. For latitude * 0 (the equator) the result is the same buffer. At 60 (or -60) degrees, the * result is twice the buffer, meaning that a shape at 60 degrees is twice as * high as it is wide when projected onto a lat-lon plane even if in the real * world it's equal all around. *

* If the result added to abs({@code lat}) is >= 90 degrees, then skewing is * so severe that the caller should consider tossing the shape and * substituting a spherical cap instead. * * @param lat latitude in degrees * @param dist distance in degrees * @return longitudinal degrees (x delta) at input latitude that is >= dist * distance. Will be >= dist and <= 90. */ public static double calcLonDegreesAtLat(double lat, double dist) { //This code was pulled out of DistanceUtils.pointOnBearingRAD() and // optimized // for bearing = 90 degrees, and so we can get an intermediate calculation. double distanceRAD = DistanceUtils.toRadians(dist); double startLat = DistanceUtils.toRadians(lat); double cosAngDist = Math.cos(distanceRAD); double cosStartLat = Math.cos(startLat); double sinAngDist = Math.sin(distanceRAD); double sinStartLat = Math.sin(startLat); double lonDelta = Math.atan2(sinAngDist * cosStartLat, cosAngDist * (1 - sinStartLat * sinStartLat)); return DistanceUtils.toDegrees(lonDelta); } /** * The square of the cartesian Distance. Not really a distance, but useful if all that matters is * comparing the result to another one. * * @param vec1 The first point * @param vec2 The second point * @return The squared cartesian distance */ @Deprecated public static double distSquaredCartesian(double[] vec1, double[] vec2) { double result = 0; for (int i = 0; i < vec1.length; i++) { double v = vec1[i] - vec2[i]; result += v * v; } return result; } /** * * @param lat1 The y coordinate of the first point, in radians * @param lon1 The x coordinate of the first point, in radians * @param lat2 The y coordinate of the second point, in radians * @param lon2 The x coordinate of the second point, in radians * @return The distance between the two points, as determined by the Haversine formula, in radians. */ public static double distHaversineRAD(double lat1, double lon1, double lat2, double lon2) { //TODO investigate slightly different formula using asin() and min() http://www.movable-type.co.uk/scripts/gis-faq-5.1.html // Check for same position if (lat1 == lat2 && lon1 == lon2) return 0.0; double hsinX = Math.sin((lon1 - lon2) * 0.5); double hsinY = Math.sin((lat1 - lat2) * 0.5); double h = hsinY * hsinY + (Math.cos(lat1) * Math.cos(lat2) * hsinX * hsinX); return 2 * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h)); } /** * Calculates the distance between two lat-lon's using the Law of Cosines. Due to numeric conditioning * errors, it is not as accurate as the Haversine formula for small distances. But with * double precision, it isn't that bad -- * allegedly 1 meter. *

* See * Why is law of cosines more preferable than haversine when calculating distance between two latitude-longitude points? *

* The arguments and return value are in radians. */ public static double distLawOfCosinesRAD(double lat1, double lon1, double lat2, double lon2) { //TODO validate formula //(MIGRATED FROM org.apache.lucene.spatial.geometry.LatLng.arcDistance()) (Lucene 3x) // Imported from mq java client. Variable references changed to match. // Check for same position if (lat1 == lat2 && lon1 == lon2) return 0.0; // Get the m_dLongitude difference. Don't need to worry about // crossing 180 since cos(x) = cos(-x) double dLon = lon2 - lon1; double a = DEG_90_AS_RADS - lat1; double c = DEG_90_AS_RADS - lat2; double cosB = (Math.cos(a) * Math.cos(c)) + (Math.sin(a) * Math.sin(c) * Math.cos(dLon)); // Find angle subtended (with some bounds checking) in radians if (cosB < -1.0) return Math.PI; else if (cosB >= 1.0) return 0; else return Math.acos(cosB); } /** * Calculates the great circle distance using the Vincenty Formula, simplified for a spherical model. This formula * is accurate for any pair of points. The equation * was taken from Wikipedia. *

* The arguments are in radians, and the result is in radians. */ public static double distVincentyRAD(double lat1, double lon1, double lat2, double lon2) { // Check for same position if (lat1 == lat2 && lon1 == lon2) return 0.0; double cosLat1 = Math.cos(lat1); double cosLat2 = Math.cos(lat2); double sinLat1 = Math.sin(lat1); double sinLat2 = Math.sin(lat2); double dLon = lon2 - lon1; double cosDLon = Math.cos(dLon); double sinDLon = Math.sin(dLon); double a = cosLat2 * sinDLon; double b = cosLat1*sinLat2 - sinLat1*cosLat2*cosDLon; double c = sinLat1*sinLat2 + cosLat1*cosLat2*cosDLon; return Math.atan2(Math.sqrt(a*a+b*b),c); } /** * Converts a distance in the units of the radius to degrees (360 degrees are * in a circle). A spherical earth model is assumed. */ public static double dist2Degrees(double dist, double radius) { return toDegrees(dist2Radians(dist, radius)); } /** * Converts degrees (1/360th of circumference of a circle) into a * distance as measured by the units of the radius. A spherical earth model * is assumed. */ public static double degrees2Dist(double degrees, double radius) { return radians2Dist(toRadians(degrees), radius); } /** * Converts a distance in the units of radius (e.g. kilometers) * to radians (multiples of the radius). A spherical earth model is assumed. */ public static double dist2Radians(double dist, double radius) { return dist / radius; } /** * Converts radians (multiples of the radius) to * distance in the units of the radius (e.g. kilometers). */ public static double radians2Dist(double radians, double radius) { return radians * radius; } /** * Same as {@link Math#toRadians(double)} but 3x faster (multiply vs. divide). * See CompareRadiansSnippet.java in tests. */ public static double toRadians(double degrees) { return degrees * DEGREES_TO_RADIANS; } /** * Same as {@link Math#toDegrees(double)} but 3x faster (multiply vs. divide). * See CompareRadiansSnippet.java in tests. */ public static double toDegrees(double radians) { return radians * RADIANS_TO_DEGREES; } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/distance/GeodesicSphereDistCalc.java000066400000000000000000000102521241767633500307160ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.distance; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.Circle; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import static com.spatial4j.core.distance.DistanceUtils.toDegrees; import static com.spatial4j.core.distance.DistanceUtils.toRadians; /** * A base class for a Distance Calculator that assumes a spherical earth model. */ public abstract class GeodesicSphereDistCalc extends AbstractDistanceCalculator { private static final double radiusDEG = DistanceUtils.toDegrees(1);//in degrees @Override public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse) { if (distDEG == 0) { if (reuse == null) return from; reuse.reset(from.getX(), from.getY()); return reuse; } Point result = DistanceUtils.pointOnBearingRAD( toRadians(from.getY()), toRadians(from.getX()), toRadians(distDEG), toRadians(bearingDEG), ctx, reuse);//output result is in radians result.reset(toDegrees(result.getX()), toDegrees(result.getY())); return result; } @Override public Rectangle calcBoxByDistFromPt(Point from, double distDEG, SpatialContext ctx, Rectangle reuse) { return DistanceUtils.calcBoxByDistFromPtDEG(from.getY(), from.getX(), distDEG, ctx, reuse); } @Override public double calcBoxByDistFromPt_yHorizAxisDEG(Point from, double distDEG, SpatialContext ctx) { return DistanceUtils.calcBoxByDistFromPt_latHorizAxisDEG(from.getY(), from.getX(), distDEG); } @Override public double area(Rectangle rect) { //From http://mathforum.org/library/drmath/view/63767.html double lat1 = toRadians(rect.getMinY()); double lat2 = toRadians(rect.getMaxY()); return Math.PI / 180 * radiusDEG * radiusDEG * Math.abs(Math.sin(lat1) - Math.sin(lat2)) * rect.getWidth(); } @Override public double area(Circle circle) { //formula is a simplified case of area(rect). double lat = toRadians(90 - circle.getRadius()); return 2 * Math.PI * radiusDEG * radiusDEG * (1 - Math.sin(lat)); } @Override public boolean equals(Object obj) { if (obj == null) return false; return getClass().equals(obj.getClass()); } @Override public int hashCode() { return getClass().hashCode(); } @Override public final double distance(Point from, double toX, double toY) { return toDegrees(distanceLatLonRAD(toRadians(from.getY()), toRadians(from.getX()), toRadians(toY), toRadians(toX))); } protected abstract double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2); public static class Haversine extends GeodesicSphereDistCalc { @Override protected double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2) { return DistanceUtils.distHaversineRAD(lat1,lon1,lat2,lon2); } } public static class LawOfCosines extends GeodesicSphereDistCalc { @Override protected double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2) { return DistanceUtils.distLawOfCosinesRAD(lat1, lon1, lat2, lon2); } } public static class Vincenty extends GeodesicSphereDistCalc { @Override protected double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2) { return DistanceUtils.distVincentyRAD(lat1, lon1, lat2, lon2); } } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/distance/package-info.java000066400000000000000000000015601241767633500267440ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Ways to calculate distance. */ package com.spatial4j.core.distance; spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/exception/000077500000000000000000000000001241767633500237575ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/exception/InvalidShapeException.java000066400000000000000000000025101241767633500310460ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.exception; /** * A shape was constructed but failed because, based on the given parts, it's invalid. For example * a rectangle's minimum Y was specified as greater than the maximum Y. This class is not used for * parsing exceptions; that's usually {@link java.text.ParseException}. */ public class InvalidShapeException extends RuntimeException { public InvalidShapeException(String reason, Throwable cause) { super(reason, cause); } public InvalidShapeException(String reason) { super(reason); } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/000077500000000000000000000000001241767633500223705ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/BinaryCodec.java000066400000000000000000000146251241767633500254250ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.context.SpatialContextFactory; import com.spatial4j.core.exception.InvalidShapeException; import com.spatial4j.core.shape.Circle; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.ShapeCollection; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.ArrayList; /** * A binary shape format. It is not designed to be a published standard, unlike Well Known * Binary (WKB). The initial release is simple but it could get more optimized to use fewer bytes or * to write & read pre-computed index structures. *

* Immutable and thread-safe. */ public class BinaryCodec { //type 0; reserved for unkonwn/generic; see readCollection protected static final byte TYPE_POINT = 1, TYPE_RECT = 2, TYPE_CIRCLE = 3, TYPE_COLL = 4, TYPE_GEOM = 5; //TODO support BufferedLineString protected final SpatialContext ctx; //This constructor is mandated by SpatialContextFactory public BinaryCodec(SpatialContext ctx, SpatialContextFactory factory) { this.ctx = ctx; } public Shape readShape(DataInput dataInput) throws IOException { byte type = dataInput.readByte(); Shape s = readShapeByTypeIfSupported(dataInput, type); if (s == null) throw new IllegalArgumentException("Unsupported shape byte "+type); return s; } public void writeShape(DataOutput dataOutput, Shape s) throws IOException { boolean written = writeShapeByTypeIfSupported(dataOutput, s); if (!written) throw new IllegalArgumentException("Unsupported shape "+s.getClass()); } protected Shape readShapeByTypeIfSupported(DataInput dataInput, byte type) throws IOException { switch (type) { case TYPE_POINT: return readPoint(dataInput); case TYPE_RECT: return readRect(dataInput); case TYPE_CIRCLE: return readCircle(dataInput); case TYPE_COLL: return readCollection(dataInput); default: return null; } } /** Note: writes the type byte even if not supported */ protected boolean writeShapeByTypeIfSupported(DataOutput dataOutput, Shape s) throws IOException { byte type = typeForShape(s); dataOutput.writeByte(type); return writeShapeByTypeIfSupported(dataOutput, s, type); //dataOutput.position(dataOutput.position() - 1);//reset putting type } protected boolean writeShapeByTypeIfSupported(DataOutput dataOutput, Shape s, byte type) throws IOException { switch (type) { case TYPE_POINT: writePoint(dataOutput, (Point) s); break; case TYPE_RECT: writeRect(dataOutput, (Rectangle) s); break; case TYPE_CIRCLE: writeCircle(dataOutput, (Circle) s); break; case TYPE_COLL: writeCollection(dataOutput, (ShapeCollection) s); break; default: return false; } return true; } protected byte typeForShape(Shape s) { if (s instanceof Point) { return TYPE_POINT; } else if (s instanceof Rectangle) { return TYPE_RECT; } else if (s instanceof Circle) { return TYPE_CIRCLE; } else if (s instanceof ShapeCollection) { return TYPE_COLL; } else { return 0; } } protected double readDim(DataInput dataInput) throws IOException { return dataInput.readDouble(); } protected void writeDim(DataOutput dataOutput, double v) throws IOException { dataOutput.writeDouble(v); } public Point readPoint(DataInput dataInput) throws IOException { return ctx.makePoint(readDim(dataInput), readDim(dataInput)); } public void writePoint(DataOutput dataOutput, Point pt) throws IOException { writeDim(dataOutput, pt.getX()); writeDim(dataOutput, pt.getY()); } public Rectangle readRect(DataInput dataInput) throws IOException { return ctx.makeRectangle(readDim(dataInput), readDim(dataInput), readDim(dataInput), readDim(dataInput)); } public void writeRect(DataOutput dataOutput, Rectangle r) throws IOException { writeDim(dataOutput, r.getMinX()); writeDim(dataOutput, r.getMaxX()); writeDim(dataOutput, r.getMinY()); writeDim(dataOutput, r.getMaxY()); } public Circle readCircle(DataInput dataInput) throws IOException { return ctx.makeCircle(readPoint(dataInput), readDim(dataInput)); } public void writeCircle(DataOutput dataOutput, Circle c) throws IOException { writePoint(dataOutput, c.getCenter()); writeDim(dataOutput, c.getRadius()); } public ShapeCollection readCollection(DataInput dataInput) throws IOException { byte type = dataInput.readByte(); int size = dataInput.readInt(); ArrayList shapes = new ArrayList(size); for (int i = 0; i < size; i++) { if (type == 0) { shapes.add(readShape(dataInput)); } else { Shape s = readShapeByTypeIfSupported(dataInput, type); if (s == null) throw new InvalidShapeException("Unsupported shape byte "+type); shapes.add(s); } } return ctx.makeCollection(shapes); } public void writeCollection(DataOutput dataOutput, ShapeCollection col) throws IOException { byte type = (byte) 0;//TODO add type to ShapeCollection dataOutput.writeByte(type); dataOutput.writeInt(col.size()); for (int i = 0; i < col.size(); i++) { Shape s = col.get(i); if (type == 0) { writeShape(dataOutput, s); } else { boolean written = writeShapeByTypeIfSupported(dataOutput, s, type); if (!written) throw new IllegalArgumentException("Unsupported shape type "+s.getClass()); } } } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/GeohashUtils.java000066400000000000000000000151761241767633500256440ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import java.util.Arrays; /** * Utilities for encoding and decoding geohashes. *

* This class isn't used by any other part of Spatial4j; it's included largely for convenience of * software using Spatial4j. There are other open-source libraries that have more comprehensive * geohash utilities but providing this one avoids an additional dependency for what's a small * amount of code. If you're using Spatial4j just for this class, consider alternatives. *

* This code originally came from * Apache Lucene, LUCENE-1512. */ public class GeohashUtils { private static final char[] BASE_32 = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};//note: this is sorted private static final int[] BASE_32_IDX;//sparse array of indexes from '0' to 'z' public static final int MAX_PRECISION = 24;//DWS: I forget what level results in needless more precision but it's about this private static final int[] BITS = {16, 8, 4, 2, 1}; static { BASE_32_IDX = new int[BASE_32[BASE_32.length-1] - BASE_32[0] + 1]; assert BASE_32_IDX.length < 100;//reasonable length Arrays.fill(BASE_32_IDX,-500); for (int i = 0; i < BASE_32.length; i++) { BASE_32_IDX[BASE_32[i] - BASE_32[0]] = i; } } private GeohashUtils() { } /** * Encodes the given latitude and longitude into a geohash * * @param latitude Latitude to encode * @param longitude Longitude to encode * @return Geohash encoding of the longitude and latitude */ public static String encodeLatLon(double latitude, double longitude) { return encodeLatLon(latitude, longitude, 12); } public static String encodeLatLon(double latitude, double longitude, int precision) { double[] latInterval = {-90.0, 90.0}; double[] lngInterval = {-180.0, 180.0}; final StringBuilder geohash = new StringBuilder(precision); boolean isEven = true; int bit = 0; int ch = 0; while (geohash.length() < precision) { double mid = 0.0; if (isEven) { mid = (lngInterval[0] + lngInterval[1]) / 2D; if (longitude > mid) { ch |= BITS[bit]; lngInterval[0] = mid; } else { lngInterval[1] = mid; } } else { mid = (latInterval[0] + latInterval[1]) / 2D; if (latitude > mid) { ch |= BITS[bit]; latInterval[0] = mid; } else { latInterval[1] = mid; } } isEven = !isEven; if (bit < 4) { bit++; } else { geohash.append(BASE_32[ch]); bit = 0; ch = 0; } } return geohash.toString(); } /** * Decodes the given geohash into a latitude and longitude * * @param geohash Geohash to deocde * @return Array with the latitude at index 0, and longitude at index 1 */ public static Point decode(String geohash, SpatialContext ctx) { Rectangle rect = decodeBoundary(geohash,ctx); double latitude = (rect.getMinY() + rect.getMaxY()) / 2D; double longitude = (rect.getMinX() + rect.getMaxX()) / 2D; return ctx.makePoint(longitude,latitude); } /** Returns min-max lat, min-max lon. */ public static Rectangle decodeBoundary(String geohash, SpatialContext ctx) { double minY = -90, maxY = 90, minX = -180, maxX = 180; boolean isEven = true; for (int i = 0; i < geohash.length(); i++) { char c = geohash.charAt(i); if (c >= 'A' && c <= 'Z') c -= ('A' - 'a'); final int cd = BASE_32_IDX[c - BASE_32[0]];//TODO check successful? for (int mask : BITS) { if (isEven) { if ((cd & mask) != 0) { minX = (minX + maxX) / 2D; } else { maxX = (minX + maxX) / 2D; } } else { if ((cd & mask) != 0) { minY = (minY + maxY) / 2D; } else { maxY = (minY + maxY) / 2D; } } isEven = !isEven; } } return ctx.makeRectangle(minX, maxX, minY, maxY); } /** Array of geohashes 1 level below the baseGeohash. Sorted. */ public static String[] getSubGeohashes(String baseGeohash) { String[] hashes = new String[BASE_32.length]; for (int i = 0; i < BASE_32.length; i++) {//note: already sorted char c = BASE_32[i]; hashes[i] = baseGeohash+c; } return hashes; } public static double[] lookupDegreesSizeForHashLen(int hashLen) { return new double[]{hashLenToLatHeight[hashLen], hashLenToLonWidth[hashLen]}; } /** * Return the shortest geohash length that will have a width & height >= specified arguments. */ public static int lookupHashLenForWidthHeight(double lonErr, double latErr) { //loop through hash length arrays from beginning till we find one. for(int len = 1; len < MAX_PRECISION; len++) { double latHeight = hashLenToLatHeight[len]; double lonWidth = hashLenToLonWidth[len]; if (latHeight < latErr && lonWidth < lonErr) return len; } return MAX_PRECISION; } /** See the table at http://en.wikipedia.org/wiki/Geohash */ private static final double[] hashLenToLatHeight, hashLenToLonWidth; static { hashLenToLatHeight = new double[MAX_PRECISION +1]; hashLenToLonWidth = new double[MAX_PRECISION +1]; hashLenToLatHeight[0] = 90*2; hashLenToLonWidth[0] = 180*2; boolean even = false; for(int i = 1; i <= MAX_PRECISION; i++) { hashLenToLatHeight[i] = hashLenToLatHeight[i-1]/(even?8:4); hashLenToLonWidth[i] = hashLenToLonWidth[i-1]/(even?4:8); even = ! even; } } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/LegacyShapeReadWriterFormat.java000066400000000000000000000146101241767633500305640ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.exception.InvalidShapeException; import com.spatial4j.core.shape.Circle; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import java.text.NumberFormat; import java.util.Locale; import java.util.StringTokenizer; /** * Reads & writes a shape from a given string in the old format. *

    *
  • Point: X Y *
    1.23 4.56 *
  • *
  • Rect: XMin YMin XMax YMax *
    1.23 4.56 7.87 4.56 *
  • *
  • {CIRCLE} '(' {POINT} {DISTANCE} ')'
    * CIRCLE is "CIRCLE" or "Circle" (no other case), and POINT is "X Y" order pair of doubles, or * "Y,X" (lat,lon) pair of doubles, and DISTANCE is "d=RADIUS" or "distance=RADIUS" where RADIUS * is a double that is the distance radius in degrees. *
  • *
*/ @Deprecated public class LegacyShapeReadWriterFormat { private LegacyShapeReadWriterFormat() { } /** * Writes a shape to a String, in a format that can be read by * {@link #readShapeOrNull(String, com.spatial4j.core.context.SpatialContext)}. * @param shape Not null. * @return Not null. */ public static String writeShape(Shape shape) { return writeShape(shape, makeNumberFormat(6)); } /** Overloaded to provide a number format. */ public static String writeShape(Shape shape, NumberFormat nf) { if (shape instanceof Point) { Point point = (Point) shape; return nf.format(point.getX()) + " " + nf.format(point.getY()); } else if (shape instanceof Rectangle) { Rectangle rect = (Rectangle)shape; return nf.format(rect.getMinX()) + " " + nf.format(rect.getMinY()) + " " + nf.format(rect.getMaxX()) + " " + nf.format(rect.getMaxY()); } else if (shape instanceof Circle) { Circle c = (Circle) shape; return "Circle(" + nf.format(c.getCenter().getX()) + " " + nf.format(c.getCenter().getY()) + " " + "d=" + nf.format(c.getRadius()) + ")"; } return shape.toString(); } /** * A convenience method to create a suitable NumberFormat for writing numbers. */ public static NumberFormat makeNumberFormat(int fractionDigits) { NumberFormat nf = NumberFormat.getInstance(Locale.ROOT);//not thread-safe nf.setGroupingUsed(false); nf.setMaximumFractionDigits(fractionDigits); nf.setMinimumFractionDigits(fractionDigits); return nf; } /** Reads the shape specification as defined in the class javadocs. If the first character is * a letter but it doesn't complete out "Circle" or "CIRCLE" then this method returns null, * offering the caller the opportunity to potentially try additional parsing. * If the first character is not a letter then it's assumed to be a point or rectangle. If that * doesn't work out then an {@link com.spatial4j.core.exception.InvalidShapeException} is thrown. */ public static Shape readShapeOrNull(String str, SpatialContext ctx) throws InvalidShapeException { if (str == null || str.length() == 0) { throw new InvalidShapeException(str); } if (Character.isLetter(str.charAt(0))) { if (str.startsWith("Circle(") || str.startsWith("CIRCLE(")) { int idx = str.lastIndexOf(')'); if (idx > 0) { String body = str.substring("Circle(".length(), idx); StringTokenizer st = new StringTokenizer(body, " "); String token = st.nextToken(); Point pt; if (token.indexOf(',') != -1) { pt = readLatCommaLonPoint(token, ctx); } else { double x = Double.parseDouble(token); double y = Double.parseDouble(st.nextToken()); pt = ctx.makePoint(x, y); } Double d = null; String arg = st.nextToken(); idx = arg.indexOf('='); if (idx > 0) { String k = arg.substring(0, idx); if (k.equals("d") || k.equals("distance")) { d = Double.parseDouble(arg.substring(idx + 1)); } else { throw new InvalidShapeException("unknown arg: " + k + " :: " + str); } } else { d = Double.parseDouble(arg); } if (st.hasMoreTokens()) { throw new InvalidShapeException("Extra arguments: " + st.nextToken() + " :: " + str); } if (d == null) { throw new InvalidShapeException("Missing Distance: " + str); } //NOTE: we are assuming the units of 'd' is the same as that of the spatial context. return ctx.makeCircle(pt, d); } } return null;//caller has opportunity to try other parsing } if (str.indexOf(',') != -1) return readLatCommaLonPoint(str, ctx); StringTokenizer st = new StringTokenizer(str, " "); double p0 = Double.parseDouble(st.nextToken()); double p1 = Double.parseDouble(st.nextToken()); if (st.hasMoreTokens()) { double p2 = Double.parseDouble(st.nextToken()); double p3 = Double.parseDouble(st.nextToken()); if (st.hasMoreTokens()) throw new InvalidShapeException("Only 4 numbers supported (rect) but found more: " + str); return ctx.makeRectangle(p0, p2, p1, p3); } return ctx.makePoint(p0, p1); } /** Reads geospatial latitude then a comma then longitude. */ private static Point readLatCommaLonPoint(String value, SpatialContext ctx) throws InvalidShapeException { double[] latLon = ParseUtils.parseLatitudeLongitude(value); return ctx.makePoint(latLon[1], latLon[0]); } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/ParseUtils.java000066400000000000000000000152251241767633500253330ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io; import com.spatial4j.core.exception.InvalidShapeException; /** * Utility methods related to parsing a series of numbers. *

* This code came from DistanceUtils, which came from * Apache * Lucene, LUCENE-773, which in turn came from "LocalLucene". * * @deprecated Not useful; see https://github.com/spatial4j/spatial4j/issues/19 */ @Deprecated public class ParseUtils { private ParseUtils() { } /** * Given a string containing dimension values encoded in it, separated by commas, return a String array of length dimension * containing the values. * * @param out A preallocated array. Must be size dimension. If it is not it will be resized. * @param externalVal The value to parse * @param dimension The expected number of values for the point * @return An array of the values that make up the point (aka vector) * @throws com.spatial4j.core.exception.InvalidShapeException if the dimension specified does not match the number of values in the externalValue. */ public static String[] parsePoint(String[] out, String externalVal, int dimension) throws InvalidShapeException { //TODO: Should we support sparse vectors? if (out == null || out.length != dimension) out = new String[dimension]; int idx = externalVal.indexOf(','); int end = idx; int start = 0; int i = 0; if (idx == -1 && dimension == 1 && externalVal.length() > 0) {//we have a single point, dimension better be 1 out[0] = externalVal.trim(); i = 1; } else if (idx > 0) {//if it is zero, that is an error //Parse out a comma separated list of point values, as in: 73.5,89.2,7773.4 for (; i < dimension; i++) { while (start < end && externalVal.charAt(start) == ' ') start++; while (end > start && externalVal.charAt(end - 1) == ' ') end--; if (start == end) { break; } out[i] = externalVal.substring(start, end); start = idx + 1; end = externalVal.indexOf(',', start); idx = end; if (end == -1) { end = externalVal.length(); } } } if (i != dimension) { throw new InvalidShapeException("incompatible dimension (" + dimension + ") and values (" + externalVal + "). Only " + i + " values specified"); } return out; } /** * Given a string containing dimension values encoded in it, separated by commas, return a double array of length dimension * containing the values. * * @param out A preallocated array. Must be size dimension. If it is not it will be resized. * @param externalVal The value to parse * @param dimension The expected number of values for the point * @return An array of the values that make up the point (aka vector) * @throws com.spatial4j.core.exception.InvalidShapeException if the dimension specified does not match the number of values in the externalValue. */ public static double[] parsePointDouble(double[] out, String externalVal, int dimension) throws InvalidShapeException{ if (out == null || out.length != dimension) out = new double[dimension]; int idx = externalVal.indexOf(','); int end = idx; int start = 0; int i = 0; if (idx == -1 && dimension == 1 && externalVal.length() > 0) {//we have a single point, dimension better be 1 out[0] = Double.parseDouble(externalVal.trim()); i = 1; } else if (idx > 0) {//if it is zero, that is an error //Parse out a comma separated list of point values, as in: 73.5,89.2,7773.4 for (; i < dimension; i++) { //TODO: abstract common code with other parsePoint while (start < end && externalVal.charAt(start) == ' ') start++; while (end > start && externalVal.charAt(end - 1) == ' ') end--; if (start == end) { break; } out[i] = Double.parseDouble(externalVal.substring(start, end)); start = idx + 1; end = externalVal.indexOf(',', start); idx = end; if (end == -1) { end = externalVal.length(); } } } if (i != dimension) { throw new InvalidShapeException("incompatible dimension (" + dimension + ") and values (" + externalVal + "). Only " + i + " values specified"); } return out; } /** * Extract (by calling {@link #parsePoint(String[], String, int)} and validate the latitude and * longitude contained in the String by making sure the latitude is between 90 & -90 and longitude * is between -180 and 180. *

* The latitude is assumed to be the first part of the string and the longitude the second part. * * @param latLonStr The string to parse. Latitude is the first value, longitude is the second. * @return The lat long * * @throws com.spatial4j.core.exception.InvalidShapeException if there was an error parsing */ public static final double[] parseLatitudeLongitude(String latLonStr) throws InvalidShapeException { return parseLatitudeLongitude(null, latLonStr); } /** * A variation of {@link #parseLatitudeLongitude(String)} that re-uses an output array. * @see #parseLatitudeLongitude(String) */ public static final double[] parseLatitudeLongitude(double[] outLatLon, String latLonStr) throws InvalidShapeException { outLatLon = parsePointDouble(outLatLon, latLonStr, 2); if (outLatLon[0] < -90.0 || outLatLon[0] > 90.0) { throw new InvalidShapeException( "Invalid latitude: latitudes are range -90 to 90: provided lat: [" + outLatLon[0] + "]"); } if (outLatLon[1] < -180.0 || outLatLon[1] > 180.0) { throw new InvalidShapeException( "Invalid longitude: longitudes are range -180 to 180: provided lon: [" + outLatLon[1] + "]"); } return outLatLon; } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/WktShapeParser.java000066400000000000000000000500361241767633500261420ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.context.SpatialContextFactory; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Shape; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * An extensible parser for * Well Known Text (WKT). * The shapes supported by this class are: *

    *
  • POINT
  • *
  • MULTIPOINT
  • *
  • ENVELOPE
  • (strictly isn't WKT but is defined by OGC's * Common Query Language (CQL)) *
  • LINESTRING
  • *
  • MULTILINESTRING
  • *
  • GEOMETRYCOLLECTION
  • *
  • BUFFER
  • (non-standard Spatial4j operation) *
* 'EMPTY' is supported. Specifying 'Z', 'M', or any other dimensionality in the WKT is effectively * ignored. Thus, you can specify any number of numbers in the coordinate points but only the first * two take effect. The javadocs for the parse___Shape methods further describe these * shapes, or you * *

* Most users of this class will call just one method: {@link #parse(String)}, or * {@link #parseIfSupported(String)} to not fail if it isn't parse-able. * *

* To support more shapes, extend this class and override * {@link #parseShapeByType(WktShapeParser.State, String)}. It's also possible to delegate to * a WKTParser by also delegating {@link #newState(String)}. * *

* Note, instances of this base class are threadsafe. */ public class WktShapeParser { //TODO support SRID: "SRID=4326;pointPOINT(1,2) //TODO should reference proposed ShapeFactory instead of ctx, which is a point of indirection that // might optionally do data validation & normalization protected final SpatialContext ctx; /** This constructor is required by {@link com.spatial4j.core.context.SpatialContextFactory#makeWktShapeParser(com.spatial4j.core.context.SpatialContext)}. */ public WktShapeParser(SpatialContext ctx, SpatialContextFactory factory) { this.ctx = ctx; } public SpatialContext getCtx() { return ctx; } /** * Parses the wktString, returning the defined Shape. * * @return Non-null Shape defined in the String * @throws ParseException Thrown if there is an error in the Shape definition */ public Shape parse(String wktString) throws ParseException { Shape shape = parseIfSupported(wktString);//sets rawString & offset if (shape != null) return shape; String shortenedString = (wktString.length() <= 128 ? wktString : wktString.substring(0, 128-3)+"..."); throw new ParseException("Unknown Shape definition [" + shortenedString + "]", 0); } /** * Parses the wktString, returning the defined Shape. If it can't because the * shape name is unknown or an empty or blank string was passed, then it returns null. * If the WKT starts with a supported shape but contains an inner unsupported shape then * it will result in a {@link ParseException}. * * @param wktString non-null, can be empty or have surrounding whitespace * @return Shape, null if unknown / unsupported shape. * @throws ParseException Thrown if there is an error in the Shape definition */ public Shape parseIfSupported(String wktString) throws ParseException { State state = newState(wktString); state.nextIfWhitespace();//leading if (state.eof()) return null; //shape types must start with a letter if (!Character.isLetter(state.rawString.charAt(state.offset))) return null; String shapeType = state.nextWord(); Shape result = null; try { result = parseShapeByType(state, shapeType); } catch (ParseException e) { throw e; } catch (Exception e) {//most likely InvalidShapeException ParseException pe = new ParseException(e.toString(), state.offset); pe.initCause(e); throw pe; } if (result != null && !state.eof()) throw new ParseException("end of shape expected", state.offset); return result; } /** (internal) Creates a new State with the given String. It's only called by * {@link #parseIfSupported(String)}. This is an extension point for subclassing. */ protected State newState(String wktString) { //NOTE: if we wanted to re-use old States to reduce object allocation, we might do that // here. But in the scheme of things, it doesn't seem worth the bother as it complicates the // thread-safety story of the API for too little of a gain. return new State(wktString); } /** * (internal) Parses the remainder of a shape definition following the shape's name * given as {@code shapeType} already consumed via * {@link State#nextWord()}. If * it's able to parse the shape, {@link WktShapeParser.State#offset} * should be advanced beyond * it (e.g. to the ',' or ')' or EOF in general). The default implementation * checks the name against some predefined names and calls corresponding * parse methods to handle the rest. Overriding this method is an * excellent extension point for additional shape types. Or, use this class by delegation to this * method. *

* When writing a parse method that reacts to a specific shape type, remember to handle the * dimension and EMPTY token via * {@link com.spatial4j.core.io.WktShapeParser.State#nextIfEmptyAndSkipZM()}. * * @param state * @param shapeType Non-Null string; could have mixed case. The first character is a letter. * @return The shape or null if not supported / unknown. */ protected Shape parseShapeByType(State state, String shapeType) throws ParseException { assert Character.isLetter(shapeType.charAt(0)) : "Shape must start with letter: "+shapeType; if (shapeType.equalsIgnoreCase("POINT")) { return parsePointShape(state); } else if (shapeType.equalsIgnoreCase("MULTIPOINT")) { return parseMultiPointShape(state); } else if (shapeType.equalsIgnoreCase("ENVELOPE")) { return parseEnvelopeShape(state); } else if (shapeType.equalsIgnoreCase("GEOMETRYCOLLECTION")) { return parseGeometryCollectionShape(state); } else if (shapeType.equalsIgnoreCase("LINESTRING")) { return parseLineStringShape(state); } else if (shapeType.equalsIgnoreCase("MULTILINESTRING")) { return parseMultiLineStringShape(state); } //extension if (shapeType.equalsIgnoreCase("BUFFER")) { return parseBufferShape(state); } // HEY! Update class Javadocs if add more shapes return null; } /** * Parses the BUFFER operation applied to a parsed shape. *

   *   '(' shape ',' number ')'
   * 
* Whereas 'number' is the distance to buffer the shape by. */ protected Shape parseBufferShape(State state) throws ParseException { state.nextExpect('('); Shape shape = shape(state); state.nextExpect(','); double distance = normDist(state.nextDouble()); state.nextExpect(')'); return shape.getBuffered(distance, ctx); } /** Called to normalize a value that isn't X or Y. X & Y or normalized via * {@link com.spatial4j.core.context.SpatialContext#normX(double)} & normY. */ protected double normDist(double v) {//TODO should this be added to ctx? return v; } /** * Parses a POINT shape from the raw string. *
   *   '(' coordinate ')'
   * 
* * @see #point(WktShapeParser.State) */ protected Shape parsePointShape(State state) throws ParseException { if (state.nextIfEmptyAndSkipZM()) return ctx.makePoint(Double.NaN, Double.NaN); state.nextExpect('('); Point coordinate = point(state); state.nextExpect(')'); return coordinate; } /** * Parses a MULTIPOINT shape from the raw string -- a collection of points. *
   *   '(' coordinate (',' coordinate )* ')'
   * 
* Furthermore, coordinate can optionally be wrapped in parenthesis. * * @see #point(WktShapeParser.State) */ protected Shape parseMultiPointShape(State state) throws ParseException { if (state.nextIfEmptyAndSkipZM()) return ctx.makeCollection(Collections.EMPTY_LIST); List shapes = new ArrayList(); state.nextExpect('('); do { boolean openParen = state.nextIf('('); Point coordinate = point(state); if (openParen) state.nextExpect(')'); shapes.add(coordinate); } while (state.nextIf(',')); state.nextExpect(')'); return ctx.makeCollection(shapes); } /** * Parses an ENVELOPE (aka Rectangle) shape from the raw string. The values are normalized. *

* Source: OGC "Catalogue Services Specification", the "CQL" (Common Query Language) sub-spec. * Note the inconsistent order of the min & max values between x & y! *

   *   '(' x1 ',' x2 ',' y2 ',' y1 ')'
   * 
*/ protected Shape parseEnvelopeShape(State state) throws ParseException { //FYI no dimension or EMPTY state.nextExpect('('); double x1 = state.nextDouble(); state.nextExpect(','); double x2 = state.nextDouble(); state.nextExpect(','); double y2 = state.nextDouble(); state.nextExpect(','); double y1 = state.nextDouble(); state.nextExpect(')'); return ctx.makeRectangle(ctx.normX(x1), ctx.normX(x2), ctx.normY(y1), ctx.normY(y2)); } /** * Parses a LINESTRING shape from the raw string -- an ordered sequence of points. *
   *   coordinateSequence
   * 
* * @see #pointList(WktShapeParser.State) */ protected Shape parseLineStringShape(State state) throws ParseException { if (state.nextIfEmptyAndSkipZM()) return ctx.makeLineString(Collections.emptyList()); List points = pointList(state); return ctx.makeLineString(points); } /** * Parses a MULTILINESTRING shape from the raw string -- a collection of line strings. *
   *   '(' coordinateSequence (',' coordinateSequence )* ')'
   * 
* * @see #parseLineStringShape(com.spatial4j.core.io.WktShapeParser.State) */ protected Shape parseMultiLineStringShape(State state) throws ParseException { if (state.nextIfEmptyAndSkipZM()) return ctx.makeCollection(Collections.EMPTY_LIST); List shapes = new ArrayList(); state.nextExpect('('); do { shapes.add(parseLineStringShape(state)); } while (state.nextIf(',')); state.nextExpect(')'); return ctx.makeCollection(shapes); } /** * Parses a GEOMETRYCOLLECTION shape from the raw string. *
   *   '(' shape (',' shape )* ')'
   * 
*/ protected Shape parseGeometryCollectionShape(State state) throws ParseException { if (state.nextIfEmptyAndSkipZM()) return ctx.makeCollection(Collections.EMPTY_LIST); List shapes = new ArrayList(); state.nextExpect('('); do { shapes.add(shape(state)); } while (state.nextIf(',')); state.nextExpect(')'); return ctx.makeCollection(shapes); } /** Reads a shape from the current position, starting with the name of the shape. It * calls {@link #parseShapeByType(com.spatial4j.core.io.WktShapeParser.State, String)} * and throws an exception if the shape wasn't supported. */ protected Shape shape(State state) throws ParseException { String type = state.nextWord(); Shape shape = parseShapeByType(state, type); if (shape == null) throw new ParseException("Shape of type "+type+" is unknown", state.offset); return shape; } /** * Reads a list of Points (AKA CoordinateSequence) from the current position. *
   *   '(' coordinate (',' coordinate )* ')'
   * 
* * @see #point(WktShapeParser.State) */ protected List pointList(State state) throws ParseException { List sequence = new ArrayList(); state.nextExpect('('); do { sequence.add(point(state)); } while (state.nextIf(',')); state.nextExpect(')'); return sequence; } /** * Reads a raw Point (AKA Coordinate) from the current position. Only the first 2 numbers are * used. The values are normalized. *
   *   number number number*
   * 
*/ protected Point point(State state) throws ParseException { double x = state.nextDouble(); double y = state.nextDouble(); state.skipNextDoubles(); return ctx.makePoint(ctx.normX(x), ctx.normY(y)); } /** The parse state. */ public class State { /** Set in {@link #parseIfSupported(String)}. */ public String rawString; /** Offset of the next char in {@link #rawString} to be read. */ public int offset; /** Dimensionality specifier (e.g. 'Z', or 'M') following a shape type name. */ public String dimension; public State(String rawString) { this.rawString = rawString; } public SpatialContext getCtx() { return ctx; } public WktShapeParser getParser() { return WktShapeParser.this; } /** * Reads the word starting at the current character position. The word * terminates once {@link Character#isJavaIdentifierPart(char)} returns false (or EOF). * {@link #offset} is advanced past whitespace. * * @return Non-null non-empty String. */ public String nextWord() throws ParseException { int startOffset = offset; while (offset < rawString.length() && Character.isJavaIdentifierPart(rawString.charAt(offset))) { offset++; } if (startOffset == offset) throw new ParseException("Word expected", startOffset); String result = rawString.substring(startOffset, offset); nextIfWhitespace(); return result; } /** * Skips over a dimensionality token (e.g. 'Z' or 'M') if found, storing in * {@link #dimension}, and then looks for EMPTY, consuming that and whitespace. *
     *   dimensionToken? 'EMPTY'?
     * 
* @return True if EMPTY was found. */ public boolean nextIfEmptyAndSkipZM() throws ParseException { if (eof()) return false; char c = rawString.charAt(offset); if (c == '(' || !Character.isJavaIdentifierPart(c)) return false; String word = nextWord(); if (word.equalsIgnoreCase("EMPTY")) return true; //we figure this word is Z or ZM or some other dimensionality signifier. We skip it. this.dimension = word; if (eof()) return false; c = rawString.charAt(offset); if (c == '(' || !Character.isJavaIdentifierPart(c)) return false; word = nextWord(); if (word.equalsIgnoreCase("EMPTY")) return true; throw new ParseException("Expected EMPTY because found dimension; but got ["+word+"]", offset); } /** * Reads in a double from the String. Parses digits with an optional decimal, sign, or exponent. * NaN and Infinity are not supported. * {@link #offset} is advanced past whitespace. * * @return Double value */ public double nextDouble() throws ParseException { int startOffset = offset; skipDouble(); if (startOffset == offset) throw new ParseException("Expected a number", offset); double result; try { result = Double.parseDouble(rawString.substring(startOffset, offset)); } catch (Exception e) { throw new ParseException(e.toString(), offset); } nextIfWhitespace(); return result; } /** Advances offset forward until it points to a character that isn't part of a number. */ public void skipDouble() { int startOffset = offset; for (; offset < rawString.length(); offset++) { char c = rawString.charAt(offset); if (!(Character.isDigit(c) || c == '.' || c == '-' || c == '+')) { //'e' is okay as long as it isn't first if (offset != startOffset && (c == 'e' || c == 'E')) continue; break; } } } /** Advances past as many doubles as there are, with intervening whitespace. */ public void skipNextDoubles() { while (!eof()) { int startOffset = offset; skipDouble(); if (startOffset == offset) return; nextIfWhitespace(); } } /** * Verifies that the current character is of the expected value. * If the character is the expected value, then it is consumed and * {@link #offset} is advanced past whitespace. * * @param expected The expected char. */ public void nextExpect(char expected) throws ParseException { if (eof()) throw new ParseException("Expected [" + expected + "] found EOF", offset); char c = rawString.charAt(offset); if (c != expected) throw new ParseException("Expected [" + expected + "] found [" + c + "]", offset); offset++; nextIfWhitespace(); } /** If the string is consumed, i.e. at end-of-file. */ public final boolean eof() { return offset >= rawString.length(); } /** * If the current character is {@code expected}, then offset is advanced after it and any * subsequent whitespace. Otherwise, false is returned. * * @param expected The expected char * @return true if consumed */ public boolean nextIf(char expected) { if (!eof() && rawString.charAt(offset) == expected) { offset++; nextIfWhitespace(); return true; } return false; } /** * Moves offset to next non-whitespace character. Doesn't move if the offset is already at * non-whitespace. There is very little reason for subclasses to call this because * most other parsing methods call it. */ public void nextIfWhitespace() { for (; offset < rawString.length(); offset++) { if (!Character.isWhitespace(rawString.charAt(offset))) { return; } } } /** * Returns the next chunk of text till the next ',' or ')' (non-inclusive) * or EOF. If a '(' is encountered, then it looks past its matching ')', * taking care to handle nested matching parenthesis too. It's designed to be * of use to subclasses that wish to get the entire subshape at the current * position as a string so that it might be passed to other software that * will parse it. *

* Example: *

     *   OUTER(INNER(3, 5))
     * 
* If this is called when offset is at the first character, then it will * return this whole string. If called at the "I" then it will return * "INNER(3, 5)". If called at "3", then it will return "3". In all cases, * offset will be positioned at the next position following the returned * substring. * * @return non-null substring. */ public String nextSubShapeString() throws ParseException { int startOffset = offset; int parenStack = 0;//how many parenthesis levels are we in? for (; offset < rawString.length(); offset++) { char c = rawString.charAt(offset); if (c == ',') { if (parenStack == 0) break; } else if (c == ')') { if (parenStack == 0) break; parenStack--; } else if (c == '(') { parenStack++; } } if (parenStack != 0) throw new ParseException("Unbalanced parenthesis", startOffset); return rawString.substring(startOffset, offset); } }//class State }spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/jts/000077500000000000000000000000001241767633500231705ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/jts/JtsBinaryCodec.java000066400000000000000000000115741241767633500267060ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io.jts; import com.spatial4j.core.context.jts.JtsSpatialContext; import com.spatial4j.core.context.jts.JtsSpatialContextFactory; import com.spatial4j.core.exception.InvalidShapeException; import com.spatial4j.core.io.BinaryCodec; import com.spatial4j.core.shape.Shape; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.PrecisionModel; import com.vividsolutions.jts.io.InStream; import com.vividsolutions.jts.io.OutStream; import com.vividsolutions.jts.io.ParseException; import com.vividsolutions.jts.io.WKBConstants; import com.vividsolutions.jts.io.WKBReader; import com.vividsolutions.jts.io.WKBWriter; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; /** * Writes shapes in WKB, if it isn't otherwise supported by the superclass. */ public class JtsBinaryCodec extends BinaryCodec { protected final boolean useFloat;//instead of double public JtsBinaryCodec(JtsSpatialContext ctx, JtsSpatialContextFactory factory) { super(ctx, factory); //note: ctx.geometryFactory hasn't been set yet useFloat = (factory.precisionModel.getType() == PrecisionModel.FLOATING_SINGLE); } @Override protected double readDim(DataInput dataInput) throws IOException { if (useFloat) return dataInput.readFloat(); return super.readDim(dataInput); } @Override protected void writeDim(DataOutput dataOutput, double v) throws IOException { if (useFloat) dataOutput.writeFloat((float) v); else super.writeDim(dataOutput, v); } @Override protected byte typeForShape(Shape s) { byte type = super.typeForShape(s); if (type == 0) { type = TYPE_GEOM;//handles everything } return type; } @Override protected Shape readShapeByTypeIfSupported(final DataInput dataInput, byte type) throws IOException { if (type != TYPE_GEOM) return super.readShapeByTypeIfSupported(dataInput, type); return readJtsGeom(dataInput); } @Override protected boolean writeShapeByTypeIfSupported(DataOutput dataOutput, Shape s, byte type) throws IOException { if (type != TYPE_GEOM) return super.writeShapeByTypeIfSupported(dataOutput, s, type); writeJtsGeom(dataOutput, s); return true; } public Shape readJtsGeom(final DataInput dataInput) throws IOException { JtsSpatialContext ctx = (JtsSpatialContext)super.ctx; WKBReader reader = new WKBReader(ctx.getGeometryFactory()); try { InStream inStream = new InStream() {//a strange JTS abstraction boolean first = true; @Override public void read(byte[] buf) throws IOException { if (first) {//we don't write JTS's leading BOM so synthesize reading it if (buf.length != 1) throw new IllegalStateException("Expected initial read of one byte, not: " + buf.length); buf[0] = WKBConstants.wkbXDR;//0 first = false; } else { //TODO for performance, specialize for common array lengths: 1, 4, 8 dataInput.readFully(buf); } } }; Geometry geom = reader.read(inStream); //false: don't check for dateline-180 cross or multi-polygon overlaps; this won't happen // once it gets written, and we're reading it now return ctx.makeShape(geom, false, false); } catch (ParseException ex) { throw new InvalidShapeException("error reading WKT", ex); } } public void writeJtsGeom(final DataOutput dataOutput, Shape s) throws IOException { JtsSpatialContext ctx = (JtsSpatialContext)super.ctx; Geometry geom = ctx.getGeometryFrom(s);//might even translate it new WKBWriter().write(geom, new OutStream() {//a strange JTS abstraction boolean first = true; @Override public void write(byte[] buf, int len) throws IOException { if (first) { first = false; //skip byte order mark if (len != 1 || buf[0] != WKBConstants.wkbXDR)//the default throw new IllegalStateException("Unexpected WKB byte order mark"); return; } dataOutput.write(buf, 0, len); } }); } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/jts/JtsWKTReaderShapeParser.java000066400000000000000000000110171241767633500304420ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io.jts; import com.spatial4j.core.context.jts.JtsSpatialContext; import com.spatial4j.core.context.jts.JtsSpatialContextFactory; import com.spatial4j.core.distance.DistanceUtils; import com.spatial4j.core.exception.InvalidShapeException; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.jts.JtsPoint; import com.vividsolutions.jts.geom.CoordinateSequence; import com.vividsolutions.jts.geom.CoordinateSequenceFilter; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.io.WKTReader; import java.text.ParseException; /** * This is an extension of {@link JtsWktShapeParser} that processes the entire * string with JTS's {@link com.vividsolutions.jts.io.WKTReader}. Some differences: *
    *
  • No support for ENVELOPE and BUFFER
  • *
  • MULTI* shapes use JTS's {@link com.vividsolutions.jts.geom.GeometryCollection} subclasses, * not {@link com.spatial4j.core.shape.ShapeCollection}
  • *
  • 'Z' coordinates are saved into the geometry
  • *
* */ public class JtsWKTReaderShapeParser extends JtsWktShapeParser { //Note: Historically, the code here originated from the defunct JtsShapeReadWriter. public JtsWKTReaderShapeParser(JtsSpatialContext ctx, JtsSpatialContextFactory factory) { super(ctx, factory); } @Override public Shape parseIfSupported(String wktString) throws ParseException { return parseIfSupported(wktString, new WKTReader(ctx.getGeometryFactory())); } /** * Reads WKT from the {@code str} via JTS's {@link com.vividsolutions.jts.io.WKTReader}. * @param str * @param reader
new WKTReader(ctx.getGeometryFactory()))
* @return Non-Null */ protected Shape parseIfSupported(String str, WKTReader reader) throws ParseException { try { Geometry geom = reader.read(str); //Normalizes & verifies coordinates checkCoordinates(geom); if (geom instanceof com.vividsolutions.jts.geom.Point) { com.vividsolutions.jts.geom.Point ptGeom = (com.vividsolutions.jts.geom.Point) geom; if (ctx.useJtsPoint()) return new JtsPoint(ptGeom, ctx); else return ctx.makePoint(ptGeom.getX(), ptGeom.getY()); } else if (geom.isRectangle()) { return super.makeRectFromPoly(geom); } else { return super.makeShapeFromGeometry(geom); } } catch (InvalidShapeException e) { throw e; } catch (Exception e) { throw new InvalidShapeException("error reading WKT: "+e.toString(), e); } } protected void checkCoordinates(Geometry geom) { // note: JTS WKTReader has already normalized coords with the JTS PrecisionModel. geom.apply(new CoordinateSequenceFilter() { boolean changed = false; @Override public void filter(CoordinateSequence seq, int i) { double x = seq.getX(i); double y = seq.getY(i); //Note: we don't simply call ctx.normX & normY because // those methods use the precisionModel, but WKTReader already // used the precisionModel. It's be nice to turn that off somehow but alas. if (ctx.isGeo() && ctx.isNormWrapLongitude()) { double xNorm = DistanceUtils.normLonDEG(x); if (Double.compare(x, xNorm) != 0) {//handles NaN changed = true; seq.setOrdinate(i, CoordinateSequence.X, xNorm); } // double yNorm = DistanceUtils.normLatDEG(y); // if (y != yNorm) { // changed = true; // seq.setOrdinate(i,CoordinateSequence.Y,yNorm); // } } ctx.verifyX(x); ctx.verifyY(y); } @Override public boolean isDone() { return false; } @Override public boolean isGeometryChanged() { return changed; } }); } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/jts/JtsWktShapeParser.java000066400000000000000000000301361241767633500274220ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io.jts; import com.spatial4j.core.context.jts.JtsSpatialContext; import com.spatial4j.core.context.jts.JtsSpatialContextFactory; import com.spatial4j.core.io.WktShapeParser; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.jts.JtsGeometry; import com.vividsolutions.jts.algorithm.CGAlgorithms; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LinearRing; import com.vividsolutions.jts.geom.Polygon; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Extends {@link com.spatial4j.core.io.WktShapeParser} adding support for polygons, using JTS. */ public class JtsWktShapeParser extends WktShapeParser { protected final JtsSpatialContext ctx; protected final DatelineRule datelineRule; protected final ValidationRule validationRule; protected final boolean autoIndex; public JtsWktShapeParser(JtsSpatialContext ctx, JtsSpatialContextFactory factory) { super(ctx, factory); this.ctx = ctx; this.datelineRule = factory.datelineRule; this.validationRule = factory.validationRule; this.autoIndex = factory.autoIndex; } /** @see JtsWktShapeParser.ValidationRule */ public ValidationRule getValidationRule() { return validationRule; } /** * JtsGeometry shapes are automatically validated when {@link #getValidationRule()} isn't * {@code none}. */ public boolean isAutoValidate() { return validationRule != ValidationRule.none; } /** * If JtsGeometry shapes should be automatically prepared (i.e. optimized) when read via WKT. * @see com.spatial4j.core.shape.jts.JtsGeometry#index() */ public boolean isAutoIndex() { return autoIndex; } /** @see DatelineRule */ public DatelineRule getDatelineRule() { return datelineRule; } @Override protected Shape parseShapeByType(WktShapeParser.State state, String shapeType) throws ParseException { if (shapeType.equalsIgnoreCase("POLYGON")) { return parsePolygonShape(state); } else if (shapeType.equalsIgnoreCase("MULTIPOLYGON")) { return parseMulitPolygonShape(state); } return super.parseShapeByType(state, shapeType); } /** Bypasses {@link JtsSpatialContext#makeLineString(java.util.List)} so that we can more * efficiently get the LineString without creating a {@code List}. */ @Override protected Shape parseLineStringShape(WktShapeParser.State state) throws ParseException { if (!ctx.useJtsLineString()) return super.parseLineStringShape(state); if (state.nextIfEmptyAndSkipZM()) return ctx.makeLineString(Collections.emptyList()); GeometryFactory geometryFactory = ctx.getGeometryFactory(); Coordinate[] coordinates = coordinateSequence(state); return makeShapeFromGeometry(geometryFactory.createLineString(coordinates)); } /** * Parses a POLYGON shape from the raw string. It might return a {@link com.spatial4j.core.shape.Rectangle} * if the polygon is one. *
   *   coordinateSequenceList
   * 
*/ protected Shape parsePolygonShape(WktShapeParser.State state) throws ParseException { Geometry geometry; if (state.nextIfEmptyAndSkipZM()) { GeometryFactory geometryFactory = ctx.getGeometryFactory(); geometry = geometryFactory.createPolygon(geometryFactory.createLinearRing( new Coordinate[]{}), null); } else { geometry = polygon(state); if (geometry.isRectangle()) { //TODO although, might want to never convert if there's a semantic difference (e.g. geodetically) return makeRectFromPoly(geometry); } } return makeShapeFromGeometry(geometry); } protected Rectangle makeRectFromPoly(Geometry geometry) { assert geometry.isRectangle(); Envelope env = geometry.getEnvelopeInternal(); boolean crossesDateline = false; if (ctx.isGeo() && getDatelineRule() != DatelineRule.none) { if (getDatelineRule() == DatelineRule.ccwRect) { // If JTS says it is clockwise, then it's actually a dateline crossing rectangle. crossesDateline = ! CGAlgorithms.isCCW(geometry.getCoordinates()); } else { crossesDateline = env.getWidth() > 180; } } if (crossesDateline) return ctx.makeRectangle(env.getMaxX(), env.getMinX(), env.getMinY(), env.getMaxY()); else return ctx.makeRectangle(env.getMinX(), env.getMaxX(), env.getMinY(), env.getMaxY()); } /** * Reads a polygon, returning a JTS polygon. */ protected Polygon polygon(WktShapeParser.State state) throws ParseException { GeometryFactory geometryFactory = ctx.getGeometryFactory(); List coordinateSequenceList = coordinateSequenceList(state); LinearRing shell = geometryFactory.createLinearRing (coordinateSequenceList.get(0)); LinearRing[] holes = null; if (coordinateSequenceList.size() > 1) { holes = new LinearRing[coordinateSequenceList.size() - 1]; for (int i = 1; i < coordinateSequenceList.size(); i++) { holes[i - 1] = geometryFactory.createLinearRing(coordinateSequenceList.get(i)); } } return geometryFactory.createPolygon(shell, holes); } /** * Parses a MULTIPOLYGON shape from the raw string. *
   *   '(' polygon (',' polygon )* ')'
   * 
*/ protected Shape parseMulitPolygonShape(WktShapeParser.State state) throws ParseException { if (state.nextIfEmptyAndSkipZM()) return ctx.makeCollection(Collections.EMPTY_LIST); List polygons = new ArrayList(); state.nextExpect('('); do { polygons.add(parsePolygonShape(state)); } while (state.nextIf(',')); state.nextExpect(')'); return ctx.makeCollection(polygons); } /** * Reads a list of JTS Coordinate sequences from the current position. *
   *   '(' coordinateSequence (',' coordinateSequence )* ')'
   * 
*/ protected List coordinateSequenceList(WktShapeParser.State state) throws ParseException { List sequenceList = new ArrayList(); state.nextExpect('('); do { sequenceList.add(coordinateSequence(state)); } while (state.nextIf(',')); state.nextExpect(')'); return sequenceList; } /** * Reads a JTS Coordinate sequence from the current position. *
   *   '(' coordinate (',' coordinate )* ')'
   * 
*/ protected Coordinate[] coordinateSequence(WktShapeParser.State state) throws ParseException { List sequence = new ArrayList(); state.nextExpect('('); do { sequence.add(coordinate(state)); } while (state.nextIf(',')); state.nextExpect(')'); return sequence.toArray(new Coordinate[sequence.size()]); } /** * Reads a {@link com.vividsolutions.jts.geom.Coordinate} from the current position. * It's akin to {@link #point(com.spatial4j.core.io.WktShapeParser.State)} but for * a JTS Coordinate. Only the first 2 numbers are parsed; any remaining are ignored. */ protected Coordinate coordinate(WktShapeParser.State state) throws ParseException { double x = ctx.normX(state.nextDouble()); ctx.verifyX(x); double y = ctx.normY(state.nextDouble()); ctx.verifyY(y); state.skipNextDoubles(); return new Coordinate(x, y); } @Override protected double normDist(double v) { return ctx.getGeometryFactory().getPrecisionModel().makePrecise(v); } /** Creates the JtsGeometry, potentially validating, repairing, and preparing. */ protected JtsGeometry makeShapeFromGeometry(Geometry geometry) { final boolean dateline180Check = getDatelineRule() != DatelineRule.none; JtsGeometry jtsGeom; try { jtsGeom = ctx.makeShape(geometry, dateline180Check, ctx.isAllowMultiOverlap()); if (isAutoValidate()) jtsGeom.validate(); } catch (RuntimeException e) { //repair: if (validationRule == ValidationRule.repairConvexHull) { jtsGeom = ctx.makeShape(geometry.convexHull(), dateline180Check, ctx.isAllowMultiOverlap()); } else if (validationRule == ValidationRule.repairBuffer0) { jtsGeom = ctx.makeShape(geometry.buffer(0), dateline180Check, ctx.isAllowMultiOverlap()); } else { //TODO there are other smarter things we could do like repairing inner holes and subtracting // from outer repaired shell; but we needn't try too hard. throw e; } } if (isAutoIndex()) jtsGeom.index(); return jtsGeom; } /** * Indicates the algorithm used to process JTS Polygons and JTS LineStrings for detecting dateline * crossings. It only applies when geo=true. */ public enum DatelineRule { /** No polygon will cross the dateline. */ none, /** Adjacent points with an x (longitude) difference that spans more than half * way around the globe will be interpreted as going the other (shorter) way, and thus cross the * dateline. */ width180,//TODO is there a better name that doesn't have '180' in it? /** For rectangular polygons, the point order is interpreted as being counter-clockwise (CCW). * However, non-rectangular polygons or other shapes aren't processed this way; they use the * {@link #width180} rule instead. The CCW rule is specified by OGC Simple Features * Specification v. 1.2.0 section 6.1.11.1. */ ccwRect } /** Indicates how JTS geometries (notably polygons but applies to other geometries too) are * validated (if at all) and repaired (if at all). */ public enum ValidationRule { /** Geometries will not be validated (because it's kinda expensive to calculate). You may or may * not ultimately get an error at some point; results are undefined. However, note that * coordinates will still be validated for falling within the world boundaries. * @see com.vividsolutions.jts.geom.Geometry#isValid(). */ none, /** Geometries will be explicitly validated on creation, possibly resulting in an exception: * {@link com.spatial4j.core.exception.InvalidShapeException}. */ error, /** Invalid Geometries are repaired by taking the convex hull. The result will very likely be a * larger shape that matches false-positives, but no false-negatives. * See {@link com.vividsolutions.jts.geom.Geometry#convexHull()}. */ repairConvexHull, /** Invalid polygons are repaired using the {@code buffer(0)} technique. From the JTS FAQ: *

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

*/ repairBuffer0 } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/package-info.java000066400000000000000000000015631241767633500255640ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** Reading & writing shapes in various forms. */ package com.spatial4j.core.io;spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/package-info.java000066400000000000000000000016321241767633500251520ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * This is the base package for Spatial4j from which the rest of it is organized. */ package com.spatial4j.core; spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/000077500000000000000000000000001241767633500230615ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/Circle.java000066400000000000000000000026311241767633500251270ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape; /** * A circle, also known as a point-radius since that is what it is comprised of. */ public interface Circle extends Shape { /** * Expert: Resets the state of this shape given the arguments. This is a * performance feature to avoid excessive Shape object allocation as well as * some argument error checking. Mutable shapes is error-prone so use with * care. */ void reset(double x, double y, double radiusDEG); /** * The distance from the point's center to its edge, measured in the same * units as x & y (e.g. degrees if WGS84). */ double getRadius(); } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/Point.java000066400000000000000000000025641241767633500250240ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape; /** * A Point with X & Y coordinates. */ public interface Point extends Shape { /** * Expert: Resets the state of this shape given the arguments. This is a * performance feature to avoid excessive Shape object allocation as well as * some argument error checking. Mutable shapes is error-prone so use with * care. */ public void reset(double x, double y); /** The X coordinate, or Longitude in geospatial contexts. */ public double getX(); /** The Y coordinate, or Latitude in geospatial contexts. */ public double getY(); } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/Rectangle.java000066400000000000000000000051321241767633500256310ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape; /** * A rectangle aligned with the axis (i.e. it is not at an angle). *

* In geospatial contexts, it may cross the international date line (-180 * longitude) if {@link #getCrossesDateLine()} however it cannot pass the poles * although it may span the globe. It spans the globe if the X coordinate * (Longitude) goes from -180 to 180 as seen from {@link #getMinX()} and {@link * #getMaxX()}. */ public interface Rectangle extends Shape { /** * Expert: Resets the state of this shape given the arguments. This is a * performance feature to avoid excessive Shape object allocation as well as * some argument error checking. Mutable shapes is error-prone so use with * care. */ public void reset(double minX, double maxX, double minY, double maxY); /** * The width. In geospatial contexts, this is generally in degrees longitude * and is aware of the international dateline. It will always be >= 0. */ public double getWidth(); /** * The height. In geospatial contexts, this is in degrees latitude. It will * always be >= 0. */ public double getHeight(); /** The left edge of the X coordinate. */ public double getMinX(); /** The bottom edge of the Y coordinate. */ public double getMinY(); /** The right edge of the X coordinate. */ public double getMaxX(); /** The top edge of the Y coordinate. */ public double getMaxY(); /** Only meaningful for geospatial contexts. */ public boolean getCrossesDateLine(); /** * A specialization of {@link Shape#relate(Shape)} * for a vertical line. */ public SpatialRelation relateYRange(double minY, double maxY); /** * A specialization of {@link Shape#relate(Shape)} * for a horizontal line. */ public SpatialRelation relateXRange(double minX, double maxX); } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/Shape.java000066400000000000000000000077321241767633500247750ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape; import com.spatial4j.core.context.SpatialContext; /** * The base interface defining a geometric shape. Shape instances should be * instantiated via one of the create* methods on a {@link SpatialContext} or * by reading WKT which calls those methods; they should not be * created directly. *

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

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

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

* Postcondition: this.getBoundingBox().relate(this) == CONTAINS */ Rectangle getBoundingBox(); /** * Does the shape have area? This will be false for points and lines. It will * also be false for shapes that normally have area but are constructed in a * degenerate case as to not have area (e.g. a circle with 0 radius or * rectangle with no height or no width). */ boolean hasArea(); /** * Calculates the area of the shape, in square-degrees. If ctx is null then * simple Euclidean calculations will be used. This figure can be an * estimate. */ double getArea(SpatialContext ctx); /** * Returns the center point of this shape. This is usually the same as * getBoundingBox().getCenter() but it doesn't have to be. *

* Postcondition: this.relate(this.getCenter()) == CONTAINS */ Point getCenter(); /** * Returns a buffered version of this shape. The buffer is usually a * rounded-corner buffer, although some shapes might buffer differently. This * is an optional operation. * * * @param distance * @return Not null, and the returned shape should contain the current shape. */ Shape getBuffered(double distance, SpatialContext ctx); /** * Shapes can be "empty", which is to say it exists nowhere. The underlying coordinates are * typically NaN. */ boolean isEmpty(); /** The sub-classes of Shape generally implement the * same contract for {@link Object#equals(Object)} and {@link Object#hashCode()} * amongst the same sub-interface type. This means, for example, that multiple * Point implementations of different classes are equal if they share the same x * & y. */ @Override public boolean equals(Object other); } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/ShapeCollection.java000066400000000000000000000174371241767633500270140ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.impl.Range; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.RandomAccess; import static com.spatial4j.core.shape.SpatialRelation.CONTAINS; import static com.spatial4j.core.shape.SpatialRelation.INTERSECTS; /** * A collection of Shape objects, analogous to an OGC GeometryCollection. The * implementation demands a List (with random access) so that the order can be * retained if an application requires it, although logically it's treated as an * unordered Set, mostly. *

* Ideally, {@link #relate(Shape)} should return the same result no matter what * the shape order is, although the default implementation can be order * dependent when the shapes overlap; see {@link #relateContainsShortCircuits()}. * To improve performance slightly, the caller could order the shapes by * largest first so that relate() will have a greater chance of * short-circuit'ing sooner. As the Shape contract states; it may return * intersects when the best answer is actually contains or within. If any shape * intersects the provided shape then that is the answer. *

* This implementation is not optimized for a large number of shapes; relate is * O(N). A more sophisticated implementation might do an R-Tree based on * bbox'es, for example. */ public class ShapeCollection extends AbstractList implements Shape { protected final List shapes; protected final Rectangle bbox; /** * WARNING: {@code shapes} is copied by reference. * @param shapes Copied by reference! (make a defensive copy if caller modifies) * @param ctx */ public ShapeCollection(List shapes, SpatialContext ctx) { if (!(shapes instanceof RandomAccess)) throw new IllegalArgumentException("Shapes arg must implement RandomAccess: "+shapes.getClass()); this.shapes = shapes; this.bbox = computeBoundingBox(shapes, ctx); } protected Rectangle computeBoundingBox(Collection shapes, SpatialContext ctx) { if (shapes.isEmpty()) return ctx.makeRectangle(Double.NaN, Double.NaN, Double.NaN, Double.NaN); Range xRange = null; double minY = Double.POSITIVE_INFINITY; double maxY = Double.NEGATIVE_INFINITY; for (Shape geom : shapes) { Rectangle r = geom.getBoundingBox(); Range xRange2 = Range.xRange(r, ctx); if (xRange == null) { xRange = xRange2; } else { xRange = xRange.expandTo(xRange2); } minY = Math.min(minY, r.getMinY()); maxY = Math.max(maxY, r.getMaxY()); } return ctx.makeRectangle(xRange.getMin(), xRange.getMax(), minY, maxY); } public List getShapes() { return shapes; } @Override public S get(int index) { return shapes.get(index); } @Override public int size() { return shapes.size(); } @Override public Rectangle getBoundingBox() { return bbox; } @Override public Point getCenter() { return bbox.getCenter(); } @Override public boolean hasArea() { for (Shape geom : shapes) { if( geom.hasArea() ) { return true; } } return false; } @Override public ShapeCollection getBuffered(double distance, SpatialContext ctx) { List bufColl = new ArrayList(size()); for (Shape shape : shapes) { bufColl.add(shape.getBuffered(distance, ctx)); } return ctx.makeCollection(bufColl); } @Override public SpatialRelation relate(Shape other) { final SpatialRelation bboxSect = bbox.relate(other); if (bboxSect == SpatialRelation.DISJOINT || bboxSect == SpatialRelation.WITHIN) return bboxSect; final boolean containsWillShortCircuit = (other instanceof Point) || relateContainsShortCircuits(); SpatialRelation sect = null; for (Shape shape : shapes) { SpatialRelation nextSect = shape.relate(other); if (sect == null) {//first pass sect = nextSect; } else { sect = sect.combine(nextSect); } if (sect == INTERSECTS) return INTERSECTS; if (sect == CONTAINS && containsWillShortCircuit) return CONTAINS; } return sect; } /** * Called by relate() to determine whether to return early if it finds * CONTAINS, instead of checking the remaining shapes. It will do so without * calling this method if the "other" shape is a Point. If a remaining shape * finds INTERSECTS, then INTERSECTS will be returned. The only problem with * this returning true is that if some of the shapes overlap, it's possible * that the result of relate() could be dependent on the order of the shapes, * which could be unexpected / wrong depending on the application. The default * implementation returns true because it probably doesn't matter. If it * does, a subclass could add a boolean flag that this method could return. * That flag could be initialized to true only if the shapes are mutually * disjoint. * * @see #computeMutualDisjoint(java.util.List) . */ protected boolean relateContainsShortCircuits() { return true; } /** * Computes whether the shapes are mutually disjoint. This is a utility method * offered for use by a subclass implementing {@link #relateContainsShortCircuits()}. * Beware: this is an O(N^2) algorithm.. Consequently, consider safely * assuming non-disjoint if shapes.size() > 10 or something. And if all shapes * are a Point then the result of this method doesn't ultimately matter. */ protected static boolean computeMutualDisjoint(List shapes) { //WARNING: this is an O(n^2) algorithm. //loop through each shape and see if it intersects any shape before it for (int i = 1; i < shapes.size(); i++) { Shape shapeI = shapes.get(i); for (int j = 0; j < i; j++) { Shape shapeJ = shapes.get(j); if (shapeJ.relate(shapeI).intersects()) return false; } } return true; } @Override public double getArea(SpatialContext ctx) { double MAX_AREA = bbox.getArea(ctx); double sum = 0; for (Shape geom : shapes) { sum += geom.getArea(ctx); if (sum >= MAX_AREA) return MAX_AREA; } return sum; } @Override public String toString() { StringBuilder buf = new StringBuilder(100); buf.append("ShapeCollection("); int i = 0; for (Shape shape : shapes) { if (i++ > 0) buf.append(", "); buf.append(shape); if (buf.length() > 150) { buf.append(" ...").append(shapes.size()); break; } } buf.append(")"); return buf.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ShapeCollection that = (ShapeCollection) o; if (!shapes.equals(that.shapes)) return false; return true; } @Override public int hashCode() { return shapes.hashCode(); } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/SpatialRelation.java000066400000000000000000000112161241767633500270200ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape; /** * The set of spatial relationships. Naming is somewhat consistent with OGC spec * conventions as seen in SQL/MM and others. *

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

* The "CONTAINS" and "WITHIN" wording here is inconsistent with OGC; these here map to OGC * "COVERS" and "COVERED BY", respectively. The distinction is in the boundaries; in Spatial4j * there is no boundary distinction -- boundaries are part of the shape as if it was an "interior", * with respect to OGC's terminology. */ public enum SpatialRelation { //see http://docs.geotools.org/latest/userguide/library/jts/dim9.html#preparedgeometry /** * The shape is within the target geometry. It's the converse of {@link #CONTAINS}. * Boundaries of shapes count too. OGC specs refer to this relation as "COVERED BY"; * WITHIN is differentiated there by not including boundaries. */ WITHIN, /** * The shape contains the target geometry. It's the converse of {@link #WITHIN}. * Boundaries of shapes count too. OGC specs refer to this relation as "COVERS"; * CONTAINS is differentiated there by not including boundaries. */ CONTAINS, /** * The shape shares no point in common with the target shape. */ DISJOINT, /** * The shape shares some points/overlap with the target shape, and the relation is * not more specifically {@link #WITHIN} or {@link #CONTAINS}. */ INTERSECTS; //Don't have these: TOUCHES, CROSSES, OVERLAPS, nor distinction between CONTAINS/COVERS /** * Given the result of shapeA.relate(shapeB), transposing that * result should yield the result of shapeB.relate(shapeA). There * is a corner case is when the shapes are equal, in which case actually * flipping the relate() call will result in the same value -- either CONTAINS * or WITHIN; this method can't possible check for that so the caller might * have to. */ public SpatialRelation transpose() { switch(this) { case CONTAINS: return SpatialRelation.WITHIN; case WITHIN: return SpatialRelation.CONTAINS; default: return this; } } /** * If you were to call aShape.relate(bShape) and aShape.relate(cShape), you * could call this to merge the intersect results as if bShape & cShape were * combined into {@link ShapeCollection}. */ public SpatialRelation combine(SpatialRelation other) { // You can think of this algorithm as a state transition / automata. // 1. The answer must be the same no matter what the order is. // 2. If any INTERSECTS, then the result is INTERSECTS (done). // 3. A DISJOINT + WITHIN == INTERSECTS (done). // 4. A DISJOINT + CONTAINS == CONTAINS. // 5. A CONTAINS + WITHIN == INTERSECTS (done). (weird scenario) // 6. X + X == X. if (other == this) return this; if (this == DISJOINT && other == CONTAINS || this == CONTAINS && other == DISJOINT) return CONTAINS; return INTERSECTS; } /** Not DISJOINT, i.e. there is some sort of intersection. */ public boolean intersects() { return this != DISJOINT; } /** * If aShape.relate(bShape) is r, then r.inverse() * is inverse(aShape).relate(bShape) whereas * inverse(shape) is theoretically the opposite area covered by a * shape, i.e. everywhere but where the shape is. *

* Note that it's not commutative! WITHIN.inverse().inverse() != * WITHIN. */ public SpatialRelation inverse() { switch(this) { case DISJOINT: return CONTAINS; case CONTAINS: return DISJOINT; case WITHIN: return INTERSECTS;//not commutative! } return INTERSECTS; } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/000077500000000000000000000000001241767633500240225ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/BufferedLine.java000066400000000000000000000202521241767633500272200ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape.impl; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.distance.DistanceUtils; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.SpatialRelation; import static com.spatial4j.core.shape.SpatialRelation.CONTAINS; import static com.spatial4j.core.shape.SpatialRelation.DISJOINT; import static com.spatial4j.core.shape.SpatialRelation.INTERSECTS; import static com.spatial4j.core.shape.SpatialRelation.WITHIN; /** * INTERNAL: A line between two points with a buffer distance extending in every direction. By * contrast, an un-buffered line covers no area and as such is extremely unlikely to intersect with * a point. BufferedLine isn't yet aware of geodesics (e.g. the dateline); it operates in Euclidean * space. */ public class BufferedLine implements Shape { private final Point pA, pB; private final double buf; private final Rectangle bbox; /** * the primary line; passes through pA & pB */ private final InfBufLine linePrimary; /** * perpendicular to the primary line, centered between pA & pB */ private final InfBufLine linePerp; /** * Creates a buffered line from pA to pB. The buffer extends on both sides of * the line, making the width 2x the buffer. The buffer extends out from * pA & pB, making the line in effect 2x the buffer longer than pA to pB. * * @param pA start point * @param pB end point * @param buf the buffer distance in degrees * @param ctx */ public BufferedLine(Point pA, Point pB, double buf, SpatialContext ctx) { assert buf >= 0;//TODO support buf=0 via another class ? /** * If true, buf should bump-out from the pA & pB, in effect * extending the line a little. */ final boolean bufExtend = true;//TODO support false and make this a // parameter this.pA = pA; this.pB = pB; this.buf = buf; double deltaY = pB.getY() - pA.getY(); double deltaX = pB.getX() - pA.getX(); PointImpl center = new PointImpl(pA.getX() + deltaX / 2, pA.getY() + deltaY / 2, null); double perpExtent = bufExtend ? buf : 0; if (deltaX == 0 && deltaY == 0) { linePrimary = new InfBufLine(0, center, buf); linePerp = new InfBufLine(Double.POSITIVE_INFINITY, center, buf); } else { linePrimary = new InfBufLine(deltaY / deltaX, center, buf); double length = Math.sqrt(deltaX * deltaX + deltaY * deltaY); linePerp = new InfBufLine(-deltaX / deltaY, center, length / 2 + perpExtent); } double minY, maxY; double minX, maxX; if (deltaX == 0) { // vertical if (pA.getY() <= pB.getY()) { minY = pA.getY(); maxY = pB.getY(); } else { minY = pB.getY(); maxY = pA.getY(); } minX = pA.getX() - buf; maxX = pA.getX() + buf; minY = minY - perpExtent; maxY = maxY + perpExtent; } else { if (!bufExtend) { throw new UnsupportedOperationException("TODO"); //solve for B & A (C=buf), one is buf-x, other is buf-y. } //Given a right triangle of A, B, C sides, C (hypotenuse) == // buf, and A + B == the bounding box offset from pA & pB in x & y. double bboxBuf = buf * (1 + Math.abs(linePrimary.getSlope())) * linePrimary.getDistDenomInv(); assert bboxBuf >= buf && bboxBuf <= buf * 1.5; if (pA.getX() <= pB.getX()) { minX = pA.getX() - bboxBuf; maxX = pB.getX() + bboxBuf; } else { minX = pB.getX() - bboxBuf; maxX = pA.getX() + bboxBuf; } if (pA.getY() <= pB.getY()) { minY = pA.getY() - bboxBuf; maxY = pB.getY() + bboxBuf; } else { minY = pB.getY() - bboxBuf; maxY = pA.getY() + bboxBuf; } } Rectangle bounds = ctx.getWorldBounds(); bbox = ctx.makeRectangle( Math.max(bounds.getMinX(), minX), Math.min(bounds.getMaxX(), maxX), Math.max(bounds.getMinY(), minY), Math.min(bounds.getMaxY(), maxY)); } @Override public boolean isEmpty() { return pA.isEmpty(); } @Override public Shape getBuffered(double distance, SpatialContext ctx) { return new BufferedLine(pA, pB, buf + distance, ctx); } /** * Calls {@link DistanceUtils#calcLonDegreesAtLat(double, double)} given pA or pB's latitude; * whichever is farthest. It's useful to expand a buffer of a line segment when used in * a geospatial context to cover the desired area. */ public static double expandBufForLongitudeSkew(Point pA, Point pB, double buf) { double absA = Math.abs(pA.getY()); double absB = Math.abs(pB.getY()); double maxLat = Math.max(absA, absB); double newBuf = DistanceUtils.calcLonDegreesAtLat(maxLat, buf); // if (newBuf + maxLat >= 90) { // //TODO substitute spherical cap ? // } assert newBuf >= buf; return newBuf; } @Override public SpatialRelation relate(Shape other) { if (other instanceof Point) return contains((Point) other) ? CONTAINS : DISJOINT; if (other instanceof Rectangle) return relate((Rectangle) other); throw new UnsupportedOperationException(); } public SpatialRelation relate(Rectangle r) { //Check BBox for disjoint & within. SpatialRelation bboxR = bbox.relate(r); if (bboxR == DISJOINT || bboxR == WITHIN) return bboxR; //Either CONTAINS, INTERSECTS, or DISJOINT Point scratch = new PointImpl(0, 0, null); Point prC = r.getCenter(); SpatialRelation result = linePrimary.relate(r, prC, scratch); if (result == DISJOINT) return DISJOINT; SpatialRelation resultOpp = linePerp.relate(r, prC, scratch); if (resultOpp == DISJOINT) return DISJOINT; if (result == resultOpp)//either CONTAINS or INTERSECTS return result; return INTERSECTS; } public boolean contains(Point p) { //TODO check bbox 1st? return linePrimary.contains(p) && linePerp.contains(p); } public Rectangle getBoundingBox() { return bbox; } @Override public boolean hasArea() { return buf > 0; } @Override public double getArea(SpatialContext ctx) { return linePrimary.getBuf() * linePerp.getBuf() * 4; } @Override public Point getCenter() { return getBoundingBox().getCenter(); } public Point getA() { return pA; } public Point getB() { return pB; } public double getBuf() { return buf; } /** * INTERNAL */ public InfBufLine getLinePrimary() { return linePrimary; } /** * INTERNAL */ public InfBufLine getLinePerp() { return linePerp; } @Override public String toString() { return "BufferedLine(" + pA + ", " + pB + " b=" + buf + ")"; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BufferedLine that = (BufferedLine) o; if (Double.compare(that.buf, buf) != 0) return false; if (!pA.equals(that.pA)) return false; if (!pB.equals(that.pB)) return false; return true; } @Override public int hashCode() { int result; long temp; result = pA.hashCode(); result = 31 * result + pB.hashCode(); temp = buf != +0.0d ? Double.doubleToLongBits(buf) : 0L; result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/BufferedLineString.java000066400000000000000000000131761241767633500304160ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape.impl; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.ShapeCollection; import com.spatial4j.core.shape.SpatialRelation; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * A BufferedLineString is a collection of {@link com.spatial4j.core.shape.impl.BufferedLine} shapes, * resulting in what some call a "Track" or "Polyline" (ESRI terminology). * The buffer can be 0. Note that BufferedLine isn't yet aware of geodesics (e.g. the dateline). */ public class BufferedLineString implements Shape { //TODO add some geospatial awareness like: // segment that spans at the dateline (split it at DL?). private final ShapeCollection segments; private final double buf; /** * Needs at least 1 point, usually more than that. If just one then it's * internally treated like 2 points. */ public BufferedLineString(List points, double buf, SpatialContext ctx) { this(points, buf, false, ctx); } /** * @param points ordered control points. If empty then this shape is empty. * @param buf Buffer >= 0 * @param expandBufForLongitudeSkew See {@link BufferedLine * #expandBufForLongitudeSkew(com.spatial4j.core.shape.Point, * com.spatial4j.core.shape.Point, double)}. * If true then the buffer for each segment * is computed. * @param ctx */ public BufferedLineString(List points, double buf, boolean expandBufForLongitudeSkew, SpatialContext ctx) { this.buf = buf; if (points.isEmpty()) { this.segments = ctx.makeCollection(Collections.emptyList()); } else { List segments = new ArrayList(points.size() - 1); Point prevPoint = null; for (Point point : points) { if (prevPoint != null) { double segBuf = buf; if (expandBufForLongitudeSkew) { //TODO this is faulty in that it over-buffers. See Issue#60. segBuf = BufferedLine.expandBufForLongitudeSkew(prevPoint, point, buf); } segments.add(new BufferedLine(prevPoint, point, segBuf, ctx)); } prevPoint = point; } if (segments.isEmpty()) {//TODO throw exception instead? segments.add(new BufferedLine(prevPoint, prevPoint, buf, ctx)); } this.segments = ctx.makeCollection(segments); } } @Override public boolean isEmpty() { return segments.isEmpty(); } @Override public Shape getBuffered(double distance, SpatialContext ctx) { return ctx.makeBufferedLineString(getPoints(), buf + distance); } public ShapeCollection getSegments() { return segments; } public double getBuf() { return buf; } @Override public double getArea(SpatialContext ctx) { return segments.getArea(ctx); } @Override public SpatialRelation relate(Shape other) { return segments.relate(other); } @Override public boolean hasArea() { return segments.hasArea(); } @Override public Point getCenter() { return segments.getCenter(); } @Override public Rectangle getBoundingBox() { return segments.getBoundingBox(); } @Override public String toString() { StringBuilder str = new StringBuilder(100); str.append("BufferedLineString(buf=").append(buf).append(" pts="); boolean first = true; for (Point point : getPoints()) { if (first) { first = false; } else { str.append(", "); } str.append(point.getX()).append(' ').append(point.getY()); } str.append(')'); return str.toString(); } public List getPoints() { if (segments.isEmpty()) return Collections.emptyList(); final List lines = segments.getShapes(); return new AbstractList() { @Override public Point get(int index) { if (index == 0) return lines.get(0).getA(); return lines.get(index - 1).getB(); } @Override public int size() { return lines.size() + 1; } }; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BufferedLineString that = (BufferedLineString) o; if (Double.compare(that.buf, buf) != 0) return false; if (!segments.equals(that.segments)) return false; return true; } @Override public int hashCode() { int result; long temp; result = segments.hashCode(); temp = buf != +0.0d ? Double.doubleToLongBits(buf) : 0L; result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/CircleImpl.java000066400000000000000000000221271241767633500267140ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape.impl; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.Circle; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.SpatialRelation; /** * A circle, also known as a point-radius, based on a {@link * com.spatial4j.core.distance.DistanceCalculator} which does all the work. This * implementation should work for both cartesian 2D and geodetic sphere * surfaces. */ public class CircleImpl implements Circle { protected final SpatialContext ctx; protected final Point point; protected double radiusDEG; // calculated & cached protected Rectangle enclosingBox; public CircleImpl(Point p, double radiusDEG, SpatialContext ctx) { //We assume any validation of params already occurred (including bounding dist) this.ctx = ctx; this.point = p; this.radiusDEG = point.isEmpty() ? Double.NaN : radiusDEG; this.enclosingBox = point.isEmpty() ? ctx.makeRectangle(Double.NaN, Double.NaN, Double.NaN, Double.NaN) : ctx.getDistCalc().calcBoxByDistFromPt(point, this.radiusDEG, ctx, null); } @Override public void reset(double x, double y, double radiusDEG) { assert ! isEmpty(); point.reset(x, y); this.radiusDEG = radiusDEG; this.enclosingBox = ctx.getDistCalc().calcBoxByDistFromPt(point, this.radiusDEG, ctx, enclosingBox); } @Override public boolean isEmpty() { return point.isEmpty(); } @Override public Point getCenter() { return point; } @Override public double getRadius() { return radiusDEG; } @Override public double getArea(SpatialContext ctx) { if (ctx == null) { return Math.PI * radiusDEG * radiusDEG; } else { return ctx.getDistCalc().area(this); } } @Override public Circle getBuffered(double distance, SpatialContext ctx) { return ctx.makeCircle(point, distance + radiusDEG); } public boolean contains(double x, double y) { return ctx.getDistCalc().within(point, x, y, radiusDEG); } @Override public boolean hasArea() { return radiusDEG > 0; } /** * Note that the bounding box might contain a minX that is > maxX, due to WGS84 dateline. */ @Override public Rectangle getBoundingBox() { return enclosingBox; } @Override public SpatialRelation relate(Shape other) { //This shortcut was problematic in testing due to distinctions of CONTAINS/WITHIN for no-area shapes (lines, points). // if (distance == 0) { // return point.relate(other,ctx).intersects() ? SpatialRelation.WITHIN : SpatialRelation.DISJOINT; // } if (isEmpty() || other.isEmpty()) return SpatialRelation.DISJOINT; if (other instanceof Point) { return relate((Point) other); } if (other instanceof Rectangle) { return relate((Rectangle) other); } if (other instanceof Circle) { return relate((Circle) other); } return other.relate(this).transpose(); } public SpatialRelation relate(Point point) { return contains(point.getX(),point.getY()) ? SpatialRelation.CONTAINS : SpatialRelation.DISJOINT; } public SpatialRelation relate(Rectangle r) { //Note: Surprisingly complicated! //--We start by leveraging the fact we have a calculated bbox that is "cheaper" than use of DistanceCalculator. final SpatialRelation bboxSect = enclosingBox.relate(r); if (bboxSect == SpatialRelation.DISJOINT || bboxSect == SpatialRelation.WITHIN) return bboxSect; else if (bboxSect == SpatialRelation.CONTAINS && enclosingBox.equals(r))//nasty identity edge-case return SpatialRelation.WITHIN; //bboxSect is INTERSECTS or CONTAINS //The result can be DISJOINT, CONTAINS, or INTERSECTS (not WITHIN) return relateRectanglePhase2(r, bboxSect); } protected SpatialRelation relateRectanglePhase2(final Rectangle r, SpatialRelation bboxSect) { // DOES NOT WORK WITH GEO CROSSING DATELINE OR WORLD-WRAP. Other methods handle such cases. //At this point, the only thing we are certain of is that circle is *NOT* WITHIN r, since the // bounding box of a circle MUST be within r for the circle to be within r. //Quickly determine if they are DISJOINT or not. // Find the closest & farthest point to the circle within the rectangle final double closestX, farthestX; final double xAxis = getXAxis(); if (xAxis < r.getMinX()) { closestX = r.getMinX(); farthestX = r.getMaxX(); } else if (xAxis > r.getMaxX()) { closestX = r.getMaxX(); farthestX = r.getMinX(); } else { closestX = xAxis; //we don't really use this value but to check this condition farthestX = r.getMaxX() - xAxis > xAxis - r.getMinX() ? r.getMaxX() : r.getMinX(); } final double closestY, farthestY; final double yAxis = getYAxis(); if (yAxis < r.getMinY()) { closestY = r.getMinY(); farthestY = r.getMaxY(); } else if (yAxis > r.getMaxY()) { closestY = r.getMaxY(); farthestY = r.getMinY(); } else { closestY = yAxis; //we don't really use this value but to check this condition farthestY = r.getMaxY() - yAxis > yAxis - r.getMinY() ? r.getMaxY() : r.getMinY(); } //If r doesn't overlap an axis, then could be disjoint. Test closestXY if (xAxis != closestX && yAxis != closestY) { if (!contains(closestX, closestY)) return SpatialRelation.DISJOINT; } // else CAN'T be disjoint if spans axis because earlier bbox check ruled that out //Now, we know it's *NOT* DISJOINT and it's *NOT* WITHIN either. // Does circle CONTAINS r or simply intersect it? //If circle contains r, then its bbox MUST also CONTAIN r. if (bboxSect != SpatialRelation.CONTAINS) return SpatialRelation.INTERSECTS; //If the farthest point of r away from the center of the circle is contained, then all of r is // contained. if (!contains(farthestX, farthestY)) return SpatialRelation.INTERSECTS; //geodetic detection of farthest Y when rect crosses x axis can't be reliably determined, so // check other corner too, which might actually be farthest if (point.getY() != getYAxis()) {//geodetic if (yAxis == closestY) {//r crosses north to south over x axis (confusing) double otherY = (farthestY == r.getMaxY() ? r.getMinY() : r.getMaxY()); if (!contains(farthestX, otherY)) return SpatialRelation.INTERSECTS; } } return SpatialRelation.CONTAINS; } /** * The Y coordinate of where the circle axis intersect. */ protected double getYAxis() { return point.getY(); } /** * The X coordinate of where the circle axis intersect. */ protected double getXAxis() { return point.getX(); } public SpatialRelation relate(Circle circle) { double crossDist = ctx.getDistCalc().distance(point, circle.getCenter()); double aDist = radiusDEG, bDist = circle.getRadius(); if (crossDist > aDist + bDist) return SpatialRelation.DISJOINT; if (crossDist < aDist && crossDist + bDist <= aDist) return SpatialRelation.CONTAINS; if (crossDist < bDist && crossDist + aDist <= bDist) return SpatialRelation.WITHIN; return SpatialRelation.INTERSECTS; } @Override public String toString() { return "Circle(" + point + ", d=" + radiusDEG + "°)"; } @Override public boolean equals(Object obj) { return equals(this,obj); } /** * All {@link Circle} implementations should use this definition of {@link Object#equals(Object)}. */ public static boolean equals(Circle thiz, Object o) { assert thiz != null; if (thiz == o) return true; if (!(o instanceof Circle)) return false; Circle circle = (Circle) o; if (!thiz.getCenter().equals(circle.getCenter())) return false; if (Double.compare(circle.getRadius(), thiz.getRadius()) != 0) return false; return true; } @Override public int hashCode() { return hashCode(this); } /** * All {@link Circle} implementations should use this definition of {@link Object#hashCode()}. */ public static int hashCode(Circle thiz) { int result; long temp; result = thiz.getCenter().hashCode(); temp = thiz.getRadius() != +0.0d ? Double.doubleToLongBits(thiz.getRadius()) : 0L; result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/GeoCircle.java000066400000000000000000000216351241767633500265300ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape.impl; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.distance.DistanceUtils; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.SpatialRelation; import java.util.Formatter; import java.util.Locale; /** * A circle as it exists on the surface of a sphere. */ public class GeoCircle extends CircleImpl { private GeoCircle inverseCircle;//when distance reaches > 1/2 way around the world, cache the inverse. private double horizAxisY;//see getYAxis public GeoCircle(Point p, double radiusDEG, SpatialContext ctx) { super(p, radiusDEG, ctx); assert ctx.isGeo(); init(); } @Override public void reset(double x, double y, double radiusDEG) { super.reset(x, y, radiusDEG); init(); } private void init() { if (radiusDEG > 90) { //--spans more than half the globe assert enclosingBox.getWidth() == 360; double backDistDEG = 180 - radiusDEG; if (backDistDEG > 0) { double backRadius = 180 - radiusDEG; double backX = DistanceUtils.normLonDEG(getCenter().getX() + 180); double backY = DistanceUtils.normLatDEG(getCenter().getY() + 180); //Shrink inverseCircle as small as possible to avoid accidental overlap. // Note that this is tricky business to come up with a value small enough // but not too small or else numerical conditioning issues become a problem. backRadius -= Math.max(Math.ulp(Math.abs(backY)+backRadius), Math.ulp(Math.abs(backX)+backRadius)); if (inverseCircle != null) { inverseCircle.reset(backX, backY, backRadius); } else { inverseCircle = new GeoCircle(ctx.makePoint(backX, backY), backRadius, ctx); } } else { inverseCircle = null;//whole globe } horizAxisY = getCenter().getY();//although probably not used } else { inverseCircle = null; double _horizAxisY = ctx.getDistCalc().calcBoxByDistFromPt_yHorizAxisDEG(getCenter(), radiusDEG, ctx); //some rare numeric conditioning cases can cause this to be barely beyond the box if (_horizAxisY > enclosingBox.getMaxY()) { horizAxisY = enclosingBox.getMaxY(); } else if (_horizAxisY < enclosingBox.getMinY()) { horizAxisY = enclosingBox.getMinY(); } else { horizAxisY = _horizAxisY; } //assert enclosingBox.relate_yRange(horizAxis,horizAxis,ctx).intersects(); } } @Override protected double getYAxis() { return horizAxisY; } /** * Called after bounding box is intersected. * @param bboxSect INTERSECTS or CONTAINS from enclosingBox's intersection * @return DISJOINT, CONTAINS, or INTERSECTS (not WITHIN) */ @Override protected SpatialRelation relateRectanglePhase2(Rectangle r, SpatialRelation bboxSect) { if (inverseCircle != null) { return inverseCircle.relate(r).inverse(); } //if a pole is wrapped, we have a separate algorithm if (enclosingBox.getWidth() == 360) { return relateRectangleCircleWrapsPole(r, ctx); } //This is an optimization path for when there are no dateline or pole issues. if (!enclosingBox.getCrossesDateLine() && !r.getCrossesDateLine()) { return super.relateRectanglePhase2(r, bboxSect); } //Rectangle wraps around the world longitudinally creating a solid band; there are no corners to test intersection if (r.getWidth() == 360) { return SpatialRelation.INTERSECTS; } //do quick check to see if all corners are within this circle for CONTAINS int cornersIntersect = numCornersIntersect(r); if (cornersIntersect == 4) { //ensure r's x axis is within c's. If it doesn't, r sneaks around the globe to touch the other side (intersect). SpatialRelation xIntersect = r.relateXRange(enclosingBox.getMinX(), enclosingBox.getMaxX()); if (xIntersect == SpatialRelation.WITHIN) return SpatialRelation.CONTAINS; return SpatialRelation.INTERSECTS; } //INTERSECT or DISJOINT ? if (cornersIntersect > 0) return SpatialRelation.INTERSECTS; //Now we check if one of the axis of the circle intersect with r. If so we have // intersection. /* x axis intersects */ if ( r.relateYRange(getYAxis(), getYAxis()).intersects() // at y vertical && r.relateXRange(enclosingBox.getMinX(), enclosingBox.getMaxX()).intersects() ) return SpatialRelation.INTERSECTS; /* y axis intersects */ if (r.relateXRange(getXAxis(), getXAxis()).intersects()) { // at x horizontal double yTop = getCenter().getY()+ radiusDEG; assert yTop <= 90; double yBot = getCenter().getY()- radiusDEG; assert yBot >= -90; if (r.relateYRange(yBot, yTop).intersects())//back bottom return SpatialRelation.INTERSECTS; } return SpatialRelation.DISJOINT; } private SpatialRelation relateRectangleCircleWrapsPole(Rectangle r, SpatialContext ctx) { //This method handles the case where the circle wraps ONE pole, but not both. For both, // there is the inverseCircle case handled before now. The only exception is for the case where // the circle covers the entire globe, and we'll check that first. if (radiusDEG == 180)//whole globe return SpatialRelation.CONTAINS; //Check if r is within the pole wrap region: double yTop = getCenter().getY() + radiusDEG; if (yTop > 90) { double yTopOverlap = yTop - 90; assert yTopOverlap <= 90; if (r.getMinY() >= 90 - yTopOverlap) return SpatialRelation.CONTAINS; } else { double yBot = point.getY() - radiusDEG; if (yBot < -90) { double yBotOverlap = -90 - yBot; assert yBotOverlap <= 90; if (r.getMaxY() <= -90 + yBotOverlap) return SpatialRelation.CONTAINS; } else { //This point is probably not reachable ?? assert yTop == 90 || yBot == -90;//we simply touch a pole //continue } } //If there are no corners to check intersection because r wraps completely... if (r.getWidth() == 360) return SpatialRelation.INTERSECTS; //Check corners: int cornersIntersect = numCornersIntersect(r); // (It might be possible to reduce contains() calls within nCI() to exactly two, but this intersection // code is complicated enough as it is.) double frontX = getCenter().getX(); if (cornersIntersect == 4) {//all double backX = frontX <= 0 ? frontX + 180 : frontX - 180; if (r.relateXRange(backX, backX).intersects()) return SpatialRelation.INTERSECTS; else return SpatialRelation.CONTAINS; } else if (cornersIntersect == 0) {//none if (r.relateXRange(frontX, frontX).intersects()) return SpatialRelation.INTERSECTS; else return SpatialRelation.DISJOINT; } else//partial return SpatialRelation.INTERSECTS; } /** Returns either 0 for none, 1 for some, or 4 for all. */ private int numCornersIntersect(Rectangle r) { //We play some logic games to avoid calling contains() which can be expensive. boolean bool;//if true then all corners intersect, if false then no corners intersect // for partial, we exit early with 1 and ignore bool. bool = (contains(r.getMinX(),r.getMinY())); if (contains(r.getMinX(),r.getMaxY())) { if (!bool) return 1;//partial } else { if (bool) return 1;//partial } if (contains(r.getMaxX(),r.getMinY())) { if (!bool) return 1;//partial } else { if (bool) return 1;//partial } if (contains(r.getMaxX(),r.getMaxY())) { if (!bool) return 1;//partial } else { if (bool) return 1;//partial } return bool?4:0; } @Override public String toString() { //Add distance in km, which may be easier to recognize. double distKm = DistanceUtils.degrees2Dist(radiusDEG, DistanceUtils.EARTH_MEAN_RADIUS_KM); //instead of String.format() so that we get consistent output no matter the locale String dStr = new Formatter(Locale.ROOT).format("%.1f\u00B0 %.2fkm", radiusDEG, distKm).toString(); return "Circle(" + point + ", d=" + dStr + ')'; } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/InfBufLine.java000066400000000000000000000120251241767633500266460ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape.impl; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.SpatialRelation; import static com.spatial4j.core.shape.SpatialRelation.*; /** * INERNAL: A buffered line of infinite length. * Public for test access. */ public class InfBufLine { //TODO consider removing support for vertical line -- let caller // do something else. BufferedLine could have a factory method // that returns a rectangle, for example. // line: y = slope * x + intercept private final double slope;//can be infinite for vertical line //if slope is infinite, this is x intercept, otherwise y intercept private final double intercept; private final double buf; private final double distDenomInv;//cached: 1 / Math.sqrt(slope * slope + 1) InfBufLine(double slope, Point point, double buf) { assert !Double.isNaN(slope); this.slope = slope; if (Double.isInfinite(slope)) { intercept = point.getX(); distDenomInv = Double.NaN; } else { intercept = point.getY() - slope * point.getX(); distDenomInv = 1 / Math.sqrt(slope * slope + 1); } this.buf = buf; } SpatialRelation relate(Rectangle r, Point prC, Point scratch) { assert r.getCenter().equals(prC); int cQuad = quadrant(prC); Point nearestP = scratch; cornerByQuadrant(r, oppositeQuad[cQuad], nearestP); boolean nearestContains = contains(nearestP); if (nearestContains) { Point farthestP = scratch; nearestP = null;//just to be safe (same scratch object) cornerByQuadrant(r, cQuad, farthestP); boolean farthestContains = contains(farthestP); if (farthestContains) return CONTAINS; return INTERSECTS; } else {// not nearestContains if (quadrant(nearestP) == cQuad) return DISJOINT;//out of buffer on same side as center return INTERSECTS;//nearest & farthest points straddle the line } } boolean contains(Point p) { return (distanceUnbuffered(p) <= buf); } /** INTERNAL AKA lineToPointDistance */ public double distanceUnbuffered(Point c) { if (Double.isInfinite(slope)) return Math.abs(c.getX() - intercept); // http://math.ucsd.edu/~wgarner/math4c/derivations/distance/distptline.htm double num = Math.abs(c.getY() - slope * c.getX() - intercept); return num * distDenomInv; } // /** Amount to add or subtract to intercept to indicate where the // * buffered line edges cross the y axis. // * @return // */ // double interceptBuffOffset() { // if (Double.isInfinite(slope)) // return slope; // if (buf == 0) // return 0; // double slopeDivBuf = slope / buf; // return Math.sqrt(buf*buf + slopeDivBuf*slopeDivBuf); // } /** INTERNAL: AKA lineToPointQuadrant */ public int quadrant(Point c) { //check vertical line case 1st if (Double.isInfinite(slope)) { //when slope is infinite, intercept is x intercept instead of y return c.getX() > intercept ? 1 : 2; //4 : 3 would work too } //(below will work for slope==0 horizontal line too) //is c above or below the line double yAtCinLine = slope * c.getX() + intercept; boolean above = c.getY() >= yAtCinLine; if (slope > 0) { //if slope is a forward slash, then result is 2 | 4 return above ? 2 : 4; } else { //if slope is a backward slash, then result is 1 | 3 return above ? 1 : 3; } } //TODO ? Use an Enum for quadrant? /* quadrants 1-4: NE, NW, SW, SE. */ private static final int[] oppositeQuad= {-1,3,4,1,2}; public static void cornerByQuadrant(Rectangle r, int cornerQuad, Point out) { double x = (cornerQuad == 1 || cornerQuad == 4) ? r.getMaxX() : r.getMinX(); double y = (cornerQuad == 1 || cornerQuad == 2) ? r.getMaxY() : r.getMinY(); out.reset(x, y); } public double getSlope() { return slope; } public double getIntercept() { return intercept; } public double getBuf() { return buf; } /** 1 / Math.sqrt(slope * slope + 1) */ public double getDistDenomInv() { return distDenomInv; } @Override public String toString() { return "InfBufLine{" + "buf=" + buf + ", intercept=" + intercept + ", slope=" + slope + '}'; } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/PointImpl.java000066400000000000000000000070551241767633500266070ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape.impl; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.Circle; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.SpatialRelation; /** A basic 2D implementation of a Point. */ public class PointImpl implements Point { private final SpatialContext ctx; private double x; private double y; /** A simple constructor without normalization / validation. */ public PointImpl(double x, double y, SpatialContext ctx) { this.ctx = ctx; reset(x, y); } @Override public boolean isEmpty() { return Double.isNaN(x); } @Override public void reset(double x, double y) { assert ! isEmpty(); this.x = x; this.y = y; } @Override public double getX() { return x; } @Override public double getY() { return y; } @Override public Rectangle getBoundingBox() { return ctx.makeRectangle(this, this); } @Override public PointImpl getCenter() { return this; } @Override public Circle getBuffered(double distance, SpatialContext ctx) { return ctx.makeCircle(this, distance); } @Override public SpatialRelation relate(Shape other) { if (isEmpty() || other.isEmpty()) return SpatialRelation.DISJOINT; if (other instanceof Point) return this.equals(other) ? SpatialRelation.INTERSECTS : SpatialRelation.DISJOINT; return other.relate(this).transpose(); } @Override public boolean hasArea() { return false; } @Override public double getArea(SpatialContext ctx) { return 0; } @Override public String toString() { return "Pt(x="+x+",y="+y+")"; } @Override public boolean equals(Object o) { return equals(this,o); } /** * All {@link Point} implementations should use this definition of {@link Object#equals(Object)}. */ public static boolean equals(Point thiz, Object o) { assert thiz != null; if (thiz == o) return true; if (!(o instanceof Point)) return false; Point point = (Point) o; if (Double.compare(point.getX(), thiz.getX()) != 0) return false; if (Double.compare(point.getY(), thiz.getY()) != 0) return false; return true; } @Override public int hashCode() { return hashCode(this); } /** * All {@link Point} implementations should use this definition of {@link Object#hashCode()}. */ public static int hashCode(Point thiz) { int result; long temp; temp = thiz.getX() != +0.0d ? Double.doubleToLongBits(thiz.getX()) : 0L; result = (int) (temp ^ (temp >>> 32)); temp = thiz.getY() != +0.0d ? Double.doubleToLongBits(thiz.getY()) : 0L; result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/Range.java000066400000000000000000000114621241767633500257250ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape.impl; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.Rectangle; /** * INTERNAL: A numeric range between a pair of numbers. * Perhaps this class could become 1st class citizen extending Shape but not now. * Only public so is accessible from tests in another package. */ public class Range { protected final double min, max; public static Range xRange(Rectangle rect, SpatialContext ctx) { if (ctx.isGeo()) return new LongitudeRange(rect.getMinX(), rect.getMaxX()); else return new Range(rect.getMinX(), rect.getMaxX()); } public static Range yRange(Rectangle rect, SpatialContext ctx) { return new Range(rect.getMinY(), rect.getMaxY()); } public Range(double min, double max) { this.min = min; this.max = max; } public double getMin() { return min; } public double getMax() { return max; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Range range = (Range) o; if (Double.compare(range.max, max) != 0) return false; if (Double.compare(range.min, min) != 0) return false; return true; } @Override public int hashCode() { int result; long temp; temp = min != +0.0d ? Double.doubleToLongBits(min) : 0L; result = (int) (temp ^ (temp >>> 32)); temp = max != +0.0d ? Double.doubleToLongBits(max) : 0L; result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } @Override public String toString() { return "Range{" + min + " TO " + max + '}'; } public double getWidth() { return max - min; } public boolean contains(double v) { return v >= min && v <= max; } public double getCenter() { return min + getWidth()/2; } public Range expandTo(Range other) { assert this.getClass() == other.getClass(); return new Range(Math.min(min, other.min), Math.max(max, other.max)); } public double deltaLen(Range other) { double min3 = Math.max(min, other.min); double max3 = Math.min(max, other.max); return max3 - min3; } public static class LongitudeRange extends Range { public static final LongitudeRange WORLD_180E180W = new LongitudeRange(-180, 180); public LongitudeRange(double min, double max) { super(min, max); } public LongitudeRange(Rectangle r) { super(r.getMinX(), r.getMaxX()); } @Override public double getWidth() { double w = super.getWidth(); if (w < 0) w += 360; return w; } @Override public boolean contains(double v) { if (!crossesDateline()) return super.contains(v); return v >= min || v <= max;// the OR is the distinction from non-dateline cross } public boolean crossesDateline() { return min > max; } public double getCenter() { double ctr = super.getCenter(); if (ctr > 180) ctr -= 360; return ctr; } public double compareTo(LongitudeRange b) { return diff(getCenter(), b.getCenter()); } /** a - b (compareTo order). < 0 if a < b */ private static double diff(double a, double b) { double diff = a - b; if (diff <= 180) { if (diff >= -180) return diff; return diff + 360; } else { return diff - 360; } } @Override public Range expandTo(Range other) { return expandTo((LongitudeRange) other); } public LongitudeRange expandTo(LongitudeRange other) { LongitudeRange a, b;// a.ctr <= b.ctr if (this.compareTo(other) <= 0) { a = this; b = other; } else { a = other; b = this; } LongitudeRange newMin = b.contains(a.min) ? b : a;//usually 'a' LongitudeRange newMax = a.contains(b.max) ? a : b;//usually 'b' if (newMin == newMax) return newMin; if (newMin == b && newMax == a) return WORLD_180E180W; return new LongitudeRange(newMin.min, newMax.max); } } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/RectangleImpl.java000066400000000000000000000246601241767633500274230ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape.impl; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.distance.DistanceUtils; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.SpatialRelation; /** * A simple Rectangle implementation that also supports a longitudinal * wrap-around. When minX > maxX, this will assume it is world coordinates that * cross the date line using degrees. Immutable & threadsafe. */ public class RectangleImpl implements Rectangle { private final SpatialContext ctx; private double minX; private double maxX; private double minY; private double maxY; /** A simple constructor without normalization / validation. */ public RectangleImpl(double minX, double maxX, double minY, double maxY, SpatialContext ctx) { //TODO change to West South East North to be more consistent with OGC? this.ctx = ctx; reset(minX, maxX, minY, maxY); } /** A convenience constructor which pulls out the coordinates. */ public RectangleImpl(Point lowerLeft, Point upperRight, SpatialContext ctx) { this(lowerLeft.getX(), upperRight.getX(), lowerLeft.getY(), upperRight.getY(), ctx); } /** Copy constructor. */ public RectangleImpl(Rectangle r, SpatialContext ctx) { this(r.getMinX(), r.getMaxX(), r.getMinY(), r.getMaxY(), ctx); } @Override public void reset(double minX, double maxX, double minY, double maxY) { assert ! isEmpty(); this.minX = minX; this.maxX = maxX; this.minY = minY; this.maxY = maxY; assert minY <= maxY || Double.isNaN(minY) : "minY, maxY: "+minY+", "+maxY; } @Override public boolean isEmpty() { return Double.isNaN(minX); } @Override public Rectangle getBuffered(double distance, SpatialContext ctx) { if (ctx.isGeo()) { //first check pole touching, triggering a world-wrap rect if (maxY + distance >= 90) { return ctx.makeRectangle(-180, 180, Math.max(-90, minY - distance), 90); } else if (minY - distance <= -90) { return ctx.makeRectangle(-180, 180, -90, Math.min(90, maxY + distance)); } else { //doesn't touch pole double latDistance = distance; double closestToPoleY = (maxY - minY > 0) ? maxY : minY; double lonDistance = DistanceUtils.calcBoxByDistFromPt_deltaLonDEG( closestToPoleY, minX, distance);//lat,lon order //could still wrap the world though... if (lonDistance * 2 + getWidth() >= 360) return ctx.makeRectangle(-180, 180, minY - latDistance, maxY + latDistance); return ctx.makeRectangle( DistanceUtils.normLonDEG(minX - lonDistance), DistanceUtils.normLonDEG(maxX + lonDistance), minY - latDistance, maxY + latDistance); } } else { Rectangle worldBounds = ctx.getWorldBounds(); double newMinX = Math.max(worldBounds.getMinX(), minX - distance); double newMaxX = Math.min(worldBounds.getMaxX(), maxX + distance); double newMinY = Math.max(worldBounds.getMinY(), minY - distance); double newMaxY = Math.min(worldBounds.getMaxY(), maxY + distance); return ctx.makeRectangle(newMinX, newMaxX, newMinY, newMaxY); } } @Override public boolean hasArea() { return maxX != minX && maxY != minY; } @Override public double getArea(SpatialContext ctx) { if (ctx == null) { return getWidth() * getHeight(); } else { return ctx.getDistCalc().area(this); } } @Override public boolean getCrossesDateLine() { return (minX > maxX); } @Override public double getHeight() { return maxY - minY; } @Override public double getWidth() { double w = maxX - minX; if (w < 0) {//only true when minX > maxX (WGS84 assumed) w += 360; assert w >= 0; } return w; } @Override public double getMaxX() { return maxX; } @Override public double getMaxY() { return maxY; } @Override public double getMinX() { return minX; } @Override public double getMinY() { return minY; } @Override public Rectangle getBoundingBox() { return this; } @Override public SpatialRelation relate(Shape other) { if (isEmpty() || other.isEmpty()) return SpatialRelation.DISJOINT; if (other instanceof Point) { return relate((Point) other); } if (other instanceof Rectangle) { return relate((Rectangle) other); } return other.relate(this).transpose(); } public SpatialRelation relate(Point point) { if (point.getY() > getMaxY() || point.getY() < getMinY()) return SpatialRelation.DISJOINT; // all the below logic is rather unfortunate but some dateline cases demand it double minX = this.minX; double maxX = this.maxX; double pX = point.getX(); if (ctx.isGeo()) { //unwrap dateline and normalize +180 to become -180 double rawWidth = maxX - minX; if (rawWidth < 0) { maxX = minX + (rawWidth + 360); } //shift to potentially overlap if (pX < minX) { pX += 360; } else if (pX > maxX) { pX -= 360; } else { return SpatialRelation.CONTAINS;//short-circuit } } if (pX < minX || pX > maxX) return SpatialRelation.DISJOINT; return SpatialRelation.CONTAINS; } public SpatialRelation relate(Rectangle rect) { SpatialRelation yIntersect = relateYRange(rect.getMinY(), rect.getMaxY()); if (yIntersect == SpatialRelation.DISJOINT) return SpatialRelation.DISJOINT; SpatialRelation xIntersect = relateXRange(rect.getMinX(), rect.getMaxX()); if (xIntersect == SpatialRelation.DISJOINT) return SpatialRelation.DISJOINT; if (xIntersect == yIntersect)//in agreement return xIntersect; //if one side is equal, return the other if (getMinX() == rect.getMinX() && getMaxX() == rect.getMaxX()) return yIntersect; if (getMinY() == rect.getMinY() && getMaxY() == rect.getMaxY()) return xIntersect; return SpatialRelation.INTERSECTS; } //TODO might this utility move to SpatialRelation ? private static SpatialRelation relate_range(double int_min, double int_max, double ext_min, double ext_max) { if (ext_min > int_max || ext_max < int_min) { return SpatialRelation.DISJOINT; } if (ext_min >= int_min && ext_max <= int_max) { return SpatialRelation.CONTAINS; } if (ext_min <= int_min && ext_max >= int_max) { return SpatialRelation.WITHIN; } return SpatialRelation.INTERSECTS; } @Override public SpatialRelation relateYRange(double ext_minY, double ext_maxY) { return relate_range(minY, maxY, ext_minY, ext_maxY); } @Override public SpatialRelation relateXRange(double ext_minX, double ext_maxX) { //For ext & this we have local minX and maxX variable pairs. We rotate them so that minX <= maxX double minX = this.minX; double maxX = this.maxX; if (ctx.isGeo()) { //unwrap dateline, plus do world-wrap short circuit double rawWidth = maxX - minX; if (rawWidth == 360) return SpatialRelation.CONTAINS; if (rawWidth < 0) { maxX = minX + (rawWidth + 360); } double ext_rawWidth = ext_maxX - ext_minX; if (ext_rawWidth == 360) return SpatialRelation.WITHIN; if (ext_rawWidth < 0) { ext_maxX = ext_minX + (ext_rawWidth + 360); } //shift to potentially overlap if (maxX < ext_minX) { minX += 360; maxX += 360; } else if (ext_maxX < minX) { ext_minX += 360; ext_maxX += 360; } } return relate_range(minX, maxX, ext_minX, ext_maxX); } @Override public String toString() { return "Rect(minX=" + minX + ",maxX=" + maxX + ",minY=" + minY + ",maxY=" + maxY + ")"; } @Override public Point getCenter() { if (Double.isNaN(minX)) return ctx.makePoint(Double.NaN, Double.NaN); final double y = getHeight() / 2 + minY; double x = getWidth() / 2 + minX; if (minX > maxX)//WGS84 x = DistanceUtils.normLonDEG(x);//in case falls outside the standard range return new PointImpl(x, y, ctx); } @Override public boolean equals(Object obj) { return equals(this,obj); } /** * All {@link Rectangle} implementations should use this definition of {@link Object#equals(Object)}. */ public static boolean equals(Rectangle thiz, Object o) { assert thiz != null; if (thiz == o) return true; if (!(o instanceof Rectangle)) return false; RectangleImpl rectangle = (RectangleImpl) o; if (Double.compare(rectangle.getMaxX(), thiz.getMaxX()) != 0) return false; if (Double.compare(rectangle.getMaxY(), thiz.getMaxY()) != 0) return false; if (Double.compare(rectangle.getMinX(), thiz.getMinX()) != 0) return false; if (Double.compare(rectangle.getMinY(), thiz.getMinY()) != 0) return false; return true; } @Override public int hashCode() { return hashCode(this); } /** * All {@link Rectangle} implementations should use this definition of {@link Object#hashCode()}. */ public static int hashCode(Rectangle thiz) { int result; long temp; temp = thiz.getMinX() != +0.0d ? Double.doubleToLongBits(thiz.getMinX()) : 0L; result = (int) (temp ^ (temp >>> 32)); temp = thiz.getMaxX() != +0.0d ? Double.doubleToLongBits(thiz.getMaxX()) : 0L; result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = thiz.getMinY() != +0.0d ? Double.doubleToLongBits(thiz.getMinY()) : 0L; result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = thiz.getMaxY() != +0.0d ? Double.doubleToLongBits(thiz.getMaxY()) : 0L; result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/jts/000077500000000000000000000000001241767633500236615ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/jts/JtsGeometry.java000077500000000000000000000462171241767633500270150ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape.jts; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.context.jts.JtsSpatialContext; import com.spatial4j.core.exception.InvalidShapeException; import com.spatial4j.core.shape.Circle; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.SpatialRelation; import com.spatial4j.core.shape.impl.BufferedLineString; import com.spatial4j.core.shape.impl.PointImpl; import com.spatial4j.core.shape.impl.Range; import com.spatial4j.core.shape.impl.RectangleImpl; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.CoordinateSequence; import com.vividsolutions.jts.geom.CoordinateSequenceFilter; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFilter; import com.vividsolutions.jts.geom.IntersectionMatrix; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.Lineal; import com.vividsolutions.jts.geom.LinearRing; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.geom.Puntal; import com.vividsolutions.jts.geom.prep.PreparedGeometry; import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory; import com.vividsolutions.jts.operation.union.UnaryUnionOp; import com.vividsolutions.jts.operation.valid.IsValidOp; import java.util.ArrayList; import java.util.List; /** * Wraps a JTS {@link Geometry} (i.e. may be a polygon or basically anything). * JTS does a great deal of the hard work, but there is work here in handling * dateline wrap. */ public class JtsGeometry implements Shape { /** System property boolean that can disable auto validation in an assert. */ public static final String SYSPROP_ASSERT_VALIDATE = "spatial4j.JtsGeometry.assertValidate"; private final Geometry geom;//cannot be a direct instance of GeometryCollection as it doesn't support relate() private final boolean hasArea; private final Rectangle bbox; protected final JtsSpatialContext ctx; protected PreparedGeometry preparedGeometry; protected boolean validated = false; public JtsGeometry(Geometry geom, JtsSpatialContext ctx, boolean dateline180Check, boolean allowMultiOverlap) { this.ctx = ctx; //GeometryCollection isn't supported in relate() if (geom.getClass().equals(GeometryCollection.class)) throw new IllegalArgumentException("JtsGeometry does not support GeometryCollection but does support its subclasses."); //NOTE: All this logic is fairly expensive. There are some short-circuit checks though. if (ctx.isGeo()) { //Unwraps the geometry across the dateline so it exceeds the standard geo bounds (-180 to +180). if (dateline180Check) unwrapDateline(geom);//potentially modifies geom //If given multiple overlapping polygons, fix it by union if (allowMultiOverlap) geom = unionGeometryCollection(geom);//returns same or new geom //Cuts an unwrapped geometry back into overlaid pages in the standard geo bounds. geom = cutUnwrappedGeomInto360(geom);//returns same or new geom assert geom.getEnvelopeInternal().getWidth() <= 360; assert ! geom.getClass().equals(GeometryCollection.class) : "GeometryCollection unsupported";//double check //Compute bbox bbox = computeGeoBBox(geom); } else {//not geo //If given multiple overlapping polygons, fix it by union if (allowMultiOverlap) geom = unionGeometryCollection(geom);//returns same or new geom Envelope env = geom.getEnvelopeInternal(); bbox = new RectangleImpl(env.getMinX(), env.getMaxX(), env.getMinY(), env.getMaxY(), ctx); } geom.getEnvelopeInternal();//ensure envelope is cached internally, which is lazy evaluated. Keeps this thread-safe. this.geom = geom; assert assertValidate();//kinda expensive but caches valid state this.hasArea = !((geom instanceof Lineal) || (geom instanceof Puntal)); } /** called via assertion */ private boolean assertValidate() { String assertValidate = System.getProperty(SYSPROP_ASSERT_VALIDATE); if (assertValidate == null || Boolean.parseBoolean(assertValidate)) validate(); return true; } /** * Validates the shape, throwing a descriptive error if it isn't valid. Note that this * is usually called automatically by default, but that can be disabled. * * @throws InvalidShapeException with descriptive error if the shape isn't valid */ public void validate() throws InvalidShapeException { if (!validated) { IsValidOp isValidOp = new IsValidOp(geom); if (!isValidOp.isValid()) throw new InvalidShapeException(isValidOp.getValidationError().toString()); validated = true; } } /** * Adds an index to this class internally to compute spatial relations faster. In JTS this * is called a {@link com.vividsolutions.jts.geom.prep.PreparedGeometry}. This * isn't done by default because it takes some time to do the optimization, and it uses more * memory. Calling this method isn't thread-safe so be careful when this is done. If it was * already indexed then nothing happens. */ public void index() { if (preparedGeometry == null) preparedGeometry = PreparedGeometryFactory.prepare(geom); } @Override public boolean isEmpty() { return geom.isEmpty(); } /** Given {@code geoms} which has already been checked for being in world * bounds, return the minimal longitude range of the bounding box. */ protected Rectangle computeGeoBBox(Geometry geoms) { if (geoms.isEmpty()) return new RectangleImpl(Double.NaN, Double.NaN, Double.NaN, Double.NaN, ctx); final Envelope env = geoms.getEnvelopeInternal();//for minY & maxY (simple) if (env.getWidth() > 180 && geoms.getNumGeometries() > 1) { // This is ShapeCollection's bbox algorithm Range xRange = null; for (int i = 0; i < geoms.getNumGeometries(); i++ ) { Envelope envI = geoms.getGeometryN(i).getEnvelopeInternal(); Range xRange2 = new Range.LongitudeRange(envI.getMinX(), envI.getMaxX()); if (xRange == null) { xRange = xRange2; } else { xRange = xRange.expandTo(xRange2); } if (xRange == Range.LongitudeRange.WORLD_180E180W) break; // can't grow any bigger } return new RectangleImpl(xRange.getMin(), xRange.getMax(), env.getMinY(), env.getMaxY(), ctx); } else { return new RectangleImpl(env.getMinX(), env.getMaxX(), env.getMinY(), env.getMaxY(), ctx); } } @Override public JtsGeometry getBuffered(double distance, SpatialContext ctx) { //TODO doesn't work correctly across the dateline. The buffering needs to happen // when it's transiently unrolled, prior to being sliced. return this.ctx.makeShape(geom.buffer(distance), true, true); } @Override public boolean hasArea() { return hasArea; } @Override public double getArea(SpatialContext ctx) { double geomArea = geom.getArea(); if (ctx == null || geomArea == 0) return geomArea; //Use the area proportional to how filled the bbox is. double bboxArea = getBoundingBox().getArea(null);//plain 2d area assert bboxArea >= geomArea; double filledRatio = geomArea / bboxArea; return getBoundingBox().getArea(ctx) * filledRatio; // (Future: if we know we use an equal-area projection then we don't need to // estimate) } @Override public Rectangle getBoundingBox() { return bbox; } @Override public JtsPoint getCenter() { if (isEmpty()) //geom.getCentroid == null return new JtsPoint(ctx.getGeometryFactory().createPoint((Coordinate)null), ctx); return new JtsPoint(geom.getCentroid(), ctx); } @Override public SpatialRelation relate(Shape other) { if (other instanceof Point) return relate((Point)other); else if (other instanceof Rectangle) return relate((Rectangle) other); else if (other instanceof Circle) return relate((Circle) other); else if (other instanceof JtsGeometry) return relate((JtsGeometry) other); else if (other instanceof BufferedLineString) throw new UnsupportedOperationException("Can't use BufferedLineString with JtsGeometry"); return other.relate(this).transpose(); } public SpatialRelation relate(Point pt) { if (!getBoundingBox().relate(pt).intersects()) return SpatialRelation.DISJOINT; Geometry ptGeom; if (pt instanceof JtsPoint) ptGeom = ((JtsPoint)pt).getGeom(); else ptGeom = ctx.getGeometryFactory().createPoint(new Coordinate(pt.getX(), pt.getY())); return relate(ptGeom);//is point-optimized } public SpatialRelation relate(Rectangle rectangle) { SpatialRelation bboxR = bbox.relate(rectangle); if (bboxR == SpatialRelation.WITHIN || bboxR == SpatialRelation.DISJOINT) return bboxR; // FYI, the right answer could still be DISJOINT or WITHIN, but we don't know yet. return relate(ctx.getGeometryFrom(rectangle)); } public SpatialRelation relate(Circle circle) { SpatialRelation bboxR = bbox.relate(circle); if (bboxR == SpatialRelation.WITHIN || bboxR == SpatialRelation.DISJOINT) return bboxR; //Test each point to see how many of them are outside of the circle. //TODO consider instead using geom.apply(CoordinateSequenceFilter) -- maybe faster since avoids Coordinate[] allocation Coordinate[] coords = geom.getCoordinates(); int outside = 0; int i = 0; for (Coordinate coord : coords) { i++; SpatialRelation sect = circle.relate(new PointImpl(coord.x, coord.y, ctx)); if (sect == SpatialRelation.DISJOINT) outside++; if (i != outside && outside != 0)//short circuit: partially outside, partially inside return SpatialRelation.INTERSECTS; } if (i == outside) { return (relate(circle.getCenter()) == SpatialRelation.DISJOINT) ? SpatialRelation.DISJOINT : SpatialRelation.CONTAINS; } assert outside == 0; return SpatialRelation.WITHIN; } public SpatialRelation relate(JtsGeometry jtsGeometry) { //don't bother checking bbox since geom.relate() does this already return relate(jtsGeometry.geom); } protected SpatialRelation relate(Geometry oGeom) { //see http://docs.geotools.org/latest/userguide/library/jts/dim9.html#preparedgeometry if (oGeom instanceof com.vividsolutions.jts.geom.Point) { if (preparedGeometry != null) return preparedGeometry.disjoint(oGeom) ? SpatialRelation.DISJOINT : SpatialRelation.CONTAINS; return geom.disjoint(oGeom) ? SpatialRelation.DISJOINT : SpatialRelation.CONTAINS; } if (preparedGeometry == null) return intersectionMatrixToSpatialRelation(geom.relate(oGeom)); else if (preparedGeometry.covers(oGeom)) return SpatialRelation.CONTAINS; else if (preparedGeometry.coveredBy(oGeom)) return SpatialRelation.WITHIN; else if (preparedGeometry.intersects(oGeom)) return SpatialRelation.INTERSECTS; return SpatialRelation.DISJOINT; } public static SpatialRelation intersectionMatrixToSpatialRelation(IntersectionMatrix matrix) { //As indicated in SpatialRelation javadocs, Spatial4j CONTAINS & WITHIN are // OGC's COVERS & COVEREDBY if (matrix.isCovers()) return SpatialRelation.CONTAINS; else if (matrix.isCoveredBy()) return SpatialRelation.WITHIN; else if (matrix.isDisjoint()) return SpatialRelation.DISJOINT; return SpatialRelation.INTERSECTS; } @Override public String toString() { return geom.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; JtsGeometry that = (JtsGeometry) o; return geom.equalsExact(that.geom);//fast equality for normalized geometries } @Override public int hashCode() { //FYI if geometry.equalsExact(that.geometry), then their envelopes are the same. return geom.getEnvelopeInternal().hashCode(); } public Geometry getGeom() { return geom; } /** * If geom spans the dateline, then this modifies it to be a * valid JTS geometry that extends to the right of the standard -180 to +180 * width such that some points are greater than +180 but some remain less. * Takes care to invoke {@link com.vividsolutions.jts.geom.Geometry#geometryChanged()} * if needed. * * @return The number of times the geometry spans the dateline. >= 0 */ private static int unwrapDateline(Geometry geom) { if (geom.getEnvelopeInternal().getWidth() < 180) return 0;//can't possibly cross the dateline final int[] crossings = {0};//an array so that an inner class can modify it. geom.apply(new GeometryFilter() { @Override public void filter(Geometry geom) { int cross = 0; if (geom instanceof LineString) {//note: LinearRing extends LineString if (geom.getEnvelopeInternal().getWidth() < 180) return;//can't possibly cross the dateline cross = unwrapDateline((LineString) geom); } else if (geom instanceof Polygon) { if (geom.getEnvelopeInternal().getWidth() < 180) return;//can't possibly cross the dateline cross = unwrapDateline((Polygon) geom); } else return; crossings[0] = Math.max(crossings[0], cross); } });//geom.apply() return crossings[0]; } /** See {@link #unwrapDateline(Geometry)}. */ private static int unwrapDateline(Polygon poly) { LineString exteriorRing = poly.getExteriorRing(); int cross = unwrapDateline(exteriorRing); if (cross > 0) { //TODO TEST THIS! Maybe bug if doesn't cross but is in another page? for(int i = 0; i < poly.getNumInteriorRing(); i++) { LineString innerLineString = poly.getInteriorRingN(i); unwrapDateline(innerLineString); for(int shiftCount = 0; ! exteriorRing.contains(innerLineString); shiftCount++) { if (shiftCount > cross) throw new IllegalArgumentException("The inner ring doesn't appear to be within the exterior: " +exteriorRing+" inner: "+innerLineString); shiftGeomByX(innerLineString, 360); } } poly.geometryChanged(); } return cross; } /** See {@link #unwrapDateline(Geometry)}. */ private static int unwrapDateline(LineString lineString) { CoordinateSequence cseq = lineString.getCoordinateSequence(); int size = cseq.size(); if (size <= 1) return 0; int shiftX = 0;//invariant: == shiftXPage*360 int shiftXPage = 0; int shiftXPageMin = 0/* <= 0 */, shiftXPageMax = 0; /* >= 0 */ double prevX = cseq.getX(0); for(int i = 1; i < size; i++) { double thisX_orig = cseq.getX(i); assert thisX_orig >= -180 && thisX_orig <= 180 : "X not in geo bounds"; double thisX = thisX_orig + shiftX; if (prevX - thisX > 180) {//cross dateline from left to right thisX += 360; shiftX += 360; shiftXPage += 1; shiftXPageMax = Math.max(shiftXPageMax,shiftXPage); } else if (thisX - prevX > 180) {//cross dateline from right to left thisX -= 360; shiftX -= 360; shiftXPage -= 1; shiftXPageMin = Math.min(shiftXPageMin,shiftXPage); } if (shiftXPage != 0) cseq.setOrdinate(i, CoordinateSequence.X, thisX); prevX = thisX; } if (lineString instanceof LinearRing) { assert cseq.getCoordinate(0).equals(cseq.getCoordinate(size-1)); assert shiftXPage == 0;//starts and ends at 0 } assert shiftXPageMax >= 0 && shiftXPageMin <= 0; //Unfortunately we are shifting again; it'd be nice to be smarter and shift once shiftGeomByX(lineString, shiftXPageMin * -360); int crossings = shiftXPageMax - shiftXPageMin; if (crossings > 0) lineString.geometryChanged(); return crossings; } private static void shiftGeomByX(Geometry geom, final int xShift) { if (xShift == 0) return; geom.apply(new CoordinateSequenceFilter() { @Override public void filter(CoordinateSequence seq, int i) { seq.setOrdinate(i, CoordinateSequence.X, seq.getX(i) + xShift ); } @Override public boolean isDone() { return false; } @Override public boolean isGeometryChanged() { return true; } }); } private static Geometry unionGeometryCollection(Geometry geom) { if (geom instanceof GeometryCollection) { return geom.union(); } return geom; } /** * This "pages" through standard geo boundaries offset by multiples of 360 * longitudinally that intersect geom, and the intersecting results of a page * and the geom are shifted into the standard -180 to +180 and added to a new * geometry that is returned. */ private static Geometry cutUnwrappedGeomInto360(Geometry geom) { Envelope geomEnv = geom.getEnvelopeInternal(); if (geomEnv.getMinX() >= -180 && geomEnv.getMaxX() <= 180) return geom; assert geom.isValid() : "geom"; //TODO opt: support geom's that start at negative pages -- // ... will avoid need to previously shift in unwrapDateline(geom). List geomList = new ArrayList(); //page 0 is the standard -180 to 180 range for (int page = 0; true; page++) { double minX = -180 + page * 360; if (geomEnv.getMaxX() <= minX) break; Geometry rect = geom.getFactory().toGeometry(new Envelope(minX, minX + 360, -90, 90)); assert rect.isValid() : "rect"; Geometry pageGeom = rect.intersection(geom);//JTS is doing some hard work assert pageGeom.isValid() : "pageGeom"; shiftGeomByX(pageGeom, page * -360); geomList.add(pageGeom); } return UnaryUnionOp.union(geomList); } // private static Geometry removePolyHoles(Geometry geom) { // //TODO this does a deep copy of geom even if no changes needed; be smarter // GeometryTransformer gTrans = new GeometryTransformer() { // @Override // protected Geometry transformPolygon(Polygon geom, Geometry parent) { // if (geom.getNumInteriorRing() == 0) // return geom; // return factory.createPolygon((LinearRing) geom.getExteriorRing(),null); // } // }; // return gTrans.transform(geom); // } // // private static Geometry snapAndClean(Geometry geom) { // return new GeometrySnapper(geom).snapToSelf(GeometrySnapper.computeOverlaySnapTolerance(geom), true); // } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/jts/JtsPoint.java000066400000000000000000000065151241767633500263050ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape.jts; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.Circle; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.SpatialRelation; import com.spatial4j.core.shape.impl.PointImpl; import com.vividsolutions.jts.geom.CoordinateSequence; /** Wraps a {@link com.vividsolutions.jts.geom.Point}. */ public class JtsPoint implements Point { private final SpatialContext ctx; private com.vividsolutions.jts.geom.Point pointGeom; private final boolean empty;//cached /** A simple constructor without normalization / validation. */ public JtsPoint(com.vividsolutions.jts.geom.Point pointGeom, SpatialContext ctx) { this.ctx = ctx; this.pointGeom = pointGeom; this.empty = pointGeom.isEmpty(); } public com.vividsolutions.jts.geom.Point getGeom() { return pointGeom; } @Override public boolean isEmpty() { return empty; } @Override public com.spatial4j.core.shape.Point getCenter() { return this; } @Override public boolean hasArea() { return false; } @Override public double getArea(SpatialContext ctx) { return 0; } @Override public Rectangle getBoundingBox() { return ctx.makeRectangle(this, this); } @Override public Circle getBuffered(double distance, SpatialContext ctx) { return ctx.makeCircle(this, distance); } @Override public SpatialRelation relate(Shape other) { // ** NOTE ** the overall order of logic is kept consistent here with simple.PointImpl. if (isEmpty() || other.isEmpty()) return SpatialRelation.DISJOINT; if (other instanceof com.spatial4j.core.shape.Point) return this.equals(other) ? SpatialRelation.INTERSECTS : SpatialRelation.DISJOINT; return other.relate(this).transpose(); } @Override public double getX() { return isEmpty() ? Double.NaN : pointGeom.getX(); } @Override public double getY() { return isEmpty() ? Double.NaN : pointGeom.getY(); } @Override public void reset(double x, double y) { assert ! isEmpty(); CoordinateSequence cSeq = pointGeom.getCoordinateSequence(); cSeq.setOrdinate(0, CoordinateSequence.X, x); cSeq.setOrdinate(0, CoordinateSequence.Y, y); } @Override public String toString() { return "Pt(x="+getX()+",y="+getY()+")"; } @Override public boolean equals(Object o) { return PointImpl.equals(this,o); } @Override public int hashCode() { return PointImpl.hashCode(this); } } spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/package-info.java000066400000000000000000000020701241767633500262470ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** Shapes are the core geometry objects that Spatial4j provides. The types * exposed in this package should be considered public. Remember not to * instantiate them directly, use the SpatialContext which acts as a * factory for shapes. */ package com.spatial4j.core.shape;spatial4j-0.4-0.4.1/src/main/java/overview.html000066400000000000000000000017351241767633500211220ustar00rootroot00000000000000 Spatial4j - Core Spatial Library The central class to the API is the SpatialContext, from which most of the rest of the API is accessed. spatial4j-0.4-0.4.1/src/test/000077500000000000000000000000001241767633500154725ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/test/java/000077500000000000000000000000001241767633500164135ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/test/java/com/000077500000000000000000000000001241767633500171715ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/000077500000000000000000000000001241767633500210645ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/000077500000000000000000000000001241767633500220145ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/TestLog.java000066400000000000000000000047361241767633500242520ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core; import com.carrotsearch.randomizedtesting.rules.TestRuleAdapter; import org.slf4j.helpers.MessageFormatter; import java.util.ArrayList; import java.util.List; /** * A utility logger for tests in which log statements are logged following * test failure only. Add this to a JUnit based test class with a {@link org.junit.Rule} * annotation. */ public class TestLog extends TestRuleAdapter { //TODO does this need to be threadsafe (such as via thread-local state)? private static ArrayList logStack = new ArrayList(); private static final int MAX_LOGS = 1000; public static final TestLog instance = new TestLog(); private TestLog() {} @Override protected void before() throws Throwable { logStack.clear(); } @Override protected void afterAlways(List errors) throws Throwable { if (!errors.isEmpty()) logThenClear(); } private void logThenClear() { for (LogEntry entry : logStack) { System.out.println(MessageFormatter.arrayFormat(entry.msg, entry.args).getMessage()); } logStack.clear(); } public static void clear() { logStack.clear(); } /** * Enqueues a log message with substitution arguments ala SLF4J (i.e. {} syntax). * If the test fails then it'll be logged then, otherwise it'll be forgotten. */ public static void log(String msg, Object... args) { if (logStack.size() > MAX_LOGS) { throw new RuntimeException("Too many log statements: "+logStack.size() + " > "+MAX_LOGS); } LogEntry entry = new LogEntry(); entry.msg = msg; entry.args = args; logStack.add(entry); } private static class LogEntry { String msg; Object[] args; } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/context/000077500000000000000000000000001241767633500235005ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/context/SpatialContextFactoryTest.java000066400000000000000000000117011241767633500314750ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.context; import com.spatial4j.core.context.jts.JtsSpatialContext; import com.spatial4j.core.context.jts.JtsSpatialContextFactory; import com.spatial4j.core.distance.CartesianDistCalc; import com.spatial4j.core.distance.GeodesicSphereDistCalc; import com.spatial4j.core.io.jts.JtsWktShapeParser; import com.spatial4j.core.shape.impl.RectangleImpl; import org.junit.After; import org.junit.Test; import java.util.HashMap; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class SpatialContextFactoryTest { public static final String PROP = "SpatialContextFactory"; @After public void tearDown() { System.getProperties().remove(PROP); } private SpatialContext call(String... argsStr) { Map args = new HashMap(); for (int i = 0; i < argsStr.length; i+=2) { String key = argsStr[i]; String val = argsStr[i+1]; args.put(key,val); } return SpatialContextFactory.makeSpatialContext(args, getClass().getClassLoader()); } @Test public void testDefault() { SpatialContext ctx = SpatialContext.GEO; SpatialContext ctx2 = call();//default assertEquals(ctx.getClass(), ctx2.getClass()); assertEquals(ctx.isGeo(), ctx2.isGeo()); assertEquals(ctx.getDistCalc(),ctx2.getDistCalc()); assertEquals(ctx.getWorldBounds(), ctx2.getWorldBounds()); } @Test public void testCustom() { SpatialContext ctx = call("geo","false"); assertTrue(!ctx.isGeo()); assertEquals(new CartesianDistCalc(), ctx.getDistCalc()); ctx = call("geo","false", "distCalculator","cartesian^2", "worldBounds","ENVELOPE(-100, 75, 200, 0)");//xMin, xMax, yMax, yMin assertEquals(new CartesianDistCalc(true),ctx.getDistCalc()); assertEquals(new RectangleImpl(-100, 75, 0, 200, ctx), ctx.getWorldBounds()); ctx = call("geo","true", "distCalculator","lawOfCosines"); assertTrue(ctx.isGeo()); assertEquals(new GeodesicSphereDistCalc.LawOfCosines(), ctx.getDistCalc()); } @Test public void testJtsContextFactory() { JtsSpatialContext ctx = (JtsSpatialContext) call( "spatialContextFactory", JtsSpatialContextFactory.class.getName(), "geo", "true", "normWrapLongitude", "true", "precisionScale", "2.0", "wktShapeParserClass", CustomWktShapeParser.class.getName(), "datelineRule", "ccwRect", "validationRule", "repairConvexHull", "autoIndex", "true"); assertTrue(ctx.isNormWrapLongitude()); assertEquals(2.0, ctx.getGeometryFactory().getPrecisionModel().getScale(), 0.0); assertTrue(CustomWktShapeParser.once);//cheap way to test it was created assertEquals(JtsWktShapeParser.DatelineRule.ccwRect, ((JtsWktShapeParser)ctx.getWktShapeParser()).getDatelineRule()); assertEquals(JtsWktShapeParser.ValidationRule.repairConvexHull, ((JtsWktShapeParser)ctx.getWktShapeParser()).getValidationRule()); //ensure geo=false with worldbounds works -- fixes #72 ctx = (JtsSpatialContext) call( "spatialContextFactory", JtsSpatialContextFactory.class.getName(), "geo", "false",//set to false "worldBounds", "ENVELOPE(-500,500,300,-300)", "normWrapLongitude", "true", "precisionScale", "2.0", "wktShapeParserClass", CustomWktShapeParser.class.getName(), "datelineRule", "ccwRect", "validationRule", "repairConvexHull", "autoIndex", "true"); assertEquals(300, ctx.getWorldBounds().getMaxY(), 0.0); } @Test public void testSystemPropertyLookup() { System.setProperty(PROP,DSCF.class.getName()); assertTrue(!call().isGeo());//DSCF returns this } public static class DSCF extends SpatialContextFactory { @Override public SpatialContext newSpatialContext() { geo = false; return new SpatialContext(this); } } public static class CustomWktShapeParser extends JtsWktShapeParser { static boolean once = false;//cheap way to test it was created public CustomWktShapeParser(JtsSpatialContext ctx, JtsSpatialContextFactory factory) { super(ctx, factory); once = true; } } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/distance/000077500000000000000000000000001241767633500236065ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/distance/CompareRadiansSnippet.java000077500000000000000000000026271241767633500307160ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.distance; //import static com.spatial4j.core.distance.DistanceUtils.toRadians; import static java.lang.Math.toRadians; /** * On my machine, using * Math.toRadians: 2090 * DistanceUtils: 626 */ public class CompareRadiansSnippet { public static void main(String[] args) { long start = System.currentTimeMillis(); double x = 1.12345; for (int i=0; i<100000000; i++) { x += toRadians(x) - toRadians(x+1); } System.out.println(x); // need to use the result... otherwise JVM may skip everything System.out.println(System.currentTimeMillis()-start); } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/distance/TestDistances.java000066400000000000000000000274061241767633500272370ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.distance; import com.carrotsearch.randomizedtesting.RandomizedTest; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.Circle; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.SpatialRelation; import com.spatial4j.core.shape.impl.PointImpl; import org.junit.Before; import org.junit.Test; import static com.spatial4j.core.distance.DistanceUtils.DEG_TO_KM; import static com.spatial4j.core.distance.DistanceUtils.KM_TO_DEG; public class TestDistances extends RandomizedTest { //NOTE! These are sometimes modified by tests. private SpatialContext ctx; private double EPS; @Before public void beforeTest() { ctx = SpatialContext.GEO; EPS = 10e-4;//delta when doing double assertions. Geo eps is not that small. } private DistanceCalculator dc() { return ctx.getDistCalc(); } @Test public void testSomeDistances() { //See to verify: from http://www.movable-type.co.uk/scripts/latlong.html Point ctr = pLL(0,100); assertEquals(11100, dc().distance(ctr, pLL(10, 0)) * DEG_TO_KM, 3); double deg = dc().distance(ctr, pLL(10, -160)); assertEquals(11100, deg * DEG_TO_KM, 3); assertEquals(314.40338, dc().distance(pLL(1, 2), pLL(3, 4)) * DEG_TO_KM, EPS); } @Test public void testCalcBoxByDistFromPt() { //first test regression { double d = 6894.1 * KM_TO_DEG; Point pCtr = pLL(-20, 84); Point pTgt = pLL(-42, 15); assertTrue(dc().distance(pCtr, pTgt) < d); //since the pairwise distance is less than d, a bounding box from ctr with d should contain pTgt. Rectangle r = dc().calcBoxByDistFromPt(pCtr, d, ctx, null); assertEquals(SpatialRelation.CONTAINS,r.relate(pTgt)); checkBBox(pCtr,d); } assertEquals("0 dist, horiz line", -45,dc().calcBoxByDistFromPt_yHorizAxisDEG(ctx.makePoint(-180, -45), 0, ctx),0); double MAXDIST = (double) 180 * DEG_TO_KM; checkBBox(ctx.makePoint(0,0), MAXDIST); checkBBox(ctx.makePoint(0,0), MAXDIST *0.999999); checkBBox(ctx.makePoint(0,0),0); checkBBox(ctx.makePoint(0,0),0.000001); checkBBox(ctx.makePoint(0,90),0.000001); checkBBox(ctx.makePoint(-32.7,-5.42),9829); checkBBox(ctx.makePoint(0,90-20), (double) 20 * DEG_TO_KM); { double d = 0.010;//10m checkBBox(ctx.makePoint(0,90- (d + 0.001) * KM_TO_DEG),d); } for (int T = 0; T < 100; T++) { double lat = -90 + randomDouble()*180; double lon = -180 + randomDouble()*360; Point ctr = ctx.makePoint(lon, lat); double dist = MAXDIST*randomDouble(); checkBBox(ctr, dist); } } private void checkBBox(Point ctr, double distKm) { String msg = "ctr: "+ctr+" distKm: "+distKm; double dist = distKm * KM_TO_DEG; Rectangle r = dc().calcBoxByDistFromPt(ctr, dist, ctx, null); double horizAxisLat = dc().calcBoxByDistFromPt_yHorizAxisDEG(ctr, dist, ctx); if (!Double.isNaN(horizAxisLat)) assertTrue(r.relateYRange(horizAxisLat, horizAxisLat).intersects()); //horizontal if (r.getWidth() >= 180) { double deg = dc().distance(ctr, r.getMinX(), r.getMaxY() == 90 ? 90 : -90); double calcDistKm = deg * DEG_TO_KM; assertTrue(msg, calcDistKm <= distKm + EPS); //horizAxisLat is meaningless in this context } else { Point tPt = findClosestPointOnVertToPoint(r.getMinX(), r.getMinY(), r.getMaxY(), ctr); double calcDistKm = dc().distance(ctr, tPt) * DEG_TO_KM; assertEquals(msg, distKm, calcDistKm, EPS); assertEquals(msg, tPt.getY(), horizAxisLat, EPS); } //vertical double topDistKm = dc().distance(ctr, ctr.getX(), r.getMaxY()) * DEG_TO_KM; if (r.getMaxY() == 90) assertTrue(msg, topDistKm <= distKm + EPS); else assertEquals(msg, distKm, topDistKm, EPS); double botDistKm = dc().distance(ctr, ctr.getX(), r.getMinY()) * DEG_TO_KM; if (r.getMinY() == -90) assertTrue(msg, botDistKm <= distKm + EPS); else assertEquals(msg, distKm, botDistKm, EPS); } private Point findClosestPointOnVertToPoint(double lon, double lowLat, double highLat, Point ctr) { //A binary search algorithm to find the point along the vertical lon between lowLat & highLat that is closest // to ctr, and returns the distance. double midLat = (highLat - lowLat)/2 + lowLat; double midLatDist = ctx.getDistCalc().distance(ctr,lon,midLat); for(int L = 0; L < 100 && (highLat - lowLat > 0.001|| L < 20); L++) { boolean bottom = (midLat - lowLat > highLat - midLat); double newMid = bottom ? (midLat - lowLat)/2 + lowLat : (highLat - midLat)/2 + midLat; double newMidDist = ctx.getDistCalc().distance(ctr,lon,newMid); if (newMidDist < midLatDist) { if (bottom) { highLat = midLat; } else { lowLat = midLat; } midLat = newMid; midLatDist = newMidDist; } else { if (bottom) { lowLat = newMid; } else { highLat = newMid; } } } return ctx.makePoint(lon,midLat); } @Test public void testDistCalcPointOnBearing_cartesian() { ctx = new SpatialContext(false); EPS = 10e-6;//tighter epsilon (aka delta) for(int i = 0; i < 1000; i++) { testDistCalcPointOnBearing(randomInt(100)); } } @Test public void testDistCalcPointOnBearing_geo() { //The haversine formula has a higher error if the points are near antipodal. We adjust EPS tolerance for this case. //TODO Eventually we should add the Vincenty formula for improved accuracy, or try some other cleverness. //test known high delta // { // Point c = ctx.makePoint(-103,-79); // double angRAD = Math.toRadians(236); // double dist = 20025; // Point p2 = dc().pointOnBearingRAD(c, dist, angRAD, ctx); // //Pt(x=76.61200011750923,y=79.04946929870962) // double calcDist = dc().distance(c, p2); // assertEqualsRatio(dist, calcDist); // } double maxDistKm = (double) 180 * DEG_TO_KM; for(int i = 0; i < 1000; i++) { int distKm = randomInt((int) maxDistKm); EPS = (distKm < maxDistKm*0.75 ? 10e-6 : 10e-3); testDistCalcPointOnBearing(distKm); } } private void testDistCalcPointOnBearing(double distKm) { for(int angDEG = 0; angDEG < 360; angDEG += randomIntBetween(1,20)) { Point c = ctx.makePoint( DistanceUtils.normLonDEG(randomInt(359)), randomIntBetween(-90,90)); //0 distance means same point Point p2 = dc().pointOnBearing(c, 0, angDEG, ctx, null); assertEquals(c,p2); p2 = dc().pointOnBearing(c, distKm * KM_TO_DEG, angDEG, ctx, null); double calcDistKm = dc().distance(c, p2) * DEG_TO_KM; assertEqualsRatio(distKm, calcDistKm); } } private void assertEqualsRatio(double expected, double actual) { double delta = Math.abs(actual - expected); double base = Math.min(actual, expected); double deltaRatio = base==0 ? delta : Math.min(delta,delta / base); assertEquals(0,deltaRatio, EPS); } @Test public void testNormLat() { double[][] lats = new double[][] { {1.23,1.23},//1.23 might become 1.2299999 after some math and we want to ensure that doesn't happen {-90,-90},{90,90},{0,0}, {-100,-80}, {-90-180,90},{-90-360,-90},{90+180,-90},{90+360,90}, {-12+180,12}}; for (double[] pair : lats) { assertEquals("input "+pair[0], pair[1], DistanceUtils.normLatDEG(pair[0]), 0); } for(int i = -1000; i < 1000; i += randomInt(9)*10) { double d = DistanceUtils.normLatDEG(i); assertTrue(i + " " + d, d >= -90 && d <= 90); } } @Test public void testNormLon() { double[][] lons = new double[][] { {1.23,1.23},//1.23 might become 1.2299999 after some math and we want to ensure that doesn't happen {-180,-180},{180,+180},{0,0}, {-190,170},{181,-179}, {-180-360,-180},{-180-720,-180}, {180+360,+180},{180+720,+180}}; for (double[] pair : lons) { assertEquals("input " + pair[0], pair[1], DistanceUtils.normLonDEG(pair[0]), 0); } for(int i = -1000; i < 1000; i += randomInt(9)*10) { double d = DistanceUtils.normLonDEG(i); assertTrue(i + " " + d, d >= -180 && d <= 180); } } @Test public void assertDistanceConversion() { assertDistanceConversion(0); assertDistanceConversion(500); assertDistanceConversion(DistanceUtils.EARTH_MEAN_RADIUS_KM); } private void assertDistanceConversion(double dist) { double radius = DistanceUtils.EARTH_MEAN_RADIUS_KM; //test back & forth conversion for both double distRAD = DistanceUtils.dist2Radians(dist, radius); assertEquals(dist, DistanceUtils.radians2Dist(distRAD, radius), EPS); double distDEG = DistanceUtils.dist2Degrees(dist, radius); assertEquals(dist, DistanceUtils.degrees2Dist(distDEG, radius), EPS); //test across rad & deg assertEquals(distDEG,DistanceUtils.toDegrees(distRAD),EPS); //test point on bearing assertEquals( DistanceUtils.pointOnBearingRAD(0, 0, DistanceUtils.dist2Radians(dist, radius), DistanceUtils.DEG_90_AS_RADS, ctx, new PointImpl(0, 0, ctx)).getX(), distRAD, 10e-5); } private Point pLL(double lat, double lon) { return ctx.makePoint(lon,lat); } @Test public void testArea() { double radius = DistanceUtils.EARTH_MEAN_RADIUS_KM * KM_TO_DEG; //surface of a sphere is 4 * pi * r^2 final double earthArea = 4 * Math.PI * radius * radius; Circle c = ctx.makeCircle(randomIntBetween(-180,180), randomIntBetween(-90,90), 180);//180 means whole earth assertEquals(earthArea, c.getArea(ctx), 1.0); assertEquals(earthArea, ctx.getWorldBounds().getArea(ctx), 1.0); //now check half earth Circle cHalf = ctx.makeCircle(c.getCenter(), 90); assertEquals(earthArea/2, cHalf.getArea(ctx), 1.0); //circle with same radius at +20 lat with one at -20 lat should have same area as well as bbox with same area Circle c2 = ctx.makeCircle(c.getCenter(), 30); Circle c3 = ctx.makeCircle(c.getCenter().getX(), 20, 30); assertEquals(c2.getArea(ctx), c3.getArea(ctx), 0.01); Circle c3Opposite = ctx.makeCircle(c.getCenter().getX(), -20, 30); assertEquals(c3.getArea(ctx), c3Opposite.getArea(ctx), 0.01); assertEquals(c3.getBoundingBox().getArea(ctx), c3Opposite.getBoundingBox().getArea(ctx), 0.01); //small shapes near the equator should have similar areas to euclidean rectangle Rectangle smallRect = ctx.makeRectangle(0, 1, 0, 1); assertEquals(1.0, smallRect.getArea(null), 0.0); double smallDelta = smallRect.getArea(null) - smallRect.getArea(ctx); assertTrue(smallDelta > 0 && smallDelta < 0.0001); Circle smallCircle = ctx.makeCircle(0,0,1); smallDelta = smallCircle.getArea(null) - smallCircle.getArea(ctx); assertTrue(smallDelta > 0 && smallDelta < 0.0001); //bigger, but still fairly similar //c2 = ctx.makeCircle(c.getCenter(), 30); double areaRatio = c2.getArea(null) / c2.getArea(ctx); assertTrue(areaRatio > 1 && areaRatio < 1.1); } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/io/000077500000000000000000000000001241767633500224235ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/io/BinaryCodecTest.java000066400000000000000000000061201241767633500263070ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io; import com.carrotsearch.randomizedtesting.RandomizedTest; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.ShapeCollection; import org.junit.Test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.text.ParseException; import java.util.Arrays; public class BinaryCodecTest extends RandomizedTest { final SpatialContext ctx; private BinaryCodec binaryCodec; protected BinaryCodecTest(SpatialContext ctx) { this.ctx = ctx; binaryCodec = ctx.getBinaryCodec();//stateless } public BinaryCodecTest() { this(SpatialContext.GEO); } //This test uses WKT to specify the shapes because the Jts based subclass tests will test // using floats instead of doubles, and WKT is normalized whereas ctx.makeXXX is not. @Test public void testPoint() { assertRoundTrip(wkt("POINT(-10 80.3)")); } @Test public void testRect() { assertRoundTrip(wkt("ENVELOPE(-10, 180, 42.3, 0)")); } @Test public void testCircle() { assertRoundTrip(wkt("BUFFER(POINT(-10 30), 5.2)")); } @Test public void testCollection() { ShapeCollection s = ctx.makeCollection( Arrays.asList( randomShape(), randomShape(), randomShape() ) ); assertRoundTrip(s); } protected Shape wkt(String wkt) { try { return ctx.readShapeFromWkt(wkt); } catch (ParseException e) { throw new RuntimeException(e); } } protected Shape randomShape() { switch (randomInt(2)) {//inclusive case 0: return wkt("POINT(-10 80.3)"); case 1: return wkt("ENVELOPE(-10, 180, 42.3, 0)"); case 2: return wkt("BUFFER(POINT(-10 30), 5.2)"); default: throw new Error(); } } protected void assertRoundTrip(Shape shape) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); binaryCodec.writeShape(new DataOutputStream(baos), shape); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); assertEquals(shape, binaryCodec.readShape(new DataInputStream(bais))); } catch (IOException e) { throw new RuntimeException(e); } } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/io/JtsBinaryCodecTest.java000066400000000000000000000050631241767633500267750ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import com.spatial4j.core.context.jts.JtsSpatialContext; import com.spatial4j.core.context.jts.JtsSpatialContextFactory; import com.spatial4j.core.shape.Shape; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.PrecisionModel; import com.vividsolutions.jts.util.GeometricShapeFactory; import org.junit.Test; import java.util.Arrays; public class JtsBinaryCodecTest extends BinaryCodecTest { @ParametersFactory public static Iterable parameters() { //try floats JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.precisionModel = new PrecisionModel(PrecisionModel.FLOATING_SINGLE); return Arrays.asList($$( $(JtsSpatialContext.GEO),//doubles $(factory.newSpatialContext())//floats )); } public JtsBinaryCodecTest(JtsSpatialContext ctx) { super(ctx); } @Test public void testPoly() { JtsSpatialContext ctx = (JtsSpatialContext)super.ctx; ctx.makeShape(randomGeometry(randomIntBetween(3, 20)), false, false); } @Override protected Shape randomShape() { if (randomInt(3) == 0) { JtsSpatialContext ctx = (JtsSpatialContext)super.ctx; return ctx.makeShape(randomGeometry(randomIntBetween(3, 20)), false, false); } else { return super.randomShape(); } } Geometry randomGeometry(int points) { //a circle JtsSpatialContext ctx = (JtsSpatialContext)super.ctx; GeometricShapeFactory gsf = new GeometricShapeFactory(ctx.getGeometryFactory()); gsf.setCentre(new Coordinate(0, 0)); gsf.setSize(180);//diameter gsf.setNumPoints(points); return gsf.createCircle(); } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/io/JtsWKTReaderShapeParserTest.java000066400000000000000000000061361241767633500305430ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io; import com.carrotsearch.randomizedtesting.RandomizedTest; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.context.jts.JtsSpatialContextFactory; import com.spatial4j.core.exception.InvalidShapeException; import com.spatial4j.core.io.jts.JtsWKTReaderShapeParser; import com.spatial4j.core.io.jts.JtsWktShapeParser; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import org.junit.Test; import java.io.IOException; public class JtsWKTReaderShapeParserTest extends RandomizedTest { final SpatialContext ctx; { JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.datelineRule = JtsWktShapeParser.DatelineRule.ccwRect; factory.wktShapeParserClass = JtsWKTReaderShapeParser.class; ctx = factory.newSpatialContext(); } @Test public void wktGeoPt() throws IOException { Shape s = ctx.readShape("Point(-160 30)"); assertEquals(ctx.makePoint(-160,30),s); } @Test public void wktGeoRect() throws IOException { //REMEMBER: Polygon WKT's outer ring is counter-clockwise order. If you accidentally give the other direction, // JtsSpatialContext will give the wrong result for a rectangle crossing the dateline. // In these two tests, we give the same set of points, one that does not cross the dateline, and the 2nd does. The // order is counter-clockwise in both cases as it should be. Shape sNoDL = ctx.readShape("Polygon((-170 30, -170 15, 160 15, 160 30, -170 30))"); Rectangle expectedNoDL = ctx.makeRectangle(-170, 160, 15, 30); assertTrue(!expectedNoDL.getCrossesDateLine()); assertEquals(expectedNoDL,sNoDL); Shape sYesDL = ctx.readShape("Polygon(( 160 30, 160 15, -170 15, -170 30, 160 30))"); Rectangle expectedYesDL = ctx.makeRectangle(160, -170, 15, 30); assertTrue(expectedYesDL.getCrossesDateLine()); assertEquals(expectedYesDL,sYesDL); } @Test public void testWrapTopologyException() { try { ctx.readShape("POLYGON((0 0, 10 0, 10 20))");//doesn't connect around fail(); } catch (InvalidShapeException e) { //expected } try { ctx.readShape("POLYGON((0 0, 10 0, 10 20, 5 -5, 0 20, 0 0))");//Topology self-intersect fail(); } catch (InvalidShapeException e) { //expected } } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/io/JtsWktShapeParserTest.java000066400000000000000000000153761241767633500275260ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io; import com.spatial4j.core.context.jts.JtsSpatialContext; import com.spatial4j.core.context.jts.JtsSpatialContextFactory; import com.spatial4j.core.io.jts.JtsWktShapeParser; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.SpatialRelation; import com.spatial4j.core.shape.jts.JtsGeometry; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.GeometryFactory; import org.junit.Test; import java.text.ParseException; import java.util.Arrays; import java.util.Collections; public class JtsWktShapeParserTest extends WktShapeParserTest { //By extending WktShapeParserTest we inherit its test too final JtsSpatialContext ctx;//note: masks superclass public JtsWktShapeParserTest() { super(JtsSpatialContext.GEO); this.ctx = (JtsSpatialContext) super.ctx; } @Test public void testParsePolygon() throws ParseException { Shape polygonNoHoles = new PolygonBuilder(ctx) .point(100, 0) .point(101, 0) .point(101, 1) .point(100, 2) .point(100, 0) .build(); String polygonNoHolesSTR = "POLYGON ((100 0, 101 0, 101 1, 100 2, 100 0))"; assertParses(polygonNoHolesSTR, polygonNoHoles); assertParses("POLYGON((100 0,101 0,101 1,100 2,100 0))", polygonNoHoles); assertParses("GEOMETRYCOLLECTION ( "+polygonNoHolesSTR+")", ctx.makeCollection(Arrays.asList(polygonNoHoles))); Shape polygonWithHoles = new PolygonBuilder(ctx) .point(100, 0) .point(101, 0) .point(101, 1) .point(100, 1) .point(100, 0) .newHole() .point(100.2, 0.2) .point(100.8, 0.2) .point(100.8, 0.8) .point(100.2, 0.8) .point(100.2, 0.2) .endHole() .build(); assertParses("POLYGON ((100 0, 101 0, 101 1, 100 1, 100 0), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2))", polygonWithHoles); GeometryFactory gf = ctx.getGeometryFactory(); assertParses("POLYGON EMPTY", ctx.makeShape( gf.createPolygon(gf.createLinearRing(new Coordinate[]{}), null) )); } @Test public void testPolyToRect() throws ParseException { //poly is a rect (no dateline issue) assertParses("POLYGON((0 5, 10 5, 10 20, 0 20, 0 5))", ctx.makeRectangle(0, 10, 5, 20)); } @Test public void polyToRect180Rule() throws ParseException { //crosses dateline Rectangle expected = ctx.makeRectangle(160, -170, 0, 10); //counter-clockwise assertParses("POLYGON((160 0, -170 0, -170 10, 160 10, 160 0))", expected); //clockwise assertParses("POLYGON((160 10, -170 10, -170 0, 160 0, 160 10))", expected); } @Test public void polyToRectCcwRule() throws ParseException { JtsSpatialContext ctx = new JtsSpatialContextFactory() { { datelineRule = JtsWktShapeParser.DatelineRule.ccwRect;} }.newSpatialContext(); //counter-clockwise assertEquals(ctx.readShapeFromWkt("POLYGON((160 0, -170 0, -170 10, 160 10, 160 0))"), ctx.makeRectangle(160, -170, 0, 10)); //clockwise assertEquals(ctx.readShapeFromWkt("POLYGON((160 10, -170 10, -170 0, 160 0, 160 10))"), ctx.makeRectangle(-170, 160, 0, 10)); } @Test public void testParseMultiPolygon() throws ParseException { Shape p1 = new PolygonBuilder(ctx) .point(100, 0) .point(101, 0)//101 .point(101, 2)//101 .point(100, 1) .point(100, 0) .build(); Shape p2 = new PolygonBuilder(ctx) .point(100, 0) .point(102, 0)//102 .point(102, 2)//102 .point(100, 1) .point(100, 0) .build(); Shape s = ctx.makeCollection( Arrays.asList(p1, p2) ); assertParses("MULTIPOLYGON(" + "((100 0, 101 0, 101 2, 100 1, 100 0))" + ',' + "((100 0, 102 0, 102 2, 100 1, 100 0))" + ")", s); assertParses("MULTIPOLYGON EMPTY", ctx.makeCollection(Collections.EMPTY_LIST)); } @Test public void testLineStringDateline() throws ParseException { //works because we use JTS (JtsGeometry); BufferedLineString doesn't yet do DL wrap. Shape s = ctx.readShapeFromWkt("LINESTRING(160 10, -170 15)"); assertEquals(30, s.getBoundingBox().getWidth(), 0.0 ); } @Test public void testWrapTopologyException() { //test that we can catch ParseException without having to detect TopologyException too assert ((JtsWktShapeParser)ctx.getWktShapeParser()).isAutoValidate(); try { ctx.readShapeFromWkt("POLYGON((0 0, 10 0, 10 20))");//doesn't connect around fail(); } catch (ParseException e) { //expected } try { ctx.readShapeFromWkt("POLYGON((0 0, 10 0, 10 20, 5 -5, 0 20, 0 0))");//Topology self-intersect fail(); } catch (ParseException e) { //expected } } @Test public void testPolygonRepair() throws ParseException { //because we're going to test validation System.setProperty(JtsGeometry.SYSPROP_ASSERT_VALIDATE, "false"); //note: doesn't repair all cases; this case isn't: //ctx.readShapeFromWkt("POLYGON((0 0, 10 0, 10 20))");//doesn't connect around String wkt = "POLYGON((0 0, 10 0, 10 20, 5 -5, 0 20, 0 0))";//Topology self-intersect JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.validationRule = JtsWktShapeParser.ValidationRule.repairBuffer0; JtsSpatialContext ctx = factory.newSpatialContext(); Shape buffer0 = ctx.readShapeFromWkt(wkt); assertTrue(buffer0.getArea(ctx) > 0); factory = new JtsSpatialContextFactory(); factory.validationRule = JtsWktShapeParser.ValidationRule.repairConvexHull; ctx = factory.newSpatialContext(); Shape cvxHull = ctx.readShapeFromWkt(wkt); assertTrue(cvxHull.getArea(ctx) > 0); assertEquals(SpatialRelation.CONTAINS, cvxHull.relate(buffer0)); factory = new JtsSpatialContextFactory(); factory.validationRule = JtsWktShapeParser.ValidationRule.none; ctx = factory.newSpatialContext(); ctx.readShapeFromWkt(wkt);//doesn't throw } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/io/PolygonBuilder.java000066400000000000000000000077331241767633500262360ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io; import com.spatial4j.core.context.jts.JtsSpatialContext; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.jts.JtsGeometry; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.LinearRing; import com.vividsolutions.jts.geom.Polygon; import java.util.ArrayList; import java.util.List; /** * Builder for creating a {@link com.spatial4j.core.shape.Shape} instance of a Polygon */ public class PolygonBuilder { private final JtsSpatialContext ctx; private final List points = new ArrayList(); private final List holes = new ArrayList(); public PolygonBuilder(JtsSpatialContext ctx) { this.ctx = ctx; } /** * Adds a point to the Polygon * * @param lon Longitude of the point * @param lat Latitude of the point * @return this */ public PolygonBuilder point(double lon, double lat) { points.add(new Coordinate(lon, lat)); return this; } /** * Starts a new hole in the Polygon * * @return PolygonHoleBuilder to create the new hole */ public PolygonHoleBuilder newHole() { return new PolygonHoleBuilder(this); } /** * Registers the LinearRing representing a hole * * @param linearRing Hole to register * @return this */ private PolygonBuilder addHole(LinearRing linearRing) { holes.add(linearRing); return this; } /** * Builds a {@link com.spatial4j.core.shape.Shape} instance representing the polygon * * @return Built polygon */ public Shape build() { return new JtsGeometry(toPolygon(), ctx, true, true); } /** * Creates the raw {@link com.vividsolutions.jts.geom.Polygon} * * @return Built polygon */ public Polygon toPolygon() { LinearRing ring = ctx.getGeometryFactory().createLinearRing(points.toArray(new Coordinate[points.size()])); LinearRing[] holes = this.holes.isEmpty() ? null : this.holes.toArray(new LinearRing[this.holes.size()]); return ctx.getGeometryFactory().createPolygon(ring, holes); } /** * Builder for defining a hole in a {@link com.vividsolutions.jts.geom.Polygon} */ public class PolygonHoleBuilder { private final List points = new ArrayList(); private final PolygonBuilder polygonBuilder; /** * Creates a new PolygonHoleBuilder * * @param polygonBuilder PolygonBuilder that the hole built by this builder * will be added to */ private PolygonHoleBuilder(PolygonBuilder polygonBuilder) { this.polygonBuilder = polygonBuilder; } /** * Adds a point to the LinearRing * * @param lon Longitude of the point * @param lat Latitude of the point * @return this */ public PolygonHoleBuilder point(double lon, double lat) { points.add(new Coordinate(lon, lat)); return this; } /** * Ends the building of the hole * * @return PolygonBuilder to use to build the remainder of the Polygon. */ public PolygonBuilder endHole() { return polygonBuilder.addHole(ctx.getGeometryFactory().createLinearRing(points.toArray(new Coordinate[points.size()]))); } } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/io/ShapeReadWriterTest.java000066400000000000000000000052431241767633500271630ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io; import com.carrotsearch.randomizedtesting.RandomizedTest; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.context.jts.JtsSpatialContext; import com.spatial4j.core.shape.Shape; import org.junit.Test; import java.io.IOException; import java.util.Arrays; @SuppressWarnings("unchecked") public class ShapeReadWriterTest extends RandomizedTest { @ParametersFactory public static Iterable parameters() { return Arrays.asList($$( $(SpatialContext.GEO), $(JtsSpatialContext.GEO) )); } private final SpatialContext ctx; public ShapeReadWriterTest(SpatialContext ctx) { this.ctx = ctx; } private T writeThenRead( T s ) throws IOException { String buff = ctx.toString( s ); return (T) ctx.readShape( buff ); } @Test public void testPoint() throws IOException { Shape s = ctx.readShape("10 20"); assertEquals(ctx.makePoint(10,20),s); assertEquals(s,writeThenRead(s)); assertEquals(s,ctx.readShape("20,10"));//check comma for y,x format assertEquals(s,ctx.readShape("20, 10"));//test space assertFalse(s.hasArea()); } @Test public void testRectangle() throws IOException { Shape s = ctx.readShape("-10 -20 10 20"); assertEquals(ctx.makeRectangle(-10, 10, -20, 20),s); assertEquals(s,writeThenRead(s)); assertTrue(s.hasArea()); } @Test public void testCircle() throws IOException { Shape s = ctx.readShape("Circle(1.23 4.56 distance=7.89)"); assertEquals(ctx.makeCircle(1.23, 4.56, 7.89),s); assertEquals(s,writeThenRead(s)); assertEquals(s,ctx.readShape("CIRCLE( 4.56,1.23 d=7.89 )")); // use lat,lon and use 'd' abbreviation assertTrue(s.hasArea()); } // Looking for more tests? Shapes are tested in TestShapes2D. } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/io/TestGeohashUtils.java000066400000000000000000000105721241767633500265320ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.Point; import org.junit.Test; import static org.junit.Assert.assertEquals; /** * Tests for {@link GeohashUtils} */ public class TestGeohashUtils { SpatialContext ctx = SpatialContext.GEO; /** * Pass condition: lat=42.6, lng=-5.6 should be encoded as "ezs42e44yx96", * lat=57.64911 lng=10.40744 should be encoded as "u4pruydqqvj8" */ @Test public void testEncode() { String hash = GeohashUtils.encodeLatLon(42.6, -5.6); assertEquals("ezs42e44yx96", hash); hash = GeohashUtils.encodeLatLon(57.64911, 10.40744); assertEquals("u4pruydqqvj8", hash); } /** * Pass condition: lat=52.3738007, lng=4.8909347 should be encoded and then * decoded within 0.00001 of the original value */ @Test public void testDecodePreciseLongitudeLatitude() { String hash = GeohashUtils.encodeLatLon(52.3738007, 4.8909347); Point point = GeohashUtils.decode(hash,ctx); assertEquals(52.3738007, point.getY(), 0.00001D); assertEquals(4.8909347, point.getX(), 0.00001D); } /** * Pass condition: lat=84.6, lng=10.5 should be encoded and then decoded * within 0.00001 of the original value */ @Test public void testDecodeImpreciseLongitudeLatitude() { String hash = GeohashUtils.encodeLatLon(84.6, 10.5); Point point = GeohashUtils.decode(hash, ctx); assertEquals(84.6, point.getY(), 0.00001D); assertEquals(10.5, point.getX(), 0.00001D); } /* * see https://issues.apache.org/jira/browse/LUCENE-1815 for details */ @Test public void testDecodeEncode() { String geoHash = "u173zq37x014"; assertEquals(geoHash, GeohashUtils.encodeLatLon(52.3738007, 4.8909347)); Point point = GeohashUtils.decode(geoHash,ctx); assertEquals(52.37380061d, point.getY(), 0.000001d); assertEquals(4.8909343d, point.getX(), 0.000001d); assertEquals(geoHash, GeohashUtils.encodeLatLon(point.getY(), point.getX())); geoHash = "u173"; point = GeohashUtils.decode("u173",ctx); geoHash = GeohashUtils.encodeLatLon(point.getY(), point.getX()); final Point point2 = GeohashUtils.decode(geoHash, ctx); assertEquals(point.getY(), point2.getY(), 0.000001d); assertEquals(point.getX(), point2.getX(), 0.000001d); } /** see the table at http://en.wikipedia.org/wiki/Geohash */ @Test public void testHashLenToWidth() { //test odd & even len double[] boxOdd = GeohashUtils.lookupDegreesSizeForHashLen(3); assertEquals(1.40625,boxOdd[0],0.0001); assertEquals(1.40625,boxOdd[1],0.0001); double[] boxEven = GeohashUtils.lookupDegreesSizeForHashLen(4); assertEquals(0.1757,boxEven[0],0.0001); assertEquals(0.3515,boxEven[1],0.0001); } /** see the table at http://en.wikipedia.org/wiki/Geohash */ @Test public void testLookupHashLenForWidthHeight() { assertEquals(1, GeohashUtils.lookupHashLenForWidthHeight(999,999)); assertEquals(1, GeohashUtils.lookupHashLenForWidthHeight(999,46)); assertEquals(1, GeohashUtils.lookupHashLenForWidthHeight(46,999)); assertEquals(2, GeohashUtils.lookupHashLenForWidthHeight(44,999)); assertEquals(2, GeohashUtils.lookupHashLenForWidthHeight(999,44)); assertEquals(2, GeohashUtils.lookupHashLenForWidthHeight(999,5.7)); assertEquals(2, GeohashUtils.lookupHashLenForWidthHeight(11.3,999)); assertEquals(3, GeohashUtils.lookupHashLenForWidthHeight(999,5.5)); assertEquals(3, GeohashUtils.lookupHashLenForWidthHeight(11.1,999)); assertEquals(GeohashUtils.MAX_PRECISION, GeohashUtils.lookupHashLenForWidthHeight(10e-20,10e-20)); } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/io/WktCustomShapeParserTest.java000066400000000000000000000065401241767633500302310ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.context.SpatialContextFactory; import com.spatial4j.core.shape.Shape; import com.spatial4j.core.shape.impl.PointImpl; import org.junit.Test; import java.text.ParseException; public class WktCustomShapeParserTest extends WktShapeParserTest { static class CustomShape extends PointImpl { private final String name; /** * A simple constructor without normalization / validation. */ public CustomShape(String name, SpatialContext ctx) { super(0, 0, ctx); this.name = name; } } public WktCustomShapeParserTest() { super(makeCtx()); } private static SpatialContext makeCtx() { SpatialContextFactory factory = new SpatialContextFactory(); factory.wktShapeParserClass = MyWKTShapeParser.class; return factory.newSpatialContext(); } @Test public void testCustomShape() throws ParseException { assertEquals("customShape", ((CustomShape)ctx.readShapeFromWkt("customShape()")).name); assertEquals("custom3d", ((CustomShape)ctx.readShapeFromWkt("custom3d ()")).name);//number supported } @Test public void testNextSubShapeString() throws ParseException { WktShapeParser.State state = ctx.getWktShapeParser().newState("OUTER(INNER(3, 5))"); state.offset = 0; assertEquals("OUTER(INNER(3, 5))", state.nextSubShapeString()); assertEquals("OUTER(INNER(3, 5))".length(), state.offset); state.offset = "OUTER(".length(); assertEquals("INNER(3, 5)", state.nextSubShapeString()); assertEquals("OUTER(INNER(3, 5)".length(), state.offset); state.offset = "OUTER(INNER(".length(); assertEquals("3", state.nextSubShapeString()); assertEquals("OUTER(INNER(3".length(), state.offset); } public static class MyWKTShapeParser extends WktShapeParser { public MyWKTShapeParser(SpatialContext ctx, SpatialContextFactory factory) { super(ctx, factory); } protected State newState(String wkt) { //First few lines compile, despite newState() being protected. Just proving extensibility. WktShapeParser other = null; if (false) other.newState(wkt); return new State(wkt); } @Override public Shape parseShapeByType(State state, String shapeType) throws ParseException { Shape result = super.parseShapeByType(state, shapeType); if (result == null && shapeType.contains("custom")) { state.nextExpect('('); state.nextExpect(')'); return new CustomShape(shapeType, ctx); } return result; } } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/io/WktShapeParserTest.java000066400000000000000000000142531241767633500270360ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.io; import com.carrotsearch.randomizedtesting.RandomizedTest; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import org.junit.Test; import java.text.ParseException; import java.util.Arrays; import java.util.Collections; public class WktShapeParserTest extends RandomizedTest { final SpatialContext ctx; protected WktShapeParserTest(SpatialContext ctx) { this.ctx = ctx; } public WktShapeParserTest() { this(SpatialContext.GEO); } protected void assertParses(String wkt, Shape expected) throws ParseException { assertEquals(ctx.readShapeFromWkt(wkt), expected); } protected void assertFails(String wkt) { try { ctx.readShapeFromWkt(wkt); fail("ParseException expected"); } catch (ParseException e) {//expected } } @Test public void testNoOp() throws ParseException { WktShapeParser wktShapeParser = ctx.getWktShapeParser(); assertNull(wktShapeParser.parseIfSupported("")); assertNull(wktShapeParser.parseIfSupported(" ")); assertNull(wktShapeParser.parseIfSupported("BogusShape()")); assertNull(wktShapeParser.parseIfSupported("BogusShape")); } @Test public void testParsePoint() throws ParseException { assertParses("POINT (100 90)", ctx.makePoint(100, 90));//typical assertParses(" POINT (100 90) ", ctx.makePoint(100, 90));//trimmed assertParses("point (100 90)", ctx.makePoint(100, 90));//case indifferent assertParses("POINT ( 100 90 )", ctx.makePoint(100, 90));//inner spaces assertParses("POINT(100 90)", ctx.makePoint(100, 90)); assertParses("POINT (-45 90 )", ctx.makePoint(-45, 90)); Point expected = ctx.makePoint(-45.3, 80.4); assertParses("POINT (-45.3 80.4 )", expected); assertParses("POINT (-45.3 +80.4 )", expected); assertParses("POINT (-45.3 8.04e1 )", expected); assertParses("POINT EMPTY", ctx.makePoint(Double.NaN, Double.NaN)); //other dimensions are skipped assertParses("POINT (100 90 2)", ctx.makePoint(100, 90)); assertParses("POINT (100 90 2 3)", ctx.makePoint(100, 90)); assertParses("POINT ZM ( 100 90 )", ctx.makePoint(100, 90));//ignore dimension assertParses("POINT ZM ( 100 90 -3 -4)", ctx.makePoint(100, 90));//ignore dimension } @Test public void testParsePoint_invalidDefinitions() { assertFails("POINT 100 90"); assertFails("POINT (100 90"); assertFails("POINT (100, 90)"); assertFails("POINT 100 90)"); assertFails("POINT (100)"); assertFails("POINT (10f0 90)"); assertFails("POINT (EMPTY)"); assertFails("POINT (1 2), POINT (2 3)"); assertFails("POINT EMPTY (1 2)"); assertFails("POINT ZM EMPTY (1 2)"); assertFails("POINT ZM EMPTY 1"); } @Test public void testParseMultiPoint() throws ParseException { Shape s1 = ctx.makeCollection(Collections.singletonList(ctx.makePoint(10, 40))); assertParses("MULTIPOINT (10 40)", s1); Shape s4 = ctx.makeCollection(Arrays.asList( ctx.makePoint(10, 40), ctx.makePoint(40, 30), ctx.makePoint(20, 20), ctx.makePoint(30, 10))); assertParses("MULTIPOINT ((10 40), (40 30), (20 20), (30 10))", s4); assertParses("MULTIPOINT (10 40, 40 30, 20 20, 30 10)", s4); assertParses("MULTIPOINT Z EMPTY", ctx.makeCollection(Collections.EMPTY_LIST)); } @Test public void testParseEnvelope() throws ParseException { Rectangle r = ctx.makeRectangle(ctx.makePoint(10, 25), ctx.makePoint(30, 45)); assertParses(" ENVELOPE ( 10 , 30 , 45 , 25 ) ", r); assertParses("ENVELOPE(10,30,45,25) ", r); assertFails("ENVELOPE (10 30 45 25)"); } @Test public void testLineStringShape() throws ParseException { Point p1 = ctx.makePoint(1, 10); Point p2 = ctx.makePoint(2, 20); Point p3 = ctx.makePoint(3, 30); Shape ls = ctx.makeLineString(Arrays.asList(p1, p2, p3)); assertParses("LINESTRING (1 10, 2 20, 3 30)", ls); assertParses("LINESTRING EMPTY", ctx.makeLineString(Collections.emptyList())); } @Test public void testMultiLineStringShape() throws ParseException { Shape s = ctx.makeCollection(Arrays.asList( ctx.makeLineString(Arrays.asList( ctx.makePoint(10, 10), ctx.makePoint(20, 20), ctx.makePoint(10, 40))), ctx.makeLineString(Arrays.asList( ctx.makePoint(40, 40), ctx.makePoint(30, 30), ctx.makePoint(40, 20), ctx.makePoint(30, 10))) )); assertParses("MULTILINESTRING ((10 10, 20 20, 10 40),\n" + "(40 40, 30 30, 40 20, 30 10))", s); assertParses("MULTILINESTRING M EMPTY", ctx.makeCollection(Collections.EMPTY_LIST)); } @Test public void testGeomCollection() throws ParseException { Shape s1 = ctx.makeCollection(Arrays.asList(ctx.makePoint(1, 2))); Shape s2 = ctx.makeCollection( Arrays.asList(ctx.makeRectangle(1, 2, 3, 4), ctx.makePoint(-1, -2)) ); assertParses("GEOMETRYCOLLECTION (POINT (1 2) )", s1); assertParses("GEOMETRYCOLLECTION ( ENVELOPE(1,2,4,3), POINT(-1 -2)) ", s2); assertParses("GEOMETRYCOLLECTION EMPTY", ctx.makeCollection(Collections.EMPTY_LIST)); assertParses("GEOMETRYCOLLECTION ( POINT EMPTY )", ctx.makeCollection(Arrays.asList(ctx.makePoint(Double.NaN, Double.NaN)))); } @Test public void testBuffer() throws ParseException { assertParses("BUFFER(POINT(1 2), 3)", ctx.makePoint(1, 2).getBuffered(3, ctx)); } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/shape/000077500000000000000000000000001241767633500231145ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/shape/AbstractTestShapes.java000077500000000000000000000206361241767633500275400ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape; import com.spatial4j.core.TestLog; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.distance.DistanceCalculator; import com.spatial4j.core.distance.DistanceUtils; import com.spatial4j.core.shape.impl.PointImpl; import com.spatial4j.core.shape.impl.RectangleImpl; import org.junit.Rule; import org.junit.Test; import static com.spatial4j.core.shape.SpatialRelation.CONTAINS; import static com.spatial4j.core.shape.SpatialRelation.DISJOINT; /** * Some basic tests that should work with various {@link SpatialContext} * configurations. Subclasses add more. */ public abstract class AbstractTestShapes extends RandomizedShapeTest { public AbstractTestShapes(SpatialContext ctx) { super(ctx); } @Rule public final TestLog testLog = TestLog.instance; protected void testRectangle(double minX, double width, double minY, double height) { double maxX = minX + width; double maxY = minY + height; minX = normX(minX); maxX = normX(maxX); Rectangle r = ctx.makeRectangle(minX, maxX, minY, maxY); //test equals & hashcode of duplicate Rectangle r2 = ctx.makeRectangle(minX, maxX, minY, maxY); assertEquals(r,r2); assertEquals(r.hashCode(),r2.hashCode()); String msg = r.toString(); assertEquals(msg, width != 0 && height != 0, r.hasArea()); assertEquals(msg, width != 0 && height != 0, r.getArea(ctx) > 0); if (ctx.isGeo() && r.getWidth() == 360 && r.getHeight() == 180) { //whole globe double earthRadius = DistanceUtils.toDegrees(1); assertEquals(4*Math.PI * earthRadius * earthRadius, r.getArea(ctx), 1.0);//1km err } assertEqualsRatio(msg, height, r.getHeight()); assertEqualsRatio(msg, width, r.getWidth()); Point center = r.getCenter(); msg += " ctr:"+center; //System.out.println(msg); assertRelation(msg, CONTAINS, r, center); DistanceCalculator dc = ctx.getDistCalc(); double dUR = dc.distance(center, r.getMaxX(), r.getMaxY()); double dLR = dc.distance(center, r.getMaxX(), r.getMinY()); double dUL = dc.distance(center, r.getMinX(), r.getMaxY()); double dLL = dc.distance(center, r.getMinX(), r.getMinY()); assertEquals(msg,width != 0 || height != 0, dUR != 0); if (dUR != 0) assertTrue(dUR > 0 && dLL > 0); assertEqualsRatio(msg, dUR, dUL); assertEqualsRatio(msg, dLR, dLL); if (!ctx.isGeo() || center.getY() == 0) assertEqualsRatio(msg, dUR, dLL); } protected void testRectIntersect() { //This test loops past the dateline for some variables but the makeNormRect() // method ensures the rect is valid. final double INCR = 45; final double Y = 20; for(double left = -180; left < 180; left += INCR) { for(double right = left; right - left <= 360; right += INCR) { Rectangle r = makeNormRect(left, right, -Y, Y); //test contains (which also tests within) for(double left2 = left; left2 <= right; left2 += INCR) { for(double right2 = left2; right2 <= right; right2 += INCR) { Rectangle r2 = makeNormRect(left2, right2, -Y, Y); assertRelation(null, SpatialRelation.CONTAINS, r, r2); //test point contains assertRelation(null, SpatialRelation.CONTAINS, r, r2.getCenter()); } } //test disjoint for(double left2 = right+INCR; left2 - left < 360; left2 += INCR) { //test point disjoint assertRelation(null, SpatialRelation.DISJOINT, r, ctx.makePoint( normX(left2), randomIntBetween(-90, 90))); for(double right2 = left2; right2 - left < 360; right2 += INCR) { Rectangle r2 = makeNormRect(left2, right2, -Y, Y); assertRelation(null, SpatialRelation.DISJOINT, r, r2); } } //test intersect for(double left2 = left+INCR; left2 <= right; left2 += INCR) { for(double right2 = right+INCR; right2 - left < 360; right2 += INCR) { Rectangle r2 = makeNormRect(left2, right2, -Y, Y); assertRelation(null, SpatialRelation.INTERSECTS, r, r2); } } } } } protected void testCircle(double x, double y, double dist) { Circle c = ctx.makeCircle(x, y, dist); String msg = c.toString(); final Circle c2 = ctx.makeCircle(ctx.makePoint(x, y), dist); assertEquals(c, c2); assertEquals(c.hashCode(),c2.hashCode()); assertEquals(msg, dist > 0, c.hasArea()); double area = c.getArea(ctx); assertTrue(msg, c.hasArea() == (area > 0.0)); final Rectangle bbox = c.getBoundingBox(); assertEquals(msg, dist > 0, bbox.getArea(ctx) > 0); assertTrue(msg, area <= bbox.getArea(ctx)); if (!ctx.isGeo()) { //if not geo then units of dist == units of x,y assertEqualsRatio(msg, bbox.getHeight(), dist * 2); assertEqualsRatio(msg, bbox.getWidth(), dist * 2); } assertRelation(msg, CONTAINS, c, c.getCenter()); assertRelation(msg, CONTAINS, bbox, c); } protected void testCircleIntersect() { new RectIntersectionTestHelper(ctx) { @Override protected Circle generateRandomShape(Point nearP) { double cX = randomIntBetweenDivisible(-180, 179); double cY = randomIntBetweenDivisible(-90, 90); double cR_dist = randomIntBetweenDivisible(0, 180); return ctx.makeCircle(cX, cY, cR_dist); } @Override protected Point randomPointInEmptyShape(Circle shape) { return shape.getCenter(); } @Override protected void onAssertFail(AssertionError e, Circle s, Rectangle r, SpatialRelation ic) { //Check if the circle's edge appears to coincide with the shape. final double radius = s.getRadius(); if (radius == 180) throw e;//if this happens, then probably a bug if (radius == 0) { Point p = s.getCenter(); //if touches a side then don't throw if (p.getX() == r.getMinX() || p.getX() == r.getMaxX() || p.getY() == r.getMinY() || p.getY() == r.getMaxY()) return; throw e; } final double eps = 0.0000001; s.reset(s.getCenter().getX(), s.getCenter().getY(), radius - eps); SpatialRelation rel1 = s.relate(r); s.reset(s.getCenter().getX(), s.getCenter().getY(), radius + eps); SpatialRelation rel2 = s.relate(r); if (rel1 == rel2) throw e; s.reset(s.getCenter().getX(), s.getCenter().getY(), radius);//reset System.out.println("Seed "+getContext().getRunnerSeedAsString()+": Hid assertion due to ambiguous edge touch: "+s+" "+r); } }.testRelateWithRectangle(); } @Test public void testMakeRect() { //test rectangle constructor assertEquals(new RectangleImpl(1,3,2,4, ctx), new RectangleImpl(new PointImpl(1,2, ctx),new PointImpl(3,4, ctx), ctx)); //test ctx.makeRect assertEquals(ctx.makeRectangle(1, 3, 2, 4), ctx.makeRectangle(ctx.makePoint(1, 2), ctx.makePoint(3, 4))); } protected void testEmptiness(Shape emptyShape) { assertTrue(emptyShape.isEmpty()); Point emptyPt = emptyShape.getCenter(); assertTrue(emptyPt.isEmpty()); Rectangle emptyRect = emptyShape.getBoundingBox(); assertTrue(emptyRect.isEmpty()); assertEquals(emptyRect, emptyShape.getBoundingBox()); assertEquals(emptyPt, emptyShape.getCenter()); assertRelation("EMPTY", DISJOINT, emptyShape, emptyPt); assertRelation("EMPTY", DISJOINT, emptyShape, randomPoint()); assertRelation("EMPTY", DISJOINT, emptyShape, emptyRect); assertRelation("EMPTY", DISJOINT, emptyShape, randomRectangle(10)); assertTrue(emptyShape.getBuffered(randomInt(4), ctx).isEmpty()); } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/shape/BufferedLineStringTest.java000066400000000000000000000044611241767633500303450ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape; import com.carrotsearch.randomizedtesting.RandomizedTest; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.context.SpatialContextFactory; import com.spatial4j.core.shape.impl.BufferedLineString; import com.spatial4j.core.shape.impl.RectangleImpl; import org.junit.Test; import java.util.ArrayList; import java.util.List; public class BufferedLineStringTest extends RandomizedTest { private final SpatialContext ctx = new SpatialContextFactory() {{geo = false; worldBounds = new RectangleImpl(-100, 100, -50, 50, null);}}.newSpatialContext(); @Test public void testRectIntersect() { new RectIntersectionTestHelper(ctx) { @Override protected BufferedLineString generateRandomShape(Point nearP) { Rectangle nearR = randomRectangle(nearP); int numPoints = 2 + randomInt(3);//2-5 points ArrayList points = new ArrayList(numPoints); while (points.size() < numPoints) { points.add(randomPointIn(nearR)); } double maxBuf = Math.max(nearR.getWidth(), nearR.getHeight()); double buf = Math.abs(randomGaussian()) * maxBuf / 4; buf = randomInt((int) divisible(buf)); return new BufferedLineString(points, buf, ctx); } protected Point randomPointInEmptyShape(BufferedLineString shape) { List points = shape.getPoints(); return points.get(randomInt(points.size() - 1)); } }.testRelateWithRectangle(); } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/shape/BufferedLineTest.java000066400000000000000000000147701241767633500271620ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape; import com.carrotsearch.randomizedtesting.RandomizedTest; import com.carrotsearch.randomizedtesting.annotations.Repeat; import com.spatial4j.core.TestLog; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.context.SpatialContextFactory; import com.spatial4j.core.shape.impl.BufferedLine; import com.spatial4j.core.shape.impl.PointImpl; import com.spatial4j.core.shape.impl.RectangleImpl; import org.junit.Rule; import org.junit.Test; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; public class BufferedLineTest extends RandomizedTest { private final SpatialContext ctx = new SpatialContextFactory() {{geo = false; worldBounds = new RectangleImpl(-100, 100, -50, 50, null);}}.newSpatialContext(); @Rule public TestLog testLog = TestLog.instance; //SpatialContext.GEO ;// public static void logShapes(final BufferedLine line, final Rectangle rect) { String lineWKT = "LINESTRING(" + line.getA().getX() + " " + line.getA().getY() + "," + line.getB().getX() + " " + line.getB().getY() + ")"; System.out.println( "GEOMETRYCOLLECTION(" + lineWKT + "," + rectToWkt(line.getBoundingBox ()) + ")"); String rectWKT = rectToWkt(rect); System.out.println(rectWKT); } static private String rectToWkt(Rectangle rect) { return "POLYGON((" + rect.getMinX() + " " + rect.getMinY() + "," + rect.getMaxX() + " " + rect.getMinY() + "," + rect.getMaxX() + " " + rect.getMaxY() + "," + rect.getMinX() + " " + rect.getMaxY() + "," + rect.getMinX() + " " + rect.getMinY() + "))"; } @Test public void distance() { //negative slope testDistToPoint(ctx.makePoint(7, -4), ctx.makePoint(3, 2), ctx.makePoint(5, 6), 3.88290); //positive slope testDistToPoint(ctx.makePoint(3, 2), ctx.makePoint(7, 5), ctx.makePoint(5, 6), 2.0); //vertical line testDistToPoint(ctx.makePoint(3, 2), ctx.makePoint(3, 8), ctx.makePoint(4, 3), 1.0); //horiz line testDistToPoint(ctx.makePoint(3, 2), ctx.makePoint(6, 2), ctx.makePoint(4, 3), 1.0); } private void testDistToPoint(Point pA, Point pB, Point pC, double dist) { if (dist > 0) { assertFalse(new BufferedLine(pA, pB, dist * 0.999, ctx).contains(pC)); } else { assert dist == 0; assertTrue(new BufferedLine(pA, pB, 0, ctx).contains(pC)); } assertTrue(new BufferedLine(pA, pB, dist * 1.001, ctx).contains(pC)); } @Test public void misc() { //pa == pb Point pt = ctx.makePoint(10, 1); BufferedLine line = new BufferedLine(pt, pt, 3, ctx); assertTrue(line.contains(ctx.makePoint(10, 1 + 3 - 0.1))); assertFalse(line.contains(ctx.makePoint(10, 1 + 3 + 0.1))); } @Test @Repeat(iterations = 15) public void quadrants() { //random line BufferedLine line = newRandomLine(); // if (line.getA().equals(line.getB())) // return;//this test doesn't work Rectangle rect = newRandomLine().getBoundingBox(); //logShapes(line, rect); //compute closest corner brute force ArrayList corners = quadrantCorners(rect); // a collection instead of 1 value due to ties Collection farthestDistanceQuads = new LinkedList(); double farthestDistance = -1; int quad = 1; for (Point corner : corners) { double d = line.getLinePrimary().distanceUnbuffered(corner); if (Math.abs(d - farthestDistance) < 0.000001) {//about equal farthestDistanceQuads.add(quad); } else if (d > farthestDistance) { farthestDistanceQuads.clear(); farthestDistanceQuads.add(quad); farthestDistance = d; } quad++; } //compare results int calcClosestQuad = line.getLinePrimary().quadrant(rect.getCenter()); assertTrue(farthestDistanceQuads.contains(calcClosestQuad)); } private BufferedLine newRandomLine() { Point pA = new PointImpl(randomInt(9), randomInt(9), ctx); Point pB = new PointImpl(randomInt(9), randomInt(9), ctx); int buf = randomInt(5); return new BufferedLine(pA, pB, buf, ctx); } private ArrayList quadrantCorners(Rectangle rect) { ArrayList corners = new ArrayList(4); corners.add(ctx.makePoint(rect.getMaxX(), rect.getMaxY())); corners.add(ctx.makePoint(rect.getMinX(), rect.getMaxY())); corners.add(ctx.makePoint(rect.getMinX(), rect.getMinY())); corners.add(ctx.makePoint(rect.getMaxX(), rect.getMinY())); return corners; } @Test public void testRectIntersect() { new RectIntersectionTestHelper(ctx) { @Override protected BufferedLine generateRandomShape(Point nearP) { Rectangle nearR = randomRectangle(nearP); ArrayList corners = quadrantCorners(nearR); int r4 = randomInt(3);//0..3 Point pA = corners.get(r4); Point pB = corners.get((r4 + 2) % 4); double maxBuf = Math.max(nearR.getWidth(), nearR.getHeight()); double buf = Math.abs(randomGaussian()) * maxBuf / 4; buf = randomInt((int) divisible(buf)); return new BufferedLine(pA, pB, buf, ctx); } protected Point randomPointInEmptyShape(BufferedLine shape) { int r = randomInt(1); if (r == 0) return shape.getA(); //if (r == 1) return shape.getB(); // Point c = shape.getCenter(); // if (shape.contains(c)); } }.testRelateWithRectangle(); } private BufferedLine newBufLine(int x1, int y1, int x2, int y2, int buf) { Point pA = ctx.makePoint(x1, y1); Point pB = ctx.makePoint(x2, y2); if (randomBoolean()) { return new BufferedLine(pB, pA, buf, ctx); } else { return new BufferedLine(pA, pB, buf, ctx); } } }spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/shape/JtsGeometryTest.java000077500000000000000000000232451241767633500271040ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape; import com.carrotsearch.randomizedtesting.RandomizedContext; import com.carrotsearch.randomizedtesting.annotations.Repeat; import com.spatial4j.core.context.jts.JtsSpatialContext; import com.spatial4j.core.context.jts.JtsSpatialContextFactory; import com.spatial4j.core.io.jts.JtsWktShapeParser; import com.spatial4j.core.shape.impl.PointImpl; import com.spatial4j.core.shape.jts.JtsGeometry; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.CoordinateFilter; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.IntersectionMatrix; import org.junit.Test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.text.ParseException; import java.util.Random; import static com.spatial4j.core.shape.SpatialRelation.CONTAINS; import static com.spatial4j.core.shape.SpatialRelation.DISJOINT; import static com.spatial4j.core.shape.SpatialRelation.INTERSECTS; /** Tests {@link com.spatial4j.core.shape.jts.JtsGeometry} and some other code related * to {@link com.spatial4j.core.context.jts.JtsSpatialContext}. */ public class JtsGeometryTest extends AbstractTestShapes { private final String POLY_STR = "Polygon((-10 30, -40 40, -10 -20, 40 20, 0 0, -10 30))"; private JtsGeometry POLY_SHAPE; private final int DL_SHIFT = 180;//since POLY_SHAPE contains 0 0, I know a shift of 180 will make it cross the DL. private JtsGeometry POLY_SHAPE_DL;//POLY_SHAPE shifted by DL_SHIFT to cross the dateline public JtsGeometryTest() throws ParseException { super(JtsSpatialContext.GEO); POLY_SHAPE = (JtsGeometry) ctx.readShapeFromWkt(POLY_STR); if (ctx.isGeo()) { POLY_SHAPE_DL = shiftPoly(POLY_SHAPE, DL_SHIFT); assertTrue(POLY_SHAPE_DL.getBoundingBox().getCrossesDateLine()); } } private JtsGeometry shiftPoly(JtsGeometry poly, final int lon_shift) throws ParseException { final Random random = RandomizedContext.current().getRandom(); Geometry pGeom = poly.getGeom(); assertTrue(pGeom.isValid()); //shift 180 to the right pGeom = (Geometry) pGeom.clone(); pGeom.apply(new CoordinateFilter() { @Override public void filter(Coordinate coord) { coord.x = normX(coord.x + lon_shift); if (ctx.isGeo() && Math.abs(coord.x) == 180 && random.nextBoolean()) coord.x = - coord.x;//invert sign of dateline boundary some of the time } }); pGeom.geometryChanged(); assertFalse(pGeom.isValid()); return (JtsGeometry) ctx.readShapeFromWkt(pGeom.toText()); } @Test public void testRelations() throws ParseException { testRelations(false); testRelations(true); } public void testRelations(boolean prepare) throws ParseException { assert !((JtsWktShapeParser)ctx.getWktShapeParser()).isAutoIndex(); //base polygon JtsGeometry base = (JtsGeometry) ctx.readShapeFromWkt("POLYGON((0 0, 10 0, 5 5, 0 0))"); //shares only "10 0" with base JtsGeometry polyI = (JtsGeometry) ctx.readShapeFromWkt("POLYGON((10 0, 20 0, 15 5, 10 0))"); //within base: differs from base by one point is within JtsGeometry polyW = (JtsGeometry) ctx.readShapeFromWkt("POLYGON((0 0, 9 0, 5 5, 0 0))"); //a boundary point of base Point pointB = ctx.makePoint(0, 0); //a shared boundary line of base JtsGeometry lineB = (JtsGeometry) ctx.readShapeFromWkt("LINESTRING(0 0, 10 0)"); //a line sharing only one point with base JtsGeometry lineI = (JtsGeometry) ctx.readShapeFromWkt("LINESTRING(10 0, 20 0)"); if (prepare) base.index(); assertRelation(CONTAINS, base, base);//preferred result as there is no EQUALS assertRelation(INTERSECTS, base, polyI); assertRelation(CONTAINS, base, polyW); assertRelation(CONTAINS, base, pointB); assertRelation(CONTAINS, base, lineB); assertRelation(INTERSECTS, base, lineI); if (prepare) lineB.index(); assertRelation(CONTAINS, lineB, lineB);//line contains itself assertRelation(CONTAINS, lineB, pointB); } @Test public void testEmpty() throws ParseException { Shape emptyGeom = ctx.readShapeFromWkt("POLYGON EMPTY"); testEmptiness(emptyGeom); assertRelation("EMPTY", DISJOINT, emptyGeom, POLY_SHAPE); } @Test public void testArea() { //simple bbox Rectangle r = randomRectangle(20); JtsSpatialContext ctxJts = (JtsSpatialContext) ctx; JtsGeometry rPoly = ctxJts.makeShape(ctxJts.getGeometryFrom(r), false, false); assertEquals(r.getArea(null), rPoly.getArea(null), 0.0); assertEquals(r.getArea(ctx), rPoly.getArea(ctx), 0.000001);//same since fills 100% assertEquals(1300, POLY_SHAPE.getArea(null), 0.0); //fills 27% assertEquals(0.27, POLY_SHAPE.getArea(ctx) / POLY_SHAPE.getBoundingBox().getArea(ctx), 0.009); assertTrue(POLY_SHAPE.getBoundingBox().getArea(ctx) > POLY_SHAPE.getArea(ctx)); } @Test @Repeat(iterations = 100) public void testPointAndRectIntersect() { Rectangle r = randomRectangle(5); assertJtsConsistentRelate(r); assertJtsConsistentRelate(r.getCenter()); } @Test public void testRegressions() { assertJtsConsistentRelate(new PointImpl(-10, 4, ctx));//PointImpl not JtsPoint, and CONTAINS assertJtsConsistentRelate(new PointImpl(-15, -10, ctx));//point on boundary assertJtsConsistentRelate(ctx.makeRectangle(135, 180, -10, 10));//180 edge-case } @Test public void testWidthGreaterThan180() throws ParseException { //does NOT cross the dateline but is a wide shape >180 JtsGeometry jtsGeo = (JtsGeometry) ctx.readShapeFromWkt("POLYGON((-161 49, 0 49, 20 49, 20 89.1, 0 89.1, -161 89.2, -161 49))"); assertEquals(161+20,jtsGeo.getBoundingBox().getWidth(), 0.001); //shift it to cross the dateline and check that it's still good jtsGeo = shiftPoly(jtsGeo, 180); assertEquals(161+20,jtsGeo.getBoundingBox().getWidth(), 0.001); } private void assertJtsConsistentRelate(Shape shape) { IntersectionMatrix expectedM = POLY_SHAPE.getGeom().relate(((JtsSpatialContext) ctx).getGeometryFrom(shape)); SpatialRelation expectedSR = JtsGeometry.intersectionMatrixToSpatialRelation(expectedM); //JTS considers a point on a boundary INTERSECTS, not CONTAINS if (expectedSR == SpatialRelation.INTERSECTS && shape instanceof Point) expectedSR = SpatialRelation.CONTAINS; assertRelation(null, expectedSR, POLY_SHAPE, shape); if (ctx.isGeo()) { //shift shape, set to shape2 Shape shape2; if (shape instanceof Rectangle) { Rectangle r = (Rectangle) shape; shape2 = makeNormRect(r.getMinX() + DL_SHIFT, r.getMaxX() + DL_SHIFT, r.getMinY(), r.getMaxY()); } else if (shape instanceof Point) { Point p = (Point) shape; shape2 = ctx.makePoint(normX(p.getX() + DL_SHIFT), p.getY()); } else { throw new RuntimeException(""+shape); } assertRelation(null, expectedSR, POLY_SHAPE_DL, shape2); } } @Test public void testRussia() throws IOException, ParseException { final String wktStr = readFirstLineFromRsrc("/russia.wkt.txt"); //Russia exercises JtsGeometry fairly well because of these characteristics: // * a MultiPolygon // * crosses the dateline // * has coordinates needing normalization (longitude +180.000xxx) //TODO THE RUSSIA TEST DATA SET APPEARS CORRUPT // But this test "works" anyhow, and exercises a ton. //Unexplained holes revealed via KML export: // TODO Test contains: 64°12'44.82"N 61°29'5.20"E // 64.21245 61.48475 // FAILS //assertRelation(null,SpatialRelation.CONTAINS, shape, ctx.makePoint(61.48, 64.21)); JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.normWrapLongitude = true; JtsSpatialContext ctx = factory.newSpatialContext(); Shape shape = ctx.readShapeFromWkt(wktStr); //System.out.println("Russia Area: "+shape.getArea(ctx)); } @Test public void testFiji() throws IOException, ParseException { //Fiji is a group of islands crossing the dateline. String wktStr = readFirstLineFromRsrc("/fiji.wkt.txt"); JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.normWrapLongitude = true; JtsSpatialContext ctx = factory.newSpatialContext(); Shape shape = ctx.readShapeFromWkt(wktStr); assertRelation(null,SpatialRelation.CONTAINS, shape, ctx.makePoint(-179.99,-16.9)); assertRelation(null,SpatialRelation.CONTAINS, shape, ctx.makePoint(+179.99,-16.9)); assertTrue(shape.getBoundingBox().getWidth() < 5);//smart bbox System.out.println("Fiji Area: "+shape.getArea(ctx)); } private String readFirstLineFromRsrc(String wktRsrcPath) throws IOException { InputStream is = getClass().getResourceAsStream(wktRsrcPath); assertNotNull(is); try { BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8")); return br.readLine(); } finally { is.close(); } } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/shape/RandomizedShapeTest.java000066400000000000000000000235361241767633500277050ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape; import com.carrotsearch.randomizedtesting.RandomizedTest; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.distance.DistanceUtils; import com.spatial4j.core.shape.impl.Range; import static com.spatial4j.core.shape.SpatialRelation.CONTAINS; import static com.spatial4j.core.shape.SpatialRelation.WITHIN; /** * A base test class with utility methods to help test shapes. * Extends from RandomizedTest. */ public abstract class RandomizedShapeTest extends RandomizedTest { protected static final double EPS = 10e-9; protected SpatialContext ctx;//needs to be set ASAP /** Used to reduce the space of numbers to increase the likelihood that * random numbers become equivalent, and thus trigger different code paths. * Also makes some random shapes easier to manually examine. */ protected final double DIVISIBLE = 2;// even coordinates; (not always used) protected RandomizedShapeTest() { } public RandomizedShapeTest(SpatialContext ctx) { this.ctx = ctx; } public static void checkShapesImplementEquals( Class[] classes ) { for( Class clazz : classes ) { try { clazz.getDeclaredMethod( "equals", Object.class ); } catch (Exception e) { fail("Shape needs to define 'equals' : " + clazz.getName()); } try { clazz.getDeclaredMethod( "hashCode" ); } catch (Exception e) { fail("Shape needs to define 'hashCode' : " + clazz.getName()); } } } /** * BUG FIX: https://github.com/carrotsearch/randomizedtesting/issues/131 * * Returns a random value greater or equal to min. The value * picked is affected by {@link #isNightly()} and {@link #multiplier()}. * * @see #scaledRandomIntBetween(int, int) */ public static int atLeast(int min) { if (min < 0) throw new IllegalArgumentException("atLeast requires non-negative argument: " + min); min = (int) Math.min(min, (isNightly() ? 3 * min : min) * multiplier()); int max = (int) Math.min(Integer.MAX_VALUE, (long) min + (min / 2)); return randomIntBetween(min, max); } //These few norm methods normalize the arguments for creating a shape to // account for the dateline. Some tests loop past the dateline or have offsets // that go past it and it's easier to have them coded that way and correct for // it here. These norm methods should be used when needed, not frivolously. protected double normX(double x) { return ctx.isGeo() ? DistanceUtils.normLonDEG(x) : x; } protected double normY(double y) { return ctx.isGeo() ? DistanceUtils.normLatDEG(y) : y; } protected Rectangle makeNormRect(double minX, double maxX, double minY, double maxY) { if (ctx.isGeo()) { if (Math.abs(maxX - minX) >= 360) { minX = -180; maxX = 180; } else { minX = DistanceUtils.normLonDEG(minX); maxX = DistanceUtils.normLonDEG(maxX); } } else { if (maxX < minX) { double t = minX; minX = maxX; maxX = t; } minX = boundX(minX, ctx.getWorldBounds()); maxX = boundX(maxX, ctx.getWorldBounds()); } if (maxY < minY) { double t = minY; minY = maxY; maxY = t; } minY = boundY(minY, ctx.getWorldBounds()); maxY = boundY(maxY, ctx.getWorldBounds()); return ctx.makeRectangle(minX, maxX, minY, maxY); } public static double divisible(double v, double divisible) { return (int) (Math.round(v / divisible) * divisible); } protected double divisible(double v) { return divisible(v, DIVISIBLE); } /** reset()'s p, and confines to world bounds. Might not be divisible if * the world bound isn't divisible too. */ protected Point divisible(Point p) { Rectangle bounds = ctx.getWorldBounds(); double newX = boundX( divisible(p.getX()), bounds ); double newY = boundY( divisible(p.getY()), bounds ); p.reset(newX, newY); return p; } static double boundX(double i, Rectangle bounds) { return bound(i, bounds.getMinX(), bounds.getMaxX()); } static double boundY(double i, Rectangle bounds) { return bound(i, bounds.getMinY(), bounds.getMaxY()); } static double bound(double i, double min, double max) { if (i < min) return min; if (i > max) return max; return i; } protected void assertRelation(SpatialRelation expected, Shape a, Shape b) { assertRelation(null, expected, a, b); } protected void assertRelation(String msg, SpatialRelation expected, Shape a, Shape b) { _assertIntersect(msg, expected, a, b); //check flipped a & b w/ transpose(), while we're at it _assertIntersect(msg, expected.transpose(), b, a); } private void _assertIntersect(String msg, SpatialRelation expected, Shape a, Shape b) { SpatialRelation sect = a.relate(b); if (sect == expected) return; msg = ((msg == null) ? "" : msg+"\r") + a +" intersect "+b; if (expected == WITHIN || expected == CONTAINS) { if (a.getClass().equals(b.getClass())) // they are the same shape type assertEquals(msg,a,b); else { //they are effectively points or lines that are the same location assertTrue(msg,!a.hasArea()); assertTrue(msg,!b.hasArea()); Rectangle aBBox = a.getBoundingBox(); Rectangle bBBox = b.getBoundingBox(); if (aBBox.getHeight() == 0 && bBBox.getHeight() == 0 && (aBBox.getMaxY() == 90 && bBBox.getMaxY() == 90 || aBBox.getMinY() == -90 && bBBox.getMinY() == -90)) ;//== a point at the pole else assertEquals(msg, aBBox, bBBox); } } else { assertEquals(msg,expected,sect);//always fails } } protected void assertEqualsRatio(String msg, double expected, double actual) { double delta = Math.abs(actual - expected); double base = Math.min(actual, expected); double deltaRatio = base==0 ? delta : Math.min(delta,delta / base); assertEquals(msg,0,deltaRatio, EPS); } protected int randomIntBetweenDivisible(int start, int end) { return randomIntBetweenDivisible(start, end, (int)DIVISIBLE); } /** Returns a random integer between [start, end]. Integers between must be divisible by the 3rd argument. */ protected int randomIntBetweenDivisible(int start, int end, int divisible) { // DWS: I tested this int divisStart = (int) Math.ceil( (start+1) / (double)divisible ); int divisEnd = (int) Math.floor( (end-1) / (double)divisible ); int divisRange = Math.max(0,divisEnd - divisStart + 1); int r = randomInt(1 + divisRange);//remember that '0' is counted if (r == 0) return start; if (r == 1) return end; return (r-2 + divisStart)*divisible; } protected Rectangle randomRectangle(Point nearP) { Rectangle bounds = ctx.getWorldBounds(); if (nearP == null) nearP = randomPointIn(bounds); Range xRange = randomRange(rarely() ? 0 : nearP.getX(), Range.xRange(bounds, ctx)); Range yRange = randomRange(rarely() ? 0 : nearP.getY(), Range.yRange(bounds, ctx)); return makeNormRect( divisible(xRange.getMin()), divisible(xRange.getMax()), divisible(yRange.getMin()), divisible(yRange.getMax()) ); } private Range randomRange(double near, Range bounds) { double mid = near + randomGaussian() * bounds.getWidth() / 6; double width = Math.abs(randomGaussian()) * bounds.getWidth() / 6;//1/3rd return new Range(mid - width / 2, mid + width / 2); } private double randomGaussianZeroTo(double max) { if (max == 0) return max; assert max > 0; double r; do { r = Math.abs(randomGaussian()) * (max * 0.50); } while (r > max); return r; } protected Rectangle randomRectangle(int divisible) { double rX = randomIntBetweenDivisible(-180, 180, divisible); double rW = randomIntBetweenDivisible(0, 360, divisible); double rY1 = randomIntBetweenDivisible(-90, 90, divisible); double rY2 = randomIntBetweenDivisible(-90, 90, divisible); double rYmin = Math.min(rY1,rY2); double rYmax = Math.max(rY1,rY2); if (rW > 0 && rX == 180) rX = -180; return makeNormRect(rX, rX + rW, rYmin, rYmax); } protected Point randomPoint() { return randomPointIn(ctx.getWorldBounds()); } protected Point randomPointIn(Circle c) { double d = c.getRadius() * randomDouble(); double angleDEG = 360 * randomDouble(); Point p = ctx.getDistCalc().pointOnBearing(c.getCenter(), d, angleDEG, ctx, null); assertEquals(CONTAINS,c.relate(p)); return p; } protected Point randomPointIn(Rectangle r) { double x = r.getMinX() + randomDouble()*r.getWidth(); double y = r.getMinY() + randomDouble()*r.getHeight(); x = normX(x); y = normY(y); Point p = ctx.makePoint(x,y); assertEquals(CONTAINS,r.relate(p)); return p; } protected Point randomPointIn(Shape shape) { if (!shape.hasArea())// or try the center? throw new UnsupportedOperationException("Need area to define shape!"); Rectangle bbox = shape.getBoundingBox(); Point p; do { p = randomPointIn(bbox); } while (!bbox.relate(p).intersects()); return p; } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/shape/RectIntersectionTestHelper.java000066400000000000000000000132731241767633500312510ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape; import com.spatial4j.core.TestLog; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.impl.InfBufLine; import com.spatial4j.core.shape.impl.PointImpl; import static com.spatial4j.core.shape.SpatialRelation.CONTAINS; import static com.spatial4j.core.shape.SpatialRelation.DISJOINT; public abstract class RectIntersectionTestHelper extends RandomizedShapeTest { public RectIntersectionTestHelper(SpatialContext ctx) { super(ctx); } protected abstract S generateRandomShape(Point nearP); protected abstract Point randomPointInEmptyShape(S shape); @SuppressWarnings("unchecked") @Override protected Point randomPointIn(Shape shape) { if (!shape.hasArea()) return randomPointInEmptyShape((S) shape); return super.randomPointIn(shape); } public void testRelateWithRectangle() { //counters for the different intersection cases int i_C = 0, i_I = 0, i_W = 0, i_D = 0, i_bboxD = 0; int laps = 0; final int MINLAPSPERCASE = atLeast(20); while(i_C < MINLAPSPERCASE || i_I < MINLAPSPERCASE || i_W < MINLAPSPERCASE || i_D < MINLAPSPERCASE || i_bboxD < MINLAPSPERCASE) { laps++; TestLog.clear(); Point nearP = randomPointIn(ctx.getWorldBounds()); S s = generateRandomShape(nearP); Rectangle r = randomRectangle(s.getBoundingBox().getCenter()); SpatialRelation ic = s.relate(r); TestLog.log("S-R Rel: {}, Shape {}, Rectangle {}", ic, s, r); try { switch (ic) { case CONTAINS: i_C++; for (int j = 0; j < atLeast(10); j++) { Point p = randomPointIn(r); assertRelation(null, CONTAINS, s, p); } break; case WITHIN: i_W++; for (int j = 0; j < atLeast(10); j++) { Point p = randomPointIn(s); assertRelation(null, CONTAINS, r, p); } break; case DISJOINT: if (!s.getBoundingBox().relate(r).intersects()) {//bboxes are disjoint i_bboxD++; if (i_bboxD > MINLAPSPERCASE) break; } else { i_D++; } for (int j = 0; j < atLeast(10); j++) { Point p = randomPointIn(r); assertRelation(null, DISJOINT, s, p); } break; case INTERSECTS: i_I++; SpatialRelation pointR = null;//set once Rectangle randomPointSpace = null; int MAX_TRIES = 1000; for (int j = 0; j < MAX_TRIES; j++) { Point p; if (j < 4) { p = new PointImpl(0, 0, ctx); InfBufLine.cornerByQuadrant(r, j + 1, p); } else { if (randomPointSpace == null) { if (pointR == DISJOINT) { randomPointSpace = intersectRects(r,s.getBoundingBox()); } else {//CONTAINS randomPointSpace = r; } } p = randomPointIn(randomPointSpace); } SpatialRelation pointRNew = s.relate(p); if (pointR == null) { pointR = pointRNew; } else if (pointR != pointRNew) { break; } else if (j >= MAX_TRIES) { //TODO consider logging instead of failing fail("Tried intersection brute-force too many times without success"); } } break; default: fail(""+ic); } } catch (AssertionError e) { onAssertFail(e, s, r, ic); } if (laps > MINLAPSPERCASE * 1000) fail("Did not find enough intersection cases in a reasonable number" + " of random attempts. CWIDbD: "+i_C+","+i_W+","+i_I+","+i_D+","+i_bboxD + " Laps exceeded "+MINLAPSPERCASE * 1000); } System.out.println("Laps: "+laps + " CWIDbD: "+i_C+","+i_W+","+i_I+","+i_D+","+i_bboxD); } protected void onAssertFail(AssertionError e, S s, Rectangle r, SpatialRelation ic) { throw e; } private Rectangle intersectRects(Rectangle r1, Rectangle r2) { assert r1.relate(r2).intersects(); final double minX, maxX; if (r1.relateXRange(r2.getMinX(),r2.getMinX()).intersects()) { minX = r2.getMinX(); } else { minX = r1.getMinX(); } if (r1.relateXRange(r2.getMaxX(),r2.getMaxX()).intersects()) { maxX = r2.getMaxX(); } else { maxX = r1.getMaxX(); } final double minY, maxY; if (r1.relateYRange(r2.getMinY(),r2.getMinY()).intersects()) { minY = r2.getMinY(); } else { minY = r1.getMinY(); } if (r1.relateYRange(r2.getMaxY(),r2.getMaxY()).intersects()) { maxY = r2.getMaxY(); } else { maxY = r1.getMaxY(); } return ctx.makeRectangle(minX, maxX, minY, maxY); } }spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/shape/RoundingDistCalc.java000066400000000000000000000027531241767633500271620ustar00rootroot00000000000000package com.spatial4j.core.shape; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.distance.AbstractDistanceCalculator; import com.spatial4j.core.distance.DistanceCalculator; /** Ameliorates some random tests cases in which shapes barely tough or barely not * touch. */ class RoundingDistCalc extends AbstractDistanceCalculator { DistanceCalculator delegate; RoundingDistCalc(DistanceCalculator delegate) { this.delegate = delegate; } double round(double val) { final double scale = Math.pow(10,10/*digits precision*/); return Math.round(val * scale) / scale; } @Override public double distance(Point from, double toX, double toY) { return round(delegate.distance(from, toX, toY)); } @Override public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse) { return delegate.pointOnBearing(from, distDEG, bearingDEG, ctx, reuse); } @Override public Rectangle calcBoxByDistFromPt(Point from, double distDEG, SpatialContext ctx, Rectangle reuse) { return delegate.calcBoxByDistFromPt(from, distDEG, ctx, reuse); } @Override public double calcBoxByDistFromPt_yHorizAxisDEG(Point from, double distDEG, SpatialContext ctx) { return delegate.calcBoxByDistFromPt_yHorizAxisDEG(from, distDEG, ctx); } @Override public double area(Rectangle rect) { return delegate.area(rect); } @Override public double area(Circle circle) { return delegate.area(circle); } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/shape/ShapeCollectionTest.java000066400000000000000000000075471241767633500277100ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape; import com.spatial4j.core.TestLog; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.context.SpatialContextFactory; import com.spatial4j.core.shape.impl.Range; import com.spatial4j.core.shape.impl.RectangleImpl; import org.junit.Rule; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static com.spatial4j.core.shape.SpatialRelation.CONTAINS; /** @author David Smiley - dsmiley@mitre.org */ public class ShapeCollectionTest extends RandomizedShapeTest { @Rule public final TestLog testLog = TestLog.instance; @Test public void testBbox() { validateWorld(-180, 180, -180, 180); validateWorld(-180, 0, 0, +180); validateWorld(-90, +90, +90, -90); } private void validateWorld(double r1MinX, double r1MaxX, double r2MinX, double r2MaxX) { ctx = SpatialContext.GEO; Rectangle r1 = ctx.makeRectangle(r1MinX, r1MaxX, -10, 10); Rectangle r2 = ctx.makeRectangle(r2MinX, r2MaxX, -10, 10); ShapeCollection s = new ShapeCollection(Arrays.asList(r1,r2), ctx); assertEquals(Range.LongitudeRange.WORLD_180E180W, new Range.LongitudeRange(s.getBoundingBox())); //flip r1, r2 order s = new ShapeCollection(Arrays.asList(r2,r1), ctx); assertEquals(Range.LongitudeRange.WORLD_180E180W, new Range.LongitudeRange(s.getBoundingBox())); } @Test public void testRectIntersect() { SpatialContext ctx = new SpatialContextFactory() {{geo = false; worldBounds = new RectangleImpl(-100, 100, -50, 50, null);}}.newSpatialContext(); new ShapeCollectionRectIntersectionTestHelper(ctx).testRelateWithRectangle(); } @Test public void testGeoRectIntersect() { ctx = SpatialContext.GEO; new ShapeCollectionRectIntersectionTestHelper(ctx).testRelateWithRectangle(); } private class ShapeCollectionRectIntersectionTestHelper extends RectIntersectionTestHelper { private ShapeCollectionRectIntersectionTestHelper(SpatialContext ctx) { super(ctx); } @Override protected ShapeCollection generateRandomShape(Point nearP) { testLog.log("Break on nearP.toString(): {}", nearP); List shapes = new ArrayList(); int count = randomIntBetween(1,4); for(int i = 0; i < count; i++) { //1st 2 are near nearP, the others are anywhere shapes.add(randomRectangle( i < 2 ? nearP : null)); } ShapeCollection shapeCollection = new ShapeCollection(shapes, ctx); //test shapeCollection.getBoundingBox(); Rectangle msBbox = shapeCollection.getBoundingBox(); if (shapes.size() == 1) { assertEquals(shapes.get(0), msBbox.getBoundingBox()); } else { for (Rectangle shape : shapes) { assertRelation("bbox contains shape", CONTAINS, msBbox, shape); } } return shapeCollection; } protected Point randomPointInEmptyShape(ShapeCollection shape) { Rectangle r = (Rectangle) shape.getShapes().get(0); return randomPointIn(r); } } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/shape/TestShapes2D.java000066400000000000000000000142201241767633500262270ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.context.SpatialContextFactory; import com.spatial4j.core.context.jts.JtsSpatialContextFactory; import com.spatial4j.core.exception.InvalidShapeException; import com.spatial4j.core.shape.impl.BufferedLine; import com.spatial4j.core.shape.impl.BufferedLineString; import com.spatial4j.core.shape.impl.CircleImpl; import com.spatial4j.core.shape.impl.PointImpl; import com.spatial4j.core.shape.impl.RectangleImpl; import org.junit.Test; import java.util.ArrayList; import java.util.Collections; import java.util.List; import static com.spatial4j.core.shape.SpatialRelation.CONTAINS; import static com.spatial4j.core.shape.SpatialRelation.DISJOINT; import static com.spatial4j.core.shape.SpatialRelation.INTERSECTS; public class TestShapes2D extends AbstractTestShapes { @ParametersFactory public static Iterable parameters() { final Rectangle WB = new RectangleImpl(-2000, 2000, -300, 300, null);//whatever List ctxs = new ArrayList(); ctxs.add($(new SpatialContextFactory() {{geo = false; worldBounds = WB;}}.newSpatialContext())); ctxs.add($(new JtsSpatialContextFactory() {{geo = false; worldBounds = WB;}}.newSpatialContext())); return ctxs; } public TestShapes2D(SpatialContext ctx) { super(ctx); } @Test public void testSimplePoint() { try { ctx.makePoint(2001,0); fail(); } catch (InvalidShapeException e) {} try { ctx.makePoint(0, -301); fail(); } catch (InvalidShapeException e) {} Point pt = ctx.makePoint(0,0); String msg = pt.toString(); //test equals & hashcode Point pt2 = ctx.makePoint(0,0); assertEquals(msg, pt, pt2); assertEquals(msg, pt.hashCode(), pt2.hashCode()); assertFalse(msg,pt.hasArea()); assertEquals(msg,pt.getCenter(),pt); Rectangle bbox = pt.getBoundingBox(); assertFalse(msg,bbox.hasArea()); assertEquals(msg,pt,bbox.getCenter()); assertRelation(msg, CONTAINS, pt, pt2); assertRelation(msg, DISJOINT, pt, ctx.makePoint(0, 1)); assertRelation(msg, DISJOINT, pt, ctx.makePoint(1, 0)); assertRelation(msg, DISJOINT, pt, ctx.makePoint(1, 1)); pt.reset(1, 2); assertEquals(ctx.makePoint(1, 2), pt); assertEquals(ctx.makeCircle(pt, 3), pt.getBuffered(3, ctx)); testEmptiness(ctx.makePoint(Double.NaN, Double.NaN)); } @Test public void testSimpleRectangle() { double v = 2001 * (randomBoolean() ? -1 : 1); try { ctx.makeRectangle(v,0,0,0); fail(); } catch (InvalidShapeException e) {} try { ctx.makeRectangle(0,v,0,0); fail(); } catch (InvalidShapeException e) {} try { ctx.makeRectangle(0,0,v,0); fail(); } catch (InvalidShapeException e) {} try { ctx.makeRectangle(0,0,0,v); fail(); } catch (InvalidShapeException e) {} try { ctx.makeRectangle(0,0,10,-10); fail(); } catch (InvalidShapeException e) {} try { ctx.makeRectangle(10,-10,0,0); fail(); } catch (InvalidShapeException e) {} double[] minXs = new double[]{-1000,-360,-180,-20,0,20,180,1000}; for (double minX : minXs) { double[] widths = new double[]{0,10,180,360,400}; for (double width : widths) { testRectangle(minX, width, 0, 0); testRectangle(minX, width, -10, 10); testRectangle(minX, width, 5, 10); } } Rectangle r = ctx.makeRectangle(0, 0, 0, 0); r.reset(1, 2, 3, 4); assertEquals(ctx.makeRectangle(1, 2, 3, 4), r); testRectIntersect(); if (!ctx.isGeo()) assertEquals(ctx.makeRectangle(0.9, 2.1, 2.9, 4.1), ctx.makeRectangle(1, 2, 3, 4).getBuffered(0.1, ctx)); testEmptiness(ctx.makeRectangle(Double.NaN, Double.NaN, Double.NaN, Double.NaN)); } @Test public void testSimpleCircle() { double[] theXs = new double[]{-10,0,10}; for (double x : theXs) { double[] theYs = new double[]{-20,0,20}; for (double y : theYs) { testCircle(x, y, 0); testCircle(x, y, 5); } } testCircleReset(ctx); //INTERSECTION: //Start with some static tests that have shown to cause failures at some point: assertEquals("getX not getY",INTERSECTS,ctx.makeCircle(107,-81,147).relate(ctx.makeRectangle(92, 121, -89, 74))); testCircleIntersect(); assertEquals(ctx.makeCircle(1, 2, 10), ctx.makeCircle(1, 2, 6).getBuffered(4, ctx)); testEmptiness(ctx.makeCircle(Double.NaN, Double.NaN, randomBoolean() ? 0 : Double.NaN)); } static void testCircleReset(SpatialContext ctx) { Circle c = ctx.makeCircle(3, 4, 5); Circle c2 = ctx.makeCircle(5, 6, 7); c2.reset(3,4,5);// to c1 assertEquals(c, c2); assertEquals(c.getBoundingBox(), c2.getBoundingBox()); } @Test public void testBufferedLineString() { //see BufferedLineStringTest & BufferedLineTest for more testEmptiness(ctx.makeBufferedLineString(Collections.emptyList(), randomInt(3))); } /** We have this test here but we'll add geo shapes as needed. */ @Test public void testImplementsEqualsAndHash() throws Exception { checkShapesImplementEquals( new Class[] { PointImpl.class, CircleImpl.class, //GeoCircle.class no: its fields are caches, not part of its identity RectangleImpl.class, ShapeCollection.class, BufferedLineString.class, BufferedLine.class }); } } spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/shape/TestShapesGeo.java000066400000000000000000000244321241767633500265020ustar00rootroot00000000000000/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.spatial4j.core.shape; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.context.SpatialContextFactory; import com.spatial4j.core.context.jts.JtsSpatialContextFactory; import com.spatial4j.core.distance.DistanceCalculator; import com.spatial4j.core.distance.DistanceUtils; import com.spatial4j.core.distance.GeodesicSphereDistCalc; import com.spatial4j.core.exception.InvalidShapeException; import org.junit.Test; import java.util.Arrays; import static com.spatial4j.core.shape.SpatialRelation.CONTAINS; import static com.spatial4j.core.shape.SpatialRelation.DISJOINT; import static com.spatial4j.core.shape.SpatialRelation.INTERSECTS; import static com.spatial4j.core.shape.SpatialRelation.WITHIN; public class TestShapesGeo extends AbstractTestShapes { @ParametersFactory public static Iterable parameters() { //TODO ENABLE LawOfCosines WHEN WORKING //DistanceCalculator distCalcL = new GeodesicSphereDistCalc.Haversine(units.earthRadius());//default final DistanceCalculator distCalcH = new GeodesicSphereDistCalc.Haversine();//default final DistanceCalculator distCalcV = new GeodesicSphereDistCalc.Vincenty(); return Arrays.asList($$( $(new SpatialContextFactory(){{geo = true; distCalc = new RoundingDistCalc(distCalcH);}}.newSpatialContext()), $(new SpatialContextFactory(){{geo = true; distCalc = new RoundingDistCalc(distCalcV);}}.newSpatialContext()), $(new JtsSpatialContextFactory(){{geo = true; distCalc = new RoundingDistCalc(distCalcH);}}.newSpatialContext())) ); } public TestShapesGeo(SpatialContext ctx) { super(ctx); } private static double degToKm(double deg) { return DistanceUtils.degrees2Dist(deg, DistanceUtils.EARTH_MEAN_RADIUS_KM); } private static double kmToDeg(double km) { return DistanceUtils.dist2Degrees(km, DistanceUtils.EARTH_MEAN_RADIUS_KM); } @Test public void testGeoRectangle() { double v = 200 * (randomBoolean() ? -1 : 1); try { ctx.makeRectangle(v,0,0,0); fail(); } catch (InvalidShapeException e) {} try { ctx.makeRectangle(0,v,0,0); fail(); } catch (InvalidShapeException e) {} try { ctx.makeRectangle(0,0,v,0); fail(); } catch (InvalidShapeException e) {} try { ctx.makeRectangle(0,0,0,v); fail(); } catch (InvalidShapeException e) {} try { ctx.makeRectangle(0,0,10,-10); fail(); } catch (InvalidShapeException e) {} //test some relateXRange // opposite +/- 180 assertEquals(INTERSECTS, ctx.makeRectangle(170, 180, 0, 0).relateXRange(-180, -170)); assertEquals(INTERSECTS, ctx.makeRectangle(-90, -45, 0, 0).relateXRange(-45, -135)); assertEquals(CONTAINS, ctx.getWorldBounds().relateXRange(-90, -135)); //point on edge at dateline using opposite +/- 180 assertEquals(CONTAINS, ctx.makeRectangle(170, 180, 0, 0).relate(ctx.makePoint(-180, 0))); //test 180 becomes -180 for non-zero width rectangle assertEquals(ctx.makeRectangle(-180, -170, 0, 0),ctx.makeRectangle(180, -170, 0, 0)); assertEquals(ctx.makeRectangle(170, 180, 0, 0),ctx.makeRectangle(170, -180, 0, 0)); double[] lons = new double[]{0,45,160,180,-45,-175, -180};//minX for (double lon : lons) { double[] lonWs = new double[]{0,20,180,200,355, 360};//width for (double lonW : lonWs) { if (lonW == 360 && lon != -180) continue; testRectangle(lon, lonW, 0, 0); testRectangle(lon, lonW, -10, 10); testRectangle(lon, lonW, 80, 10);//polar cap testRectangle(lon, lonW, -90, 180);//full lat range } } TestShapes2D.testCircleReset(ctx); //Test geo rectangle intersections testRectIntersect(); //Test buffer assertEquals(ctx.makeRectangle(-10, 10, -10, 10), ctx.makeRectangle(0, 0, 0, 0).getBuffered(10, ctx)); for (int i = 0; i < atLeast(100); i++) { Rectangle r = randomRectangle(1); int buf = randomIntBetween(0, 90); Rectangle br = (Rectangle) r.getBuffered(buf, ctx); assertRelation(null, CONTAINS, br, r); if (r.getWidth() + 2 * buf >= 360) assertEquals(360, br.getWidth(), 0.0); else assertTrue(br.getWidth() - r.getWidth() >= 2 * buf); //TODO test more thoroughly; we don't check that we over-buf } assertTrue(ctx.makeRectangle(0, 10, 0, 89).getBuffered(0.5, ctx).getBoundingBox().getWidth() > 11); } @Test public void testGeoCircle() { assertEquals("Circle(Pt(x=10.0,y=20.0), d=30.0° 3335.85km)", ctx.makeCircle(10,20,30).toString()); double v = 200 * (randomBoolean() ? -1 : 1); try { ctx.makeCircle(v,0,5); fail(); } catch (InvalidShapeException e) {} try { ctx.makeCircle(0, v, 5); fail(); } catch (InvalidShapeException e) {} // try { ctx.makeCircle(randomIntBetween(-180,180), randomIntBetween(-90,90), v); fail(); } // catch (InvalidShapeException e) {} //--Start with some static tests that once failed: //Bug: numeric edge at pole, fails to init ctx.makeCircle(110, -12, 90 + 12); //Bug: horizXAxis not in enclosing rectangle, assertion ctx.makeCircle(-44,16,106); ctx.makeCircle(-36,-76,14); ctx.makeCircle(107,82,172); //TODO need to update this test to be valid // { // //Bug in which distance was being confused as being in the same coordinate system as x,y. // double distDeltaToPole = 0.001;//1m // double distDeltaToPoleDEG = ctx.getDistCalc().distanceToDegrees(distDeltaToPole); // double dist = 1;//1km // double distDEG = ctx.getDistCalc().distanceToDegrees(dist); // Circle c = ctx.makeCircle(0,90-distDeltaToPoleDEG-distDEG,dist); // Rectangle cBBox = c.getBoundingBox(); // Rectangle r = ctx.makeRect(cBBox.getMaxX()*0.99,cBBox.getMaxX()+1,c.getCenter().getY(),c.getCenter().getY()); // assertEquals(INTERSECTS,c.getBoundingBox().relate(r)); // assertEquals("dist != xy space",INTERSECTS,c.relate(r));//once failed here // } //These two are related to a circle being on-edge with another shape //assertEquals("?", INTERSECTS, ctx.makeCircle(156, -70, 20).relate(ctx.makeRectangle(-62, -52, -90, -90))); //Pt(x=-52.24150368914137,y=-90.0) //assertEquals("?", DISJOINT, ctx.makeCircle(156, -70, 20).relate(ctx.makePoint(-52, -90)));//pt.x != c.x //What is the "correct" result? Add a DistUtils edge condition check to return a nibble // when dist 0 and points not the same? No; we cancel the assertion failure // if the circle touches the rect edge in onAssertFail() instead. //assertEquals("0 radius at pole", DISJOINT, ctx.makeCircle(-98, 90, 0).relate(ctx.makePoint(-144,90))); assertEquals("bad proportion logic", INTERSECTS, ctx.makeCircle(64, -70, 18).relate(ctx.makeRectangle(46, 116, -86, -62))); assertEquals("Both touch pole", INTERSECTS, ctx.makeCircle(-90, 30, 60).relate(ctx.makeRectangle(-24, -16, 14, 90))); assertEquals("Spherical cap should contain enclosed band", CONTAINS, ctx.makeCircle(0, -90, 30).relate(ctx.makeRectangle(-180, 180, -90, -80))); assertEquals("touches pole", INTERSECTS, ctx.makeCircle(0, -88, 2).relate(ctx.makeRectangle(40,60,-90,-86))); assertEquals("wrong farthest opp corner", INTERSECTS, ctx.makeCircle(92, 36, 46).relate(ctx.makeRectangle(134,136,32,80))); assertEquals("edge rounding issue 2", INTERSECTS, ctx.makeCircle(84, -40, 136).relate(ctx.makeRectangle(-150, -80, 34, 84))); assertEquals("edge rounding issue", CONTAINS, ctx.makeCircle(0, 66, 156).relate(ctx.makePoint(0, -90))); assertEquals("nudge back circle", CONTAINS, ctx.makeCircle(-150, -90, 122).relate(ctx.makeRectangle(0, -132, 32, 32))); assertEquals("wrong estimate", DISJOINT,ctx.makeCircle(-166,59,kmToDeg(5226.2)).relate(ctx.makeRectangle(36, 66, 23, 23))); assertEquals("bad CONTAINS (dateline)",INTERSECTS,ctx.makeCircle(56,-50,kmToDeg(12231.5)).relate(ctx.makeRectangle(108, 26, 39, 48))); assertEquals("bad CONTAINS (backwrap2)",INTERSECTS, ctx.makeCircle(112,-3,91).relate(ctx.makeRectangle(-163, 29, -38, 10))); assertEquals("bad CONTAINS (r x-wrap)",INTERSECTS, ctx.makeCircle(-139,47,80).relate(ctx.makeRectangle(-180, 180, -3, 12))); assertEquals("bad CONTAINS (pwrap)",INTERSECTS, ctx.makeCircle(-139,47,80).relate(ctx.makeRectangle(-180, 179, -3, 12))); assertEquals("no-dist 1",WITHIN, ctx.makeCircle(135,21,0).relate(ctx.makeRectangle(-103, -154, -47, 52))); assertEquals("bbox <= >= -90 bug",CONTAINS, ctx.makeCircle(-64,-84,124).relate(ctx.makeRectangle(-96, 96, -10, -10))); //The horizontal axis line of a geo circle doesn't necessarily pass through c's ctr. assertEquals("c's horiz axis doesn't pass through ctr",INTERSECTS, ctx.makeCircle(71,-44,40).relate(ctx.makeRectangle(15, 27, -62, -34))); assertEquals("pole boundary",INTERSECTS, ctx.makeCircle(-100,-12,102).relate(ctx.makeRectangle(143, 175, 4, 32))); assertEquals("full circle assert",CONTAINS, ctx.makeCircle(-64,32,180).relate(ctx.makeRectangle(47, 47, -14, 90))); //--Now proceed with systematic testing: assertEquals(ctx.getWorldBounds(), ctx.makeCircle(0,0,180).getBoundingBox()); //assertEquals(ctx.makeCircle(0,0,180/2 - 500).getBoundingBox()); double[] theXs = new double[]{-180,-45,90}; for (double x : theXs) { double[] theYs = new double[]{-90,-45,0,45,90}; for (double y : theYs) { testCircle(x, y, 0); testCircle(x, y, kmToDeg(500)); testCircle(x, y, 90); testCircle(x, y, 180); } } testCircleIntersect(); } } spatial4j-0.4-0.4.1/src/test/resources/000077500000000000000000000000001241767633500175045ustar00rootroot00000000000000spatial4j-0.4-0.4.1/src/test/resources/fiji.wkt.txt000066400000000000000000000222731241767633500220000ustar00rootroot00000000000000MULTIPOLYGON (((177.47301833292272 -18.16278193309637, 177.33150033279094 -18.11763593305433, 177.29913633276078 -18.078608933017975, 177.25915433272354 -17.99833593294322, 177.24856333271367 -17.959581932907128, 177.25804533272253 -17.872081932825637, 177.28088133274377 -17.854172932808964, 177.37133633282804 -17.81249993277015, 177.43178133288433 -17.74986393271182, 177.42247233287566 -17.68700893265327, 177.38873633284425 -17.665281932633036, 177.38664533284225 -17.642081932611433, 177.51469133296155 -17.50667293248533, 177.62466333306395 -17.444717932427622, 177.8242003332498 -17.363754932352222, 177.83447233325933 -17.383890932370974, 177.89776333331827 -17.407772932393215, 177.95141833336828 -17.41020893239549, 178.1246633335296 -17.35777293234665, 178.17053633357233 -17.34249993233243, 178.18691833358758 -17.323335932314578, 178.1908003335912 -17.302499932295177, 178.21747233361606 -17.309999932302162, 178.2419003336388 -17.324717932315863, 178.27024533366517 -17.34639093233605, 178.2804543336747 -17.38721793237407, 178.30276333369545 -17.430281932414175, 178.37246333376038 -17.47499993245583, 178.59468133396734 -17.63944593260898, 178.60093633397315 -17.66429993263212, 178.5969093339694 -17.688054932654254, 178.59774533397018 -17.784172932743772, 178.62079133399163 -17.89389093284595, 178.6933003340592 -18.024999932968058, 178.6948363340606 -18.052081932993275, 178.6795633340464 -18.07625493301579, 178.6611003340292 -18.089717933028325, 178.58829133396137 -18.135272933070752, 178.55809133393325 -18.107217933044623, 178.53220033390915 -18.09222693303066, 178.4468813338297 -18.151417933085796, 178.39831833378446 -18.103890933041527, 178.3437093337336 -18.118054933054722, 178.27331833366804 -18.147499933082145, 178.24356333364034 -18.18860893312042, 178.22497233362304 -18.191526933123143, 178.1944273335946 -18.21778193314759, 178.1821913335832 -18.234163933162847, 178.1638633335661 -18.251245933178765, 178.01913633343133 -18.267781933194158, 177.96023633337649 -18.268335933194678, 177.86413633328698 -18.263890933190538, 177.61357233305364 -18.17736393310996, 177.5160813329628 -18.15305493308732, 177.47301833292272 -18.16278193309637)), ((178.7471913341094 -17.011945932024574, 178.71829133408244 -17.009445932022246, 178.69433633406015 -16.996945932010604, 178.68052733404727 -16.967499931983184, 178.67080933403827 -16.921663931940486, 178.62497233399557 -16.8355549318603, 178.5476093339235 -16.811390931837792, 178.52165433389933 -16.816254931842323, 178.49217233387185 -16.804445931831324, 178.47787233385856 -16.765763931795306, 178.52831833390553 -16.706663931740252, 178.59774533397018 -16.64278193168076, 178.6987183340642 -16.67402693170986, 178.74105433410364 -16.63159993167035, 178.77525433413552 -16.600417931641303, 178.8269093341836 -16.57471793161737, 178.8632813342175 -16.566663931609867, 178.89456333424664 -16.55083593159513, 178.9274003342772 -16.50923593155639, 178.98190033432797 -16.469717931519583, 179.2308543345598 -16.40221793145672, 179.2985813346229 -16.412781931466554, 179.33190933465391 -16.413054931466817, 179.40302733472015 -16.40603593146028, 179.39166333470956 -16.38194593143784, 179.4013633347186 -16.349163931407304, 179.46329133477627 -16.29222693135428, 179.5033273348136 -16.273890931337206, 179.67276333497136 -16.22833593129478, 179.77080933506272 -16.203608931271745, 179.8421913351292 -16.20749993127538, 179.87106333515607 -16.205835931273825, 179.91552733519745 -16.17916393124898, 179.96316333524186 -16.153472931225053, 180.00000033527613 -16.15472693122622, 180.00000033527613 -16.172745931243014, 179.9862093352633 -16.179581931249373, 179.95800933523702 -16.197499931266066, 179.89499133517836 -16.240972931306544, 179.85302733513925 -16.291108931353236, 179.80969133509893 -16.36083593141818, 179.74885433504227 -16.450063931501276, 179.6819183349799 -16.49139093153977, 179.65304533495305 -16.50166393154933, 179.59164533489582 -16.539717931584775, 179.48149133479325 -16.696945931731207, 179.49300933480396 -16.761672931791495, 179.54302733485054 -16.764863931794466, 179.56413633487023 -16.76083593179071, 179.61605433491854 -16.7205549317532, 179.6430363349437 -16.690835931725516, 179.71469133501046 -16.624999931664206, 179.7519183350451 -16.5913909316329, 179.81301833510202 -16.54055493158556, 179.85244533513873 -16.508890931556067, 179.938645335219 -16.46794593151793, 179.9490633352287 -16.513754931560598, 179.92538133520668 -16.534445931579867, 179.90109133518405 -16.577226931619705, 179.87439133515915 -16.63666393167506, 179.88093633516525 -16.668126931704364, 179.94620933522606 -16.745626931776542, 179.90011833518315 -16.770145931799377, 179.86126333514693 -16.765763931795306, 179.8447003351315 -16.743890931774928, 179.8105183350997 -16.734717931766383, 179.77887233507022 -16.727499931759667, 179.74633633503993 -16.72639093175863, 179.70996333500602 -16.729717931761726, 179.66275433496207 -16.73971793177104, 179.57162733487718 -16.80110893182821, 179.40942733472616 -16.810835931837275, 179.33190933465391 -16.802226931829253, 179.34440933466556 -16.765835931795365, 179.3538543346744 -16.744926931775893, 179.27046333459674 -16.691317931725962, 179.206363334537 -16.70527293173896, 179.1683093345016 -16.72416393175655, 179.04878133439024 -16.805345931832164, 179.01990933436338 -16.89492693191559, 179.03733633437957 -16.879372931901102, 179.07142733441134 -16.889308931910364, 179.0213633343647 -16.92625493194477, 178.95288133430097 -16.898335931918766, 178.9441183342928 -16.879163931900905, 178.90664533425786 -16.85499993187841, 178.8696913342235 -16.86083593188384, 178.8106723341685 -16.920072931939004, 178.8096913341676 -16.949717931966617, 178.7921913341513 -16.987217932001542, 178.75638133411792 -17.010417932023145, 178.7471913341094 -17.011945932024574)), ((178.09411833350117 -19.16278193402769, 178.08550933349312 -19.158335934023555, 178.03580933344688 -19.147917934013847, 178.01580933342825 -19.15944593402459, 178.00177233341515 -19.162635934027563, 177.95830033337467 -19.14194593400829, 177.95218133336897 -19.129581933996775, 177.96997233338556 -19.104172933973118, 178.0685723334774 -19.066108933937656, 178.1713543335731 -18.999999933876097, 178.17927233358046 -18.984926933862056, 178.30691833369934 -18.93555493381608, 178.34205433373205 -18.926945933808057, 178.37219133376016 -18.931663933812445, 178.4682913338496 -18.95639093383548, 178.49432733387385 -18.974790933852617, 178.49829133387755 -18.989999933866784, 178.49801833387733 -19.00944593388489, 178.48635433386647 -19.018890933893687, 178.46551833384706 -19.033890933907657, 178.4201183338048 -19.046254933919172, 178.37718133376478 -19.05222693392473, 178.33955433372972 -19.050690933923306, 178.328036333719 -19.027781933901963, 178.32711833371815 -19.006108933881777, 178.3052543336978 -18.99916393387531, 178.18997233359045 -19.047499933920335, 178.17137233357312 -19.072917933943998, 178.16982733357167 -19.09110893396094, 178.13610933354028 -19.13778193400441, 178.09411833350117 -19.16278193402769)), ((-180 -16.78736393181542, -179.93859999994282 -16.715863931748828, -179.9295359999344 -16.71011793174347, -179.89245499989985 -16.68739993172231, -179.86486399987413 -16.67989093171532, -179.8531179998632 -16.692781931727325, -179.8211089998334 -16.781090931809572, -179.9021909999089 -16.8755089318975, -179.93588199994028 -16.89969993192004, -179.98758199998844 -16.94696393196405, -179.99331799999376 -16.95527293197179, -180 -16.965726931981536, -180 -16.78736393181542)), ((179.34164533466299 -18.12249993305886, 179.33609133465785 -18.121108933057556, 179.2457913345737 -18.036390932978662, 179.24300933457113 -18.025281932968312, 179.23635433456496 -17.968326932915275, 179.2432813345714 -17.95110893289923, 179.26999133459628 -17.935835932885013, 179.29623633462074 -17.9395819328885, 179.3080183346317 -17.948335932896654, 179.35426333467478 -18.011526932955505, 179.36773633468732 -18.089717933028325, 179.35858133467877 -18.11360893305057, 179.35190033467256 -18.121108933057556, 179.34164533466299 -18.12249993305886)), ((180.00000033527613 -16.965726931981536, 179.98468133526188 -16.98305493199767, 179.95745433523655 -16.998608932012147, 179.94748133522722 -17.002781932016035, 179.92913633521016 -17.00610893201913, 179.91858133520032 -16.999163932012664, 179.8848453351689 -16.972081931987447, 179.88357233516774 -16.96166393197774, 179.89276333517626 -16.946108931963252, 179.90441833518713 -16.92971793194799, 179.93219133521302 -16.900835931921094, 179.94692733522675 -16.88694593190816, 179.9541273352334 -16.879999931901693, 179.95940933523832 -16.871108931893403, 179.96884533524712 -16.852499931876082, 180.00000033527613 -16.787390931815438, 180.00000033527613 -16.965726931981536)), ((178.77190933413237 -17.75444593271608, 178.74774533410988 -17.719717932683736, 178.7441093341065 -17.672499932639766, 178.74969133411173 -17.653472932622037, 178.75859133412 -17.64110893261052, 178.77804533413814 -17.62860893259888, 178.79192733415107 -17.621108932591895, 178.8134453341711 -17.62097293259177, 178.83136333418776 -17.62666393259707, 178.85025433420537 -17.65860893262682, 178.85302733420792 -17.66971793263717, 178.85357233420848 -17.688326932654505, 178.8533003342082 -17.704999932670034, 178.84970033420484 -17.715272932679596, 178.83773633419372 -17.731945932695126, 178.82025433417743 -17.742772932705208, 178.77190933413237 -17.75444593271608))) spatial4j-0.4-0.4.1/src/test/resources/russia.wkt.txt000066400000000000000000001113461241767633500223650ustar00rootroot00000000000000MULTIPOLYGON (((137.26720929547815 53.60971813374698, 47.76081821211881 41.196582122186356, 27.348436193108284 57.58898213745297, 180.00000033527613 68.98010014806175, 137.26720929547815 53.60971813374698)), ((-173.191408993659 64.25442714366065, -180 65.0689091444192, -180 68.98010014806175, -169.70717299041405 66.12664514540427, -172.80264499329695 65.67470014498338, -173.191408993659 64.25442714366065)), ((60.94443622439701 76.062763154658, 67.57026323056778 77.01304515554304, 68.9313632318354 76.78276315532855, 56.749582220490254 73.245263152034, 53.63270021758743 73.75894515251241, 60.94443622439701 76.062763154658)), ((143.43136330121894 46.01943612667799, 141.8194272997177 46.485827127112344, 142.71342730055034 54.42457313450586, 144.74069130243834 48.64530012912351, 143.43136330121894 46.01943612667799)), ((53.51500021747779 71.27470015019878, 55.76305421957147 73.32360915210697, 57.63311822131311 70.72810914968971, 55.14214521899319 70.55568214952913, 53.51500021747779 71.27470015019878)), ((142.4874543003399 74.8116451534928, 139.050536297139 74.64803615334046, 136.8606542950995 75.35206315399611, 138.83218229693563 76.22026315480468, 145.38210930303575 75.51547315414831, 142.4874543003399 74.8116451534928)), ((93.22442725446007 79.43776315780121, 94.97053625608629 80.101091158419, 100.06803626083371 79.77269115811316, 98.5599822594292 78.776091157185, 93.22442725446007 79.43776315780121)), ((96.77470025776654 80.22249115853205, 94.8474912559717 80.140827158456, 93.67831825488281 79.99413615831938, 91.42490925278418 80.31011815861368, 96.77470025776654 80.22249115853205)), ((105.25278226566235 78.47998215690922, 102.71804526330169 78.1599911566112, 99.34137226015696 78.01999115648081, 102.30554526291752 79.42553615778985, 105.25278226566235 78.47998215690922)), ((123.55498228270761 73.20832715199961, 124.35860028345604 73.80359115255399, 126.71089128564677 73.08102715188105, 126.29387228525843 72.8997181517122, 126.16581828513915 72.30192715115547, 124.71831828379106 72.67637315150418, 122.43235428166207 72.97718215178435, 123.55498228270761 73.20832715199961)), ((146.5072633040836 75.5871911542151, 146.9722003045166 75.33831815398332, 150.95303630822406 75.1394361537981, 148.25610030571232 74.78915415347186, 146.0741453036802 75.22372715387661, 146.5072633040836 75.5871911542151)), ((20.942836187142603 55.28720013530926, 22.60290918868867 55.04485413508357, 22.78588218885909 54.363836134449315, 19.797009186075485 54.43754513451796, 20.942836187142603 55.28720013530926)), ((126.77192728570361 73.07638215187671, 127.04165428595485 73.53776315230641, 129.11856328788912 73.09776315189663, 126.33096328529297 72.47790915131935, 126.77192728570361 73.07638215187671)), ((141.16080029910432 73.87733615262266, 142.51302730036366 73.83888215258685, 143.50582730128832 73.23027315202006, 139.65359129770064 73.40220915218018, 141.16080029910432 73.87733615262266)), ((46.69887321112978 80.26610015857267, 49.192218213451895 80.52138215881041, 49.814154214031134 80.89110015915475, 51.74624521583053 80.71512715899087, 46.69887321112978 80.26610015857267)), ((-180 70.99720914994035, -179.6285999996541 71.5771911504805, -177.44154499761726 71.22934515015655, -179.2744449993243 70.90776314985703, -180 70.99720914994035)), ((48.232100212557725 69.08409114815862, 49.00901821328128 69.509709148555, 50.32943621451102 69.12449114819623, 48.785409213073024 68.72303614782237, 48.232100212557725 69.08409114815862)), ((59.31334522287793 80.5414271588291, 59.724709223261044 80.83388215910145, 62.28401822564459 80.77082715904274, 61.06610022451031 80.40359115870072, 59.31334522287793 80.5414271588291)), ((62.557463225899255 80.84411815911099, 64.55026322775518 81.19581815943855, 65.4673452286093 80.92512715918645, 63.212491226509286 80.68165415895967, 62.557463225899255 80.84411815911099)), ((59.91193622343542 69.6663821487009, 59.01777322260267 69.85386314887552, 58.40915422203585 70.25360914924781, 60.54693622402681 69.80248214882766, 59.91193622343542 69.6663821487009)), ((126.67566328561401 72.42893615127375, 127.80053628666161 72.64137315147158, 129.55872728829905 72.22207315108108, 128.76360928755855 72.07415415094331, 126.67566328561401 72.42893615127375)), ((91.08665425246915 80.0486001583701, 90.86442725226215 80.05720915837813, 92.90776325416516 80.02192715834528, 93.80872725500427 79.89179115822407, 91.08665425246915 80.0486001583701)), ((180.00000033527613 71.53585415044199, 180.00000033527613 70.99720914994035, 178.61940033399037 71.0315091499723, 178.87219133422582 71.21748215014549, 180.00000033527613 71.53585415044199)), ((46.08416321055731 80.43691815873177, 44.860000209417194 80.61345415889616, 47.501936211877705 80.85554515912165, 48.764854213053894 80.64942715892968, 46.08416321055731 80.43691815873177)), ((112.78777227267989 74.09193615282254, 112.15941827209468 74.13471815286238, 111.45596327143954 74.32159115303642, 113.41998227326866 74.42499115313271, 112.78777227267989 74.09193615282254)), ((69.87350923271285 73.05055415185268, 70.48553623328286 73.49304515226478, 71.24748223399246 73.45416315222857, 70.9991452337612 73.28888215207462, 71.67776323439318 73.21081815200193, 69.87350923271285 73.05055415185268)), ((169.412718325416 69.76378214879162, 168.86828132490893 69.56776314860906, 167.75191832386923 69.82748214885095, 168.27026332435196 70.02053614903076, 169.412718325416 69.76378214879162)), ((57.635009221314874 80.11051815842777, 56.94638222067354 80.4744271587667, 59.27512722284234 80.33123615863335, 58.37165422200093 80.31387315861716, 57.635009221314874 80.11051815842777)), ((54.429454218329454 81.02415415927865, 56.08970921987569 81.03831815929186, 57.71880922139292 80.79081815906136, 57.017773220740025 80.69442715897159, 56.63166322038043 80.80748215907687, 54.429454218329454 81.02415415927865)), ((146.88302730443354 44.39694512516692, 147.90414530538453 45.40416412610497, 148.85191830626724 45.4777641261735, 147.61245430511292 44.96082712569208, 146.88302730443354 44.39694512516692)), ((55.802845219608514 80.12713615844325, 56.00721821979886 80.33581815863761, 57.130127220844656 80.31248215861586, 57.024436220746225 80.07110915839107, 55.802845219608514 80.12713615844325)), ((76.86827223922722 72.3438451511945, 77.62025423992759 72.63053615146151, 78.39138224064573 72.48595415132684, 77.78248224007865 72.29664515115053, 76.86827223922722 72.3438451511945)), ((163.3855273198027 58.55940013835672, 163.69940032009504 59.01444513878053, 164.70412732103074 59.02470913879009, 164.6513633209816 58.88276313865788, 163.3855273198027 58.55940013835672)), ((140.44860929844106 73.90160915264528, 140.09661829811324 74.18858215291255, 141.11705429906357 74.16498215289056, 141.0205362989737 73.99275415273016, 140.44860929844106 73.90160915264528)), ((155.2261633122037 50.05260013043414, 155.2477453122238 50.30138213066584, 156.11440931303093 50.751100131084684, 155.8927633128245 50.26360913063067, 155.2261633122037 50.05260013043414)), ((137.2213092954354 54.77371813483103, 137.56692729575724 55.18888213521768, 138.2053822963519 55.04069113507967, 137.7071822958879 54.618327134686325, 137.2213092954354 54.77371813483103)), ((55.55633621937895 81.2191631594603, 56.36582722013284 81.38499115961471, 57.902773221564246 81.29026315952649, 56.48304522024202 81.16276315940775, 55.55633621937895 81.2191631594603)), ((78.67571824091056 72.90167315171402, 79.21609124141384 73.09248215189172, 79.58110024175375 72.74733615157027, 78.60470924084444 72.80303615162217, 78.67571824091056 72.90167315171402)), ((52.31891821636387 80.21850915852835, 52.93166321693454 80.40887315870563, 53.86991821780836 80.26110015856801, 53.43193621740045 80.16775415848107, 52.31891821636387 80.21850915852835)), ((91.02970925241613 81.05554515930791, 90.00110025145813 81.09832715934775, 89.89360025135801 81.16859115941318, 91.57720925292602 81.14305415938941, 91.02970925241613 81.05554515930791)), ((60.101091223611576 81.00747315926313, 60.75638222422188 81.10081815935007, 61.65610022505979 81.09387315934359, 61.05249122449763 80.91943615918115, 60.101091223611576 81.00747315926313)), ((56.10450921988948 81.10307315935216, 57.48665422117671 81.04637315929935, 58.278600221914246 80.9197091591814, 57.610273221291834 80.85664515912265, 56.10450921988948 81.10307315935216)), ((135.4475092937834 75.3743731540169, 135.708009294026 75.84999115445984, 136.17845429446413 75.61609115424201, 135.930263294233 75.39610015403713, 135.4475092937834 75.3743731540169)), ((58.08055422172981 81.36638215959738, 57.44137322113451 81.43165415965817, 56.74138222048262 81.44859115967395, 58.57138222218694 81.4090091596371, 58.08055422172981 81.36638215959738)), ((146.16076330376092 44.50661812526906, 146.56802730414017 44.43832712520546, 145.4374723030873 43.71693612453362, 145.40858230306037 43.832354124641114, 146.16076330376092 44.50661812526906)), ((79.21777224141539 80.95468215921397, 79.70860024187249 80.97942715923702, 80.43595424254988 80.93040015919135, 78.97498224118925 80.8336001591012, 79.21777224141539 80.95468215921397)), ((54.00470021793387 80.82664515909471, 55.130818218982654 80.89360015915707, 55.979991219773495 80.7986001590686, 54.841100218712825 80.71970915899513, 54.00470021793387 80.82664515909471)), ((53.00102721699912 70.9771731499217, 52.64500021666754 71.21914515014703, 52.21110921626345 71.31944515024045, 53.20374521718793 71.25387315017937, 53.00102721699912 70.9771731499217)), ((149.47354530684618 45.603318126290446, 149.92913630727048 46.007500126666855, 150.4985633078008 46.192491126839144, 150.15887230748444 45.89999112656673, 149.47354530684618 45.603318126290446)), ((58.061382221711966 81.68776315989669, 57.88985422155221 81.70985415991728, 59.43554522299175 81.8193001600192, 59.15970922273485 81.72886315993497, 58.061382221711966 81.68776315989669)), ((166.24624532246696 55.32962713534877, 166.24858132246914 55.14707313517874, 166.66400032285605 54.677491134741416, 165.83190932208106 55.303318135324275, 166.24624532246696 55.32962713534877)), ((55.50028221932675 80.72330915899849, 56.514163220271 80.77831815904972, 56.94860022067559 80.69274515897001, 55.904991219703646 80.63165415891311, 55.50028221932675 80.72330915899849)), ((62.16192722553089 81.68719115989617, 63.29610922658716 81.7199821599267, 63.80277322705905 81.65331815986463, 63.47082722674989 81.58249115979865, 62.16192722553089 81.68719115989617)), ((127.31817228621236 72.6507091514803, 128.27914528710733 72.78720015160741, 129.34350928809863 72.70400915152993, 129.18691828795278 72.65359115148297, 128.6344272874382 72.70054515152671, 127.31817228621236 72.6507091514803)), ((58.799682222399554 80.00670015833109, 59.45082722300597 80.11499115843193, 59.86054522338756 79.98776315831344, 59.364718222925774 79.91442715824516, 58.799682222399554 80.00670015833109)), ((96.35053625737152 76.09749115469035, 96.1769272572098 76.15220915474131, 95.26360925635925 76.21304515479798, 95.34582725643583 76.2855361548655, 96.33942725736114 76.30386315488255, 96.6451362576459 76.26096315484259, 96.35053625737152 76.09749115469035)), ((69.82313623266595 66.4885271457413, 69.41943623228997 66.76971814600319, 69.12691823201754 66.79192714602388, 69.45776323232565 66.79860014603008, 69.53360023239628 66.71998214595686, 69.64499123250002 66.68719114592633, 70.08526323291005 66.68886314592788, 69.82313623266595 66.4885271457413)), ((96.52085425753012 77.20158215571863, 96.16609125719975 76.98915415552077, 95.23123625632905 76.99665415552775, 95.59304525666602 77.07804515560358, 96.52085425753012 77.20158215571863)), ((57.94438222160298 80.82999115909783, 58.71915422232456 80.89638215915966, 59.025273222609655 80.82249115909084, 57.81874522148598 80.80303615907275, 57.94438222160298 80.82999115909783)), ((50.06637321426601 79.97920015830547, 51.00804521514303 80.09887315841692, 51.501936215602996 79.93165415826118, 50.50971821467891 79.9402631582692, 50.06637321426601 79.97920015830547)), ((161.4227273179747 68.88678214797486, 161.13442731770624 69.08970914816385, 161.09634531767074 69.47053614851853, 161.37524531793048 69.53581814857932, 161.32066331787968 69.23775414830172, 161.5199913180653 68.96693614804951, 161.4227273179747 68.88678214797486)), ((74.09582723664519 73.02692715183068, 74.42720023695381 73.13192715192847, 74.88499123738015 73.08665415188628, 74.96249123745233 73.053036151855, 74.09582723664519 73.02692715183068)), ((76.617845238994 79.54356315789977, 76.17053623857743 79.56441815791919, 76.04400923845958 79.63790915798762, 77.61734523992487 79.50888215786745, 76.617845238994 79.54356315789977)), ((137.96460029612763 71.50796315041603, 137.67690929585967 71.41165415032631, 136.99287229522264 71.51860015042593, 137.75027229592803 71.59443615049656, 137.96460029612763 71.50796315041603)), ((135.41760029375553 74.24774515296764, 136.03414529432973 74.08831815281917, 136.2709632945503 73.93275415267428, 136.0708002943639 73.89694515264094, 135.41760029375553 74.24774515296764)), ((82.15046324414664 75.49759115413164, 82.01193624401765 75.17220915382862, 81.49553624353672 75.35457315399844, 81.7174822437434 75.45193615408914, 82.15046324414664 75.49759115413164)), ((107.43887226769834 78.04942715650824, 106.49304526681743 78.12164515657548, 106.49498226681925 78.15887315661016, 107.69914526794071 78.1352731565882, 107.43887226769834 78.04942715650824)), ((57.8479732215132 81.03920015929268, 58.05110022170237 81.10693615935577, 58.69651822230347 81.0240821592786, 58.399991222027296 80.94609115920596, 57.8479732215132 81.03920015929268)), ((49.8743632140872 80.06366315838414, 49.53220921376854 80.1522091584666, 50.3316632145131 80.1744271584873, 50.06610021426576 80.05914515837992, 49.8743632140872 80.06366315838414)), ((83.06760924500082 70.4162631493993, 83.21415424513731 70.8072091497634, 83.45721824536366 70.74498214970544, 83.3016542452188 70.45637314943664, 83.06760924500082 70.4162631493993)), ((53.83140921777249 80.49997315879048, 54.00750021793647 80.6074821588906, 54.46193621835971 80.47123615876373, 54.22942721814317 80.4191451587152, 53.83140921777249 80.49997315879048)), ((129.14080928790986 72.78166315160226, 128.3269092871518 72.80887315162758, 128.2919912871193 72.86970015168424, 129.29803628805627 72.80026315161956, 129.14080928790986 72.78166315160226)), ((112.5750002724817 76.44192715501114, 112.42470027234174 76.45526315502354, 111.95664527190581 76.59860015515704, 112.50721827241858 76.62747315518394, 112.5750002724817 76.44192715501114)), ((58.461936222085 81.33859115957151, 58.95499122254418 81.40470915963309, 59.380273222940275 81.32124515955536, 58.977763222565386 81.28471815952133, 58.461936222085 81.33859115957151)), ((128.10052728694097 72.63195415146282, 128.68829128748837 72.67220915150031, 128.9722002877528 72.59081815142451, 128.65331828745582 72.52360915136191, 128.10052728694097 72.63195415146282)), ((87.01482724867697 74.9881451536572, 86.8352632485097 74.82638215350653, 86.21110924792845 74.8986091535738, 86.49567224819344 74.97650915364636, 87.01482724867697 74.9881451536572)), ((57.083291220801044 81.18140015942512, 57.8741632215376 81.23720015947708, 58.08055422172981 81.20971815945148, 56.99470922071853 81.16554515941036, 57.083291220801044 81.18140015942512)), ((148.40111830584738 76.6342001551902, 148.74660930616915 76.74581815529416, 149.31441830669797 76.7536001553014, 149.16885430656242 76.65054515520544, 148.40111830584738 76.6342001551902)), ((120.00000027939677 73.03819115184115, 119.8088722792188 73.03414515183738, 119.63261827905461 73.11803615191553, 120.27527227965317 73.09540915189444, 120.00000027939677 73.03819115184115)), ((122.31714528155482 72.94492715175429, 123.18747228236538 72.86804515168271, 123.60179128275121 72.77499115159603, 122.75277228196052 72.82110915163898, 122.31714528155482 72.94492715175429)), ((57.28270922098676 80.61472715889735, 57.41610022111098 80.6419361589227, 58.03500022168737 80.61913615890145, 57.52804522121525 80.54748215883473, 57.28270922098676 80.61472715889735)), ((154.59390031161485 49.29102712972488, 154.7462003117567 49.589018130002415, 154.90441831190407 49.62082713003204, 154.80720931181355 49.300000129733235, 154.59390031161485 49.29102712972488)), ((136.6672632949194 54.90506413495336, 136.80831829505075 55.018054135058605, 137.18788229540428 55.10540913513995, 137.04608229527219 54.91750013496494, 136.6672632949194 54.90506413495336)), ((89.16782725068208 77.16442715568402, 89.14360025065952 77.23803615575258, 89.26304525077074 77.29637315580689, 89.68457225116333 77.28110015579267, 89.16782725068208 77.16442715568402)), ((75.30631823777256 73.4183361521952, 75.6210912380657 73.5499911523178, 76.07485423848829 73.56095415232801, 75.8802632383071 73.45749115223165, 75.30631823777256 73.4183361521952)), ((50.415245214590925 81.03946315929292, 50.51639121468514 81.16192715940699, 50.9823542151191 81.10345415935251, 50.37596321455436 81.02207315927672, 50.415245214590925 81.03946315929292)), ((35.81219120099078 65.18055414452317, 35.74916320093209 64.9650001443224, 35.52137320071992 65.14582714449082, 35.56581820076133 65.17859114452133, 35.81219120099078 65.18055414452317)), ((156.40084531329774 50.62563613096785, 156.18914531310054 50.674436131013294, 156.1752633130876 50.75360913108702, 156.4684453133607 50.86749113119308, 156.40084531329774 50.62563613096785)), ((106.19080926653595 78.18997315663913, 105.99275426635154 78.21415415666164, 106.75985426706592 78.30678215674791, 106.71500026702415 78.2569451567015, 106.19080926653595 78.18997315663913)), ((-172.67599099317897 64.73124514410472, -172.43975499295897 64.8613731442259, -172.1672639927052 64.7724731441431, -172.59057299309944 64.7033091440787, -172.67599099317897 64.73124514410472)), ((85.64029124739682 74.541245153241, 85.38527224715932 74.45110015315703, 85.14512724693566 74.52749115322817, 85.31080924708994 74.58137315327835, 85.64029124739682 74.541245153241)), ((82.82941824477899 74.08363615281479, 83.20277224512671 74.14971815287635, 83.61845424551382 74.08165415281294, 83.4497092453567 74.03776315277207, 82.82941824477899 74.08363615281479)), ((54.89410021876219 80.26275415856955, 55.32860921916685 80.34443615864564, 55.54388221936736 80.29387315859853, 55.12054521897309 80.21831815852818, 54.89410021876219 80.26275415856955)), ((161.3724633179279 69.40585414845827, 161.42843631798002 69.4595731485083, 161.39580931794967 69.58749114862744, 161.6221913181605 69.58859114862847, 161.3724633179279 69.40585414845827)), ((152.20660030939155 47.12501812770762, 152.0156723092137 46.89180012749043, 151.7121913089311 46.80110012740596, 152.22121830940512 47.17319112775249, 152.20660030939155 47.12501812770762)), ((82.89220924483749 75.90942715451521, 82.77249124472598 75.94442715454781, 82.25819124424697 75.96234515456447, 83.29956324521686 75.93803615454186, 82.89220924483749 75.90942715451521)), ((167.43298132357222 54.86307313491426, 167.73358132385215 54.756945134815425, 168.11259132420514 54.50930013458478, 167.5463543236778 54.759164134817496, 167.43298132357222 54.86307313491426)), ((53.21444521719789 80.51574515880517, 53.14138221712986 80.64888215892915, 53.54395421750476 80.52665415881532, 53.428882217397614 80.48165415877341, 53.21444521719789 80.51574515880517)), ((107.35442726761966 77.22886315574402, 107.20310926747874 77.23442715574922, 107.68970026793193 77.26416315577691, 107.56191826781293 77.25499115576835, 107.35442726761966 77.22886315574402)), ((85.85637224759807 74.43970015314642, 85.66095424741604 74.47359115317798, 85.82360924756756 74.570263153268, 86.21262724792984 74.52262715322365, 85.85637224759807 74.43970015314642)), ((40.3485452052156 64.75831814412993, 40.47137320532997 64.5660821439509, 39.976236204868854 64.67941814405646, 40.274991205147074 64.64193614402154, 40.3485452052156 64.75831814412993)), ((91.61886325296479 79.64942715799836, 91.23304525260545 79.69442715804027, 91.12441825250431 79.72249115806639, 91.85845425318792 79.66830915801594, 91.61886325296479 79.64942715799836)), ((67.03288223006732 69.49736314854351, 67.30165423031764 69.59526314863467, 67.36638223037792 69.53970014858294, 67.25249123027183 69.44470014849446, 67.03288223006732 69.49736314854351)), ((96.46408225747723 76.70600915525708, 96.24220025727061 76.6097091551674, 95.89054525694308 76.61790015517502, 95.97330925702016 76.6722091552256, 96.46408225747723 76.70600915525708)), ((96.7588822577518 76.17360015476123, 96.83442725782214 76.34693615492267, 97.07304525804437 76.3030361548818, 96.85831825784442 76.27221815485308, 96.7588822577518 76.17360015476123)), ((59.86172722338864 81.3002631595358, 60.42610022391426 81.29914515953476, 60.63617322410991 81.27069115950826, 59.722209223258716 81.28332715952004, 59.86172722338864 81.3002631595358)), ((54.665591218549366 80.51939115880856, 54.99721821885822 80.56191815884819, 55.16318221901278 80.49776315878842, 54.947491218811905 80.46666315875947, 54.665591218549366 80.51939115880856)), ((76.1843722385903 73.17106315196492, 76.12359123853372 73.20415415199571, 76.73610023910413 73.1505451519458, 76.46887223885528 73.12915415192586, 76.1843722385903 73.17106315196492)), ((79.16440024136568 74.60523615330058, 79.27943624147281 74.65664515334845, 79.61220024178272 74.59498215329103, 79.49860024167691 74.5180541532194, 79.16440024136568 74.60523615330058)), ((97.33728225829049 76.10208215469464, 97.02609125800069 76.00000015459955, 96.6951182576924 76.00749115460653, 96.76693625775931 76.02804515462569, 97.33728225829049 76.10208215469464)), ((82.56470024453245 74.15931815288528, 82.73387224469002 74.09540915282577, 82.31984524430442 74.09360015282408, 82.35165424433404 74.13804515286549, 82.56470024453245 74.15931815288528)), ((82.78259124473539 70.19781814919585, 82.95555424489646 70.24609114924081, 83.11290024504302 70.14026314914224, 82.97249124491225 70.13109114913371, 82.78259124473539 70.19781814919585)), ((146.874936304426 43.86079112466757, 146.78027230433787 43.751936124566214, 146.59731830416746 43.734436124549916, 146.60759130417705 43.80526412461586, 146.874936304426 43.86079112466757)), ((84.69220024651383 74.50274515320513, 84.94220024674667 74.47430015317863, 84.37441824621789 74.45248215315831, 84.43248224627195 74.46693615317179, 84.69220024651383 74.50274515320513)), ((54.45166321835015 80.41543615871174, 54.673045218556325 80.49053615878171, 54.86305421873328 80.45027315874421, 54.37332721827718 80.40304515870022, 54.45166321835015 80.41543615871174)), ((83.97750924584824 74.02604515276116, 84.3966542462386 74.04304515277701, 84.41665424625722 73.9650001527043, 83.8822092457595 74.01012715274635, 83.97750924584824 74.02604515276116)), ((54.17701821809436 80.21856315852841, 54.221100218135405 80.32720915862959, 54.445827218344704 80.28081815858638, 54.421109218321675 80.22499115853438, 54.17701821809436 80.21856315852841)), ((152.5596913097204 76.11975415471107, 152.457318309625 76.15985415474844, 152.803036309947 76.15832715474701, 152.76886330991516 76.11164515470352, 152.5596913097204 76.11975415471107)), ((59.01550922260054 81.21210015945371, 59.68443622322354 81.21026315945198, 59.84277322337101 81.18275415942637, 59.49693622304892 81.17330915941758, 59.01550922260054 81.21210015945371)), ((85.47091824723907 74.81223615349336, 85.70193624745423 74.72553615341263, 85.09707224689089 74.75165415343693, 85.33471824711222 74.77026315345427, 85.47091824723907 74.81223615349336)), ((50.836727214983455 68.4090091475299, 51.16388221528817 68.49525414761021, 51.45665421556083 68.47692714759316, 50.79680021494627 68.3754001474986, 50.836727214983455 68.4090091475299)), ((160.71979131732007 70.81850914977392, 160.4699723170874 70.82388214977891, 160.40720031702892 70.91595414986466, 160.64471831725012 70.89638214984643, 160.71979131732007 70.81850914977392)), ((86.89627224856656 73.69307315245106, 86.73664524841786 73.59526315235996, 86.3949912480997 73.58831815235351, 86.53526324823031 73.64526315240653, 86.89627224856656 73.69307315245106)), ((59.932727223454776 76.13471815472502, 60.04110922355571 76.15693615474572, 60.49471822397817 76.18331815477029, 59.86721822339376 76.10734515469952, 59.932727223454776 76.13471815472502)), ((58.36246322199236 79.93808215826718, 58.664300222273454 79.97581815830233, 58.98610022257316 79.89846315823027, 58.28013622191568 79.92970015825938, 58.36246322199236 79.93808215826718)), ((94.75100925588185 76.25416315483628, 94.99748225611137 76.26914515485021, 94.84027225596498 76.17942715476667, 94.4112272555654 76.20776315479304, 94.75100925588185 76.25416315483628)), ((106.52370926684603 77.38691815589124, 106.73193626703994 77.46638215596522, 106.90416326720032 77.44497315594529, 106.64665426696052 77.37608215588114, 106.52370926684603 77.38691815589124)), ((124.50945428359654 73.83770915258577, 124.37052728346714 73.84498215259254, 124.2894272833916 73.88762715263226, 124.66040028373715 73.894854152639, 124.50945428359654 73.83770915258577)), ((155.44769131241003 50.88000913120473, 155.58026331253348 50.934154131255156, 155.66970931261682 50.85665413118298, 155.55691831251175 50.804164131134115, 155.44769131241003 50.88000913120473)), ((97.51999125846066 76.5808271551405, 97.31749125827207 76.6031821551613, 97.44053625838666 76.71500015526544, 97.51999125846066 76.5808271551405)), ((83.6065092455027 70.45436314943478, 83.5336002454348 70.37720014936292, 83.37970024529147 70.36499114935154, 83.55831824545783 70.52276314949847, 83.6065092455027 70.45436314943478)), ((150.45456330775983 59.017800138783656, 150.65860030794983 59.153882138910376, 150.7474633080326 59.101936138862015, 150.53442730783422 59.001391138768355, 150.45456330775983 59.017800138783656)), ((153.9808093110439 48.734691129206766, 154.1160723111699 48.897773129358626, 154.2298273112758 48.8990181293598, 154.06329131112068 48.742764129214265, 153.9808093110439 48.734691129206766)), ((88.9344002504647 77.1410091556622, 88.78305425032374 77.00499115553552, 88.62720025017859 77.07610015560175, 88.71053625025621 77.1391631556605, 88.9344002504647 77.1410091556622)), ((91.83093625316229 79.41150015777677, 92.28442725358462 79.44941815781209, 92.46748225375512 79.42998215779397, 92.24359125354664 79.3799731577474, 91.83093625316229 79.41150015777677)), ((61.776682225172095 81.60831815982272, 62.12054522549235 81.58442715980047, 62.20138222556764 81.56206315977963, 61.65443622505825 81.60304515981781, 61.776682225172095 81.60831815982272)), ((71.14364523389577 73.33856315212091, 71.24720923399221 73.41110015218845, 71.35914523409647 73.3933271521719, 71.22082723396764 73.28193615206817, 71.14364523389577 73.33856315212091)), ((86.87858224855006 73.62302715238582, 87.17859124882949 73.81441815256409, 87.2544362489001 73.76582715251882, 87.0894272487464 73.6630361524231, 86.87858224855006 73.62302715238582)), ((55.802745219608425 80.41714515871334, 55.87304521967391 80.4352541587302, 56.32513622009495 80.39137315868933, 56.223318220000124 80.3713731586707, 55.802745219608425 80.41714515871334)), ((57.05693622077649 70.50045414947772, 56.91582722064507 70.53776314951244, 56.843182220577404 70.59595414956664, 57.224573220932626 70.52360014949926, 57.05693622077649 70.50045414947772)), ((42.54064520725714 66.79039114602244, 42.661663207369855 66.76914514600264, 42.715545207420035 66.6927451459315, 42.433745207157585 66.7626361459966, 42.54064520725714 66.79039114602244)), ((34.213373199501774 69.40272714845537, 34.40491819968014 69.34380014840048, 33.975818199280525 69.36290014841828, 34.043327199343395 69.39526314844841, 34.213373199501774 69.40272714845537)), ((58.2996912219339 81.59092715980651, 58.37720922200609 81.61192715982608, 58.71728222232281 81.59817315981326, 58.229154221868214 81.57873615979517, 58.2996912219339 81.59092715980651)), ((99.942745260717 79.57887315793263, 99.9422002607165 79.68441815803095, 100.30511826105447 79.66720915801491, 100.02692726079539 79.62803615797844, 99.942745260717 79.57887315793263)), ((57.28720022099094 68.75480914785194, 57.89082722155311 68.81164514790487, 57.92082722158105 68.80331814789713, 57.200000220909715 68.72095414782041, 57.28720022099094 68.75480914785194)), ((95.12408225622926 76.712354155263, 95.32443625641588 76.65707315521149, 94.81470925594118 76.64554515520078, 95.07165425618047 76.67942715523233, 95.12408225622926 76.712354155263)), ((59.24895422281796 69.13730014820817, 59.04527322262828 69.24775414831103, 58.76332722236569 69.33665414839382, 59.19665422276927 69.23095414829538, 59.24895422281796 69.13730014820817)), ((49.35556321360403 80.04783615836939, 49.460545213701806 80.09275415841122, 49.681245213907346 80.03318215835574, 49.43166321367491 80.01527315833908, 49.35556321360403 80.04783615836939)), ((58.793545222393846 75.92140015452637, 59.19777322277031 75.98248215458324, 59.263609222831604 75.96596315456785, 58.69470922230178 75.8994271545059, 58.793545222393846 75.92140015452637)), ((127.40010028628865 73.51777315228782, 127.66220028653277 73.53803615230669, 128.05900928690232 73.48442715225676, 127.6755362865452 73.49775415226915, 127.40010028628865 73.51777315228782)), ((82.16769124416271 77.51580915601127, 82.4438632444199 77.51027315600612, 82.57762724454449 77.47027315596887, 82.12302724412109 77.502627155999, 82.16769124416271 77.51580915601127)), ((55.10307321895681 80.42330915871909, 55.286382219127546 80.45138215874522, 55.364018219199835 80.43163615872683, 54.98054521884271 80.41415415871055, 55.10307321895681 80.42330915871909)), ((126.67330928561182 72.18748215104887, 126.52748228547597 72.29498215114899, 126.53888228548658 72.35220015120228, 126.66360028560274 72.34790015119827, 126.67330928561182 72.18748215104887)), ((76.08143623849446 73.52750915229686, 76.2102632386144 73.55386315232141, 76.65748223903091 73.4794271522521, 76.76421823913034 73.43234515220823, 76.08143623849446 73.52750915229686)), ((92.59763625387637 79.40205415776796, 92.77192725403864 79.43941815780278, 92.98581825423787 79.41360015777872, 92.70664525397785 79.37776315774533, 92.59763625387637 79.40205415776796)), ((115.9172362755944 74.29588215301249, 115.993045275665 74.37469115308588, 116.12720027578996 74.31080915302638, 116.04802727571621 74.28499115300232, 115.9172362755944 74.29588215301249)), ((67.00062723003728 69.39343614844671, 66.93984522998068 69.44692714849654, 67.21748223023923 69.40720914845954, 67.13109123015877 69.36137314841685, 67.00062723003728 69.39343614844671)), ((80.33638224245715 73.50000015227124, 80.13527224226988 73.52499115229452, 80.05663624219665 73.55581815232324, 80.40359124251978 73.54610015231418, 80.33638224245715 73.50000015227124)), ((95.64845425671763 76.67341815522673, 95.83138225688799 76.68178215523452, 95.49720925657681 76.64721815520232, 95.41276325649812 76.69970915525121, 95.64845425671763 76.67341815522673)), ((82.35480924433699 70.9025541498522, 82.34860024433118 70.98830914993206, 82.50305424447504 70.95888214990464, 82.41470024439275 70.87858214982987, 82.35480924433699 70.9025541498522)), ((96.34324525736474 76.91654515545315, 96.2219272572517 76.88581815542454, 95.94274525699171 76.90637315544367, 96.18719125721935 76.93830915547343, 96.34324525736474 76.91654515545315)), ((161.73050031826136 69.55660914859868, 161.65428131819039 69.62914514866623, 161.84720031837003 69.64694514868282, 161.85453631837686 69.58373614862396, 161.73050031826136 69.55660914859868)), ((81.63636324366786 75.92791815453242, 82.25193624424116 75.87691815448494, 82.25401824424313 75.86595415447471, 81.55080924358816 75.9236001545284, 81.63636324366786 75.92791815453242)), ((65.95410022906262 69.09596314816966, 66.23387222932317 69.07443614814963, 66.53762722960607 68.94679114803074, 66.15664522925127 69.07110914814652, 65.95410022906262 69.09596314816966)), ((128.95636328773804 72.90664515171864, 128.7820542875757 72.91248215172408, 129.19537228796065 72.92428215173507, 129.1630272879305 72.91415415172563, 128.95636328773804 72.90664515171864)), ((93.98825425517146 80.00978215833396, 94.27110025543487 80.03610015835847, 94.32332725548355 80.02526315834837, 93.92747225511488 79.96831815829535, 93.98825425517146 80.00978215833396)), ((127.00450028592024 72.00000015087426, 127.1566272860619 71.95582715083313, 127.17406328607814 71.93956315081797, 126.9027722858255 72.02331815089599, 127.00450028592024 72.00000015087426)), ((58.54860922216571 80.61804515890046, 58.80582722240527 80.64694515892737, 58.85527322245133 80.64027315892116, 58.8063822224058 80.58276315886758, 58.54860922216571 80.61804515890046)), ((83.40360924531376 70.51127314948778, 83.36831824528088 70.56776314954038, 83.57290924547141 70.57249114954479, 83.48387224538851 70.48942714946745, 83.40360924531376 70.51127314948778)), ((131.92279129050075 42.99505412386131, 131.82412729040885 42.95332712382245, 131.75332729034295 42.98721812385401, 131.83649129042038 43.06276412392435, 131.92279129050075 42.99505412386131)), ((97.43970025838587 76.73360015528277, 97.35941825831111 76.74941815529752, 97.10512725807428 76.76416315531125, 97.6158182585499 76.77638215532261, 97.43970025838587 76.73360015528277)), ((137.718018295898 54.380882134465196, 137.84023629601182 54.49888213457507, 137.91388229608037 54.507773134583346, 137.83190929600403 54.39249113447599, 137.718018295898 54.380882134465196)), ((58.21588222185585 81.13635415938316, 58.406654222033524 81.15277315939846, 58.64860922225884 81.13499115938188, 58.14943622179396 81.12886315937618, 58.21588222185585 81.13635415938316)), ((86.4676182481673 74.81875415349944, 86.74887224842928 74.79720915347937, 86.78332724846138 74.78081815346411, 86.59999124829062 74.75110015343643, 86.4676182481673 74.81875415349944)), ((83.16314524508977 70.89182714984219, 83.07193624500485 70.93637314988368, 83.05247224498675 70.97692714992147, 83.25708224517729 70.90359114985316, 83.16314524508977 70.89182714984219)), ((91.93165425325611 77.59971815608941, 91.77998225311484 77.62414515611215, 91.73998225307759 77.64360015613028, 92.03770025335484 77.62622715611411, 91.93165425325611 77.59971815608941)), ((113.11876327298813 76.36999115494413, 113.25888227311862 76.43637315500595, 113.44220027328936 76.36387315493843, 113.30692727316335 76.35914515493403, 113.11876327298813 76.36999115494413)), ((85.20121824698788 74.42139115312935, 85.50638224727209 74.43470915314177, 85.58581824734608 74.41776315312597, 85.40470924717744 74.38888215309908, 85.20121824698788 74.42139115312935)), ((140.89553629885728 76.05949115465495, 140.93664529889554 76.13581815472605, 141.07704529902634 76.11248215470431, 141.07885429902802 76.08581815467949, 140.89553629885728 76.05949115465495)), ((138.46884529659724 71.6533181505514, 138.3735632965085 71.68303615057908, 138.72107229683218 71.68637315058217, 138.66330029677835 71.67330915057002, 138.46884529659724 71.6533181505514)), ((-172.75036399324824 64.67144514404902, -172.53225499304511 64.66137314403963, -172.49082699300655 64.63122714401158, -172.62859999313486 64.6172001439985, -172.75036399324824 64.67144514404902)), ((161.69168131822522 70.74540914970584, 161.64859131818508 70.74831814970855, 161.46329131801247 70.80359114976002, 161.65774531819358 70.80859114976468, 161.69168131822522 70.74540914970584)), ((122.01603628127435 72.93196315174222, 122.1774632814247 72.95360015176237, 122.37400928160775 72.89430015170714, 122.32860028156546 72.89193615170495, 122.01603628127435 72.93196315174222)), ((125.79213628479113 73.50661815227741, 125.7083272847131 73.59081815235584, 125.80386328480205 73.63888215240058, 125.8372002848331 73.5252731522948, 125.79213628479113 73.50661815227741)), ((54.25976321817143 81.29170015952784, 54.11596321803751 81.34970915958186, 54.42193621832246 81.27318215951058, 54.31721821822492 81.26860015950632, 54.25976321817143 81.29170015952784)), ((93.9518092551375 76.60981815516749, 94.29414525545633 76.5855361551449, 94.3572092555151 76.57443615513455, 93.87220025506338 76.58915415514824, 93.9518092551375 76.60981815516749)), ((137.64279129582792 54.38745413447131, 137.5496822957412 54.50985413458531, 137.61411829580123 54.56457313463625, 137.64553629583048 54.5075001345831, 137.64279129582792 54.38745413447131)), ((93.73997225494026 78.15138215660318, 93.57221825478399 78.1666541566174, 93.52442725473946 78.20332715665157, 93.64027225484739 78.22276315666966, 93.73997225494026 78.15138215660318)), ((59.90914522343283 80.181627158494, 60.0988732236095 80.20387315851471, 60.28762722378531 80.16984515848304, 60.2302732237319 80.16165415847539, 59.90914522343283 80.181627158494)), ((140.74594529871797 75.65185415427533, 140.5966182985789 75.65220915427565, 140.519436298507 75.70290915432287, 140.76999129874036 75.67720015429893, 140.74594529871797 75.65185415427533)), ((97.85165425876954 76.76610015531304, 97.73997225866555 76.81276315535649, 97.72942725865568 76.81832715536169, 97.91943625883266 76.8383181553803, 97.85165425876954 76.76610015531304)), ((156.49685431338713 77.14706315566784, 156.67746331355534 77.14054515566178, 156.73176331360594 77.1227541556452, 156.4363543133308 77.13206315565387, 156.49685431338713 77.14706315566784)), ((154.46471831149455 49.16791812961023, 154.58303631160476 49.14582712958966, 154.59592731161678 49.10901812955538, 154.4873363115156 49.080827129529126, 154.46471831149455 49.16791812961023)), ((126.49224528544318 73.39206315217072, 126.4680452854206 73.44386315221897, 126.65833628559784 73.45860015223269, 126.61302728555563 73.3994361521776, 126.49224528544318 73.39206315217072)), ((19.65110918593959 54.455827134534985, 19.627254185917366 54.46327313454191, 19.836382186112132 54.600000134669244, 19.830273186106467 54.57277313464388, 19.65110918593959 54.455827134534985)))