pax_global_header 0000666 0000000 0000000 00000000064 13757552667 0014541 g ustar 00root root 0000000 0000000 52 comment=8704d9ca3e3c96202464b6609028124d59f34c66
spatial4j-spatial4j-0.8/ 0000775 0000000 0000000 00000000000 13757552667 0015174 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/.gitignore 0000775 0000000 0000000 00000000075 13757552667 0017171 0 ustar 00root root 0000000 0000000 /*.ipr
/.idea/
*.iml
target/
.classpath
.project
.settings/
spatial4j-spatial4j-0.8/.travis.yml 0000664 0000000 0000000 00000000577 13757552667 0017316 0 ustar 00root root 0000000 0000000 language: 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.md 0000664 0000000 0000000 00000026740 13757552667 0016577 0 ustar 00root root 0000000 0000000 ## 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.md 0000664 0000000 0000000 00000007050 13757552667 0017427 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000016367 13757552667 0016646 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000022305 13757552667 0016455 0 ustar 00root root 0000000 0000000 # Spatial4j
[](https://travis-ci.org/locationtech/spatial4j)
[](https://codecov.io/github/locationtech/spatial4j/)
[](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.md 0000664 0000000 0000000 00000001772 13757552667 0016637 0 ustar 00root root 0000000 0000000 ## 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.txt 0000664 0000000 0000000 00000026136 13757552667 0017131 0 ustar 00root root 0000000 0000000
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.md 0000664 0000000 0000000 00000012637 13757552667 0017356 0 ustar 00root root 0000000 0000000 This 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/eclipse 0000775 0000000 0000000 00000000315 13757552667 0016545 0 ustar 00root root 0000000 0000000 rm */.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.md 0000664 0000000 0000000 00000001300 13757552667 0016771 0 ustar 00root root 0000000 0000000 # 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.xml 0000664 0000000 0000000 00000037511 13757552667 0016520 0 ustar 00root root 0000000 0000000
4.0.0org.locationtech.spatial4jspatial4j0.8bundleSpatial4J
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.spatial4jLocationTechhttp://www.locationtech.org/The Apache Software License, Version 2.0http://www.apache.org/licenses/LICENSE-2.0.txtrepoGitHubhttps://github.com/locationtech/spatial4j/issuesJenkinshttps://ci.eclipse.org/spatial4j/job/Spatial4j/spatial4j-devhttp://www.eclipse.org/lists/spatial4j-devhttp://spatial4j.16575.x6.nabble.comspatial4j-dev@eclipse.orghttps://accounts.eclipse.org/mailing-list/spatial4j-devDavid SmileyRyan McKinleyVoyager SearchJustin DeoliveiraVoyager Searchscm:git:git@github.com:locationtech/spatial4j.gitscm:git:git@github.com:locationtech/spatial4j.githttps://github.com/locationtech/spatial4jspatial4j-0.8UTF-81.81.8org.noggitnoggit0.8truecom.fasterxml.jackson.corejackson-databind2.9.10.5trueorg.locationtech.jtsjts-core1.17.0truejunitjunit4.13.1testorg.slf4jslf4j-simple1.7.25testcom.carrotsearch.randomizedtestingrandomizedtesting-runner2.5.3testmaven-clean-plugin3.1.0maven-resources-plugin3.0.2maven-compiler-plugin3.8.0maven-surefire-plugin2.22.1maven-jar-plugin3.0.2maven-install-plugin2.5.2maven-deploy-plugin2.8.2maven-site-plugin3.7.1maven-project-info-reports-plugin3.0.0maven-source-plugin3.2.1maven-javadoc-plugin3.2.0maven-gpg-plugin1.6org.apache.maven.pluginsmaven-compiler-plugintruefalse-Xlint:uncheckedorg.apache.maven.pluginsmaven-surefire-pluginde.thetaphiforbiddenapis3.0.1checktruejdk-system-outjdk-unsafejdk-deprecatedfalseorg.apache.felixmaven-bundle-plugin4.2.1org.locationtech.spatial4j*;version=${project.version}trueorg.apache.maven.pluginsmaven-site-pluginorg.jacocojacoco-maven-plugin0.8.2prepare-agentprepare-agentorg.apache.maven.pluginsmaven-scm-publish-plugin1.1Publishing maven generated site for ${project.artifactId}:${project.version}${project.reporting.outputDirectory}truescm:git:https://github.com/locationtech/spatial4j.gitgh-pagescom.google.code.maven-replacer-pluginreplacer1.5.3process-packageVersiongenerate-sourcesreplace${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.pluginsmaven-pmd-plugin3.5true100org.codehaus.mojofindbugs-maven-plugin3.0.3trueorg.apache.maven.pluginsmaven-jxr-plugin2.5jxrorg.apache.maven.pluginsmaven-surefire-report-plugin2.18.1org.apache.maven.pluginsmaven-javadoc-pluginSpatial4j, ${project.version}Spatial4j, ${project.version}
http://locationtech.github.io/jts/javadoc/
all,-missingjavadocreleaseorg.apache.maven.pluginsmaven-source-pluginattach-sourcesjar-no-forkorg.apache.maven.pluginsmaven-jar-pluginattach-test-sourcestest-jarorg.apache.maven.pluginsmaven-javadoc-pluginattach-javadocsjarall,-missingorg.apache.maven.pluginsmaven-gpg-pluginsign-artifactsverifysignossrhhttps://oss.sonatype.org/content/repositories/snapshotsossrhhttps://oss.sonatype.org/service/local/staging/deploy/maven2/
spatial4j-spatial4j-0.8/src/ 0000775 0000000 0000000 00000000000 13757552667 0015763 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/main/ 0000775 0000000 0000000 00000000000 13757552667 0016707 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/main/java/ 0000775 0000000 0000000 00000000000 13757552667 0017630 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/main/java/org/ 0000775 0000000 0000000 00000000000 13757552667 0020417 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/main/java/org/locationtech/ 0000775 0000000 0000000 00000000000 13757552667 0023073 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/ 0000775 0000000 0000000 00000000000 13757552667 0024766 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/SpatialPredicate.java 0000664 0000000 0000000 00000014773 13757552667 0031063 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13757552667 0026452 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/context/SpatialContext.java 0000664 0000000 0000000 00000025115 13757552667 0032263 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000027550 13757552667 0033620 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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()}
{@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 extends ShapeFactory> shapeFactoryClass = ShapeFactoryImpl.class;
public Class extends BinaryCodec> 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 extends ShapeReader> clazz : readers) {
try {
read.add(makeClassInstance(clazz, ctx, this));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
List write = new ArrayList<>(writers.size());
for (Class extends ShapeWriter> 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 extends ShapeReader> 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 extends T> 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/ 0000775 0000000 0000000 00000000000 13757552667 0027252 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/context/jts/DatelineRule.java 0000664 0000000 0000000 00000002531 13757552667 0032473 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000775 0000000 0000000 00000015742 13757552667 0033554 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000012373 13757552667 0035017 0 ustar 00root root 0000000 0000000 spatial4j-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.java 0000664 0000000 0000000 00000004626 13757552667 0033047 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000001105 13757552667 0031636 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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/ 0000775 0000000 0000000 00000000000 13757552667 0026560 5 ustar 00root root 0000000 0000000 AbstractDistanceCalculator.java 0000664 0000000 0000000 00000001740 13757552667 0034576 0 ustar 00root root 0000000 0000000 spatial4j-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.java 0000664 0000000 0000000 00000011471 13757552667 0032747 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000004520 13757552667 0033170 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000047315 13757552667 0032210 0 ustar 00root root 0000000 0000000 /*
* 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.
*
* 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.java 0000664 0000000 0000000 00000007616 13757552667 0033656 0 ustar 00root root 0000000 0000000 spatial4j-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.java 0000664 0000000 0000000 00000001044 13757552667 0031746 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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/ 0000775 0000000 0000000 00000000000 13757552667 0026764 5 ustar 00root root 0000000 0000000 InvalidShapeException.java 0000664 0000000 0000000 00000001774 13757552667 0034007 0 ustar 00root root 0000000 0000000 spatial4j-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.java 0000664 0000000 0000000 00000002342 13757552667 0035240 0 ustar 00root root 0000000 0000000 spatial4j-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/ 0000775 0000000 0000000 00000000000 13757552667 0025375 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/BinaryCodec.java 0000664 0000000 0000000 00000014165 13757552667 0030431 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000032231 13757552667 0030570 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000014520 13757552667 0030643 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000015222 13757552667 0030641 0 ustar 00root root 0000000 0000000 /*
* 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.java 0000664 0000000 0000000 00000012563 13757552667 0031557 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000006564 13757552667 0031635 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000002676 13757552667 0031500 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000015423 13757552667 0030340 0 ustar 00root root 0000000 0000000 /*
* 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.java 0000664 0000000 0000000 00000017061 13757552667 0031334 0 ustar 00root root 0000000 0000000 /*
* 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.java 0000664 0000000 0000000 00000015344 13757552667 0031410 0 ustar 00root root 0000000 0000000 /*
* 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.java 0000664 0000000 0000000 00000001415 13757552667 0027531 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000003150 13757552667 0030422 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000001604 13757552667 0030476 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000004354 13757552667 0031567 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000055531 13757552667 0030041 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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:
*
* '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.
*
*
*
* @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.java 0000664 0000000 0000000 00000010520 13757552667 0030100 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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 extends Shape> collection = (ShapeCollection extends Shape>) 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.java 0000664 0000000 0000000 00000001633 13757552667 0031146 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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/ 0000775 0000000 0000000 00000000000 13757552667 0027025 5 ustar 00root root 0000000 0000000 GeometryAsGeoJSONSerializer.java 0000664 0000000 0000000 00000011117 13757552667 0035050 0 ustar 00root root 0000000 0000000 spatial4j-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.java 0000664 0000000 0000000 00000002075 13757552667 0034314 0 ustar 00root root 0000000 0000000 spatial4j-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.java 0000664 0000000 0000000 00000003433 13757552667 0033752 0 ustar 00root root 0000000 0000000 spatial4j-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.java 0000664 0000000 0000000 00000002037 13757552667 0032573 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.in 0000664 0000000 0000000 00000002043 13757552667 0033175 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000015726 13757552667 0034327 0 ustar 00root root 0000000 0000000 spatial4j-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.java 0000664 0000000 0000000 00000002306 13757552667 0033556 0 ustar 00root root 0000000 0000000 spatial4j-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.java 0000664 0000000 0000000 00000016652 13757552667 0033305 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000002704 13757552667 0033616 0 ustar 00root root 0000000 0000000 spatial4j-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.java 0000664 0000000 0000000 00000002664 13757552667 0033143 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000001113 13757552667 0032210 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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/ 0000775 0000000 0000000 00000000000 13757552667 0026175 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/io/jts/JtsBinaryCodec.java 0000664 0000000 0000000 00000011066 13757552667 0031707 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000013430 13757552667 0032123 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000010233 13757552667 0032661 0 ustar 00root root 0000000 0000000 /*
* 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.java0000664 0000000 0000000 00000011034 13757552667 0033446 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000002206 13757552667 0031363 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000001053 13757552667 0030563 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000001116 13757552667 0030154 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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/ 0000775 0000000 0000000 00000000000 13757552667 0026066 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/BaseShape.java 0000664 0000000 0000000 00000001405 13757552667 0030564 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000002121 13757552667 0030126 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000002560 13757552667 0030025 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000004431 13757552667 0030637 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000007345 13757552667 0030002 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000016234 13757552667 0032013 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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 extends Shape> 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 extends Shape> 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.java 0000664 0000000 0000000 00000015736 13757552667 0031335 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000010647 13757552667 0032034 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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/ 0000775 0000000 0000000 00000000000 13757552667 0027027 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/impl/BBoxCalculator.java 0000664 0000000 0000000 00000016472 13757552667 0032550 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000017751 13757552667 0032237 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000012650 13757552667 0033417 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000021566 13757552667 0031727 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000021172 13757552667 0031531 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000011442 13757552667 0031655 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000006675 13757552667 0031623 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000011052 13757552667 0030725 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000025400 13757552667 0032421 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000017644 13757552667 0033120 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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/ 0000775 0000000 0000000 00000000000 13757552667 0026666 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/main/java/org/locationtech/spatial4j/shape/jts/JtsGeometry.java 0000775 0000000 0000000 00000060455 13757552667 0032022 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000006462 13757552667 0031313 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000775 0000000 0000000 00000051326 13757552667 0032614 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000001354 13757552667 0031260 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.html 0000664 0000000 0000000 00000001250 13757552667 0022362 0 ustar 00root root 0000000 0000000
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/ 0000775 0000000 0000000 00000000000 13757552667 0016742 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/test/java/ 0000775 0000000 0000000 00000000000 13757552667 0017663 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/test/java/org/ 0000775 0000000 0000000 00000000000 13757552667 0020452 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/test/java/org/locationtech/ 0000775 0000000 0000000 00000000000 13757552667 0023126 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/ 0000775 0000000 0000000 00000000000 13757552667 0025021 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/TestLog.java 0000664 0000000 0000000 00000004170 13757552667 0027247 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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/ 0000775 0000000 0000000 00000000000 13757552667 0026505 5 ustar 00root root 0000000 0000000 SpatialContextFactoryTest.java 0000664 0000000 0000000 00000012006 13757552667 0034422 0 ustar 00root root 0000000 0000000 spatial4j-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/ 0000775 0000000 0000000 00000000000 13757552667 0027305 5 ustar 00root root 0000000 0000000 JtsSpatialContextTest.java 0000664 0000000 0000000 00000007170 13757552667 0034361 0 ustar 00root root 0000000 0000000 spatial4j-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/ 0000775 0000000 0000000 00000000000 13757552667 0026613 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/distance/CompareRadiansSnippet.java0000775 0000000 0000000 00000002124 13757552667 0033713 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000033603 13757552667 0032240 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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/ 0000775 0000000 0000000 00000000000 13757552667 0025430 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.8/src/test/java/org/locationtech/spatial4j/io/BaseRoundTripTest.java 0000664 0000000 0000000 00000004077 13757552667 0031664 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000003400 13757552667 0031312 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000010756 13757552667 0031646 0 ustar 00root root 0000000 0000000 /*
* 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.java 0000664 0000000 0000000 00000003655 13757552667 0032406 0 ustar 00root root 0000000 0000000 /*
* 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.java 0000664 0000000 0000000 00000020314 13757552667 0033300 0 ustar 00root root 0000000 0000000 /*
* 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.java 0000664 0000000 0000000 00000004352 13757552667 0031202 0 ustar 00root root 0000000 0000000 /*
* 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.java 0000664 0000000 0000000 00000004155 13757552667 0032003 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000003050 13757552667 0032733 0 ustar 00root root 0000000 0000000 package 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.java0000664 0000000 0000000 00000005763 13757552667 0033555 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000015220 13757552667 0032517 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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.java 0000664 0000000 0000000 00000005332 13757552667 0033314 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* 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
*
*
* 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.
*