pax_global_header00006660000000000000000000000064137575526670014541gustar00rootroot0000000000000052 comment=8704d9ca3e3c96202464b6609028124d59f34c66 spatial4j-spatial4j-0.8/000077500000000000000000000000001375755266700151745ustar00rootroot00000000000000spatial4j-spatial4j-0.8/.gitignore000077500000000000000000000000751375755266700171710ustar00rootroot00000000000000/*.ipr /.idea/ *.iml target/ .classpath .project .settings/ spatial4j-spatial4j-0.8/.travis.yml000066400000000000000000000005771375755266700173160ustar00rootroot00000000000000language: java sudo: false script: mvn -Drandomized.multiplier=10 clean verify jacoco:report jdk: - oraclejdk8 - openjdk8 - openjdk11 #TODO - oraclejdk9 dist: trusty # Travis Xenial doesn't have oraclejdk8 notifications: email: - spatial4j-dev@locationtech.org after_success: - du -hs target/site/jacoco/jacoco.xml - bash <(curl -s https://codecov.io/bash) spatial4j-spatial4j-0.8/CHANGES.md000066400000000000000000000267401375755266700165770ustar00rootroot00000000000000## VERSION 0.8 DATE: _unreleased_ * \#194: Circles that cross a dateline can now be converted to a JTS Geometry. Previous attempts would throw an exception. (Stijn Caerts) * \#194: JtsGeometry now supports input an Geometry that crosses the dateline multiple times (wraps the globe multiple times). Previous attempts would yield erroneous behavior. (Stijn Caerts) * \#188: Upgraded to JTS 1.17.0. This JTS release has a small [API change](https://github.com/locationtech/jts/blob/master/doc/JTS_Version_History.md#api-changes) and it requires Java 1.8. Spatial4J should work fine with older versions still. (Jim Hughes) * \#177: Improve conversion of a Circle to Shape. JtsShapeFactory allows converting from a Shape object to a JTS Geometry object. Geodetic circles now translate to a polygon that has points equidistant from the center. Before the change, there was potentially a large inaccuracy. (Hrishi Bakshi) * \#163: "Empty" points in JTS are now convertible to a Spatial4j Shape instead of throwing an exception. (David Smiley) * \#162: Fixed WKT & GeoJSON \[de\]serialization of "empty" points and geometrycollections. (Jeen Broekstra, David Smiley) * \#165: Added ShapeFactory.pointLatLon convenience method. (MoeweX) * \#167: WKTWriter now has a means to customize the NumberFromat. (MoeweX) * \#175: ShapesAsWKTModule, a Jackson databind module, didn't deserialize WKT inside JSON to a Spatial4j Shape at all. Now it does. It continues to serialize correctly. (David Smiley) ## VERSION 0.7 DATE: 27 December 2017 * \#153: Upgraded to JTS 1.15. This JTS release has an API breakage in that the package root was changed from com.vividsolutions to org.locationtech but should otherwise be compatible. JTS is now dual-licensed as EPL 1.0 and EDL 1.0 (a BSD style license). This JTS release also included various improvements, including faster LineString intersection. (David Smiley) * \#138: Feature: Added integration for the Jackson-databind ("ObjectMapper") serialization library. It's a popular library to serialize/deserialize JSON, XML, and other formats to/from Java objects. Jackson is an optional dependency for Spatial4j that is only required for this feature. (Ryan McKinley) * \#151: Moved application of JtsContext.autoIndex from JtsShapeFactory.makeShapeFromGeometry() to JtsSpatialContext.makeShape is which is more central. (Justin Deoliveira) * \#155: Ensure that JTS Geometry objects passed to Spatial4j are never modified. If Spatial4j wants to do so in order to dateline wrap it, then it'll now be cloned first (at some new cost, which we try to avoid). (David Smiley) * 408c14a7: Bug: JtsShapeFactory.lineString: if useJtsLineString (true by default) and we have a non-0 buffer then a buffer of 0 was applied instead of the intended buffer. (Ryan McKinley) * \#152: Removed test dependency on jeo library; some classes were copied in. (Justin Deoliveira) ## VERSION 0.6 DATE: 26 February 2016 ### Notes: * Package change from com.spatial4j.core to org.locationtech.spatial4j. Also, maven coordinates change from groupId com.spatial4j to org.locationtech.spatial4j. (David Smiley) ### Features: * \#130: New ShapeFactory interface for shape creation. Related methods on SpatialContext are now deprecated; get the ShapeFactory from the SpatialContext. ShapeFactory has builders for Polygon, LineString, MultiShape, MultiPoint, MultiLineString, and MultiPolygon. The ShapeReader formats now use these and thus no longer have hard dependencies on JTS just to create a polygon, although should you need to create a polygon, you still need JTS on the classpath at this time. A new JtsSpatialContextFactory.useJtsMulti option (defaults to true) toggles whether JTS's Multi* implementations are to be used in preference to Spatial4j's native ShapeCollection. (David Smiley, Justin Deoliveira) ### Bugs: * If tried to use an "empty" JTS geometry when geo=false, it would throw an exception. (David Smiley) * \#127: JtsGeometry.relate(Circle) was incorrect. (David Smiley) --------------------------------------- ## VERSION 0.5 DATE: 18 September 2015 ### User/API changes & Notes: * \#96: Java 1.7 is now the minimum version supported by Spatial4j. * Spatial4j supports more formats now; see "FORMATS.md". More info below. * \#107: The DatelineRule and ValidationRule enums were moved to the com.spatial4j.context.jts package, and the current setting is exposed on JtsSpatialContext. The autoIndex boolean was moved there too. These things used to be defined in JtsWktShapeParser. (Justin Deoliveira & David Smiley) * \#92: Shape now exposes the SpatialContext * Given a JTS Geometry instance and the desire to have a Spatial4j equivalent Shape, consider using JtsSpatialContext.createShapeFromGeometry(geometry) vs. makeShape(geometry). See the javadocs. * WktShapeParser was renamed to WKTReader, although WktShapeParser still exists as a deprecated subclass of WKTReader. SpatialContext.getWktShapeParser returns WKTReader. JtsWktShapeParser was renamed to JtsWKTReader. * \#90: Publish the test-jar. (Nick Knize) * The internal class "Range" is deprecated, unused, and will be removed in a future release. ### Features: * \#91: SpatialContext.getFormats() returns a new SupportedFormats which lists the supported Shape formats for readers & writers. (Ryan McKinley) * \#94: WKT writing support. (Ryan McKinley) * \#94: GeoJSON format support; both reading & writing. (Ryan McKinley, Justin Deoliveira) * \#117: Polyshape format support; both reading & writing. (Ryan McKinley, Justin Deoliveira) * \#98: SpatialPredicate class -- moved from Lucene Spatial. (Ryan McKinley) ### Improvements: * \#103: JtsGeometry now accepts GeometryCollection if it's component geometry types are homogeneous. (Justin Deoliveira) note: for non-homogeneous ones, see JtsSpatialContext.createShapeFromGeometry * \#97: More consistent use of ParseException vs InvalidShapeException (Ryan McKinley) ### Bugs: * \#104: DistanceUtils.distHaversineRAD could return NaN given anti-podal points. (David Smiley) * \#77: When ShapeCollection & JtsGeometry computed the bounding box for multiple shapes longitudinally, it could sometimes produce a world-wrap longitude instead of the minimal enclosing span. (David Smiley) * \#86: Rectangle.getBuffered() in geo mode was sometimes slightly off in the southern hemisphere. (David Smiley) * \#85: Vertical line Rectangles at the dateline should relate with another such Rectangle consistently if one is declared at -180 longitude and the other at +180 longitude. Before this fix, INTERSECTS would sometimes be returned instead of CONTAINS or WITHIN more accurately. (David Smiley) --------------------------------------- ## 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-spatial4j-0.8/CONTRIBUTING.md000066400000000000000000000070501375755266700174270ustar00rootroot00000000000000# Contributing to Spatial4j The Spatial4j project is always excited to accept contributions from the community. This document contains some guidelines to help users and developers contribute to the project. - [Code Style](#code) - [Issues and Bugs](#bugs) - [Discussion Forum](#discuss) - [Submitting Patches](#patches) ## Code Style Spatial4j adheres to (as much as possible) the [Google Java Style](https://google.github.io/styleguide/javaguide.html) conventions. If a patch or commit deviates from these guidelines a reviewer will likely ask for it to be reformatted. ## Issues, Bugs, and Feature Requests Spatial4j utilizes Github for issue tracking. Bugs, issues, and feature requests should be filed [here](https://github.com/locationtech/spatial4j/issues). ## Discussion Forum Often communication can be carried out through comments on an issue or pull request directly but for larger discussions that are more general in nature it is recommended that the project [mailing list](https://locationtech.org/mailman/listinfo/spatial4j-dev) be used. ## Submitting Patches The best way to submit a patch or add a new feature to the code is to submit a [ pull request](https://help.github.com/articles/using-pull-requests/). Below are some guidelines to follow when developing code intended to be submitted via pull request. This [guide](http://people.redhat.com/rjones/how-to-supply-code-to-open-source-projects/) contains some useful guidelines for contributing to open source projects in general. Below are some additionally stressed points. ### Send email first It is never a bad idea to email the mailing list with thoughts about a change you intend to make before you make it. This allows the committers to weigh in with thoughts and suggestions that will help you make the change and ultimately ensure your successful contribution to the project. ### Sign off on commits, and filing an ECA Commits that are provided by non-committers must have a Signed-off-by field in the footer indicating that the author is aware of the terms by which the contribution has been provided to the project. This can be added manually, or by using the "-s" (sign-off) flag with git. The following is an example of a commit with the sign-off flag. git commit -s -m "the commit message" Furthermore, contributors must electronically sign the Eclipse Contributor Agreement (ECA). This is a one-time event, and is quick & easy. * http://www.eclipse.org/legal/ECA.php For more information, please see the Eclipse Committer Handbook: https://www.eclipse.org/projects/handbook/#resources-commit ### One patch per one bug/feature Avoid submitting patches that mix together multiple features and/or bug fixes into a single changeset. It is much easier to review and understand a patch that is dedicated to a single purpose. ### No cruft While working on a patch often developers can't resist the urge to reformat code that is unrelated to the patch. This adds unnecessary "noise" that makes the job of the reviewer more difficult. It also makes the history of a change harder to analyze after the fact. If a patch contains unnecessary whitespace or other formatting changes a reviewer will ask for them to be removed. ### Viewing the entire project history (Optional) We recommended that developers do this step to be able to view the pre-LocationTech history of the project. This can be achieved with the following command after the repository has been cloned: git fetch origin refs/replace/*:refs/replace/* spatial4j-spatial4j-0.8/FORMATS.md000066400000000000000000000163671375755266700166460ustar00rootroot00000000000000# Spatial4j Supported Formats Spatial4j supports reading and writing Shapes to and from strings in the following formats: * Well Known Text (WKT) * GeoJSON * Polyshape ## Reader/Writer Api The classes `ShapeReader` and `ShapeWriter` are the interfaces for encoding and decoding shapes respectively. They can be obtained from a `SpatialContext` instance. SpatialContext ctx = ...; ShapeReader shpReader = ctx.getFormats().getReader(ShapeIO.WKT); ShapeWriter shpWriter = ctx.getFormats().getWriter(ShapeIO.WKT); Reading and writing polygons requires a reader/writer obtained from `JtsSpatialContext`. ## Well Known Text Well-Known-Text (WKT) is a simple to read text based format for representing spatial objects. It is defined by the [OGC Simple Feature Specification](http://www.opengeospatial.org/standards/sfa). [Wikipedia's page](https://en.wikipedia.org/wiki/Well-known_text) on it is pretty decent. The following table shows the various shape types encoded as WKT: | Shape | WKT | | ----------------|-------------------------------------------------------| | Point | `POINT(1 2)` | | Rectangle | `ENVELOPE(1, 2, 4, 3)` _(minX, maxX, maxY, minY)_| | Circle | `BUFFER(POINT(-10 30), 5.2)` | | LineString | `LINESTRING(1 2, 3 4)` | | Buffered L/S | `BUFFER(LINESTRING(1 2, 3 4), 0.5)` | | Polygon | `POLYGON ((1 1, 2 1, 2 2, 1 2, 1 1))` | | ShapeCollection | `GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(1 2, 3 4))` | The `ENVELOPE` keyword/syntax was borrowed from OGC's [Common Query Language (CQL)](http://docs.geoserver.org/stable/en/user/tutorials/cql/cql_tutorial.html) standard, a superset of WKT. Note the odd argument order. It's not widely used. Alternatively a rectangular polygon can be used, which will be recognized as-such when it is read and turned into a Rectangle instance. The `BUFFER` keyword is a Spatial4j extension to the WKT spec. It's used to produce a circle by buffering a point, and to buffer shapes generally. The 2nd argument is the buffer distance in degrees. ## GeoJSON GeoJSON is a format for representing geographic objects using JavaScript Object Notation. It is defined by the open standard available at http://geojson.org. The following list shows the various shape types encoded as GeoJSON: * Point { "type": "Point", "coordinates": [1, 2] } * Rectangle { "type": "Polygon", "coordinates": [ [[1,3], [1,4], [2,4], [2,3], [1,3]] ] } * Circle { "type": "Circle", "coordinates": [1, 2], "radius": 111.19508, "properties": { "radius_units": "km" } } * LineString { "type": "LineString", "coordinates": [[1, 2], [3, 4]] } * Buffered LineString { "type": "LineString", "coordinates": [[1, 2], [3, 4]], "buffer": 10 } * Polygon { "type": "Polygon", "coordinates": [ [[1, 1], [2, 1], [2, 2], [1, 2], [1, 1]] ] } * ShapeCollection { "type": "GeometryCollection", "geometries": [ { "type": "Point", "coordinates": [1, 2] }, { "type": "LineString", "coordinates": [ [1, 2], [3, 4] ] } ] } ## Polyshape The Polyshape format is an extension to the [Polyline](https://developers.google.com/maps/documentation/utilities/polylinealgorithm) format from Google to include a wider diversity of shapes. The standard "Encoded Polyline Algorithm Format" algorithm offers a compact way to encode a list of lat/lon pairs into a simple ASCII string. The following table shows the various shape types encoded as Polyshape: | Shape | Polyshape | | ----------------|-------------------------------------------------------| | Point | `0_ibE_seK` | | Rectangle | `5_ibE_}hQ_ibE_ibE` | | Circle | `4(_ibE)_ibE_seK` | | LineString | `1_ibE_seK_seK_seK` | | Buffered L/S | ``1(_c`|@)_ibE_seK_seK_seK`` | | Polygon | `2_ibE_ibE_ibE??_ibE~hbE??~hbE` | | ShapeCollection | `0_ibE_seK 1_ibE_seK_seK_seK` | ### Prefix Key The Polyshape format uses a prefix key to denote the shape type: | Shape | Key | |------------|:---:| | Point | 0 | | LineString | 1 | | Polygon | 2 | | MultiPoint | 3 | | Circle | 4 | | Rectangle | 5 | ### Collections Shape collections are represented as each of the individual shape encodings concatenated with a space. ### Arguments The Polyshape format supports optional arguments to handle things like a Circle (with a radius) and a BufferedLineString (with a buffer size). If the character immediately after the prefix is a `'('`, everything up to the next `')'` is considered an argument to the Shape. ### Polygons with Holes A Polygon with interior rings is represented by appending the encoding of each ring prefixed with a `')'`: '2' + encode(exteriorRing) + ['(' + encode(interiorRing)]* ### Known Limitations - The format is optimized to store lat/lon points, very big or very small values may get lost in the rounding - All values are rounded to: Math.round(value * 1e5) - In the JTS version, a homogeneous ShapeCollection will be read as a MultPoint, MultiLineString, or MultiPolygon ## Benchmarks The following table shows a comparison among the encoded formats in terms of number of bytes in the final encoding. Note that this comparison may be somewhat misleading since the other encoding formats do not implicitly limit precision as Polyshape does. Percentages are calculated relative to the largest encoding. | GeoJSON | WKT | Binary | Polyshape | Shape | |:----------:|:--------:|:---------:|:----------:|-------| | 100% (42) | 38% (16) | 40% (17) | 20% (9) | `POINT(100.1 0.1)` | | 100% (61) | 52% (33) | 67% (41) | 28% (17) | `LINESTRING (100.1 0.1, 101.1 1.1)` | | 100% (96) | 33% (32) | 34% (33) | 18% (17) | `ENVELOPE(100.1, 101.1, 1.1, 0.1)` | | 90% (158) | 69% (122)| %100 (177)| 33% (59) | `POLYGON ((100.1 0.1, 101.1 0.1, 101.1 1.1, 100.1 1.1, 100.1 0.1), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2))` | | 100% (169) | 51% (87) | %52 (88) | 21% (36) | `GEOMETRYCOLLECTION(LINESTRING (100.1 0.1, 101.1 1.1),LINESTRING (102.1 2.1, 103.1 3.1))` | | 100% (131) | 40% (53) | %31 (40) | 15% (20) | `GEOMETRYCOLLECTION(POINT(100.1 0.1),POINT(101.1 1.1))` | With this limited dataset it looks like the Polyshape format is on average ~25% as big as GeoJSON, ~50% as big as WKT, and ~40% as big as the binary encoding. THe data used for this benchmark contains data with low precision numbers (100.1 vs 100.123456) so that may be a low estimate. spatial4j-spatial4j-0.8/README.md000066400000000000000000000223051375755266700164550ustar00rootroot00000000000000# Spatial4j [![Build](https://travis-ci.org/locationtech/spatial4j.svg)](https://travis-ci.org/locationtech/spatial4j) [![Coverage](https://img.shields.io/codecov/c/github/locationtech/spatial4j.svg)](https://codecov.io/github/locationtech/spatial4j/) [![Maven](https://img.shields.io/maven-central/v/org.locationtech.spatial4j/spatial4j.svg)](https://maven-badges.herokuapp.com/maven-central/org.locationtech.spatial4j/spatial4j/) _(note: Spatial4j's official home page is at LocationTech: https://projects.eclipse.org/projects/locationtech.spatial4j but this README has richer information)_ 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 & write shapes from formats like [WKT](http://en.wikipedia.org/wiki/Well-known_text) and [GeoJSON](http://geojson.org/geojson-spec.html#geometry-objects). Spatial4j is a project of the [LocationTech](http://www.locationtech.org) Industry Working Group of the Eclipse Foundation. 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. Spatial4j is well tested; it's monitored via [Travis-CI](https://travis-ci.org/locationtech/spatial4j) continuous integration (plus another Hudson build) and we use [Codecov](https://codecov.io/github/locationtech/spatial4j/) for code coverage. If you are interested in contributing to Spatial4j please review the [contribution guidelines](CONTRIBUTING.md). ## 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. The term "geodetic" or "geodesic" or "geo" is used here 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 * Read and write Shapes as [WKT](http://en.wikipedia.org/wiki/Well-known_text). Include the ENVELOPE extension from CQL, plus a Spatial4j custom BUFFER operation. Buffering a point gets you a Circle. * Read and write Shapes as [GeoJSON](http://geojson.org/geojson-spec.html#geometry-objects). * Read and write Shapes as [Polyshape](FORMATS.md#polyshape). * Read and write Shapes using the [Jackson-databdind](https://github.com/FasterXML/jackson-databind) serialization framework. * 3 great-circle distance calculators: Law of Cosines, Haversine, Vincenty For more information on the formats supported, see [FORMATS.md](FORMATS.md). ## Dependencies Spatial4j runs on Java 8 (v1.8) or better. Otherwise, all dependencies listed in the maven [pom.xml](pom.xml) are either marked optional or are for testing. The optional dependencies are: * [JTS](https://github.com/locationtech/jts): You need JTS if you use polygons, or obviously if you use any of the classes prefixed with "Jts". * [Noggit](https://github.com/yonik/noggit): The Noggit JSON parsing library is only needed for GeoJSON parsing (not required for writing). * [Jackson-databind](https://github.com/FasterXML/jackson-databind): If you wish to use Spatial4j's Jackson-databind feature to read/write shapes. ## 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) and it has no Circle shape. Spatial4j has a geodesic circle implementation, and it wraps JTS geometries to add dateline-wrap support (no pole wrap yet). JTS recently broadened it's licensing but originally this was a major factor contributing to the founding of Spatial4j. 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 (e.g. 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://locationtech.github.io/spatial4j/apidocs/)** The facade to all of Spatial4j is the [`SpatialContext`](https://locationtech.github.io/spatial4j/apidocs/org/locationtech/spatial4j/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://locationtech.github.io/spatial4j/apidocs/org/locationtech/spatial4j/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` which 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://locationtech.github.io/spatial4j/apidocs/org/locationtech/spatial4j/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](https://locationtech.org/mailman/listinfo/spatial4j-dev) (note: old list is [here](http://spatial4j.16575.n6.nabble.com/)). View metadata about the project as generated by Maven: [maven site](https://locationtech.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), such as by using Geo3D * 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). On February 26th 2016, with release 0.6, Spatial4j became a LocationTech project (a part of Eclipse) following a long incubation period. spatial4j-spatial4j-0.8/about.md000066400000000000000000000017721375755266700166370ustar00rootroot00000000000000## About This Content May 22, 2015 ### License The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the Apache License, Version 2.0. A copy of the Apache License, Version 2.0 is available at [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may apply to your use of any object code in the Content. Check the Redistributor’s license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise indicated below, the terms and conditions of the Apache License, Version 2.0 still apply to any source code in the Content and such source code may be obtained at [http://www.eclipse.org](http://www.eclipse.org). spatial4j-spatial4j-0.8/asl-v20.txt000066400000000000000000000261361375755266700171310ustar00rootroot00000000000000 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-spatial4j-0.8/devnotes.md000066400000000000000000000126371375755266700173560ustar00rootroot00000000000000This file has notes for committers. # Making a snapshot release Note: depends on having access to the Sonatype repo described further below mvn deploy -Prelease # Making a new release First, understand that LocationTech projects undergo releases using an official process described here: https://www.eclipse.org/projects/handbook/#release with complete and thorough details here: https://wiki.eclipse.org/Development_Resources/HOWTO/Release_Reviews Note: * See https://projects.eclipse.org/projects/locationtech.spatial4j and "Committer Tools" panel at right, including * "Create a new release" * "Generate IP Log" * References to the "PMC" (Project Management Committee) in Spatial4j's case is the LocationTech Technology PMC. There aren't project-specific PMCs. *TODO distill the process here.* Those steps can be concurrent with following some of the earlier technical steps below. Deploying/releasing any jars must wait until the release date assuming the release review is successful. ## Review files... * Review CHANGES.md — up to date? * Review README.md — up to date? * Review pom — up to date? Run display-plugin-updates & display-dependency-updates. Do *not* remove the SNAPSHOT; that'll be handled later. ## Build, Tag, and Deploy to Sonatype 1. Optional: create a release branch if there will be release-specific changes. Probably not. 2. Use Maven's `release` plugin, but ONLY for the "prepare" step, *not* perform: ``` mvn release:prepare ``` This will create a tag in git named spatial4j-0.6 (or whatever the version is) with the pom updated. If something goes wrong, you'll have to do release:rollback and then possibly remove the tag (pushing to GitHub) so that next time it can succeed. Remove the "release.properties" file and "pom.xml.releaseBackup". Since we won't do release:perform, we can remove these artifacts now. Note that org.locationtech.spatial4j.io.jackson.PackageVersion includes a hard-coded version. It's overwritten in the build process. You should manually update it to the next snapshot release and commit. It's okay that the Maven release plugin, when setting the final version, did so only in the POM but not this souce code file because the jars that get created include the modified file (both compiled and source). mvn release:clean 3. Have LocationTech sign the artifacts (via Hudson) Go to https://ci.locationtech.org/spatial4j/job/Spatial4j-Jarsign2/ and modify the configuration to reference the release tag in the git configuration area. Then execute a build; it should succeed. Download a zip of the build artifacts (pom.xml plus jar files). Do that by seeing the link "Artifact(s) of the Last Successful Build", clicking it, then clicking the link "(all files in zip)". Expand it somewhere. Note that the jar files will contain META-INF/ECLIPSE_* entries with binary signing info. Also, ensure these file names have the right version name and not a -SNAPSHOT. 4. Use GPG to sign the artifacts then deploy to Sonatype You should open a command prompt to the expanded directory of downloaded artifacts from Hudson. There will be a pom.xml and some jars. For reference on what we're about to do, see: http://central.sonatype.org/pages/manual-staging-bundle-creation-and-deployment.html This requires having an account on Sonatype for their repo, plus installing & configuring GPG. I'll assume those steps have been followed. We're going to follow the "bundle creation" method as it's pretty easy and doesn't require special SonaType plugins / configuration; likewise for GPG. #First, rename the pom.xml to include the artifact & version: mv pom.xml target/spatial4j-0.6.pom cd target/ #Generate a signature for each file. gpg has no way to do this at once, so you'll enter your password each time. find . -type f -exec gpg --detach-sign --armor \{} \; #Create a bundle jar jar -cvf ../bundle.jar * Now log into Sonatype and upload the bundle JAR according to the illustrated instructions at the link above. ## Release deployed artifacts to Maven Central http://central.sonatype.org/pages/releasing-the-deployment.html ## Publish the Maven site (includes Javadoc) We publish the Maven "site" HTML on GitHub, and we link to it from the readme and others might too. The site includes the javadoc API. Instructions: http://blog.progs.be/517/publishing-javadoc-to-github-using-maven Summary: First checkout the release tag (e.g. spatial4j-0.5) or modify pom.xml temporarily to have this version. The site reports reference the version, so this is why. mvn clean site mvn scm-publish:publish-scm When site completes, open the target/site/index.html to view it to see if it's reasonable. Then continue to the publish step. The publish step will require your username & password for GitHub. Observe the final published content online: https://locationtech.github.io/spatial4j/ ## Update download.locationtech.org and the CMS Upload the download artifacts to: sftp://download.locationtech.org:/home/httpd/downloads/spatial4j/ Look at the existing file structure and files to see what should be put where. Then update the LocationTech CMS to have an updated download link. Follow this link: https://www.locationtech.org/projects/technology.spatial4j/edit And edit the download URL to be like this (with an appropriate version) http://download.locationtech.org/spatial4j/0_6/?d spatial4j-spatial4j-0.8/eclipse000077500000000000000000000003151375755266700165450ustar00rootroot00000000000000rm */.classpath rm */.project rm */*/.classpath rm */*/.project rm */*/*/.classpath rm */*/*/.project rm */*/*/*/.classpath rm */*/*/*/.project mvn eclipse:eclipse -P updateLucene spatial4j-spatial4j-0.8/notice.md000066400000000000000000000013001375755266700167710ustar00rootroot00000000000000# Notices for Spatial4j This content is produced and maintained by the Eclipse Spatial4j project. * Project home: https://projects.eclipse.org/projects/locationtech.spatial4j ## Trademarks LocationTech Spatial4j, and Spatial4j are trademarks of the Eclipse Foundation. ## Declared Project Licenses This program and the accompanying materials are made available under the terms of the Apache License, Version 2.0 which is available at https://www.apache.org/licenses/LICENSE-2.0. SPDX-License-Identifier: Apache-2.0 ## Source Code The project maintains the following source code repositories: * https://github.com/locationtech/spatial4j ## Third-party Content (none) ## Cryptography (none)spatial4j-spatial4j-0.8/pom.xml000066400000000000000000000375111375755266700165200ustar00rootroot00000000000000 4.0.0 org.locationtech.spatial4j spatial4j 0.8 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 shape formats like WKT and GeoJSON. https://projects.eclipse.org/projects/locationtech.spatial4j LocationTech http://www.locationtech.org/ The Apache Software License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo GitHub https://github.com/locationtech/spatial4j/issues Jenkins https://ci.eclipse.org/spatial4j/job/Spatial4j/ spatial4j-dev http://www.eclipse.org/lists/spatial4j-dev http://spatial4j.16575.x6.nabble.com spatial4j-dev@eclipse.org https://accounts.eclipse.org/mailing-list/spatial4j-dev David Smiley Ryan McKinley Voyager Search Justin Deoliveira Voyager Search scm:git:git@github.com:locationtech/spatial4j.git scm:git:git@github.com:locationtech/spatial4j.git https://github.com/locationtech/spatial4j spatial4j-0.8 UTF-8 1.8 1.8 org.noggit noggit 0.8 true com.fasterxml.jackson.core jackson-databind 2.9.10.5 true org.locationtech.jts jts-core 1.17.0 true junit junit 4.13.1 test org.slf4j slf4j-simple 1.7.25 test com.carrotsearch.randomizedtesting randomizedtesting-runner 2.5.3 test maven-clean-plugin 3.1.0 maven-resources-plugin 3.0.2 maven-compiler-plugin 3.8.0 maven-surefire-plugin 2.22.1 maven-jar-plugin 3.0.2 maven-install-plugin 2.5.2 maven-deploy-plugin 2.8.2 maven-site-plugin 3.7.1 maven-project-info-reports-plugin 3.0.0 maven-source-plugin 3.2.1 maven-javadoc-plugin 3.2.0 maven-gpg-plugin 1.6 org.apache.maven.plugins maven-compiler-plugin true false -Xlint:unchecked org.apache.maven.plugins maven-surefire-plugin de.thetaphi forbiddenapis 3.0.1 check true jdk-system-out jdk-unsafe jdk-deprecated false org.apache.felix maven-bundle-plugin 4.2.1 org.locationtech.spatial4j*;version=${project.version} true org.apache.maven.plugins maven-site-plugin org.jacoco jacoco-maven-plugin 0.8.2 prepare-agent prepare-agent org.apache.maven.plugins maven-scm-publish-plugin 1.1 Publishing maven generated site for ${project.artifactId}:${project.version} ${project.reporting.outputDirectory} true scm:git:https://github.com/locationtech/spatial4j.git gh-pages com.google.code.maven-replacer-plugin replacer 1.5.3 process-packageVersion generate-sources replace ${basedir}/src/main/java/org/locationtech/spatial4j/io/jackson/PackageVersion.java.in ${basedir}/src/main/java/org/locationtech/spatial4j/io/jackson/PackageVersion.java @package@ ${packageVersion.package} @projectversion@ ${project.version} @projectgroupid@ ${project.groupId} @projectartifactid@ ${project.artifactId} org.apache.maven.plugins maven-pmd-plugin 3.5 true 100 org.codehaus.mojo findbugs-maven-plugin 3.0.3 true org.apache.maven.plugins maven-jxr-plugin 2.5 jxr org.apache.maven.plugins maven-surefire-report-plugin 2.18.1 org.apache.maven.plugins maven-javadoc-plugin
Spatial4j, ${project.version}
Spatial4j, ${project.version}
Spatial4j, ${project.version} http://locationtech.github.io/jts/javadoc/ all,-missing
javadoc
release org.apache.maven.plugins maven-source-plugin attach-sources jar-no-fork org.apache.maven.plugins maven-jar-plugin attach-test-sources test-jar org.apache.maven.plugins maven-javadoc-plugin attach-javadocs jar all,-missing org.apache.maven.plugins maven-gpg-plugin sign-artifacts verify sign ossrh https://oss.sonatype.org/content/repositories/snapshots ossrh https://oss.sonatype.org/service/local/staging/deploy/maven2/
spatial4j-spatial4j-0.8/src/000077500000000000000000000000001375755266700157635ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/000077500000000000000000000000001375755266700167075ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/000077500000000000000000000000001375755266700176305ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/000077500000000000000000000000001375755266700204175ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/000077500000000000000000000000001375755266700230735ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/000077500000000000000000000000001375755266700247665ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/SpatialPredicate.java000066400000000000000000000147731375755266700310630ustar00rootroot00000000000000package org.locationtech.spatial4j; /* * 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. */ // NOTE: we keep the header as it came from ASF; it did not originate in Spatial4j import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.SpatialRelation; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; /** * A predicate that compares a stored geometry to a supplied geometry. It's enum-like. For more * explanation of each predicate, consider looking at the source implementation * of {@link #evaluate(org.locationtech.spatial4j.shape.Shape, org.locationtech.spatial4j.shape.Shape)}. It's important * to be aware that Lucene-spatial makes no distinction of shape boundaries, unlike many standardized * definitions. Nor does it make dimensional distinctions (e.g. line vs polygon). * You can lookup a predicate by "Covers" or "Contains", for example, and you will get the * same underlying predicate implementation. * * @see DE-9IM at Wikipedia, based on OGC specs * @see * ESRIs docs on spatial relations */ public abstract class SpatialPredicate implements Serializable { //TODO? Use enum? LUCENE-5771 // Private registry private static final Map registry = new HashMap<>();//has aliases private static final List list = new ArrayList<>(); // Geometry Operations /** Bounding box of the *indexed* shape, then {@link #Intersects}. */ public static final SpatialPredicate BBoxIntersects = new SpatialPredicate("BBoxIntersects") { @Override public boolean evaluate(Shape indexedShape, Shape queryShape) { return indexedShape.getBoundingBox().relate(queryShape).intersects(); } }; /** Bounding box of the *indexed* shape, then {@link #IsWithin}. */ public static final SpatialPredicate BBoxWithin = new SpatialPredicate("BBoxWithin") { { register("BBoxCoveredBy");//alias -- the better name } @Override public boolean evaluate(Shape indexedShape, Shape queryShape) { Rectangle bbox = indexedShape.getBoundingBox(); return bbox.relate(queryShape) == SpatialRelation.WITHIN || bbox.equals(queryShape); } }; /** Meets the "Covers" OGC definition (boundary-neutral). */ public static final SpatialPredicate Contains = new SpatialPredicate("Contains") { { register("Covers");//alias -- the better name } @Override public boolean evaluate(Shape indexedShape, Shape queryShape) { return indexedShape.relate(queryShape) == SpatialRelation.CONTAINS || indexedShape.equals(queryShape); } }; /** Meets the "Intersects" OGC definition. */ public static final SpatialPredicate Intersects = new SpatialPredicate("Intersects") { @Override public boolean evaluate(Shape indexedShape, Shape queryShape) { return indexedShape.relate(queryShape).intersects(); } }; /** Meets the "Equals" OGC definition. */ public static final SpatialPredicate IsEqualTo = new SpatialPredicate("Equals") { { register("IsEqualTo");//alias (deprecated) } @Override public boolean evaluate(Shape indexedShape, Shape queryShape) { return indexedShape.equals(queryShape); } }; /** Meets the "Disjoint" OGC definition. */ public static final SpatialPredicate IsDisjointTo = new SpatialPredicate("Disjoint") { { register("IsDisjointTo");//alias (deprecated) } @Override public boolean evaluate(Shape indexedShape, Shape queryShape) { return ! indexedShape.relate(queryShape).intersects(); } }; /** Meets the "CoveredBy" OGC definition (boundary-neutral). */ public static final SpatialPredicate IsWithin = new SpatialPredicate("Within") { { register("IsWithin");//alias (deprecated) register("CoveredBy");//alias -- the more appropriate name. } @Override public boolean evaluate(Shape indexedShape, Shape queryShape) { return indexedShape.relate(queryShape) == SpatialRelation.WITHIN || indexedShape.equals(queryShape); } }; /** Almost meets the "Overlaps" OGC definition, but boundary-neutral (boundary==interior). */ public static final SpatialPredicate Overlaps = new SpatialPredicate("Overlaps") { @Override public boolean evaluate(Shape indexedShape, Shape queryShape) { return indexedShape.relate(queryShape) == SpatialRelation.INTERSECTS;//not Contains or Within or Disjoint } }; private final String name; protected SpatialPredicate(String name) { this.name = name; register(name); list.add( this ); } protected void register(String name) { registry.put(name, this); registry.put(name.toUpperCase(Locale.ROOT), this); } public static SpatialPredicate get( String v ) { SpatialPredicate op = registry.get( v ); if( op == null ) { op = registry.get(v.toUpperCase(Locale.ROOT)); } if( op == null ) { throw new IllegalArgumentException("Unknown Operation: " + v ); } return op; } public static List values() { return list; } public static boolean is( SpatialPredicate op, SpatialPredicate ... tst ) { for( SpatialPredicate t : tst ) { if( op == t ) { return true; } } return false; } /** * Returns whether the relationship between indexedShape and queryShape is * satisfied by this operation. */ public abstract boolean evaluate(Shape indexedShape, Shape queryShape); public String getName() { return name; } @Override public String toString() { return name; } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/context/000077500000000000000000000000001375755266700264525ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/context/SpatialContext.java000066400000000000000000000251151375755266700322630ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.context; import org.locationtech.spatial4j.distance.CartesianDistCalc; import org.locationtech.spatial4j.distance.DistanceCalculator; import org.locationtech.spatial4j.distance.GeodesicSphereDistCalc; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.io.BinaryCodec; import org.locationtech.spatial4j.io.LegacyShapeWriter; import org.locationtech.spatial4j.io.SupportedFormats; import org.locationtech.spatial4j.io.WKTReader; import org.locationtech.spatial4j.shape.*; import org.locationtech.spatial4j.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 ShapeFactory}, * {@link org.locationtech.spatial4j.io.ShapeIO}. *

