pax_global_header 0000666 0000000 0000000 00000000064 12577056562 0014531 g ustar 00root root 0000000 0000000 52 comment=08ba0f4b6b9abe533cd287f291522dbfdd5e6531
spatial4j-spatial4j-0.5/ 0000775 0000000 0000000 00000000000 12577056562 0015161 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/.gitignore 0000775 0000000 0000000 00000000075 12577056562 0017156 0 ustar 00root root 0000000 0000000 /*.ipr
/.idea/
*.iml
target/
.classpath
.project
.settings/
spatial4j-spatial4j-0.5/.travis.yml 0000664 0000000 0000000 00000000466 12577056562 0017300 0 ustar 00root root 0000000 0000000 language: java
sudo: false
script: mvn -Drandomized.multiplier=10 clean verify jacoco:report
jdk:
- openjdk7
- oraclejdk8
- oraclejdk7
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.5/CHANGES.md 0000664 0000000 0000000 00000016661 12577056562 0016565 0 ustar 00root root 0000000 0000000 ## 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.5/FORMATS.md 0000664 0000000 0000000 00000016367 12577056562 0016633 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.5/LICENSE.txt 0000664 0000000 0000000 00000026136 12577056562 0017014 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.5/README.md 0000664 0000000 0000000 00000020116 12577056562 0016440 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/com.spatial4j/spatial4j/)
Spatial4j is a general purpose spatial / geospatial [ASL](http://www.apache.org/licenses/LICENSE-2.0.html) licensed open-source Java library. It's core capabilities are 3-fold: to provide common shapes that can work in Euclidean and geodesic (surface of sphere) world models, to provide distance calculations and other math, and to read & write shapes from strings in formats like [WKT](http://en.wikipedia.org/wiki/Well-known_text) and [GeoJSON](http://geojson.org/geojson-spec.html#geometry-objects). Spatial4j is a member of the [LocationTech](http://www.locationtech.org) Industry Working Group family of projects.
If you are working with spatial grid-square indexing schemes, be it [Geohash](http://en.wikipedia.org/wiki/Geohash) or something custom, then you are likely to find especially high utility from Spatial4j -- doubly-so for Apache Software Foundation projects due to restrictions on use of LGPL software there.
## Shapes and Other Features
The main part of Spatial4j is its collection of shapes. Shapes in Spatial4j have these features:
* Compute its lat-lon bounding box.
* Compute an area. For some shapes its more of an estimate.
* Compute if it contains a provided point.
* Compute the relationship to a lat-lon rectangle. Relationships are: CONTAINS, WITHIN, DISJOINT, INTERSECTS. Note that Spatial4j doesn't have a notion of "touching".
Spatial4j has a variety of shapes that operate in Euclidean-space -- i.e. a flat 2D plane. Most shapes are augmented to support a wrap-around at `X` -180/+180 for compatibility with latitude & longitudes, which is effectively a cylindrical model. But the real bonus is its circle (i.e. point-radius shape that can operate on a surface-of-a-sphere model. See below for further info. 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).
* 3 great-circle distance calculators: Law of Cosines, Haversine, Vincenty
* The code is well tested; it's monitored via [Travis-CI](https://travis-ci.org/locationtech/spatial4j) continuous integration and we use [Codecov](https://codecov.io/github/locationtech/spatial4j/) for code coverage.
* Spatial4j has no dependencies on other libraries except for JTS, which is only triggered if you use Polygons, or obviously if you use any of the classes prefixed with "Jts".
## Why not use JTS? Why should you use Spatial4j?
Spatial4j was born out of an unmet need from other open-source Java software.
[JTS](https://sourceforge.net/projects/jts-topo-suite/) is the most popular spatial library in Java. JTS is powerful but it only supports Euclidean geometry (no geodesics), it has no Circle shape, and it is LGPL licensed (which is planned to change soon). Spatial4j has a geodesic circle implementation, and it wraps JTS geometries to add dateline-wrap support (no pole wrap yet).
A geodesic circle implementation (i.e. point-radius on surface of a sphere), has been non-trivial; see for yourself and look at the extensive testing. Presumably many applications will use a polygon substitute for a circle, however note that not only is it an approximation, but common algorithms *inscribe* instead of *circumscribe* the circle. The result is a polygon that doesn't quite completely cover the intended shape, potentially resulting in not finding desired data when applied to the information-retrieval domain (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/com/spatial4j/core/context/SpatialContext.html). It acts as a factory for shapes and it holds references to most other classes you might use and/or it has convenience methods for them. For example you can get a [`DistanceCalculator`](https://locationtech.github.io/spatial4j/apidocs/com/spatial4j/core/distance/DistanceCalculator.html) but if you just want to calculate the distance then the context has a method for that.
To get a SpatialContext (or just "context" for short), you could use a global singleton `SpatialContext.GEO` or `JtsSpatialContext.GEO` 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/com/spatial4j/core/context/SpatialContextFactory.html) (or `JtsSpatialContextFactory`), set the options, then invoke `newSpatialContext()`. If you have a set of name-value string pairs, perhaps from a java properties file, then instead use the static `makeSpatialContext(map, classLoader)` method which adds a lot of flexibility to the configuration initialization versus hard-coding it.
*You should generally avoid calling constructors for anything in Spatial4j except for the `SpatialContextFactory`.* Constructors aren't strictly forbidden but the factories are there to provide an extension point / abstraction, so don't side-step them unless there's a deliberate reason.
## Miscellaneous
Discuss Spatial4j on our [mailing list](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)
* Polygon pole wrap
* Multi-dimensional?
### History
Before Spatial4j, there was [Lucene Spatial Playground](http://code.google.com/p/lucene-spatial-playground/) (LSP) and from this work a generic core spatial library emerged, independent of Lucene: Spatial4j. The other parts of LSP were either merged into Lucene / Solr itself or were migrated to
[Spatial Solr Sandbox](https://github.com/ryantxu/spatial-solr-sandbox).
spatial4j-spatial4j-0.5/eclipse 0000775 0000000 0000000 00000000315 12577056562 0016532 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.5/pom.xml 0000664 0000000 0000000 00000026102 12577056562 0016477 0 ustar 00root root 0000000 0000000
4.0.0org.sonatype.ossoss-parent7com.spatial4jspatial4j0.5bundleSpatial4J
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.
The Apache Software License, Version 2.0http://www.apache.org/licenses/LICENSE-2.0.txtrepoGitHubhttps://github.com/locationtech/spatial4j/issuesTravis-CIhttps://travis-ci.org/locationtech/spatial4jspatial4j-devhttp://locationtech.org/mhonarc/lists/spatial4j-devhttp://spatial4j.16575.x6.nabble.comhttp://lists.spatial4j.com/pipermail/dev-spatial4j.com/spatial4j-dev@locationtech.orghttps://locationtech.org/mailman/listinfo/spatial4j-devscm:git:git@github.com:locationtech/spatial4j.gitscm:git:git@github.com:locationtech/spatial4j.githttps://github.com/locationtech/spatial4jUTF-82.2.1org.noggitnoggit0.7truecom.vividsolutionsjts1.13truexercesxercesImpljunitjunit4.12testorg.slf4jslf4j-simple1.7.12testio.jeojeo0.6testcom.carrotsearch.randomizedtestingrandomizedtesting-runner2.1.16testorg.apache.maven.pluginsmaven-compiler-plugin3.31.71.7truetrueorg.apache.maven.pluginsmaven-jar-plugin2.6attach-sourcesjarattach-test-sourcestest-jarcom/spatial4j/core/**/*org.apache.maven.pluginsmaven-surefire-plugin2.18.1de.thetaphiforbiddenapis1.8checktruejdk-system-outjdk-unsafejdk-deprecatedfalse1.7org.apache.felixmaven-bundle-plugin2.5.3com.spatial4j.core*;version=${project.version}trueorg.apache.maven.pluginsmaven-site-plugin3.4org.jacocojacoco-maven-plugin0.7.5.201505241946prepare-agentprepare-agentorg.apache.maven.pluginsmaven-scm-publish-plugin1.0-beta-2${project.build.directory}/scmpublishPublishing javadoc for ${project.artifactId}:${project.version}${project.reporting.outputDirectory}truescm:git:file://${project.basedir}gh-pagesorg.apache.maven.pluginsmaven-project-info-reports-plugin2.8org.apache.maven.pluginsmaven-pmd-plugin3.5true1001.7org.codehaus.mojofindbugs-maven-plugin2.5.5trueorg.apache.maven.pluginsmaven-jxr-plugin2.5jxrorg.apache.maven.pluginsmaven-surefire-report-plugin2.18.1org.apache.maven.pluginsmaven-javadoc-plugin2.10.3Spatial4j, ${project.version}Spatial4j, ${project.version}
http://tsusiatsoftware.net/jts/javadoc/
javadoc
spatial4j-spatial4j-0.5/src/ 0000775 0000000 0000000 00000000000 12577056562 0015750 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/main/ 0000775 0000000 0000000 00000000000 12577056562 0016674 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/main/java/ 0000775 0000000 0000000 00000000000 12577056562 0017615 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/main/java/com/ 0000775 0000000 0000000 00000000000 12577056562 0020373 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/ 0000775 0000000 0000000 00000000000 12577056562 0022266 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/ 0000775 0000000 0000000 00000000000 12577056562 0023216 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/SpatialPredicate.java 0000664 0000000 0000000 00000014713 12577056562 0027305 0 ustar 00root root 0000000 0000000 package com.spatial4j.core;
/*
* 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 com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.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(com.spatial4j.core.shape.Shape, com.spatial4j.core.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.5/src/main/java/com/spatial4j/core/context/ 0000775 0000000 0000000 00000000000 12577056562 0024702 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/context/SpatialContext.java 0000664 0000000 0000000 00000031022 12577056562 0030505 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 com.spatial4j.core.context;
import java.text.ParseException;
import java.util.List;
import com.spatial4j.core.distance.CartesianDistCalc;
import com.spatial4j.core.distance.DistanceCalculator;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.distance.GeodesicSphereDistCalc;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.io.BinaryCodec;
import com.spatial4j.core.io.LegacyShapeWriter;
import com.spatial4j.core.io.SupportedFormats;
import com.spatial4j.core.io.WKTReader;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.ShapeCollection;
import com.spatial4j.core.shape.impl.BufferedLineString;
import com.spatial4j.core.shape.impl.CircleImpl;
import com.spatial4j.core.shape.impl.GeoCircle;
import com.spatial4j.core.shape.impl.PointImpl;
import com.spatial4j.core.shape.impl.RectangleImpl;
/**
* This is a facade to most of Spatial4j, holding things like {@link DistanceCalculator},
* {@link com.spatial4j.core.io.ShapeIO}, and acting as a factory for the {@link Shape}s.
*
* If you want a typical geodetic context, just reference {@link #GEO}. Otherwise,
* You should either create and configure a {@link SpatialContextFactory} and then call
* {@link SpatialContextFactory#newSpatialContext()}, OR, call
* {@link com.spatial4j.core.context.SpatialContextFactory#makeSpatialContext(java.util.Map, ClassLoader)}
* to do this via configuration data.
*
* Thread-safe & immutable.
*/
public class SpatialContext {
/** A popular default SpatialContext implementation for geospatial. */
public static final SpatialContext GEO = new SpatialContext(new SpatialContextFactory());
//These are non-null
private final boolean geo;
private final DistanceCalculator calculator;
private final Rectangle worldBounds;
private final BinaryCodec binaryCodec;
private final SupportedFormats formats;
private final boolean normWrapLongitude;
/**
* Consider using {@link com.spatial4j.core.context.SpatialContextFactory} instead.
*
* @param geo Establishes geo vs cartesian / Euclidean.
* @param calculator Optional; defaults to haversine or cartesian depending on {@code geo}.
* @param worldBounds Optional; defaults to GEO_WORLDBOUNDS or MAX_WORLDBOUNDS depending on units.
*/
@Deprecated
public SpatialContext(boolean geo, DistanceCalculator calculator, Rectangle worldBounds) {
this(initFromLegacyConstructor(geo, calculator, worldBounds));
}
private static SpatialContextFactory initFromLegacyConstructor(boolean geo,
DistanceCalculator calculator,
Rectangle worldBounds) {
SpatialContextFactory factory = new SpatialContextFactory();
factory.geo = geo;
factory.distCalc = calculator;
factory.worldBounds = worldBounds;
return factory;
}
@Deprecated
public SpatialContext(boolean geo) {
this(initFromLegacyConstructor(geo, null, null));
}
/**
* Called by {@link com.spatial4j.core.context.SpatialContextFactory#newSpatialContext()}.
*/
public SpatialContext(SpatialContextFactory factory) {
this.geo = factory.geo;
if (factory.distCalc == null) {
this.calculator = isGeo()
? new GeodesicSphereDistCalc.Haversine()
: new CartesianDistCalc();
} else {
this.calculator = factory.distCalc;
}
//TODO remove worldBounds from Spatial4j: see Issue #55
Rectangle bounds = factory.worldBounds;
if (bounds == null) {
this.worldBounds = isGeo()
? new RectangleImpl(-180, 180, -90, 90, this)
: new RectangleImpl(-Double.MAX_VALUE, Double.MAX_VALUE,
-Double.MAX_VALUE, Double.MAX_VALUE, this);
} else {
if (isGeo() && !bounds.equals(new RectangleImpl(-180, 180, -90, 90, this)))
throw new IllegalArgumentException("for geo (lat/lon), bounds must be " + GEO.getWorldBounds());
if (bounds.getMinX() > bounds.getMaxX())
throw new IllegalArgumentException("worldBounds minX should be <= maxX: "+ bounds);
if (bounds.getMinY() > bounds.getMaxY())
throw new IllegalArgumentException("worldBounds minY should be <= maxY: "+ bounds);
//hopefully worldBounds' rect implementation is compatible
this.worldBounds = new RectangleImpl(bounds, this);
}
this.normWrapLongitude = factory.normWrapLongitude && this.isGeo();
this.binaryCodec = factory.makeBinaryCodec(this);
factory.checkDefaultFormats();
this.formats = factory.makeFormats(this);
}
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. */
public boolean isNormWrapLongitude() {
return normWrapLongitude;
}
/** Is the mathematical world model based on a sphere, or is it a flat plane? The word
* "geodetic" or "geodesic" is sometimes used to refer to the former, and the latter is sometimes
* referred to as "Euclidean" or "cartesian". */
public boolean isGeo() {
return geo;
}
/** Normalize the 'x' dimension. Might reduce precision or wrap it to be within the bounds. This
* is called by {@link com.spatial4j.core.io.WKTReader} before creating a shape. */
public double normX(double x) {
if (normWrapLongitude)
x = DistanceUtils.normLonDEG(x);
return x;
}
/** Normalize the 'y' dimension. Might reduce precision or wrap it to be within the bounds. This
* is called by {@link com.spatial4j.core.io.WKTReader} before creating a shape. */
public double normY(double y) { return y; }
/** Ensure fits in {@link #getWorldBounds()}. It's called by any shape factory method that
* gets an 'x' dimension. */
public void verifyX(double x) {
Rectangle bounds = getWorldBounds();
if (x < bounds.getMinX() || x > bounds.getMaxX())//NaN will pass
throw new InvalidShapeException("Bad X value "+x+" is not in boundary "+bounds);
}
/** Ensure fits in {@link #getWorldBounds()}. It's called by any shape factory method that
* gets a 'y' dimension. */
public void verifyY(double y) {
Rectangle bounds = getWorldBounds();
if (y < bounds.getMinY() || y > bounds.getMaxY())//NaN will pass
throw new InvalidShapeException("Bad Y value "+y+" is not in boundary "+bounds);
}
/** Construct a point. */
public Point makePoint(double x, double y) {
verifyX(x);
verifyY(y);
return new PointImpl(x, y, this);
}
/** Construct a rectangle. */
public Rectangle makeRectangle(Point lowerLeft, Point upperRight) {
return makeRectangle(lowerLeft.getX(), upperRight.getX(),
lowerLeft.getY(), upperRight.getY());
}
/**
* Construct a rectangle. If just one longitude is on the dateline (+/- 180)
* then potentially adjust its sign to ensure the rectangle does not cross the
* dateline.
*/
public Rectangle makeRectangle(double minX, double maxX, double minY, double maxY) {
Rectangle bounds = getWorldBounds();
// Y
if (minY < bounds.getMinY() || maxY > bounds.getMaxY())//NaN will pass
throw new InvalidShapeException("Y values ["+minY+" to "+maxY+"] not in boundary "+bounds);
if (minY > maxY)
throw new InvalidShapeException("maxY must be >= minY: " + minY + " to " + maxY);
// X
if (isGeo()) {
verifyX(minX);
verifyX(maxX);
//TODO consider removing this logic so that there is no normalization here
//if (minX != maxX) { USUALLY TRUE, inline check below
//If an edge coincides with the dateline then don't make this rect cross it
if (minX == 180 && minX != maxX) {
minX = -180;
} else if (maxX == -180 && minX != maxX) {
maxX = 180;
}
//}
} else {
if (minX < bounds.getMinX() || maxX > bounds.getMaxX())//NaN will pass
throw new InvalidShapeException("X values ["+minX+" to "+maxX+"] not in boundary "+bounds);
if (minX > maxX)
throw new InvalidShapeException("maxX must be >= minX: " + minX + " to " + maxX);
}
return new RectangleImpl(minX, maxX, minY, maxY, this);
}
/** Construct a circle. The units of "distance" should be the same as x & y. */
public Circle makeCircle(double x, double y, double distance) {
return makeCircle(makePoint(x, y), distance);
}
/** Construct a circle. The units of "distance" should be the same as x & y. */
public Circle makeCircle(Point point, double distance) {
if (distance < 0)
throw new InvalidShapeException("distance must be >= 0; got " + distance);
if (isGeo()) {
if (distance > 180) {
// (it's debatable whether to error or not)
//throw new InvalidShapeException("distance must be <= 180; got " + distance);
distance = 180;
}
return new GeoCircle(point, distance, this);
} else {
return new CircleImpl(point, distance, this);
}
}
/** Constructs a line string. It's an ordered sequence of connected vertexes. There
* is no official shape/interface for it yet so we just return Shape. */
public Shape makeLineString(List points) {
return new BufferedLineString(points, 0, false, this);
}
/** Constructs a buffered line string. It's an ordered sequence of connected vertexes,
* with a buffer distance along the line in all directions. There
* is no official shape/interface for it so we just return Shape. */
public Shape makeBufferedLineString(List points, double buf) {
return new BufferedLineString(points, buf, isGeo(), this);
}
/** Construct a ShapeCollection, analogous to an OGC GeometryCollection. */
public ShapeCollection makeCollection(List coll) {
return new ShapeCollection(coll, this);
}
/** The {@link com.spatial4j.core.io.WKTReader} used by {@link #readShapeFromWkt(String)}. */
@Deprecated
public WKTReader getWktShapeParser() {
return (WKTReader)formats.getWktReader();
}
/** Reads a shape from the string formatted in WKT.
* @see com.spatial4j.core.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
*
* @param value
* @return shape or null if unable to parse any shape
* @throws InvalidShapeException
*/
@Deprecated
public Shape readShape(String value) throws InvalidShapeException {
return formats.read(value);
}
/** Writes the shape to a String using the old/deprecated
* {@link com.spatial4j.core.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.5/src/main/java/com/spatial4j/core/context/SpatialContextFactory.java 0000664 0000000 0000000 00000026635 12577056562 0032053 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 com.spatial4j.core.context;
import com.spatial4j.core.distance.CartesianDistCalc;
import com.spatial4j.core.distance.DistanceCalculator;
import com.spatial4j.core.distance.GeodesicSphereDistCalc;
import com.spatial4j.core.io.*;
import com.spatial4j.core.shape.Rectangle;
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
*
com.spatial4j.core.context.SpatialContext or
* com.spatial4j.core.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 com.spatial4j.core.io.ShapeReader} class names
*
writers
*
Comma separated list of {@link com.spatial4j.core.io.ShapeWriter} class names
*
binaryCodecClass
*
Java class of the {@link com.spatial4j.core.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 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");
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. */
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 BinaryCodec makeBinaryCodec(SpatialContext ctx) {
return makeClassInstance(binaryCodecClass, ctx, this);
}
@SuppressWarnings("unchecked")
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.5/src/main/java/com/spatial4j/core/context/jts/ 0000775 0000000 0000000 00000000000 12577056562 0025502 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/context/jts/DatelineRule.java 0000664 0000000 0000000 00000002475 12577056562 0030732 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 com.spatial4j.core.context.jts;
/**
* Indicates the algorithm used to process JTS Polygons and JTS LineStrings for detecting dateline
* crossings. It only applies when geo=true.
*/
public enum DatelineRule {
/** No polygon will cross the dateline. */
none,
/**
* Adjacent points with an x (longitude) difference that spans more than half way around the
* globe will be interpreted as going the other (shorter) way, and thus cross the dateline.
*/
width180, // TODO is there a better name that doesn't have '180' in it?
/**
* For rectangular polygons, the point order is interpreted as being counter-clockwise (CCW).
* However, non-rectangular polygons or other shapes aren't processed this way; they use the
* {@link #width180} rule instead. The CCW rule is specified by OGC Simple Features
* Specification v. 1.2.0 section 6.1.11.1.
*/
ccwRect
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/context/jts/JtsSpatialContext.java 0000775 0000000 0000000 00000032451 12577056562 0032000 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 com.spatial4j.core.context.jts;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.io.ShapeReader;
import com.spatial4j.core.io.jts.JtsWKTReader;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.spatial4j.core.shape.jts.JtsPoint;
import com.vividsolutions.jts.algorithm.CGAlgorithms;
import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.util.GeometricShapeFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Enhances the default {@link SpatialContext} with support for Polygons (and
* other geometries) using JTS.
* To the extent possible, our {@link JtsGeometry} adds some amount of geodetic support over
* vanilla JTS which only has a Euclidean (flat plane) model.
*/
public class JtsSpatialContext extends SpatialContext {
public static final JtsSpatialContext GEO;
static {
JtsSpatialContextFactory factory = new JtsSpatialContextFactory();
factory.geo = true;
GEO = new JtsSpatialContext(factory);
}
protected final GeometryFactory geometryFactory;
protected final boolean allowMultiOverlap;
protected final boolean useJtsPoint;
protected final boolean useJtsLineString;
protected final DatelineRule datelineRule;
protected final ValidationRule validationRule;
protected final boolean autoIndex;
/**
* Called by {@link com.spatial4j.core.context.jts.JtsSpatialContextFactory#newSpatialContext()}.
*/
public JtsSpatialContext(JtsSpatialContextFactory factory) {
super(factory);
this.geometryFactory = factory.getGeometryFactory();
this.allowMultiOverlap = factory.allowMultiOverlap;
this.useJtsPoint = factory.useJtsPoint;
this.useJtsLineString = factory.useJtsLineString;
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 com.spatial4j.core.shape.ShapeCollection#relateContainsShortCircuits()}.
*/
public boolean isAllowMultiOverlap() {
return allowMultiOverlap;
}
/**
* Returns the rule used to handle geometry objects that have dateline 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 com.spatial4j.core.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 String toString(Shape shape) {
//Note: this logic is from the defunct JtsShapeReadWriter
if (shape instanceof JtsGeometry) {
JtsGeometry jtsGeom = (JtsGeometry) shape;
return jtsGeom.getGeom().toText();
}
//Note: doesn't handle ShapeCollection or BufferedLineString
return super.toString(shape);
}
/**
* Gets a JTS {@link Geometry} for the given {@link Shape}. Some shapes hold a
* JTS geometry whereas new ones must be created for the rest.
* @param shape Not null
* @return Not null
*/
public Geometry getGeometryFrom(Shape shape) {
if (shape instanceof JtsGeometry) {
return ((JtsGeometry)shape).getGeom();
}
if (shape instanceof JtsPoint) {
return ((JtsPoint) shape).getGeom();
}
if (shape instanceof Point) {
Point point = (Point) shape;
return geometryFactory.createPoint(new Coordinate(point.getX(),point.getY()));
}
if (shape instanceof Rectangle) {
Rectangle r = (Rectangle)shape;
if (r.getCrossesDateLine()) {
Collection pair = new ArrayList(2);
pair.add(geometryFactory.toGeometry(new Envelope(
r.getMinX(), getWorldBounds().getMaxX(), r.getMinY(), r.getMaxY())));
pair.add(geometryFactory.toGeometry(new Envelope(
getWorldBounds().getMinX(), r.getMaxX(), r.getMinY(), r.getMaxY())));
return geometryFactory.buildGeometry(pair);//a MultiPolygon or MultiLineString
} else {
return geometryFactory.toGeometry(new Envelope(r.getMinX(), r.getMaxX(), r.getMinY(), r.getMaxY()));
}
}
if (shape instanceof Circle) {
// FYI Some interesting code for this is here:
// http://docs.codehaus.org/display/GEOTDOC/01+How+to+Create+a+Geometry#01HowtoCreateaGeometry-CreatingaCircle
//TODO This should ideally have a geodetic version
Circle circle = (Circle)shape;
if (circle.getBoundingBox().getCrossesDateLine())
throw new IllegalArgumentException("Doesn't support dateline cross yet: "+circle);//TODO
GeometricShapeFactory gsf = new GeometricShapeFactory(geometryFactory);
gsf.setSize(circle.getBoundingBox().getWidth());
gsf.setNumPoints(4*25);//multiple of 4 is best
gsf.setCentre(new Coordinate(circle.getCenter().getX(), circle.getCenter().getY()));
return gsf.createCircle();
}
//TODO add BufferedLineString
throw new InvalidShapeException("can't make Geometry from: " + shape);
}
/** Should {@link #makePoint(double, double)} return {@link JtsPoint}? */
public boolean useJtsPoint() {
return useJtsPoint;
}
@Override
public Point makePoint(double x, double y) {
if (!useJtsPoint())
return super.makePoint(x, y);
//A Jts Point is fairly heavyweight! TODO could/should we optimize this? SingleCoordinateSequence
verifyX(x);
verifyY(y);
Coordinate coord = Double.isNaN(x) ? null : new Coordinate(x, y);
return new JtsPoint(geometryFactory.createPoint(coord), this);
}
/** Should {@link #makeLineString(java.util.List)} return {@link JtsGeometry}? */
public boolean useJtsLineString() {
//BufferedLineString doesn't yet do dateline cross, and can't yet be relate()'ed with a
// JTS geometry
return useJtsLineString;
}
@Override
public Shape makeLineString(List points) {
if (!useJtsLineString())
return super.makeLineString(points);
//convert List to Coordinate[]
Coordinate[] coords = new Coordinate[points.size()];
for (int i = 0; i < coords.length; i++) {
Point p = points.get(i);
if (p instanceof JtsPoint) {
JtsPoint jtsPoint = (JtsPoint) p;
coords[i] = jtsPoint.getGeom().getCoordinate();
} else {
coords[i] = new Coordinate(p.getX(), p.getY());
}
}
LineString lineString = geometryFactory.createLineString(coords);
return makeShape(lineString);
}
/**
* INTERNAL 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 com.vividsolutions.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)}.
*/
public Shape makeShapeFromGeometry(Geometry geom) {
// Direct instances of GeometryCollection can't be wrapped in JtsGeometry but can be expanded into
// a ShapeCollection.
if (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 makeCollection(shapes);
}
if (geom instanceof com.vividsolutions.jts.geom.Point) {
com.vividsolutions.jts.geom.Point pt = (com.vividsolutions.jts.geom.Point) geom;
return makePoint(pt.getX(), pt.getY());
}
if (!useJtsLineString() && geom instanceof LineString) {
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(makePoint(coord.x, coord.y));
}
return makeLineString(points);
}
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;
}
}
if (isAutoIndex())
jtsGeom.index();
return jtsGeom;
}
/**
* INTERNAL
* @see #makeShape(com.vividsolutions.jts.geom.Geometry)
*
* @param geom Non-null
* @param dateline180Check if both this is true and {@link #isGeo()}, then JtsGeometry will check
* for adjacent coordinates greater than 180 degrees longitude apart, and
* it will do tricks to make that line segment (and the shape as a whole)
* cross the dateline even though JTS doesn't have geodetic support.
* @param allowMultiOverlap See {@link #isAllowMultiOverlap()}.
*/
public JtsGeometry makeShape(Geometry geom, boolean dateline180Check, boolean allowMultiOverlap) {
return new JtsGeometry(geom, this, dateline180Check, allowMultiOverlap);
}
/**
* INTERNAL: Creates a {@link Shape} from a JTS {@link Geometry}. Generally, this shouldn't be
* called when one of the other factory methods are available, such as for points. The caller
* needs to have done some verification/normalization of the coordinates by now, if any. Also,
* note that direct instances of {@link GeometryCollection} isn't supported.
*
* Instead of calling this method, consider {@link JtsWKTReader#makeShapeFromGeometry(Geometry)}
* which
*/
public JtsGeometry makeShape(Geometry geom) {
return makeShape(geom, datelineRule != DatelineRule.none, allowMultiOverlap);
}
public GeometryFactory getGeometryFactory() {
return geometryFactory;
}
@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.
*/
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 (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 makeRectangle(env.getMaxX(), env.getMinX(), env.getMinY(), env.getMaxY());
else
return makeRectangle(env.getMinX(), env.getMaxX(), env.getMinY(), env.getMaxY());
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/context/jts/JtsSpatialContextFactory.java 0000664 0000000 0000000 00000011411 12577056562 0033316 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 com.spatial4j.core.context.jts;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.io.LegacyShapeReader;
import com.spatial4j.core.io.LegacyShapeWriter;
import com.spatial4j.core.io.jts.*;
import com.vividsolutions.jts.geom.CoordinateSequenceFactory;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.geom.impl.CoordinateArraySequenceFactory;
import java.util.Map;
/**
* See {@link SpatialContextFactory#makeSpatialContext(java.util.Map, ClassLoader)}.
*
* The following keys are looked up in the args map, in addition to those in the
* superclass:
*
*
datelineRule
*
width180(default)|ccwRect|none
* -- see {@link DatelineRule}
*
validationRule
*
error(default)|none|repairConvexHull|repairBuffer0
* -- see {@link ValidationRule}
*
autoIndex
*
true|false(default) -- see {@link JtsWKTReader#isAutoIndex()}
*
allowMultiOverlap
*
true|false(default) -- see {@link JtsSpatialContext#isAllowMultiOverlap()}
*
precisionModel
*
floating(default) | floating_single | fixed
* -- see {@link com.vividsolutions.jts.geom.PrecisionModel}.
* If {@code fixed} then you must also provide {@code precisionScale}
* -- see {@link com.vividsolutions.jts.geom.PrecisionModel#getScale()}
*
*/
public class JtsSpatialContextFactory extends SpatialContextFactory {
protected static final PrecisionModel defaultPrecisionModel = new PrecisionModel();//floating
//These 3 are JTS defaults for new GeometryFactory()
public PrecisionModel precisionModel = defaultPrecisionModel;
public int srid = 0;
public CoordinateSequenceFactory coordinateSequenceFactory = CoordinateArraySequenceFactory.instance();
//ignored if geo=false
public 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 JtsSpatialContextFactory() {
super.binaryCodecClass = JtsBinaryCodec.class;
}
@Override
protected void checkDefaultFormats() {
if (readers.isEmpty() ) {
addReaderIfNoggitExists(JtsGeoJSONReader.class);
readers.add(JtsWKTReader.class);
readers.add(JtsPolyshapeReader.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");
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.5/src/main/java/com/spatial4j/core/context/jts/ValidationRule.java 0000664 0000000 0000000 00000004607 12577056562 0031276 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 com.spatial4j.core.context.jts;
import com.spatial4j.core.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 com.vividsolutions.jts.geom.Geometry#isValid().
*/
none,
/**
* Geometries will be explicitly validated on creation, possibly resulting in an exception:
* {@link com.spatial4j.core.exception.InvalidShapeException}.
*/
error,
/**
* Invalid Geometries are repaired by taking the convex hull. The result will very likely be a
* larger shape that matches false-positives, but no false-negatives. See
* {@link com.vividsolutions.jts.geom.Geometry#convexHull()}.
*/
repairConvexHull,
/**
* Invalid polygons are repaired using the {@code buffer(0)} technique. From the JTS FAQ:
*
* The buffer operation is fairly insensitive to topological invalidity, and the act of
* computing the buffer can often resolve minor issues such as self-intersecting rings. However,
* in some situations the computed result may not be what is desired (i.e. the buffer operation
* may be "confused" by certain topologies, and fail to produce a result which is close to the
* original. An example where this can happen is a "bow-tie: or "figure-8" polygon, with one
* very small lobe and one large one. Depending on the orientations of the lobes, the buffer(0)
* operation may keep the small lobe and discard the "valid" large lobe).
*
*/
repairBuffer0
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/context/package-info.java 0000664 0000000 0000000 00000001075 12577056562 0030074 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 com.spatial4j.core.context; spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/distance/ 0000775 0000000 0000000 00000000000 12577056562 0025010 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/distance/AbstractDistanceCalculator.java 0000664 0000000 0000000 00000001720 12577056562 0033103 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 com.spatial4j.core.distance;
import com.spatial4j.core.shape.Point;
/**
*/
public abstract class AbstractDistanceCalculator implements DistanceCalculator {
@Override
public double distance(Point from, Point to) {
return distance(from, to.getX(), to.getY());
}
@Override
public boolean within(Point from, double toX, double toY, double distance) {
return distance(from, toX, toY) <= distance;
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/distance/CartesianDistCalc.java 0000664 0000000 0000000 00000007037 12577056562 0031202 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 com.spatial4j.core.distance;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
/**
* Calculates based on Euclidean / Cartesian 2d plane.
*/
public class CartesianDistCalc extends AbstractDistanceCalculator {
private final boolean squared;
public CartesianDistCalc() {
this.squared = false;
}
/**
* @param squared Set to true to have {@link #distance(com.spatial4j.core.shape.Point, com.spatial4j.core.shape.Point)}
* return the square of the correct answer. This is a
* performance optimization used when sorting in which the
* actual distance doesn't matter so long as the sort order is
* consistent.
*/
public CartesianDistCalc(boolean squared) {
this.squared = squared;
}
@Override
public double distance(Point from, double toX, double toY) {
double deltaX = from.getX() - toX;
double deltaY = from.getY() - toY;
double xSquaredPlusYSquared = deltaX*deltaX + deltaY*deltaY;
if (squared)
return xSquaredPlusYSquared;
return Math.sqrt(xSquaredPlusYSquared);
}
@Override
public boolean within(Point from, double toX, double toY, double distance) {
double deltaX = from.getX() - toX;
double deltaY = from.getY() - toY;
return deltaX*deltaX + deltaY*deltaY <= distance*distance;
}
@Override
public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse) {
if (distDEG == 0) {
if (reuse == null)
return from;
reuse.reset(from.getX(), from.getY());
return reuse;
}
double bearingRAD = DistanceUtils.toRadians(bearingDEG);
double x = from.getX() + Math.sin(bearingRAD) * distDEG;
double y = from.getY() + Math.cos(bearingRAD) * distDEG;
if (reuse == null) {
return ctx.makePoint(x, y);
} else {
reuse.reset(x, y);
return reuse;
}
}
@Override
public Rectangle calcBoxByDistFromPt(Point from, double distDEG, SpatialContext ctx, Rectangle reuse) {
double minX = from.getX() - distDEG;
double maxX = from.getX() + distDEG;
double minY = from.getY() - distDEG;
double maxY = from.getY() + distDEG;
if (reuse == null) {
return ctx.makeRectangle(minX, maxX, minY, maxY);
} else {
reuse.reset(minX, maxX, minY, maxY);
return reuse;
}
}
@Override
public double calcBoxByDistFromPt_yHorizAxisDEG(Point from, double distDEG, SpatialContext ctx) {
return from.getY();
}
@Override
public double area(Rectangle rect) {
return rect.getArea(null);
}
@Override
public double area(Circle circle) {
return circle.getArea(null);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CartesianDistCalc that = (CartesianDistCalc) o;
if (squared != that.squared) return false;
return true;
}
@Override
public int hashCode() {
return (squared ? 1 : 0);
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/distance/DistanceCalculator.java 0000664 0000000 0000000 00000004445 12577056562 0031426 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 com.spatial4j.core.distance;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
/**
* Performs calculations relating to distance, such as the distance between a pair of points. A
* calculator might be based on Euclidean space, or a spherical model, or theoretically something
* else like an ellipsoid.
*/
public interface DistanceCalculator {
/** The distance between from and to. */
public double distance(Point from, Point to);
/** The distance between from and Point(toX,toY). */
public double distance(Point from, double toX, double toY);
/** Returns true if the distance between from and to is <= distance. */
public boolean within(Point from, double toX, double toY, double distance);
/**
* Calculates where a destination point is given an origin (from)
* distance, and bearing (given in degrees -- 0-360). If reuse is given, then
* this method may reset() it and return it.
*/
public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse);
/**
* Calculates the bounding box of a circle, as specified by its center point
* and distance.
*/
public Rectangle calcBoxByDistFromPt(Point from, double distDEG, SpatialContext ctx, Rectangle reuse);
/**
* The Y coordinate of the horizontal axis of a circle that has maximum width. On a
* 2D plane, this result is always from.getY() but, perhaps surprisingly, on a sphere
* it is going to be slightly different.
*/
public double calcBoxByDistFromPt_yHorizAxisDEG(Point from, double distDEG, SpatialContext ctx);
public double area(Rectangle rect);
public double area(Circle circle);
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/distance/DistanceUtils.java 0000664 0000000 0000000 00000047234 12577056562 0030440 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 com.spatial4j.core.distance;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
/**
* Various distance calculations and constants. To the extent possible, a {@link
* com.spatial4j.core.distance.DistanceCalculator}, retrieved from {@link
* com.spatial4j.core.context.SpatialContext#getDistCalc()} should be used in preference to calling
* these methods directly.
*
* This code came from Apache
* Lucene, LUCENE-1387, which in turn came from "LocalLucene".
*/
public class DistanceUtils {
//pre-compute some angles that are commonly used
@Deprecated
public static final double DEG_45_AS_RADS = Math.PI / 4;
@Deprecated
public static final double SIN_45_AS_RADS = Math.sin(DEG_45_AS_RADS);
public static final double DEG_90_AS_RADS = Math.PI / 2;
public static final double DEG_180_AS_RADS = Math.PI;
@Deprecated
public static final double DEG_225_AS_RADS = 5 * DEG_45_AS_RADS;
@Deprecated
public static final double DEG_270_AS_RADS = 3 * DEG_90_AS_RADS;
public static final double DEGREES_TO_RADIANS = Math.PI / 180;
public static final double RADIANS_TO_DEGREES = 1 / DEGREES_TO_RADIANS;
public static final double KM_TO_MILES = 0.621371192;
public static final double MILES_TO_KM = 1 / KM_TO_MILES;//1.609
/**
* The International Union of Geodesy and Geophysics says the Earth's mean radius in KM is:
*
* [1] http://en.wikipedia.org/wiki/Earth_radius
*/
public static final double EARTH_MEAN_RADIUS_KM = 6371.0087714;
public static final double EARTH_EQUATORIAL_RADIUS_KM = 6378.1370;
/** Equivalent to degrees2Dist(1, EARTH_MEAN_RADIUS_KM) */
public static final double DEG_TO_KM = DEGREES_TO_RADIANS * EARTH_MEAN_RADIUS_KM;
public static final double KM_TO_DEG = 1 / DEG_TO_KM;
public static final double EARTH_MEAN_RADIUS_MI = EARTH_MEAN_RADIUS_KM * KM_TO_MILES;
public static final double EARTH_EQUATORIAL_RADIUS_MI = EARTH_EQUATORIAL_RADIUS_KM * KM_TO_MILES;
private DistanceUtils() {}
/**
* Calculate the p-norm (i.e. length) between two vectors.
*
* See Lp space
*
* @param vec1 The first vector
* @param vec2 The second vector
* @param power The power (2 for cartesian distance, 1 for manhattan, etc.)
* @return The length.
*
* @see #vectorDistance(double[], double[], double, double)
*
*/
@Deprecated
public static double vectorDistance(double[] vec1, double[] vec2, double power) {
//only calc oneOverPower if it's needed
double oneOverPower = (power == 0 || power == 1.0 || power == 2.0) ? Double.NaN : 1.0 / power;
return vectorDistance(vec1, vec2, power, oneOverPower);
}
/**
* Calculate the p-norm (i.e. length) between two vectors.
*
* @param vec1 The first vector
* @param vec2 The second vector
* @param power The power (2 for cartesian distance, 1 for manhattan, etc.)
* @param oneOverPower If you've pre-calculated oneOverPower and cached it, use this method to save
* one division operation over {@link #vectorDistance(double[], double[], double)}.
* @return The length.
*/
@Deprecated
public static double vectorDistance(double[] vec1, double[] vec2, double power, double oneOverPower) {
double result = 0;
if (power == 0) {
for (int i = 0; i < vec1.length; i++) {
result += vec1[i] - vec2[i] == 0 ? 0 : 1;
}
} else if (power == 1.0) { // Manhattan
for (int i = 0; i < vec1.length; i++) {
result += Math.abs(vec1[i] - vec2[i]);
}
} else if (power == 2.0) { // Cartesian
result = Math.sqrt(distSquaredCartesian(vec1, vec2));
} else if (power == Integer.MAX_VALUE || Double.isInfinite(power)) {//infinite norm?
for (int i = 0; i < vec1.length; i++) {
result = Math.max(result, Math.max(vec1[i], vec2[i]));
}
} else {
for (int i = 0; i < vec1.length; i++) {
result += Math.pow(vec1[i] - vec2[i], power);
}
result = Math.pow(result, oneOverPower);
}
return result;
}
/**
* Return the coordinates of a vector that is the corner of a box (upper right or lower left), assuming a Rectangular
* coordinate system. Note, this does not apply for points on a sphere or ellipse (although it could be used as an approximation).
*
* @param center The center point
* @param result Holds the result, potentially resizing if needed.
* @param distance The d from the center to the corner
* @param upperRight If true, return the coords for the upper right corner, else return the lower left.
* @return The point, either the upperLeft or the lower right
*/
@Deprecated
public static double[] vectorBoxCorner(double[] center, double[] result, double distance, boolean upperRight) {
if (result == null || result.length != center.length) {
result = new double[center.length];
}
if (upperRight == false) {
distance = -distance;
}
//We don't care about the power here,
// b/c we are always in a rectangular coordinate system, so any norm can be used by
//using the definition of sine
distance = SIN_45_AS_RADS * distance; // sin(Pi/4) == (2^0.5)/2 == opp/hyp == opp/distance, solve for opp, similarly for cosine
for (int i = 0; i < center.length; i++) {
result[i] = center[i] + distance;
}
return result;
}
/**
* Given a start point (startLat, startLon), 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 ctx
* @param reuse A preallocated object to hold the results.
* @return The destination point, IN RADIANS.
*/
public static Point pointOnBearingRAD(double startLat, double startLon, double distanceRAD, double bearingRAD, SpatialContext ctx, Point reuse) {
/*
lat2 = asin(sin(lat1)*cos(d/R) + cos(lat1)*sin(d/R)*cos(θ))
lon2 = lon1 + atan2(sin(θ)*sin(d/R)*cos(lat1), cos(d/R)−sin(lat1)*sin(lat2))
*/
double cosAngDist = Math.cos(distanceRAD);
double cosStartLat = Math.cos(startLat);
double sinAngDist = Math.sin(distanceRAD);
double sinStartLat = Math.sin(startLat);
double sinLat2 = sinStartLat * cosAngDist +
cosStartLat * sinAngDist * Math.cos(bearingRAD);
double lat2 = Math.asin(sinLat2);
double lon2 = startLon + Math.atan2(Math.sin(bearingRAD) * sinAngDist * cosStartLat,
cosAngDist - sinStartLat * sinLat2);
// normalize lon first
if (lon2 > DEG_180_AS_RADS) {
lon2 = -1.0 * (DEG_180_AS_RADS - (lon2 - DEG_180_AS_RADS));
} else if (lon2 < -DEG_180_AS_RADS) {
lon2 = (lon2 + DEG_180_AS_RADS) + DEG_180_AS_RADS;
}
// normalize lat - could flip poles
if (lat2 > DEG_90_AS_RADS) {
lat2 = DEG_90_AS_RADS - (lat2 - DEG_90_AS_RADS);
if (lon2 < 0) {
lon2 = lon2 + DEG_180_AS_RADS;
} else {
lon2 = lon2 - DEG_180_AS_RADS;
}
} else if (lat2 < -DEG_90_AS_RADS) {
lat2 = -DEG_90_AS_RADS - (lat2 + DEG_90_AS_RADS);
if (lon2 < 0) {
lon2 = lon2 + DEG_180_AS_RADS;
} else {
lon2 = lon2 - DEG_180_AS_RADS;
}
}
if (reuse == null) {
return ctx.makePoint(lon2, lat2);
} else {
reuse.reset(lon2, lat2);//x y
return reuse;
}
}
/**
* Puts in range -180 <= lon_deg <= +180.
*/
public static double normLonDEG(double lon_deg) {
if (lon_deg >= -180 && lon_deg <= 180)
return lon_deg;//common case, and avoids slight double precision shifting
double off = (lon_deg + 180) % 360;
if (off < 0)
return 180 + off;
else if (off == 0 && lon_deg > 0)
return 180;
else
return -180 + off;
}
/**
* Puts in range -90 <= lat_deg <= 90.
*/
public static double normLatDEG(double lat_deg) {
if (lat_deg >= -90 && lat_deg <= 90)
return lat_deg;//common case, and avoids slight double precision shifting
double off = Math.abs((lat_deg + 90) % 360);
return (off <= 180 ? off : 360-off) - 90;
}
/**
* Calculates the bounding box of a circle, as specified by its center point
* and distance. reuse is an optional argument to store the
* results to avoid object creation.
*/
public static Rectangle calcBoxByDistFromPtDEG(double lat, double lon, double distDEG, SpatialContext ctx, Rectangle reuse) {
//See http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates Section 3.1, 3.2 and 3.3
double minX; double maxX; double minY; double maxY;
if (distDEG == 0) {
minX = lon; maxX = lon; minY = lat; maxY = lat;
} else if (distDEG >= 180) {//distance is >= opposite side of the globe
minX = -180; maxX = 180; minY = -90; maxY = 90;
} else {
//--calc latitude bounds
maxY = lat + distDEG;
minY = lat - distDEG;
if (maxY >= 90 || minY <= -90) {//touches either pole
//we have special logic for longitude
minX = -180; maxX = 180;//world wrap: 360 deg
if (maxY <= 90 && minY >= -90) {//doesn't pass either pole: 180 deg
minX = normLonDEG(lon - 90);
maxX = normLonDEG(lon + 90);
}
if (maxY > 90)
maxY = 90;
if (minY < -90)
minY = -90;
} else {
//--calc longitude bounds
double lon_delta_deg = calcBoxByDistFromPt_deltaLonDEG(lat, lon, distDEG);
minX = normLonDEG(lon - lon_delta_deg);
maxX = normLonDEG(lon + lon_delta_deg);
}
}
if (reuse == null) {
return ctx.makeRectangle(minX, maxX, minY, maxY);
} else {
reuse.reset(minX, maxX, minY, maxY);
return reuse;
}
}
/**
* The delta longitude of a point-distance. In other words, half the width of
* the bounding box of a circle.
*/
public static double calcBoxByDistFromPt_deltaLonDEG(double lat, double lon, double distDEG) {
//http://gis.stackexchange.com/questions/19221/find-tangent-point-on-circle-furthest-east-or-west
if (distDEG == 0)
return 0;
double lat_rad = toRadians(lat);
double dist_rad = toRadians(distDEG);
double result_rad = Math.asin(Math.sin(dist_rad) / Math.cos(lat_rad));
if (!Double.isNaN(result_rad))
return toDegrees(result_rad);
return 90;
}
/**
* The latitude of the horizontal axis (e.g. left-right line)
* of a circle. The horizontal axis of a circle passes through its furthest
* left-most and right-most edges. On a 2D plane, this result is always
* from.getY() but, perhaps surprisingly, on a sphere it is going
* to be slightly different.
*/
public static double calcBoxByDistFromPt_latHorizAxisDEG(double lat, double lon, double distDEG) {
//http://gis.stackexchange.com/questions/19221/find-tangent-point-on-circle-furthest-east-or-west
if (distDEG == 0)
return lat;
// if we don't do this when == 90 or -90, computed result can be (+/-)89.9999 when at pole.
// No biggie but more accurate.
else if (lat + distDEG >= 90)
return 90;
else if (lat - distDEG <= -90)
return -90;
double lat_rad = toRadians(lat);
double dist_rad = toRadians(distDEG);
double result_rad = Math.asin( Math.sin(lat_rad) / Math.cos(dist_rad));
if (!Double.isNaN(result_rad))
return toDegrees(result_rad);
//handle NaN (shouldn't happen due to checks earlier)
if (lat > 0)
return 90;
if (lat < 0)
return -90;
return lat;//0
}
/**
* Calculates the degrees longitude distance at latitude {@code lat} to cover
* a distance {@code dist}.
*
* Used to calculate a new expanded buffer distance to account for skewing
* effects for shapes that use the lat-lon space as a 2D plane instead of a
* sphere. The expanded buffer will be sure to cover the intended area, but
* the shape is still skewed and so it will cover a larger area. For latitude
* 0 (the equator) the result is the same buffer. At 60 (or -60) degrees, the
* result is twice the buffer, meaning that a shape at 60 degrees is twice as
* high as it is wide when projected onto a lat-lon plane even if in the real
* world it's equal all around.
*
* If the result added to abs({@code lat}) is >= 90 degrees, then skewing is
* so severe that the caller should consider tossing the shape and
* substituting a spherical cap instead.
*
* @param lat latitude in degrees
* @param dist distance in degrees
* @return longitudinal degrees (x delta) at input latitude that is >= dist
* distance. Will be >= dist and <= 90.
*/
public static double calcLonDegreesAtLat(double lat, double dist) {
//This code was pulled out of DistanceUtils.pointOnBearingRAD() and
// optimized
// for bearing = 90 degrees, and so we can get an intermediate calculation.
double distanceRAD = DistanceUtils.toRadians(dist);
double startLat = DistanceUtils.toRadians(lat);
double cosAngDist = Math.cos(distanceRAD);
double cosStartLat = Math.cos(startLat);
double sinAngDist = Math.sin(distanceRAD);
double sinStartLat = Math.sin(startLat);
double lonDelta = Math.atan2(sinAngDist * cosStartLat,
cosAngDist * (1 - sinStartLat * sinStartLat));
return DistanceUtils.toDegrees(lonDelta);
}
/**
* The square of the cartesian Distance. Not really a distance, but useful if all that matters is
* comparing the result to another one.
*
* @param vec1 The first point
* @param vec2 The second point
* @return The squared cartesian distance
*/
@Deprecated
public static double distSquaredCartesian(double[] vec1, double[] vec2) {
double result = 0;
for (int i = 0; i < vec1.length; i++) {
double v = vec1[i] - vec2[i];
result += v * v;
}
return result;
}
/**
*
* @param lat1 The y coordinate of the first point, in radians
* @param lon1 The x coordinate of the first point, in radians
* @param lat2 The y coordinate of the second point, in radians
* @param lon2 The x coordinate of the second point, in radians
* @return The distance between the two points, as determined by the Haversine formula, in radians.
*/
public static double distHaversineRAD(double lat1, double lon1, double lat2, double lon2) {
//TODO investigate slightly different formula using asin() and min() http://www.movable-type.co.uk/scripts/gis-faq-5.1.html
// Check for same position
if (lat1 == lat2 && lon1 == lon2)
return 0.0;
double hsinX = Math.sin((lon1 - lon2) * 0.5);
double hsinY = Math.sin((lat1 - lat2) * 0.5);
double h = hsinY * hsinY +
(Math.cos(lat1) * Math.cos(lat2) * hsinX * hsinX);
if (h > 1)//numeric robustness issue. If we didn't check, the answer would be NaN!
h = 1;
return 2 * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h));
}
/**
* Calculates the distance between two lat-lon's using the Law of Cosines. Due to numeric conditioning
* errors, it is not as accurate as the Haversine formula for small distances. But with
* double precision, it isn't that bad --
* allegedly 1 meter.
*
* See
* Why is law of cosines more preferable than haversine when calculating distance between two latitude-longitude points?
*
* The arguments and return value are in radians.
*/
public static double distLawOfCosinesRAD(double lat1, double lon1, double lat2, double lon2) {
// Check for same position
if (lat1 == lat2 && lon1 == lon2)
return 0.0;
// Get the longitude difference. Don't need to worry about
// crossing dateline since cos(x) = cos(-x)
double dLon = lon2 - lon1;
double cosB = (Math.sin(lat1) * Math.sin(lat2))
+ (Math.cos(lat1) * Math.cos(lat2) * Math.cos(dLon));
// Find angle subtended (with some bounds checking) in radians
if (cosB < -1.0)
return Math.PI;
else if (cosB >= 1.0)
return 0;
else
return Math.acos(cosB);
}
/**
* Calculates the great circle distance using the Vincenty Formula, simplified for a spherical model. This formula
* is accurate for any pair of points. The equation
* was taken from Wikipedia.
*
* The arguments are in radians, and the result is in radians.
*/
public static double distVincentyRAD(double lat1, double lon1, double lat2, double lon2) {
// Check for same position
if (lat1 == lat2 && lon1 == lon2)
return 0.0;
double cosLat1 = Math.cos(lat1);
double cosLat2 = Math.cos(lat2);
double sinLat1 = Math.sin(lat1);
double sinLat2 = Math.sin(lat2);
double dLon = lon2 - lon1;
double cosDLon = Math.cos(dLon);
double sinDLon = Math.sin(dLon);
double a = cosLat2 * sinDLon;
double b = cosLat1*sinLat2 - sinLat1*cosLat2*cosDLon;
double c = sinLat1*sinLat2 + cosLat1*cosLat2*cosDLon;
return Math.atan2(Math.sqrt(a*a+b*b),c);
}
/**
* Converts a distance in the units of the radius to degrees (360 degrees are
* in a circle). A spherical earth model is assumed.
*/
public static double dist2Degrees(double dist, double radius) {
return toDegrees(dist2Radians(dist, radius));
}
/**
* Converts degrees (1/360th of circumference of a circle) into a
* distance as measured by the units of the radius. A spherical earth model
* is assumed.
*/
public static double degrees2Dist(double degrees, double radius) {
return radians2Dist(toRadians(degrees), radius);
}
/**
* Converts a distance in the units of radius (e.g. kilometers)
* to radians (multiples of the radius). A spherical earth model is assumed.
*/
public static double dist2Radians(double dist, double radius) {
return dist / radius;
}
/**
* Converts radians (multiples of the radius) to
* distance in the units of the radius (e.g. kilometers).
*/
public static double radians2Dist(double radians, double radius) {
return radians * radius;
}
/**
* Same as {@link Math#toRadians(double)} but 3x faster (multiply vs. divide).
* See CompareRadiansSnippet.java in tests.
*/
public static double toRadians(double degrees) {
return degrees * DEGREES_TO_RADIANS;
}
/**
* Same as {@link Math#toDegrees(double)} but 3x faster (multiply vs. divide).
* See CompareRadiansSnippet.java in tests.
*/
public static double toDegrees(double radians) {
return radians * RADIANS_TO_DEGREES;
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/distance/GeodesicSphereDistCalc.java 0000664 0000000 0000000 00000007526 12577056562 0032165 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 com.spatial4j.core.distance;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import static com.spatial4j.core.distance.DistanceUtils.toDegrees;
import static com.spatial4j.core.distance.DistanceUtils.toRadians;
/**
* A base class for a Distance Calculator that assumes a spherical earth model.
*/
public abstract class GeodesicSphereDistCalc extends AbstractDistanceCalculator {
private static final double radiusDEG = DistanceUtils.toDegrees(1);//in degrees
@Override
public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse) {
if (distDEG == 0) {
if (reuse == null)
return from;
reuse.reset(from.getX(), from.getY());
return reuse;
}
Point result = DistanceUtils.pointOnBearingRAD(
toRadians(from.getY()), toRadians(from.getX()),
toRadians(distDEG),
toRadians(bearingDEG), ctx, reuse);//output result is in radians
result.reset(toDegrees(result.getX()), toDegrees(result.getY()));
return result;
}
@Override
public Rectangle calcBoxByDistFromPt(Point from, double distDEG, SpatialContext ctx, Rectangle reuse) {
return DistanceUtils.calcBoxByDistFromPtDEG(from.getY(), from.getX(), distDEG, ctx, reuse);
}
@Override
public double calcBoxByDistFromPt_yHorizAxisDEG(Point from, double distDEG, SpatialContext ctx) {
return DistanceUtils.calcBoxByDistFromPt_latHorizAxisDEG(from.getY(), from.getX(), distDEG);
}
@Override
public double area(Rectangle rect) {
//From http://mathforum.org/library/drmath/view/63767.html
double lat1 = toRadians(rect.getMinY());
double lat2 = toRadians(rect.getMaxY());
return Math.PI / 180 * radiusDEG * radiusDEG *
Math.abs(Math.sin(lat1) - Math.sin(lat2)) *
rect.getWidth();
}
@Override
public double area(Circle circle) {
//formula is a simplified case of area(rect).
double lat = toRadians(90 - circle.getRadius());
return 2 * Math.PI * radiusDEG * radiusDEG * (1 - Math.sin(lat));
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
return getClass().equals(obj.getClass());
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public final double distance(Point from, double toX, double toY) {
return toDegrees(distanceLatLonRAD(toRadians(from.getY()), toRadians(from.getX()), toRadians(toY), toRadians(toX)));
}
protected abstract double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2);
public static class Haversine extends GeodesicSphereDistCalc {
@Override
protected double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2) {
return DistanceUtils.distHaversineRAD(lat1,lon1,lat2,lon2);
}
}
public static class LawOfCosines extends GeodesicSphereDistCalc {
@Override
protected double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2) {
return DistanceUtils.distLawOfCosinesRAD(lat1, lon1, lat2, lon2);
}
}
public static class Vincenty extends GeodesicSphereDistCalc {
@Override
protected double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2) {
return DistanceUtils.distVincentyRAD(lat1, lon1, lat2, lon2);
}
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/distance/package-info.java 0000664 0000000 0000000 00000001034 12577056562 0030175 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 com.spatial4j.core.distance;
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/exception/ 0000775 0000000 0000000 00000000000 12577056562 0025214 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/exception/InvalidShapeException.java 0000664 0000000 0000000 00000001764 12577056562 0032315 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 com.spatial4j.core.exception;
/**
* A shape was constructed but failed because, based on the given parts, it's invalid. For example
* a rectangle's minimum Y was specified as greater than the maximum Y. This class is not used for
* parsing exceptions; that's usually {@link java.text.ParseException}.
*/
public class InvalidShapeException extends RuntimeException {
public InvalidShapeException(String reason, Throwable cause) {
super(reason, cause);
}
public InvalidShapeException(String reason) {
super(reason);
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/exception/UnsupportedSpatialPredicate.java 0000664 0000000 0000000 00000002371 12577056562 0033551 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 com.spatial4j.core.exception;
import com.spatial4j.core.SpatialPredicate;
/**
* Exception thrown when the {@link org.apache.lucene.spatial.SpatialStrategy} cannot implement the requested operation.
*/
public class UnsupportedSpatialPredicate extends UnsupportedOperationException {
public UnsupportedSpatialPredicate(SpatialPredicate op) {
super(op.getName());
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/io/ 0000775 0000000 0000000 00000000000 12577056562 0023625 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/io/BinaryCodec.java 0000664 0000000 0000000 00000014057 12577056562 0026661 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 com.spatial4j.core.io;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.ShapeCollection;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
/**
* A binary shape format. It is not designed to be a published standard, unlike Well Known
* Binary (WKB). The initial release is simple but it could get more optimized to use fewer bytes or
* to write & read pre-computed index structures.
*
* Immutable and thread-safe.
*/
public class BinaryCodec {
//type 0; reserved for unkonwn/generic; see readCollection
protected static final byte
TYPE_POINT = 1,
TYPE_RECT = 2,
TYPE_CIRCLE = 3,
TYPE_COLL = 4,
TYPE_GEOM = 5;
//TODO support BufferedLineString
protected final SpatialContext ctx;
//This constructor is mandated by SpatialContextFactory
public BinaryCodec(SpatialContext ctx, SpatialContextFactory factory) {
this.ctx = ctx;
}
public Shape readShape(DataInput dataInput) throws IOException {
byte type = dataInput.readByte();
Shape s = readShapeByTypeIfSupported(dataInput, type);
if (s == null)
throw new IllegalArgumentException("Unsupported shape byte "+type);
return s;
}
public void writeShape(DataOutput dataOutput, Shape s) throws IOException {
boolean written = writeShapeByTypeIfSupported(dataOutput, s);
if (!written)
throw new IllegalArgumentException("Unsupported shape "+s.getClass());
}
protected Shape readShapeByTypeIfSupported(DataInput dataInput, byte type) throws IOException {
switch (type) {
case TYPE_POINT: return readPoint(dataInput);
case TYPE_RECT: return readRect(dataInput);
case TYPE_CIRCLE: return readCircle(dataInput);
case TYPE_COLL: return readCollection(dataInput);
default: return null;
}
}
/** Note: writes the type byte even if not supported */
protected boolean writeShapeByTypeIfSupported(DataOutput dataOutput, Shape s) throws IOException {
byte type = typeForShape(s);
dataOutput.writeByte(type);
return writeShapeByTypeIfSupported(dataOutput, s, type);
//dataOutput.position(dataOutput.position() - 1);//reset putting type
}
protected boolean writeShapeByTypeIfSupported(DataOutput dataOutput, Shape s, byte type) throws IOException {
switch (type) {
case TYPE_POINT: writePoint(dataOutput, (Point) s); break;
case TYPE_RECT: writeRect(dataOutput, (Rectangle) s); break;
case TYPE_CIRCLE: writeCircle(dataOutput, (Circle) s); break;
case TYPE_COLL: writeCollection(dataOutput, (ShapeCollection) s); break;
default:
return false;
}
return true;
}
protected byte typeForShape(Shape s) {
if (s instanceof Point) {
return TYPE_POINT;
} else if (s instanceof Rectangle) {
return TYPE_RECT;
} else if (s instanceof Circle) {
return TYPE_CIRCLE;
} else if (s instanceof ShapeCollection) {
return TYPE_COLL;
} else {
return 0;
}
}
protected double readDim(DataInput dataInput) throws IOException {
return dataInput.readDouble();
}
protected void writeDim(DataOutput dataOutput, double v) throws IOException {
dataOutput.writeDouble(v);
}
public Point readPoint(DataInput dataInput) throws IOException {
return ctx.makePoint(readDim(dataInput), readDim(dataInput));
}
public void writePoint(DataOutput dataOutput, Point pt) throws IOException {
writeDim(dataOutput, pt.getX());
writeDim(dataOutput, pt.getY());
}
public Rectangle readRect(DataInput dataInput) throws IOException {
return ctx.makeRectangle(readDim(dataInput), readDim(dataInput), readDim(dataInput), readDim(dataInput));
}
public void writeRect(DataOutput dataOutput, Rectangle r) throws IOException {
writeDim(dataOutput, r.getMinX());
writeDim(dataOutput, r.getMaxX());
writeDim(dataOutput, r.getMinY());
writeDim(dataOutput, r.getMaxY());
}
public Circle readCircle(DataInput dataInput) throws IOException {
return ctx.makeCircle(readPoint(dataInput), readDim(dataInput));
}
public void writeCircle(DataOutput dataOutput, Circle c) throws IOException {
writePoint(dataOutput, c.getCenter());
writeDim(dataOutput, c.getRadius());
}
public ShapeCollection readCollection(DataInput dataInput) throws IOException {
byte type = dataInput.readByte();
int size = dataInput.readInt();
ArrayList shapes = new ArrayList(size);
for (int i = 0; i < size; i++) {
if (type == 0) {
shapes.add(readShape(dataInput));
} else {
Shape s = readShapeByTypeIfSupported(dataInput, type);
if (s == null)
throw new InvalidShapeException("Unsupported shape byte "+type);
shapes.add(s);
}
}
return ctx.makeCollection(shapes);
}
public void writeCollection(DataOutput dataOutput, ShapeCollection col) throws IOException {
byte type = (byte) 0;//TODO add type to ShapeCollection
dataOutput.writeByte(type);
dataOutput.writeInt(col.size());
for (int i = 0; i < col.size(); i++) {
Shape s = col.get(i);
if (type == 0) {
writeShape(dataOutput, s);
} else {
boolean written = writeShapeByTypeIfSupported(dataOutput, s, type);
if (!written)
throw new IllegalArgumentException("Unsupported shape type "+s.getClass());
}
}
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/io/GeoJSONReader.java 0000664 0000000 0000000 00000030452 12577056562 0027023 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 com.spatial4j.core.io;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.text.ParseException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import org.noggit.JSONParser;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Shape;
public class GeoJSONReader implements ShapeReader {
protected static final String BUFFER = "buffer";
protected static final String BUFFER_UNITS = "buffer_units";
final SpatialContext ctx;
public GeoJSONReader(SpatialContext ctx, SpatialContextFactory factory) {
this.ctx = ctx;
}
@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
// --------------------------------------------------------------
public List> readCoordinates(JSONParser parser) throws IOException, ParseException {
assert (parser.lastEvent() == JSONParser.ARRAY_START);
Deque stack = new ArrayDeque();
stack.push(new ArrayList());
int depth = 1;
while (true) {
int evt = parser.nextEvent();
switch (evt) {
case JSONParser.LONG:
case JSONParser.NUMBER:
case JSONParser.BIGNUMBER:
stack.peek().add(parser.getDouble());
break;
case JSONParser.ARRAY_START:
stack.push(new ArrayList());
depth++;
break;
case JSONParser.ARRAY_END:
depth--;
List val = stack.pop();
if (depth == 0) {
return val;
}
stack.peek().add(val);
break;
default:
throw new ParseException("Unexpected " + JSONParser.getEventString(evt),
(int) parser.getPosition());
}
}
}
public double[] readCoordXY(JSONParser parser) throws IOException, ParseException {
assert (parser.lastEvent() == JSONParser.ARRAY_START);
double[] coord = new double[3];
int idx = 0;
int evt = parser.nextEvent();
while (evt != JSONParser.EOF) {
switch (evt) {
case JSONParser.LONG:
case JSONParser.NUMBER:
case JSONParser.BIGNUMBER:
coord[idx++] = parser.getDouble();
break;
case JSONParser.ARRAY_END:
return coord;
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 coord;
}
public List readCoordListXY(JSONParser parser) throws IOException, ParseException {
assert (parser.lastEvent() == JSONParser.ARRAY_START);
List coords = new ArrayList();
int evt = parser.nextEvent();
while (evt != JSONParser.EOF) {
switch (evt) {
case JSONParser.ARRAY_START:
coords.add(readCoordXY(parser));
break;
case JSONParser.ARRAY_END:
return coords;
default:
throw new ParseException("Unexpected " + JSONParser.getEventString(evt),
(int) parser.getPosition());
}
evt = parser.nextEvent();
}
return coords;
}
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);
double[] coord = readCoordXY(parser);
Shape v = ctx.makePoint(coord[0], coord[1]);
readUntilEvent(parser, JSONParser.OBJECT_END);
return v;
}
protected Shape readLineString(JSONParser parser) throws IOException, ParseException {
assert (parser.lastEvent() == JSONParser.ARRAY_START);
List coords = readCoordListXY(parser);
List points = new ArrayList(coords.size());
for (double[] coord : coords) {
points.add(ctx.makePoint(coord[0], coord[1]));
}
// check for buffer field
double buf = readDistance(BUFFER, BUFFER_UNITS, parser);
Shape out = buf == 0d ? ctx.makeLineString(points) : ctx.makeBufferedLineString(points, buf);
readUntilEvent(parser, JSONParser.OBJECT_END);
return out;
}
protected Circle readCircle(JSONParser parser) throws IOException, ParseException {
assert (parser.lastEvent() == JSONParser.ARRAY_START);
double[] coord = readCoordXY(parser);
Point point = ctx.makePoint(coord[0], coord[1]);
return ctx.makeCircle(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 dist;
}
/**
* This method takes a polygon and makes a bbox from it
*
* NOTE: not currently used! polygon is currently implemented in:
* {@link GeoJSONReader#makeShapeFromCoords(String, List)}
*
* We could add a 'strict' or 'leinent' mode that would try the best it can
*
* @throws ParseException
*/
protected Shape readPolygon(JSONParser parser) throws IOException, ParseException {
assert (parser.lastEvent() == JSONParser.ARRAY_START);
double[] min = new double[] {Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE};
double[] max = new double[] {Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE};
// Just get all coords and expand
double[] coords = new double[3];
int idx = 0;
int evt = parser.nextEvent();
while (evt != JSONParser.EOF) {
// System.out.println( ">> "+JSONParser.getEventString(evt));
switch (evt) {
case JSONParser.LONG:
case JSONParser.NUMBER:
case JSONParser.BIGNUMBER:
coords[idx] = parser.getDouble();
if (coords[idx] > max[idx]) {
max[idx] = coords[idx];
}
if (coords[idx] < min[idx]) {
min[idx] = coords[idx];
}
idx++;
break;
case JSONParser.ARRAY_END:
idx = 0;
break;
case JSONParser.OBJECT_END: {
return ctx.makeRectangle(min[0], max[0], min[1], max[1]);
}
}
evt = parser.nextEvent();
}
throw new RuntimeException("Could not find polygon");
}
public 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 = null;
if ("Point".equals(type)) {
shape = readPoint(parser);
} else if ("LineString".equals(type)) {
shape = readLineString(parser);
} else if ("Circle".equals(type)) {
shape = readCircle(parser);
}else {
shape = makeShapeFromCoords(type, readCoordinates(parser));
}
if (shape != null) {
readUntilEvent(parser, JSONParser.OBJECT_END);
return shape;
}
throw new ParseException("Unable to make shape type: " + type,
(int) parser.getPosition());
} 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();
}
if (shapes.isEmpty()) {
throw new ParseException("Shape Collection with now geometries!",
(int) parser.getPosition());
}
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 makeShapeFromCoords(String type, List coords) {
// TODO?: we could default to making a bbox rather than throwing an error
throw new RuntimeException("Unsupported: " + type); // JTS Supports this
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/io/GeoJSONWriter.java 0000664 0000000 0000000 00000014242 12577056562 0027074 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 com.spatial4j.core.io;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.shape.*;
import com.spatial4j.core.shape.impl.BufferedLine;
import com.spatial4j.core.shape.impl.BufferedLineString;
import com.spatial4j.core.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 com.spatial4j.core.io.GeoJSONReader.BUFFER;
import static com.spatial4j.core.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 (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.5/src/main/java/com/spatial4j/core/io/GeohashUtils.java 0000664 0000000 0000000 00000015322 12577056562 0027072 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 com.spatial4j.core.io;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import java.util.Arrays;
/**
* Utilities for encoding and decoding geohashes.
*
* This class isn't used by any other part of Spatial4j; it's included largely for convenience of
* software using Spatial4j. There are other open-source libraries that have more comprehensive
* geohash utilities but providing this one avoids an additional dependency for what's a small
* amount of code. If you're using Spatial4j just for this class, consider alternatives.
*
* This code originally came from
* Apache Lucene, LUCENE-1512.
*/
public class GeohashUtils {
private static final char[] BASE_32 = {'0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};//note: this is sorted
private static final int[] BASE_32_IDX;//sparse array of indexes from '0' to 'z'
public static final int MAX_PRECISION = 24;//DWS: I forget what level results in needless more precision but it's about this
private static final int[] BITS = {16, 8, 4, 2, 1};
static {
BASE_32_IDX = new int[BASE_32[BASE_32.length-1] - BASE_32[0] + 1];
assert BASE_32_IDX.length < 100;//reasonable length
Arrays.fill(BASE_32_IDX,-500);
for (int i = 0; i < BASE_32.length; i++) {
BASE_32_IDX[BASE_32[i] - BASE_32[0]] = i;
}
}
private GeohashUtils() {
}
/**
* Encodes the given latitude and longitude into a geohash
*
* @param latitude Latitude to encode
* @param longitude Longitude to encode
* @return Geohash encoding of the longitude and latitude
*/
public static String encodeLatLon(double latitude, double longitude) {
return encodeLatLon(latitude, longitude, 12);
}
public static String encodeLatLon(double latitude, double longitude, int precision) {
double[] latInterval = {-90.0, 90.0};
double[] lngInterval = {-180.0, 180.0};
final StringBuilder geohash = new StringBuilder(precision);
boolean isEven = true;
int bit = 0;
int ch = 0;
while (geohash.length() < precision) {
double mid = 0.0;
if (isEven) {
mid = (lngInterval[0] + lngInterval[1]) / 2D;
if (longitude > mid) {
ch |= BITS[bit];
lngInterval[0] = mid;
} else {
lngInterval[1] = mid;
}
} else {
mid = (latInterval[0] + latInterval[1]) / 2D;
if (latitude > mid) {
ch |= BITS[bit];
latInterval[0] = mid;
} else {
latInterval[1] = mid;
}
}
isEven = !isEven;
if (bit < 4) {
bit++;
} else {
geohash.append(BASE_32[ch]);
bit = 0;
ch = 0;
}
}
return geohash.toString();
}
/**
* Decodes the given geohash into a latitude and longitude
*
* @param geohash Geohash to deocde
* @return Array with the latitude at index 0, and longitude at index 1
*/
public static Point decode(String geohash, SpatialContext ctx) {
Rectangle rect = decodeBoundary(geohash,ctx);
double latitude = (rect.getMinY() + rect.getMaxY()) / 2D;
double longitude = (rect.getMinX() + rect.getMaxX()) / 2D;
return ctx.makePoint(longitude,latitude);
}
/** Returns min-max lat, min-max lon. */
public static Rectangle decodeBoundary(String geohash, SpatialContext ctx) {
double minY = -90, maxY = 90, minX = -180, maxX = 180;
boolean isEven = true;
for (int i = 0; i < geohash.length(); i++) {
char c = geohash.charAt(i);
if (c >= 'A' && c <= 'Z')
c -= ('A' - 'a');
final int cd = BASE_32_IDX[c - BASE_32[0]];//TODO check successful?
for (int mask : BITS) {
if (isEven) {
if ((cd & mask) != 0) {
minX = (minX + maxX) / 2D;
} else {
maxX = (minX + maxX) / 2D;
}
} else {
if ((cd & mask) != 0) {
minY = (minY + maxY) / 2D;
} else {
maxY = (minY + maxY) / 2D;
}
}
isEven = !isEven;
}
}
return ctx.makeRectangle(minX, maxX, minY, maxY);
}
/** Array of geohashes 1 level below the baseGeohash. Sorted. */
public static String[] getSubGeohashes(String baseGeohash) {
String[] hashes = new String[BASE_32.length];
for (int i = 0; i < BASE_32.length; i++) {//note: already sorted
char c = BASE_32[i];
hashes[i] = baseGeohash+c;
}
return hashes;
}
public static double[] lookupDegreesSizeForHashLen(int hashLen) {
return new double[]{hashLenToLatHeight[hashLen], hashLenToLonWidth[hashLen]};
}
/**
* Return the shortest geohash length that will have a width & height >= specified arguments.
*/
public static int lookupHashLenForWidthHeight(double lonErr, double latErr) {
//loop through hash length arrays from beginning till we find one.
for(int len = 1; len < MAX_PRECISION; len++) {
double latHeight = hashLenToLatHeight[len];
double lonWidth = hashLenToLonWidth[len];
if (latHeight < latErr && lonWidth < lonErr)
return len;
}
return MAX_PRECISION;
}
/** See the table at http://en.wikipedia.org/wiki/Geohash */
private static final double[] hashLenToLatHeight, hashLenToLonWidth;
static {
hashLenToLatHeight = new double[MAX_PRECISION +1];
hashLenToLonWidth = new double[MAX_PRECISION +1];
hashLenToLatHeight[0] = 90*2;
hashLenToLonWidth[0] = 180*2;
boolean even = false;
for(int i = 1; i <= MAX_PRECISION; i++) {
hashLenToLatHeight[i] = hashLenToLatHeight[i-1]/(even?8:4);
hashLenToLonWidth[i] = hashLenToLonWidth[i-1]/(even?4:8);
even = ! even;
}
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/io/LegacyShapeReader.java 0000664 0000000 0000000 00000012500 12577056562 0027776 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 com.spatial4j.core.io;
import java.io.IOException;
import java.io.Reader;
import java.text.ParseException;
import java.util.StringTokenizer;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.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 com.spatial4j.core.exception.InvalidShapeException} is thrown.
*/
public static Shape readShapeOrNull(String str, SpatialContext ctx) throws InvalidShapeException {
if (str == null || str.length() == 0) {
throw new InvalidShapeException(str);
}
if (Character.isLetter(str.charAt(0))) {
if (str.startsWith("Circle(") || str.startsWith("CIRCLE(")) {
int idx = str.lastIndexOf(')');
if (idx > 0) {
String body = str.substring("Circle(".length(), idx);
StringTokenizer st = new StringTokenizer(body, " ");
String token = st.nextToken();
Point pt;
if (token.indexOf(',') != -1) {
pt = readLatCommaLonPoint(token, ctx);
} else {
double x = Double.parseDouble(token);
double y = Double.parseDouble(st.nextToken());
pt = ctx.makePoint(x, y);
}
Double d = null;
String arg = st.nextToken();
idx = arg.indexOf('=');
if (idx > 0) {
String k = arg.substring(0, idx);
if (k.equals("d") || k.equals("distance")) {
d = Double.parseDouble(arg.substring(idx + 1));
} else {
throw new InvalidShapeException("unknown arg: " + k + " :: " + str);
}
} else {
d = Double.parseDouble(arg);
}
if (st.hasMoreTokens()) {
throw new InvalidShapeException("Extra arguments: " + st.nextToken() + " :: " + str);
}
if (d == null) {
throw new InvalidShapeException("Missing Distance: " + str);
}
//NOTE: we are assuming the units of 'd' is the same as that of the spatial context.
return ctx.makeCircle(pt, d);
}
}
return null;//caller has opportunity to try other parsing
}
if (str.indexOf(',') != -1)
return readLatCommaLonPoint(str, ctx);
StringTokenizer st = new StringTokenizer(str, " ");
double p0 = Double.parseDouble(st.nextToken());
double p1 = Double.parseDouble(st.nextToken());
if (st.hasMoreTokens()) {
double p2 = Double.parseDouble(st.nextToken());
double p3 = Double.parseDouble(st.nextToken());
if (st.hasMoreTokens())
throw new InvalidShapeException("Only 4 numbers supported (rect) but found more: " + str);
return ctx.makeRectangle(p0, p2, p1, p3);
}
return ctx.makePoint(p0, p1);
}
/** Reads geospatial latitude then a comma then longitude. */
private static Point readLatCommaLonPoint(String value, SpatialContext ctx) throws InvalidShapeException {
double[] latLon = ParseUtils.parseLatitudeLongitude(value);
return ctx.makePoint(latLon[1], latLon[0]);
}
//-------
@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.5/src/main/java/com/spatial4j/core/io/LegacyShapeWriter.java 0000664 0000000 0000000 00000006514 12577056562 0030060 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 com.spatial4j.core.io;
import java.io.IOException;
import java.io.Writer;
import java.text.NumberFormat;
import java.util.Locale;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.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 #readShapeOrNull(String, com.spatial4j.core.context.SpatialContext)}.
* @param shape Not null.
* @return Not null.
*/
public static String writeShape(Shape shape) {
return writeShape(shape, makeNumberFormat(6));
}
/** Overloaded to provide a number format. */
public static String writeShape(Shape shape, NumberFormat nf) {
if (shape instanceof Point) {
Point point = (Point) shape;
return nf.format(point.getX()) + " " + nf.format(point.getY());
}
else if (shape instanceof Rectangle) {
Rectangle rect = (Rectangle)shape;
return
nf.format(rect.getMinX()) + " " +
nf.format(rect.getMinY()) + " " +
nf.format(rect.getMaxX()) + " " +
nf.format(rect.getMaxY());
}
else if (shape instanceof Circle) {
Circle c = (Circle) shape;
return "Circle(" +
nf.format(c.getCenter().getX()) + " " +
nf.format(c.getCenter().getY()) + " " +
"d=" + nf.format(c.getRadius()) +
")";
}
return shape.toString();
}
/**
* A convenience method to create a suitable NumberFormat for writing numbers.
*/
public static NumberFormat makeNumberFormat(int fractionDigits) {
NumberFormat nf = NumberFormat.getInstance(Locale.ROOT);//not thread-safe
nf.setGroupingUsed(false);
nf.setMaximumFractionDigits(fractionDigits);
nf.setMinimumFractionDigits(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.5/src/main/java/com/spatial4j/core/io/ParseUtils.java 0000664 0000000 0000000 00000015351 12577056562 0026570 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 com.spatial4j.core.io;
import com.spatial4j.core.exception.InvalidShapeException;
/**
* Utility methods related to parsing a series of numbers.
*
* This code came from DistanceUtils, which came from
* Apache
* Lucene, LUCENE-773, which in turn came from "LocalLucene".
*
* @deprecated Not useful; see https://github.com/spatial4j/spatial4j/issues/19
*/
@Deprecated
public class ParseUtils {
private ParseUtils() {
}
/**
* Given a string containing dimension values encoded in it, separated by commas, return a String array of length dimension
* containing the values.
*
* @param out A preallocated array. Must be size dimension. If it is not it will be resized.
* @param externalVal The value to parse
* @param dimension The expected number of values for the point
* @return An array of the values that make up the point (aka vector)
* @throws com.spatial4j.core.exception.InvalidShapeException if the dimension specified does not match the number of values in the externalValue.
*/
public static String[] parsePoint(String[] out, String externalVal, int dimension) throws InvalidShapeException {
//TODO: Should we support sparse vectors?
if (out == null || out.length != dimension) out = new String[dimension];
int idx = externalVal.indexOf(',');
int end = idx;
int start = 0;
int i = 0;
if (idx == -1 && dimension == 1 && externalVal.length() > 0) {//we have a single point, dimension better be 1
out[0] = externalVal.trim();
i = 1;
} else if (idx > 0) {//if it is zero, that is an error
//Parse out a comma separated list of point values, as in: 73.5,89.2,7773.4
for (; i < dimension; i++) {
while (start < end && externalVal.charAt(start) == ' ') start++;
while (end > start && externalVal.charAt(end - 1) == ' ') end--;
if (start == end) {
break;
}
out[i] = externalVal.substring(start, end);
start = idx + 1;
end = externalVal.indexOf(',', start);
idx = end;
if (end == -1) {
end = externalVal.length();
}
}
}
if (i != dimension) {
throw new InvalidShapeException("incompatible dimension (" + dimension +
") and values (" + externalVal + "). Only " + i + " values specified");
}
return out;
}
/**
* Given a string containing dimension values encoded in it, separated by commas, return a double array of length dimension
* containing the values.
*
* @param out A preallocated array. Must be size dimension. If it is not it will be resized.
* @param externalVal The value to parse
* @param dimension The expected number of values for the point
* @return An array of the values that make up the point (aka vector)
* @throws com.spatial4j.core.exception.InvalidShapeException if the dimension specified does not match the number of values in the externalValue.
*/
public static double[] parsePointDouble(double[] out, String externalVal, int dimension) throws InvalidShapeException{
if (out == null || out.length != dimension) out = new double[dimension];
int idx = externalVal.indexOf(',');
int end = idx;
int start = 0;
int i = 0;
if (idx == -1 && dimension == 1 && externalVal.length() > 0) {//we have a single point, dimension better be 1
out[0] = Double.parseDouble(externalVal.trim());
i = 1;
} else if (idx > 0) {//if it is zero, that is an error
//Parse out a comma separated list of point values, as in: 73.5,89.2,7773.4
for (; i < dimension; i++) {
//TODO: abstract common code with other parsePoint
while (start < end && externalVal.charAt(start) == ' ') start++;
while (end > start && externalVal.charAt(end - 1) == ' ') end--;
if (start == end) {
break;
}
out[i] = Double.parseDouble(externalVal.substring(start, end));
start = idx + 1;
end = externalVal.indexOf(',', start);
idx = end;
if (end == -1) {
end = externalVal.length();
}
}
}
if (i != dimension) {
throw new InvalidShapeException("incompatible dimension (" + dimension +
") and values (" + externalVal + "). Only " + i + " values specified");
}
return out;
}
/**
* Extract (by calling {@link #parsePoint(String[], String, int)} and validate the latitude and
* longitude contained in the String by making sure the latitude is between 90 & -90 and longitude
* is between -180 and 180.
*
* The latitude is assumed to be the first part of the string and the longitude the second part.
*
* @param latLonStr The string to parse. Latitude is the first value, longitude is the second.
* @return The lat long
*
* @throws com.spatial4j.core.exception.InvalidShapeException if there was an error parsing
*/
public static final double[] parseLatitudeLongitude(String latLonStr) throws InvalidShapeException {
return parseLatitudeLongitude(null, latLonStr);
}
/**
* A variation of {@link #parseLatitudeLongitude(String)} that re-uses an output array.
* @see #parseLatitudeLongitude(String)
*/
public static final double[] parseLatitudeLongitude(double[] outLatLon, String latLonStr) throws InvalidShapeException {
outLatLon = parsePointDouble(outLatLon, latLonStr, 2);
if (outLatLon[0] < -90.0 || outLatLon[0] > 90.0) {
throw new InvalidShapeException(
"Invalid latitude: latitudes are range -90 to 90: provided lat: ["
+ outLatLon[0] + "]");
}
if (outLatLon[1] < -180.0 || outLatLon[1] > 180.0) {
throw new InvalidShapeException(
"Invalid longitude: longitudes are range -180 to 180: provided lon: ["
+ outLatLon[1] + "]");
}
return outLatLon;
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/io/PolyshapeReader.java 0000664 0000000 0000000 00000016054 12577056562 0027565 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 com.spatial4j.core.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 com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Shape;
/**
* @see PolyshapeWriter
*/
public class PolyshapeReader implements ShapeReader {
final SpatialContext ctx;
public PolyshapeReader(SpatialContext ctx, SpatialContextFactory factory) {
this.ctx = ctx;
}
@Override
public String getFormatName() {
return ShapeIO.POLY;
}
/**
* Subclass may try to make multiple points into a MultiPoint
*/
protected Shape makeCollection(List extends Shape> shapes) {
return ctx.makeCollection(shapes);
}
@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);
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 = ctx.makePoint(reader.readLat(), reader.readLng());
break;
}
case PolyshapeWriter.KEY_LINE: {
if(arg!=null) {
lastShape = ctx.makeBufferedLineString(reader.readPoints(ctx), arg.doubleValue());
}
else {
lastShape = ctx.makeLineString(reader.readPoints(ctx));
}
break;
}
case PolyshapeWriter.KEY_BOX: {
Point lowerLeft = ctx.makePoint(reader.readLat(), reader.readLng());
Point upperRight = ctx.makePoint(reader.readLat(), reader.readLng());
lastShape = ctx.makeRectangle(lowerLeft, upperRight);
break;
}
case PolyshapeWriter.KEY_MULTIPOINT : {
List points = reader.readPoints(ctx);
lastShape = makeCollection(points);
break;
}
case PolyshapeWriter.KEY_CIRCLE : {
if(arg==null) {
throw new IllegalArgumentException("the input should have a radius argument");
}
lastShape = ctx.makeCircle(reader.readLat(), reader.readLng(), 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);
}
return makeCollection(shapes);
}
return lastShape;
}
protected Shape readPolygon(XReader reader) throws IOException {
throw new IllegalArgumentException("This reader does not support polygons");
}
/**
* 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;
public XReader(final Reader input) throws IOException {
this.input = input;
head = input.read();
}
public List readPoints(SpatialContext ctx) throws IOException {
List points = new ArrayList();
while(isData()) {
points.add(ctx.makePoint(readLat(), readLng()));
}
return points;
}
public List readPoints() throws IOException {
List points = new ArrayList();
while(isData()) {
points.add(new double[]{readLat(), readLng()});
}
return points;
}
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.5/src/main/java/com/spatial4j/core/io/PolyshapeWriter.java 0000664 0000000 0000000 00000015147 12577056562 0027641 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 com.spatial4j.core.io;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Iterator;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.ShapeCollection;
import com.spatial4j.core.shape.impl.BufferedLine;
import com.spatial4j.core.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) {
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.5/src/main/java/com/spatial4j/core/io/ShapeIO.java 0000664 0000000 0000000 00000001405 12577056562 0025760 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 com.spatial4j.core.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.5/src/main/java/com/spatial4j/core/io/ShapeReader.java 0000664 0000000 0000000 00000003400 12577056562 0026650 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 com.spatial4j.core.io;
import java.io.IOException;
import java.io.Reader;
import java.text.ParseException;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.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
* @param error -- flag if we should throw an error or not (true will throw an error)
* @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)
* @throws IOException
* @throws ParseException
* @throws InvalidShapeException
*/
public Shape read(Reader reader) throws IOException, ParseException, InvalidShapeException;
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/io/ShapeWriter.java 0000664 0000000 0000000 00000001564 12577056562 0026733 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 com.spatial4j.core.io;
import java.io.IOException;
import java.io.Writer;
import com.spatial4j.core.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.5/src/main/java/com/spatial4j/core/io/SupportedFormats.java 0000664 0000000 0000000 00000004324 12577056562 0030014 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 com.spatial4j.core.io;
import java.util.List;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.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.5/src/main/java/com/spatial4j/core/io/WKTReader.java 0000664 0000000 0000000 00000051550 12577056562 0026266 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 com.spatial4j.core.io;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Shape;
import java.io.IOException;
import java.io.Reader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* An extensible parser for Well Known Text
* (WKT). The shapes supported by this class are:
*
* '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;
// TODO support SRID: "SRID=4326;POINT(1,2)
/**
* This constructor is required by
* {@link com.spatial4j.core.context.SpatialContextFactory#makeWktShapeParser(com.spatial4j.core.context.SpatialContext)}
* .
*/
public WKTReader(SpatialContext ctx, SpatialContextFactory factory) {
this.ctx = ctx;
}
/**
* Parses the wktString, returning the defined Shape.
*
* @return Non-null Shape defined in the String
* @throws ParseException Thrown if there is an error in the Shape definition
*/
public Shape parse(String wktString) throws ParseException, 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 e) {
throw e;
} catch (InvalidShapeException e) {
throw e;
} catch (IllegalArgumentException e) { // JTS Throws IllegalArgment 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 com.spatial4j.core.io.WKTReader.State#nextIfEmptyAndSkipZM()}.
*
* @param state
* @param shapeType Non-Null string; could have mixed case. The first character is a letter.
* @return The shape or null if not supported / unknown.
*/
protected Shape parseShapeByType(State state, String shapeType) throws ParseException {
assert Character.isLetter(shapeType.charAt(0)) : "Shape must start with letter: " + shapeType;
if (shapeType.equalsIgnoreCase("POINT")) {
return parsePointShape(state);
} else if (shapeType.equalsIgnoreCase("MULTIPOINT")) {
return parseMultiPointShape(state);
} else if (shapeType.equalsIgnoreCase("ENVELOPE")) {
return parseEnvelopeShape(state);
} else if (shapeType.equalsIgnoreCase("GEOMETRYCOLLECTION")) {
return parseGeometryCollectionShape(state);
} else if (shapeType.equalsIgnoreCase("LINESTRING")) {
return parseLineStringShape(state);
} else if (shapeType.equalsIgnoreCase("MULTILINESTRING")) {
return parseMultiLineStringShape(state);
}
// extension
if (shapeType.equalsIgnoreCase("BUFFER")) {
return parseBufferShape(state);
}
// HEY! Update class Javadocs if add more shapes
return null;
}
/**
* Parses the BUFFER operation applied to a parsed shape.
*
*
* '(' shape ',' number ')'
*
*
* Whereas 'number' is the distance to buffer the shape by.
*/
protected Shape parseBufferShape(State state) throws ParseException {
state.nextExpect('(');
Shape shape = shape(state);
state.nextExpect(',');
double distance = normDist(state.nextDouble());
state.nextExpect(')');
return shape.getBuffered(distance, ctx);
}
/**
* Called to normalize a value that isn't X or Y. X & Y or normalized via
* {@link com.spatial4j.core.context.SpatialContext#normX(double)} & normY.
*/
protected double normDist(double v) {// TODO should this be added to ctx?
return v;
}
/**
* Parses a POINT shape from the raw string.
*
*
* '(' coordinate ')'
*
*
* @see #point(WKTReader.State)
*/
protected Shape parsePointShape(State state) throws ParseException {
if (state.nextIfEmptyAndSkipZM())
return ctx.makePoint(Double.NaN, Double.NaN);
state.nextExpect('(');
Point coordinate = point(state);
state.nextExpect(')');
return coordinate;
}
/**
* Parses a MULTIPOINT shape from the raw string -- a collection of points.
*
*
* '(' coordinate (',' coordinate )* ')'
*
*
* Furthermore, coordinate can optionally be wrapped in parenthesis.
*
* @see #point(WKTReader.State)
*/
protected Shape parseMultiPointShape(State state) throws ParseException {
if (state.nextIfEmptyAndSkipZM())
return ctx.makeCollection(Collections.EMPTY_LIST);
List shapes = new ArrayList();
state.nextExpect('(');
do {
boolean openParen = state.nextIf('(');
Point coordinate = point(state);
if (openParen)
state.nextExpect(')');
shapes.add(coordinate);
} while (state.nextIf(','));
state.nextExpect(')');
return ctx.makeCollection(shapes);
}
/**
* Parses an ENVELOPE (aka Rectangle) shape from the raw string. The values are normalized.
*
* Source: OGC "Catalogue Services Specification", the "CQL" (Common Query Language) sub-spec.
* Note the inconsistent order of the min & max values between x & y!
*
*
* '(' x1 ',' x2 ',' y2 ',' y1 ')'
*
*/
protected Shape parseEnvelopeShape(State state) throws ParseException {
// FYI no dimension or EMPTY
state.nextExpect('(');
double x1 = state.nextDouble();
state.nextExpect(',');
double x2 = state.nextDouble();
state.nextExpect(',');
double y2 = state.nextDouble();
state.nextExpect(',');
double y1 = state.nextDouble();
state.nextExpect(')');
return ctx.makeRectangle(ctx.normX(x1), ctx.normX(x2), ctx.normY(y1), ctx.normY(y2));
}
/**
* Parses a LINESTRING shape from the raw string -- an ordered sequence of points.
*
*
* coordinateSequence
*
*
* @see #pointList(WKTReader.State)
*/
protected Shape parseLineStringShape(State state) throws ParseException {
if (state.nextIfEmptyAndSkipZM())
return ctx.makeLineString(Collections.emptyList());
List points = pointList(state);
return ctx.makeLineString(points);
}
/**
* Parses a MULTILINESTRING shape from the raw string -- a collection of line strings.
*
*
*
* @see #parseLineStringShape(com.spatial4j.core.io.WKTReader.State)
*/
protected Shape parseMultiLineStringShape(State state) throws ParseException {
if (state.nextIfEmptyAndSkipZM())
return ctx.makeCollection(Collections.EMPTY_LIST);
List shapes = new ArrayList();
state.nextExpect('(');
do {
shapes.add(parseLineStringShape(state));
} while (state.nextIf(','));
state.nextExpect(')');
return ctx.makeCollection(shapes);
}
/**
* Parses a GEOMETRYCOLLECTION shape from the raw string.
*
*
* '(' shape (',' shape )* ')'
*
*/
protected Shape parseGeometryCollectionShape(State state) throws ParseException {
if (state.nextIfEmptyAndSkipZM())
return ctx.makeCollection(Collections.EMPTY_LIST);
List shapes = new ArrayList();
state.nextExpect('(');
do {
shapes.add(shape(state));
} while (state.nextIf(','));
state.nextExpect(')');
return ctx.makeCollection(shapes);
}
/**
* Reads a shape from the current position, starting with the name of the shape. It calls
* {@link #parseShapeByType(com.spatial4j.core.io.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(WKTReader.State)
*/
protected List pointList(State state) throws ParseException {
List sequence = new ArrayList();
state.nextExpect('(');
do {
sequence.add(point(state));
} while (state.nextIf(','));
state.nextExpect(')');
return sequence;
}
/**
* Reads a raw Point (AKA Coordinate) from the current position. Only the first 2 numbers are
* used. The values are normalized.
*
*
* number number number*
*
*/
protected Point point(State state) throws ParseException {
double x = state.nextDouble();
double y = state.nextDouble();
state.skipNextDoubles();
return ctx.makePoint(ctx.normX(x), ctx.normY(y));
}
/** The parse state. */
public class State {
/** Set in {@link #parseIfSupported(String)}. */
public String rawString;
/** Offset of the next char in {@link #rawString} to be read. */
public int offset;
/** Dimensionality specifier (e.g. 'Z', or 'M') following a shape type name. */
public String dimension;
public State(String rawString) {
this.rawString = rawString;
}
public SpatialContext getCtx() {
return ctx;
}
public 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.5/src/main/java/com/spatial4j/core/io/WKTWriter.java 0000664 0000000 0000000 00000010165 12577056562 0026335 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 com.spatial4j.core.io;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.ShapeCollection;
import com.spatial4j.core.shape.impl.BufferedLine;
import com.spatial4j.core.shape.impl.BufferedLineString;
import java.io.IOException;
import java.io.Writer;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.Iterator;
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()));
}
@Override
public String toString(Shape shape) {
NumberFormat nf = LegacyShapeWriter.makeNumberFormat(6);
if (shape instanceof Point) {
StringBuilder buffer = new StringBuilder();
return append(buffer.append("POINT ("),(Point)shape,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) {
StringBuilder buffer = new StringBuilder();
buffer.append("GEOMETRYCOLLECTION (");
boolean first = true;
for(Shape sub : ((ShapeCollection extends Shape>)shape).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.5/src/main/java/com/spatial4j/core/io/WktShapeParser.java 0000664 0000000 0000000 00000002044 12577056562 0027373 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 com.spatial4j.core.io;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.SpatialContextFactory;
@Deprecated
public class WktShapeParser extends WKTReader {
/** This constructor is required by {@link com.spatial4j.core.context.SpatialContextFactory#makeWktShapeParser(com.spatial4j.core.context.SpatialContext)}. */
public WktShapeParser(SpatialContext ctx, SpatialContextFactory factory) {
super(ctx,factory);
}
} spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/io/jts/ 0000775 0000000 0000000 00000000000 12577056562 0024425 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/io/jts/JtsBinaryCodec.java 0000664 0000000 0000000 00000011026 12577056562 0030133 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 com.spatial4j.core.io.jts;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.io.BinaryCodec;
import com.spatial4j.core.shape.Shape;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.io.InStream;
import com.vividsolutions.jts.io.OutStream;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKBConstants;
import com.vividsolutions.jts.io.WKBReader;
import com.vividsolutions.jts.io.WKBWriter;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* Writes shapes in WKB, if it isn't otherwise supported by the superclass.
*/
public class JtsBinaryCodec extends BinaryCodec {
protected final boolean useFloat;//instead of double
public JtsBinaryCodec(JtsSpatialContext ctx, JtsSpatialContextFactory factory) {
super(ctx, factory);
//note: ctx.geometryFactory hasn't been set yet
useFloat = (factory.precisionModel.getType() == PrecisionModel.FLOATING_SINGLE);
}
@Override
protected double readDim(DataInput dataInput) throws IOException {
if (useFloat)
return dataInput.readFloat();
return super.readDim(dataInput);
}
@Override
protected void writeDim(DataOutput dataOutput, double v) throws IOException {
if (useFloat)
dataOutput.writeFloat((float) v);
else
super.writeDim(dataOutput, v);
}
@Override
protected byte typeForShape(Shape s) {
byte type = super.typeForShape(s);
if (type == 0) {
type = TYPE_GEOM;//handles everything
}
return type;
}
@Override
protected Shape readShapeByTypeIfSupported(final DataInput dataInput, byte type) throws IOException {
if (type != TYPE_GEOM)
return super.readShapeByTypeIfSupported(dataInput, type);
return readJtsGeom(dataInput);
}
@Override
protected boolean writeShapeByTypeIfSupported(DataOutput dataOutput, Shape s, byte type) throws IOException {
if (type != TYPE_GEOM)
return super.writeShapeByTypeIfSupported(dataOutput, s, type);
writeJtsGeom(dataOutput, s);
return true;
}
public Shape readJtsGeom(final DataInput dataInput) throws IOException {
JtsSpatialContext ctx = (JtsSpatialContext)super.ctx;
WKBReader reader = new WKBReader(ctx.getGeometryFactory());
try {
InStream inStream = new InStream() {//a strange JTS abstraction
boolean first = true;
@Override
public void read(byte[] buf) throws IOException {
if (first) {//we don't write JTS's leading BOM so synthesize reading it
if (buf.length != 1)
throw new IllegalStateException("Expected initial read of one byte, not: " + buf.length);
buf[0] = WKBConstants.wkbXDR;//0
first = false;
} else {
//TODO for performance, specialize for common array lengths: 1, 4, 8
dataInput.readFully(buf);
}
}
};
Geometry geom = reader.read(inStream);
//false: don't check for dateline-180 cross or multi-polygon overlaps; this won't happen
// once it gets written, and we're reading it now
return ctx.makeShape(geom, false, false);
} catch (ParseException ex) {
throw new InvalidShapeException("error reading WKT", ex);
}
}
public void writeJtsGeom(final DataOutput dataOutput, Shape s) throws IOException {
JtsSpatialContext ctx = (JtsSpatialContext)super.ctx;
Geometry geom = ctx.getGeometryFrom(s);//might even translate it
new WKBWriter().write(geom, new OutStream() {//a strange JTS abstraction
boolean first = true;
@Override
public void write(byte[] buf, int len) throws IOException {
if (first) {
first = false;
//skip byte order mark
if (len != 1 || buf[0] != WKBConstants.wkbXDR)//the default
throw new IllegalStateException("Unexpected WKB byte order mark");
return;
}
dataOutput.write(buf, 0, len);
}
});
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/io/jts/JtsGeoJSONReader.java 0000664 0000000 0000000 00000016235 12577056562 0030307 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 com.spatial4j.core.io.jts;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.io.GeoJSONReader;
import com.spatial4j.core.shape.Shape;
import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.impl.PackedCoordinateSequenceFactory;
import org.noggit.JSONParser;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class JtsGeoJSONReader extends GeoJSONReader {
protected final JtsSpatialContext ctx;
public JtsGeoJSONReader(JtsSpatialContext ctx, SpatialContextFactory factory) {
super(ctx, factory);
this.ctx = ctx;
}
// --------------------------------------------------------------
// Read GeoJSON
// --------------------------------------------------------------
public Coordinate readCoord(JSONParser parser) throws IOException, ParseException {
assert (parser.lastEvent() == JSONParser.ARRAY_START);
Coordinate coord = new Coordinate();
int idx = 0;
int evt = parser.nextEvent();
while (evt != JSONParser.EOF) {
switch (evt) {
case JSONParser.LONG:
case JSONParser.NUMBER:
case JSONParser.BIGNUMBER:
coord.setOrdinate(idx++, parser.getDouble());
break;
case JSONParser.ARRAY_END:
return coord;
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 coord;
}
public List readCoordList(JSONParser parser) throws IOException, ParseException {
assert (parser.lastEvent() == JSONParser.ARRAY_START);
List coords = new ArrayList();
int evt = parser.nextEvent();
while (evt != JSONParser.EOF) {
switch (evt) {
case JSONParser.ARRAY_START:
coords.add(readCoord(parser));
break;
case JSONParser.ARRAY_END:
return coords;
default:
throw new ParseException("Unexpected " + JSONParser.getEventString(evt),
(int) parser.getPosition());
}
evt = parser.nextEvent();
}
return coords;
}
@Override
protected Shape readPoint(JSONParser parser) throws IOException, ParseException {
assert (parser.lastEvent() == JSONParser.ARRAY_START);
Coordinate coord = readCoord(parser);
return ctx.makePoint(coord.x, coord.y);
}
@Override
protected Shape readPolygon(JSONParser parser) throws IOException, ParseException {
assert (parser.lastEvent() == JSONParser.ARRAY_START);
GeometryFactory gf = ctx.getGeometryFactory();
return ctx.makeShape(createPolygon(gf, readCoordinates(parser)));
}
@Override
protected Shape makeShapeFromCoords(String type, List coords) {
GeometryFactory gf = ctx.getGeometryFactory();
switch(type) {
case "Polygon":
Polygon polygon = createPolygon(gf, coords);
if (polygon.isRectangle()) {
return ctx.makeRectFromRectangularPoly(polygon);
} else {
return ctx.makeShapeFromGeometry(polygon);
}
case "MultiPoint":
return ctx.makeShapeFromGeometry(createMultiPoint(gf, coords));
case "MultiLineString":
return ctx.makeShapeFromGeometry(createMultiLineString(gf, coords));
case "MultiPolygon":
return ctx.makeShapeFromGeometry(createMultiPolygon(gf, coords));
}
return null;
}
// --------------------------------------------------------------
// FROM JEO.ORG:
// https://github.com/jeo/jeo/blob/master/core/src/main/java/org/jeo/geojson/parser/GeometryHandler.java#L111
// --------------------------------------------------------------
protected Point createPoint(GeometryFactory gf, List list) {
return gf.createPoint(coord(list));
}
protected LineString createLineString(GeometryFactory gf, List list) {
return gf.createLineString(coordseq(list));
}
protected Polygon createPolygon(GeometryFactory gf, List list) {
LinearRing shell = gf.createLinearRing(coordseq((List) ensureSize(list, 1).get(0)));
LinearRing[] holes = list.size() > 1 ? new LinearRing[list.size() - 1] : null;
for (int i = 1; i < list.size(); i++) {
holes[i - 1] = gf.createLinearRing(coordseq((List) list.get(i)));
}
return gf.createPolygon(shell, holes);
}
protected MultiPoint createMultiPoint(GeometryFactory gf, List list) {
return gf.createMultiPoint(coordseq(list));
}
protected MultiLineString createMultiLineString(GeometryFactory gf, List list) {
LineString[] lines = new LineString[ensureSize(list, 1).size()];
for (int i = 0; i < list.size(); i++) {
lines[i] = createLineString(gf, (List) list.get(i));
}
return gf.createMultiLineString(lines);
}
protected MultiPolygon createMultiPolygon(GeometryFactory gf, List list) {
Polygon[] polys = new Polygon[ensureSize(list, 1).size()];
for (int i = 0; i < list.size(); i++) {
polys[i] = createPolygon(gf, (List) list.get(i));
}
return gf.createMultiPolygon(polys);
}
protected GeometryCollection createGeometryCollection(GeometryFactory gf, List geoms) {
return gf.createGeometryCollection((Geometry[]) geoms.toArray(new Geometry[geoms.size()]));
}
protected Coordinate coord(List list) {
ensureSize(list, 2);
double x = number(list.get(0));
double y = number(list.get(1));
double z = list.size() > 2 ? number(list.get(2)) : Double.NaN;
Coordinate c = new Coordinate(x, y);
if (!Double.isNaN(z)) {
c.z = z;
}
return c;
}
protected CoordinateSequence coordseq(List list) {
ensureSize(list, 1);
int dim = ensureSize((List) list.get(0), 2).size();
CoordinateSequence seq =
PackedCoordinateSequenceFactory.DOUBLE_FACTORY.create(list.size(), dim);
for (int i = 0; i < list.size(); i++) {
List c = (List) list.get(i);
seq.setOrdinate(i, 0, number(c.get(0)));
seq.setOrdinate(i, 1, number(c.get(1)));
if (dim > 2) {
seq.setOrdinate(i, 2, number(c.get(2)));
}
}
return seq;
}
protected double number(Object obj) {
return ((Number) obj).doubleValue();
}
protected List ensureSize(List list, int size) {
if (list.size() < size) {
throw new IllegalArgumentException(String.format(Locale.ROOT,
"expected coordinate arary of size %d but is of size %d", size, list.size()));
}
return list;
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/io/jts/JtsGeoJSONWriter.java 0000664 0000000 0000000 00000013364 12577056562 0030361 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 com.spatial4j.core.io.jts;
import java.io.IOException;
import java.io.Writer;
import java.text.NumberFormat;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.io.GeoJSONWriter;
import com.spatial4j.core.io.LegacyShapeWriter;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.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.5/src/main/java/com/spatial4j/core/io/jts/JtsPolyshapeReader.java 0000664 0000000 0000000 00000007350 12577056562 0031045 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 com.spatial4j.core.io.jts;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.io.PolyshapeReader;
import com.spatial4j.core.io.PolyshapeWriter;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.spatial4j.core.shape.jts.JtsPoint;
import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.geom.impl.PackedCoordinateSequenceFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class JtsPolyshapeReader extends PolyshapeReader {
protected final JtsSpatialContext ctx;
public JtsPolyshapeReader(JtsSpatialContext ctx, SpatialContextFactory factory) {
super(ctx, factory);
this.ctx = ctx;
}
@Override
protected Shape makeCollection(List extends Shape> shapes) {
Class> last = null;
List geoms = new ArrayList<>(shapes.size());
for(Shape s : shapes) {
if(last!=null && last!=s.getClass()) {
return super.makeCollection(shapes);
}
if(s instanceof JtsGeometry) {
geoms.add(((JtsGeometry)s).getGeom());
}
else if(s instanceof JtsPoint) {
geoms.add(((JtsPoint)s).getGeom());
}
else {
return super.makeCollection(shapes);
}
last = s.getClass();
}
Geometry result = ctx.getGeometryFactory().buildGeometry(geoms);
if(result.getClass().equals(GeometryCollection.class)) {
return super.makeCollection(shapes);
}
// *not* calling makeShapeFromGeometry() since the underlying geometries here have
// already been converted to shapes via that method (or equivalent).
return ctx.makeShape(result);
}
// --------------------------------------------------------------
// Read GeoJSON
// --------------------------------------------------------------
protected CoordinateSequence coordseq(List list) {
CoordinateSequence seq =
PackedCoordinateSequenceFactory.DOUBLE_FACTORY.create(list.size(), 2);
for (int i = 0; i < list.size(); i++) {
double[] point = list.get(i);
seq.setOrdinate(i, 0, point[0]);
seq.setOrdinate(i, 1, point[1]);
}
return seq;
}
@Override
protected Shape readPolygon(XReader reader) throws IOException {
GeometryFactory gf = ctx.getGeometryFactory();
List outer = reader.readPoints();
LinearRing shell = gf.createLinearRing(coordseq(outer));
LinearRing[] holes = null;
if(!reader.isDone() && reader.peek()==PolyshapeWriter.KEY_ARG_START) {
List list = new ArrayList();
while(reader.isEvent() && reader.peek()==PolyshapeWriter.KEY_ARG_START) {
reader.readKey(); // eat the event;
list.add(gf.createLinearRing(coordseq(reader.readPoints())));
}
holes = list.toArray(new LinearRing[list.size()]);
}
return ctx.makeShapeFromGeometry(gf.createPolygon(shell, holes));
}
} spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/io/jts/JtsPolyshapeWriter.java 0000664 0000000 0000000 00000010173 12577056562 0031114 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 com.spatial4j.core.io.jts;
import java.io.IOException;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.io.PolyshapeWriter;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.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.5/src/main/java/com/spatial4j/core/io/jts/JtsWKTReader.java 0000664 0000000 0000000 00000014224 12577056562 0027544 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* Copyright (c) 2015 ElasticSearch and MITRE
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Apache License, Version 2.0 which
* accompanies this distribution and is available at
* http://www.apache.org/licenses/LICENSE-2.0.txt
******************************************************************************/
// A derivative of commit 14bc4dee08355048d6a94e33834b919a3999a06e
// at https://github.com/chrismale/elasticsearch
package com.spatial4j.core.io.jts;
import com.spatial4j.core.context.jts.DatelineRule;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.io.WKTReader;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Shape;
import com.vividsolutions.jts.geom.*;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Extends {@link com.spatial4j.core.io.WKTReader} adding support for polygons, using JTS.
*/
public class JtsWKTReader extends WKTReader {
protected final JtsSpatialContext ctx;
public JtsWKTReader(JtsSpatialContext ctx, JtsSpatialContextFactory factory) {
super(ctx, factory);
this.ctx = ctx;
}
/** @see DatelineRule */
public DatelineRule getDatelineRule() {
return ctx.getDatelineRule();
}
@Override
protected Shape parseShapeByType(WKTReader.State state, String shapeType) throws ParseException {
if (shapeType.equalsIgnoreCase("POLYGON")) {
return parsePolygonShape(state);
} else if (shapeType.equalsIgnoreCase("MULTIPOLYGON")) {
return parseMulitPolygonShape(state);
}
return super.parseShapeByType(state, shapeType);
}
/**
* Bypasses {@link JtsSpatialContext#makeLineString(java.util.List)} so that we can more
* efficiently get the LineString without creating a {@code List}.
*/
@Override
protected Shape parseLineStringShape(WKTReader.State state) throws ParseException {
if (!ctx.useJtsLineString())
return super.parseLineStringShape(state);
if (state.nextIfEmptyAndSkipZM())
return ctx.makeLineString(Collections.emptyList());
GeometryFactory geometryFactory = ctx.getGeometryFactory();
Coordinate[] coordinates = coordinateSequence(state);
return ctx.makeShapeFromGeometry(geometryFactory.createLineString(coordinates));
}
/**
* Parses a POLYGON shape from the raw string. It might return a
* {@link com.spatial4j.core.shape.Rectangle} if the polygon is one.
*
*
* coordinateSequenceList
*
*/
protected Shape parsePolygonShape(WKTReader.State state) throws ParseException {
Geometry geometry;
if (state.nextIfEmptyAndSkipZM()) {
GeometryFactory geometryFactory = ctx.getGeometryFactory();
geometry =
geometryFactory
.createPolygon(geometryFactory.createLinearRing(new Coordinate[] {}), null);
} else {
geometry = polygon(state);
if (geometry.isRectangle()) {
return ctx.makeRectFromRectangularPoly(geometry);
}
}
return ctx.makeShapeFromGeometry(geometry);
}
/**
* Reads a polygon, returning a JTS polygon.
*/
protected Polygon polygon(WKTReader.State state) throws ParseException {
GeometryFactory geometryFactory = ctx.getGeometryFactory();
List coordinateSequenceList = coordinateSequenceList(state);
LinearRing shell = geometryFactory.createLinearRing(coordinateSequenceList.get(0));
LinearRing[] holes = null;
if (coordinateSequenceList.size() > 1) {
holes = new LinearRing[coordinateSequenceList.size() - 1];
for (int i = 1; i < coordinateSequenceList.size(); i++) {
holes[i - 1] = geometryFactory.createLinearRing(coordinateSequenceList.get(i));
}
}
return geometryFactory.createPolygon(shell, holes);
}
/**
* Parses a MULTIPOLYGON shape from the raw string.
*
*
* '(' polygon (',' polygon )* ')'
*
*/
protected Shape parseMulitPolygonShape(WKTReader.State state) throws ParseException {
if (state.nextIfEmptyAndSkipZM())
return ctx.makeCollection(Collections.EMPTY_LIST);
List polygons = new ArrayList();
state.nextExpect('(');
do {
polygons.add(parsePolygonShape(state));
} while (state.nextIf(','));
state.nextExpect(')');
return ctx.makeCollection(polygons);
}
/**
* Reads a list of JTS Coordinate sequences from the current position.
*
*
*/
protected List coordinateSequenceList(WKTReader.State state) throws ParseException {
List sequenceList = new ArrayList();
state.nextExpect('(');
do {
sequenceList.add(coordinateSequence(state));
} while (state.nextIf(','));
state.nextExpect(')');
return sequenceList;
}
/**
* Reads a JTS Coordinate sequence from the current position.
*
*
* '(' coordinate (',' coordinate )* ')'
*
*/
protected Coordinate[] coordinateSequence(WKTReader.State state) throws ParseException {
List sequence = new ArrayList();
state.nextExpect('(');
do {
sequence.add(coordinate(state));
} while (state.nextIf(','));
state.nextExpect(')');
return sequence.toArray(new Coordinate[sequence.size()]);
}
/**
* Reads a {@link com.vividsolutions.jts.geom.Coordinate} from the current position. It's akin to
* {@link #point(com.spatial4j.core.io.WKTReader.State)} but for a JTS Coordinate. Only the first
* 2 numbers are parsed; any remaining are ignored.
*/
protected Coordinate coordinate(WKTReader.State state) throws ParseException {
double x = ctx.normX(state.nextDouble());
ctx.verifyX(x);
double y = ctx.normY(state.nextDouble());
ctx.verifyY(y);
state.skipNextDoubles();
return new Coordinate(x, y);
}
@Override
protected double normDist(double v) {
return ctx.getGeometryFactory().getPrecisionModel().makePrecise(v);
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/io/jts/JtsWKTReaderShapeParser.java 0000664 0000000 0000000 00000010305 12577056562 0031676 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 com.spatial4j.core.io.jts;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.jts.JtsPoint;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.CoordinateSequenceFilter;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.WKTReader;
import java.text.ParseException;
/**
* This is an extension of {@link JtsWKTReader} that processes the entire
* string with JTS's {@link com.vividsolutions.jts.io.WKTReader}. Some differences:
*
*
No support for ENVELOPE and BUFFER
*
MULTI* shapes use JTS's {@link com.vividsolutions.jts.geom.GeometryCollection} subclasses,
* not {@link com.spatial4j.core.shape.ShapeCollection}
*
'Z' coordinates are saved into the geometry
*
*
*/
public class JtsWKTReaderShapeParser extends JtsWKTReader {
//Note: Historically, the code here originated from the defunct JtsShapeReadWriter.
public JtsWKTReaderShapeParser(JtsSpatialContext ctx, JtsSpatialContextFactory factory) {
super(ctx, factory);
}
@Override
public Shape parseIfSupported(String wktString) throws ParseException {
return parseIfSupported(wktString, new WKTReader(ctx.getGeometryFactory()));
}
/**
* Reads WKT from the {@code str} via JTS's {@link com.vividsolutions.jts.io.WKTReader}.
* @param str
* @param reader
new WKTReader(ctx.getGeometryFactory()))
* @return Non-Null
*/
protected Shape parseIfSupported(String str, WKTReader reader) throws ParseException {
try {
Geometry geom = reader.read(str);
//Normalizes & verifies coordinates
checkCoordinates(geom);
if (geom instanceof com.vividsolutions.jts.geom.Point) {
com.vividsolutions.jts.geom.Point ptGeom = (com.vividsolutions.jts.geom.Point) geom;
if (ctx.useJtsPoint())
return new JtsPoint(ptGeom, ctx);
else
return ctx.makePoint(ptGeom.getX(), ptGeom.getY());
} else if (geom.isRectangle()) {
return super.ctx.makeRectFromRectangularPoly(geom);
} else {
return super.ctx.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.5/src/main/java/com/spatial4j/core/io/jts/JtsWKTWriter.java 0000664 0000000 0000000 00000002126 12577056562 0027614 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 com.spatial4j.core.io.jts;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.io.WKTWriter;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.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.5/src/main/java/com/spatial4j/core/io/package-info.java 0000664 0000000 0000000 00000001037 12577056562 0027015 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 com.spatial4j.core.io; spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/package-info.java 0000664 0000000 0000000 00000001106 12577056562 0026403 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 com.spatial4j.core;
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/shape/ 0000775 0000000 0000000 00000000000 12577056562 0024316 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/shape/BaseShape.java 0000664 0000000 0000000 00000001365 12577056562 0027021 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 com.spatial4j.core.shape;
import com.spatial4j.core.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.5/src/main/java/com/spatial4j/core/shape/Circle.java 0000664 0000000 0000000 00000002105 12577056562 0026360 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 com.spatial4j.core.shape;
/**
* A circle, also known as a point-radius since that is what it is comprised of.
*/
public interface Circle extends Shape {
/**
* Expert: Resets the state of this shape given the arguments. This is a
* performance feature to avoid excessive Shape object allocation as well as
* some argument error checking. Mutable shapes is error-prone so use with
* care.
*/
void reset(double x, double y, double radiusDEG);
/**
* The distance from the point's center to its edge, measured in the same
* units as x & y (e.g. degrees if WGS84).
*/
double getRadius();
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/shape/Point.java 0000664 0000000 0000000 00000002040 12577056562 0026246 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 com.spatial4j.core.shape;
/**
* A Point with X & Y coordinates.
*/
public interface Point extends Shape {
/**
* Expert: Resets the state of this shape given the arguments. This is a
* performance feature to avoid excessive Shape object allocation as well as
* some argument error checking. Mutable shapes is error-prone so use with
* care.
*/
public void reset(double x, double y);
/** The X coordinate, or Longitude in geospatial contexts. */
public double getX();
/** The Y coordinate, or Latitude in geospatial contexts. */
public double getY();
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/shape/Rectangle.java 0000664 0000000 0000000 00000004406 12577056562 0027071 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 com.spatial4j.core.shape;
/**
* A rectangle aligned with the axis (i.e. it is not at an angle).
*
* In geospatial contexts, it may cross the international date line (-180
* longitude) if {@link #getCrossesDateLine()} however it cannot pass the poles
* although it may span the globe. It spans the globe if the X coordinate
* (Longitude) goes from -180 to 180 as seen from {@link #getMinX()} and {@link
* #getMaxX()}.
*/
public interface Rectangle extends Shape {
/**
* Expert: Resets the state of this shape given the arguments. This is a
* performance feature to avoid excessive Shape object allocation as well as
* some argument error checking. Mutable shapes is error-prone so use with
* care.
*/
public void reset(double minX, double maxX, double minY, double maxY);
/**
* The width. In geospatial contexts, this is generally in degrees longitude
* and is aware of the international dateline. It will always be >= 0.
*/
public double getWidth();
/**
* The height. In geospatial contexts, this is in degrees latitude. It will
* always be >= 0.
*/
public double getHeight();
/** The left edge of the X coordinate. */
public double getMinX();
/** The bottom edge of the Y coordinate. */
public double getMinY();
/** The right edge of the X coordinate. */
public double getMaxX();
/** The top edge of the Y coordinate. */
public double getMaxY();
/** Only meaningful for geospatial contexts. */
public boolean getCrossesDateLine();
/**
* A specialization of {@link Shape#relate(Shape)}
* for a vertical line.
*/
public SpatialRelation relateYRange(double minY, double maxY);
/**
* A specialization of {@link Shape#relate(Shape)}
* for a horizontal line.
*/
public SpatialRelation relateXRange(double minX, double maxX);
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/shape/Shape.java 0000664 0000000 0000000 00000007362 12577056562 0026231 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 com.spatial4j.core.shape;
import com.spatial4j.core.context.SpatialContext;
/**
* The base interface defining a geometric shape. Shape instances should be
* instantiated via one of the create* methods on a {@link SpatialContext} or
* by reading WKT which calls those methods; they should not be
* created directly.
*
* Shapes are generally immutable and thread-safe. If a particular shape has a
* reset(...) method then its use means the shape is actually
* mutable. Mutating shape state is considered expert and should be done with care.
*/
public interface Shape {
/**
* Describe the relationship between the two objects. For example
*
*
this is WITHIN other
*
this CONTAINS other
*
this is DISJOINT other
*
this INTERSECTS other
*
* Note that a Shape implementation may choose to return INTERSECTS when the
* true answer is WITHIN or CONTAINS for performance reasons. If a shape does
* this then it must document when it does. Ideally the shape will not
* do this approximation in all circumstances, just sometimes.
*
* If the shapes are equal then the result is CONTAINS (preferred) or WITHIN.
*/
SpatialRelation relate(Shape other);
/**
* Get the bounding box for this Shape. This means the shape is within the
* bounding box and that it touches each side of the rectangle.
*
* Postcondition: this.getBoundingBox().relate(this) == CONTAINS
*/
Rectangle getBoundingBox();
/**
* Does the shape have area? This will be false for points and lines. It will
* also be false for shapes that normally have area but are constructed in a
* degenerate case as to not have area (e.g. a circle with 0 radius or
* rectangle with no height or no width).
*/
boolean hasArea();
/**
* Calculates the area of the shape, in square-degrees. If ctx is null then
* simple Euclidean calculations will be used. This figure can be an
* estimate.
*/
double getArea(SpatialContext ctx);
/**
* Returns the center point of this shape. This is usually the same as
* getBoundingBox().getCenter() but it doesn't have to be.
*
* Postcondition: this.relate(this.getCenter()) == CONTAINS
*/
Point getCenter();
/**
* Returns a buffered version of this shape. The buffer is usually a
* rounded-corner buffer, although some shapes might buffer differently. This
* is an optional operation.
*
*
* @param distance
* @return Not null, and the returned shape should contain the current shape.
*/
Shape getBuffered(double distance, SpatialContext ctx);
/**
* Shapes can be "empty", which is to say it exists nowhere. The underlying coordinates are
* typically NaN.
*/
boolean isEmpty();
/** The sub-classes of Shape generally implement the
* same contract for {@link Object#equals(Object)} and {@link Object#hashCode()}
* amongst the same sub-interface type. This means, for example, that multiple
* Point implementations of different classes are equal if they share the same x
* & y. */
@Override
public boolean equals(Object other);
/**
* Get the SpatialContext that created the Shape
*/
public SpatialContext getContext();
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/shape/ShapeCollection.java 0000664 0000000 0000000 00000016210 12577056562 0030235 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 com.spatial4j.core.shape;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.impl.BBoxCalculator;
import java.util.*;
import static com.spatial4j.core.shape.SpatialRelation.CONTAINS;
import static com.spatial4j.core.shape.SpatialRelation.INTERSECTS;
/**
* A collection of Shape objects, analogous to an OGC GeometryCollection. The
* implementation demands a List (with random access) so that the order can be
* retained if an application requires it, although logically it's treated as an
* unordered Set, mostly.
*
* Ideally, {@link #relate(Shape)} should return the same result no matter what
* the shape order is, although the default implementation can be order
* dependent when the shapes overlap; see {@link #relateContainsShortCircuits()}.
* To improve performance slightly, the caller could order the shapes by
* largest first so that relate() will have a greater chance of
* short-circuit'ing sooner. As the Shape contract states; it may return
* intersects when the best answer is actually contains or within. If any shape
* intersects the provided shape then that is the answer.
*
* This implementation is not optimized for a large number of shapes; relate is
* O(N). A more sophisticated implementation might do an R-Tree based on
* bbox'es, for example.
*/
public class ShapeCollection extends AbstractList implements Shape {
protected final 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)
* @param ctx
*/
public ShapeCollection(List shapes, SpatialContext ctx) {
if (!(shapes instanceof RandomAccess))
throw new IllegalArgumentException("Shapes arg must implement RandomAccess: "+shapes.getClass());
this.shapes = shapes;
this.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.5/src/main/java/com/spatial4j/core/shape/SpatialRelation.java 0000664 0000000 0000000 00000010473 12577056562 0030261 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 com.spatial4j.core.shape;
/**
* The set of spatial relationships. Naming is somewhat consistent with OGC spec
* conventions as seen in SQL/MM and others.
*
* There is no equality case. If two Shape instances are equal then the result
* might be CONTAINS (preferred) or WITHIN. Client logic may have to be aware
* of this edge condition; Spatial4j testing certainly does.
*
* The "CONTAINS" and "WITHIN" wording here is inconsistent with OGC; these here map to OGC
* "COVERS" and "COVERED BY", respectively. The distinction is in the boundaries; in Spatial4j
* there is no boundary distinction -- boundaries are part of the shape as if it was an "interior",
* with respect to OGC's terminology.
*/
public enum SpatialRelation {
//see http://docs.geotools.org/latest/userguide/library/jts/dim9.html#preparedgeometry
/**
* The shape is within the target geometry. It's the converse of {@link #CONTAINS}.
* Boundaries of shapes count too. OGC specs refer to this relation as "COVERED BY";
* WITHIN is differentiated there by not including boundaries.
*/
WITHIN,
/**
* The shape contains the target geometry. It's the converse of {@link #WITHIN}.
* Boundaries of shapes count too. OGC specs refer to this relation as "COVERS";
* CONTAINS is differentiated there by not including boundaries.
*/
CONTAINS,
/**
* The shape shares no point in common with the target shape.
*/
DISJOINT,
/**
* The shape shares some points/overlap with the target shape, and the relation is
* not more specifically {@link #WITHIN} or {@link #CONTAINS}.
*/
INTERSECTS;
//Don't have these: TOUCHES, CROSSES, OVERLAPS, nor distinction between CONTAINS/COVERS
/**
* Given the result of shapeA.relate(shapeB), transposing that
* result should yield the result of shapeB.relate(shapeA). There
* is a corner case is when the shapes are equal, in which case actually
* flipping the relate() call will result in the same value -- either CONTAINS
* or WITHIN; this method can't possible check for that so the caller might
* have to.
*/
public SpatialRelation transpose() {
switch(this) {
case CONTAINS: return SpatialRelation.WITHIN;
case WITHIN: return SpatialRelation.CONTAINS;
default: return this;
}
}
/**
* If you were to call aShape.relate(bShape) and aShape.relate(cShape), you
* could call this to merge the intersect results as if bShape & cShape were
* combined into {@link ShapeCollection}.
*/
public SpatialRelation combine(SpatialRelation other) {
// You can think of this algorithm as a state transition / automata.
// 1. The answer must be the same no matter what the order is.
// 2. If any INTERSECTS, then the result is INTERSECTS (done).
// 3. A DISJOINT + WITHIN == INTERSECTS (done).
// 4. A DISJOINT + CONTAINS == CONTAINS.
// 5. A CONTAINS + WITHIN == INTERSECTS (done). (weird scenario)
// 6. X + X == X.
if (other == this)
return this;
if (this == DISJOINT && other == CONTAINS
|| this == CONTAINS && other == DISJOINT)
return CONTAINS;
return INTERSECTS;
}
/** Not DISJOINT, i.e. there is some sort of intersection. */
public boolean intersects() {
return this != DISJOINT;
}
/**
* If aShape.relate(bShape) is r, then r.inverse()
* is inverse(aShape).relate(bShape) whereas
* inverse(shape) is theoretically the opposite area covered by a
* shape, i.e. everywhere but where the shape is.
*
* Note that it's not commutative! WITHIN.inverse().inverse() !=
* WITHIN.
*/
public SpatialRelation inverse() {
switch(this) {
case DISJOINT: return CONTAINS;
case CONTAINS: return DISJOINT;
case WITHIN: return INTERSECTS;//not commutative!
}
return INTERSECTS;
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/shape/impl/ 0000775 0000000 0000000 00000000000 12577056562 0025257 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/shape/impl/BBoxCalculator.java 0000664 0000000 0000000 00000016442 12577056562 0030775 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 com.spatial4j.core.shape.impl;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.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.5/src/main/java/com/spatial4j/core/shape/impl/BufferedLine.java 0000664 0000000 0000000 00000017620 12577056562 0030462 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 com.spatial4j.core.shape.impl;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.shape.BaseShape;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
import static com.spatial4j.core.shape.SpatialRelation.CONTAINS;
import static com.spatial4j.core.shape.SpatialRelation.DISJOINT;
import static com.spatial4j.core.shape.SpatialRelation.INTERSECTS;
import static com.spatial4j.core.shape.SpatialRelation.WITHIN;
/**
* INTERNAL: A line between two points with a buffer distance extending in every direction. By
* contrast, an un-buffered line covers no area and as such is extremely unlikely to intersect with
* a point. BufferedLine isn't yet aware of geodesics (e.g. the dateline); it operates in Euclidean
* space.
*/
public class BufferedLine 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
* @param ctx
*/
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.5/src/main/java/com/spatial4j/core/shape/impl/BufferedLineString.java 0000664 0000000 0000000 00000012544 12577056562 0031651 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 com.spatial4j.core.shape.impl;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.BaseShape;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.ShapeCollection;
import com.spatial4j.core.shape.SpatialRelation;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A BufferedLineString is a collection of {@link com.spatial4j.core.shape.impl.BufferedLine} shapes,
* resulting in what some call a "Track" or "Polyline" (ESRI terminology).
* The buffer can be 0. Note that BufferedLine isn't yet aware of geodesics (e.g. the dateline).
*/
public class BufferedLineString 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(com.spatial4j.core.shape.Point,
* com.spatial4j.core.shape.Point, double)}.
* If true then the buffer for each segment
* is computed.
* @param ctx
*/
public BufferedLineString(List points, double buf, boolean expandBufForLongitudeSkew,
SpatialContext ctx) {
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.5/src/main/java/com/spatial4j/core/shape/impl/CircleImpl.java 0000664 0000000 0000000 00000021446 12577056562 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
******************************************************************************/
package com.spatial4j.core.shape.impl;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.BaseShape;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
/**
* A circle, also known as a point-radius, based on a {@link
* com.spatial4j.core.distance.DistanceCalculator} which does all the work. This
* implementation should work for both cartesian 2D and geodetic sphere
* surfaces.
*/
public class CircleImpl 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 dateline.
*/
@Override
public Rectangle getBoundingBox() {
return enclosingBox;
}
@Override
public SpatialRelation relate(Shape other) {
//This shortcut was problematic in testing due to distinctions of CONTAINS/WITHIN for no-area shapes (lines, points).
// if (distance == 0) {
// return point.relate(other,ctx).intersects() ? SpatialRelation.WITHIN : SpatialRelation.DISJOINT;
// }
if (isEmpty() || other.isEmpty())
return SpatialRelation.DISJOINT;
if (other instanceof Point) {
return relate((Point) other);
}
if (other instanceof Rectangle) {
return relate((Rectangle) other);
}
if (other instanceof Circle) {
return relate((Circle) other);
}
return other.relate(this).transpose();
}
public SpatialRelation relate(Point point) {
return contains(point.getX(),point.getY()) ? SpatialRelation.CONTAINS : SpatialRelation.DISJOINT;
}
public SpatialRelation relate(Rectangle r) {
//Note: Surprisingly complicated!
//--We start by leveraging the fact we have a calculated bbox that is "cheaper" than use of DistanceCalculator.
final SpatialRelation bboxSect = enclosingBox.relate(r);
if (bboxSect == SpatialRelation.DISJOINT || bboxSect == SpatialRelation.WITHIN)
return bboxSect;
else if (bboxSect == SpatialRelation.CONTAINS && enclosingBox.equals(r))//nasty identity edge-case
return SpatialRelation.WITHIN;
//bboxSect is INTERSECTS or CONTAINS
//The result can be DISJOINT, CONTAINS, or INTERSECTS (not WITHIN)
return relateRectanglePhase2(r, bboxSect);
}
protected SpatialRelation relateRectanglePhase2(final Rectangle r, SpatialRelation bboxSect) {
// DOES NOT WORK WITH GEO CROSSING DATELINE OR WORLD-WRAP. Other methods handle such cases.
//At this point, the only thing we are certain of is that circle is *NOT* WITHIN r, since the
// bounding box of a circle MUST be within r for the circle to be within r.
//Quickly determine if they are DISJOINT or not.
// Find the closest & farthest point to the circle within the rectangle
final double closestX, farthestX;
final double xAxis = getXAxis();
if (xAxis < r.getMinX()) {
closestX = r.getMinX();
farthestX = r.getMaxX();
} else if (xAxis > r.getMaxX()) {
closestX = r.getMaxX();
farthestX = r.getMinX();
} else {
closestX = xAxis; //we don't really use this value but to check this condition
farthestX = r.getMaxX() - xAxis > xAxis - r.getMinX() ? r.getMaxX() : r.getMinX();
}
final double closestY, farthestY;
final double yAxis = getYAxis();
if (yAxis < r.getMinY()) {
closestY = r.getMinY();
farthestY = r.getMaxY();
} else if (yAxis > r.getMaxY()) {
closestY = r.getMaxY();
farthestY = r.getMinY();
} else {
closestY = yAxis; //we don't really use this value but to check this condition
farthestY = r.getMaxY() - yAxis > yAxis - r.getMinY() ? r.getMaxY() : r.getMinY();
}
//If r doesn't overlap an axis, then could be disjoint. Test closestXY
if (xAxis != closestX && yAxis != closestY) {
if (!contains(closestX, closestY))
return SpatialRelation.DISJOINT;
} // else CAN'T be disjoint if spans axis because earlier bbox check ruled that out
//Now, we know it's *NOT* DISJOINT and it's *NOT* WITHIN either.
// Does circle CONTAINS r or simply intersect it?
//If circle contains r, then its bbox MUST also CONTAIN r.
if (bboxSect != SpatialRelation.CONTAINS)
return SpatialRelation.INTERSECTS;
//If the farthest point of r away from the center of the circle is contained, then all of r is
// contained.
if (!contains(farthestX, farthestY))
return SpatialRelation.INTERSECTS;
//geodetic detection of farthest Y when rect crosses x axis can't be reliably determined, so
// check other corner too, which might actually be farthest
if (point.getY() != getYAxis()) {//geodetic
if (yAxis == closestY) {//r crosses north to south over x axis (confusing)
double otherY = (farthestY == r.getMaxY() ? r.getMinY() : r.getMaxY());
if (!contains(farthestX, otherY))
return SpatialRelation.INTERSECTS;
}
}
return SpatialRelation.CONTAINS;
}
/**
* The Y coordinate of where the circle axis intersect.
*/
protected double getYAxis() {
return point.getY();
}
/**
* The X coordinate of where the circle axis intersect.
*/
protected double getXAxis() {
return point.getX();
}
public SpatialRelation relate(Circle circle) {
double crossDist = ctx.getDistCalc().distance(point, circle.getCenter());
double aDist = radiusDEG, bDist = circle.getRadius();
if (crossDist > aDist + bDist)
return SpatialRelation.DISJOINT;
if (crossDist < aDist && crossDist + bDist <= aDist)
return SpatialRelation.CONTAINS;
if (crossDist < bDist && crossDist + aDist <= bDist)
return SpatialRelation.WITHIN;
return SpatialRelation.INTERSECTS;
}
@Override
public String toString() {
return "Circle(" + point + ", d=" + radiusDEG + "°)";
}
@Override
public boolean equals(Object obj) {
return equals(this,obj);
}
/**
* All {@link Circle} implementations should use this definition of {@link Object#equals(Object)}.
*/
public static boolean equals(Circle thiz, Object o) {
assert thiz != null;
if (thiz == o) return true;
if (!(o instanceof Circle)) return false;
Circle circle = (Circle) o;
if (!thiz.getCenter().equals(circle.getCenter())) return false;
if (Double.compare(circle.getRadius(), thiz.getRadius()) != 0) return false;
return true;
}
@Override
public int hashCode() {
return hashCode(this);
}
/**
* All {@link Circle} implementations should use this definition of {@link Object#hashCode()}.
*/
public static int hashCode(Circle thiz) {
int result;
long temp;
result = thiz.getCenter().hashCode();
temp = thiz.getRadius() != +0.0d ? Double.doubleToLongBits(thiz.getRadius()) : 0L;
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/shape/impl/GeoCircle.java 0000664 0000000 0000000 00000021112 12577056562 0027753 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 com.spatial4j.core.shape.impl;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.SpatialRelation;
import java.util.Formatter;
import java.util.Locale;
/**
* A circle as it exists on the surface of a sphere.
*/
public class GeoCircle extends CircleImpl {
private GeoCircle inverseCircle;//when distance reaches > 1/2 way around the world, cache the inverse.
private double horizAxisY;//see getYAxis
public GeoCircle(Point p, double radiusDEG, SpatialContext ctx) {
super(p, radiusDEG, ctx);
assert ctx.isGeo();
init();
}
@Override
public void reset(double x, double y, double radiusDEG) {
super.reset(x, y, radiusDEG);
init();
}
private void init() {
if (radiusDEG > 90) {
//--spans more than half the globe
assert enclosingBox.getWidth() == 360;
double backDistDEG = 180 - radiusDEG;
if (backDistDEG > 0) {
double backRadius = 180 - radiusDEG;
double backX = DistanceUtils.normLonDEG(getCenter().getX() + 180);
double backY = DistanceUtils.normLatDEG(getCenter().getY() + 180);
//Shrink inverseCircle as small as possible to avoid accidental overlap.
// Note that this is tricky business to come up with a value small enough
// but not too small or else numerical conditioning issues become a problem.
backRadius -= Math.max(Math.ulp(Math.abs(backY)+backRadius), Math.ulp(Math.abs(backX)+backRadius));
if (inverseCircle != null) {
inverseCircle.reset(backX, backY, backRadius);
} else {
inverseCircle = new GeoCircle(ctx.makePoint(backX, backY), backRadius, ctx);
}
} else {
inverseCircle = null;//whole globe
}
horizAxisY = getCenter().getY();//although probably not used
} else {
inverseCircle = null;
double _horizAxisY = ctx.getDistCalc().calcBoxByDistFromPt_yHorizAxisDEG(getCenter(), radiusDEG, ctx);
//some rare numeric conditioning cases can cause this to be barely beyond the box
if (_horizAxisY > enclosingBox.getMaxY()) {
horizAxisY = enclosingBox.getMaxY();
} else if (_horizAxisY < enclosingBox.getMinY()) {
horizAxisY = enclosingBox.getMinY();
} else {
horizAxisY = _horizAxisY;
}
//assert enclosingBox.relate_yRange(horizAxis,horizAxis,ctx).intersects();
}
}
@Override
protected double getYAxis() {
return horizAxisY;
}
/**
* Called after bounding box is intersected.
* @param bboxSect INTERSECTS or CONTAINS from enclosingBox's intersection
* @return DISJOINT, CONTAINS, or INTERSECTS (not WITHIN)
*/
@Override
protected SpatialRelation relateRectanglePhase2(Rectangle r, SpatialRelation bboxSect) {
if (inverseCircle != null) {
return inverseCircle.relate(r).inverse();
}
//if a pole is wrapped, we have a separate algorithm
if (enclosingBox.getWidth() == 360) {
return relateRectangleCircleWrapsPole(r, ctx);
}
//This is an optimization path for when there are no dateline or pole issues.
if (!enclosingBox.getCrossesDateLine() && !r.getCrossesDateLine()) {
return super.relateRectanglePhase2(r, bboxSect);
}
//Rectangle wraps around the world longitudinally creating a solid band; there are no corners to test intersection
if (r.getWidth() == 360) {
return SpatialRelation.INTERSECTS;
}
//do quick check to see if all corners are within this circle for CONTAINS
int cornersIntersect = numCornersIntersect(r);
if (cornersIntersect == 4) {
//ensure r's x axis is within c's. If it doesn't, r sneaks around the globe to touch the other side (intersect).
SpatialRelation xIntersect = r.relateXRange(enclosingBox.getMinX(), enclosingBox.getMaxX());
if (xIntersect == SpatialRelation.WITHIN)
return SpatialRelation.CONTAINS;
return SpatialRelation.INTERSECTS;
}
//INTERSECT or DISJOINT ?
if (cornersIntersect > 0)
return SpatialRelation.INTERSECTS;
//Now we check if one of the axis of the circle intersect with r. If so we have
// intersection.
/* x axis intersects */
if ( r.relateYRange(getYAxis(), getYAxis()).intersects() // at y vertical
&& r.relateXRange(enclosingBox.getMinX(), enclosingBox.getMaxX()).intersects() )
return SpatialRelation.INTERSECTS;
/* y axis intersects */
if (r.relateXRange(getXAxis(), getXAxis()).intersects()) { // at x horizontal
double yTop = getCenter().getY()+ radiusDEG;
assert yTop <= 90;
double yBot = getCenter().getY()- radiusDEG;
assert yBot >= -90;
if (r.relateYRange(yBot, yTop).intersects())//back bottom
return SpatialRelation.INTERSECTS;
}
return SpatialRelation.DISJOINT;
}
private SpatialRelation relateRectangleCircleWrapsPole(Rectangle r, SpatialContext ctx) {
//This method handles the case where the circle wraps ONE pole, but not both. For both,
// there is the inverseCircle case handled before now. The only exception is for the case where
// the circle covers the entire globe, and we'll check that first.
if (radiusDEG == 180)//whole globe
return SpatialRelation.CONTAINS;
//Check if r is within the pole wrap region:
double yTop = getCenter().getY() + radiusDEG;
if (yTop > 90) {
double yTopOverlap = yTop - 90;
assert yTopOverlap <= 90;
if (r.getMinY() >= 90 - yTopOverlap)
return SpatialRelation.CONTAINS;
} else {
double yBot = point.getY() - radiusDEG;
if (yBot < -90) {
double yBotOverlap = -90 - yBot;
assert yBotOverlap <= 90;
if (r.getMaxY() <= -90 + yBotOverlap)
return SpatialRelation.CONTAINS;
} else {
//This point is probably not reachable ??
assert yTop == 90 || yBot == -90;//we simply touch a pole
//continue
}
}
//If there are no corners to check intersection because r wraps completely...
if (r.getWidth() == 360)
return SpatialRelation.INTERSECTS;
//Check corners:
int cornersIntersect = numCornersIntersect(r);
// (It might be possible to reduce contains() calls within nCI() to exactly two, but this intersection
// code is complicated enough as it is.)
double frontX = getCenter().getX();
if (cornersIntersect == 4) {//all
double backX = frontX <= 0 ? frontX + 180 : frontX - 180;
if (r.relateXRange(backX, backX).intersects())
return SpatialRelation.INTERSECTS;
else
return SpatialRelation.CONTAINS;
} else if (cornersIntersect == 0) {//none
if (r.relateXRange(frontX, frontX).intersects())
return SpatialRelation.INTERSECTS;
else
return SpatialRelation.DISJOINT;
} else//partial
return SpatialRelation.INTERSECTS;
}
/** Returns either 0 for none, 1 for some, or 4 for all. */
private int numCornersIntersect(Rectangle r) {
//We play some logic games to avoid calling contains() which can be expensive.
boolean bool;//if true then all corners intersect, if false then no corners intersect
// for partial, we exit early with 1 and ignore bool.
bool = (contains(r.getMinX(),r.getMinY()));
if (contains(r.getMinX(),r.getMaxY())) {
if (!bool)
return 1;//partial
} else {
if (bool)
return 1;//partial
}
if (contains(r.getMaxX(),r.getMinY())) {
if (!bool)
return 1;//partial
} else {
if (bool)
return 1;//partial
}
if (contains(r.getMaxX(),r.getMaxY())) {
if (!bool)
return 1;//partial
} else {
if (bool)
return 1;//partial
}
return bool?4:0;
}
@Override
public String toString() {
//Add distance in km, which may be easier to recognize.
double distKm = DistanceUtils.degrees2Dist(radiusDEG, DistanceUtils.EARTH_MEAN_RADIUS_KM);
//instead of String.format() so that we get consistent output no matter the locale
String dStr = new Formatter(Locale.ROOT).format("%.1f\u00B0 %.2fkm", radiusDEG, distKm).toString();
return "Circle(" + point + ", d=" + dStr + ')';
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/shape/impl/InfBufLine.java 0000664 0000000 0000000 00000011372 12577056562 0030107 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 com.spatial4j.core.shape.impl;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.SpatialRelation;
import static com.spatial4j.core.shape.SpatialRelation.*;
/**
* INERNAL: A buffered line of infinite length.
* Public for test access.
*/
public class InfBufLine {
/** 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.5/src/main/java/com/spatial4j/core/shape/impl/PointImpl.java 0000664 0000000 0000000 00000006377 12577056562 0030052 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 com.spatial4j.core.shape.impl;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.BaseShape;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
/** A basic 2D implementation of a Point. */
public class PointImpl 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 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.5/src/main/java/com/spatial4j/core/shape/impl/Range.java 0000664 0000000 0000000 00000011022 12577056562 0027152 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 com.spatial4j.core.shape.impl;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Rectangle;
/**
* INTERNAL: A numeric range between a pair of numbers.
* Perhaps this class could become 1st class citizen extending Shape but not now.
* Only public so is accessible from tests in another package.
*/
@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.5/src/main/java/com/spatial4j/core/shape/impl/RectangleImpl.java 0000664 0000000 0000000 00000025271 12577056562 0030657 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 com.spatial4j.core.shape.impl;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.shape.BaseShape;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
/**
* A simple Rectangle implementation that also supports a longitudinal
* wrap-around. When minX > maxX, this will assume it is world coordinates that
* cross the date line using degrees. Immutable & threadsafe.
*/
public class RectangleImpl 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.5/src/main/java/com/spatial4j/core/shape/jts/ 0000775 0000000 0000000 00000000000 12577056562 0025116 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/shape/jts/JtsGeometry.java 0000775 0000000 0000000 00000045521 12577056562 0030247 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 com.spatial4j.core.shape.jts;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.shape.*;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.impl.BBoxCalculator;
import com.spatial4j.core.shape.impl.BufferedLineString;
import com.spatial4j.core.shape.impl.PointImpl;
import com.spatial4j.core.shape.impl.RectangleImpl;
import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.geom.prep.PreparedGeometry;
import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory;
import com.vividsolutions.jts.operation.union.UnaryUnionOp;
import com.vividsolutions.jts.operation.valid.IsValidOp;
import java.util.ArrayList;
import java.util.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 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 (ctx.isGeo()) {
//Unwraps the geometry across the dateline so it exceeds the standard geo bounds (-180 to +180).
if (dateline180Check)
unwrapDateline(geom);//potentially modifies geom
//If given multiple overlapping polygons, fix it by union
if (allowMultiOverlap)
geom = unionGeometryCollection(geom);//returns same or new geom
//Cuts an unwrapped geometry back into overlaid pages in the standard geo bounds.
geom = cutUnwrappedGeomInto360(geom);//returns same or new geom
assert geom.getEnvelopeInternal().getWidth() <= 360;
assert ! geom.getClass().equals(GeometryCollection.class) : "GeometryCollection unsupported";//double check
//Compute bbox
bbox = computeGeoBBox(geom);
} else {//not geo
//If given multiple overlapping polygons, fix it by union
if (allowMultiOverlap)
geom = unionGeometryCollection(geom);//returns same or new geom
Envelope env = geom.getEnvelopeInternal();
bbox = new RectangleImpl(env.getMinX(), env.getMaxX(), env.getMinY(), env.getMaxY(), ctx);
}
geom.getEnvelopeInternal();//ensure envelope is cached internally, which is lazy evaluated. Keeps this thread-safe.
this.geom = geom;
assert assertValidate();//kinda expensive but caches valid state
this.hasArea = !((geom instanceof Lineal) || (geom instanceof Puntal));
}
/**
* 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;
}
}
/**
* Adds an index to this class internally to compute spatial relations faster. In JTS this
* is called a {@link com.vividsolutions.jts.geom.prep.PreparedGeometry}. This
* isn't done by default because it takes some time to do the optimization, and it uses more
* memory. Calling this method isn't thread-safe so be careful when this is done. If it was
* already indexed then nothing happens.
*/
public void index() {
if (preparedGeometry == null)
preparedGeometry = PreparedGeometryFactory.prepare(geom);
}
@Override
public boolean isEmpty() {
return geom.isEmpty();
}
/** Given {@code geoms} which has already been checked for being in world
* bounds, return the minimal longitude range of the bounding box.
*/
protected Rectangle computeGeoBBox(Geometry geoms) {
if (geoms.isEmpty())
return new RectangleImpl(Double.NaN, Double.NaN, Double.NaN, Double.NaN, ctx);
final Envelope env = geoms.getEnvelopeInternal();//for minY & maxY (simple)
if (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(Circle circle) {
SpatialRelation bboxR = bbox.relate(circle);
if (bboxR == SpatialRelation.WITHIN || bboxR == SpatialRelation.DISJOINT)
return bboxR;
//Test each point to see how many of them are outside of the circle.
//TODO consider instead using geom.apply(CoordinateSequenceFilter) -- maybe faster since avoids Coordinate[] allocation
Coordinate[] coords = geom.getCoordinates();
int outside = 0;
int i = 0;
for (Coordinate coord : coords) {
i++;
SpatialRelation sect = circle.relate(new PointImpl(coord.x, coord.y, ctx));
if (sect == SpatialRelation.DISJOINT)
outside++;
if (i != outside && outside != 0)//short circuit: partially outside, partially inside
return SpatialRelation.INTERSECTS;
}
if (i == outside) {
return (relate(circle.getCenter()) == SpatialRelation.DISJOINT)
? SpatialRelation.DISJOINT : SpatialRelation.CONTAINS;
}
assert outside == 0;
return SpatialRelation.WITHIN;
}
public SpatialRelation relate(JtsGeometry jtsGeometry) {
//don't bother checking bbox since geom.relate() does this already
return relate(jtsGeometry.geom);
}
protected SpatialRelation relate(Geometry oGeom) {
//see http://docs.geotools.org/latest/userguide/library/jts/dim9.html#preparedgeometry
if (oGeom instanceof com.vividsolutions.jts.geom.Point) {
if (preparedGeometry != null)
return preparedGeometry.disjoint(oGeom) ? SpatialRelation.DISJOINT : SpatialRelation.CONTAINS;
return geom.disjoint(oGeom) ? SpatialRelation.DISJOINT : SpatialRelation.CONTAINS;
}
if (preparedGeometry == null)
return intersectionMatrixToSpatialRelation(geom.relate(oGeom));
else if (preparedGeometry.covers(oGeom))
return SpatialRelation.CONTAINS;
else if (preparedGeometry.coveredBy(oGeom))
return SpatialRelation.WITHIN;
else if (preparedGeometry.intersects(oGeom))
return SpatialRelation.INTERSECTS;
return SpatialRelation.DISJOINT;
}
public static SpatialRelation intersectionMatrixToSpatialRelation(IntersectionMatrix matrix) {
//As indicated in SpatialRelation javadocs, Spatial4j CONTAINS & WITHIN are
// OGC's COVERS & COVEREDBY
if (matrix.isCovers())
return SpatialRelation.CONTAINS;
else if (matrix.isCoveredBy())
return SpatialRelation.WITHIN;
else if (matrix.isDisjoint())
return SpatialRelation.DISJOINT;
return SpatialRelation.INTERSECTS;
}
@Override
public String toString() {
return geom.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JtsGeometry that = (JtsGeometry) o;
return geom.equalsExact(that.geom);//fast equality for normalized geometries
}
@Override
public int hashCode() {
//FYI if geometry.equalsExact(that.geometry), then their envelopes are the same.
return geom.getEnvelopeInternal().hashCode();
}
public Geometry getGeom() {
return geom;
}
/**
* If geom spans the dateline, then this modifies it to be a
* valid JTS geometry that extends to the right of the standard -180 to +180
* width such that some points are greater than +180 but some remain less.
* Takes care to invoke {@link com.vividsolutions.jts.geom.Geometry#geometryChanged()}
* if needed.
*
* @return The number of times the geometry spans the dateline. >= 0
*/
private static int unwrapDateline(Geometry geom) {
if (geom.getEnvelopeInternal().getWidth() < 180)
return 0;//can't possibly cross the dateline
final int[] crossings = {0};//an array so that an inner class can modify it.
geom.apply(new GeometryFilter() {
@Override
public void filter(Geometry geom) {
int cross = 0;
if (geom instanceof LineString) {//note: LinearRing extends LineString
if (geom.getEnvelopeInternal().getWidth() < 180)
return;//can't possibly cross the dateline
cross = unwrapDateline((LineString) geom);
} else if (geom instanceof Polygon) {
if (geom.getEnvelopeInternal().getWidth() < 180)
return;//can't possibly cross the dateline
cross = unwrapDateline((Polygon) geom);
} else
return;
crossings[0] = Math.max(crossings[0], cross);
}
});//geom.apply()
if (crossings[0] > 0)
geom.geometryChanged();//applies to call component Geometries
return crossings[0];
}
/** See {@link #unwrapDateline(Geometry)}. */
private static int unwrapDateline(Polygon poly) {
LineString exteriorRing = poly.getExteriorRing();
int cross = unwrapDateline(exteriorRing);
if (cross > 0) {
//TODO TEST THIS! Maybe bug if doesn't cross but is in another page?
for(int i = 0; i < poly.getNumInteriorRing(); i++) {
LineString innerLineString = poly.getInteriorRingN(i);
unwrapDateline(innerLineString);
for(int shiftCount = 0; ! exteriorRing.contains(innerLineString); shiftCount++) {
if (shiftCount > cross)
throw new IllegalArgumentException("The inner ring doesn't appear to be within the exterior: "
+exteriorRing+" inner: "+innerLineString);
shiftGeomByX(innerLineString, 360);
}
}
}
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";
//TODO opt: support geom's that start at negative pages --
// ... will avoid need to previously shift in unwrapDateline(geom).
List geomList = new ArrayList();
//page 0 is the standard -180 to 180 range
for (int page = 0; true; page++) {
double minX = -180 + page * 360;
if (geomEnv.getMaxX() <= minX)
break;
Geometry rect = geom.getFactory().toGeometry(new Envelope(minX, minX + 360, -90, 90));
assert rect.isValid() : "rect";
Geometry pageGeom = rect.intersection(geom);//JTS is doing some hard work
assert pageGeom.isValid() : "pageGeom";
shiftGeomByX(pageGeom, page * -360);
geomList.add(pageGeom);
}
return UnaryUnionOp.union(geomList);
}
// private static Geometry removePolyHoles(Geometry geom) {
// //TODO this does a deep copy of geom even if no changes needed; be smarter
// GeometryTransformer gTrans = new GeometryTransformer() {
// @Override
// protected Geometry transformPolygon(Polygon geom, Geometry parent) {
// if (geom.getNumInteriorRing() == 0)
// return geom;
// return factory.createPolygon((LinearRing) geom.getExteriorRing(),null);
// }
// };
// return gTrans.transform(geom);
// }
//
// private static Geometry snapAndClean(Geometry geom) {
// return new GeometrySnapper(geom).snapToSelf(GeometrySnapper.computeOverlaySnapTolerance(geom), true);
// }
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/shape/jts/JtsPoint.java 0000664 0000000 0000000 00000006136 12577056562 0027541 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 com.spatial4j.core.shape.jts;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.shape.BaseShape;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
import com.spatial4j.core.shape.impl.PointImpl;
import com.vividsolutions.jts.geom.CoordinateSequence;
/** Wraps a {@link com.vividsolutions.jts.geom.Point}. */
public class JtsPoint extends BaseShape implements Point {
private com.vividsolutions.jts.geom.Point pointGeom;
private final boolean empty;//cached
/** A simple constructor without normalization / validation. */
public JtsPoint(com.vividsolutions.jts.geom.Point pointGeom, JtsSpatialContext ctx) {
super(ctx);
this.pointGeom = pointGeom;
this.empty = pointGeom.isEmpty();
}
public com.vividsolutions.jts.geom.Point getGeom() {
return pointGeom;
}
@Override
public boolean isEmpty() {
return empty;
}
@Override
public com.spatial4j.core.shape.Point getCenter() {
return this;
}
@Override
public boolean hasArea() {
return false;
}
@Override
public double getArea(SpatialContext ctx) {
return 0;
}
@Override
public Rectangle getBoundingBox() {
return ctx.makeRectangle(this, this);
}
@Override
public Circle getBuffered(double distance, SpatialContext ctx) {
return ctx.makeCircle(this, distance);
}
@Override
public SpatialRelation relate(Shape other) {
// ** NOTE ** the overall order of logic is kept consistent here with simple.PointImpl.
if (isEmpty() || other.isEmpty())
return SpatialRelation.DISJOINT;
if (other instanceof com.spatial4j.core.shape.Point)
return this.equals(other) ? SpatialRelation.INTERSECTS : SpatialRelation.DISJOINT;
return other.relate(this).transpose();
}
@Override
public double getX() {
return isEmpty() ? Double.NaN : pointGeom.getX();
}
@Override
public double getY() {
return isEmpty() ? Double.NaN : pointGeom.getY();
}
@Override
public void reset(double x, double y) {
assert ! isEmpty();
CoordinateSequence cSeq = pointGeom.getCoordinateSequence();
cSeq.setOrdinate(0, CoordinateSequence.X, x);
cSeq.setOrdinate(0, CoordinateSequence.Y, y);
}
@Override
public String toString() {
return "Pt(x="+getX()+",y="+getY()+")";
}
@Override
public boolean equals(Object o) {
return PointImpl.equals(this,o);
}
@Override
public int hashCode() {
return PointImpl.hashCode(this);
}
}
spatial4j-spatial4j-0.5/src/main/java/com/spatial4j/core/shape/package-info.java 0000664 0000000 0000000 00000001344 12577056562 0027507 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 com.spatial4j.core.shape; spatial4j-spatial4j-0.5/src/main/java/overview.html 0000664 0000000 0000000 00000001250 12577056562 0022347 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.5/src/test/ 0000775 0000000 0000000 00000000000 12577056562 0016727 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/test/java/ 0000775 0000000 0000000 00000000000 12577056562 0017650 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/test/java/com/ 0000775 0000000 0000000 00000000000 12577056562 0020426 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/ 0000775 0000000 0000000 00000000000 12577056562 0022321 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/core/ 0000775 0000000 0000000 00000000000 12577056562 0023251 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/core/TestLog.java 0000664 0000000 0000000 00000004170 12577056562 0025477 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 com.spatial4j.core;
import com.carrotsearch.randomizedtesting.rules.TestRuleAdapter;
import org.slf4j.helpers.MessageFormatter;
import java.util.ArrayList;
import java.util.List;
/**
* A utility logger for tests in which log statements are logged following
* test failure only. Add this to a JUnit based test class with a {@link org.junit.Rule}
* annotation.
*/
public class TestLog extends TestRuleAdapter {
//TODO does this need to be threadsafe (such as via thread-local state)?
private static ArrayList logStack = new ArrayList();
private static final int MAX_LOGS = 1000;
public static final TestLog instance = new TestLog();
private TestLog() {}
@Override
protected void before() throws Throwable {
logStack.clear();
}
@Override
protected void afterAlways(List errors) throws Throwable {
if (!errors.isEmpty())
logThenClear();
}
private void logThenClear() {
for (LogEntry entry : logStack) {
System.out.println(MessageFormatter.arrayFormat(entry.msg, entry.args).getMessage());
}
logStack.clear();
}
public static void clear() {
logStack.clear();
}
/**
* Enqueues a log message with substitution arguments ala SLF4J (i.e. {} syntax).
* If the test fails then it'll be logged then, otherwise it'll be forgotten.
*/
public static void log(String msg, Object... args) {
if (logStack.size() > MAX_LOGS) {
throw new RuntimeException("Too many log statements: "+logStack.size() + " > "+MAX_LOGS);
}
LogEntry entry = new LogEntry();
entry.msg = msg;
entry.args = args;
logStack.add(entry);
}
private static class LogEntry { String msg; Object[] args; }
}
spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/core/context/ 0000775 0000000 0000000 00000000000 12577056562 0024735 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/core/context/SpatialContextFactoryTest.java 0000664 0000000 0000000 00000011715 12577056562 0032737 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 com.spatial4j.core.context;
import com.spatial4j.core.context.jts.DatelineRule;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.context.jts.ValidationRule;
import com.spatial4j.core.distance.CartesianDistCalc;
import com.spatial4j.core.distance.GeodesicSphereDistCalc;
import com.spatial4j.core.io.ShapeIO;
import com.spatial4j.core.io.jts.JtsWKTReader;
import com.spatial4j.core.shape.impl.RectangleImpl;
import org.junit.After;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class SpatialContextFactoryTest {
public static final String PROP = "SpatialContextFactory";
@After
public void tearDown() {
System.getProperties().remove(PROP);
}
private SpatialContext call(String... argsStr) {
Map args = new HashMap();
for (int i = 0; i < argsStr.length; i+=2) {
String key = argsStr[i];
String val = argsStr[i+1];
args.put(key,val);
}
return SpatialContextFactory.makeSpatialContext(args, getClass().getClassLoader());
}
@Test
public void testDefault() {
SpatialContext ctx = SpatialContext.GEO;
SpatialContext ctx2 = call();//default
assertEquals(ctx.getClass(), ctx2.getClass());
assertEquals(ctx.isGeo(), ctx2.isGeo());
assertEquals(ctx.getDistCalc(),ctx2.getDistCalc());
assertEquals(ctx.getWorldBounds(), ctx2.getWorldBounds());
}
@Test
public void testCustom() {
SpatialContext ctx = call("geo","false");
assertTrue(!ctx.isGeo());
assertEquals(new CartesianDistCalc(), ctx.getDistCalc());
ctx = call("geo","false",
"distCalculator","cartesian^2",
"worldBounds","ENVELOPE(-100, 75, 200, 0)");//xMin, xMax, yMax, yMin
assertEquals(new CartesianDistCalc(true),ctx.getDistCalc());
assertEquals(new RectangleImpl(-100, 75, 0, 200, ctx), ctx.getWorldBounds());
ctx = call("geo","true",
"distCalculator","lawOfCosines");
assertTrue(ctx.isGeo());
assertEquals(new GeodesicSphereDistCalc.LawOfCosines(),
ctx.getDistCalc());
}
@Test
public void testJtsContextFactory() {
JtsSpatialContext ctx = (JtsSpatialContext) call(
"spatialContextFactory", JtsSpatialContextFactory.class.getName(),
"geo", "true",
"normWrapLongitude", "true",
"precisionScale", "2.0",
"wktShapeParserClass", CustomWktShapeParser.class.getName(),
"datelineRule", "ccwRect",
"validationRule", "repairConvexHull",
"autoIndex", "true");
assertTrue(ctx.isNormWrapLongitude());
assertEquals(2.0, ctx.getGeometryFactory().getPrecisionModel().getScale(), 0.0);
assertTrue(CustomWktShapeParser.once);//cheap way to test it was created
assertEquals(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 JtsWKTReader {
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.5/src/test/java/com/spatial4j/core/context/jts/ 0000775 0000000 0000000 00000000000 12577056562 0025535 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/core/context/jts/JtsSpatialContextTest.java 0000664 0000000 0000000 00000002650 12577056562 0032666 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 com.spatial4j.core.context.jts;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.Polygon;
import io.jeo.geom.Geom;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
public class JtsSpatialContextTest {
@Test
public void testDatelineRule() {
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();
JtsGeometry shp = ctx.makeShape((Polygon) polygon.clone());
assertTrue(shp.getGeom() instanceof GeometryCollection);
factory.datelineRule = DatelineRule.none;
ctx = factory.newSpatialContext();
shp = ctx.makeShape(polygon);
assertTrue(shp.getGeom() instanceof Polygon);
}
}
spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/core/distance/ 0000775 0000000 0000000 00000000000 12577056562 0025043 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/core/distance/CompareRadiansSnippet.java 0000775 0000000 0000000 00000002104 12577056562 0032141 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 com.spatial4j.core.distance;
//import static com.spatial4j.core.distance.DistanceUtils.toRadians;
import static java.lang.Math.toRadians;
/**
* On my machine, using
* Math.toRadians: 2090
* DistanceUtils: 626
*/
public class CompareRadiansSnippet {
public static void main(String[] args) {
long start = System.currentTimeMillis();
double x = 1.12345;
for (int i=0; i<100000000; i++) {
x += toRadians(x) - toRadians(x+1);
}
System.out.println(x); // need to use the result... otherwise JVM may skip everything
System.out.println(System.currentTimeMillis()-start);
}
}
spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/core/distance/TestDistances.java 0000664 0000000 0000000 00000033316 12577056562 0030471 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 com.spatial4j.core.distance;
import com.carrotsearch.randomizedtesting.RandomizedTest;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.SpatialRelation;
import com.spatial4j.core.shape.impl.PointImpl;
import org.junit.Before;
import org.junit.Test;
import static com.spatial4j.core.distance.DistanceUtils.DEG_TO_KM;
import static com.spatial4j.core.distance.DistanceUtils.KM_TO_DEG;
public class TestDistances extends RandomizedTest {
//NOTE! These are sometimes modified by tests.
private SpatialContext ctx;
private double EPS;
@Before
public void beforeTest() {
ctx = SpatialContext.GEO;
EPS = 10e-4;//delta when doing double assertions. Geo eps is not that small.
}
private DistanceCalculator dc() {
return ctx.getDistCalc();
}
@Test
public void testSomeDistances() {
//See to verify: from http://www.movable-type.co.uk/scripts/latlong.html
Point ctr = pLL(0,100);
assertEquals(11100, dc().distance(ctr, pLL(10, 0)) * DEG_TO_KM, 3);
double deg = dc().distance(ctr, pLL(10, -160));
assertEquals(11100, deg * DEG_TO_KM, 3);
assertEquals(314.40338, dc().distance(pLL(1, 2), pLL(3, 4)) * DEG_TO_KM, EPS);
}
@Test
public void 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.makePoint(lon,lat);
}
@Test
public void testArea() {
double radius = DistanceUtils.EARTH_MEAN_RADIUS_KM * KM_TO_DEG;
//surface of a sphere is 4 * pi * r^2
final double earthArea = 4 * Math.PI * radius * radius;
Circle c = ctx.makeCircle(randomIntBetween(-180,180), randomIntBetween(-90,90),
180);//180 means whole earth
assertEquals(earthArea, c.getArea(ctx), 1.0);
assertEquals(earthArea, ctx.getWorldBounds().getArea(ctx), 1.0);
//now check half earth
Circle cHalf = ctx.makeCircle(c.getCenter(), 90);
assertEquals(earthArea/2, cHalf.getArea(ctx), 1.0);
//circle with same radius at +20 lat with one at -20 lat should have same area as well as bbox with same area
Circle c2 = ctx.makeCircle(c.getCenter(), 30);
Circle c3 = ctx.makeCircle(c.getCenter().getX(), 20, 30);
assertEquals(c2.getArea(ctx), c3.getArea(ctx), 0.01);
Circle c3Opposite = ctx.makeCircle(c.getCenter().getX(), -20, 30);
assertEquals(c3.getArea(ctx), c3Opposite.getArea(ctx), 0.01);
assertEquals(c3.getBoundingBox().getArea(ctx), c3Opposite.getBoundingBox().getArea(ctx), 0.01);
//small shapes near the equator should have similar areas to euclidean rectangle
Rectangle smallRect = ctx.makeRectangle(0, 1, 0, 1);
assertEquals(1.0, smallRect.getArea(null), 0.0);
double smallDelta = smallRect.getArea(null) - smallRect.getArea(ctx);
assertTrue(smallDelta > 0 && smallDelta < 0.0001);
Circle smallCircle = ctx.makeCircle(0,0,1);
smallDelta = smallCircle.getArea(null) - smallCircle.getArea(ctx);
assertTrue(smallDelta > 0 && smallDelta < 0.0001);
//bigger, but still fairly similar
//c2 = ctx.makeCircle(c.getCenter(), 30);
double areaRatio = c2.getArea(null) / c2.getArea(ctx);
assertTrue(areaRatio > 1 && areaRatio < 1.1);
}
}
spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/core/io/ 0000775 0000000 0000000 00000000000 12577056562 0023660 5 ustar 00root root 0000000 0000000 spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/core/io/BaseRoundTripTest.java 0000664 0000000 0000000 00000003742 12577056562 0030112 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 com.spatial4j.core.io;
import com.carrotsearch.randomizedtesting.RandomizedTest;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Shape;
import org.junit.Test;
import java.text.ParseException;
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)"));
}
protected Shape wkt(String wkt) {
try {
return ctx.readShapeFromWkt(wkt);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
protected Shape randomShape() {
switch (randomInt(2)) {//inclusive
case 0: return wkt("POINT(-10 80.3)");
case 1: return wkt("ENVELOPE(-10, 180, 42.3, 0)");
case 2: return wkt("BUFFER(POINT(-10 30), 5.2)");
default: throw new Error();
}
}
protected final void assertRoundTrip(Shape shape) throws Exception {
assertRoundTrip(shape, shouldBeEqualAfterRoundTrip());
}
protected abstract void assertRoundTrip(Shape shape, boolean andEquals) throws Exception;
}
spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/core/io/BinaryCodecTest.java 0000664 0000000 0000000 00000003253 12577056562 0027550 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 com.spatial4j.core.io;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.ShapeCollection;
import org.junit.Test;
import java.io.*;
import java.util.Arrays;
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.5/src/test/java/com/spatial4j/core/io/GeneralGeoJSONTest.java 0000664 0000000 0000000 00000010547 12577056562 0030074 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 com.spatial4j.core.io;
import com.spatial4j.core.shape.Shape;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class GeneralGeoJSONTest extends GeneralReadWriteShapeTest {
ShapeReader reader;
ShapeWriter writer;
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.5/src/test/java/com/spatial4j/core/io/GeneralPolyshapeTest.java 0000664 0000000 0000000 00000003413 12577056562 0030626 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 com.spatial4j.core.io;
import org.junit.Assert;
import org.junit.Before;
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)
}
} spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/core/io/GeneralReadWriteShapeTest.java 0000664 0000000 0000000 00000017336 12577056562 0031542 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 com.spatial4j.core.io;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import io.jeo.geom.GeomBuilder;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
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();
Assert.assertEquals(writer.toString(shape), writer.toString(out));
if(andEquals) {
Assert.assertEquals(shape, out);
}
}
@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]}");
}
com.spatial4j.core.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());
}
String bufferedLineText() {
return strip(
"{'type': 'LineString', " +
"'coordinates': [[100.1,0.1],[101.1,1.1]], " +
"'buffer': 1111.950797, " +
"'properties': {'buffer_units': 'km'}}");
}
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);
}
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.5/src/test/java/com/spatial4j/core/io/GeneralWktTest.java 0000664 0000000 0000000 00000004342 12577056562 0027431 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 com.spatial4j.core.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.5/src/test/java/com/spatial4j/core/io/JtsBinaryCodecTest.java 0000664 0000000 0000000 00000004115 12577056562 0030227 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 com.spatial4j.core.io;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.shape.Shape;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.util.GeometricShapeFactory;
import org.junit.Test;
import java.util.Arrays;
public class JtsBinaryCodecTest extends BinaryCodecTest {
@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.5/src/test/java/com/spatial4j/core/io/JtsWKTReaderShapeParserTest.java 0000664 0000000 0000000 00000005417 12577056562 0032001 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 com.spatial4j.core.io;
import com.carrotsearch.randomizedtesting.RandomizedTest;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.context.jts.DatelineRule;
import com.spatial4j.core.io.jts.JtsWKTReaderShapeParser;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import org.junit.Test;
import java.io.IOException;
public class JtsWKTReaderShapeParserTest extends RandomizedTest {
final SpatialContext ctx;
{
JtsSpatialContextFactory factory = new JtsSpatialContextFactory();
factory.datelineRule = DatelineRule.ccwRect;
factory.readers.clear();
factory.readers.add( JtsWKTReaderShapeParser.class );
ctx = factory.newSpatialContext();
}
@Test
public void wktGeoPt() throws IOException {
Shape s = ctx.readShape("Point(-160 30)");
assertEquals(ctx.makePoint(-160,30),s);
}
@Test
public void wktGeoRect() throws IOException {
//REMEMBER: Polygon WKT's outer ring is counter-clockwise order. If you accidentally give the other direction,
// JtsSpatialContext will give the wrong result for a rectangle crossing the dateline.
// In these two tests, we give the same set of points, one that does not cross the dateline, and the 2nd does. The
// order is counter-clockwise in both cases as it should be.
Shape sNoDL = ctx.readShape("Polygon((-170 30, -170 15, 160 15, 160 30, -170 30))");
Rectangle expectedNoDL = ctx.makeRectangle(-170, 160, 15, 30);
assertTrue(!expectedNoDL.getCrossesDateLine());
assertEquals(expectedNoDL,sNoDL);
Shape sYesDL = ctx.readShape("Polygon(( 160 30, 160 15, -170 15, -170 30, 160 30))");
Rectangle expectedYesDL = ctx.makeRectangle(160, -170, 15, 30);
assertTrue(expectedYesDL.getCrossesDateLine());
assertEquals(expectedYesDL,sYesDL);
}
@Test
public void testWrapTopologyException() {
try {
ctx.readShape("POLYGON((0 0, 10 0, 10 20))");//doesn't connect around
fail();
} catch (InvalidShapeException e) {
//expected
}
try {
ctx.readShape("POLYGON((0 0, 10 0, 10 20, 5 -5, 0 20, 0 0))");//Topology self-intersect
fail();
} catch (InvalidShapeException e) {
//expected
}
}
}
spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/core/io/JtsWktShapeParserTest.java 0000664 0000000 0000000 00000014744 12577056562 0030761 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 com.spatial4j.core.io;
import com.spatial4j.core.context.jts.DatelineRule;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.context.jts.ValidationRule;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import org.junit.Test;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Collections;
public class JtsWktShapeParserTest extends WktShapeParserTest {
//By extending WktShapeParserTest we inherit its test too
final JtsSpatialContext ctx;//note: masks superclass
public JtsWktShapeParserTest() {
super(JtsSpatialContext.GEO);
this.ctx = (JtsSpatialContext) super.ctx;
}
@Test
public void testParsePolygon() throws ParseException {
Shape polygonNoHoles = new PolygonBuilder(ctx)
.point(100, 0)
.point(101, 0)
.point(101, 1)
.point(100, 2)
.point(100, 0)
.build();
String polygonNoHolesSTR = "POLYGON ((100 0, 101 0, 101 1, 100 2, 100 0))";
assertParses(polygonNoHolesSTR, polygonNoHoles);
assertParses("POLYGON((100 0,101 0,101 1,100 2,100 0))", polygonNoHoles);
assertParses("GEOMETRYCOLLECTION ( "+polygonNoHolesSTR+")",
ctx.makeCollection(Arrays.asList(polygonNoHoles)));
Shape polygonWithHoles = new PolygonBuilder(ctx)
.point(100, 0)
.point(101, 0)
.point(101, 1)
.point(100, 1)
.point(100, 0)
.newHole()
.point(100.2, 0.2)
.point(100.8, 0.2)
.point(100.8, 0.8)
.point(100.2, 0.8)
.point(100.2, 0.2)
.endHole()
.build();
assertParses("POLYGON ((100 0, 101 0, 101 1, 100 1, 100 0), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2))", polygonWithHoles);
GeometryFactory gf = ctx.getGeometryFactory();
assertParses("POLYGON EMPTY", ctx.makeShape(
gf.createPolygon(gf.createLinearRing(new Coordinate[]{}), null)
));
}
@Test
public void testPolyToRect() throws ParseException {
//poly is a rect (no dateline issue)
assertParses("POLYGON((0 5, 10 5, 10 20, 0 20, 0 5))", ctx.makeRectangle(0, 10, 5, 20));
}
@Test
public void polyToRect180Rule() throws ParseException {
//crosses dateline
Rectangle expected = ctx.makeRectangle(160, -170, 0, 10);
//counter-clockwise
assertParses("POLYGON((160 0, -170 0, -170 10, 160 10, 160 0))", expected);
//clockwise
assertParses("POLYGON((160 10, -170 10, -170 0, 160 0, 160 10))", expected);
}
@Test
public void polyToRectCcwRule() throws ParseException {
JtsSpatialContext ctx = new JtsSpatialContextFactory() { { datelineRule = DatelineRule.ccwRect;} }.newSpatialContext();
//counter-clockwise
assertEquals(ctx.readShapeFromWkt("POLYGON((160 0, -170 0, -170 10, 160 10, 160 0))"),
ctx.makeRectangle(160, -170, 0, 10));
//clockwise
assertEquals(ctx.readShapeFromWkt("POLYGON((160 10, -170 10, -170 0, 160 0, 160 10))"),
ctx.makeRectangle(-170, 160, 0, 10));
}
@Test
public void testParseMultiPolygon() throws ParseException {
Shape p1 = new PolygonBuilder(ctx)
.point(100, 0)
.point(101, 0)//101
.point(101, 2)//101
.point(100, 1)
.point(100, 0)
.build();
Shape p2 = new PolygonBuilder(ctx)
.point(100, 0)
.point(102, 0)//102
.point(102, 2)//102
.point(100, 1)
.point(100, 0)
.build();
Shape s = ctx.makeCollection(
Arrays.asList(p1, p2)
);
assertParses("MULTIPOLYGON(" +
"((100 0, 101 0, 101 2, 100 1, 100 0))" + ',' +
"((100 0, 102 0, 102 2, 100 1, 100 0))" +
")", s);
assertParses("MULTIPOLYGON EMPTY", ctx.makeCollection(Collections.EMPTY_LIST));
}
@Test
public void testLineStringDateline() throws ParseException {
//works because we use JTS (JtsGeometry); BufferedLineString doesn't yet do DL wrap.
Shape s = ctx.readShapeFromWkt("LINESTRING(160 10, -170 15)");
assertEquals(30, s.getBoundingBox().getWidth(), 0.0 );
}
@Test
public void testWrapTopologyException() throws Exception {
//test that we can catch ParseException without having to detect TopologyException too
assert ctx.getValidationRule() != ValidationRule.none;
try {
ctx.readShapeFromWkt("POLYGON((0 0, 10 0, 10 20))");//doesn't connect around
fail();
} catch (InvalidShapeException e) {
//expected
}
try {
ctx.readShapeFromWkt("POLYGON((0 0, 10 0, 10 20, 5 -5, 0 20, 0 0))");//Topology self-intersect
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 = ctx.readShapeFromWkt(wkt);
assertTrue(buffer0.getArea(ctx) > 0);
factory = new JtsSpatialContextFactory();
factory.validationRule = ValidationRule.repairConvexHull;
ctx = factory.newSpatialContext();
Shape cvxHull = ctx.readShapeFromWkt(wkt);
assertTrue(cvxHull.getArea(ctx) > 0);
assertEquals(SpatialRelation.CONTAINS, cvxHull.relate(buffer0));
factory = new JtsSpatialContextFactory();
factory.validationRule = ValidationRule.none;
ctx = factory.newSpatialContext();
ctx.readShapeFromWkt(wkt);//doesn't throw
}
}
spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/core/io/PolygonBuilder.java 0000664 0000000 0000000 00000007375 12577056562 0027475 0 ustar 00root root 0000000 0000000 /*******************************************************************************
* Copyright (c) 2015 ElasticSearch and MITRE
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Apache License, Version 2.0 which
* accompanies this distribution and is available at
* http://www.apache.org/licenses/LICENSE-2.0.txt
******************************************************************************/
// A derivative of commit 14bc4dee08355048d6a94e33834b919a3999a06e
// at https://github.com/chrismale/elasticsearch
package com.spatial4j.core.io;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Polygon;
import java.util.ArrayList;
import java.util.List;
/**
* Builder for creating a {@link com.spatial4j.core.shape.Shape} instance of a Polygon
*/
public class PolygonBuilder {
private final JtsSpatialContext ctx;
private final List points = new ArrayList();
private final List holes = new ArrayList();
public PolygonBuilder(JtsSpatialContext ctx) {
this.ctx = ctx;
}
/**
* Adds a point to the Polygon
*
* @param lon Longitude of the point
* @param lat Latitude of the point
* @return this
*/
public PolygonBuilder point(double lon, double lat) {
points.add(new Coordinate(lon, lat));
return this;
}
/**
* Starts a new hole in the Polygon
*
* @return PolygonHoleBuilder to create the new hole
*/
public PolygonHoleBuilder newHole() {
return new PolygonHoleBuilder(this);
}
/**
* Registers the LinearRing representing a hole
*
* @param linearRing Hole to register
* @return this
*/
private PolygonBuilder addHole(LinearRing linearRing) {
holes.add(linearRing);
return this;
}
/**
* Builds a {@link com.spatial4j.core.shape.Shape} instance representing the polygon
*
* @return Built polygon
*/
public Shape build() {
return new JtsGeometry(toPolygon(), ctx, true, true);
}
/**
* Creates the raw {@link com.vividsolutions.jts.geom.Polygon}
*
* @return Built polygon
*/
public Polygon toPolygon() {
LinearRing ring = ctx.getGeometryFactory().createLinearRing(points.toArray(new Coordinate[points.size()]));
LinearRing[] holes = this.holes.isEmpty() ? null : this.holes.toArray(new LinearRing[this.holes.size()]);
return ctx.getGeometryFactory().createPolygon(ring, holes);
}
/**
* Builder for defining a hole in a {@link com.vividsolutions.jts.geom.Polygon}
*/
public class PolygonHoleBuilder {
private final List points = new ArrayList();
private final PolygonBuilder polygonBuilder;
/**
* Creates a new PolygonHoleBuilder
*
* @param polygonBuilder PolygonBuilder that the hole built by this builder
* will be added to
*/
private PolygonHoleBuilder(PolygonBuilder polygonBuilder) {
this.polygonBuilder = polygonBuilder;
}
/**
* Adds a point to the LinearRing
*
* @param lon Longitude of the point
* @param lat Latitude of the point
* @return this
*/
public PolygonHoleBuilder point(double lon, double lat) {
points.add(new Coordinate(lon, lat));
return this;
}
/**
* Ends the building of the hole
*
* @return PolygonBuilder to use to build the remainder of the Polygon.
*/
public PolygonBuilder endHole() {
return polygonBuilder.addHole(ctx.getGeometryFactory().createLinearRing(points.toArray(new Coordinate[points.size()])));
}
}
}
spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/core/io/ShapeFormatTest.java 0000664 0000000 0000000 00000012252 12577056562 0027576 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 com.spatial4j.core.io;
import static org.junit.Assert.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.text.ParseException;
import java.util.Arrays;
import org.junit.Test;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.io.jts.JtsWKTReader;
import com.spatial4j.core.shape.Shape;
/**
* Tests for {@link ShapeFormat}
*/
public class ShapeFormatTest {
public Shape testReadAndWriteTheSame(Shape shape, ShapeReader reader,ShapeWriter writer) throws IOException, ParseException {
assertNotNull(shape);
StringWriter str = new StringWriter();
writer.write(str, shape);
// System.out.println( "OUT: "+str.toString());
Shape out = reader.read(new StringReader(str.toString()));
StringWriter copy = new StringWriter();
writer.write(copy, out);
assertEquals(str.toString(), copy.toString());
return out;
}
public void testCommon(SpatialContext ctx, String name) throws Exception {
ShapeReader reader = ctx.getFormats().getReader(name);
ShapeWriter writer = ctx.getFormats().getWriter(name);
assertNotNull(reader);
assertNotNull(writer);
testReadAndWriteTheSame(ctx.makePoint(10, 20),reader,writer);
testReadAndWriteTheSame(ctx.makeLineString(
Arrays.asList(
ctx.makePoint(1, 2),
ctx.makePoint(3, 4),
ctx.makePoint(5, 6)
)),reader,writer);
// testReadAndWriteTheSame(ctx.makeRectangle(10, 20, 30, 40),format);
}
public void testJTS(JtsSpatialContext ctx, String name) throws Exception {
ShapeReader reader = ctx.getFormats().getReader(name);
ShapeWriter writer = ctx.getFormats().getWriter(name);
Shape shape = null;
//
// wkt = readFirstLineFromRsrc("/russia.wkt.txt");
// shape = ctx.readShape(wkt);
// // testReadAndWriteTheSame(shape,format);
// Examples from Wikipedia
shape = ctx.readShape("LINESTRING (30 10, 10 30, 40 40)");
// testReadAndWriteTheSame(shape,format);
shape = ctx.readShape("POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10))");
testReadAndWriteTheSame(shape,reader,writer);
shape = ctx.readShape("POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30))");
testReadAndWriteTheSame(shape,reader,writer);
shape = ctx.readShape("MULTIPOINT ((10 40), (40 30), (20 20), (30 10))");
testReadAndWriteTheSame(shape,reader,writer);
shape = ctx.readShape("MULTIPOINT (10 40, 40 30, 20 20, 30 10)");
testReadAndWriteTheSame(shape,reader,writer);
shape = ctx.readShape("MULTILINESTRING ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10))");
testReadAndWriteTheSame(shape,reader,writer);
shape = ctx.readShape("MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)),((15 5, 40 10, 10 20, 5 10, 15 5)))");
testReadAndWriteTheSame(shape,reader,writer);
}
@Test
public void testReadAndWriteTheSame() throws Exception {
// GeoJSON
String format = ShapeIO.GeoJSON;
testCommon(SpatialContext.GEO, format);
testCommon(JtsSpatialContext.GEO, format);
testJTS(JtsSpatialContext.GEO, format);
// WKT
format = ShapeIO.WKT;
testCommon(SpatialContext.GEO, format);
testCommon(JtsSpatialContext.GEO, format);
testJTS(JtsSpatialContext.GEO, format);
}
public void testParseVsInvalidExceptions(WKTReader reader) throws Exception
{
String txt = null;
try {
txt = "garbage";
reader.read(txt);
fail("should throw invalid exception");
} catch(ParseException ex) {
//expected
}
try {
txt = "POINT(-1000 1000)";
reader.read(txt);
fail("should throw invalid shape");
} catch(InvalidShapeException ex) {
//expected
}
if(reader instanceof JtsWKTReader) {
try {
txt = readFirstLineFromRsrc("/fiji.wkt.txt");
reader.read(txt);
fail("should throw invalid exception");
} catch(InvalidShapeException ex) {
//expected
}
}
}
@Test
public void testParseVsInvalidExceptions() throws Exception
{
testParseVsInvalidExceptions(SpatialContext.GEO.getWktShapeParser());
testParseVsInvalidExceptions(JtsSpatialContext.GEO.getWktShapeParser());
}
private String readFirstLineFromRsrc(String wktRsrcPath) throws IOException {
InputStream is = getClass().getResourceAsStream(wktRsrcPath);
assertNotNull(is);
try {
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
return br.readLine();
} finally {
is.close();
}
}
}
spatial4j-spatial4j-0.5/src/test/java/com/spatial4j/core/io/ShapeReadWriterTest.java 0000664 0000000 0000000 00000004517 12577056562 0030423 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 com.spatial4j.core.io;
import com.carrotsearch.randomizedtesting.RandomizedTest;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.shape.Shape;
import org.junit.Test;
import java.io.IOException;
import java.util.Arrays;
@SuppressWarnings("unchecked")
public class ShapeReadWriterTest extends RandomizedTest {
@ParametersFactory
public static Iterable