* 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 org.locationtech.spatial4j.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 ShapeFactory shapeFactory; private final DistanceCalculator calculator; private final Rectangle worldBounds; private final BinaryCodec binaryCodec; private final SupportedFormats formats; /** * Consider using {@link org.locationtech.spatial4j.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 org.locationtech.spatial4j.context.SpatialContextFactory#newSpatialContext()}. */ public SpatialContext(SpatialContextFactory factory) { this.geo = factory.geo; this.shapeFactory = factory.makeShapeFactory(this); 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.binaryCodec = factory.makeBinaryCodec(this); factory.checkDefaultFormats(); this.formats = factory.makeFormats(this); } /** A factory for {@link Shape}s. */ public ShapeFactory getShapeFactory() { return shapeFactory; } public SupportedFormats getFormats() { return formats; } 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. */ @Deprecated public boolean isNormWrapLongitude() { return shapeFactory.isNormWrapLongitude(); } /** 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 org.locationtech.spatial4j.io.WKTReader} before creating a shape. */ @Deprecated public double normX(double x) { return shapeFactory.normX(x); } /** Normalize the 'y' dimension. Might reduce precision or wrap it to be within the bounds. This * is called by {@link org.locationtech.spatial4j.io.WKTReader} before creating a shape. */ @Deprecated public double normY(double y) { return shapeFactory.normY(y); } /** Ensure fits in {@link #getWorldBounds()}. It's called by any shape factory method that * gets an 'x' dimension. */ @Deprecated public void verifyX(double x) { shapeFactory.verifyX(x); } /** Ensure fits in {@link #getWorldBounds()}. It's called by any shape factory method that * gets a 'y' dimension. */ @Deprecated public void verifyY(double y) { shapeFactory.verifyY(y); } /** Construct a point. */ @Deprecated public Point makePoint(double x, double y) { return shapeFactory.pointXY(x, y); } /** Construct a rectangle. */ @Deprecated public Rectangle makeRectangle(Point lowerLeft, Point upperRight) { return shapeFactory.rect(lowerLeft, upperRight); } /** * Construct a rectangle. If just one longitude is on the dateline (+/- 180) (aka anti-meridian) * then potentially adjust its sign to ensure the rectangle does not cross the * dateline. */ @Deprecated public Rectangle makeRectangle(double minX, double maxX, double minY, double maxY) { return shapeFactory.rect(minX, maxX, minY, maxY); } /** Construct a circle. The units of "distance" should be the same as x & y. */ @Deprecated public Circle makeCircle(double x, double y, double distance) { return shapeFactory.circle(x, y, distance); } /** Construct a circle. The units of "distance" should be the same as x & y. */ @Deprecated public Circle makeCircle(Point point, double distance) { return shapeFactory.circle(point, distance); } /** 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. */ @Deprecated public Shape makeLineString(List points) { return shapeFactory.lineString(points, 0); } /** 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. */ @Deprecated public Shape makeBufferedLineString(List points, double buf) { return shapeFactory.lineString(points, buf); } /** Construct a ShapeCollection, analogous to an OGC GeometryCollection. */ @Deprecated public ShapeCollection makeCollection(List coll) { return shapeFactory.multiShape(coll); } /** The {@link org.locationtech.spatial4j.io.WKTReader} used by {@link #readShapeFromWkt(String)}. */ @Deprecated public WKTReader getWktShapeParser() { return (WKTReader)formats.getWktReader(); } /** Reads a shape from the string formatted in WKT. * @see org.locationtech.spatial4j.io.WKTReader * @param wkt non-null WKT. * @return non-null * @throws ParseException if it failed to parse. */ @Deprecated public Shape readShapeFromWkt(String wkt) throws ParseException, InvalidShapeException { return getWktShapeParser().parse(wkt); } public BinaryCodec getBinaryCodec() { return binaryCodec; } /** * Try to read a shape from any supported formats * * @return shape or null if unable to parse any shape */ @Deprecated public Shape readShape(String value) throws InvalidShapeException { return formats.read(value); } /** Writes the shape to a String using the old/deprecated * {@link org.locationtech.spatial4j.io.LegacyShapeWriter}. 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 LegacyShapeWriter.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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/context/SpatialContextFactory.java000066400000000000000000000275501375755266700336200ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE and VoyagerSearch * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.context; import org.locationtech.spatial4j.distance.CartesianDistCalc; import org.locationtech.spatial4j.distance.DistanceCalculator; import org.locationtech.spatial4j.distance.GeodesicSphereDistCalc; import org.locationtech.spatial4j.io.*; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.ShapeFactory; import org.locationtech.spatial4j.shape.impl.ShapeFactoryImpl; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.*; //import org.slf4j.LoggerFactory; /** * 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
*
org.locationtech.spatial4j.context.SpatialContext or * org.locationtech.spatial4j.context.jts.JtsSpatialContext
*
geo
*
true (default)| false -- see {@link SpatialContext#isGeo()}
*
shapeFactoryClass
*
Java class of the {@link ShapeFactory}.
*
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()}
*
readers
*
Comma separated list of {@link org.locationtech.spatial4j.io.ShapeReader} class names
*
writers
*
Comma separated list of {@link org.locationtech.spatial4j.io.ShapeWriter} class names
*
binaryCodecClass
*
Java class of the {@link org.locationtech.spatial4j.io.BinaryCodec}
*
*/ 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 shapeFactoryClass = ShapeFactoryImpl.class; public Class binaryCodecClass = BinaryCodec.class; public final List> readers = new ArrayList<>(); public final List> writers = new ArrayList<>(); public boolean hasFormatConfig = false; public SpatialContextFactory() { } /** * 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"); initField("shapeFactoryClass"); initCalculator(); //init wktParser before worldBounds because WB needs to be parsed initFormats(); initWorldBounds(); initField("normWrapLongitude"); initField("binaryCodecClass"); } /** Gets {@code name} from args and populates a field by the same name with the value. */ @SuppressWarnings("unchecked") 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); } } /** * Check args for 'readers' and 'writers'. The value should be a comma separated list * of class names. * * The legacy parameter 'wktShapeParserClass' is also supported to add a specific WKT prarser */ protected void initFormats() { try { String val = args.get("readers"); if (val != null) { for (String name : val.split(",")) { readers.add(Class.forName(name.trim(), false, classLoader).asSubclass(ShapeReader.class)); } } else {//deprecated; a parameter from when this was a raw class val = args.get("wktShapeParserClass"); if (val != null) { //LoggerFactory.getLogger(getClass()).warn("Using deprecated argument: wktShapeParserClass={}", val); readers.add(Class.forName(val.trim(), false, classLoader).asSubclass(ShapeReader.class)); } } val = args.get("writers"); if (val != null) { for (String name : val.split(",")) { writers.add(Class.forName(name.trim(), false, classLoader).asSubclass(ShapeWriter.class)); } } } catch (ClassNotFoundException ex) { throw new RuntimeException("Unable to find format class", ex); } } public SupportedFormats makeFormats(SpatialContext ctx) { checkDefaultFormats(); // easy to override List read = new ArrayList<>(readers.size()); for (Class clazz : readers) { try { read.add(makeClassInstance(clazz, ctx, this)); } catch (Exception ex) { throw new RuntimeException(ex); } } List write = new ArrayList<>(writers.size()); for (Class clazz : writers) { try { write.add(makeClassInstance(clazz, ctx, this)); } catch (Exception ex) { throw new RuntimeException(ex); } } return new SupportedFormats( Collections.unmodifiableList(read), Collections.unmodifiableList(write)); } /** * If no formats were defined in the config, this will make sure GeoJSON and WKT are registered */ protected void checkDefaultFormats() { if (readers.isEmpty()) { addReaderIfNoggitExists(GeoJSONReader.class); readers.add(WKTReader.class); readers.add(PolyshapeReader.class); readers.add(LegacyShapeReader.class); } if (writers.isEmpty()) { writers.add(GeoJSONWriter.class); writers.add(WKTWriter.class); writers.add(PolyshapeWriter.class); writers.add(LegacyShapeWriter.class); } } public void addReaderIfNoggitExists(Class reader) { try { if (classLoader==null) { Class.forName("org.noggit.JSONParser"); } else { Class.forName("org.noggit.JSONParser", true, classLoader); } readers.add(reader); } catch (ClassNotFoundException e) { //LoggerFactory.getLogger(getClass()).warn("Unable to support GeoJSON Without Noggit"); } } 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 ShapeFactory makeShapeFactory(SpatialContext ctx) { return makeClassInstance(shapeFactoryClass, ctx, this); } public BinaryCodec makeBinaryCodec(SpatialContext ctx) { return makeClassInstance(binaryCodecClass, ctx, this); } private T makeClassInstance(Class clazz, Object... ctorArgs) { try { Constructor empty = null; //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 == 0) { empty = ctor; // the empty constructor; } 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)); } // If an empty constructor exists, use that if (empty != null) { return clazz.cast(empty.newInstance()); } } catch (Exception e) { throw new RuntimeException(e); } throw new RuntimeException(clazz + " needs a constructor that takes: " + Arrays.toString(ctorArgs)); } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/context/jts/000077500000000000000000000000001375755266700272525ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/context/jts/DatelineRule.java000066400000000000000000000025311375755266700324730ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.context.jts; /** * Indicates the algorithm used to process JTS Polygons and JTS LineStrings for detecting dateline * (aka anti-meridian) 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 } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/context/jts/JtsSpatialContext.java000077500000000000000000000157421375755266700335540ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.context.jts; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.io.ShapeReader; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.jts.JtsGeometry; import org.locationtech.spatial4j.shape.jts.JtsPoint; import org.locationtech.spatial4j.shape.jts.JtsShapeFactory; import org.locationtech.jts.geom.*; 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); } /** * Called by {@link org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory#newSpatialContext()}. */ public JtsSpatialContext(JtsSpatialContextFactory factory) { super(factory); } // TODO I expect to delete this eventually once the other deprecated methods in this class disappear @Override public JtsShapeFactory getShapeFactory() { return (JtsShapeFactory) super.getShapeFactory(); } /** * 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 org.locationtech.spatial4j.shape.ShapeCollection#relateContainsShortCircuits()}. */ @Deprecated public boolean isAllowMultiOverlap() { return getShapeFactory().isAllowMultiOverlap(); } /** * Returns the rule used to handle geometry objects that have dateline (aka anti-meridian) crossing considerations. */ @Deprecated public DatelineRule getDatelineRule() { return getShapeFactory().getDatelineRule(); } /** * Returns the rule used to handle errors when creating a JTS {@link Geometry}, particularly after it has been * read from one of the {@link ShapeReader}s. */ @Deprecated public ValidationRule getValidationRule() { return getShapeFactory().getValidationRule(); } /** * If JtsGeometry shapes should be automatically "prepared" (i.e. optimized) when read via from a {@link ShapeReader}. * * @see org.locationtech.spatial4j.shape.jts.JtsGeometry#index() */ @Deprecated public boolean isAutoIndex() { return getShapeFactory().isAutoIndex(); } /** * 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 */ @Deprecated public Geometry getGeometryFrom(Shape shape) { return getShapeFactory().getGeometryFrom(shape); } /** Should {@link #makePoint(double, double)} return {@link JtsPoint}? */ @Deprecated public boolean useJtsPoint() { return getShapeFactory().useJtsPoint(); } /** Should {@link #makeLineString(java.util.List)} return {@link JtsGeometry}? */ @Deprecated public boolean useJtsLineString() { return getShapeFactory().useJtsLineString(); } /** * INTERNAL Usually creates a JtsGeometry, potentially validating, repairing, and indexing ("preparing"). This method * is intended for use by {@link ShapeReader} instances. * * If given a direct instance of {@link GeometryCollection} then it's contents will be * recursively converted and then the resulting list will be passed to * {@link SpatialContext#makeCollection(List)} and returned. * * If given a {@link org.locationtech.jts.geom.Point} then {@link SpatialContext#makePoint(double, double)} * is called, which will return a {@link JtsPoint} if {@link JtsSpatialContext#useJtsPoint()}; otherwise * a standard Spatial4j Point is returned. * * If given a {@link LineString} and if {@link JtsSpatialContext#useJtsLineString()} is true then * then the geometry's parts are exposed to call {@link SpatialContext#makeLineString(List)}. */ @Deprecated public Shape makeShapeFromGeometry(Geometry geom) { return getShapeFactory().makeShapeFromGeometry(geom); } /** * INTERNAL * @see #makeShape(org.locationtech.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()}. */ @Deprecated public JtsGeometry makeShape(Geometry geom, boolean dateline180Check, boolean allowMultiOverlap) { return getShapeFactory().makeShape(geom, 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. Also, * note that direct instances of {@link GeometryCollection} isn't supported. * * Instead of calling this method, consider {@link JtsShapeFactory#makeShapeFromGeometry(Geometry)} * which */ @Deprecated public JtsGeometry makeShape(Geometry geom) { return getShapeFactory().makeShape(geom); } @Deprecated public GeometryFactory getGeometryFactory() { return getShapeFactory().getGeometryFactory(); } @Override public String toString() { if (this.equals(GEO)) { return GEO.getClass().getSimpleName()+".GEO"; } else { return super.toString(); } } /** * INTERNAL: Returns a Rectangle of the JTS {@link Envelope} (bounding box) of the given {@code geom}. This asserts * that {@link Geometry#isRectangle()} is true. This method reacts to the {@link DatelineRule} setting. * @param geom non-null * @return null equivalent Rectangle. */ @Deprecated public Rectangle makeRectFromRectangularPoly(Geometry geom) { return getShapeFactory().makeRectFromRectangularPoly(geom); } } JtsSpatialContextFactory.java000066400000000000000000000123731375755266700350170ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/context/jts/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.context.jts; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.io.GeoJSONReader; import org.locationtech.spatial4j.io.LegacyShapeReader; import org.locationtech.spatial4j.io.LegacyShapeWriter; import org.locationtech.spatial4j.io.PolyshapeReader; import org.locationtech.spatial4j.io.WKTReader; import org.locationtech.spatial4j.io.jts.*; import org.locationtech.spatial4j.shape.jts.JtsShapeFactory; import org.locationtech.jts.geom.CoordinateSequenceFactory; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.PrecisionModel; import org.locationtech.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 DatelineRule}
*
validationRule
*
error(default)|none|repairConvexHull|repairBuffer0 * -- see {@link ValidationRule}
*
autoIndex
*
true|false(default) -- see {@link JtsShapeFactory#isAutoIndex()}
*
allowMultiOverlap
*
true|false(default) -- see {@link JtsSpatialContext#isAllowMultiOverlap()}
*
precisionModel
*
floating(default) | floating_single | fixed * -- see {@link org.locationtech.jts.geom.PrecisionModel}. * If {@code fixed} then you must also provide {@code precisionScale} * -- see {@link org.locationtech.jts.geom.PrecisionModel#getScale()}
*
useJtsPoint, useJtsLineString, useJtsMulti
*
All default to true. See corresponding methods on {@link JtsShapeFactory}.
*
*/ 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 DatelineRule datelineRule = DatelineRule.width180; public ValidationRule validationRule = 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 boolean useJtsMulti = true; public JtsSpatialContextFactory() { super.shapeFactoryClass = JtsShapeFactory.class; super.binaryCodecClass = JtsBinaryCodec.class; } @Override protected void checkDefaultFormats() { if (readers.isEmpty() ) { addReaderIfNoggitExists(GeoJSONReader.class); readers.add(WKTReader.class); readers.add(PolyshapeReader.class); readers.add(LegacyShapeReader.class); } if (writers.isEmpty()) { writers.add(JtsGeoJSONWriter.class); writers.add(JtsWKTWriter.class); writers.add(JtsPolyshapeWriter.class); writers.add(LegacyShapeWriter.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"); initField("useJtsMulti"); 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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/context/jts/ValidationRule.java000066400000000000000000000046261375755266700330470ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.context.jts; import org.locationtech.spatial4j.io.ShapeReader; /** * Indicates how JTS geometries (notably polygons but applies to other geometries too) are * validated (if at all) and repaired (if at all). This setting usually only applies to * {@link ShapeReader}. */ 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 org.locationtech.jts.geom.Geometry#isValid() */ none, /** * Geometries will be explicitly validated on creation, possibly resulting in an exception: * {@link org.locationtech.spatial4j.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 org.locationtech.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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/context/package-info.java000066400000000000000000000011051375755266700316360ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ /** SpatialContext implementations are the facade to the Spatial4j API. */ package org.locationtech.spatial4j.context;spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/distance/000077500000000000000000000000001375755266700265605ustar00rootroot00000000000000AbstractDistanceCalculator.java000066400000000000000000000017401375755266700345760ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/distance/******************************************************************************* * Copyright (c) 2015 MITRE and VoyagerSearch * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.distance; import org.locationtech.spatial4j.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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/distance/CartesianDistCalc.java000066400000000000000000000114711375755266700327470ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.distance; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.Circle; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; /** * Calculates based on Euclidean / Cartesian 2d plane. */ public class CartesianDistCalc extends AbstractDistanceCalculator { public static final CartesianDistCalc INSTANCE = new CartesianDistCalc(); public static final CartesianDistCalc INSTANCE_SQUARED = new CartesianDistCalc(true); private final boolean squared; public CartesianDistCalc() { this.squared = false; } /** * @param squared Set to true to have {@link #distance(org.locationtech.spatial4j.shape.Point, org.locationtech.spatial4j.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 xSquaredPlusYSquared = distanceSquared(from.getX(), from.getY(), toX, toY); if (squared) return xSquaredPlusYSquared; return Math.sqrt(xSquaredPlusYSquared); } private static double distanceSquared(double fromX, double fromY, double toX, double toY) { double deltaX = fromX - toX; double deltaY = fromY - toY; return deltaX*deltaX + deltaY*deltaY; } /** * Distance from point to a line segment formed between points 'v' and 'w'. * It respects the "squared" option. */ // TODO add to generic DistanceCalculator and develop geo versions. public double distanceToLineSegment(Point point, double vX, double vY, double wX, double wY) { // Translated from: http://bl.ocks.org/mbostock/4218871 double d = distanceSquared(vX, vY, wX, wY); double toX; double toY; if (d <= 0) { toX = vX; toY = vY; } else { // t = ((point[0] - v[0]) * (w[0] - v[0]) + (point[1] - v[1]) * (w[1] - v[1])) / d double t = ((point.getX() - vX) * (wX - vX) + (point.getY() - vY) * (wY - vY)) / d; if (t < 0) { toX = vX; toY = vY; } else if (t > 1) { toX = wX; toY = wY; } else { toX = vX + t * (wX - vX); toY = vY + t * (wY - vY); } } return distance(point, toX, toY); } @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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/distance/DistanceCalculator.java000066400000000000000000000045201375755266700331700ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.distance; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.Circle; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/distance/DistanceUtils.java000066400000000000000000000473151375755266700322100ustar00rootroot00000000000000/* * 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. */ // NOTE: we keep the header as it came from ASF; it did not originate in Spatial4j package org.locationtech.spatial4j.distance; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; /** * Various distance calculations and constants. To the extent possible, a {@link * org.locationtech.spatial4j.distance.DistanceCalculator}, retrieved from {@link * org.locationtech.spatial4j.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), distance, 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 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); if (h > 1)//numeric robustness issue. If we didn't check, the answer would be NaN! h = 1; 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) { // Check for same position if (lat1 == lat2 && lon1 == lon2) return 0.0; // Get the longitude difference. Don't need to worry about // crossing dateline since cos(x) = cos(-x) double dLon = lon2 - lon1; double cosB = (Math.sin(lat1) * Math.sin(lat2)) + (Math.cos(lat1) * Math.cos(lat2) * 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; } } GeodesicSphereDistCalc.java000066400000000000000000000076161375755266700336560ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/distance/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.distance; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.Circle; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import static org.locationtech.spatial4j.distance.DistanceUtils.toDegrees; import static org.locationtech.spatial4j.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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/distance/package-info.java000066400000000000000000000010441375755266700317460ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ /** * Ways to calculate distance. */ package org.locationtech.spatial4j.distance; spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/exception/000077500000000000000000000000001375755266700267645ustar00rootroot00000000000000InvalidShapeException.java000066400000000000000000000017741375755266700340070ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/exception/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.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); } } UnsupportedSpatialPredicate.java000066400000000000000000000023421375755266700352400ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/exception/* * 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. */ // NOTE: we keep the header as it came from ASF; it did not originate in Spatial4j package org.locationtech.spatial4j.exception; import org.locationtech.spatial4j.SpatialPredicate; /** * Exception thrown when something cannot implement the {@link SpatialPredicate}. */ public class UnsupportedSpatialPredicate extends UnsupportedOperationException { public UnsupportedSpatialPredicate(SpatialPredicate op) { super(op.getName()); } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/000077500000000000000000000000001375755266700253755ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/BinaryCodec.java000066400000000000000000000141651375755266700304310ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.shape.Circle; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/GeoJSONReader.java000066400000000000000000000322311375755266700305700ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 VoyagerSearch and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.distance.DistanceUtils; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.shape.Circle; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.ShapeFactory; import org.noggit.JSONParser; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.text.ParseException; import java.util.ArrayList; import java.util.List; public class GeoJSONReader implements ShapeReader { protected static final String BUFFER = "buffer"; protected static final String BUFFER_UNITS = "buffer_units"; protected final SpatialContext ctx; protected final ShapeFactory shapeFactory; public GeoJSONReader(SpatialContext ctx, SpatialContextFactory factory) { this.ctx = ctx; this.shapeFactory = ctx.getShapeFactory(); } @Override public String getFormatName() { return ShapeIO.GeoJSON; } @Override public final Shape read(Reader reader) throws IOException, ParseException { return readShape(new JSONParser(reader)); } @Override public Shape read(Object value) throws IOException, ParseException, InvalidShapeException { String v = value.toString().trim(); return read(new StringReader(v)); } @Override public Shape readIfSupported(Object value) throws InvalidShapeException { String v = value.toString().trim(); if (!(v.startsWith("{") && v.endsWith("}"))) { return null; } try { return read(new StringReader(v)); } catch (IOException ex) { } catch (ParseException e) { } return null; } // -------------------------------------------------------------- // Read GeoJSON // -------------------------------------------------------------- protected void readCoordXYZ(JSONParser parser, ShapeFactory.PointsBuilder pointsBuilder) throws IOException, ParseException { assert (parser.lastEvent() == JSONParser.ARRAY_START); double x = Double.NaN, y = Double.NaN, z = Double.NaN; int idx = 0; int evt = parser.nextEvent(); while (evt != JSONParser.EOF) { switch (evt) { case JSONParser.LONG: case JSONParser.NUMBER: case JSONParser.BIGNUMBER: double value = parser.getDouble(); switch(idx) { case 0: x = value; break; case 1: y = value; break; case 2: z = value; break; } idx++; break; case JSONParser.ARRAY_END: if (idx <= 2) { // don't have a 'z' pointsBuilder.pointXY(shapeFactory.normX(x), shapeFactory.normY(y)); } else { pointsBuilder.pointXYZ(shapeFactory.normX(x), shapeFactory.normY(y), shapeFactory.normZ(z)); } return; case JSONParser.STRING: case JSONParser.BOOLEAN: case JSONParser.NULL: case JSONParser.OBJECT_START: case JSONParser.OBJECT_END: case JSONParser.ARRAY_START: default: throw new ParseException("Unexpected " + JSONParser.getEventString(evt), (int) parser.getPosition()); } evt = parser.nextEvent(); } return; } protected void readCoordListXYZ(JSONParser parser, ShapeFactory.PointsBuilder pointsBuilder) throws IOException, ParseException { assert (parser.lastEvent() == JSONParser.ARRAY_START); int evt = parser.nextEvent(); while (evt != JSONParser.EOF) { switch (evt) { case JSONParser.ARRAY_START: readCoordXYZ(parser, pointsBuilder); // reads until ARRAY_END break; case JSONParser.ARRAY_END: return; default: throw new ParseException("Unexpected " + JSONParser.getEventString(evt), (int) parser.getPosition()); } evt = parser.nextEvent(); } } protected void readUntilEvent(JSONParser parser, final int event) throws IOException { int evt = parser.lastEvent(); while (true) { if (evt == event || evt == JSONParser.EOF) { return; } evt = parser.nextEvent(); } } protected Shape readPoint(JSONParser parser) throws IOException, ParseException { assert (parser.lastEvent() == JSONParser.ARRAY_START); OnePointsBuilder onePointsBuilder = new OnePointsBuilder(shapeFactory); readCoordXYZ(parser, onePointsBuilder); Point point = onePointsBuilder.getPoint(); readUntilEvent(parser, JSONParser.OBJECT_END); return point; } protected Shape readLineString(JSONParser parser) throws IOException, ParseException { assert (parser.lastEvent() == JSONParser.ARRAY_START); ShapeFactory.LineStringBuilder builder = shapeFactory.lineString(); readCoordListXYZ(parser, builder); // check for buffer field builder.buffer(readDistance(BUFFER, BUFFER_UNITS, parser)); Shape out = builder.build(); readUntilEvent(parser, JSONParser.OBJECT_END); return out; } protected Circle readCircle(JSONParser parser) throws IOException, ParseException { assert (parser.lastEvent() == JSONParser.ARRAY_START); OnePointsBuilder onePointsBuilder = new OnePointsBuilder(shapeFactory); readCoordXYZ(parser, onePointsBuilder); Point point = onePointsBuilder.getPoint(); return shapeFactory.circle(point, readDistance("radius", "radius_units", parser)); } /** * Helper method to read a up until a distance value (radius, buffer) and it's corresponding unit are found. *

* This method returns 0 if no distance value is found. This method currently only handles distance units of "km". *

* @param distProperty The name of the property containing the distance value. * @param distUnitsProperty The name of the property containing the distance unit. */ protected double readDistance(String distProperty, String distUnitsProperty, JSONParser parser) throws IOException { double dist = 0; String key = null; int event = JSONParser.OBJECT_END; int evt = parser.lastEvent(); while (true) { if (evt == event || evt == JSONParser.EOF) { break; } evt = parser.nextEvent(); if(parser.wasKey()) { key = parser.getString(); } else if(evt==JSONParser.NUMBER || evt==JSONParser.LONG) { if(distProperty.equals(key)) { dist = parser.getDouble(); } } else if(evt==JSONParser.STRING) { if(distUnitsProperty.equals(key)) { String units = parser.getString(); //TODO: support for more units? if("km".equals(units)) { // Convert KM to degrees dist = DistanceUtils.dist2Degrees(dist, DistanceUtils.EARTH_MEAN_RADIUS_KM); } } } } return shapeFactory.normDist(dist); } protected Shape readShape(JSONParser parser) throws IOException, ParseException { String type = null; String key = null; int evt = parser.nextEvent(); while (evt != JSONParser.EOF) { switch (evt) { case JSONParser.STRING: if (parser.wasKey()) { key = parser.getString(); } else { if ("type".equals(key)) { type = parser.getString(); } else { throw new ParseException("Unexpected String Value for key: " + key, (int) parser.getPosition()); } } break; case JSONParser.ARRAY_START: if ("coordinates".equals(key)) { Shape shape = readShapeFromCoordinates(type, parser); readUntilEvent(parser, JSONParser.OBJECT_END); return shape; } else if ("geometries".equals(key)) { List shapes = new ArrayList<>(); int sub = parser.nextEvent(); while (sub != JSONParser.EOF) { if (sub == JSONParser.OBJECT_START) { Shape s = readShape(parser); if (s != null) { shapes.add(s); } } else if (sub == JSONParser.OBJECT_END) { break; } sub = parser.nextEvent(); } return ctx.makeCollection(shapes); } else { throw new ParseException("Unknown type: "+type, (int) parser.getPosition()); } case JSONParser.ARRAY_END: break; case JSONParser.OBJECT_START: if (key != null) { // System.out.println("Unexpected object: " + key); } break; case JSONParser.LONG: case JSONParser.NUMBER: case JSONParser.BIGNUMBER: case JSONParser.BOOLEAN: case JSONParser.NULL: case JSONParser.OBJECT_END: // System.out.println(">>>>>" + JSONParser.getEventString(evt) + " :: " + key); break; default: throw new ParseException("Unexpected " + JSONParser.getEventString(evt), (int) parser.getPosition()); } evt = parser.nextEvent(); } throw new RuntimeException("unable to parse shape"); } protected Shape readShapeFromCoordinates(String type, JSONParser parser) throws IOException, ParseException { switch(type) { case "Point": return readPoint(parser); case "LineString": return readLineString(parser); case "Circle": return readCircle(parser); case "Polygon": return readPolygon(parser, shapeFactory.polygon()).buildOrRect(); case "MultiPoint": return readMultiPoint(parser); case "MultiLineString": return readMultiLineString(parser); case "MultiPolygon": return readMultiPolygon(parser); default: throw new ParseException("Unable to make shape type: " + type, (int) parser.getPosition()); } } protected ShapeFactory.PolygonBuilder readPolygon(JSONParser parser, ShapeFactory.PolygonBuilder polygonBuilder) throws IOException, ParseException { assert (parser.lastEvent() == JSONParser.ARRAY_START); boolean firstRing = true; int evt = parser.nextEvent(); while (true) { switch (evt) { case JSONParser.ARRAY_START: if (firstRing) { readCoordListXYZ(parser, polygonBuilder); firstRing = false; } else { ShapeFactory.PolygonBuilder.HoleBuilder holeBuilder = polygonBuilder.hole(); readCoordListXYZ(parser, holeBuilder); holeBuilder.endHole(); } break; case JSONParser.ARRAY_END: return polygonBuilder; default: throw new ParseException("Unexpected " + JSONParser.getEventString(evt), (int) parser.getPosition()); } evt = parser.nextEvent(); } } protected Shape readMultiPoint(JSONParser parser) throws IOException, ParseException { assert (parser.lastEvent() == JSONParser.ARRAY_START); ShapeFactory.MultiPointBuilder builder = shapeFactory.multiPoint(); readCoordListXYZ(parser, builder); return builder.build(); } protected Shape readMultiLineString(JSONParser parser) throws IOException, ParseException { assert (parser.lastEvent() == JSONParser.ARRAY_START); // TODO need Spatial4j LineString interface ShapeFactory.MultiLineStringBuilder builder = shapeFactory.multiLineString(); int evt = parser.nextEvent(); while (true) { switch (evt) { case JSONParser.ARRAY_START: ShapeFactory.LineStringBuilder lineStringBuilder = builder.lineString(); readCoordListXYZ(parser, lineStringBuilder); builder.add(lineStringBuilder); break; case JSONParser.ARRAY_END: return builder.build(); default: throw new ParseException("Unexpected " + JSONParser.getEventString(evt), (int) parser.getPosition()); } evt = parser.nextEvent(); } } protected Shape readMultiPolygon(JSONParser parser) throws IOException, ParseException { assert (parser.lastEvent() == JSONParser.ARRAY_START); // TODO need Spatial4j Polygon interface ShapeFactory.MultiPolygonBuilder builder = shapeFactory.multiPolygon(); int evt = parser.nextEvent(); while (true) { switch (evt) { case JSONParser.ARRAY_START: ShapeFactory.PolygonBuilder polygonBuilder = readPolygon(parser, builder.polygon()); builder.add(polygonBuilder); break; case JSONParser.ARRAY_END: return builder.build(); default: throw new ParseException("Unexpected " + JSONParser.getEventString(evt), (int) parser.getPosition()); } evt = parser.nextEvent(); } } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/GeoJSONWriter.java000066400000000000000000000145201375755266700306430ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 VoyagerSearch * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.distance.DistanceUtils; import org.locationtech.spatial4j.shape.*; import org.locationtech.spatial4j.shape.impl.BufferedLine; import org.locationtech.spatial4j.shape.impl.BufferedLineString; import org.locationtech.spatial4j.shape.impl.GeoCircle; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.text.NumberFormat; import java.util.Iterator; import static org.locationtech.spatial4j.io.GeoJSONReader.BUFFER; import static org.locationtech.spatial4j.io.GeoJSONReader.BUFFER_UNITS; public class GeoJSONWriter implements ShapeWriter { public GeoJSONWriter(SpatialContext ctx, SpatialContextFactory factory) { } @Override public String getFormatName() { return ShapeIO.GeoJSON; } protected void write(Writer output, NumberFormat nf, double... coords) throws IOException { output.write('['); for (int i = 0; i < coords.length; i++) { if (Double.isNaN(coords[i])) { break; // empty point or no more coordinates } if (i > 0) { output.append(','); } output.append(nf.format(coords[i])); } output.write(']'); } @Override public void write(Writer output, Shape shape) throws IOException { if (shape == null) { throw new NullPointerException("Shape can not be null"); } NumberFormat nf = LegacyShapeWriter.makeNumberFormat(6); if (shape instanceof Point) { Point v = (Point) shape; output.append("{\"type\":\"Point\",\"coordinates\":"); write(output, nf, v.getX(), v.getY()); output.append('}'); return; } if (shape instanceof Rectangle) { Rectangle v = (Rectangle) shape; output.append("{\"type\":\"Polygon\",\"coordinates\":[["); write(output, nf, v.getMinX(), v.getMinY()); output.append(','); write(output, nf, v.getMinX(), v.getMaxY()); output.append(','); write(output, nf, v.getMaxX(), v.getMaxY()); output.append(','); write(output, nf, v.getMaxX(), v.getMinY()); output.append(','); write(output, nf, v.getMinX(), v.getMinY()); output.append("]]}"); return; } if (shape instanceof BufferedLine) { BufferedLine v = (BufferedLine) shape; output.append("{\"type\":\"LineString\",\"coordinates\":["); write(output, nf, v.getA().getX(), v.getA().getY()); output.append(','); write(output, nf, v.getB().getX(), v.getB().getY()); output.append(','); output.append("]"); if (v.getBuf() > 0) { output.append(','); output.append("\"buffer\":"); output.append(nf.format(v.getBuf())); } output.append('}'); return; } if (shape instanceof BufferedLineString) { BufferedLineString v = (BufferedLineString) shape; output.append("{\"type\":\"LineString\",\"coordinates\":["); BufferedLine last = null; Iterator iter = v.getSegments().iterator(); while (iter.hasNext()) { BufferedLine seg = iter.next(); if (last != null) { output.append(','); } write(output, nf, seg.getA().getX(), seg.getA().getY()); last = seg; } if (last != null) { output.append(','); write(output, nf, last.getB().getX(), last.getB().getY()); } output.append("]"); if (v.getBuf() > 0) { writeDistance(output, nf, v.getBuf(), shape.getContext().isGeo(), BUFFER, BUFFER_UNITS); } output.append('}'); return; } if (shape instanceof Circle) { // See: https://github.com/geojson/geojson-spec/wiki/Proposal---Circles-and-Ellipses-Geoms Circle v = (Circle) shape; Point center = v.getCenter(); output.append("{\"type\":\"Circle\",\"coordinates\":"); write(output, nf, center.getX(), center.getY()); writeDistance(output, nf, v.getRadius(), v instanceof GeoCircle, "radius", "radius_units"); output.append("}"); return; } if (shape instanceof ShapeCollection) { ShapeCollection v = (ShapeCollection) shape; output.append("{\"type\":\"GeometryCollection\",\"geometries\":["); for (int i = 0; i < v.size(); i++) { if (i > 0) { output.append(','); } write(output, v.get(i)); } output.append("]}"); return; } output.append("{\"type\":\"Unknown\",\"wkt\":\""); output.append(LegacyShapeWriter.writeShape(shape)); output.append("\"}"); } /** * Helper method to encode a distance property (with optional unit). *

* The distance unit is only encoded when isGeo is true, and it is converted to km. *

*

* The distance unit is encoded within a properties object. *

* @param output The writer. * @param nf The number format. * @param dist The distance value to encode. * @param isGeo The flag determining * @param distProperty The distance property name. * @param distUnitsProperty The distance unit property name. */ void writeDistance(Writer output, NumberFormat nf, double dist, boolean isGeo, String distProperty, String distUnitsProperty) throws IOException { output.append(",\"").append(distProperty).append("\":"); if (isGeo) { double distKm = DistanceUtils.degrees2Dist(dist, DistanceUtils.EARTH_MEAN_RADIUS_KM); output.append(nf.format(distKm)); output.append(",\"properties\":{"); output.append("\"").append(distUnitsProperty).append("\":\"km\"}"); } else { output.append(nf.format(dist)); } } @Override public String toString(Shape shape) { try { StringWriter buffer = new StringWriter(); write(buffer, shape); return buffer.toString(); } catch (IOException ex) { throw new RuntimeException(ex); } } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/GeohashUtils.java000066400000000000000000000152221375755266700306410ustar00rootroot00000000000000/* * 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. */ // NOTE: we keep the header as it came from ASF; it did not originate in Spatial4j package org.locationtech.spatial4j.io; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.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 longitude (X) and latitude (Y) */ 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 lon (X), min-max lat (Y). */ 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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/LegacyShapeReader.java000066400000000000000000000125631375755266700315570ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import java.io.IOException; import java.io.Reader; import java.text.ParseException; import java.util.StringTokenizer; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Shape; /** * Reads a shape from 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 LegacyShapeReader implements ShapeReader { final SpatialContext ctx; public LegacyShapeReader(SpatialContext ctx, SpatialContextFactory factory) { this.ctx = ctx; } /** 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 org.locationtech.spatial4j.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]); } //------- @Override public String getFormatName() { return ShapeIO.LEGACY; } @Override public Shape read(Object value) throws IOException, ParseException, InvalidShapeException { Shape shape = readShapeOrNull(value.toString(), ctx); if(shape==null) { throw new ParseException("unable to read shape: "+value, 0); } return readShapeOrNull(value.toString(), ctx); } @Override public Shape readIfSupported(Object value) throws InvalidShapeException { return readShapeOrNull(value.toString(), ctx); } @Override public Shape read(Reader reader) throws IOException, ParseException, InvalidShapeException { return read(WKTReader.readString(reader)); } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/LegacyShapeWriter.java000066400000000000000000000065641375755266700316350ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import java.io.IOException; import java.io.Writer; import java.text.NumberFormat; import java.util.Locale; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.shape.Circle; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; /** * Writes a shape 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 LegacyShapeWriter implements ShapeWriter { final SpatialContext ctx; public LegacyShapeWriter(SpatialContext ctx, SpatialContextFactory factory) { this.ctx = ctx; } /** * Writes a shape to a String, in a format that can be read by * {@link LegacyShapeReader#readShapeOrNull(String, 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(0); return nf; } @Override public String getFormatName() { return ShapeIO.LEGACY; } @Override public void write(Writer output, Shape shape) throws IOException { output.append(writeShape(shape)); } @Override public String toString(Shape shape) { return writeShape(shape); } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/OnePointsBuilder.java000066400000000000000000000026761375755266700315000ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2016 David Smiley * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.ShapeFactory; /** INTERNAL class used by some {@link ShapeReader}s. */ public class OnePointsBuilder implements ShapeFactory.PointsBuilder { private ShapeFactory shapeFactory; private Point point; public OnePointsBuilder(ShapeFactory shapeFactory) { this.shapeFactory = shapeFactory; } @Override public OnePointsBuilder pointXY(double x, double y) { assert point == null; point = shapeFactory.pointXY(x, y); return this; } @Override public OnePointsBuilder pointXYZ(double x, double y, double z) { assert point == null; point = shapeFactory.pointXYZ(x, y, z); return this; } @Override public OnePointsBuilder pointLatLon(double latitude, double longitude) { assert point == null; point = shapeFactory.pointLatLon(latitude, longitude); return this; } public Point getPoint() { return point; } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/ParseUtils.java000066400000000000000000000154231375755266700303400ustar00rootroot00000000000000/* * 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. */ // NOTE: we keep the header as it came from ASF; it did not originate in Spatial4j package org.locationtech.spatial4j.io; import org.locationtech.spatial4j.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 org.locationtech.spatial4j.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 org.locationtech.spatial4j.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 org.locationtech.spatial4j.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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/PolyshapeReader.java000066400000000000000000000170611375755266700313340ustar00rootroot00000000000000/* * 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 org.locationtech.spatial4j.io; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.text.ParseException; import java.util.ArrayList; import java.util.List; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.ShapeFactory; import org.locationtech.jts.geom.LinearRing; /** * @see PolyshapeWriter */ public class PolyshapeReader implements ShapeReader { final SpatialContext ctx; final ShapeFactory shpFactory; public PolyshapeReader(SpatialContext ctx, SpatialContextFactory factory) { this.ctx = ctx; this.shpFactory = ctx.getShapeFactory(); } @Override public String getFormatName() { return ShapeIO.POLY; } @Override public Shape read(Object value) throws IOException, ParseException, InvalidShapeException { return read(new StringReader(value.toString().trim())); } @Override public Shape readIfSupported(Object value) throws InvalidShapeException { String v = value.toString().trim(); char first = v.charAt(0); if(first >= '0' && first <= '9') { try { return read(new StringReader(v)); } catch (ParseException e) { } catch (IOException e) { } } return null; } // -------------------------------------------------------------- // Read GeoJSON // -------------------------------------------------------------- @Override public final Shape read(Reader r) throws ParseException, IOException { XReader reader = new XReader(r, shpFactory); Double arg = null; Shape lastShape = null; List shapes = null; while(!reader.isDone()) { char event = reader.readKey(); if(event<'0' || event > '9') { if(event == PolyshapeWriter.KEY_SEPERATOR) { continue; // read the next key } throw new ParseException("expecting a shape key. not '"+event+"'", -1); } if(lastShape!=null) { if(shapes==null) { shapes = new ArrayList<>(); } shapes.add(lastShape); } arg = null; if(reader.peek()==PolyshapeWriter.KEY_ARG_START) { reader.readKey(); // skip the key arg = reader.readDouble(); if(reader.readKey()!=PolyshapeWriter.KEY_ARG_END) { throw new ParseException("expecting an argument end", -1); } } if(reader.isEvent()) { throw new ParseException("Invalid input. Event should be followed by data", -1); } switch(event) { case PolyshapeWriter.KEY_POINT: { lastShape = shpFactory.pointXY(shpFactory.normX(reader.readLat()), shpFactory.normY(reader.readLng())); break; } case PolyshapeWriter.KEY_LINE: { ShapeFactory.LineStringBuilder lineBuilder = shpFactory.lineString(); reader.readPoints(lineBuilder); if(arg!=null) { lineBuilder.buffer(shpFactory.normDist(arg)); } lastShape = lineBuilder.build(); break; } case PolyshapeWriter.KEY_BOX: { double lat1 = shpFactory.normX(reader.readLat()); double lon1 = shpFactory.normY(reader.readLng()); lastShape = shpFactory.rect(lat1, shpFactory.normX(reader.readLat()), lon1, shpFactory.normY(reader.readLng())); break; } case PolyshapeWriter.KEY_MULTIPOINT : { lastShape = reader.readPoints(shpFactory.multiPoint()).build(); break; } case PolyshapeWriter.KEY_CIRCLE : { if(arg==null) { throw new IllegalArgumentException("the input should have a radius argument"); } lastShape = shpFactory.circle(shpFactory.normX(reader.readLat()), shpFactory.normY(reader.readLng()), shpFactory.normDist(arg.doubleValue())); break; } case PolyshapeWriter.KEY_POLYGON: { lastShape = readPolygon(reader); break; } default: { throw new ParseException("unhandled key: "+event, -1); } } } if(shapes!=null) { if(lastShape!=null) { shapes.add(lastShape); } ShapeFactory.MultiShapeBuilder multiBuilder = shpFactory.multiShape(Shape.class); for (Shape shp : shapes) { multiBuilder.add(shp); } return multiBuilder.build(); } return lastShape; } protected Shape readPolygon(XReader reader) throws IOException { ShapeFactory.PolygonBuilder polyBuilder = shpFactory.polygon(); reader.readPoints(polyBuilder); if(!reader.isDone() && reader.peek()==PolyshapeWriter.KEY_ARG_START) { while(reader.isEvent() && reader.peek()==PolyshapeWriter.KEY_ARG_START) { reader.readKey(); // eat the event; reader.readPoints(polyBuilder.hole()).endHole(); } } return polyBuilder.build(); } /** * from Apache 2.0 licensed: * https://github.com/googlemaps/android-maps-utils/blob/master/library/src/com/google/maps/android/PolyUtil.java */ public static class XReader { int lat = 0; int lng = 0; int head = -1; final Reader input; final ShapeFactory shpFactory; public XReader(final Reader input, ShapeFactory shpFactory) throws IOException { this.input = input; this.shpFactory = shpFactory; head = input.read(); } public T readPoints(T builder) throws IOException { while(isData()) { builder.pointXY(shpFactory.normX(readLat()), shpFactory.normY(readLng())); } return builder; } public double readLat() throws IOException { lat += readInt(); return lat * 1e-5; } public double readLng() throws IOException { lng += readInt(); return lng * 1e-5; } public double readDouble() throws IOException { return readInt() * 1e-5; } public int peek() { return head; } public char readKey() throws IOException { lat = lng = 0; // reset the offset char key = (char)head; head = input.read(); return key; } public boolean isData() { return head >= '?'; } public boolean isDone() { return head < 0; } public boolean isEvent() { return head > 0 && head < '?'; } int readInt() throws IOException { int b; int result = 1; int shift = 0; do { b = head - 63 - 1; result += b << shift; shift += 5; head = input.read(); } while (b >= 0x1f); return (result & 1) != 0 ? ~(result >> 1) : (result >> 1); } } }spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/PolyshapeWriter.java000066400000000000000000000153441375755266700314100ustar00rootroot00000000000000/* * 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 org.locationtech.spatial4j.io; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.util.Iterator; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.shape.Circle; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.ShapeCollection; import org.locationtech.spatial4j.shape.impl.BufferedLine; import org.locationtech.spatial4j.shape.impl.BufferedLineString; /** * Wrap the 'Encoded Polyline Algorithm Format' defined in: * Google Maps API * with flags to encode various Shapes: Point, Line, Polygon, etc * * For more details, see FORMATS.md * * This format works well for geographic shapes (-180...+180 / -90...+90), but is not appropriate for other coordinate systems * * @see Google Maps API * @see PolyshapeReader */ public class PolyshapeWriter implements ShapeWriter { public PolyshapeWriter(SpatialContext ctx, SpatialContextFactory factory) { } @Override public String getFormatName() { return ShapeIO.POLY; } @Override public void write(Writer output, Shape shape) throws IOException { if (shape == null) { throw new NullPointerException("Shape can not be null"); } write(new Encoder(output), shape); } public void write(Encoder enc, Shape shape) throws IOException { if (shape instanceof Point) { Point v = (Point) shape; enc.write(KEY_POINT); enc.write(v.getX(), v.getY()); return; } if (shape instanceof Rectangle) { Rectangle v = (Rectangle) shape; enc.write(KEY_BOX); enc.write(v.getMinX(), v.getMinY()); enc.write(v.getMaxX(), v.getMaxY()); return; } if (shape instanceof BufferedLine) { BufferedLine v = (BufferedLine) shape; enc.write(KEY_LINE); if(v.getBuf()>0) { enc.writeArg(v.getBuf()); } enc.write(v.getA().getX(), v.getA().getY()); enc.write(v.getB().getX(), v.getB().getY()); return; } if (shape instanceof BufferedLineString) { BufferedLineString v = (BufferedLineString) shape; enc.write(KEY_LINE); if(v.getBuf()>0) { enc.writeArg(v.getBuf()); } BufferedLine last = null; Iterator iter = v.getSegments().iterator(); while (iter.hasNext()) { BufferedLine seg = iter.next(); enc.write(seg.getA().getX(), seg.getA().getY()); last = seg; } if (last != null) { enc.write(last.getB().getX(), last.getB().getY()); } return; } if (shape instanceof Circle) { // See: https://github.com/geojson/geojson-spec/wiki/Proposal---Circles-and-Ellipses-Geoms Circle v = (Circle) shape; Point center = v.getCenter(); double radius = v.getRadius(); enc.write(KEY_CIRCLE); enc.writeArg(radius); enc.write(center.getX(), center.getY()); return; } if (shape instanceof ShapeCollection) { @SuppressWarnings("unchecked") ShapeCollection v = (ShapeCollection) shape; Iterator iter = v.iterator(); while(iter.hasNext()) { write(enc, iter.next()); if(iter.hasNext()) { enc.seperator(); } } return; } enc.writer.write("{unkwnwon " + LegacyShapeWriter.writeShape(shape) +"}"); } @Override public String toString(Shape shape) { try { StringWriter buffer = new StringWriter(); write(buffer, shape); return buffer.toString(); } catch (IOException ex) { throw new RuntimeException(ex); } } public static final char KEY_POINT = '0'; public static final char KEY_LINE = '1'; public static final char KEY_POLYGON = '2'; public static final char KEY_MULTIPOINT = '3'; public static final char KEY_CIRCLE = '4'; public static final char KEY_BOX = '5'; public static final char KEY_ARG_START = '('; public static final char KEY_ARG_END = ')'; public static final char KEY_SEPERATOR = ' '; /** * Encodes a sequence of LatLngs into an encoded path string. * * from Apache 2.0 licensed: * https://github.com/googlemaps/android-maps-utils/blob/master/library/src/com/google/maps/android/PolyUtil.java */ public static class Encoder { long lastLat = 0; long lastLng = 0; final Writer writer; public Encoder(Writer writer) { this.writer = writer; } public void seperator() throws IOException { writer.write(KEY_SEPERATOR); lastLat = lastLng = 0; } public void startRing() throws IOException { writer.write(KEY_ARG_START); lastLat = lastLng = 0; } public void write(char event) throws IOException { writer.write(event); lastLat = lastLng = 0; } public void writeArg(double value) throws IOException { writer.write(KEY_ARG_START); encode(Math.round(value * 1e5)); writer.write(KEY_ARG_END); } public void write(double latitude, double longitude) throws IOException { long lat = Math.round(latitude * 1e5); long lng = Math.round(longitude * 1e5); long dLat = lat - lastLat; long dLng = lng - lastLng; encode(dLat); encode(dLng); lastLat = lat; lastLng = lng; } private void encode(long v) throws IOException { v = v < 0 ? ~(v << 1) : v << 1; while (v >= 0x20) { writer.write(Character.toChars((int) ((0x20 | (v & 0x1f)) + 63))); v >>= 5; } writer.write(Character.toChars((int) (v + 63))); } } }spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/ShapeIO.java000066400000000000000000000014151375755266700275310ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 VoyagerSearch * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; public interface ShapeIO { public static final String WKT = "WKT"; public static final String GeoJSON = "GeoJSON"; public static final String POLY = "POLY"; public static final String LEGACY = "LEGACY"; /** * @return the format name */ public String getFormatName(); } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/ShapeReader.java000066400000000000000000000031501375755266700304220ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 VoyagerSearch * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import java.io.IOException; import java.io.Reader; import java.text.ParseException; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.shape.Shape; /** * Implementations are expected to be thread safe */ public interface ShapeReader extends ShapeIO { /** * @param value -- the input value, could be a String or other object * @return a shape valid shape (not null) */ public Shape read(Object value) throws IOException, ParseException, InvalidShapeException; /** * @param value -- the input value, could be a String or other object * @return a shape or null, if the input was un readable. * * This will throw {@link InvalidShapeException} when we could read a shape, but it was * invalid */ public Shape readIfSupported(Object value) throws InvalidShapeException; /** * Read a {@link Shape} from the reader. * * @param reader -- the input. Note, it will not be closed by this function * @return a valid Shape (never null) */ public Shape read(Reader reader) throws IOException, ParseException, InvalidShapeException; } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/ShapeWriter.java000066400000000000000000000016041375755266700304760ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 VoyagerSearch * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import java.io.IOException; import java.io.Writer; import org.locationtech.spatial4j.shape.Shape; /** * Implementations are expected to be thread safe */ public interface ShapeWriter extends ShapeIO { /** * Write a shape to the output writer */ public void write(Writer output, Shape shape) throws IOException; /** * Write a shape to String */ public String toString(Shape shape); } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/SupportedFormats.java000066400000000000000000000043541375755266700315670ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 VoyagerSearch * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import java.util.List; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.Shape; /** * Information about the formats a {@link SpatialContext} can read/write */ public class SupportedFormats { private final List readers; private final List writers; private final ShapeReader wktReader; private final ShapeWriter wktWriter; private final ShapeReader geoJsonReader; private final ShapeWriter geoJsonWriter; public SupportedFormats(List readers, List writers) { this.readers = readers; this.writers = writers; wktReader = getReader(ShapeIO.WKT); wktWriter = getWriter(ShapeIO.WKT); geoJsonReader = getReader(ShapeIO.GeoJSON); geoJsonWriter = getWriter(ShapeIO.GeoJSON); } public List getReaders() { return readers; } public List getWriters() { return writers; } public ShapeReader getReader(String fmt) { for(ShapeReader f : readers) { if(fmt.equals(f.getFormatName())) { return f; } } return null; } public ShapeWriter getWriter(String fmt) { for(ShapeWriter f : writers) { if(fmt.equals(f.getFormatName())) { return f; } } return null; } public ShapeReader getWktReader() { return wktReader; } public ShapeWriter getWktWriter() { return wktWriter; } public ShapeReader getGeoJsonReader() { return geoJsonReader; } public ShapeWriter getGeoJsonWriter() { return geoJsonWriter; } public Shape read(String value) { for(ShapeReader format : readers) { Shape v = format.readIfSupported(value); if(v!=null) { return v; } } return null; } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/WKTReader.java000066400000000000000000000555311375755266700300410ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 ElasticSearch and MITRE, and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ // A derivative of commit 14bc4dee08355048d6a94e33834b919a3999a06e // at https://github.com/chrismale/elasticsearch package org.locationtech.spatial4j.io; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.ShapeFactory; import java.io.IOException; import java.io.Reader; import java.text.ParseException; /** * 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
  • *
  • POLYGON
  • *
  • MULTIPOLYGON
  • *
  • 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(WKTReader.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 WKTReader implements ShapeReader { protected final SpatialContext ctx; protected final ShapeFactory shapeFactory; // TODO support SRID: "SRID=4326;POINT(1,2) /** * This constructor is required by * {@link org.locationtech.spatial4j.context.SpatialContextFactory#makeFormats(SpatialContext)}. */ public WKTReader(SpatialContext ctx, SpatialContextFactory factory) { this.ctx = ctx; this.shapeFactory = ctx.getShapeFactory(); } /** * 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, InvalidShapeException { 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, InvalidShapeException { 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 | InvalidShapeException e) { throw e; } catch (IllegalArgumentException e) { // NOTE: JTS Throws IAE for bad WKT throw new InvalidShapeException(e.getMessage(), e); } catch (Exception e) { 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 WKTReader.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 org.locationtech.spatial4j.io.WKTReader.State#nextIfEmptyAndSkipZM()}. * * @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("LINESTRING")) { return parseLineStringShape(state); } else if (shapeType.equalsIgnoreCase("POLYGON")) { return parsePolygonShape(state); } else if (shapeType.equalsIgnoreCase("GEOMETRYCOLLECTION")) { return parseGeometryCollectionShape(state); } else if (shapeType.equalsIgnoreCase("MULTILINESTRING")) { return parseMultiLineStringShape(state); } else if (shapeType.equalsIgnoreCase("MULTIPOLYGON")) { return parseMulitPolygonShape(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 = shapeFactory.normDist(state.nextDouble()); state.nextExpect(')'); return shape.getBuffered(distance, ctx); } /** * Parses a POINT shape from the raw string. * *
   *   '(' coordinate ')'
   * 
* * @see #point(State, ShapeFactory.PointsBuilder) */ protected Shape parsePointShape(State state) throws ParseException { if (state.nextIfEmptyAndSkipZM()) return shapeFactory.pointXY(Double.NaN, Double.NaN); state.nextExpect('('); OnePointsBuilder onePointsBuilder = new OnePointsBuilder(shapeFactory); point(state, onePointsBuilder); state.nextExpect(')'); return onePointsBuilder.getPoint(); } /** * Parses a MULTIPOINT shape from the raw string -- a collection of points. * *
   *   '(' coordinate (',' coordinate )* ')'
   * 
* * Furthermore, coordinate can optionally be wrapped in parenthesis. * * @see #point(State, ShapeFactory.PointsBuilder) */ protected Shape parseMultiPointShape(State state) throws ParseException { ShapeFactory.MultiPointBuilder builder = shapeFactory.multiPoint(); if (state.nextIfEmptyAndSkipZM()) return builder.build(); state.nextExpect('('); do { boolean openParen = state.nextIf('('); point(state, builder); if (openParen) state.nextExpect(')'); } while (state.nextIf(',')); state.nextExpect(')'); return builder.build(); } /** * 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 shapeFactory.rect(shapeFactory.normX(x1), shapeFactory.normX(x2), shapeFactory.normY(y1), shapeFactory.normY(y2)); } /** * Parses a LINESTRING shape from the raw string -- an ordered sequence of points. * *
   * coordinateSequence
   * 
* * @see #pointList(State, ShapeFactory.PointsBuilder) */ protected Shape parseLineStringShape(State state) throws ParseException { ShapeFactory.LineStringBuilder lineStringBuilder = shapeFactory.lineString(); if (state.nextIfEmptyAndSkipZM()) return lineStringBuilder.build(); return pointList(state, lineStringBuilder).build(); } /** * Parses a MULTILINESTRING shape from the raw string -- a collection of line strings. * *
   *   '(' coordinateSequence (',' coordinateSequence )* ')'
   * 
* * @see #parseLineStringShape(org.locationtech.spatial4j.io.WKTReader.State) */ protected Shape parseMultiLineStringShape(State state) throws ParseException { ShapeFactory.MultiLineStringBuilder multiLineStringBuilder = shapeFactory.multiLineString(); if (!state.nextIfEmptyAndSkipZM()) { state.nextExpect('('); do { multiLineStringBuilder.add(pointList(state, multiLineStringBuilder.lineString())); } while (state.nextIf(',')); state.nextExpect(')'); } return multiLineStringBuilder.build(); } /** * Parses a POLYGON shape from the raw string. It might return a * {@link org.locationtech.spatial4j.shape.Rectangle} if the polygon is one. * *
   * coordinateSequenceList
   * 
*/ protected Shape parsePolygonShape(WKTReader.State state) throws ParseException { ShapeFactory.PolygonBuilder polygonBuilder = shapeFactory.polygon(); if (!state.nextIfEmptyAndSkipZM()) { polygonBuilder = polygon(state, polygonBuilder); } return polygonBuilder.buildOrRect(); } /** * Parses a MULTIPOLYGON shape from the raw string. * *
   *   '(' polygon (',' polygon )* ')'
   * 
*/ protected Shape parseMulitPolygonShape(WKTReader.State state) throws ParseException { ShapeFactory.MultiPolygonBuilder multiPolygonBuilder = shapeFactory.multiPolygon(); if (!state.nextIfEmptyAndSkipZM()) { state.nextExpect('('); do { multiPolygonBuilder.add(polygon(state, multiPolygonBuilder.polygon())); } while (state.nextIf(',')); state.nextExpect(')'); } return multiPolygonBuilder.build(); } /** * Parses a GEOMETRYCOLLECTION shape from the raw string. * *
   *   '(' shape (',' shape )* ')'
   * 
*/ protected Shape parseGeometryCollectionShape(State state) throws ParseException { ShapeFactory.MultiShapeBuilder multiShapeBuilder = shapeFactory.multiShape(Shape.class); if (state.nextIfEmptyAndSkipZM()) return multiShapeBuilder.build(); state.nextExpect('('); do { multiShapeBuilder.add(shape(state)); } while (state.nextIf(',')); state.nextExpect(')'); return multiShapeBuilder.build(); } /** * Reads a shape from the current position, starting with the name of the shape. It calls * {@link #parseShapeByType(org.locationtech.spatial4j.io.WKTReader.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(State, ShapeFactory.PointsBuilder) */ protected B pointList(State state, B pointsBuilder) throws ParseException { state.nextExpect('('); do { point(state, pointsBuilder); } while (state.nextIf(',')); state.nextExpect(')'); return pointsBuilder; } /** * 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 ShapeFactory.PointsBuilder point(State state, ShapeFactory.PointsBuilder pointsBuilder) throws ParseException { double x = state.nextDouble(); double y = state.nextDouble(); state.skipNextDoubles();//TODO capture to call pointXYZ pointsBuilder.pointXY(shapeFactory.normX(x), shapeFactory.normY(y)); return pointsBuilder; } /** * Reads a polygon */ protected ShapeFactory.PolygonBuilder polygon(WKTReader.State state, ShapeFactory.PolygonBuilder polygonBuilder) throws ParseException { state.nextExpect('('); pointList(state, polygonBuilder); // outer ring while (state.nextIf(',')) { ShapeFactory.PolygonBuilder.HoleBuilder holeBuilder = polygonBuilder.hole(); pointList(state, holeBuilder); holeBuilder.endHole(); } state.nextExpect(')'); return polygonBuilder; } /** 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 WKTReader getParser() { return WKTReader.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 @Override public String getFormatName() { return ShapeIO.WKT; } static String readString(Reader reader) throws IOException { char[] arr = new char[1024]; StringBuilder buffer = new StringBuilder(); int numCharsRead; while ((numCharsRead = reader.read(arr, 0, arr.length)) != -1) { buffer.append(arr, 0, numCharsRead); } return buffer.toString(); } @Override public Shape read(Reader reader) throws IOException, ParseException { return parse(readString(reader)); } @Override public Shape read(Object value) throws IOException, ParseException, InvalidShapeException { return parse(value.toString()); } @Override public Shape readIfSupported(Object value) throws InvalidShapeException { try { return parseIfSupported(value.toString()); } catch (ParseException e) { } return null; } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/WKTWriter.java000066400000000000000000000105201375755266700301000ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 VoyagerSearch * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import java.io.IOException; import java.io.Writer; import java.math.RoundingMode; import java.text.NumberFormat; import java.util.Iterator; import org.locationtech.spatial4j.shape.*; import org.locationtech.spatial4j.shape.impl.BufferedLine; import org.locationtech.spatial4j.shape.impl.BufferedLineString; public class WKTWriter implements ShapeWriter { @Override public String getFormatName() { return ShapeIO.WKT; } protected StringBuilder append(StringBuilder buffer, Point p, NumberFormat nf) { return buffer.append(nf.format(p.getX())).append(' ').append( nf.format(p.getY())); } protected NumberFormat getNumberFormat() { return LegacyShapeWriter.makeNumberFormat(6); } @Override public String toString(Shape shape) { NumberFormat nf = getNumberFormat(); if (shape instanceof Point) { Point point = (Point)shape; if (point.isEmpty()) { return "POINT EMPTY"; } StringBuilder buffer = new StringBuilder(); return append(buffer.append("POINT ("), point, nf).append(")").toString(); } if (shape instanceof Rectangle) { NumberFormat nfMIN = nf; NumberFormat nfMAX = LegacyShapeWriter.makeNumberFormat(6); nfMIN.setRoundingMode( RoundingMode.FLOOR ); nfMAX.setRoundingMode( RoundingMode.CEILING ); Rectangle rect = (Rectangle)shape; return "ENVELOPE (" + // '(' x1 ',' x2 ',' y2 ',' y1 ')' nfMIN.format(rect.getMinX()) + ", " + nfMAX.format(rect.getMaxX()) + ", "+ nfMAX.format(rect.getMaxY()) + ", " + nfMIN.format(rect.getMinY()) + ")"; // // return "POLYGON(( "+ // nf.format(rect.getMinX()) + " " + nf.format(rect.getMinY()) + ", "+ // nf.format(rect.getMinX()) + " " + nf.format(rect.getMaxY()) + ", "+ // nf.format(rect.getMaxX()) + " " + nf.format(rect.getMaxY()) + ", "+ // nf.format(rect.getMaxX()) + " " + nf.format(rect.getMinY()) + ", "+ // nf.format(rect.getMinX()) + " " + nf.format(rect.getMinY()) + "))"; } if (shape instanceof Circle) { Circle c = (Circle) shape; StringBuilder str = new StringBuilder(); str.append("BUFFER (POINT (") .append(nf.format(c.getCenter().getX())).append(" ") .append(nf.format(c.getCenter().getY())) .append("), ") .append(nf.format(c.getRadius())) .append(")"); return str.toString(); } if (shape instanceof BufferedLineString) { BufferedLineString line = (BufferedLineString) shape; StringBuilder str = new StringBuilder(); double buf = line.getBuf(); if (buf > 0d) { str.append("BUFFER ("); } str.append("LINESTRING ("); Iterator iter = line.getSegments().iterator(); while(iter.hasNext()) { BufferedLine seg = iter.next(); append(str,seg.getA(),nf).append(", "); if(!iter.hasNext()) { append(str,seg.getB(),nf); } } str.append(")"); if (buf > 0d) { str.append(", ").append(nf.format(buf)).append(")"); } return str.toString(); } if(shape instanceof ShapeCollection) { @SuppressWarnings("unchecked") ShapeCollection collection = (ShapeCollection) shape; if (collection.isEmpty()) { return "GEOMETRYCOLLECTION EMPTY"; } StringBuilder buffer = new StringBuilder(); buffer.append("GEOMETRYCOLLECTION ("); boolean first = true; for (Shape sub : collection.getShapes()) { if(!first) { buffer.append(","); } buffer.append(toString(sub)); first = false; } buffer.append(")"); return buffer.toString(); } return LegacyShapeWriter.writeShape(shape, nf); } @Override public void write(Writer output, Shape shape) throws IOException { output.append( toString(shape) ); } }spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/WktShapeParser.java000066400000000000000000000016331375755266700311460ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 ElasticSearch and MITRE, and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ // A derivative of commit 14bc4dee08355048d6a94e33834b919a3999a06e // at https://github.com/chrismale/elasticsearch package org.locationtech.spatial4j.io; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContextFactory; @Deprecated public class WktShapeParser extends WKTReader { public WktShapeParser(SpatialContext ctx, SpatialContextFactory factory) { super(ctx,factory); } }spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jackson/000077500000000000000000000000001375755266700270255ustar00rootroot00000000000000GeometryAsGeoJSONSerializer.java000066400000000000000000000111171375755266700350500ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jackson/******************************************************************************* * Copyright (c) 2015 VoyagerSearch and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jackson; import java.io.IOException; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.CoordinateSequence; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryCollection; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.MultiLineString; import org.locationtech.jts.geom.MultiPoint; import org.locationtech.jts.geom.MultiPolygon; import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Polygon; public class GeometryAsGeoJSONSerializer extends JsonSerializer { // -------------------------------------------------------------- // Write JTS To GeoJSON // -------------------------------------------------------------- protected void write(JsonGenerator gen, Coordinate coord) throws IOException { gen.writeStartArray(); gen.writeNumber(coord.x); gen.writeNumber(coord.y); gen.writeEndArray(); } protected void write(JsonGenerator gen, CoordinateSequence coordseq) throws IOException { gen.writeStartArray(); int dim = coordseq.getDimension(); for (int i = 0; i < coordseq.size(); i++) { gen.writeStartArray(); gen.writeNumber(coordseq.getOrdinate(i, 0)); gen.writeNumber(coordseq.getOrdinate(i, 1)); if (dim > 2) { double v = coordseq.getOrdinate(i, 2); if (!Double.isNaN(v)) { gen.writeNumber(v); } } gen.writeEndArray(); } gen.writeEndArray(); } protected void write(JsonGenerator gen, Coordinate[] coord) throws IOException { gen.writeStartArray(); for (int i = 0; i < coord.length; i++) { write(gen, coord[i]); } gen.writeEndArray(); } protected void write(JsonGenerator gen, Polygon p) throws IOException { gen.writeStartArray(); write(gen, p.getExteriorRing().getCoordinateSequence()); for (int i = 0; i < p.getNumInteriorRing(); i++) { write(gen, p.getInteriorRingN(i).getCoordinateSequence()); } gen.writeEndArray(); } @Override public void serialize(Geometry geom, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { gen.writeStartObject(); gen.writeFieldName("type"); gen.writeString(geom.getClass().getSimpleName()); if (geom instanceof Point) { Point v = (Point) geom; gen.writeFieldName("coordinates"); write(gen, v.getCoordinate()); } else if (geom instanceof Polygon) { gen.writeFieldName("coordinates"); write(gen, (Polygon) geom); } else if (geom instanceof LineString) { LineString v = (LineString) geom; gen.writeFieldName("coordinates"); write(gen, v.getCoordinateSequence()); } else if (geom instanceof MultiPoint) { MultiPoint v = (MultiPoint) geom; gen.writeFieldName("coordinates"); write(gen, v.getCoordinates()); return; } else if (geom instanceof MultiLineString) { MultiLineString v = (MultiLineString) geom; gen.writeFieldName("coordinates"); gen.writeStartArray(); for (int i = 0; i < v.getNumGeometries(); i++) { write(gen, v.getGeometryN(i).getCoordinates()); } gen.writeEndArray(); } else if (geom instanceof MultiPolygon) { MultiPolygon v = (MultiPolygon) geom; gen.writeFieldName("coordinates"); gen.writeStartArray(); for (int i = 0; i < v.getNumGeometries(); i++) { write(gen, (Polygon) v.getGeometryN(i)); } gen.writeEndArray(); } else if (geom instanceof GeometryCollection) { GeometryCollection v = (GeometryCollection) geom; gen.writeFieldName("geometries"); gen.writeStartArray(); for (int i = 0; i < v.getNumGeometries(); i++) { serialize(v.getGeometryN(i), gen, serializers); } gen.writeEndArray(); } else { throw new UnsupportedOperationException("unknown: " + geom); } gen.writeEndObject(); } } GeometryAsWKTSerializer.java000066400000000000000000000020751375755266700343140ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jackson/******************************************************************************* * Copyright (c) 2015 VoyagerSearch and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jackson; import java.io.IOException; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import org.locationtech.jts.geom.Geometry; public class GeometryAsWKTSerializer extends JsonSerializer { @Override public void serialize(Geometry value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { gen.writeString(value.toText()); } } GeometryDeserializer.java000066400000000000000000000034331375755266700337520ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jackson/******************************************************************************* * Copyright (c) 2015 VoyagerSearch and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jackson; import java.io.IOException; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.shape.Shape; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import org.locationtech.jts.geom.Geometry; public class GeometryDeserializer extends JsonDeserializer { // Create a context that will allow any JTS shape static final JtsSpatialContext JTS; static { JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.geo = false; factory.useJtsLineString = true; factory.useJtsMulti = true; factory.useJtsPoint = true; JTS = new JtsSpatialContext(factory); } final ShapeDeserializer dser; public GeometryDeserializer() { dser = new ShapeDeserializer(JTS); } @Override public Geometry deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { Shape shape = dser.deserialize(jp, ctxt); if(shape!=null) { return JTS.getShapeFactory().getGeometryFrom(shape); } return null; } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jackson/PackageVersion.java000066400000000000000000000020371375755266700325730ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 VoyagerSearch and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jackson; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.core.Versioned; import com.fasterxml.jackson.core.util.VersionUtil; /** * Automatically generated from PackageVersion.java.in during * packageVersion-generate execution of maven-replacer-plugin in * pom.xml. */ public final class PackageVersion implements Versioned { public final static Version VERSION = VersionUtil.parseVersion( "0.8-SNAPSHOT", "org.locationtech.spatial4j", "spatial4j"); @Override public Version version() { return VERSION; } }spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jackson/PackageVersion.java.in000066400000000000000000000020431375755266700331750ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 VoyagerSearch and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jackson; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.core.Versioned; import com.fasterxml.jackson.core.util.VersionUtil; /** * Automatically generated from PackageVersion.java.in during * packageVersion-generate execution of maven-replacer-plugin in * pom.xml. */ public final class PackageVersion implements Versioned { public final static Version VERSION = VersionUtil.parseVersion( "@projectversion@", "@projectgroupid@", "@projectartifactid@"); @Override public Version version() { return VERSION; } }ShapeAsGeoJSONSerializer.java000066400000000000000000000157261375755266700343270ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jackson/******************************************************************************* * Copyright (c) 2015 VoyagerSearch and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jackson; import java.io.IOException; import java.util.Iterator; import org.locationtech.spatial4j.distance.DistanceUtils; import org.locationtech.spatial4j.shape.Circle; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.ShapeCollection; import org.locationtech.spatial4j.shape.impl.BufferedLine; import org.locationtech.spatial4j.shape.impl.BufferedLineString; import org.locationtech.spatial4j.shape.impl.GeoCircle; import org.locationtech.spatial4j.shape.jts.JtsGeometry; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; public class ShapeAsGeoJSONSerializer extends JsonSerializer { static final String BUFFER = "buffer"; static final String BUFFER_UNITS = "buffer_units"; final GeometryAsGeoJSONSerializer forJTS = new GeometryAsGeoJSONSerializer(); protected void write(JsonGenerator gen, double... coords) throws IOException { gen.writeStartArray(); for (double coord : coords) { if (Double.isNaN(coord)) { break; // empty } gen.writeNumber(coord); } gen.writeEndArray(); } // Keep 6 decimal places public static double round(double v) { return Math.round(v * 1000000d) / 1000000d; } /** * Helper method to encode a distance property (with optional unit). *

* The distance unit is only encoded when isGeo is true, and it is converted to km. *

*

* The distance unit is encoded within a properties object. *

* @param output The writer. * @param nf The number format. * @param dist The distance value to encode. * @param isGeo The flag determining * @param distProperty The distance property name. * @param distUnitsProperty The distance unit property name. */ void writeDistance(JsonGenerator gen, double dist, boolean isGeo, String distProperty, String distUnitsProperty) throws IOException { gen.writeFieldName(distProperty); if (isGeo) { double distKm = DistanceUtils.degrees2Dist(dist, DistanceUtils.EARTH_MEAN_RADIUS_KM); gen.writeNumber( round(distKm) ); gen.writeFieldName("properties"); gen.writeStartObject(); gen.writeFieldName(distUnitsProperty); gen.writeString("km"); gen.writeEndObject(); } else { gen.writeNumber(dist); } } public void write(JsonGenerator gen, Shape shape) throws IOException { if (shape == null) { throw new NullPointerException("Shape can not be null"); } if (shape instanceof Point) { Point v = (Point) shape; gen.writeStartObject(); gen.writeFieldName("type"); gen.writeString("Point"); gen.writeFieldName("coordinates"); write(gen, v.getX(), v.getY()); gen.writeEndObject(); return; } if (shape instanceof Rectangle) { Rectangle v = (Rectangle) shape; gen.writeStartObject(); gen.writeFieldName("type"); gen.writeString("Polygon"); gen.writeFieldName("coordinates"); gen.writeStartArray(); gen.writeStartArray(); write(gen, v.getMinX(), v.getMinY()); write(gen, v.getMinX(), v.getMaxY()); write(gen, v.getMaxX(), v.getMaxY()); write(gen, v.getMaxX(), v.getMinY()); write(gen, v.getMinX(), v.getMinY()); gen.writeEndArray(); gen.writeEndArray(); gen.writeEndObject(); return; } if (shape instanceof BufferedLine) { BufferedLine v = (BufferedLine) shape; gen.writeStartObject(); gen.writeFieldName("type"); gen.writeString("LineString"); gen.writeFieldName("coordinates"); gen.writeStartArray(); write(gen, v.getA().getX(), v.getA().getY()); write(gen, v.getB().getX(), v.getB().getY()); gen.writeEndArray(); if (v.getBuf() > 0) { writeDistance(gen, v.getBuf(), shape.getContext().isGeo(), BUFFER, BUFFER_UNITS); } gen.writeEndObject(); return; } if (shape instanceof BufferedLineString) { BufferedLineString v = (BufferedLineString) shape; gen.writeStartObject(); gen.writeFieldName("type"); gen.writeString("LineString"); gen.writeFieldName("coordinates"); gen.writeStartArray(); BufferedLine last = null; Iterator iter = v.getSegments().iterator(); while (iter.hasNext()) { BufferedLine seg = iter.next(); write(gen, seg.getA().getX(), seg.getA().getY()); last = seg; } if (last != null) { write(gen, last.getB().getX(), last.getB().getY()); } gen.writeEndArray(); if (v.getBuf() > 0) { writeDistance(gen, v.getBuf(), shape.getContext().isGeo(), BUFFER, BUFFER_UNITS); } gen.writeEndObject(); return; } if (shape instanceof Circle) { // See: https://github.com/geojson/geojson-spec/wiki/Proposal---Circles-and-Ellipses-Geoms Circle v = (Circle) shape; Point center = v.getCenter(); gen.writeStartObject(); gen.writeFieldName("type"); gen.writeString("Circle"); gen.writeFieldName("coordinates"); write(gen, center.getX(), center.getY()); writeDistance(gen, v.getRadius(), v instanceof GeoCircle, "radius", "radius_units"); gen.writeEndObject(); return; } if (shape instanceof ShapeCollection) { ShapeCollection v = (ShapeCollection) shape; gen.writeStartObject(); gen.writeFieldName("type"); gen.writeString("GeometryCollection"); gen.writeFieldName("geometries"); gen.writeStartArray(); for (int i = 0; i < v.size(); i++) { write(gen, v.get(i)); } gen.writeEndArray(); gen.writeEndObject(); return; } // Write the unknown geometry to WKT gen.writeStartObject(); gen.writeFieldName("type"); gen.writeString("Unknown"); gen.writeFieldName("wkt"); gen.writeString(shape.getContext().getFormats().getWktWriter().toString(shape)); gen.writeEndObject(); } @Override public void serialize(Shape shape, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { // Use the specialized class if (shape instanceof JtsGeometry) { forJTS.serialize(((JtsGeometry) shape).getGeom(), gen, serializers); return; } write(gen, shape); } }ShapeAsWKTSerializer.java000066400000000000000000000023061375755266700335560ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jackson/******************************************************************************* * Copyright (c) 2015 VoyagerSearch and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jackson; import java.io.IOException; import java.io.StringWriter; import org.locationtech.spatial4j.shape.Shape; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; public class ShapeAsWKTSerializer extends JsonSerializer { @Override public void serialize(Shape value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { StringWriter str = new StringWriter(); value.getContext().getFormats().getWktWriter().write(str, value); gen.writeString(str.toString()); } }spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jackson/ShapeDeserializer.java000066400000000000000000000166521375755266700333050ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 VoyagerSearch and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jackson; import java.io.IOException; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.distance.DistanceUtils; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.ShapeFactory; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; public class ShapeDeserializer extends JsonDeserializer { public final SpatialContext ctx; public ShapeDeserializer() { this(JtsSpatialContext.GEO); } public ShapeDeserializer(SpatialContext ctx) { this.ctx = ctx; } public Point readPoint(ArrayNode arr, ShapeFactory factory) { if(arr.size()==0) { return factory.pointXY(Double.NaN, Double.NaN); } double x = arr.get(0).asDouble(); double y = arr.get(1).asDouble(); if(arr.size()==3) { double z = arr.get(3).asDouble(); return factory.pointXYZ(x, y, z); } return factory.pointXY(x, y); } private void fillPoints( ShapeFactory.PointsBuilder b, ArrayNode arrs ) { for(int i=0; i b = factory.multiShape(Shape.class); ArrayNode arr = (ArrayNode)node.get("geometries"); for(int i=0; i0) { try { return ctx.getFormats().read(txt); } catch (Exception e) { throw new JsonParseException(jp, "error reading shape", e); } } return null; // empty string } throw new JsonParseException(jp, "can't read GeoJSON yet"); } }ShapesAsGeoJSONModule.java000066400000000000000000000027041375755266700336160ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jackson/******************************************************************************* * Copyright (c) 2015 VoyagerSearch and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jackson; import org.locationtech.spatial4j.shape.Shape; import com.fasterxml.jackson.databind.module.SimpleModule; import org.locationtech.jts.geom.Geometry; public class ShapesAsGeoJSONModule extends SimpleModule { private static final long serialVersionUID = 1L; public ShapesAsGeoJSONModule() { super(PackageVersion.VERSION); // first deserializers addDeserializer(Geometry.class, new GeometryDeserializer()); addDeserializer(Shape.class, new ShapeDeserializer()); // then serializers: addSerializer(Geometry.class, new GeometryAsGeoJSONSerializer()); addSerializer(Shape.class, new ShapeAsGeoJSONSerializer()); } // will try to avoid duplicate registations (if MapperFeature enabled) @Override public String getModuleName() { return getClass().getSimpleName(); } @Override public int hashCode() { return getClass().hashCode(); } @Override public boolean equals(Object o) { return this == o; } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jackson/ShapesAsWKTModule.java000066400000000000000000000026641375755266700331430ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 VoyagerSearch and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jackson; import org.locationtech.spatial4j.shape.Shape; import com.fasterxml.jackson.databind.module.SimpleModule; import org.locationtech.jts.geom.Geometry; public class ShapesAsWKTModule extends SimpleModule { private static final long serialVersionUID = 1L; public ShapesAsWKTModule() { super(PackageVersion.VERSION); // first deserializers addDeserializer(Geometry.class, new GeometryDeserializer()); addDeserializer(Shape.class, new ShapeDeserializer()); // then serializers: addSerializer(Geometry.class, new GeometryAsWKTSerializer()); addSerializer(Shape.class, new ShapeAsWKTSerializer()); } // will try to avoid duplicate registations (if MapperFeature enabled) @Override public String getModuleName() { return getClass().getSimpleName(); } @Override public int hashCode() { return getClass().hashCode(); } @Override public boolean equals(Object o) { return this == o; } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jackson/package-info.java000066400000000000000000000011131375755266700322100ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 VoyagerSearch and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ /** * Optional support to read/write Shapes and Geometry using Jackson */ package org.locationtech.spatial4j.io.jackson; spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jts/000077500000000000000000000000001375755266700261755ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jts/JtsBinaryCodec.java000066400000000000000000000110661375755266700317070ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jts; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.io.BinaryCodec; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.PrecisionModel; import org.locationtech.jts.io.InStream; import org.locationtech.jts.io.OutStream; import org.locationtech.jts.io.ParseException; import org.locationtech.jts.io.WKBConstants; import org.locationtech.jts.io.WKBReader; import org.locationtech.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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jts/JtsGeoJSONWriter.java000066400000000000000000000134301375755266700321230ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 VoyagerSearch * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jts; import java.io.IOException; import java.io.Writer; import java.text.NumberFormat; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.io.GeoJSONWriter; import org.locationtech.spatial4j.io.LegacyShapeWriter; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.jts.JtsGeometry; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.CoordinateSequence; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryCollection; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.MultiLineString; import org.locationtech.jts.geom.MultiPoint; import org.locationtech.jts.geom.MultiPolygon; import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Polygon; public class JtsGeoJSONWriter extends GeoJSONWriter { protected final JtsSpatialContext ctx; public JtsGeoJSONWriter(JtsSpatialContext ctx, SpatialContextFactory factory) { super(ctx, factory); this.ctx = ctx; } // -------------------------------------------------------------- // Write JTS To GeoJSON // -------------------------------------------------------------- protected void write(Writer output, NumberFormat nf, Coordinate coord) throws IOException { output.write('['); output.write(nf.format(coord.x)); output.write(','); output.write(nf.format(coord.y)); output.write(']'); } protected void write(Writer output, NumberFormat nf, CoordinateSequence coordseq) throws IOException { output.write('['); int dim = coordseq.getDimension(); for (int i = 0; i < coordseq.size(); i++) { if (i > 0) { output.write(','); } output.write('['); output.write(nf.format(coordseq.getOrdinate(i, 0))); output.write(','); output.write(nf.format(coordseq.getOrdinate(i, 1))); if (dim > 2) { double v = coordseq.getOrdinate(i, 2); if (!Double.isNaN(v)) { output.write(','); output.write(nf.format(v)); } } output.write(']'); } output.write(']'); } protected void write(Writer output, NumberFormat nf, Coordinate[] coord) throws IOException { output.write('['); for (int i = 0; i < coord.length; i++) { if (i > 0) { output.append(','); } write(output, nf, coord[i]); } output.write(']'); } protected void write(Writer output, NumberFormat nf, Polygon p) throws IOException { output.write('['); write(output, nf, p.getExteriorRing().getCoordinateSequence()); for (int i = 0; i < p.getNumInteriorRing(); i++) { output.append(','); write(output, nf, p.getInteriorRingN(i).getCoordinateSequence()); } output.write(']'); } public void write(Writer output, Geometry geom) throws IOException { NumberFormat nf = LegacyShapeWriter.makeNumberFormat(6); if (geom instanceof Point) { Point v = (Point) geom; output.append("{\"type\":\"Point\",\"coordinates\":"); write(output, nf, v.getCoordinate()); output.append("}"); return; } else if (geom instanceof Polygon) { output.append("{\"type\":\"Polygon\",\"coordinates\":"); write(output, nf, (Polygon) geom); output.append("}"); return; } else if (geom instanceof LineString) { LineString v = (LineString) geom; output.append("{\"type\":\"LineString\",\"coordinates\":"); write(output, nf, v.getCoordinateSequence()); output.append("}"); return; } else if (geom instanceof MultiPoint) { MultiPoint v = (MultiPoint) geom; output.append("{\"type\":\"MultiPoint\",\"coordinates\":"); write(output, nf, v.getCoordinates()); output.append("}"); return; } else if (geom instanceof MultiLineString) { MultiLineString v = (MultiLineString) geom; output.append("{\"type\":\"MultiLineString\",\"coordinates\":["); for (int i = 0; i < v.getNumGeometries(); i++) { if (i > 0) { output.append(','); } write(output, nf, v.getGeometryN(i).getCoordinates()); } output.append("]}"); } else if (geom instanceof MultiPolygon) { MultiPolygon v = (MultiPolygon) geom; output.append("{\"type\":\"MultiPolygon\",\"coordinates\":["); for (int i = 0; i < v.getNumGeometries(); i++) { if (i > 0) { output.append(','); } write(output, nf, (Polygon) v.getGeometryN(i)); } output.append("]}"); } else if (geom instanceof GeometryCollection) { GeometryCollection v = (GeometryCollection) geom; output.append("{\"type\":\"GeometryCollection\",\"geometries\":["); for (int i = 0; i < v.getNumGeometries(); i++) { if (i > 0) { output.append(','); } write(output, v.getGeometryN(i)); } output.append("]}"); } else { throw new UnsupportedOperationException("unknown: " + geom); } } @Override public void write(Writer output, Shape shape) throws IOException { if (shape == null) { throw new NullPointerException("Shape can not be null"); } if (shape instanceof JtsGeometry) { write(output, ((JtsGeometry) shape).getGeom()); return; } super.write(output, shape); } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jts/JtsPolyshapeWriter.java000066400000000000000000000102331375755266700326610ustar00rootroot00000000000000/* * 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 org.locationtech.spatial4j.io.jts; import java.io.IOException; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.io.PolyshapeWriter; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.jts.JtsGeometry; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.CoordinateSequence; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryCollection; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.MultiPoint; import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Polygon; public class JtsPolyshapeWriter extends PolyshapeWriter { protected final JtsSpatialContext ctx; public JtsPolyshapeWriter(JtsSpatialContext ctx, SpatialContextFactory factory) { super(ctx, factory); this.ctx = ctx; } // -------------------------------------------------------------- // Write JTS To GeoJSON // -------------------------------------------------------------- protected void write(Encoder output, CoordinateSequence coordseq) throws IOException { int dim = coordseq.getDimension(); // if(dim>2) { // throw new IllegalArgumentException("only supports 2d geometry now ("+dim+")"); // } for (int i = 0; i < coordseq.size(); i++) { output.write(coordseq.getOrdinate(i, 0), coordseq.getOrdinate(i, 1)); } } protected void write(Encoder output, Coordinate[] coord) throws IOException { for (int i = 0; i < coord.length; i++) { output.write(coord[i].x, coord[i].y); } } protected void write(Encoder output, Polygon p) throws IOException { output.write(PolyshapeWriter.KEY_POLYGON); write(output, p.getExteriorRing().getCoordinateSequence()); for (int i = 0; i < p.getNumInteriorRing(); i++) { output.startRing(); write(output, p.getInteriorRingN(i).getCoordinateSequence()); } } public void write(Encoder output, Geometry geom) throws IOException { if (geom instanceof Point) { Point v = (Point) geom; output.write(PolyshapeWriter.KEY_POINT); write(output, v.getCoordinateSequence()); return; } else if (geom instanceof Polygon) { write(output, (Polygon) geom); return; } else if (geom instanceof LineString) { LineString v = (LineString) geom; output.write(PolyshapeWriter.KEY_LINE); write(output, v.getCoordinateSequence()); return; } else if (geom instanceof MultiPoint) { MultiPoint v = (MultiPoint) geom; output.write(PolyshapeWriter.KEY_MULTIPOINT); write(output, v.getCoordinates()); return; } else if (geom instanceof GeometryCollection) { GeometryCollection v = (GeometryCollection) geom; for (int i = 0; i < v.getNumGeometries(); i++) { if (i > 0) { output.seperator(); } write(output, v.getGeometryN(i)); } } else { throw new UnsupportedOperationException("unknown: " + geom); } } @Override public void write(Encoder enc, Shape shape) throws IOException { if (shape == null) { throw new NullPointerException("Shape can not be null"); } if (shape instanceof JtsGeometry) { write(enc, ((JtsGeometry) shape).getGeom()); return; } super.write(enc, shape); } }spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jts/JtsWKTReaderShapeParser.java000066400000000000000000000110341375755266700334460ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jts; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.distance.DistanceUtils; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.jts.JtsPoint; import org.locationtech.spatial4j.shape.jts.JtsShapeFactory; import org.locationtech.jts.geom.CoordinateSequence; import org.locationtech.jts.geom.CoordinateSequenceFilter; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.io.WKTReader; import java.text.ParseException; /** * This is an extension of Spatial4j's {@link org.locationtech.spatial4j.io.WKTReader} that processes the entire * string with JTS's {@link org.locationtech.jts.io.WKTReader}. Some differences: *
    *
  • No support for ENVELOPE and BUFFER
  • *
  • MULTI* shapes use JTS's {@link org.locationtech.jts.geom.GeometryCollection} subclasses, * not {@link org.locationtech.spatial4j.shape.ShapeCollection}
  • *
  • 'Z' coordinates are saved into the geometry
  • *
* */ @Deprecated public class JtsWKTReaderShapeParser extends org.locationtech.spatial4j.io.WKTReader { //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(getShapeFactory().getGeometryFactory())); } private JtsShapeFactory getShapeFactory() { return ((JtsShapeFactory)shapeFactory); } /** * Reads WKT from the {@code str} via JTS's {@link org.locationtech.jts.io.WKTReader}. * * @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 org.locationtech.jts.geom.Point) { org.locationtech.jts.geom.Point ptGeom = (org.locationtech.jts.geom.Point) geom; if (getShapeFactory().useJtsPoint()) return new JtsPoint(ptGeom, (JtsSpatialContext) ctx); else return getShapeFactory().pointXY(ptGeom.getX(), ptGeom.getY()); } else if (geom.isRectangle()) { return getShapeFactory().makeRectFromRectangularPoly(geom); } else { return getShapeFactory().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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jts/JtsWKTWriter.java000066400000000000000000000022061375755266700313630ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 VoyagerSearch * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jts; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.io.WKTWriter; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.jts.JtsGeometry; /** * Writes the WKT using JTS directly */ public class JtsWKTWriter extends WKTWriter { public JtsWKTWriter(JtsSpatialContext ctx, JtsSpatialContextFactory factory) { } @Override public String toString(Shape shape) { if (shape instanceof JtsGeometry) { return ((JtsGeometry) shape).getGeom().toText(); } return super.toString(shape); } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/package-info.java000066400000000000000000000010531375755266700305630ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ /** Reading & writing shapes in various forms. */ package org.locationtech.spatial4j.io;spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/package-info.java000066400000000000000000000011161375755266700301540ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ /** * This is the base package for Spatial4j from which the rest of it is organized. */ package org.locationtech.spatial4j; spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/000077500000000000000000000000001375755266700260665ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/BaseShape.java000066400000000000000000000014051375755266700305640ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 VoyagerSearch * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape; import org.locationtech.spatial4j.context.SpatialContext; public abstract class BaseShape implements Shape { protected final T ctx; public BaseShape(T ctx) { this.ctx = ctx; } @Override public T getContext() { return ctx; } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/Circle.java000066400000000000000000000021211375755266700301260ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/Point.java000066400000000000000000000025601375755266700300250ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.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(); /** Convenience method that usually maps on {@link org.locationtech.spatial4j.shape.Point#getY()} */ public default double getLat() { return getY(); } /** Convenience method that usually maps on {@link org.locationtech.spatial4j.shape.Point#getX()} */ public default double getLon() { return getX(); } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/Rectangle.java000066400000000000000000000044311375755266700306370ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.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 dateline (aka anti-meridian). 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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/Shape.java000066400000000000000000000073451375755266700300020ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape; import org.locationtech.spatial4j.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. * * @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); /** * Get the SpatialContext that created the Shape */ public SpatialContext getContext(); } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/ShapeCollection.java000066400000000000000000000162341375755266700320130ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.impl.BBoxCalculator; import java.util.*; import static org.locationtech.spatial4j.shape.SpatialRelation.CONTAINS; import static org.locationtech.spatial4j.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 SpatialContext ctx; 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) */ public ShapeCollection(List shapes, SpatialContext ctx) { if (!(shapes instanceof RandomAccess)) throw new IllegalArgumentException("Shapes arg must implement RandomAccess: "+shapes.getClass()); this.shapes = shapes; this.ctx = ctx; 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); BBoxCalculator bboxCalc = new BBoxCalculator(ctx); for (Shape geom : shapes) { bboxCalc.expandRange(geom.getBoundingBox()); } return bboxCalc.getBoundary(); } 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(); } @Override public SpatialContext getContext() { return ctx; } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/ShapeFactory.java000066400000000000000000000157361375755266700313350ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2016 David Smiley * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape; import org.locationtech.spatial4j.context.SpatialContext; import java.util.List; /** * A factory for {@link Shape}s. * Stateless and thread-safe, except for any returned builders. */ public interface ShapeFactory { SpatialContext getSpatialContext(); /** If true then {@link #normX(double)} will wrap longitudes outside of the standard * geodetic boundary into it. Example: 181 will become -179. */ boolean isNormWrapLongitude(); // TODO annoying that a ShapeReader must remember to call norm* methods. Perhaps // there should be another shapeFactory impl for shape reading? :-/ Or not. /** Normalize the 'x' dimension. Might reduce precision or wrap it to be within the bounds. This * is called by {@link org.locationtech.spatial4j.io.ShapeReader}s before creating a shape. */ double normX(double x); /** @see #normX(double) */ double normY(double y); /** (disclaimer: the Z dimension isn't fully supported) * @see #normX(double) */ double normZ(double z); /** * Called to normalize a value that isn't X or Y or Z. X & Y & Z are normalized via * {@link org.locationtech.spatial4j.context.SpatialContext#normX(double)} & normY & normZ. This * is called by a {@link org.locationtech.spatial4j.io.ShapeReader} before creating a shape. */ double normDist(double d); /** Ensure fits in the world bounds. It's called by any shape factory method that * gets an 'x' dimension. */ void verifyX(double x); /** @see #verifyX(double) */ void verifyY(double y); /** (disclaimer: the Z dimension isn't fully supported) * @see #verifyX(double) */ void verifyZ(double z); /** Construct a point. */ Point pointXY(double x, double y); /** Construct a point of latitude, longitude coordinates */ default Point pointLatLon(double latitude, double longitude) { return pointXY(longitude, latitude); } /** Construct a point of 3 dimensions. The implementation might ignore unsupported * dimensions like 'z' or throw an error. */ Point pointXYZ(double x, double y, double z); /** Construct a rectangle. */ Rectangle rect(Point lowerLeft, Point upperRight); /** * Construct a rectangle. If just one longitude is on the dateline (+/- 180) and if * {@link SpatialContext#isGeo()} * then potentially adjust its sign to ensure the rectangle does not cross the * dateline (aka anti-meridian). */ Rectangle rect(double minX, double maxX, double minY, double maxY); /** Construct a circle. The units of "distance" should be the same as x & y. */ Circle circle(double x, double y, double distance); /** Construct a circle. The units of "distance" should be the same as x & y. */ Circle circle(Point point, double distance); /** Constructs a line string with a possible buffer. 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. */ @Deprecated // use a builder Shape lineString(List points, double buf); /** Construct a ShapeCollection, analogous to an OGC GeometryCollection. */ @Deprecated // use a builder ShapeCollection multiShape(List coll); // BUILDERS: /** (Builder) Constructs a line string, with a possible buffer. * It's an ordered sequence of connected vertexes. * There is no official shape/interface for it yet so we just return Shape. */ LineStringBuilder lineString(); /** (Builder) Constructs a polygon. * There is no official shape/interface for it yet so we just return Shape. */ PolygonBuilder polygon(); /** (Builder) Constructs a Shape aggregate in which each component/member * is an instance of the specified class. */ MultiShapeBuilder multiShape(Class shapeClass); /** (Builder) Constructs a MultiPoint. */ MultiPointBuilder multiPoint(); /** (Builder) Constructs a MultiLineString, or possibly the result of that buffered. */ MultiLineStringBuilder multiLineString(); /** (Builder) Constructs a MultiPolygon. */ MultiPolygonBuilder multiPolygon(); // misc: //Shape buffer(Shape shape); ? // TODO need Polygon shape // TODO need LineString shape // TODO need BufferedLineString shape // TODO need ShapeCollection to be typed /** Builds a point and returns the generic specified type (usually whatever "this" is). */ interface PointsBuilder { /** @see ShapeFactory#pointXY(double, double) */ T pointXY(double x, double y); /** @see ShapeFactory#pointXYZ(double, double, double) */ T pointXYZ(double x, double y, double z); /** @see ShapeFactory#pointLatLon(double, double) */ default T pointLatLon(double latitude, double longitude) { return pointXY(longitude, latitude); } } /** @see #lineString() */ interface LineStringBuilder extends PointsBuilder { // TODO add dimensionality hint method? LineStringBuilder buffer(double distance); Shape build(); } /** @see #polygon() */ interface PolygonBuilder extends PointsBuilder { // TODO add dimensionality hint method? /** Starts a new hole. You must add at least 4 points; furthermore the first and last must be the same. * And don't forget to call {@link HoleBuilder#endHole()}! */ HoleBuilder hole(); /** Builds the polygon and renders this builder instance invalid. */ Shape build();// never a Rect Shape buildOrRect(); interface HoleBuilder extends PointsBuilder { /** Finishes the hole and returns the {@link PolygonBuilder}.*/ PolygonBuilder endHole(); } } // TODO add dimensionality hint method to the multi* builders? /** @see #multiShape(Class) */ interface MultiShapeBuilder { // TODO add dimensionality hint method? MultiShapeBuilder add(T shape); //ShapeCollection build(); TODO wait till it's a typed interface Shape build(); } /** @see #multiPoint() */ interface MultiPointBuilder extends PointsBuilder { Shape build(); // TODO MultiShape } /** @see #multiLineString() */ interface MultiLineStringBuilder { LineStringBuilder lineString(); MultiLineStringBuilder add(LineStringBuilder lineStringBuilder); Shape build(); // TODO MultiShape } /** @see #multiPolygon() */ interface MultiPolygonBuilder { PolygonBuilder polygon(); MultiPolygonBuilder add(PolygonBuilder polygonBuilder); Shape build(); // TODO MultiShape } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/SpatialRelation.java000066400000000000000000000106471375755266700320340ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.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}. If {@code other} is null then the * result is "this". */ 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.) // 7. X + null == X; if (other == this || other == null) 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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/impl/000077500000000000000000000000001375755266700270275ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/impl/BBoxCalculator.java000066400000000000000000000164721375755266700325500ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 David Smiley * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape.impl; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.Rectangle; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; /** * (INTERNAL) Calculates the minimum bounding box given a bunch of rectangles (ranges). It's a temporary object and not * thread-safe; throw it away when done. * For a cartesian space, the calculations are trivial but it is not for geodetic. For * geodetic, it must maintain an ordered set of disjoint ranges as each range is provided. */ public class BBoxCalculator { private final SpatialContext ctx; private double minY = Double.POSITIVE_INFINITY; private double maxY = Double.NEGATIVE_INFINITY; private double minX = Double.POSITIVE_INFINITY; private double maxX = Double.NEGATIVE_INFINITY; /** Sorted list of disjoint X ranges keyed by maxX and with minX stored as the "value". */ private TreeMap ranges; // maxX -> minX // note: The use of a TreeMap of Double objects is a bit heavy for the points-only use-case. In such a case, // we could instead maintain an array of longitudes we just add onto during expandXRange(). A simplified version // of the processRanges() method could be used that initially sorts and then proceeds in a similar but simplified // fashion. public BBoxCalculator(SpatialContext ctx) { this.ctx = ctx; } public void expandRange(Rectangle rect) { expandRange(rect.getMinX(), rect.getMaxX(), rect.getMinY(), rect.getMaxY()); } public void expandRange(final double minX, final double maxX, double minY, double maxY) { this.minY = Math.min(this.minY, minY); this.maxY = Math.max(this.maxY, maxY); expandXRange(minX, maxX); }//expandRange public void expandXRange(double minX, double maxX) { if (!ctx.isGeo()) { this.minX = Math.min(this.minX, minX); this.maxX = Math.max(this.maxX, maxX); return; } if (doesXWorldWrap()) return; if (ranges == null) { ranges = new TreeMap<>(); ranges.put(maxX, minX); return; } assert !ranges.isEmpty(); //now the hard part! //Get an iterator starting from the first entry that either contains minX or it's to the right of minX Iterator> entryIter = ranges.tailMap(minX, true/*inclusive*/).entrySet().iterator(); if (!entryIter.hasNext()) { entryIter = ranges.entrySet().iterator();//wrapped across dateline } Map.Entry entry = entryIter.next(); Double entryMin = entry.getValue(); Double entryMax = entry.getKey(); //See if entry contains maxX if (rangeContains(entryMin, entryMax, maxX)) { // Easy: either minX is also within this entry in which case nothing to do, or it's below in which case // we just need to update the minX of this entry. //See if entry contains minX if (rangeContains(entryMin, entryMax, minX)) { // This entry & the new range together might wrap the world. if ( (minX != entryMin || maxX != entryMax) //ranges not equal && rangeContains(minX, maxX, entryMin) && rangeContains(minX, maxX, entryMax)) { this.minX = -180; this.maxX = +180; ranges = null; } // Done; nothing to do. } else { //Done: Update entry's start to be minX // note: TreeMap's Map.Entry doesn't support setting the value :-( So we remove & add the entry. entryIter.remove(); ranges.put(entryMax, minX); } } else {//entry does NOT contain maxX: // We're going to insert an entry. Determine it's min & max. While finding the max, we'll delete entries // that overlap with the new entry. // newMinX is basically the lower of minX & entryMin final Double newMinX = rangeContains(entryMin, entryMax, minX) ? entryMin : minX; Double newMaxX = maxX; //Loop through entries (starting with current) to see if we should remove it. At the last one, update newMaxX. while (rangeContains(newMinX, newMaxX, entryMin)) { entryIter.remove();//remove entry! if (!rangeContains(minX, maxX, entryMax)) { newMaxX = entryMax;//adjust newMaxX and stop. break; } // get new entry: if (!entryIter.hasNext()) { if (ranges.isEmpty()) { break; } //wrap around (can only happen once) entryIter = ranges.entrySet().iterator(); } entry = entryIter.next(); entryMin = entry.getValue(); entryMax = entry.getKey(); } //Add entry ranges.put(newMaxX, newMinX); } } private void processRanges() { if (ranges.size() == 1) { // an optimization Map.Entry rangeEntry = ranges.firstEntry(); minX = rangeEntry.getValue(); maxX = rangeEntry.getKey(); } else { // Find the biggest gap. Whenever we do, update minX & maxX for the rect opposite of the gap. Map.Entry prevRange = ranges.lastEntry(); double biggestGap = 0; double possibleRemainingGap = 360; //calculating this enables us to exit early; often on the first lap! for (Map.Entry range : ranges.entrySet()) { // calc width of this range and the gap before it. double widthPlusGap = range.getKey() - prevRange.getKey();// this max - last max if (widthPlusGap < 0) { widthPlusGap += 360; } double gap = range.getValue() - prevRange.getKey(); // this min - last max if (gap < 0) { gap += 360; } // reduce possibleRemainingGap by this range width and trailing gap. possibleRemainingGap -= widthPlusGap; if (gap > biggestGap) { biggestGap = gap; minX = range.getValue(); maxX = prevRange.getKey(); if (possibleRemainingGap <= biggestGap) { break;// no point in continuing } } prevRange = range; } } // Null out the ranges to signify we processed them ranges = null; } private static boolean rangeContains(double minX, double maxX, double x) { if (minX <= maxX) return x >= minX && x <= maxX; else return x >= minX || x <= maxX; } public boolean doesXWorldWrap() { assert ctx.isGeo(); //note: not dependent on "ranges", since once we expand to world bounds then ranges is null'ed out return minX == -180 && maxX == 180; } public Rectangle getBoundary() { return ctx.makeRectangle(getMinX(), getMaxX(), getMinY(), getMaxY()); } public double getMinX() { if (ranges != null) { processRanges(); } return minX; } public double getMaxX() { if (ranges != null) { processRanges(); } return maxX; } public double getMinY() { return minY; } public double getMaxY() { return maxY; } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/impl/BufferedLine.java000066400000000000000000000177511375755266700322370ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape.impl; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.distance.DistanceUtils; import org.locationtech.spatial4j.shape.BaseShape; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.SpatialRelation; import static org.locationtech.spatial4j.shape.SpatialRelation.CONTAINS; import static org.locationtech.spatial4j.shape.SpatialRelation.DISJOINT; import static org.locationtech.spatial4j.shape.SpatialRelation.INTERSECTS; import static org.locationtech.spatial4j.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 anti-meridian); it operates in Euclidean * space. */ public class BufferedLine extends BaseShape { 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 */ public BufferedLine(Point pA, Point pB, double buf, SpatialContext ctx) { super(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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/impl/BufferedLineString.java000066400000000000000000000126501375755266700334170ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape.impl; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.BaseShape; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.ShapeCollection; import org.locationtech.spatial4j.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 org.locationtech.spatial4j.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 anti-meridian). */ public class BufferedLineString extends BaseShape { //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(org.locationtech.spatial4j.shape.Point, * org.locationtech.spatial4j.shape.Point, double)}. * If true then the buffer for each segment * is computed. */ public BufferedLineString(List points, double buf, boolean expandBufForLongitudeSkew, SpatialContext ctx) { super(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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/impl/CircleImpl.java000066400000000000000000000215661375755266700317270ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape.impl; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.BaseShape; import org.locationtech.spatial4j.shape.Circle; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.SpatialRelation; /** * A circle, also known as a point-radius, based on a {@link * org.locationtech.spatial4j.distance.DistanceCalculator} which does all the work. This * implementation should work for both cartesian 2D and geodetic sphere * surfaces. */ public class CircleImpl extends BaseShape implements Circle { protected final Point point; protected double radiusDEG; // calculated & cached protected Rectangle enclosingBox; public CircleImpl(Point p, double radiusDEG, SpatialContext ctx) { super(ctx); //We assume any validation of params already occurred (including bounding dist) 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 anti-meridian. */ @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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/impl/GeoCircle.java000066400000000000000000000211721375755266700315310ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape.impl; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.distance.DistanceUtils; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/impl/InfBufLine.java000066400000000000000000000114421375755266700316550ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape.impl; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.SpatialRelation; import static org.locationtech.spatial4j.shape.SpatialRelation.*; /** * INERNAL: A buffered line of infinite length. * Public for test access. */ public class InfBufLine { /** Error epsilon. */ private static final double EPS = 10e-14; //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 + EPS); } /** 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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/impl/PointImpl.java000066400000000000000000000066751375755266700316230ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape.impl; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.BaseShape; import org.locationtech.spatial4j.shape.Circle; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.SpatialRelation; /** A basic 2D implementation of a Point. */ public class PointImpl extends BaseShape implements Point { private double x; private double y; /** A simple constructor without normalization / validation. */ public PointImpl(double x, double y, SpatialContext ctx) { super(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 double getLat() { return getY(); } @Override public double getLon() { return getX(); } @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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/impl/Range.java000066400000000000000000000110521375755266700307250ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape.impl; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.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. */ @Deprecated // See BBoxCalculator 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; } @Deprecated // See BBoxCalculator 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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/impl/RectangleImpl.java000066400000000000000000000254001375755266700324210ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape.impl; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.distance.DistanceUtils; import org.locationtech.spatial4j.shape.BaseShape; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.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 extends BaseShape implements Rectangle { 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) { super(ctx); //TODO change to West South East North to be more consistent with OGC? 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 = Math.abs(maxY) > Math.abs(minY) ? 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 (getMinY() == rect.getMinY() && getMaxY() == rect.getMaxY()) return xIntersect; if (getMinX() == rect.getMinX() && getMaxX() == rect.getMaxX() || (ctx.isGeo() && verticalAtDateline(this, rect))) { return yIntersect; } return SpatialRelation.INTERSECTS; } //note: if vertical lines at the dateline were normalized (say to -180.0) then this method wouldn't be necessary. private static boolean verticalAtDateline(RectangleImpl rect1, Rectangle rect2) { if (rect1.getMinX() == rect1.getMaxX() && rect2.getMinX() == rect2.getMaxX()) { if (rect1.getMinX() == -180) { return rect2.getMinX() == +180; } else if (rect1.getMinX() == +180) { return rect2.getMinX() == -180; } } return false; } //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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/impl/ShapeFactoryImpl.java000066400000000000000000000176441375755266700331200ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape.impl; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.distance.DistanceUtils; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.shape.*; import java.util.ArrayList; import java.util.List; /** The default {@link org.locationtech.spatial4j.shape.ShapeFactory}. It does not support polygon shapes. */ public class ShapeFactoryImpl implements ShapeFactory { protected final SpatialContext ctx; private final boolean normWrapLongitude; public ShapeFactoryImpl(SpatialContext ctx, SpatialContextFactory factory) { this.ctx = ctx; this.normWrapLongitude = ctx.isGeo() && factory.normWrapLongitude; } @Override public SpatialContext getSpatialContext() { return ctx; } @Override public boolean isNormWrapLongitude() { return normWrapLongitude; } @Override public double normX(double x) { if (normWrapLongitude) x = DistanceUtils.normLonDEG(x); return x; } @Override public double normY(double y) { return y; } @Override public double normZ(double z) { return z; } @Override public double normDist(double d) { return d; } @Override public void verifyX(double x) { Rectangle bounds = ctx.getWorldBounds(); if (x < bounds.getMinX() || x > bounds.getMaxX())//NaN will pass throw new InvalidShapeException("Bad X value "+x+" is not in boundary "+bounds); } @Override public void verifyY(double y) { Rectangle bounds = ctx.getWorldBounds(); if (y < bounds.getMinY() || y > bounds.getMaxY())//NaN will pass throw new InvalidShapeException("Bad Y value "+y+" is not in boundary "+bounds); } @Override public void verifyZ(double z) { // bounds has no 'z' for this simple shapeFactory } @Override public Point pointXY(double x, double y) { verifyX(x); verifyY(y); return new PointImpl(x, y, ctx); } @Override public Point pointXYZ(double x, double y, double z) { return pointXY(x, y); // or throw? } @Override public Rectangle rect(Point lowerLeft, Point upperRight) { return rect(lowerLeft.getX(), upperRight.getX(), lowerLeft.getY(), upperRight.getY()); } @Override public Rectangle rect(double minX, double maxX, double minY, double maxY) { Rectangle bounds = ctx.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 (ctx.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, ctx); } @Override public Circle circle(double x, double y, double distance) { return circle(pointXY(x, y), distance); } @Override public Circle circle(Point point, double distance) { if (distance < 0) throw new InvalidShapeException("distance must be >= 0; got " + distance); if (ctx.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, ctx); } else { return new CircleImpl(point, distance, ctx); } } @Override public Shape lineString(List points, double buf) { return new BufferedLineString(points, buf, ctx.isGeo(), ctx); } @Override public LineStringBuilder lineString() { return new LineStringBuilder() { final List points = new ArrayList<>(); double bufferDistance = 0; @Override public LineStringBuilder buffer(double distance) { this.bufferDistance = distance; return this; } @Override public LineStringBuilder pointXY(double x, double y) { points.add(ShapeFactoryImpl.this.pointXY(x, y)); return this; } @Override public LineStringBuilder pointXYZ(double x, double y, double z) { points.add(ShapeFactoryImpl.this.pointXYZ(x, y, z)); return this; } @Override public LineStringBuilder pointLatLon(double latitude, double longitude) { points.add(ShapeFactoryImpl.this.pointLatLon(latitude, longitude)); return this; } @Override public Shape build() { return new BufferedLineString(points, bufferDistance, false, ctx); } }; } @Override public ShapeCollection multiShape(List coll) { return new ShapeCollection<>(coll, ctx); } @Override public MultiShapeBuilder multiShape(Class shapeClass) { return new GeneralShapeMultiShapeBuilder<>(); } @Override public MultiPointBuilder multiPoint() { return new GeneralShapeMultiShapeBuilder<>(); } @Override public MultiLineStringBuilder multiLineString() { return new GeneralShapeMultiShapeBuilder<>(); } @Override public MultiPolygonBuilder multiPolygon() { return new GeneralShapeMultiShapeBuilder<>(); } @Override public PolygonBuilder polygon() { throw new UnsupportedOperationException("Unsupported shape of this SpatialContext. Try JTS or Geo3D."); } protected class GeneralShapeMultiShapeBuilder implements MultiShapeBuilder, MultiPointBuilder, MultiLineStringBuilder, MultiPolygonBuilder { protected List shapes = new ArrayList<>(); @Override public MultiShapeBuilder add(T shape) { shapes.add(shape); return this; } @Override public MultiPointBuilder pointXY(double x, double y) { shapes.add(ShapeFactoryImpl.this.pointXY(x, y)); return this; } @Override public MultiPointBuilder pointXYZ(double x, double y, double z) { shapes.add(ShapeFactoryImpl.this.pointXYZ(x, y, z)); return this; } @Override public MultiPointBuilder pointLatLon(double latitude, double longitude) { shapes.add(ShapeFactoryImpl.this.pointLatLon(latitude, longitude)); return this; } @Override public LineStringBuilder lineString() { return ShapeFactoryImpl.this.lineString(); } @Override public MultiLineStringBuilder add(LineStringBuilder lineStringBuilder) { shapes.add(lineStringBuilder.build()); return this; } @Override public PolygonBuilder polygon() { return ShapeFactoryImpl.this.polygon(); } @Override public MultiPolygonBuilder add(PolygonBuilder polygonBuilder) { shapes.add(polygonBuilder.build()); return this; } @Override public Shape build() { return new ShapeCollection<>(shapes, ctx); } } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/jts/000077500000000000000000000000001375755266700266665ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/jts/JtsGeometry.java000077500000000000000000000604551375755266700320220ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape.jts; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.distance.CartesianDistCalc; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.shape.*; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.impl.BBoxCalculator; import org.locationtech.spatial4j.shape.impl.BufferedLineString; import org.locationtech.spatial4j.shape.impl.RectangleImpl; import org.locationtech.jts.geom.*; import org.locationtech.jts.geom.prep.PreparedGeometry; import org.locationtech.jts.geom.prep.PreparedGeometryFactory; import org.locationtech.jts.operation.union.UnaryUnionOp; import org.locationtech.jts.operation.valid.IsValidOp; import java.util.ArrayList; import java.util.Collection; 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 (aka anti-meridian) wrap. */ public class JtsGeometry extends BaseShape { /** 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 PreparedGeometry preparedGeometry; protected boolean validated = false; public JtsGeometry(Geometry geom, JtsSpatialContext ctx, boolean dateline180Check, boolean allowMultiOverlap) { super(ctx); //GeometryCollection isn't supported in relate() if (geom.getClass().equals(GeometryCollection.class)) { geom = narrowCollectionIfPossible((GeometryCollection)geom); if (geom == null) { 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 (geom.isEmpty()) { bbox = new RectangleImpl(Double.NaN, Double.NaN, Double.NaN, Double.NaN, this.ctx); } else if (ctx.isGeo()) { //Unwraps the geometry across the dateline so it exceeds the standard geo bounds (-180 to +180). if (dateline180Check) geom = unwrapDateline(geom);//returns same or new 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)); } /** * Attempts to retype a geometry collection under the following circumstances, returning * null if the collection can not be retyped. *

    *
  • Single object collections are collapsed down to the object.
  • *
  • Homogenous collections are recast as the appropriate subclass.
  • *
* * @see GeometryFactory#buildGeometry(Collection) */ private Geometry narrowCollectionIfPossible(GeometryCollection gc) { List geoms = new ArrayList<>(); for (int i = 0; i < gc.getNumGeometries(); i++) { geoms.add(gc.getGeometryN(i)); } Geometry result = gc.getFactory().buildGeometry(geoms); return !result.getClass().equals(GeometryCollection.class) ? result : null; } /** 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; } } /** * Determines if the shape has been indexed. */ boolean isIndexed() { return preparedGeometry != null; } /** * Adds an index to this class internally to compute spatial relations faster. In JTS this * is called a {@link org.locationtech.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 bbox.isEmpty(); // fast } /** 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) { final Envelope env = geoms.getEnvelopeInternal();//for minY & maxY (simple) if (ctx.isGeo() && env.getWidth() > 180 && geoms.getNumGeometries() > 1) { // This is ShapeCollection's bbox algorithm BBoxCalculator bboxCalc = new BBoxCalculator(ctx); for (int i = 0; i < geoms.getNumGeometries(); i++ ) { Envelope envI = geoms.getGeometryN(i).getEnvelopeInternal(); bboxCalc.expandXRange(envI.getMinX(), envI.getMaxX()); if (bboxCalc.doesXWorldWrap()) break; // can't grow any bigger } return new RectangleImpl(bboxCalc.getMinX(), bboxCalc.getMaxX(), 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(final Circle circle) { SpatialRelation bboxR = bbox.relate(circle); if (bboxR == SpatialRelation.WITHIN || bboxR == SpatialRelation.DISJOINT) return bboxR; // The result could be anything still. final SpatialRelation[] result = {null}; // Visit each geometry (this geom might contain others). geom.apply(new GeometryFilter() { // We use cartesian math. It's a limitation/assumption when working with JTS. When geo=true (i.e. we're using // WGS84 instead of a projected coordinate system), the errors here will be pretty terrible east-west. At // 60 degrees latitude, the circle will work as if it has half the width it should. // Instead, consider converting the circle to a polygon first (not great but better), or projecting both first. // Ideally, use Geo3D. final CartesianDistCalc calcSqd = CartesianDistCalc.INSTANCE_SQUARED; final double radiusSquared = circle.getRadius() * circle.getRadius(); final Geometry ctrGeom = ctx.getGeometryFrom(circle.getCenter()); @Override public void filter(Geometry geom) { if (result[0] == SpatialRelation.INTERSECTS || result[0] == SpatialRelation.CONTAINS) { // a previous filter(geom) call had a result that won't be changed no matter how this geom relates return; } if (geom instanceof Polygon) { Polygon polygon = (Polygon) geom; SpatialRelation rel = relateEnclosedRing((LinearRing) polygon.getExteriorRing()); // if rel == INTERSECTS or WITHIN or DISJOINT; done. But CONTAINS... if (rel == SpatialRelation.CONTAINS) { // if the poly outer ring contains the circle, check the holes. Could become DISJOINT or INTERSECTS. HOLE_LOOP: for (int i = 0; i < polygon.getNumInteriorRing(); i++){ // TODO short-circuit based on the hole's bbox if it's disjoint or within the circle. switch (relateEnclosedRing((LinearRing) polygon.getInteriorRingN(i))) { case WITHIN:// fall through case INTERSECTS: rel = SpatialRelation.INTERSECTS; break HOLE_LOOP; case CONTAINS: rel = SpatialRelation.DISJOINT; break HOLE_LOOP; //case DISJOINT: break; // continue hole loop } } } result[0] = rel.combine(result[0]); } else if (geom instanceof LineString) { LineString lineString = (LineString) geom; SpatialRelation rel = relateLineString(lineString); result[0] = rel.combine(result[0]); } else if (geom instanceof org.locationtech.jts.geom.Point) { org.locationtech.jts.geom.Point point = (org.locationtech.jts.geom.Point) geom; SpatialRelation rel = calcSqd.distance(circle.getCenter(), point.getX(), point.getY()) > radiusSquared ? SpatialRelation.DISJOINT : SpatialRelation.WITHIN; result[0] = rel.combine(result[0]); } // else it's going to be some GeometryCollection and we'll visit the contents. } /** As if the ring is the outer ring of a polygon */ SpatialRelation relateEnclosedRing(LinearRing ring) { SpatialRelation rel = relateLineString(ring); if (rel == SpatialRelation.DISJOINT && ctx.getGeometryFactory().createPolygon(ring, null).contains(ctrGeom)) { // If it contains the circle center point, then the result is CONTAINS rel = SpatialRelation.CONTAINS; } return rel; } SpatialRelation relateLineString(LineString lineString) { final CoordinateSequence seq = lineString.getCoordinateSequence(); final boolean isRing = lineString instanceof LinearRing; int numOutside = 0; // Compare the coordinates: for (int i = 0, numComparisons = 0; i < seq.size(); i++) { if (i == 0 && isRing) { continue; } numComparisons++; boolean outside = calcSqd.distance(circle.getCenter(), seq.getX(i), seq.getY(i)) > radiusSquared; if (outside) { numOutside++; } // If the comparisons have a mix of outside/inside, then we can short-circuit INTERSECTS. if (numComparisons != numOutside && numOutside != 0) { assert numComparisons > 1; return SpatialRelation.INTERSECTS; } } // Either all vertices are outside or inside, by this stage. if (numOutside == 0) { // all inside return SpatialRelation.WITHIN.combine(result[0]); } // They are all outside. // Check the edges (line segments) to see if any are inside. for (int i = 1; i < seq.size(); i++) { boolean outside = calcSqd.distanceToLineSegment( circle.getCenter(), seq.getX(i-1), seq.getY(i-1), seq.getX(i), seq.getY(i)) > radiusSquared; if (!outside) { return SpatialRelation.INTERSECTS; } } return SpatialRelation.DISJOINT; } }); return result[0] == null ? SpatialRelation.DISJOINT : result[0]; } 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 org.locationtech.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 (aka anti-meridian), 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. * * @return The same geometry or a new one if it was unwrapped */ private static Geometry unwrapDateline(Geometry geom) { if (geom.getEnvelopeInternal().getWidth() < 180) return geom;//can't possibly cross the dateline // if a multi-geom: (this is purely an optimization to avoid cloning more than we need to) if (geom instanceof GeometryCollection) { if (geom instanceof MultiPoint) { return geom; // always safe since no point crosses the dateline (on it is okay) } GeometryCollection gc = (GeometryCollection) geom; List list = new ArrayList<>(gc.getNumGeometries()); boolean didUnwrap = false; for (int n = 0; n < gc.getNumGeometries(); n++) { Geometry geometryN = gc.getGeometryN(n); Geometry geometryUnwrapped = unwrapDateline(geometryN); // recursion list.add(geometryUnwrapped); didUnwrap |= (geometryUnwrapped != geometryN); } return !didUnwrap ? geom : geom.getFactory().buildGeometry(list); } // a geom (not multi): Geometry newGeom = geom.copy(); // clone final int[] crossings = {0};//an array so that an inner class can modify it. newGeom.apply(new GeometryFilter() { @Override public void filter(Geometry geom) { int cross; 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 { // The only other JTS subclass of Geometry is a Point, which can't cross anything. // If the geom is something custom, we don't know what else to do but return. return; } crossings[0] = Math.max(crossings[0], cross); } });//geom.apply() if (crossings[0] > 0) { newGeom.geometryChanged(); return newGeom; } else { return geom; // original } } /** 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); } } } 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; 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"; List geomList = new ArrayList<>(); //page 0 is the standard -180 to 180 range int startPage = (int) Math.floor((geomEnv.getMinX() + 180) / 360); for (int page = startPage; 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"; if (page != 0) { pageGeom = pageGeom.copy(); // because shiftGeomByX modifies the underlying coordinates shared by geom. 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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/jts/JtsPoint.java000066400000000000000000000064621375755266700313130ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape.jts; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.shape.BaseShape; import org.locationtech.spatial4j.shape.Circle; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.SpatialRelation; import org.locationtech.spatial4j.shape.impl.PointImpl; import org.locationtech.jts.geom.CoordinateSequence; /** Wraps a {@link org.locationtech.jts.geom.Point}. */ public class JtsPoint extends BaseShape implements Point { private org.locationtech.jts.geom.Point pointGeom; private final boolean empty;//cached /** A simple constructor without normalization / validation. */ public JtsPoint(org.locationtech.jts.geom.Point pointGeom, JtsSpatialContext ctx) { super(ctx); this.pointGeom = pointGeom; this.empty = pointGeom.isEmpty(); } public org.locationtech.jts.geom.Point getGeom() { return pointGeom; } @Override public boolean isEmpty() { return empty; } @Override public org.locationtech.spatial4j.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 org.locationtech.spatial4j.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 double getLat() { return getY(); } @Override public double getLon() { return getX(); } @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-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/jts/JtsShapeFactory.java000077500000000000000000000513261375755266700326140ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape.jts; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.jts.DatelineRule; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.context.jts.ValidationRule; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.io.ShapeReader; import org.locationtech.spatial4j.shape.Circle; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.impl.ShapeFactoryImpl; import org.locationtech.jts.algorithm.CGAlgorithms; import org.locationtech.jts.geom.*; import org.locationtech.jts.util.GeometricShapeFactory; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * Enhances {@link ShapeFactoryImpl} with support for Polygons * 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 JtsShapeFactory extends ShapeFactoryImpl { protected static final LinearRing[] EMPTY_HOLES = new LinearRing[0]; protected final GeometryFactory geometryFactory; protected final boolean allowMultiOverlap; protected final boolean useJtsPoint; protected final boolean useJtsLineString; protected final boolean useJtsMulti; protected final DatelineRule datelineRule; protected final ValidationRule validationRule; protected final boolean autoIndex; /** * Called by {@link org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory#newSpatialContext()}. */ public JtsShapeFactory(JtsSpatialContext ctx, JtsSpatialContextFactory factory) { super(ctx, factory); this.geometryFactory = factory.getGeometryFactory(); this.allowMultiOverlap = factory.allowMultiOverlap; this.useJtsPoint = factory.useJtsPoint; this.useJtsLineString = factory.useJtsLineString; this.useJtsMulti = factory.useJtsMulti; this.datelineRule = factory.datelineRule; this.validationRule = factory.validationRule; this.autoIndex = factory.autoIndex; } /** * 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 org.locationtech.spatial4j.shape.ShapeCollection#relateContainsShortCircuits()}. */ public boolean isAllowMultiOverlap() { return allowMultiOverlap; } /** * Returns the rule used to handle geometry objects that have dateline (aka anti-meridian) crossing considerations. */ public DatelineRule getDatelineRule() { return datelineRule; } /** * Returns the rule used to handle errors when creating a JTS {@link Geometry}, particularly after it has been * read from one of the {@link ShapeReader}s. */ public ValidationRule getValidationRule() { return validationRule; } /** * If JtsGeometry shapes should be automatically "prepared" (i.e. optimized) when read via from a {@link ShapeReader}. * * @see org.locationtech.spatial4j.shape.jts.JtsGeometry#index() */ public boolean isAutoIndex() { return autoIndex; } @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 double normZ(double z) { z = super.normZ(z); return geometryFactory.getPrecisionModel().makePrecise(z); } @Override public double normDist(double d) { return geometryFactory.getPrecisionModel().makePrecise(d); } /** * 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(), ctx.getWorldBounds().getMaxX(), r.getMinY(), r.getMaxY()))); pair.add(geometryFactory.toGeometry(new Envelope( ctx.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; GeometricShapeFactory gsf = new GeometricShapeFactory(geometryFactory); gsf.setWidth(circle.getBoundingBox().getWidth()); gsf.setHeight(circle.getBoundingBox().getHeight()); gsf.setNumPoints(4*25);//multiple of 4 is best gsf.setCentre(new Coordinate(circle.getCenter().getX(), circle.getCenter().getY())); Geometry geom = gsf.createCircle(); if (circle.getBoundingBox().getCrossesDateLine()) // wrap the geometry in a JtsGeometry to handle date line wrapping geom = new JtsGeometry(geom, (JtsSpatialContext) getSpatialContext(),false, false).getGeom(); return geom; } //TODO add BufferedLineString throw new InvalidShapeException("can't make Geometry from: " + shape); } /** Should {@link #pointXY(double, double)} return {@link JtsPoint}? */ public boolean useJtsPoint() { return useJtsPoint; } @Override public Point pointXY(double x, double y) { return pointXYZ(x, y, Coordinate.NULL_ORDINATE); } @Override public Point pointXYZ(double x, double y, double z) { if (!useJtsPoint()) return super.pointXY(x, y);// ignore z //A Jts Point is fairly heavyweight! TODO could/should we optimize this? SingleCoordinateSequence verifyX(x); verifyY(y); verifyZ(z); // verifyZ(z)? Coordinate coord = Double.isNaN(x) ? null : new Coordinate(x, y, z); return new JtsPoint(geometryFactory.createPoint(coord), (JtsSpatialContext) ctx); } /** Should {@link #lineString(java.util.List,double)} 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 lineString(List points, double bufferDistance) { if (!useJtsLineString()) return super.lineString(points, bufferDistance); //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()); } } JtsGeometry shape = makeShape(geometryFactory.createLineString(coords)); if(bufferDistance!=0) { return shape.getBuffered(bufferDistance, ctx); } return shape; } @Override public LineStringBuilder lineString() { if (!useJtsLineString()) return super.lineString(); return new JtsLineStringBuilder(); } private class JtsLineStringBuilder extends CoordinatesAccumulator implements LineStringBuilder { protected double bufDistance; public JtsLineStringBuilder() { } @Override public LineStringBuilder buffer(double distance) { this.bufDistance = distance; return this; } @Override public Shape build() { Geometry geom = buildLineStringGeom(); if (bufDistance != 0.0) { geom = geom.buffer(bufDistance); } return makeShape(geom); } LineString buildLineStringGeom() { return geometryFactory.createLineString(getCoordsArray()); } } @Override public PolygonBuilder polygon() { return new JtsPolygonBuilder(); } private class JtsPolygonBuilder extends CoordinatesAccumulator implements PolygonBuilder { List holes;// lazy instantiated @Override public JtsHoleBuilder hole() { return new JtsHoleBuilder(); } private class JtsHoleBuilder extends CoordinatesAccumulator implements PolygonBuilder.HoleBuilder { @Override public JtsPolygonBuilder endHole() { LinearRing linearRing = geometryFactory.createLinearRing(getCoordsArray()); if (JtsPolygonBuilder.this.holes == null) { JtsPolygonBuilder.this.holes = new ArrayList<>(4);//short } JtsPolygonBuilder.this.holes.add(linearRing); return JtsPolygonBuilder.this; } } @Override public Shape build() { return makeShapeFromGeometry(buildPolygonGeom()); } @Override public Shape buildOrRect() { Polygon geom = buildPolygonGeom(); if (geom.isRectangle()) { return makeRectFromRectangularPoly(geom); } return makeShapeFromGeometry(geom); } Polygon buildPolygonGeom() { LinearRing outerRing = geometryFactory.createLinearRing(getCoordsArray()); LinearRing[] holeRings = holes == null ? EMPTY_HOLES : holes.toArray(new LinearRing[this.holes.size()]); return geometryFactory.createPolygon(outerRing, holeRings); } } // class JtsPolygonBuilder private abstract class CoordinatesAccumulator { protected List coordinates = new ArrayList<>(); public T pointXY(double x, double y) { return pointXYZ(x, y, Coordinate.NULL_ORDINATE); } public T pointXYZ(double x, double y, double z) { verifyX(x); verifyY(y); coordinates.add(new Coordinate(x, y, z)); return getThis(); } // TODO would be be useful to add other ways of providing points? e.g. point(Coordinate)? // TODO consider wrapping the List in a custom CoordinateSequence and then (conditionally) use // geometryFactory's coordinateSequenceFactory to create a new CS if configured to do so. // Also consider instead natively storing the double[] and then auto-expanding on pointXY* as needed. protected Coordinate[] getCoordsArray() { return coordinates.toArray(new Coordinate[coordinates.size()]); } @SuppressWarnings("unchecked") protected T getThis() { return (T) this; } } /** Whether {@link #multiPoint()}, {@link #multiLineString()}, and {@link #multiPolygon()} should all use JTS's * subclasses of {@link GeometryCollection} instead of Spatial4j's basic impl. The general {@link #multiShape(Class)} * will never use {@link GeometryCollection} because that class doesn't support relations. */ public boolean useJtsMulti() { return useJtsMulti; } @Override public MultiPointBuilder multiPoint() { if (!useJtsMulti) { return super.multiPoint(); } return new JtsMultiPointBuilder(); } private class JtsMultiPointBuilder extends CoordinatesAccumulator implements MultiPointBuilder { @Override public Shape build() { return makeShape(geometryFactory.createMultiPoint(getCoordsArray())); } } @Override public MultiLineStringBuilder multiLineString() { if (!useJtsMulti) { return super.multiLineString(); } return new JtsMultiLineStringBuilder(); } private class JtsMultiLineStringBuilder implements MultiLineStringBuilder { List geoms = new ArrayList<>(); @Override public LineStringBuilder lineString() { return new JtsLineStringBuilder(); } @Override public MultiLineStringBuilder add(LineStringBuilder lineStringBuilder) { geoms.add(((JtsLineStringBuilder)lineStringBuilder).buildLineStringGeom()); return this; } @Override public Shape build() { return makeShape(geometryFactory.createMultiLineString(geoms.toArray(new LineString[geoms.size()]))); } } @Override public MultiPolygonBuilder multiPolygon() { if (!useJtsMulti) { return super.multiPolygon(); } return new JtsMultiPolygonBuilder(); } private class JtsMultiPolygonBuilder implements MultiPolygonBuilder { List geoms = new ArrayList<>(); @Override public PolygonBuilder polygon() { return new JtsPolygonBuilder(); } @Override public MultiPolygonBuilder add(PolygonBuilder polygonBuilder) { geoms.add(((JtsPolygonBuilder)polygonBuilder).buildPolygonGeom()); return this; } @Override public Shape build() { return makeShape(geometryFactory.createMultiPolygon(geoms.toArray(new Polygon[geoms.size()]))); } } @Override public MultiShapeBuilder multiShape(Class shapeClass) { if (!useJtsMulti()) { return super.multiShape(shapeClass); } return new JtsMultiShapeBuilder<>(); } // TODO: once we have typed shapes for Polygons & LineStrings, this logic could move to the superclass // (not JTS specific) and the multi* builders could take a Shape private class JtsMultiShapeBuilder extends GeneralShapeMultiShapeBuilder { @Override public Shape build() { Class last = null; List geoms = new ArrayList<>(shapes.size()); for(Shape s : shapes) { if (last != null && last != s.getClass()) { return super.build(); } if (s instanceof JtsGeometry) { geoms.add(((JtsGeometry)s).getGeom()); } else if (s instanceof JtsPoint) { geoms.add(((JtsPoint)s).getGeom()); } else { return super.build(); } last = s.getClass(); } return makeShapeFromGeometry(geometryFactory.buildGeometry(geoms)); } } /** * INTERNAL Usually creates a JtsGeometry, potentially validating, repairing, and indexing ("preparing"). This method * is intended for use by {@link ShapeReader} instances. * * If given a direct instance of {@link GeometryCollection} then it's contents will be * recursively converted and then the resulting list will be passed to * {@link SpatialContext#makeCollection(List)} and returned. * * If given a {@link org.locationtech.jts.geom.Point} then {@link SpatialContext#makePoint(double, double)} * is called, which will return a {@link JtsPoint} if {@link JtsSpatialContext#useJtsPoint()}; otherwise * a standard Spatial4j Point is returned. * * If given a {@link LineString} and if {@link JtsSpatialContext#useJtsLineString()} is true then * then the geometry's parts are exposed to call {@link SpatialContext#makeLineString(List)}. */ // TODO should this be called always (consistent but sometimes not needed?) // v.s. only from a ShapeReader (pre-ShapeFactory behavior) public Shape makeShapeFromGeometry(Geometry geom) { if (geom instanceof GeometryCollection) { // Direct instances of GeometryCollection can't be wrapped in JtsGeometry but can be expanded into // a ShapeCollection. if (!useJtsMulti || geom.getClass() == GeometryCollection.class) { List shapes = new ArrayList<>(geom.getNumGeometries()); for (int i = 0; i < geom.getNumGeometries(); i++) { Geometry geomN = geom.getGeometryN(i); shapes.add(makeShapeFromGeometry(geomN));//recursion } return multiShape(shapes); } } else if (geom instanceof org.locationtech.jts.geom.Point) { org.locationtech.jts.geom.Point pt = (org.locationtech.jts.geom.Point) geom; if (pt.isEmpty()) { return pointXY(Double.NaN, Double.NaN); } else { return pointXY(pt.getX(), pt.getY()); } } else if (geom instanceof LineString) { if (!useJtsLineString()) { LineString lineString = (LineString) geom; List points = new ArrayList<>(lineString.getNumPoints()); for (int i = 0; i < lineString.getNumPoints(); i++) { Coordinate coord = lineString.getCoordinateN(i); points.add(pointXY(coord.x, coord.y)); } return lineString(points, 0); } } JtsGeometry jtsGeom; try { jtsGeom = makeShape(geom); if (getValidationRule() != ValidationRule.none) jtsGeom.validate(); } catch (RuntimeException e) { // repair: if (getValidationRule() == ValidationRule.repairConvexHull) { jtsGeom = makeShape(geom.convexHull()); } else if (getValidationRule() == ValidationRule.repairBuffer0) { jtsGeom = makeShape(geom.buffer(0)); } 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; } } return jtsGeom; } /** * INTERNAL * @see #makeShape(org.locationtech.jts.geom.Geometry) * * @param geom Non-null * @param dateline180Check if both this is true and {@link SpatialContext#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) { JtsGeometry jtsGeom = new JtsGeometry(geom, (JtsSpatialContext) ctx, dateline180Check, allowMultiOverlap); if (isAutoIndex()) { jtsGeom.index(); } return jtsGeom; } /** * 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. Also, * note that direct instances of {@link GeometryCollection} isn't supported. * * Instead of calling this method, consider {@link #makeShapeFromGeometry(Geometry)} * which */ public JtsGeometry makeShape(Geometry geom) { return makeShape(geom, datelineRule != DatelineRule.none, allowMultiOverlap); } public GeometryFactory getGeometryFactory() { return geometryFactory; } /** * INTERNAL: Returns a Rectangle of the JTS {@link Envelope} (bounding box) of the given {@code geom}. This asserts * that {@link Geometry#isRectangle()} is true. This method reacts to the {@link DatelineRule} setting. * @param geom non-null * @return the equivalent Rectangle. */ public Rectangle makeRectFromRectangularPoly(Geometry geom) { // TODO although, might want to never convert if there's a semantic difference (e.g. // geodetically)? Should have a setting for that. assert geom.isRectangle(); Envelope env = geom.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(geom.getCoordinates()); } else { crossesDateline = env.getWidth() > 180; } } if (crossesDateline) return rect(env.getMaxX(), env.getMinX(), env.getMinY(), env.getMaxY()); else return rect(env.getMinX(), env.getMaxX(), env.getMinY(), env.getMaxY()); } } spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/package-info.java000066400000000000000000000013541375755266700312600ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ /** 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 org.locationtech.spatial4j.shape;spatial4j-spatial4j-0.8/src/main/java/overview.html000066400000000000000000000012501375755266700223620ustar00rootroot00000000000000 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-spatial4j-0.8/src/test/000077500000000000000000000000001375755266700167425ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/000077500000000000000000000000001375755266700176635ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/org/000077500000000000000000000000001375755266700204525ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/org/locationtech/000077500000000000000000000000001375755266700231265ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/000077500000000000000000000000001375755266700250215ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/TestLog.java000066400000000000000000000041701375755266700272470ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j; 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-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/context/000077500000000000000000000000001375755266700265055ustar00rootroot00000000000000SpatialContextFactoryTest.java000066400000000000000000000120061375755266700344220ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/context/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.context; import org.locationtech.spatial4j.context.jts.DatelineRule; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.context.jts.ValidationRule; import org.locationtech.spatial4j.distance.CartesianDistCalc; import org.locationtech.spatial4j.distance.GeodesicSphereDistCalc; import org.locationtech.spatial4j.io.ShapeIO; import org.locationtech.spatial4j.io.WKTReader; import org.locationtech.spatial4j.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(DatelineRule.ccwRect, ctx.getDatelineRule()); assertEquals(ValidationRule.repairConvexHull, ctx.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 testFormatsConfig() { JtsSpatialContext ctx = (JtsSpatialContext) call( "spatialContextFactory", JtsSpatialContextFactory.class.getName(), "readers", CustomWktShapeParser.class.getName()); assertTrue( ctx.getFormats().getReader(ShapeIO.WKT) instanceof CustomWktShapeParser ); } @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 WKTReader { static boolean once = false;//cheap way to test it was created public CustomWktShapeParser(JtsSpatialContext ctx, JtsSpatialContextFactory factory) { super(ctx, factory); once = true; } } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/context/jts/000077500000000000000000000000001375755266700273055ustar00rootroot00000000000000JtsSpatialContextTest.java000066400000000000000000000071701375755266700343610ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/context/jts/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.context.jts; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.spatial4j.shape.jts.JtsGeometry; import org.locationtech.jts.geom.GeometryCollection; import org.locationtech.jts.geom.Polygon; import org.locationtech.jts.geom.MultiPolygon; import org.junit.Test; import org.locationtech.spatial4j.shape.jts.JtsShapeFactory; import org.locationtech.spatial4j.util.Geom; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class JtsSpatialContextTest { @Test public void testDatelineRule() { // rectangle enclosing the dateline Polygon polygon = Geom.build().points(-179, -90, 179, -90, 179, 90, -179, 90).toPolygon(); JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.datelineRule = DatelineRule.width180; JtsSpatialContext ctx = factory.newSpatialContext(); final Polygon polygonCloned = (Polygon) polygon.copy(); JtsGeometry shp = ctx.makeShape(polygonCloned); assertEquals("shouldn't be modified after calling makeShape", polygon, polygonCloned); assertTrue(shp.getGeom() instanceof GeometryCollection); factory.datelineRule = DatelineRule.none; ctx = factory.newSpatialContext(); shp = ctx.makeShape(polygon); assertTrue(shp.getGeom() instanceof Polygon); } @Test public void testDatelineRuleWithMultiPolygon() { JtsSpatialContext ctx = new JtsSpatialContextFactory().newSpatialContext(); JtsShapeFactory shapeFactory = ctx.getShapeFactory(); GeometryFactory geomFactory = shapeFactory.getGeometryFactory(); // rectangle enclosing the dateline Polygon poly1Geom = Geom.build().points(-179, -90, 179, -90, 179, 90, -179, 90).toPolygon(); // simple triangle Polygon poly2Geom = Geom.build().points(0, 0, 1, 1, 1, 0, 0, 0).toPolygon(); GeometryCollection geomColl = geomFactory.createGeometryCollection( new Geometry[]{poly1Geom, poly2Geom}); JtsGeometry jtsGeometry = shapeFactory.makeShape(geomColl); // one of them is split; other is unchanged assertEquals("MULTIPOLYGON (" + "((-180 -90, -180 90, -179 90, -179 -90, -180 -90)), " + "((179 90, 180 90, 180 -90, 179 -90, 179 90)), " + "((0 0, 1 1, 1 0, 0 0))" + ")", jtsGeometry.toString()); } @Test public void testMultiDatelineWrap() { // polygon crosses the dateline twice Polygon polygon = Geom.build().points(-179, 45, 179, 44, 1, 35, -179, 25, 179, 24, 179, 19, -179, 20, 1, 30, 179, 39, -179, 40).toPolygon(); JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.datelineRule = DatelineRule.width180; JtsSpatialContext ctx = factory.newSpatialContext(); JtsShapeFactory shapeFactory = ctx.getShapeFactory(); JtsGeometry jtsGeometry = shapeFactory.makeShape(polygon); Geometry geometry = jtsGeometry.getGeom(); assertTrue(geometry.isValid()); assertTrue(geometry instanceof MultiPolygon); } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/distance/000077500000000000000000000000001375755266700266135ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/distance/CompareRadiansSnippet.java000077500000000000000000000021241375755266700337130ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.distance; //import static org.locationtech.spatial4j.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-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/distance/TestDistances.java000066400000000000000000000336031375755266700322400ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE and VoyagerSearch * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.distance; import com.carrotsearch.randomizedtesting.RandomizedTest; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.Circle; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.SpatialRelation; import org.locationtech.spatial4j.shape.impl.PointImpl; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.locationtech.spatial4j.distance.DistanceUtils.DEG_TO_KM; import static org.locationtech.spatial4j.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 testDistancesAgainstVincenty() { DistanceCalculator vincenty = new GeodesicSphereDistCalc.Vincenty(); DistanceCalculator haversine = new GeodesicSphereDistCalc.Haversine(); DistanceCalculator lawOfCos = new GeodesicSphereDistCalc.LawOfCosines(); final int TRIES = 100000 * (int)multiplier(); for (int i = 0; i < TRIES; i++) { Point p1 = randomGeoPoint(); Point p2 = randomGeoPointFrom(p1); double distV = vincenty.distance(p1, p2); //Haversine: accurate to a centimeter if on same side of globe, // otherwise possibly 1m apart (antipodal) double havV = haversine.distance(p1, p2); assertEquals(distV, havV, (distV <= 90) ? DistanceUtils.KM_TO_DEG * 0.00001 : DistanceUtils.KM_TO_DEG * 0.001); // Fractionally compared to truth, also favorably accurate. if (distV != 0 && distV > 0.0000001) assertEquals(1.0, havV/distV, 0.001);//0.1% //LawOfCosines: accurate to within 1 meter (or better?) double locV = lawOfCos.distance(p1, p2); assertEquals(distV, locV, DistanceUtils.KM_TO_DEG * 0.001); } } private Point randomGeoPoint() { //not uniformly distributed but that's ok return ctx.makePoint(randomDouble()*360 + -180, randomDouble()*180 + -90); } private Point randomGeoPointFrom(Point p1) { int which = randomInt(10);//inclusive double distDEG; if (which <= 2) { distDEG = 180 - randomDouble() * 0.001 / Math.pow(10, which); } else if (which >= 8) { distDEG = randomDouble() * 0.001 / Math.pow(10, 10-which); } else { distDEG = randomDouble()*180; } double bearingDEG = randomDouble() * 360; Point p2RAD = DistanceUtils.pointOnBearingRAD(DistanceUtils.toRadians(p1.getY()), DistanceUtils.toRadians(p1.getX()), DistanceUtils.toRadians(distDEG), DistanceUtils.toRadians(bearingDEG), ctx, null); p2RAD.reset(DistanceUtils.toDegrees(p2RAD.getX()), DistanceUtils.toDegrees(p2RAD.getY())); return p2RAD;//now it's in degrees } @Test /** See #81 */ public void testHaversineNaN() { assertEquals(180, new GeodesicSphereDistCalc.Haversine().distance( ctx.makePoint(-81.05206968336057, 71.82629271026536), 98.9479297952497, -71.82629264390964), 0.00001); } @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.getShapeFactory().pointLatLon(lat, lon); } @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-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/000077500000000000000000000000001375755266700254305ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/BaseRoundTripTest.java000066400000000000000000000040771375755266700316640ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import com.carrotsearch.randomizedtesting.RandomizedTest; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.Shape; import org.junit.Test; public abstract class BaseRoundTripTest extends RandomizedTest { protected T ctx; protected BinaryCodec binaryCodec; protected BaseRoundTripTest() { this.ctx = initContext(); binaryCodec = ctx.getBinaryCodec();//stateless } public abstract T initContext(); public boolean shouldBeEqualAfterRoundTrip() { return true; } //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() throws Exception { assertRoundTrip(wkt("POINT(-10 80.3)")); } /** Convenience to read static data. */ protected Shape wkt(String wkt) { try { return ctx.getFormats().getWktReader().read(wkt); } catch (RuntimeException e) { throw e; } catch (Exception 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 final void assertRoundTrip(Shape shape) throws Exception { assertRoundTrip(shape, shouldBeEqualAfterRoundTrip()); } protected abstract void assertRoundTrip(Shape shape, boolean andEquals) throws Exception; } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/BinaryCodecTest.java000066400000000000000000000034001375755266700313120ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.ShapeCollection; import org.junit.Test; import java.io.*; import java.util.Arrays; import static org.junit.Assert.assertEquals; public class BinaryCodecTest extends BaseRoundTripTest { @Override public SpatialContext initContext() { return SpatialContext.GEO; } @Test public void testRect() throws Exception { assertRoundTrip(wkt("ENVELOPE(-10, 180, 42.3, 0)")); } @Test public void testCircle() throws Exception { assertRoundTrip(wkt("BUFFER(POINT(-10 30), 5.2)")); } @Test public void testCollection() throws Exception { ShapeCollection s = ctx.makeCollection( Arrays.asList( randomShape(), randomShape(), randomShape() ) ); assertRoundTrip(s); } @Override protected void assertRoundTrip(Shape shape, boolean andEquals) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); binaryCodec.writeShape(new DataOutputStream(baos), shape); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); assertEquals(shape, binaryCodec.readShape(new DataInputStream(bais))); } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/GeneralGeoJSONTest.java000066400000000000000000000107561375755266700316460ustar00rootroot00000000000000/* * 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 org.locationtech.spatial4j.io; import org.locationtech.spatial4j.shape.Shape; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class GeneralGeoJSONTest extends GeneralReadWriteShapeTest { protected ShapeReader reader; protected ShapeWriter writer; protected ShapeWriter writerForTests; @Before @Override public void setUp() { super.setUp(); reader = ctx.getFormats().getReader(ShapeIO.GeoJSON); writer = ctx.getFormats().getWriter(ShapeIO.GeoJSON); writerForTests = writer; //ctx.getFormats().getWriter(ShapeIO.GeoJSON); Assert.assertNotNull(reader); Assert.assertNotNull(writer); Assert.assertNotNull(writerForTests); } @Override protected ShapeReader getShapeReader() { return reader; } @Override protected ShapeWriter getShapeWriter() { return writer; } @Override protected ShapeWriter getShapeWriterForTests() { return writerForTests; } @Test @Override public void testWriteThenReadCircle() throws Exception { //don't test shape equality; rounding issue in 'km' conversion assertRoundTrip(circle(), false); } @Test @Override public void testWriteThenReadBufferedLine() throws Exception { //don't test shape equality; rounding issue in 'km' conversion assertRoundTrip(bufferedLine(), false); } // // Below ported from GeoJSONReadWriteTest: // @Test public void testParsePoint() throws Exception { Shape v = reader.read(pointText()); assertTrue(point().equals(v)); } @Test public void testEncodePoint() throws Exception { assertEquals(pointText(), writer.toString(point())); } @Test public void testParseLineString() throws Exception { assertEquals(line(), reader.read(lineText())); } @Test public void testEncodeLineString() throws Exception { assertEquals(lineText(), strip(writer.toString(line()))); } @Test public void testParsePolygon() throws Exception { assertEquals(polygon1(), reader.read(polygonText1())); assertEquals(polygon2(), reader.read(polygonText2())); } @Test public void testEncodePolygon() throws Exception { assertEquals(polygonText1(), writer.toString(polygon1())); assertEquals(polygonText2(), writer.toString(polygon2())); } @Test public void testParseMultiPoint() throws Exception { assertEquals(multiPoint(), reader.read(multiPointText())); } @Test public void testEncodeMultiPoint() throws Exception { assertEquals(multiPointText(), writer.toString(multiPoint())); } @Test public void testParseMultiLineString() throws Exception { assertEquals(multiLine(), reader.read(multiLineText())); } @Test public void testEncodeMultiLineString() throws Exception { assertEquals(multiLineText(), writer.toString(multiLine())); } @Test public void testParseMultiPolygon() throws Exception { assertEquals(multiPolygon(), reader.read(multiPolygonText())); } @Test public void testEncodeMultiPolygon() throws Exception { assertEquals(multiPolygonText(), writer.toString(multiPolygon())); } @Test public void testEncodeRectangle() throws Exception { assertEquals(rectangleText(), strip(writer.toString(rectangle()))); } @Test public void testParseGeometryCollection() throws Exception { assertEquals(collection(), reader.read(collectionText())); } @Test public void testEncodeGeometryCollection() throws Exception { assertEquals(collectionText(), strip(writer.toString(collection()))); } @Test public void testEncodeBufferedLineString() throws Exception { assertEquals(bufferedLineText(), strip(writer.toString(bufferedLine()))); } }spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/GeneralPolyshapeTest.java000066400000000000000000000036551375755266700324060ustar00rootroot00000000000000/* * 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 org.locationtech.spatial4j.io; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; public class GeneralPolyshapeTest extends GeneralReadWriteShapeTest { ShapeReader reader; ShapeWriter writer; ShapeWriter writerForTests; @Before @Override public void setUp() { super.setUp(); reader = ctx.getFormats().getReader(ShapeIO.POLY); writer = ctx.getFormats().getWriter(ShapeIO.POLY); writerForTests = writer; Assert.assertNotNull(reader); Assert.assertNotNull(writer); Assert.assertNotNull(writerForTests); } @Override protected ShapeReader getShapeReader() { return reader; } @Override protected ShapeWriter getShapeWriter() { return writer; } @Override protected ShapeWriter getShapeWriterForTests() { return writerForTests; } @Override public boolean shouldBeEqualAfterRoundTrip() { return false; // the polyline values will be off by a small fraction -- everything is rounded to: Math.round(value * 1e5) } @Override @Ignore public void testEmptyGeometryCollection() throws Exception { assumeTrue(false); // not supported } }spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/GeneralReadWriteShapeTest.java000066400000000000000000000203141375755266700333000ustar00rootroot00000000000000/* * 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 org.locationtech.spatial4j.io; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.locationtech.spatial4j.util.GeomBuilder; import java.util.Arrays; import java.util.Collections; public abstract class GeneralReadWriteShapeTest extends BaseRoundTripTest { protected GeomBuilder gb; @Before public void setUp() { gb = new GeomBuilder(); } @Override public JtsSpatialContext initContext() { // Like the out-of-the-box GEO, but it wraps datelines JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.geo = true; factory.normWrapLongitude = true; factory.useJtsLineString = false; // false so that buffering lineString round-trips return new JtsSpatialContext(factory); } protected abstract ShapeReader getShapeReader(); protected abstract ShapeWriter getShapeWriter(); protected abstract ShapeWriter getShapeWriterForTests(); @Override protected void assertRoundTrip(Shape shape, boolean andEquals) throws Exception { String str = getShapeWriter().toString(shape); Shape out = getShapeReader().read(str); // GeoJSON has limited numeric precision so the off by .0000001 does not affect its equals ShapeWriter writer = getShapeWriterForTests(); String expect = writer.toString(shape); String actual = writer.toString(out); Assert.assertEquals(expect, actual); if(andEquals) { Assert.assertEquals(shape, out); } } @Test public void testEmptyPoint() throws Exception { assertRoundTrip(ctx.getShapeFactory().pointXY(Double.NaN, Double.NaN)); } @Test public void testEmptyGeometryCollection() throws Exception { assertRoundTrip(ctx.getShapeFactory().multiShape(Collections.emptyList())); } @Test public void testWriteThenReadPoint() throws Exception { assertRoundTrip(point()); } @Test public void testWriteThenReadLineString() throws Exception { assertRoundTrip(line()); } @Test public void testWriteThenReadPolygon() throws Exception { assertRoundTrip(polygon1()); assertRoundTrip(polygon2()); } @Test public void testWriteThenReadMultiPoint() throws Exception { assertRoundTrip(multiPoint()); } @Test public void testWriteThenReadMultiLineString() throws Exception { assertRoundTrip(multiLine()); } @Test public void testWriteThenReadMultiPolygon() throws Exception { assertRoundTrip(multiPolygon()); } @Test public void testWriteThenReadRectangle() throws Exception { assertRoundTrip(polygon1().getBoundingBox()); } @Test public void testWriteThenReadCollection() throws Exception { assertRoundTrip(collection()); } @Test public void testWriteThenReadBufferedLine() throws Exception { assertRoundTrip(bufferedLine()); } @Test public void testWriteThenReadCircle() throws Exception { assertRoundTrip(circle()); } String pointText() { return strip("{'type': 'Point','coordinates':[100.1,0.1]}"); } org.locationtech.spatial4j.shape.Point point() { return ctx.makePoint(100.1, 0.1); } String lineText() { return strip( "{'type': 'LineString', 'coordinates': [[100.1,0.1],[101.1,1.1]]}"); } Shape line() { return ctx.makeLineString(Arrays.asList(ctx.makePoint(100.1, 0.1), ctx.makePoint(101.1, 1.1))); } Shape polygon1() { // close to a rectangle but not quite return ctx.makeShape(gb.points(100.1, 0.1, 101.2, 0.1, 101.1, 1.1, 100.1, 1.1, 100.1, 0.1).ring().toPolygon()); } String polygonText1() { return strip("{ 'type': 'Polygon',"+ "'coordinates': ["+ " [ [100.1, 0.1], [101.2, 0.1], [101.1, 1.1], [100.1, 1.1], [100.1, 0.1] ]"+ " ]"+ "}"); } String polygonText2() { return strip("{ 'type': 'Polygon',"+ " 'coordinates': ["+ " [ [100.1, 0.1], [101.1, 0.1], [101.1, 1.1], [100.1, 1.1], [100.1, 0.1] ],"+ " [ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]"+ " ]"+ " }"); } Shape polygon2() { return ctx.makeShape(gb.points(100.1, 0.1, 101.1, 0.1, 101.1, 1.1, 100.1, 1.1, 100.1, 0.1).ring() .points(100.2, 0.2, 100.8, 0.2, 100.8, 0.8, 100.2, 0.8, 100.2, 0.2).ring().toPolygon()); } String multiPointText() { return strip( "{ 'type': 'MultiPoint',"+ "'coordinates': [ [100.1, 0.1], [101.1, 1.1] ]"+ "}"); } Shape multiPoint() { return ctx.makeShape(gb.points(100.1, 0.1, 101.1, 1.1).toMultiPoint()); } String multiLineText() { return strip( "{ 'type': 'MultiLineString',"+ " 'coordinates': ["+ " [ [100.1, 0.1], [101.1, 1.1] ],"+ " [ [102.1, 2.1], [103.1, 3.1] ]"+ " ]"+ " }"); } Shape multiLine() { return ctx.makeShape(gb.points(100.1, 0.1, 101.1, 1.1).lineString() .points(102.1, 2.1, 103.1, 3.1).lineString().toMultiLineString()); } String multiPolygonText() { return strip( "{ 'type': 'MultiPolygon',"+ " 'coordinates': ["+ " [[[102.1, 2.1], [103.1, 2.1], [103.1, 3.1], [102.1, 3.1], [102.1, 2.1]]],"+// rect " [[[100.1, 0.1], [101.1, 0.1], [101.1, 1.1], [100.1, 1.1], [100.1, 0.1]],"+ // rect with rect hole " [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]]"+ " ]"+ " }"); } Shape multiPolygon() { return ctx.makeShape( gb.points(102.1, 2.1, 103.1, 2.1, 103.1, 3.1, 102.1, 3.1, 102.1, 2.1).ring().polygon() .points(100.1, 0.1, 101.1, 0.1, 101.1, 1.1, 100.1, 1.1, 100.1, 0.1).ring() .points(100.2, 0.2, 100.8, 0.2, 100.8, 0.8, 100.2, 0.8, 100.2, 0.2).ring().polygon() .toMultiPolygon()); } Rectangle rectangle() { return ctx.makeRectangle(100.1, 101.1, 0.1, 1.1); } String rectangleText() { return strip( "{" + "'type':'Polygon'," + "'coordinates': [[[100.1,0.1], [100.1,1.1], [101.1,1.1], [101.1,0.1], [100.1,0.1]]]" + "}"); } String collectionText() { return strip( "{ 'type': 'GeometryCollection',"+ " 'geometries': ["+ " { 'type': 'Point',"+ " 'coordinates': [100.1, 0.1]"+ " },"+ " { 'type': 'LineString',"+ " 'coordinates': [ [101.1, 0.1], [102.1, 1.1] ]"+ " }"+ " ]"+ " }"); } Shape collection() { return ctx.makeShapeFromGeometry(gb.point(100.1, 0.1).point().points(101.1, 0.1, 102.1, 1.1).lineString().toCollection()); } protected String bufferedLineText() { return strip( "{'type': 'LineString', " + "'coordinates': [[100.1,0.1],[101.1,1.1]], " + "'buffer': 1111.950797, " + "'properties': {'buffer_units': 'km'}}"); } protected Shape bufferedLine() { return ctx.makeBufferedLineString(Arrays.asList(ctx.makePoint(100.1, 0.1), ctx.makePoint(101.1, 1.1)), 10); } Shape circle() { return ctx.makeCircle(1, 2, 10); } protected String strip(String json) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < json.length(); i++) { char c = json.charAt(i); if (c == ' ' || c == '\n') continue; if (c == '\'') { sb.append("\""); } else { sb.append(c); } } return sb.toString(); } }spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/GeneralWktTest.java000066400000000000000000000043521375755266700312020ustar00rootroot00000000000000/* * 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 org.locationtech.spatial4j.io; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; public class GeneralWktTest extends GeneralReadWriteShapeTest { ShapeReader reader; ShapeWriter writer; ShapeWriter writerForTests; @Before @Override public void setUp() { super.setUp(); reader = ctx.getFormats().getReader(ShapeIO.WKT); writer = ctx.getFormats().getWriter(ShapeIO.WKT); writerForTests = writer; //ctx.getFormats().getWriter(ShapeIO.GeoJSON); Assert.assertNotNull(reader); Assert.assertNotNull(writer); Assert.assertNotNull(writerForTests); } @Override protected ShapeReader getShapeReader() { return reader; } @Override protected ShapeWriter getShapeWriter() { return writer; } @Override protected ShapeWriter getShapeWriterForTests() { return writerForTests; } //TODO: Either the WKT read/writer should try to flatten to the original type (not GeometryCollection) // and/or ShapeCollection needs to become typed. @Ignore @Test @Override public void testWriteThenReadMultiPoint() throws Exception { super.testWriteThenReadMultiPoint(); } @Ignore @Test @Override public void testWriteThenReadMultiLineString() throws Exception { super.testWriteThenReadMultiLineString(); } @Ignore @Test @Override public void testWriteThenReadMultiPolygon() throws Exception { super.testWriteThenReadMultiPolygon(); } }spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/JtsBinaryCodecTest.java000066400000000000000000000041551375755266700320030ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.PrecisionModel; import org.locationtech.jts.util.GeometricShapeFactory; import org.junit.Test; import java.util.Arrays; public class JtsBinaryCodecTest extends BinaryCodecTest { @Override public SpatialContext initContext() { JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.precisionModel = new PrecisionModel(PrecisionModel.FLOATING_SINGLE); return factory.newSpatialContext(); } @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-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/JtsPolyshapeParserTest.java000066400000000000000000000030501375755266700327330ustar00rootroot00000000000000package org.locationtech.spatial4j.io; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.ShapeCollection; import org.locationtech.spatial4j.shape.jts.JtsGeometry; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.MultiPoint; import org.junit.Test; import org.locationtech.spatial4j.util.Geom; import java.io.IOException; import java.text.ParseException; import static org.junit.Assert.assertTrue; public class JtsPolyshapeParserTest { @Test public void testUseMulti() throws IOException, ParseException { String ps = write(Geom.build().point(0,0).point(0,0).toMultiPoint()); Shape shape = newContext(false).getFormats().getReader(ShapeIO.POLY).read(ps); assertTrue(shape instanceof ShapeCollection); shape = newContext(true).getFormats().getReader(ShapeIO.POLY).read(ps); assertTrue(shape instanceof JtsGeometry); assertTrue(((JtsGeometry)shape).getGeom() instanceof MultiPoint); } JtsSpatialContext newContext(boolean useMulti) { JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.useJtsMulti = useMulti; return factory.newSpatialContext(); } String write(Geometry g) { Shape shp = JtsSpatialContext.GEO.getShapeFactory().makeShape(g); return JtsSpatialContext.GEO.getFormats().getWriter(ShapeIO.POLY).toString(shp); } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/JtsWKTReaderShapeParserTest.java000066400000000000000000000057631375755266700335550ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import com.carrotsearch.randomizedtesting.RandomizedTest; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.jts.DatelineRule; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.io.jts.JtsWKTReaderShapeParser; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.junit.Test; import java.io.IOException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class JtsWKTReaderShapeParserTest extends RandomizedTest { final SpatialContext ctx; { JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.datelineRule = DatelineRule.ccwRect; factory.readers.clear(); factory.readers.add( JtsWKTReaderShapeParser.class ); ctx = factory.newSpatialContext(); } @Test public void wktGeoPt() throws IOException { Shape s = read("Point(-160 30)"); assertEquals(ctx.makePoint(-160,30),s); } private Shape read(String value) { return ctx.getFormats().read(value); } @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 = read("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 = read("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 { read("POLYGON((0 0, 10 0, 10 20))");//doesn't connect around fail(); } catch (InvalidShapeException e) { //expected } try { read("POLYGON((0 0, 10 0, 10 20, 5 -5, 0 20, 0 0))");//Topology self-intersect fail(); } catch (InvalidShapeException e) { //expected } } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/JtsWktShapeParserTest.java000066400000000000000000000152201375755266700325170ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import org.locationtech.spatial4j.context.jts.DatelineRule; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.context.jts.ValidationRule; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.ShapeFactory; import org.locationtech.spatial4j.shape.SpatialRelation; import org.locationtech.spatial4j.shape.jts.JtsGeometry; import org.junit.Test; import java.text.ParseException; import java.util.Arrays; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class JtsWktShapeParserTest extends WktShapeParserTest { //By extending WktShapeParserTest we inherit its test too final JtsSpatialContext ctx;//note: masks superclass public JtsWktShapeParserTest() { super(createSpatialContext()); this.ctx = (JtsSpatialContext) super.ctx; } static JtsSpatialContext createSpatialContext() { JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.useJtsMulti = false; return factory.newSpatialContext(); } @Test public void testParsePolygon() throws ParseException { Shape polygonNoHoles = ctx.getShapeFactory().polygon() .pointXY(100, 0) .pointXY(101, 0) .pointXY(101, 1) .pointXY(100, 2) .pointXY(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 = ctx.getShapeFactory().polygon() .pointXY(100, 0) .pointXY(101, 0) .pointXY(101, 1) .pointXY(100, 1) .pointXY(100, 0) .hole() .pointXY(100.2, 0.2) .pointXY(100.8, 0.2) .pointXY(100.8, 0.8) .pointXY(100.2, 0.8) .pointXY(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); assertParses("POLYGON EMPTY", ctx.getShapeFactory().polygon().build()); } @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 = DatelineRule.ccwRect;} }.newSpatialContext(); //counter-clockwise assertEquals(wkt(ctx, "POLYGON((160 0, -170 0, -170 10, 160 10, 160 0))"), ctx.makeRectangle(160, -170, 0, 10)); //clockwise assertEquals(wkt(ctx, "POLYGON((160 10, -170 10, -170 0, 160 0, 160 10))"), ctx.makeRectangle(-170, 160, 0, 10)); } @Test public void testParseMultiPolygon() throws ParseException { ShapeFactory.MultiPolygonBuilder multiPolygonBuilder = ctx.getShapeFactory().multiPolygon(); multiPolygonBuilder.add(multiPolygonBuilder.polygon() .pointXY(100, 0) .pointXY(101, 0)//101 .pointXY(101, 2)//101 .pointXY(100, 1) .pointXY(100, 0)); multiPolygonBuilder.add(multiPolygonBuilder.polygon() .pointXY( 0, 0) .pointXY( 2, 0) .pointXY( 2, 2) .pointXY( 0, 1) .pointXY( 0, 0)); Shape s = multiPolygonBuilder.build(); assertParses("MULTIPOLYGON(" + "((100 0, 101 0, 101 2, 100 1, 100 0))" + ',' + "((0 0, 2 0, 2 2, 0 1, 0 0))" + ")", s); assertParses("MULTIPOLYGON EMPTY", ctx.getShapeFactory().multiPolygon().build()); } @Test public void testLineStringDateline() throws ParseException { //works because we use JTS (JtsGeometry); BufferedLineString doesn't yet do DL wrap. Shape s = wkt("LINESTRING(160 10, -170 15)"); assertEquals(30, s.getBoundingBox().getWidth(), 0.0 ); } @Test public void testWrapTopologyException() throws Exception { //test that we can catch ParseException without having to detect TopologyException too assert ctx.getValidationRule() != ValidationRule.none; try { wkt("POLYGON((0 0, 10 0, 10 20))"); fail(); } catch (InvalidShapeException e) { //expected } try { wkt("POLYGON((0 0, 10 0, 10 20, 5 -5, 0 20, 0 0))"); fail(); } catch (InvalidShapeException 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 = ValidationRule.repairBuffer0; JtsSpatialContext ctx = factory.newSpatialContext(); Shape buffer0 = wkt(ctx,wkt); assertTrue(buffer0.getArea(ctx) > 0); factory = new JtsSpatialContextFactory(); factory.validationRule = ValidationRule.repairConvexHull; ctx = factory.newSpatialContext(); Shape cvxHull = wkt(ctx,wkt); assertTrue(cvxHull.getArea(ctx) > 0); assertEquals(SpatialRelation.CONTAINS, cvxHull.relate(buffer0)); factory = new JtsSpatialContextFactory(); factory.validationRule = ValidationRule.none; ctx = factory.newSpatialContext(); wkt(ctx,wkt); } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/LegacyShapeReadWriterTest.java000066400000000000000000000053321375755266700333140ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import com.carrotsearch.randomizedtesting.RandomizedTest; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.shape.Shape; import org.junit.Test; import java.io.IOException; import java.util.Arrays; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class LegacyShapeReadWriterTest extends RandomizedTest { private final LegacyShapeReader reader; private final LegacyShapeWriter writer; @ParametersFactory public static Iterable parameters() { return Arrays.asList($$( $(SpatialContext.GEO), $(JtsSpatialContext.GEO) )); } private final SpatialContext ctx; public LegacyShapeReadWriterTest(SpatialContext ctx) { this.ctx = ctx; this.reader = new LegacyShapeReader(ctx, null); this.writer = new LegacyShapeWriter(ctx, null); } @SuppressWarnings("unchecked") private T writeThenRead(T s ) throws IOException { String buff = writer.toString( s ); return (T) read( buff ); } private Shape read(String value) { return reader.readIfSupported(value); } @Test public void testPoint() throws IOException { Shape s = read("10 20"); assertEquals(ctx.makePoint(10,20),s); assertEquals(s,writeThenRead(s)); assertEquals(s,read("20,10"));//check comma for y,x format assertEquals(s,read("20, 10"));//test space assertFalse(s.hasArea()); } @Test public void testRectangle() throws IOException { Shape s = read("-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 = read("Circle(1.23 4.56 distance=7.89)"); assertEquals(ctx.makeCircle(1.23, 4.56, 7.89),s); assertEquals(s,writeThenRead(s)); assertEquals(s,read("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-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/ShapeFormatTest.java000066400000000000000000000124231375755266700313460ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 VoyagerSearch * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.shape.Shape; import org.junit.Test; import java.io.*; import java.text.ParseException; import java.util.Arrays; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; /** * Tests for {@link ShapeFormat} */ public class ShapeFormatTest { public Shape testReadAndWriteTheSame(Shape shape, ShapeReader reader,ShapeWriter writer) throws IOException, ParseException { assertNotNull(shape); StringWriter str = new StringWriter(); writer.write(str, shape); // System.out.println( "OUT: "+str.toString()); Shape out = reader.read(new StringReader(str.toString())); StringWriter copy = new StringWriter(); writer.write(copy, out); assertEquals(str.toString(), copy.toString()); return out; } public void testCommon(SpatialContext ctx, String name) throws Exception { ShapeReader reader = ctx.getFormats().getReader(name); ShapeWriter writer = ctx.getFormats().getWriter(name); assertNotNull(reader); assertNotNull(writer); testReadAndWriteTheSame(ctx.makePoint(10, 20),reader,writer); testReadAndWriteTheSame(ctx.makeLineString( Arrays.asList( ctx.makePoint(1, 2), ctx.makePoint(3, 4), ctx.makePoint(5, 6) )),reader,writer); // testReadAndWriteTheSame(ctx.makeRectangle(10, 20, 30, 40),format); } public void testJTS(JtsSpatialContext ctx, String name) throws Exception { ShapeReader reader = ctx.getFormats().getReader(name); ShapeWriter writer = ctx.getFormats().getWriter(name); Shape shape; // // wkt = readFirstLineFromRsrc("/russia.wkt.txt"); // shape = ctx.readShape(wkt); // // testReadAndWriteTheSame(shape,format); // Examples from Wikipedia shape = wkt(ctx,"LINESTRING (30 10, 10 30, 40 40)"); // testReadAndWriteTheSame(shape,format); shape = wkt(ctx,"POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10))"); testReadAndWriteTheSame(shape,reader,writer); shape = wkt(ctx,"POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30))"); testReadAndWriteTheSame(shape,reader,writer); shape = wkt(ctx,"MULTIPOINT ((10 40), (40 30), (20 20), (30 10))"); testReadAndWriteTheSame(shape,reader,writer); shape = wkt(ctx,"MULTIPOINT (10 40, 40 30, 20 20, 30 10)"); testReadAndWriteTheSame(shape,reader,writer); shape = wkt(ctx,"MULTILINESTRING ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10))"); testReadAndWriteTheSame(shape,reader,writer); shape = wkt(ctx,"MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)),((15 5, 40 10, 10 20, 5 10, 15 5)))"); testReadAndWriteTheSame(shape,reader,writer); } @Test public void testReadAndWriteTheSame() throws Exception { // GeoJSON String format = ShapeIO.GeoJSON; testCommon(SpatialContext.GEO, format); testCommon(JtsSpatialContext.GEO, format); testJTS(JtsSpatialContext.GEO, format); // WKT format = ShapeIO.WKT; testCommon(SpatialContext.GEO, format); testCommon(JtsSpatialContext.GEO, format); testJTS(JtsSpatialContext.GEO, format); } public void testParseVsInvalidExceptions(ShapeReader reader, boolean supportsPolygon) throws Exception { String txt = null; try { txt = "garbage"; reader.read(txt); fail("should throw invalid exception"); } catch(ParseException ex) { //expected } try { txt = "POINT(-1000 1000)"; reader.read(txt); fail("should throw invalid shape"); } catch(InvalidShapeException ex) { //expected } if(supportsPolygon) { try { txt = readFirstLineFromRsrc("/fiji.wkt.txt"); reader.read(txt); fail("should throw invalid exception"); } catch(InvalidShapeException ex) { //expected } } } @Test public void testParseVsInvalidExceptions() throws Exception { testParseVsInvalidExceptions(SpatialContext.GEO.getFormats().getWktReader(), false); testParseVsInvalidExceptions(JtsSpatialContext.GEO.getFormats().getWktReader(), true); } 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(); } } /** Convenience to read static data. */ protected Shape wkt(SpatialContext ctx, String wkt) throws IOException, ParseException { return ctx.getFormats().getWktReader().read(wkt); } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/TestGeohashUtils.java000066400000000000000000000100761375755266700315360ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.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-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/WKTWriterTest.java000066400000000000000000000020421375755266700307730ustar00rootroot00000000000000package org.locationtech.spatial4j.io; import static org.junit.Assert.assertEquals; import java.util.ArrayList; import org.junit.Test; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.ShapeCollection; public class WKTWriterTest { private SpatialContext ctx; protected WKTWriterTest(SpatialContext ctx) { this.ctx = ctx; } public WKTWriterTest() { this(SpatialContext.GEO); } @Test public void testToStringOnEmptyPoint() throws Exception { ShapeWriter writer = ctx.getFormats().getWktWriter(); Point emptyPoint = ctx.makePoint(Double.NaN, Double.NaN); assertEquals("POINT EMPTY", writer.toString(emptyPoint)); } @Test public void testToStringOnEmptyShapeCollection() throws Exception { ShapeWriter writer = ctx.getFormats().getWktWriter(); ShapeCollection emptyCollection = ctx.makeCollection(new ArrayList<>()); assertEquals("GEOMETRYCOLLECTION EMPTY", writer.toString(emptyCollection)); } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/WktCustomShapeParserTest.java000066400000000000000000000061551375755266700332400ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.impl.PointImpl; import org.junit.Test; import java.text.ParseException; import static org.junit.Assert.assertEquals; 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.readers.clear(); factory.readers.add( MyWKTShapeParser.class ); return factory.newSpatialContext(); } @Test public void testCustomShape() throws ParseException { assertEquals("customShape", ((CustomShape) wkt("customShape()")).name); assertEquals("custom3d", ((CustomShape) wkt("custom3d ()")).name);//number supported } @Test public void testNextSubShapeString() throws ParseException { WKTReader.State state = ((WKTReader)ctx.getFormats().getWktReader()).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 WKTReader { public MyWKTShapeParser(SpatialContext ctx, SpatialContextFactory factory) { super(ctx, factory); } @Override protected State newState(String wkt) { //First few lines compile, despite newState() being protected. Just proving extensibility. WKTReader 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-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/WktShapeParserTest.java000066400000000000000000000145701375755266700320450ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 ElasticSearch and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ // A derivative of commit 14bc4dee08355048d6a94e33834b919a3999a06e // at https://github.com/chrismale/elasticsearch package org.locationtech.spatial4j.io; import com.carrotsearch.randomizedtesting.RandomizedTest; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.ShapeFactory; import org.junit.Test; import java.text.ParseException; import java.util.Collections; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; 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(wkt(wkt), expected); } protected Shape wkt(String wkt) throws ParseException { return wkt(ctx, wkt); } protected Shape wkt(SpatialContext ctx, String wkt) throws ParseException { return ((WKTReader) ctx.getFormats().getWktReader()).parse(wkt); } protected void assertFails(String wkt) { try { wkt(wkt); fail("ParseException expected"); } catch (ParseException e) {//expected } } @Test public void testNoOp() throws ParseException { WKTReader wktShapeParser = (WKTReader) ctx.getFormats().getWktReader(); 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.getShapeFactory().multiPoint().pointXY(10, 40).build(); assertParses("MULTIPOINT (10 40)", s1); Shape s4 = ctx.getShapeFactory().multiPoint() .pointXY(10, 40).pointXY(40, 30).pointXY(20, 20).pointXY(30, 10).build(); 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.getShapeFactory().multiPoint().build()); } @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 { Shape ls = ctx.getShapeFactory().lineString().pointXY(1, 10).pointXY(2, 20).pointXY(3, 30).build(); assertParses("LINESTRING (1 10, 2 20, 3 30)", ls); assertParses("LINESTRING EMPTY", ctx.makeLineString(Collections.emptyList())); } @Test public void testMultiLineStringShape() throws ParseException { ShapeFactory.MultiLineStringBuilder builder = ctx.getShapeFactory().multiLineString(); builder.add(builder.lineString().pointXY(10, 10).pointXY(20, 20).pointXY(10, 40)); builder.add(builder.lineString().pointXY(40, 40).pointXY(30, 30).pointXY(40, 20).pointXY(30, 10)); Shape s = builder.build(); assertParses("MULTILINESTRING ((10 10, 20 20, 10 40),\n" + "(40 40, 30 30, 40 20, 30 10))", s); assertParses("MULTILINESTRING M EMPTY", ctx.getShapeFactory().multiLineString().build()); } @Test public void testGeomCollection() throws ParseException { ShapeFactory shapeFactory = ctx.getShapeFactory(); Shape s1 = shapeFactory.multiShape(Shape.class).add(shapeFactory.pointXY(1, 2)).build(); Shape s2 = shapeFactory.multiShape(Shape.class) .add(shapeFactory.rect(1, 2, 3, 4)).add(shapeFactory.pointXY(-1, -2)).build(); assertParses("GEOMETRYCOLLECTION (POINT (1 2) )", s1); assertParses("GEOMETRYCOLLECTION ( ENVELOPE(1,2,4,3), POINT(-1 -2)) ", s2); assertParses("GEOMETRYCOLLECTION EMPTY", shapeFactory.multiShape(Shape.class).build()); assertParses("GEOMETRYCOLLECTION ( POINT EMPTY )", shapeFactory.multiShape(Shape.class).add(shapeFactory.pointXY(Double.NaN, Double.NaN)).build()); } @Test public void testBuffer() throws ParseException { assertParses("BUFFER(POINT(1 2), 3)", ctx.makePoint(1, 2).getBuffered(3, ctx)); } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/benchmark/000077500000000000000000000000001375755266700273625ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/benchmark/ShapeBenchmarks.java000066400000000000000000000045471375755266700332750ustar00rootroot00000000000000package org.locationtech.spatial4j.io.benchmark; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.io.LegacyShapeWriter; import org.locationtech.spatial4j.io.ShapeIO; import org.locationtech.spatial4j.io.ShapeWriter; import org.locationtech.spatial4j.shape.Shape; import java.io.*; import java.text.NumberFormat; public class ShapeBenchmarks { public static void main(String[] args) throws Exception { JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); factory.geo = true; factory.normWrapLongitude = true; JtsSpatialContext ctx = new JtsSpatialContext(factory); PrintStream out = System.out; NumberFormat nf = NumberFormat.getPercentInstance(); InputStreamReader in = new InputStreamReader(ShapeBenchmarks.class.getResourceAsStream("/samples.txt")); try (BufferedReader br = new BufferedReader(in)) { String line; while ((line = br.readLine()) != null) { line = line.trim(); if(line.startsWith("#") || line.length()==0) { continue; } Shape shape = ctx.getFormats().getWktReader().read(line); double poly = ctx.getFormats().getWriter(ShapeIO.POLY).toString(shape).getBytes().length; out.println("Format | bytes | %poly | encoded"); out.println("------ | ----- | ----- | -------"); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ctx.getBinaryCodec().writeShape(new DataOutputStream(baos), shape); out.print(" binary | "); out.print(baos.size()); out.print(" | "); out.print(nf.format(poly/baos.size())); out.print(" | "); out.print("..."); out.println(); for(ShapeWriter writer : ctx.getFormats().getWriters()) { if(writer instanceof LegacyShapeWriter) { continue; } String str = writer.toString(shape); out.print(writer.getFormatName()); out.print(" | "); out.print(str.length()); out.print(" | "); out.print(nf.format(poly/str.getBytes().length)); out.print(" | "); out.print(str); out.println(); } out.println(); out.println(); } } } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/jackson/000077500000000000000000000000001375755266700270605ustar00rootroot00000000000000JacksonGeoJSONReaderTest.java000066400000000000000000000034611375755266700343500ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/jackson/******************************************************************************* * Copyright (c) 2017 Voyager Search * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jackson; import static org.junit.Assert.assertEquals; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.io.GeneralGeoJSONTest; import org.locationtech.spatial4j.io.ShapeIO; import org.locationtech.spatial4j.io.jackson.ShapesAsGeoJSONModule; import org.locationtech.spatial4j.shape.Shape; import com.fasterxml.jackson.databind.ObjectMapper; /** * This test compares the jackson JSONWriter to the standard GeoJSON Writer */ public class JacksonGeoJSONReaderTest extends GeneralGeoJSONTest { @Before @Override public void setUp() { super.setUp(); ctx = JtsSpatialContext.GEO; ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new ShapesAsGeoJSONModule()); reader = new JacksonShapeReader(mapper); writer = ctx.getFormats().getWriter(ShapeIO.GeoJSON); writerForTests = writer; Assert.assertNotNull(reader); Assert.assertNotNull(writer); Assert.assertNotNull(writerForTests); } @Override @Test public void testEncodeBufferedLineString() throws Exception { // the JTS buffered LineString becomes a polygon! Shape out = reader.read( bufferedLineText() ); assertEquals(out.getClass(), bufferedLine().getClass()); } } JacksonGeoJSONWriterTest.java000066400000000000000000000057321375755266700344250ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/jackson/******************************************************************************* * Copyright (c) 2017 Voyager Search * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jackson; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.io.GeneralGeoJSONTest; import org.locationtech.spatial4j.io.ShapeIO; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.SpatialRelation; import org.locationtech.spatial4j.shape.jts.JtsGeometry; /** * This test compares the jackson JSONWriter to the standard GeoJSON Writer */ public class JacksonGeoJSONWriterTest extends GeneralGeoJSONTest { @Before @Override public void setUp() { super.setUp(); ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new ShapesAsGeoJSONModule()); JacksonShapeWriter w = new JacksonShapeWriter(mapper); reader = ctx.getFormats().getReader(ShapeIO.GeoJSON); writer = w; writerForTests = writer; Assert.assertNotNull(reader); Assert.assertNotNull(writer); Assert.assertNotNull(writerForTests); } @Test public void testWriteUnknownAsWKT() throws Exception { // some anonymous impl that doesn't do anything Shape shape = new Shape() { @Override public SpatialRelation relate(Shape other) { throw new UnsupportedOperationException("TODO unimplemented");//TODO } @Override public Rectangle getBoundingBox() { throw new UnsupportedOperationException("TODO unimplemented");//TODO } @Override public boolean hasArea() { throw new UnsupportedOperationException("TODO unimplemented");//TODO } @Override public double getArea(SpatialContext ctx) { throw new UnsupportedOperationException("TODO unimplemented");//TODO } @Override public Point getCenter() { throw new UnsupportedOperationException("TODO unimplemented");//TODO } @Override public Shape getBuffered(double distance, SpatialContext ctx) { throw new UnsupportedOperationException("TODO unimplemented");//TODO } @Override public boolean isEmpty() { throw new UnsupportedOperationException("TODO unimplemented");//TODO } @Override public SpatialContext getContext() { return SpatialContext.GEO; } }; String str = writer.toString(shape); Assert.assertTrue(str.indexOf("wkt")>0); } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/jackson/JacksonShapeReader.java000066400000000000000000000032111375755266700334140ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2017 Voyager Search * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jackson; import java.io.IOException; import java.io.Reader; import java.text.ParseException; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.io.ShapeReader; import org.locationtech.spatial4j.shape.Shape; import com.fasterxml.jackson.databind.ObjectMapper; /** * This is really just a utility for testing */ public class JacksonShapeReader implements ShapeReader { final ObjectMapper mapper; public JacksonShapeReader(ObjectMapper m) { this.mapper = m; } @Override public String getFormatName() { return getClass().getSimpleName(); } @Override public Shape read(Object value) throws IOException, ParseException, InvalidShapeException { String str = value.toString(); return mapper.readValue(str, Shape.class); } @Override public Shape readIfSupported(Object value) throws InvalidShapeException { try { return read(value); } catch (IOException | ParseException e) { return null; } } @Override public Shape read(Reader reader) throws IOException, ParseException, InvalidShapeException { return mapper.readValue(reader, Shape.class); } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/jackson/JacksonShapeWriter.java000066400000000000000000000025651375755266700335010ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2017 Voyager Search * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jackson; import java.io.IOException; import java.io.Writer; import org.locationtech.spatial4j.io.ShapeWriter; import org.locationtech.spatial4j.shape.Shape; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; /** * This is really just a utility for testing */ public class JacksonShapeWriter implements ShapeWriter { final ObjectMapper mapper; public JacksonShapeWriter(ObjectMapper m) { this.mapper = m; } @Override public String getFormatName() { return getClass().getSimpleName(); } @Override public void write(Writer output, Shape shape) throws IOException { output.write(toString(shape)); } @Override public String toString(Shape shape) { try { return mapper.writeValueAsString(shape); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/jackson/ObjectWithGeometry.java000066400000000000000000000012701375755266700335010ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2017 Voyager Search * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jackson; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.jts.geom.Geometry; public class ObjectWithGeometry { public String name; public Geometry geo; public Shape shape; } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/jackson/SimpleJacksonTest.java000066400000000000000000000047351375755266700333360ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2017 Voyager Search * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.io.jackson; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import org.junit.Test; import org.locationtech.jts.geom.Coordinate; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.shape.RandomizedShapeTest; import org.locationtech.spatial4j.shape.jts.JtsShapeFactory; import java.io.IOException; import static org.junit.Assert.assertEquals; public class SimpleJacksonTest extends RandomizedShapeTest { public SimpleJacksonTest() { super(JtsSpatialContext.GEO); } @Test public void testReadWriteShapeAsGeoJSON() throws IOException { ObjectWithGeometry obj = new ObjectWithGeometry(); obj.name = "Hello"; obj.shape = ctx.getShapeFactory().pointXY(11,12); // Spatial4j type obj.geo = null; // ObjectMapper mapper = new ObjectMapper(); mapper.enable(SerializationFeature.INDENT_OUTPUT); mapper.registerModule(new ShapesAsGeoJSONModule()); String json = mapper.writeValueAsString(obj); ObjectWithGeometry out = mapper.readValue(json, ObjectWithGeometry.class); assertEquals(obj.shape, out.shape); } @Test public void testReadWriteJtsAsWKT() throws IOException { final JtsShapeFactory shapeFactory = ((JtsSpatialContext) ctx).getShapeFactory(); ObjectWithGeometry obj = new ObjectWithGeometry(); obj.name = "Hello"; obj.shape = null; obj.geo = shapeFactory.getGeometryFactory().createPoint(new Coordinate(11, 12)); // JTS type ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new ShapesAsWKTModule()); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); String json = objectMapper.writeValueAsString(obj); assertEquals("{\"name\":\"Hello\",\"geo\":\"POINT (11 12)\"}", json); ObjectWithGeometry deserialized = objectMapper.readValue(json, ObjectWithGeometry.class); assertEquals(obj.geo, deserialized.geo); } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/shape/000077500000000000000000000000001375755266700261215ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/shape/AbstractTestShapes.java000077500000000000000000000203311375755266700325350ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape; import org.locationtech.spatial4j.TestLog; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.distance.DistanceCalculator; import org.locationtech.spatial4j.distance.DistanceUtils; import org.locationtech.spatial4j.shape.impl.PointImpl; import org.locationtech.spatial4j.shape.impl.RectangleImpl; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.locationtech.spatial4j.shape.SpatialRelation.CONTAINS; import static org.locationtech.spatial4j.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-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/shape/BufferedLineStringTest.java000066400000000000000000000040011375755266700333400ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape; import com.carrotsearch.randomizedtesting.RandomizedTest; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.shape.impl.BufferedLineString; import org.locationtech.spatial4j.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-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/shape/BufferedLineTest.java000066400000000000000000000144511375755266700321630ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape; import com.carrotsearch.randomizedtesting.RandomizedTest; import com.carrotsearch.randomizedtesting.annotations.Repeat; import org.locationtech.spatial4j.TestLog; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.shape.impl.BufferedLine; import org.locationtech.spatial4j.shape.impl.PointImpl; import org.locationtech.spatial4j.shape.impl.RectangleImpl; import org.junit.Rule; import org.junit.Test; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; 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-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/shape/JtsGeometryTest.java000077500000000000000000000337671375755266700321230ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape; import com.carrotsearch.randomizedtesting.RandomizedContext; import com.carrotsearch.randomizedtesting.annotations.Repeat; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.io.WKTReader; import org.locationtech.spatial4j.shape.impl.PointImpl; import org.locationtech.spatial4j.shape.impl.RectangleImpl; import org.locationtech.spatial4j.shape.jts.JtsGeometry; import org.locationtech.jts.geom.*; import org.junit.Test; import org.locationtech.spatial4j.util.Geom; 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 org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.locationtech.spatial4j.shape.SpatialRelation.CONTAINS; import static org.locationtech.spatial4j.shape.SpatialRelation.DISJOINT; import static org.locationtech.spatial4j.shape.SpatialRelation.INTERSECTS; import static org.locationtech.spatial4j.shape.SpatialRelation.WITHIN; /** Tests {@link org.locationtech.spatial4j.shape.jts.JtsGeometry} and some other code related * to {@link org.locationtech.spatial4j.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 final JtsSpatialContext ctxNotGeo; public JtsGeometryTest() throws ParseException { super(JtsSpatialContext.GEO); POLY_SHAPE = (JtsGeometry) wkt(ctx, POLY_STR); if (ctx.isGeo()) { POLY_SHAPE_DL = shiftPoly(POLY_SHAPE, DL_SHIFT); assertTrue(POLY_SHAPE_DL.getBoundingBox().getCrossesDateLine()); } JtsSpatialContextFactory ctxFactory = new JtsSpatialContextFactory(); ctxFactory.geo = false; ctxFactory.worldBounds = new RectangleImpl(-1000, 1000, -1000, 1000, null); ctxNotGeo = ctxFactory.newSpatialContext(); } 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) wkt(ctx, pGeom.toText()); } @Test public void testRelations() throws ParseException { testRelations(false); testRelations(true); } public void testRelations(boolean prepare) throws ParseException { assert !((JtsSpatialContext)ctx).isAutoIndex(); //base polygon JtsGeometry base = (JtsGeometry) wkt(ctx, "POLYGON((0 0, 10 0, 5 5, 0 0))"); //shares only "10 0" with base JtsGeometry polyI = (JtsGeometry) wkt(ctx, "POLYGON((10 0, 20 0, 15 5, 10 0))"); //within base: differs from base by one point is within JtsGeometry polyW = (JtsGeometry) wkt(ctx, "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) wkt(ctx, "LINESTRING(0 0, 10 0)"); //a line sharing only one point with base JtsGeometry lineI = (JtsGeometry) wkt(ctx, "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 = wkt(ctx, "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) wkt(ctx, "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; // either we need to not use JTS's MultiPolygon, or we need to set allowMultiOverlap=true if (randomBoolean()) { factory.allowMultiOverlap = true; } else { factory.useJtsMulti = false; } JtsSpatialContext ctx = factory.newSpatialContext(); Shape shape = wkt(ctx, 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 = wkt(ctx, 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(); } } @Test public void testNarrowGeometryCollection() { // test points GeometryCollection gcol = Geom.build() .point(1, 1).point() .point(2, 3).point() .toCollection(); assertFalse(gcol instanceof MultiPoint); JtsGeometry geom = JtsSpatialContext.GEO.makeShape(gcol); assertTrue(geom.getGeom() instanceof MultiPoint); // test lines gcol = Geom.build() .point(1,1).point(2,2).lineString() .point(3,3).point(4,4).lineString() .toCollection(); geom = JtsSpatialContext.GEO.makeShape(gcol); assertTrue(geom.getGeom() instanceof MultiLineString); // test polygons gcol = Geom.build() .point(1,1).point().buffer(1) .point(2,3).point().buffer(1) .toCollection(); geom = JtsSpatialContext.GEO.makeShape(gcol); assertTrue(geom.getGeom() instanceof MultiPolygon); // test heterogenous gcol = Geom.build() .point(0,0).point() .point(1,1).point(2,2).lineString() .toCollection(); try { JtsSpatialContext.GEO.makeShape(gcol); fail("heterogenous geometry collection should throw exception"); } catch(IllegalArgumentException expected) { } } @Test public void testPolyRelatesToCircle() throws ParseException { // The polygon is a triangle with a 90-degree angle and two equal sides, and with // a rectangular hole in the middle. Shape poly = wkt(ctxNotGeo, "POLYGON ((1 1, 1 50, 50 1, 1 1), (10 10, 10 15, 15 15, 15 10, 10 10))"); assertRelation(WITHIN, poly, ctxNotGeo.makeCircle(25, 25, 40)); assertRelation(CONTAINS, poly, ctxNotGeo.makeCircle(10, 25, 5)); assertRelation(DISJOINT, poly, ctxNotGeo.makeCircle(35, 35, 5)); assertRelation(DISJOINT, poly, ctxNotGeo.makeCircle(12, 12, 1)); // inside the hole // Intersects, or almost intersects and is something else // The circle... assertRelation(INTERSECTS, poly, ctxNotGeo.makeCircle(25, 25, 34)); // not *quite* within assertRelation(INTERSECTS, poly, ctxNotGeo.makeCircle(30, 30, 10)); // crosses into the long angle assertRelation(DISJOINT, poly, ctxNotGeo.makeCircle(30, 30, 5)); // almost crosses into the long angle assertRelation(INTERSECTS, poly, ctxNotGeo.makeCircle(25, -5, 10)); // crosses into the bottom edge assertRelation(DISJOINT, poly, ctxNotGeo.makeCircle(25, -5, 1)); // almost crosses into the bottom edge assertRelation(INTERSECTS, poly, ctxNotGeo.makeCircle(0, 0, 10)); // encloses a corner assertRelation(INTERSECTS, poly, ctxNotGeo.makeCircle(10, 35, 5)); // inside but sticks out at the angle assertRelation(INTERSECTS, poly, ctxNotGeo.makeCircle(12, 12, 10)); // encloses the hole but otherwise inside the triangle } @Test public void testMultiLineStringRelatesToCircle() throws org.locationtech.jts.io.ParseException { // use JTS WKTReader to ensure we get one Geometry in the end org.locationtech.jts.io.WKTReader wktReader = new org.locationtech.jts.io.WKTReader(); Shape poly = ctxNotGeo.makeShape(wktReader.read("MULTILINESTRING ((5 20, 5 5, 20 5), (20 25, 30 15))")); assertEquals(JtsGeometry.class, poly.getClass()); assertRelation(WITHIN, poly, ctxNotGeo.makeCircle(15, 15, 20)); assertRelation(DISJOINT, poly, ctxNotGeo.makeCircle(15, 15, 5)); // much smaller now; doesn't touch anything assertRelation(INTERSECTS, poly, ctxNotGeo.makeCircle(5, 5, 16)); // circle encloses the left lineString assertRelation(INTERSECTS, poly, ctxNotGeo.makeCircle(25, 20, 10)); // circle encloses the right lineString assertRelation(INTERSECTS, poly, ctxNotGeo.makeCircle(5, 20, 1)); // circle encloses first point assertRelation(INTERSECTS, poly, ctxNotGeo.makeCircle(26, 21, 2)); // only intersects an edge of 2nd // not CONTAINS is impossible with a circle; line strings don't contain anything } private Shape wkt(SpatialContext ctx, String wkt) throws ParseException { return ((WKTReader) ctx.getFormats().getWktReader()).parse(wkt); } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/shape/RandomizedShapeTest.java000066400000000000000000000235171375755266700327110ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape; import com.carrotsearch.randomizedtesting.RandomizedTest; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.distance.DistanceUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.locationtech.spatial4j.shape.SpatialRelation.CONTAINS; import static org.locationtech.spatial4j.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()); } } } //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); double[] worldXRange = {bounds.getMinX(), bounds.getMaxX()}; double[] worldYRange = {bounds.getMinY(), bounds.getMaxY()}; double[] xRange = randomRange(rarely() ? 0 : nearP.getX(), worldXRange); double[] yRange = randomRange(rarely() ? 0 : nearP.getY(), worldYRange); return makeNormRect( divisible(xRange[0]), divisible(xRange[1]), divisible(yRange[0]), divisible(yRange[1]) ); } private double[] randomRange(double near, double[] bounds) { final double boundsWidth = bounds[1] - bounds[0]; double mid = near + randomGaussian() * boundsWidth / 6; double width = Math.abs(randomGaussian()) * boundsWidth / 6;//1/3rd return new double[]{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 (!shape.relate(p).intersects()); return p; } protected Point randomPointInOrNull(Shape shape) { if (!shape.hasArea())// or try the center? throw new UnsupportedOperationException("Need area to define shape!"); Rectangle bbox = shape.getBoundingBox(); for (int i = 0; i < 1000; i++) { Point p = randomPointIn(bbox); if (shape.relate(p).intersects()) { return p; } } return null;//tried too many times and failed } /** Tests that {@code left} >= {@code right}, but may be less if within some tolerance. */ public static void assertGreaterOrEqual(double left, double right, double delta) { if (left > right) { return; } assertEquals(left, right, delta); } } RectIntersectionTestHelper.java000066400000000000000000000160161375755266700341750ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/shape/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape; import org.locationtech.spatial4j.TestLog; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.impl.InfBufLine; import org.locationtech.spatial4j.shape.impl.PointImpl; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.locationtech.spatial4j.shape.SpatialRelation.CONTAINS; import static org.locationtech.spatial4j.shape.SpatialRelation.DISJOINT; public abstract class RectIntersectionTestHelper extends RandomizedShapeTest { public RectIntersectionTestHelper(SpatialContext ctx) { super(ctx); } /** Override to return true if generateRandomShape is essentially a Rectangle. */ protected boolean isRandomShapeRectangular() { return false; } protected abstract S generateRandomShape(Point nearP); /** shape has no area; return a point in it */ protected abstract Point randomPointInEmptyShape(S shape); // Minimum distribution of relationships // Each shape has different characteristics, so we don't expect (for instance) shapes that // are likely to be long and thin to contain as many rectangles as those that // short and fat. protected int getContainsMinimum(int laps) { return laps/1000; } protected int getIntersectsMinimum(int laps) { return laps/1000; } protected int getWithinMinimum(int laps) { return laps/1000; } protected int getDisjointMinimum(int laps) { return laps/1000; } protected int getBoundingMinimum(int laps) { return laps/1000; } @SuppressWarnings("unchecked") @Override protected Point randomPointInOrNull(Shape shape) { if (!shape.hasArea()) { final Point pt = randomPointInEmptyShape((S) shape); assert shape.relate(pt).intersects() : "faulty randomPointInEmptyShape"; return pt; } return super.randomPointInOrNull(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 MINLAPS = scaledRandomIntBetween(20000, 200000); while(i_C < getContainsMinimum(MINLAPS) || i_I < getIntersectsMinimum(MINLAPS) || i_W < getWithinMinimum(MINLAPS) || (!isRandomShapeRectangular() && i_D < getDisjointMinimum(MINLAPS)) || i_bboxD < getBoundingMinimum(MINLAPS)) { laps++; TestLog.clear(); if (laps > MINLAPS) { fail("Did not find enough contains/within/intersection/disjoint/bounds cases in a reasonable number" + " of random attempts. CWIDbD: " + i_C + "("+getContainsMinimum(MINLAPS)+")," + i_W + "("+getWithinMinimum(MINLAPS)+")," + i_I + "("+getIntersectsMinimum(MINLAPS)+")," + i_D + "("+getDisjointMinimum(MINLAPS)+")," + i_bboxD + "("+getBoundingMinimum(MINLAPS)+")" + " Laps exceeded " + MINLAPS); } 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); if (ic != DISJOINT) { assertTrue("if not disjoint then the shape's bbox shouldn't be disjoint", s.getBoundingBox().relate(r).intersects()); } try { int MAX_TRIES = scaledRandomIntBetween(10, 100); switch (ic) { case CONTAINS: i_C++; for (int j = 0; j < MAX_TRIES; j++) { Point p = randomPointIn(r); assertRelation(null, CONTAINS, s, p); } break; case WITHIN: i_W++; for (int j = 0; j < MAX_TRIES; j++) { Point p = randomPointInOrNull(s); if (p == null) {//couldn't find a random point in shape break; } assertRelation(null, CONTAINS, r, p); } break; case DISJOINT: if (!s.getBoundingBox().relate(r).intersects()) {//bboxes are disjoint i_bboxD++; if (i_bboxD >= getBoundingMinimum(MINLAPS)) break; } else { i_D++; } for (int j = 0; j < MAX_TRIES; j++) { Point p = randomPointIn(r); assertRelation(null, DISJOINT, s, p); } break; case INTERSECTS: i_I++; SpatialRelation pointR = null;//set once Rectangle randomPointSpace = null; MAX_TRIES = 1000;//give many attempts 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; } } break; default: fail(""+ic); } // switch } catch (AssertionError e) { onAssertFail(e, s, r, ic); } } // while loop 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-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/shape/RoundingDistCalc.java000066400000000000000000000037071375755266700321670ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.distance.AbstractDistanceCalculator; import org.locationtech.spatial4j.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-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/shape/ShapeCollectionTest.java000066400000000000000000000112361375755266700327030ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape; import org.locationtech.spatial4j.TestLog; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.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 org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.locationtech.spatial4j.shape.SpatialRelation.CONTAINS; public class ShapeCollectionTest extends RandomizedShapeTest { public static final String WORLD180 = getLonRangeString(SpatialContext.GEO.getWorldBounds()); protected static String getLonRangeString(Rectangle bbox) { return bbox.getMinX()+" "+bbox.getMaxX(); } @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); } @Test public void testBboxNotWorldWrap() { ctx = SpatialContext.GEO; //doesn't contain 102, thus shouldn't world-wrap Rectangle r1 = ctx.makeRectangle(-92, 90, -10, 10); Rectangle r2 = ctx.makeRectangle(130, 172, -10, 10); Rectangle r3 = ctx.makeRectangle(172, -60, -10, 10); ShapeCollection s = new ShapeCollection<>(Arrays.asList(r1, r2, r3), ctx); assertEquals("130.0 90.0", getLonRangeString(s.getBoundingBox())); // note: BBoxCalculatorTest thoroughly tests the longitude range } 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(WORLD180, getLonRangeString(s.getBoundingBox())); //flip r1, r2 order s = new ShapeCollection<>(Arrays.asList(r2, r1), ctx); assertEquals(WORLD180, getLonRangeString(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); } if (ctx.isGeo() && msBbox.getMinX() == -180 && msBbox.getMaxX() == 180) { int lonTest = randomIntBetween(-180, 180); boolean valid = false; for (Rectangle shape : shapes) { if (shape.relateXRange(lonTest, lonTest).intersects()) { valid = true; break; } } if (!valid) fail("ShapeCollection bbox world-wrap doesn't contain "+lonTest+" for shapes: "+shapes); } } return shapeCollection; } protected Point randomPointInEmptyShape(ShapeCollection shape) { Rectangle r = (Rectangle) shape.getShapes().get(0); return randomPointIn(r); } } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/shape/TestShapes2D.java000066400000000000000000000134131375755266700312370ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 Voyager Search and MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.shape.impl.*; import org.junit.Test; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; import static org.locationtech.spatial4j.shape.SpatialRelation.CONTAINS; import static org.locationtech.spatial4j.shape.SpatialRelation.DISJOINT; import static org.locationtech.spatial4j.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 testLineString() { //see BufferedLineStringTest & BufferedLineTest for more Shape shape = ctx.getShapeFactory().lineString().buffer(randomInt(3)).build(); testEmptiness(shape); } /** 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-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/shape/TestShapesGeo.java000066400000000000000000000252741375755266700315140ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 MITRE * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import com.carrotsearch.randomizedtesting.annotations.Repeat; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContextFactory; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.distance.DistanceCalculator; import org.locationtech.spatial4j.distance.DistanceUtils; import org.locationtech.spatial4j.distance.GeodesicSphereDistCalc; import org.locationtech.spatial4j.exception.InvalidShapeException; import org.junit.Test; import java.util.Arrays; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.locationtech.spatial4j.shape.SpatialRelation.CONTAINS; import static org.locationtech.spatial4j.shape.SpatialRelation.DISJOINT; import static org.locationtech.spatial4j.shape.SpatialRelation.INTERSECTS; import static org.locationtech.spatial4j.shape.SpatialRelation.WITHIN; public class TestShapesGeo extends AbstractTestShapes { @ParametersFactory public static Iterable parameters() { final DistanceCalculator distCalcL = new GeodesicSphereDistCalc.LawOfCosines(); final DistanceCalculator distCalcH = new GeodesicSphereDistCalc.Haversine();//default final DistanceCalculator distCalcV = new GeodesicSphereDistCalc.Vincenty(); return Arrays.asList($$( $(new SpatialContextFactory(){{geo = true; distCalc = new RoundingDistCalc(distCalcL);}}.newSpatialContext()), $(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 @Repeat(iterations = 1) 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 } } //Test geo rectangle intersections testRectIntersect(); //Ambiguous vertical line at dateline; -180 vs +180. Bug #85 assertRelation(WITHIN, ctx.makeRectangle(-180, -180, -10, 10), ctx.makeRectangle(180, 180, -30, 30)); //Test buffer assertEquals(ctx.makeRectangle(-10, 10, -10, 10), ctx.makeRectangle(0, 0, 0, 0).getBuffered(10, ctx)); int MAX_TRIES = scaledRandomIntBetween(100, 1000); for (int i = 0; i < MAX_TRIES; 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 { assertGreaterOrEqual(br.getWidth() - r.getWidth(), 2 * buf, EPS); //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: TestShapes2D.testCircleReset(ctx); 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(); } @Test public void testEmptyLineString() { Shape shape = ctx.getShapeFactory().lineString().buffer(randomInt(3)).build(); testEmptiness(shape); } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/shape/impl/000077500000000000000000000000001375755266700270625ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/shape/impl/BBoxCalculatorTest.java000066400000000000000000000071541375755266700334400ustar00rootroot00000000000000/******************************************************************************* * Copyright (c) 2015 David Smiley * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License, Version 2.0 which * accompanies this distribution and is available at * http://www.apache.org/licenses/LICENSE-2.0.txt ******************************************************************************/ package org.locationtech.spatial4j.shape.impl; import com.carrotsearch.randomizedtesting.annotations.Repeat; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.RandomizedShapeTest; import org.locationtech.spatial4j.shape.Rectangle; import org.locationtech.spatial4j.shape.SpatialRelation; import org.junit.Test; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class BBoxCalculatorTest extends RandomizedShapeTest { public BBoxCalculatorTest() { super(SpatialContext.GEO); } // note: testing latitude would be so simple that's effectively the same code as the code to be tested. So I don't. @Test @Repeat(iterations = 100) public void testGeoLongitude() { BBoxCalculator calc = new BBoxCalculator(ctx); final int numShapes = randomIntBetween(1, 4);//inclusive List rects = new ArrayList<>(numShapes); for (int i = 0; i < numShapes; i++) { Rectangle rect = randomRectangle(30);// divisible by rects.add(rect); calc.expandRange(rect); } Rectangle boundary = calc.getBoundary(); if (numShapes == 1) { assertEquals(rects.get(0), boundary); return; } // If the boundary is the world-bounds, check that it's right. if (boundary.getMinX() == -180 && boundary.getMaxX() == 180) { // each longitude should be present in at least one shape: for (int lon = -180; lon <= +180; lon++) { assertTrue(atLeastOneRectHasLon(rects, lon)); } return; } // Test that it contains all shapes: for (Rectangle rect : rects) { assertRelation(SpatialRelation.CONTAINS, boundary, rect); } // Test that the left & right are boundaries: assertTrue(atLeastOneRectHasLon(rects, boundary.getMinX())); assertFalse(atLeastOneRectHasLon(rects, normX(boundary.getMinX() - 0.5))); assertTrue(atLeastOneRectHasLon(rects, boundary.getMaxX())); assertFalse(atLeastOneRectHasLon(rects, normX(boundary.getMaxX() + 0.5))); // Test that this is the smallest enclosing boundary by ensuring the gap (opposite the bbox) is // the largest: if (boundary.getWidth() > 180) { // conversely if wider than 180 then no wider gap is possible double biggerGap = 360.0 - boundary.getWidth() + 0.5; for (Rectangle rect : rects) { // try to see if a bigger gap could lie to the right of this rect double gapRectLeft = rect.getMaxX() + 0.25; double gapRectRight = gapRectLeft + biggerGap; Rectangle testGap = makeNormRect(gapRectLeft, gapRectRight, -90, 90); boolean fits = true; for (Rectangle rect2 : rects) { if (rect2.relate(testGap).intersects()) { fits = false; break; } } assertFalse(fits);//should never fit because it's larger than the biggest gap } } } private boolean atLeastOneRectHasLon(List rects, double lon) { for (Rectangle rect : rects) { if (rect.relateXRange(lon, lon).intersects()) { return true; } } return false; } }spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/shape/jts/000077500000000000000000000000001375755266700267215ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/shape/jts/JtsShapeFactoryTest.java000066400000000000000000000134421375755266700335010ustar00rootroot00000000000000/* * 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 org.locationtech.spatial4j.shape.jts; import org.junit.Assert; import org.junit.Test; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.Polygon; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.distance.DistanceUtils; import org.locationtech.spatial4j.distance.GeodesicSphereDistCalc; import org.locationtech.spatial4j.shape.Circle; import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.impl.GeoCircle; import org.locationtech.spatial4j.shape.impl.PointImpl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class JtsShapeFactoryTest { @Test public void testIndex() { JtsSpatialContextFactory ctxFactory = new JtsSpatialContextFactory(); ctxFactory.autoIndex = true; Geometry g = ctxFactory.getGeometryFactory().createPoint(new Coordinate(0,0)).buffer(0.1); JtsSpatialContext ctx = ctxFactory.newSpatialContext(); JtsGeometry jtsGeom1 = (JtsGeometry) ctx.getShapeFactory().makeShapeFromGeometry(g); assertTrue(jtsGeom1.isIndexed()); JtsGeometry jtsGeom2 = ctx.getShapeFactory().makeShape(g); assertTrue(jtsGeom2.isIndexed()); } @Test public void testEmptyPoint() { JtsSpatialContextFactory jtsCtxFactory = new JtsSpatialContextFactory(); JtsSpatialContext jtsCtx = jtsCtxFactory.newSpatialContext(); GeometryFactory geometryFactory = jtsCtxFactory.getGeometryFactory(); final org.locationtech.jts.geom.Point point = geometryFactory.createPoint();//empty final Shape shape = jtsCtx.getShapeFactory().makeShapeFromGeometry(point); // don't throw assertTrue(shape.isEmpty()); } @Test public void testCircleGeometryConversions() { // Hawaii (Far West) circleGeometryConversionTest(-155.84, 19.74, 50); // Nunavat (Far North) circleGeometryConversionTest(-83.10, 70.30, 100); // Sydney (South East) circleGeometryConversionTest(151.21, 33.87, 1); } private void circleGeometryConversionTest(double x, double y, double radiusKm) { Point circleCenter = new PointImpl(x, y, SpatialContext.GEO); double radiusDeg = DistanceUtils.dist2Degrees(radiusKm, DistanceUtils.EARTH_EQUATORIAL_RADIUS_KM); GeoCircle geoCircle = new GeoCircle(circleCenter, radiusDeg, SpatialContext.GEO); JtsShapeFactory shapeFactory = JtsSpatialContext.GEO.getShapeFactory(); // Let's ensure the circle-to-polygon conversion is accurate accounting for geodesics. Geometry geometry = shapeFactory.getGeometryFrom(geoCircle); Assert.assertTrue(geometry instanceof Polygon); Polygon polygon = (Polygon) geometry; Coordinate[] coordinates = polygon.getExteriorRing().getCoordinates(); int size = coordinates.length; Assert.assertTrue(size >= 100); GeodesicSphereDistCalc distCalc = new GeodesicSphereDistCalc.Haversine(); double maxDeltaKm = radiusKm / 100; // allow 1% inaccuracy for (Coordinate coordinate : coordinates) { // Check distance from center of each point Point point = new PointImpl(coordinate.x, coordinate.y, SpatialContext.GEO); double distance = distCalc.distance(point, circleCenter); double distanceKm = DistanceUtils.degrees2Dist(distance, DistanceUtils.EARTH_MEAN_RADIUS_KM); assertEquals(String.format("Distance from point to center: %.2f km. Expected: %.2f km", distanceKm, radiusKm), radiusKm, distanceKm, maxDeltaKm); } } @Test public void testCircleDateLineWrapping() { // left wrapping (-180) circleGeometryConversionDateLineTest(-179.99, 51.22, 5); // right wrapping (+180) circleGeometryConversionDateLineTest(179.99, -35.6, 10); } private void circleGeometryConversionDateLineTest(double lon, double lat, double radiusKm) { JtsShapeFactory shapeFactory = JtsSpatialContext.GEO.getShapeFactory(); Circle circle = shapeFactory.circle(lon, lat, DistanceUtils.dist2Degrees(radiusKm, DistanceUtils.EARTH_MEAN_RADIUS_KM)); Geometry geom = shapeFactory.getGeometryFrom(circle); assertTrue(circle.getBoundingBox().getCrossesDateLine()); assertEquals("MultiPolygon", geom.getGeometryType()); GeodesicSphereDistCalc distCalc = new GeodesicSphereDistCalc.Haversine(); double maxDeltaKm = radiusKm / 100; // allow 1% inaccuracy for (Coordinate c : geom.getCoordinates()) { // Check distance from center of each point Point point = new PointImpl(c.x, c.y, SpatialContext.GEO); double distance = distCalc.distance(point, lon, lat); double distanceKm = DistanceUtils.degrees2Dist(distance, DistanceUtils.EARTH_MEAN_RADIUS_KM); assertEquals(String.format("Distance from point to center: %.2f km. Expected: %.2f km", distanceKm, radiusKm), radiusKm, distanceKm, maxDeltaKm); } } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/util/000077500000000000000000000000001375755266700257765ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/util/Geom.java000066400000000000000000000322751375755266700275410ustar00rootroot00000000000000/* Copyright 2013 The jeo project. All rights reserved. * * 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. */ package org.locationtech.spatial4j.util; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.prep.PreparedGeometry; import org.locationtech.jts.geom.prep.PreparedGeometryFactory; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryCollection; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.MultiLineString; import org.locationtech.jts.geom.MultiPoint; import org.locationtech.jts.geom.MultiPolygon; import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Polygon; /** * Geometry module utility module. * *

* Class taken from http://github.com/jeo/jeo, Nov 2017 by Justin Deoliveira. *

* * @author Justin Deoliveira, OpenGeo */ public class Geom { /** * static default factory */ public final static GeometryFactory factory = new GeometryFactory(); /** * Geometry type enumeration. */ public enum Type { POINT(Point.class), LINESTRING(LineString.class), POLYGON(Polygon.class), MULTIPOINT(MultiPoint.class), MULTILINESTRING(MultiLineString.class), MULTIPOLYGON(MultiPolygon.class), GEOMETRY(Geometry.class), GEOMETRYCOLLECTION(GeometryCollection.class); private final Class type; private final String name; private final String simpleName; Type(Class type) { this.type = type; this.name = type.getSimpleName(); this.simpleName = (name.startsWith("Multi") ? name.substring(5) : name); } /** * Return the {@code Geometry} class associated with this type. * * @return the {@code Geometry} class */ public Class getType() { return type; } /** * Equivalent to {@linkplain #getName()}. * * @return the name of this type */ @Override public String toString() { return name; } /** * Return a name for this type that is suitable for text descriptions. * * @return the name */ public String getName() { return name; } /** * Get the 'simple name'. Returns the same value as {@linkplain #getName()} * except for MULTIPOINT, MULTILINESTRING and MULTIPOLYGON, for which it returns * the name without the 'Multi' prefix. * * @return the simple name */ public String getSimpleName() { return simpleName; } /** * Get the {@code Geometries} for the given object. * * @param geom a JTS Geometry object * * @return the {@code Geometries} for the argument's class, or {@code null} * if the argument is {@code null} */ public static Type from(Geometry geom) { if (geom != null) { return from(geom.getClass()); } return null; } /** * Get the {@code Geometries} for the given {@code Geometry} class. * * @param geomClass the class * * @return the constant for this class */ public static Type from(Class geomClass) { for (Type gt : Type.values()) { if (gt.type == geomClass) { return gt; } } //no direct match look for a subclass Type match = null; for (Type gt : Type.values()) { if (gt == GEOMETRY || gt == GEOMETRYCOLLECTION) { continue; } if (gt.type.isAssignableFrom(geomClass)) { if (match == null) { match = gt; } else { // more than one match return null; } } } if (match == null) { //no matches from concrete classes, try abstract classes if (GeometryCollection.class.isAssignableFrom(geomClass)) { return GEOMETRYCOLLECTION; } if (Geometry.class.isAssignableFrom(geomClass)) { return GEOMETRY; } } return match; } /** * Get the {@code Geometries} for the specified name. * * @param name The name of the geometry, eg: "POINT" * * @return The constant for the name. */ public static Type from(String name) { for (Type gt : Type.values()) { if (gt.getName().equalsIgnoreCase(name)) { return gt; } } return null; } } /** * Creates a new geometry builder. */ public static GeomBuilder build() { return new GeomBuilder(); } /** * Convenience method to build a Point geometry. */ public static Point point(double x, double y) { return build().point(x, y).toPoint(); } /** * Convenience method to build a LineString geometry. * * @param ord Even number of ordinates forming coordinates for the line string. */ public static LineString lineString(double... ord) { return build().points(ord).toLineString(); } /** * Convenience method to build a Polygon geometry. * * @param ord Even number of ordinates forming coordinates for the outer ring of the polygon. */ public static Polygon polygon(double... ord) { return build().points(ord).toPolygon(); } /** * Returns an iterable over the points of a multipoint. */ public static Iterable iterate(MultiPoint mp) { return new GeometryIterable<>(mp); } /** * Returns an iterable over the lines of a multilinestring. */ public static Iterable iterate(MultiLineString ml) { return new GeometryIterable<>(ml); } /** * Returns an iterable over the polygons of a multipolygon. */ public static Iterable iterate(MultiPolygon mp) { return new GeometryIterable<>(mp); } /** * Returns an iterable over the geometries of a geometry collection.. */ public static Iterable iterate(GeometryCollection gc) { return new GeometryIterable<>(gc); } /** * Returns an iterable over the interior rings of a polygon. */ public static Iterable holes(final Polygon p) { return new Iterable() { int i = 0; @Override public Iterator iterator() { return new Iterator() { @Override public boolean hasNext() { return i < p.getNumInteriorRing(); } @Override public LineString next() { return p.getInteriorRingN(i++); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } }; } /** * Returns the first point in a multi point, or null if the multi point is empty. */ public static Point first(MultiPoint mp) { return mp.getNumGeometries() > 0 ? (Point) mp.getGeometryN(0) : null; } /** * Returns the first line in a multi line, or null if the multi line is empty. */ public static LineString first(MultiLineString ml) { return ml.getNumGeometries() > 0 ? (LineString) ml.getGeometryN(0) : null; } /** * Returns the first polygon in a multi polygon, or null if the multi polygon is empty. */ public static Polygon first(MultiPolygon mp) { return mp.getNumGeometries() > 0 ? (Polygon) mp.getGeometryN(0) : null; } /** * Returns the geometries of a geometry collection as an array. */ @SuppressWarnings("unchecked") public static T[] array(GeometryCollection gc, T[] array) { for (int i =0 ; i < gc.getNumGeometries(); i++) { array[i] = (T) gc.getGeometryN(i); } return array; } /** * Retypes (ie. narrows) a geometry collection if possible. *

* If the geometry contains a single object it is narrowed to that object. If the geometry collection is * homogeneous it is narrowed to the appropriate sub collection type. Otherwise the collection is returned * as is. *

* * @see {@link GeometryFactory#buildGeometry(Collection)} */ public static Geometry narrow(GeometryCollection gc) { if (gc.getNumGeometries() == 0) { return gc; } List objects = new ArrayList<>(gc.getNumGeometries()); for (Geometry g : iterate(gc)) { objects.add(g); } return gc.getFactory().buildGeometry(objects); } /** * Recursively flattens a geometry collection into it's constituent geometry objects. */ public static List flatten(GeometryCollection gc) { return flatten(Collections.singletonList(gc)); } /** * Recursively flattens a list of geometries into it's constituent geometry objects. */ public static List flatten(List gl) { List flat = new ArrayList<>(); LinkedList q = new LinkedList<>(gl); while (!q.isEmpty()) { Geometry g = q.removeFirst(); if (g instanceof GeometryCollection) { for (Geometry h : iterate((GeometryCollection)g)) { q.addLast(h); } } else { flat.add(g); } } return flat; } /** * Recursively unwraps a geometry collection containing single object. */ public static Geometry singlify(Geometry geom) { while (geom instanceof GeometryCollection && geom.getNumGeometries() == 1) { geom = geom.getGeometryN(0); } return geom; } /* * Private iterable class. */ private static class GeometryIterable implements Iterable { GeometryCollection gc; GeometryIterable(GeometryCollection gc) { this.gc = gc; } @Override public Iterator iterator() { return new GeometryIterator<>(gc); } } /* * Private iterator class. */ private static class GeometryIterator implements Iterator { int i = 0; GeometryCollection gc; GeometryIterator(GeometryCollection gc) { this.gc = gc; } @Override public boolean hasNext() { return i < gc.getNumGeometries(); } @SuppressWarnings("unchecked") @Override public T next() { return (T) gc.getGeometryN(i++); } @Override public void remove() { throw new UnsupportedOperationException(); } } /** * Creates a prepared geometry. *

* Prepared geometries make operations like intersection must faster. *

*/ public static PreparedGeometry prepare(Geometry g) { return PreparedGeometryFactory.prepare(g); } /** * Converts a geometry object into the associated geometry collection. For * example Polygon to MultiPolygon. *

* If the input is already a collection it is returned as is. *

*/ public static GeometryCollection multi(Geometry g) { switch(Geom.Type.from(g)) { case POINT: return factory.createMultiPoint(new Point[]{(Point)g}); case LINESTRING: return factory.createMultiLineString(new LineString[]{(LineString)g}); case POLYGON: return factory.createMultiPolygon(new Polygon[]{(Polygon)g}); default: return (GeometryCollection) g; } } } spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/util/GeomBuilder.java000066400000000000000000000267771375755266700310620ustar00rootroot00000000000000/* Copyright 2013 The jeo project. All rights reserved. * * 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. */ package org.locationtech.spatial4j.util; import java.lang.reflect.Array; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Deque; import java.util.Iterator; import java.util.Locale; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryCollection; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.LinearRing; import org.locationtech.jts.geom.MultiLineString; import org.locationtech.jts.geom.MultiPoint; import org.locationtech.jts.geom.MultiPolygon; import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Polygon; import org.locationtech.jts.geom.PrecisionModel; /** * Builder for geometry objects. *

* Example usage: *


 * GeometryBuilder gb = new GeometryBuilder();
 *
 * // create array two 2d points and turn into a line string
 * gb.points(1,2,3,4,5,6).toLineString();
 * 
 * // build a polygon with holes
 * gb.points(0,0,10,0,10,10,0,10,0,0).ring()
 *   .points(4,4,6,4,6,6,4,6,4,4).ring()
 *   .toPolygon();
 *   
 * 
*

* *

* Class taken from http://github.com/jeo/jeo, Nov 2017 by Justin Deoliveira. *

* * @author Justin Deoliveira, OpenGeo * */ public class GeomBuilder { GeometryFactory factory; Deque cstack = new ArrayDeque<>(); Deque gstack = new ArrayDeque<>(); /** * Constructs a builder with the default geometry factory. */ public GeomBuilder() { this(new GeometryFactory()); } /** * Constructs a builder with an explicit geometry factory. */ public GeomBuilder(GeometryFactory factory) { this.factory = factory; } /** * Constructs a builder with a specific srid for geometry objects. */ public GeomBuilder(int srid) { this.factory = new GeometryFactory(new PrecisionModel(), srid); } /** * Adds a 2d point to the coordinate stack. */ public GeomBuilder point(double x, double y) { cstack.push(new Coordinate(x, y)); return this; } /** * Adds a 3d point to the coordinate stack. */ public GeomBuilder pointz(double x, double y, double z) { cstack.push(new Coordinate(x,y,z)); return this; } /** * Adds an array of 2d points to the coordinate stack. */ public GeomBuilder points(double ...ord) { if (ord.length % 2 != 0) { throw new IllegalArgumentException("Must specify even number of ordinates"); } for (int i = 0; i < ord.length; i +=2 ) { point(ord[i], ord[i+1]); } return this; } /** * Adds an array of 3d points to the coordinate stack. */ public GeomBuilder pointsz(double ...ord) { if (ord.length % 3 != 0) { throw new IllegalArgumentException("Must specify ordinates as triples"); } for (int i = 0; i < ord.length; i +=3 ) { pointz(ord[i], ord[i+1], ord[i+2] ); } return this; } /** * Creates a Point from the last point on the coordinate stack, and places the result * on the geometry stack. */ public GeomBuilder point() { gstack.push(factory.createPoint(cpop())); return this; } /** * Creates a LineString from all points on the coordinate stack, and places the result * on the geometry stack. */ public GeomBuilder lineString() { gstack.push(factory.createLineString(cpopAll())); return this; } /** * Creates a LinearRing from all points on the coordinate stack, and places the result * on the geometry stack. *

* If the first and last coordinate on the point stack are not equal an additional point * will be added. *

*/ public GeomBuilder ring() { Coordinate[] coords = cpopAll(); if (coords.length > 1 && !coords[0].equals(coords[coords.length-1])) { Coordinate[] tmp = new Coordinate[coords.length+1]; System.arraycopy(coords, 0, tmp, 0, coords.length); tmp[tmp.length-1] = new Coordinate(tmp[0]); coords = tmp; } gstack.push(factory.createLinearRing(coords)); return this; } /** * Creates a Polygon from all LinearRings on the geometry stack and places the result back * on the geometry stack. */ public GeomBuilder polygon() { if (gstack.isEmpty() || !(gstack.peek() instanceof LinearRing)) { ring(); } LinearRing[] rings = gpopAll(LinearRing.class); LinearRing outer = rings[0]; LinearRing[] inner = null; if (rings.length > 1) { inner = Arrays.copyOfRange(rings, 1, rings.length); } gstack.push(factory.createPolygon(outer, inner)); return this; } /** * Creates a MultiPoint from all coordinates on the coordinate stack, plaching the result * back on the geometry stack. *

* If the coordinate stack is empty this method will consume all Point geometries on * the geometry stack. *

*/ public GeomBuilder multiPoint() { if (!cstack.isEmpty()) { gstack.push(factory.createMultiPoint(cpopAll())); } else { gstack.push(factory.createMultiPoint(gpopAll(Point.class))); } return this; } /** * Creates a MultiLineString from all LineStrings on the geometry stack and places the result * back on the geometry stack. */ public GeomBuilder multiLineString() { gstack.push(factory.createMultiLineString(gpopAll(LineString.class))); return this; } /** * Creates a MultiPolygon from all Polygons on the geometry stack and places the result * back on the geometry stack. */ public GeomBuilder multiPolygon() { gstack.push(factory.createMultiPolygon(gpopAll(Polygon.class))); return this; } /** * Creates a GeometryCollection from all Geometries on the geometry stack and places the result * back on the geometry stack. */ public GeomBuilder collection() { gstack.push(factory.createGeometryCollection(gpopAll(Geometry.class))); return this; } /** * Buffers the geometry at the top of the geometry stack, and places the result back on the * geometry stack. */ public GeomBuilder buffer(double amt) { gstack.push(gpop(Geometry.class).buffer(amt)); return this; } /** * Consumes the top of the geometry stack. */ public Geometry get() { return gpop(Geometry.class); } /** * Builds and returns a Point. *

* This method is equivalent to: *

     *   (Point) point().get();
     * 
*

*/ public Point toPoint() { return point().gpop(Point.class); } /** * Builds and returns a LineString. *

* This method is equivalent to: *

     *   (LineString) lineString().get();
     * 
*

*/ public LineString toLineString() { return lineString().gpop(LineString.class); } /** * Builds and returns a LineString. *

* This method is equivalent to: *

     *   (LinearRing) ring().get();
     * 
*

*/ public LinearRing toLinearRing() { return ring().gpop(LinearRing.class); } /** * Builds and returns a Polygon. *

* This method is equivalent to: *

     *   (Polygon) polygon().get();
     * 
*

*/ public Polygon toPolygon() { return polygon().gpop(Polygon.class); } /** * Builds and returns a MultiPoint. *

* This method is equivalent to: *

     *   (MultiPoint) multiPoint().get();
     * 
*

*/ public MultiPoint toMultiPoint() { return multiPoint().gpop(MultiPoint.class); } /** * Builds and returns a MultiLineString. *

* This method is equivalent to: *

     *   (MultiLineString) multiLineString().get();
     * 
*

*/ public MultiLineString toMultiLineString() { return multiLineString().gpop(MultiLineString.class); } /** * Builds and returns a MultiPolygon. *

* This method is equivalent to: *

     *   (MultiPolygon) multiPolygon().get();
     * 
*

*/ public MultiPolygon toMultiPolygon() { return multiPolygon().gpop(MultiPolygon.class); } /** * Builds and returns a GEometryCollection. *

* This method is equivalent to: *

     *   (GeometryCollection) collection().get();
     * 
*

*/ public GeometryCollection toCollection() { return collection().gpop(GeometryCollection.class); } Coordinate cpop() { return cpop(1)[0]; } Coordinate[] cpop(int n) { if (cstack.size() < n) { throw new IllegalStateException(String.format(Locale.ROOT,"Expected %d values on coordinate stack, " + "but found %d", n, cstack.size())); } Coordinate[] c = new Coordinate[n]; for (int i = 0; i < n; i++) { c[n-i-1] = cstack.pop(); } return c; } Coordinate[] cpopAll() { if (cstack.isEmpty()) { throw new IllegalStateException("Coordinate stack is empty"); } return cpop(cstack.size()); } T gpop(Class clazz) { return gpop(1, clazz)[0]; } T[] gpop(int n, Class clazz) { if (gstack.size() < n) { throw new IllegalStateException(String.format(Locale.ROOT,"Expected %d values on geometry stack, " + "but found %d", n, gstack.size())); } @SuppressWarnings("unchecked") T[] l = (T[]) Array.newInstance(clazz, n); for (int i = 0; i < n; i++) { Geometry g = gstack.pop(); if (!clazz.isInstance(g)) { throw new IllegalStateException(String.format(Locale.ROOT,"Expected %s on geometry stack, but " + "found %s", clazz.getSimpleName(), g.getClass().getSimpleName())); } l[n-i-1] = clazz.cast(g); } return l; } T[] gpopAll(Class clazz) { if (gstack.isEmpty()) { throw new IllegalArgumentException("Geometry stack is empty"); } int n = 0; Iterator it = gstack.iterator(); while (it.hasNext() && clazz.isInstance(it.next())) { n++; } if (n == 0) { throw new IllegalArgumentException( String.format(Locale.ROOT,"Expected %s on geometry stack", clazz.getSimpleName())); } return gpop(n, clazz); } } spatial4j-spatial4j-0.8/src/test/resources/000077500000000000000000000000001375755266700207545ustar00rootroot00000000000000spatial4j-spatial4j-0.8/src/test/resources/fiji.wkt.txt000066400000000000000000000222731375755266700232500ustar00rootroot00000000000000MULTIPOLYGON (((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-spatial4j-0.8/src/test/resources/russia.wkt.txt000066400000000000000000001113461375755266700236350ustar00rootroot00000000000000MULTIPOLYGON (((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))) spatial4j-spatial4j-0.8/src/test/resources/samples.txt000066400000000000000000000006001375755266700231550ustar00rootroot00000000000000POINT(100.1 0.1) LINESTRING (100.1 0.1, 101.1 1.1) POLYGON ((100.1 0.1, 101.1 0.1, 101.1 1.1, 100.1 1.1, 100.1 0.1)) POLYGON ((100.1 0.1, 101.1 0.1, 101.1 1.1, 100.1 1.1, 100.1 0.1), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2)) MULTILINESTRING ((100.1 0.1, 101.1 1.1), (102.1 2.1, 103.1 3.1)) MULTIPOINT ((100.1 0.1), (101.1 1.1)) ENVELOPE(100.1, 101.1, 1.1, 0.1)