pax_global_header 0000666 0000000 0000000 00000000064 12417676335 0014530 g ustar 00root root 0000000 0000000 52 comment=c4b76a92063750805d6905c0da776490968e9deb
spatial4j-0.4-0.4.1/ 0000775 0000000 0000000 00000000000 12417676335 0013724 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/.gitignore 0000775 0000000 0000000 00000000035 12417676335 0015715 0 ustar 00root root 0000000 0000000 /*.ipr
/.idea/
*.iml
target/
spatial4j-0.4-0.4.1/.travis.yml 0000664 0000000 0000000 00000000204 12417676335 0016031 0 ustar 00root root 0000000 0000000 language: java
script: mvn -Drandomized.multiplier=10 clean verify
jdk:
- openjdk6
notifications:
email:
- dev@spatial4j.com spatial4j-0.4-0.4.1/CHANGES.md 0000664 0000000 0000000 00000011356 12417676335 0015324 0 ustar 00root root 0000000 0000000 ## VERSION 0.4
DATE: 20 January 2014
### User/API changes & Notes:
* It used to be the case that rectangular polygons provided in WKT had to have its vertexes given
in counter-clockwise order to indicate which way around the earth it went. The default is now
the shorter width (less than 180 degrees). This setting can be changed via the “datelineRule”
setting. To avoid ambiguity, just use the ENVELOPE syntax.
* Unless you refer to the SpatialContext.GEO or JtsSpatialContext.GEO instances, the only true way
to create a context is to use the SpatialContextFactory (and including the JTS based subclass),
which have a host of settings that make Spatial4j very customizable.
* SpatialContext.readShape(String) and toString(Shape) are still deprecated but will be
removed in the next release. You should instead read WKT via the new method
readShapeFromWkt(String). This also means the worldBounds initialization via SpatialContextFactory
should be specified as an ENVELOPE WKT. Spatial4j will no longer provide a way to generate WKT
from a shape although it’s pretty easy to use JTS for that.
* In Spatial4j’s older JTS based implementation, it used to be the case that when reading WKT,
latitudes would be normalized into the -90 to 90 range; it’s now an error. Longitudes outside of
-180 to 180 were also normalized and this is now an error too. Longitude normalization can be
enabled with the “normWrapLongitude” option.
* Newly deprecated: ParseUtils, some methods and constants in DistanceUtils (which seemed only used
by Solr), and SpatialContext constructors other than that which takes a SpatialContextFactory.
In the "io" package, these classes were **deleted**: LineReader, SampleData..., Geoname...
Copies of those were moved to the _Spatial Solr Sandbox_ project.
### Features:
* New built-in WKT parser separate from JTS’s. JTS is no longer required for WKT but still is for
certain shapes like polygons. It supports the ENVELOPE rectangle syntax seen in OGC’s CQL spec,
and it has a custom BUFFER(shape, distance) that can be used to buffer a shape. A buffered point
is a circle. Parse via SpatialContext.readShapeFromWkt(String). JTS’s WKT parser can be used as
an alternative via the JtsWKTReaderShapeParser class.
* New ShapeCollection shape. It’s similar to a JTS/OGC GeometryCollection and is used to hold
multiple shapes of the same or different types.
* New BufferedLine & BufferedLineString shapes. A 0 buffer results in effectively Line &
LineString shapes. A non-0 buffer is buffered in a rectangular corner fashion (i.e. a rectangle
on an angle). This new shape does not yet support geodesics (e.g. the dateline), and so JTS’s
equivalents are used by default for now.
* JtsGeometry can now be “indexed”. It builds an internal index to speed up subsequent
calculations. It can be done automatically when read from WKT via the new “autoIndex” option.
* JtsGeometry shapes when read via WKT have configurable validation and automatic repair via a
couple algorithms. See the new “validationRule” setting.
* Shapes can now be “empty”; see Shape.isEmpty().
* Configurable dateline crossing algorithm: none, width180, ccwRect
* JTS’s PrecisionModel is configurable from the JtsSpatialContextFactory.
* A new “BinaryCodec” is added which is a binary format for shapes; see
SpatialContext.getBinaryCodec(). In this release it’s a pretty straight-forward implementation,
but might get optimized for more compactness in the future. When using JTS and the
“floating_single” PrecisionModel, it will use 4-byte floats instead of 8-byte doubles for shapes
other than JtsGeometry (i.e. non-polygons). JtsGeometry is written in WKB format which is always
8-byte doubles. In the future, WKB will not likely be used.
* New SpatialContext.calcDistance convenience methods to avoid referencing DistanceCalculator
* New DistanceCalculator.withinDistance method used by Euclidean circle avoids a Math.sqrt call.
* DistanceUtils.DEG_TO_KM & KM_TO_DEG convenience constants
### Bugs:
* Geodetic circles sometimes computed the wrong relationship with a rectangle.
* JtsGeometry now calculates the minimum geodetic bounding box (also used by ShapeCollection).
This fixed an issue where Lucene-spatial would give Fiji a much courser grid granularity than it
deserved.
* JtsGeometry shapes that had > 180 width sometimes resulted in an exception. (#41 & SOLR-4879)
* Converting a Euclidean circle to a polygonal geometry was wrong. (#44)
* DistanceUtil.vectorDistance for Manhattan style was wrong. (LUCENE-3814)
* More consistently wrap shape parsing errors with InvalidShapeException or ParseException as
applicable.
spatial4j-0.4-0.4.1/LICENSE.txt 0000664 0000000 0000000 00000026136 12417676335 0015557 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-0.4-0.4.1/README.md 0000664 0000000 0000000 00000016524 12417676335 0015213 0 ustar 00root root 0000000 0000000 # Spatial4j
Spatial4j is a general purpose spatial / geospatial [ASL](http://www.apache.org/licenses/LICENSE-2.0.html) licensed open-source Java library. It's core capabilities are 3-fold: to provide common shapes that can work in Euclidean and geodesic (surface of sphere) world models, to provide distance calculations and other math, and to read shapes from [WKT](http://en.wikipedia.org/wiki/Well-known_text) formatted strings. Spatial4j is a [pending](http://www.locationtech.org/proposals/spatial4j) member of the [LocationTech](http://www.locationtech.org) Industry Working Group family of projects.
If you are working with spatial grid-square indexing schemes, be it [Geohash](http://en.wikipedia.org/wiki/Geohash) or something custom, then you are likely to find especially high utility from Spatial4j -- doubly-so for Apache Software Foundation projects due to restrictions on use of LGPL software there.
## Shapes and Other Features
The main part of Spatial4j is its collection of shapes. Shapes in Spatial4j have these features:
* Compute its lat-lon bounding box.
* Compute an area. For some shapes its more of an estimate.
* Compute if it contains a provided point.
* Compute the relationship to a lat-lon rectangle. Relationships are: CONTAINS, WITHIN, DISJOINT, INTERSECTS. Note that Spatial4j doesn't have a notion of "touching".
Spatial4j has a variety of shapes that operate in Euclidean-space -- i.e. a flat 2D plane. Most shapes are augmented to support a wrap-around at `X` -180/+180 for compatibility with latitude & longitudes, which is effectively a cylindrical model. But the real bonus is its circle (i.e. point-radius shape that can operate on a surface-of-a-sphere model. See below for further info. I often use the term "geodetic" or "geodesic" or "geo" as synonymous with that model but technically those words have a more broad meaning.
| Shape | Euclidean | Cylindrical | Spherical|
| -----------|:---------:|:-----------:|:--------:|
| **Point** | Y | Y | Y |
| **Rectangle** | Y | Y | Y |
| **Circle** | Y | N | Y |
| **LineString** | Y | N | N |
| **Buffered L/S** | Y | N | N |
| **Polygon** | Y | Y | N |
| **ShapeCollection** | Y | Y | Y |
* The Rectangle shape exists in the spherical model as a lat-lon rectangle, which basically means it's math is no different than cylindrical.
* Polygons don't support pole-wrap (sorry, no Antarctica polygon); just dateline-cross. Polygons are supported by wrapping JTS's `Geometry`, which is to say that most of the fundamental logic for that shape is implemented by JTS.
### Other Features
* Parse shapes from strings in [WKT](http://en.wikipedia.org/wiki/Well-known_text) to include the ENVELOPE extension from CQL, plus a Spatial4j custom BUFFER operation. Buffering a point gets you a Circle.
* 3 great-circle distance calculators: Law of Cosines, Haversine, Vincenty
* The code is well tested and it's monitored via [Travis-CI](http://travis-ci.org/#!/spatial4j/spatial4j) continuous integration.
* Spatial4j has no dependencies on other libraries except for JTS, which is only triggered if you use Polygons, or obviously if you use any of the classes prefixed with "Jts".
## Why not use JTS? Why should you use Spatial4j?
Spatial4j was born out of an unmet need from other open-source Java software.
[JTS](https://sourceforge.net/projects/jts-topo-suite/) is the most popular spatial library in Java. JTS is powerful but it only supports Euclidean geometry (no geodesics), it has no Circle shape, and it is LGPL licensed (which is planned to change soon). Spatial4j has a geodesic circle implementation, and it wraps JTS geometries to add dateline-wrap support (no pole wrap yet).
A geodesic circle implementation (i.e. point-radius on surface of a sphere), has been non-trivial; see for yourself and look at the extensive testing. Presumably many applications will use a polygon substitute for a circle, however note that not only is it an approximation, but common algorithms *inscribe* instead of *circumscribe* the circle. The result is a polygon that doesn't quite completely cover the intended shape, potentially resulting in not finding desired data when applied to the information-retrieval domain (i.e. indexing/search in Apache Lucene) where it is usually better to find a false match versus not find a positive match when making approximations. Also, Spatial4j's implementation goes to some lengths to be efficient by only calculating the great-circle-distance a minimum number of times in order to find the intersection relationship with a rectangle. Even computing the bounding-box of this shape was non-obvious, as the initial algorithm lifted from the web at a popular site turned out to be false.
## Getting Started
**[Javadoc API](https://spatial4j.github.io/spatial4j/apidocs/)**
The facade to all of Spatial4j is the [`SpatialContext`](https://spatial4j.github.io/spatial4j/apidocs/com/spatial4j/core/context/SpatialContext.html). It acts as a factory for shapes and it holds references to most other classes you might use and/or it has convenience methods for them. For example you can get a [`DistanceCalculator`](https://spatial4j.github.io/spatial4j/apidocs/com/spatial4j/core/distance/DistanceCalculator.html) but if you just want to calculate the distance then the context has a method for that.
To get a SpatialContext (or just "context" for short), you could use a global singleton `SpatialContext.GEO` or `JtsSpatialContext.GEO` with both use geodesic surface-of-sphere calculations (when available); the JTS one principally adds Polygon support. If you want a non-geodesic implementation or you want to customize one of many options, then instantiate a [`SpatialContextFactory`](https://spatial4j.github.io/spatial4j/apidocs/com/spatial4j/core/context/SpatialContextFactory.html) (or `JtsSpatialContextFactory`), set the options, then invoke `newSpatialContext()`. If you have a set of name-value string pairs, perhaps from a java properties file, then instead use the static `makeSpatialContext(map, classLoader)` method which adds a lot of flexibility to the configuration initialization versus hard-coding it.
*You should generally avoid calling constructors for anything in Spatial4j except for the `SpatialContextFactory`.* Constructors aren't strictly forbidden but the factories are there to provide an extension point / abstraction, so don't side-step them unless there's a deliberate reason.
## Miscellaneous
Discuss Spatial4j on our [mailing list](http://spatial4j.16575.n6.nabble.com/).
View metadata about the project as generated by Maven: [maven site](https://spatial4j.github.io/spatial4j/).
Spatial4j has been ported to .NET (C#) where it is appropriately named [Spatial4n](https://github.com/synhershko/Spatial4n).
### Future Road Map Ideas
* Support for projections by incorporating Proj4j
* More surface-of-sphere implemented shapes (LineString, Polygon)
* Polygon pole wrap
* Multi-dimensional?
### History
Before Spatial4j, there was [Lucene Spatial Playground](http://code.google.com/p/lucene-spatial-playground/) (LSP) and from this work a generic core spatial library emerged, independent of Lucene: Spatial4j. The other parts of LSP were either merged into Lucene / Solr itself or were migrated to
[Spatial Solr Sandbox](https://github.com/ryantxu/spatial-solr-sandbox). spatial4j-0.4-0.4.1/eclipse 0000775 0000000 0000000 00000000315 12417676335 0015275 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-0.4-0.4.1/pom.xml 0000664 0000000 0000000 00000023572 12417676335 0015252 0 ustar 00root root 0000000 0000000
4.0.0org.sonatype.ossoss-parent7com.spatial4jspatial4j0.4.1bundleSpatial4J
Spatial4j is a general purpose spatial / geospatial ASL licensed open-source Java library. It's
core capabilities are 3-fold: to provide common geospatially-aware shapes, to provide distance
calculations and other math, and to read shapes in WKT format.
LocationTechhttp://locationtech.orghttps://github.com/spatial4j/spatial4jThe Apache Software License, Version 2.0http://www.apache.org/licenses/LICENSE-2.0.txtrepoGitHubhttps://github.com/spatial4j/spatial4j/issuesTravis-CIhttps://travis-ci.org/spatial4j/spatial4jdev@lists.spatial4j.comhttp://spatial4j.16575.x6.nabble.comhttp://lists.spatial4j.com/pipermail/dev-spatial4j.com/dev@lists.spatial4j.comscm:git:git@github.com:spatial4j/spatial4j.gitscm:git:git@github.com:spatial4j/spatial4j.githttps://github.com/spatial4j/spatial4jUTF-81.7.52.2.1com.vividsolutionsjts1.13truexercesxercesImpljunitjunit4.11testorg.slf4jslf4j-simple${slf4j.version}testcom.carrotsearch.randomizedtestingrandomizedtesting-runner2.0.15testorg.apache.maven.pluginsmaven-compiler-plugin3.11.61.6truetrueorg.apache.maven.pluginsmaven-surefire-plugin2.160de.thetaphiforbiddenapis1.4checktruejdk-unsafejdk-deprecatedfalse1.6org.apache.felixmaven-bundle-plugin2.4.0com.spatial4j.core*;version=${project.version}trueorg.apache.maven.pluginsmaven-site-plugin3.3org.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.7org.apache.maven.pluginsmaven-pmd-plugin3.0.1true1001.6org.codehaus.mojofindbugs-maven-plugin2.5.3trueorg.apache.maven.pluginsmaven-jxr-plugin2.4jxrorg.apache.maven.pluginsmaven-surefire-report-plugin2.16org.apache.maven.pluginsmaven-javadoc-plugin2.9.1Spatial4j, ${project.version}Spatial4j, ${project.version}
http://tsusiatsoftware.net/jts/javadoc/
javadoc
spatial4j-0.4-0.4.1/src/ 0000775 0000000 0000000 00000000000 12417676335 0014513 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/main/ 0000775 0000000 0000000 00000000000 12417676335 0015437 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/main/java/ 0000775 0000000 0000000 00000000000 12417676335 0016360 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/main/java/com/ 0000775 0000000 0000000 00000000000 12417676335 0017136 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/ 0000775 0000000 0000000 00000000000 12417676335 0021031 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/ 0000775 0000000 0000000 00000000000 12417676335 0021761 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/context/ 0000775 0000000 0000000 00000000000 12417676335 0023445 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/context/SpatialContext.java 0000664 0000000 0000000 00000032302 12417676335 0027252 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.context;
import com.spatial4j.core.distance.CartesianDistCalc;
import com.spatial4j.core.distance.DistanceCalculator;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.distance.GeodesicSphereDistCalc;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.io.BinaryCodec;
import com.spatial4j.core.io.LegacyShapeReadWriterFormat;
import com.spatial4j.core.io.WktShapeParser;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.ShapeCollection;
import com.spatial4j.core.shape.impl.BufferedLineString;
import com.spatial4j.core.shape.impl.CircleImpl;
import com.spatial4j.core.shape.impl.GeoCircle;
import com.spatial4j.core.shape.impl.PointImpl;
import com.spatial4j.core.shape.impl.RectangleImpl;
import java.text.ParseException;
import java.util.List;
/**
* This is a facade to most of Spatial4j, holding things like {@link DistanceCalculator},
* {@link com.spatial4j.core.io.WktShapeParser}, and acting as a factory for the {@link Shape}s.
*
* If you want a typical geodetic context, just reference {@link #GEO}. Otherwise,
* You should either create and configure a {@link SpatialContextFactory} and then call
* {@link SpatialContextFactory#newSpatialContext()}, OR, call
* {@link com.spatial4j.core.context.SpatialContextFactory#makeSpatialContext(java.util.Map, ClassLoader)}
* to do this via configuration data.
*
* Thread-safe & immutable.
*/
public class SpatialContext {
/** A popular default SpatialContext implementation for geospatial. */
public static final SpatialContext GEO = new SpatialContext(new SpatialContextFactory());
//These are non-null
private final boolean geo;
private final DistanceCalculator calculator;
private final Rectangle worldBounds;
private final WktShapeParser wktShapeParser;
private final BinaryCodec binaryCodec;
private final boolean normWrapLongitude;
/**
* Consider using {@link com.spatial4j.core.context.SpatialContextFactory} instead.
*
* @param geo Establishes geo vs cartesian / Euclidean.
* @param calculator Optional; defaults to haversine or cartesian depending on {@code geo}.
* @param worldBounds Optional; defaults to GEO_WORLDBOUNDS or MAX_WORLDBOUNDS depending on units.
*/
@Deprecated
public SpatialContext(boolean geo, DistanceCalculator calculator, Rectangle worldBounds) {
this(initFromLegacyConstructor(geo, calculator, worldBounds));
}
private static SpatialContextFactory initFromLegacyConstructor(boolean geo,
DistanceCalculator calculator,
Rectangle worldBounds) {
SpatialContextFactory factory = new SpatialContextFactory();
factory.geo = geo;
factory.distCalc = calculator;
factory.worldBounds = worldBounds;
return factory;
}
@Deprecated
public SpatialContext(boolean geo) {
this(initFromLegacyConstructor(geo, null, null));
}
/**
* Called by {@link com.spatial4j.core.context.SpatialContextFactory#newSpatialContext()}.
*/
public SpatialContext(SpatialContextFactory factory) {
this.geo = factory.geo;
if (factory.distCalc == null) {
this.calculator = isGeo()
? new GeodesicSphereDistCalc.Haversine()
: new CartesianDistCalc();
} else {
this.calculator = factory.distCalc;
}
//TODO remove worldBounds from Spatial4j: see Issue #55
Rectangle bounds = factory.worldBounds;
if (bounds == null) {
this.worldBounds = isGeo()
? new RectangleImpl(-180, 180, -90, 90, this)
: new RectangleImpl(-Double.MAX_VALUE, Double.MAX_VALUE,
-Double.MAX_VALUE, Double.MAX_VALUE, this);
} else {
if (isGeo() && !bounds.equals(new RectangleImpl(-180, 180, -90, 90, this)))
throw new IllegalArgumentException("for geo (lat/lon), bounds must be " + GEO.getWorldBounds());
if (bounds.getMinX() > bounds.getMaxX())
throw new IllegalArgumentException("worldBounds minX should be <= maxX: "+ bounds);
if (bounds.getMinY() > bounds.getMaxY())
throw new IllegalArgumentException("worldBounds minY should be <= maxY: "+ bounds);
//hopefully worldBounds' rect implementation is compatible
this.worldBounds = new RectangleImpl(bounds, this);
}
this.normWrapLongitude = factory.normWrapLongitude && this.isGeo();
this.wktShapeParser = factory.makeWktShapeParser(this);
this.binaryCodec = factory.makeBinaryCodec(this);
}
public DistanceCalculator getDistCalc() {
return calculator;
}
/** Convenience that uses {@link #getDistCalc()} */
public double calcDistance(Point p, double x2, double y2) {
return getDistCalc().distance(p, x2, y2);
}
/** Convenience that uses {@link #getDistCalc()} */
public double calcDistance(Point p, Point p2) {
return getDistCalc().distance(p, p2);
}
/**
* The extent of x & y coordinates should fit within the return'ed rectangle.
* Do *NOT* invoke reset() on this return type.
*/
public Rectangle getWorldBounds() {
return worldBounds;
}
/** If true then {@link #normX(double)} will wrap longitudes outside of the standard
* geodetic boundary into it. Example: 181 will become -179. */
public boolean isNormWrapLongitude() {
return normWrapLongitude;
}
/** Is the mathematical world model based on a sphere, or is it a flat plane? The word
* "geodetic" or "geodesic" is sometimes used to refer to the former, and the latter is sometimes
* referred to as "Euclidean" or "cartesian". */
public boolean isGeo() {
return geo;
}
/** Normalize the 'x' dimension. Might reduce precision or wrap it to be within the bounds. This
* is called by {@link com.spatial4j.core.io.WktShapeParser} before creating a shape. */
public double normX(double x) {
if (normWrapLongitude)
x = DistanceUtils.normLonDEG(x);
return x;
}
/** Normalize the 'y' dimension. Might reduce precision or wrap it to be within the bounds. This
* is called by {@link com.spatial4j.core.io.WktShapeParser} before creating a shape. */
public double normY(double y) { return y; }
/** Ensure fits in {@link #getWorldBounds()}. It's called by any shape factory method that
* gets an 'x' dimension. */
public void verifyX(double x) {
Rectangle bounds = getWorldBounds();
if (x < bounds.getMinX() || x > bounds.getMaxX())//NaN will pass
throw new InvalidShapeException("Bad X value "+x+" is not in boundary "+bounds);
}
/** Ensure fits in {@link #getWorldBounds()}. It's called by any shape factory method that
* gets a 'y' dimension. */
public void verifyY(double y) {
Rectangle bounds = getWorldBounds();
if (y < bounds.getMinY() || y > bounds.getMaxY())//NaN will pass
throw new InvalidShapeException("Bad Y value "+y+" is not in boundary "+bounds);
}
/** Construct a point. */
public Point makePoint(double x, double y) {
verifyX(x);
verifyY(y);
return new PointImpl(x, y, this);
}
/** Construct a rectangle. */
public Rectangle makeRectangle(Point lowerLeft, Point upperRight) {
return makeRectangle(lowerLeft.getX(), upperRight.getX(),
lowerLeft.getY(), upperRight.getY());
}
/**
* Construct a rectangle. If just one longitude is on the dateline (+/- 180)
* then potentially adjust its sign to ensure the rectangle does not cross the
* dateline.
*/
public Rectangle makeRectangle(double minX, double maxX, double minY, double maxY) {
Rectangle bounds = getWorldBounds();
// Y
if (minY < bounds.getMinY() || maxY > bounds.getMaxY())//NaN will pass
throw new InvalidShapeException("Y values ["+minY+" to "+maxY+"] not in boundary "+bounds);
if (minY > maxY)
throw new InvalidShapeException("maxY must be >= minY: " + minY + " to " + maxY);
// X
if (isGeo()) {
verifyX(minX);
verifyX(maxX);
//TODO consider removing this logic so that there is no normalization here
//if (minX != maxX) { USUALLY TRUE, inline check below
//If an edge coincides with the dateline then don't make this rect cross it
if (minX == 180 && minX != maxX) {
minX = -180;
} else if (maxX == -180 && minX != maxX) {
maxX = 180;
}
//}
} else {
if (minX < bounds.getMinX() || maxX > bounds.getMaxX())//NaN will pass
throw new InvalidShapeException("X values ["+minX+" to "+maxX+"] not in boundary "+bounds);
if (minX > maxX)
throw new InvalidShapeException("maxX must be >= minX: " + minX + " to " + maxX);
}
return new RectangleImpl(minX, maxX, minY, maxY, this);
}
/** Construct a circle. The units of "distance" should be the same as x & y. */
public Circle makeCircle(double x, double y, double distance) {
return makeCircle(makePoint(x, y), distance);
}
/** Construct a circle. The units of "distance" should be the same as x & y. */
public Circle makeCircle(Point point, double distance) {
if (distance < 0)
throw new InvalidShapeException("distance must be >= 0; got " + distance);
if (isGeo()) {
if (distance > 180) {
// (it's debatable whether to error or not)
//throw new InvalidShapeException("distance must be <= 180; got " + distance);
distance = 180;
}
return new GeoCircle(point, distance, this);
} else {
return new CircleImpl(point, distance, this);
}
}
/** Constructs a line string. It's an ordered sequence of connected vertexes. There
* is no official shape/interface for it yet so we just return Shape. */
public Shape makeLineString(List points) {
return new BufferedLineString(points, 0, false, this);
}
/** Constructs a buffered line string. It's an ordered sequence of connected vertexes,
* with a buffer distance along the line in all directions. There
* is no official shape/interface for it so we just return Shape. */
public Shape makeBufferedLineString(List points, double buf) {
return new BufferedLineString(points, buf, isGeo(), this);
}
/** Construct a ShapeCollection, analogous to an OGC GeometryCollection. */
public ShapeCollection makeCollection(List coll) {
return new ShapeCollection(coll, this);
}
/** The {@link com.spatial4j.core.io.WktShapeParser} used by {@link #readShapeFromWkt(String)}. */
public WktShapeParser getWktShapeParser() {
return wktShapeParser;
}
/** Reads a shape from the string formatted in WKT.
* @see com.spatial4j.core.io.WktShapeParser
* @param wkt non-null WKT.
* @return non-null
* @throws ParseException if it failed to parse.
*/
public Shape readShapeFromWkt(String wkt) throws ParseException {
return wktShapeParser.parse(wkt);
}
public BinaryCodec getBinaryCodec() { return binaryCodec; }
/** Reads the shape from a String using the old/deprecated
* {@link com.spatial4j.core.io.LegacyShapeReadWriterFormat}.
* Instead you should use standard WKT via {@link #readShapeFromWkt(String)}. This method falls
* back on WKT if it's not in the legacy format.
* @param value non-null
* @return non-null
*/
@Deprecated
public Shape readShape(String value) throws InvalidShapeException {
Shape s = LegacyShapeReadWriterFormat.readShapeOrNull(value, this);
if (s == null) {
try {
s = readShapeFromWkt(value);
} catch (ParseException e) {
if (e.getCause() instanceof InvalidShapeException)
throw (InvalidShapeException) e.getCause();
throw new InvalidShapeException(e.toString(), e);
}
}
return s;
}
/** Writes the shape to a String using the old/deprecated
* {@link com.spatial4j.core.io.LegacyShapeReadWriterFormat}. The JTS based subclass will write it
* to WKT if the legacy format doesn't support that shape.
* Spatial4j in the near future won't support writing shapes to strings.
* @param shape non-null
* @return non-null
*/
@Deprecated
public String toString(Shape shape) {
return LegacyShapeReadWriterFormat.writeShape(shape);
}
@Override
public String toString() {
if (this.equals(GEO)) {
return GEO.getClass().getSimpleName()+".GEO";
} else {
return getClass().getSimpleName()+"{" +
"geo=" + geo +
", calculator=" + calculator +
", worldBounds=" + worldBounds +
'}';
}
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/context/SpatialContextFactory.java 0000664 0000000 0000000 00000020314 12417676335 0030602 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.context;
import com.spatial4j.core.distance.CartesianDistCalc;
import com.spatial4j.core.distance.DistanceCalculator;
import com.spatial4j.core.distance.GeodesicSphereDistCalc;
import com.spatial4j.core.io.BinaryCodec;
import com.spatial4j.core.io.WktShapeParser;
import com.spatial4j.core.shape.Rectangle;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Map;
/**
* Factory for a {@link SpatialContext} based on configuration data. Call
* {@link #makeSpatialContext(java.util.Map, ClassLoader)} to construct one via String name-value
* pairs. To construct one via code then create a factory instance, set the fields, then call
* {@link #newSpatialContext()}.
*
* The following keys are looked up in the args map:
*
*
spatialContextFactory
*
com.spatial4j.core.context.SpatialContext or
* com.spatial4j.core.context.jts.JtsSpatialContext
*
geo
*
true (default)| false -- see {@link SpatialContext#isGeo()}
{@code ENVELOPE(xMin, xMax, yMax, yMin)} -- see {@link SpatialContext#getWorldBounds()}
*
normWrapLongitude
*
true | false (default) -- see {@link SpatialContext#isNormWrapLongitude()}
*
*/
public class SpatialContextFactory {
/** Set by {@link #makeSpatialContext(java.util.Map, ClassLoader)}. */
protected Map args;
/** Set by {@link #makeSpatialContext(java.util.Map, ClassLoader)}. */
protected ClassLoader classLoader;
/* These fields are public to make it easy to set them without bothering with setters. */
public boolean geo = true;
public DistanceCalculator distCalc;//defaults in SpatialContext c'tor based on geo
public Rectangle worldBounds;//defaults in SpatialContext c'tor based on geo
public boolean normWrapLongitude = false;
public Class extends WktShapeParser> wktShapeParserClass = WktShapeParser.class;
public Class extends BinaryCodec> binaryCodecClass = BinaryCodec.class;
/**
* Creates a new {@link SpatialContext} based on configuration in
* args. See the class definition for what keys are looked up
* in it.
* The factory class is looked up via "spatialContextFactory" in args
* then falling back to a Java system property (with initial caps). If neither are specified
* then {@link SpatialContextFactory} is chosen.
*
* @param args Non-null map of name-value pairs.
* @param classLoader Optional, except when a class name is provided to an
* argument.
*/
public static SpatialContext makeSpatialContext(Map args, ClassLoader classLoader) {
if (classLoader == null)
classLoader = SpatialContextFactory.class.getClassLoader();
SpatialContextFactory instance;
String cname = args.get("spatialContextFactory");
if (cname == null)
cname = System.getProperty("SpatialContextFactory");
if (cname == null)
instance = new SpatialContextFactory();
else {
try {
Class c = classLoader.loadClass(cname);
instance = (SpatialContextFactory) c.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
instance.init(args,classLoader);
return instance.newSpatialContext();
}
protected void init(Map args, ClassLoader classLoader) {
this.args = args;
this.classLoader = classLoader;
initField("geo");
initCalculator();
//init wktParser before worldBounds because WB needs to be parsed
initField("wktShapeParserClass");
initWorldBounds();
initField("normWrapLongitude");
initField("binaryCodecClass");
}
/** Gets {@code name} from args and populates a field by the same name with the value. */
protected void initField(String name) {
// note: java.beans API is more verbose to use correctly (?) but would arguably be better
Field field;
try {
field = getClass().getField(name);
} catch (NoSuchFieldException e) {
throw new Error(e);
}
String str = args.get(name);
if (str != null) {
try {
Object o;
if (field.getType() == Boolean.TYPE) {
o = Boolean.valueOf(str);
} else if (field.getType() == Class.class) {
try {
o = classLoader.loadClass(str);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
} else if (field.getType().isEnum()) {
o = Enum.valueOf(field.getType().asSubclass(Enum.class), str);
} else {
throw new Error("unsupported field type: "+field.getType());//not plausible at runtime unless developing
}
field.set(this, o);
} catch (IllegalAccessException e) {
throw new Error(e);
} catch (Exception e) {
throw new RuntimeException(
"Invalid value '"+str+"' on field "+name+" of type "+field.getType(), e);
}
}
}
protected void initCalculator() {
String calcStr = args.get("distCalculator");
if (calcStr == null)
return;
if (calcStr.equalsIgnoreCase("haversine")) {
distCalc = new GeodesicSphereDistCalc.Haversine();
} else if (calcStr.equalsIgnoreCase("lawOfCosines")) {
distCalc = new GeodesicSphereDistCalc.LawOfCosines();
} else if (calcStr.equalsIgnoreCase("vincentySphere")) {
distCalc = new GeodesicSphereDistCalc.Vincenty();
} else if (calcStr.equalsIgnoreCase("cartesian")) {
distCalc = new CartesianDistCalc();
} else if (calcStr.equalsIgnoreCase("cartesian^2")) {
distCalc = new CartesianDistCalc(true);
} else {
throw new RuntimeException("Unknown calculator: "+calcStr);
}
}
protected void initWorldBounds() {
String worldBoundsStr = args.get("worldBounds");
if (worldBoundsStr == null)
return;
//kinda ugly we do this just to read a rectangle. TODO refactor
final SpatialContext ctx = newSpatialContext();
worldBounds = (Rectangle) ctx.readShape(worldBoundsStr);//TODO use readShapeFromWkt
}
/** Subclasses should simply construct the instance from the initialized configuration. */
public SpatialContext newSpatialContext() {
return new SpatialContext(this);
}
public WktShapeParser makeWktShapeParser(SpatialContext ctx) {
return makeClassInstance(wktShapeParserClass, ctx, this);
}
public BinaryCodec makeBinaryCodec(SpatialContext ctx) {
return makeClassInstance(binaryCodecClass, ctx, this);
}
@SuppressWarnings("unchecked")
private T makeClassInstance(Class extends T> clazz, Object... ctorArgs) {
try {
//can't simply lookup constructor by arg type because might be subclass type
ctorLoop: for (Constructor> ctor : clazz.getConstructors()) {
Class[] parameterTypes = ctor.getParameterTypes();
if (parameterTypes.length != ctorArgs.length)
continue;
for (int i = 0; i < ctorArgs.length; i++) {
Object ctorArg = ctorArgs[i];
if (!parameterTypes[i].isAssignableFrom(ctorArg.getClass()))
continue ctorLoop;
}
return clazz.cast(ctor.newInstance(ctorArgs));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
throw new RuntimeException(clazz + " needs a constructor that takes: "
+ Arrays.toString(ctorArgs));
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/context/jts/ 0000775 0000000 0000000 00000000000 12417676335 0024245 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/context/jts/JtsSpatialContext.java 0000775 0000000 0000000 00000020771 12417676335 0030545 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.context.jts;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.spatial4j.core.shape.jts.JtsPoint;
import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.util.GeometricShapeFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Enhances the default {@link SpatialContext} with support for Polygons (and
* other geometries) using JTS.
* To the extent possible, our {@link JtsGeometry} adds some amount of geodetic support over
* vanilla JTS which only has a Euclidean (flat plane) model.
*/
public class JtsSpatialContext extends SpatialContext {
public static final JtsSpatialContext GEO;
static {
JtsSpatialContextFactory factory = new JtsSpatialContextFactory();
factory.geo = true;
GEO = new JtsSpatialContext(factory);
}
protected final GeometryFactory geometryFactory;
protected final boolean allowMultiOverlap;
protected final boolean useJtsPoint;
protected final boolean useJtsLineString;
/**
* Called by {@link com.spatial4j.core.context.jts.JtsSpatialContextFactory#newSpatialContext()}.
*/
public JtsSpatialContext(JtsSpatialContextFactory factory) {
super(factory);
this.geometryFactory = factory.getGeometryFactory();
this.allowMultiOverlap = factory.allowMultiOverlap;
this.useJtsPoint = factory.useJtsPoint;
this.useJtsLineString = factory.useJtsLineString;
}
/**
* If geom might be a multi geometry of some kind, then might multiple
* component geometries overlap? Strict OGC says this is invalid but we
* can accept it by computing the union. Note: Our ShapeCollection mostly
* doesn't care but it has a method related to this
* {@link com.spatial4j.core.shape.ShapeCollection#relateContainsShortCircuits()}.
*/
public boolean isAllowMultiOverlap() {
return allowMultiOverlap;
}
@Override
public double normX(double x) {
x = super.normX(x);
return geometryFactory.getPrecisionModel().makePrecise(x);
}
@Override
public double normY(double y) {
y = super.normY(y);
return geometryFactory.getPrecisionModel().makePrecise(y);
}
@Override
public String toString(Shape shape) {
//Note: this logic is from the defunct JtsShapeReadWriter
if (shape instanceof JtsGeometry) {
JtsGeometry jtsGeom = (JtsGeometry) shape;
return jtsGeom.getGeom().toText();
}
//Note: doesn't handle ShapeCollection or BufferedLineString
return super.toString(shape);
}
/**
* Gets a JTS {@link Geometry} for the given {@link Shape}. Some shapes hold a
* JTS geometry whereas new ones must be created for the rest.
* @param shape Not null
* @return Not null
*/
public Geometry getGeometryFrom(Shape shape) {
if (shape instanceof JtsGeometry) {
return ((JtsGeometry)shape).getGeom();
}
if (shape instanceof JtsPoint) {
return ((JtsPoint) shape).getGeom();
}
if (shape instanceof Point) {
Point point = (Point) shape;
return geometryFactory.createPoint(new Coordinate(point.getX(),point.getY()));
}
if (shape instanceof Rectangle) {
Rectangle r = (Rectangle)shape;
if (r.getCrossesDateLine()) {
Collection pair = new ArrayList(2);
pair.add(geometryFactory.toGeometry(new Envelope(
r.getMinX(), getWorldBounds().getMaxX(), r.getMinY(), r.getMaxY())));
pair.add(geometryFactory.toGeometry(new Envelope(
getWorldBounds().getMinX(), r.getMaxX(), r.getMinY(), r.getMaxY())));
return geometryFactory.buildGeometry(pair);//a MultiPolygon or MultiLineString
} else {
return geometryFactory.toGeometry(new Envelope(r.getMinX(), r.getMaxX(), r.getMinY(), r.getMaxY()));
}
}
if (shape instanceof Circle) {
// FYI Some interesting code for this is here:
// http://docs.codehaus.org/display/GEOTDOC/01+How+to+Create+a+Geometry#01HowtoCreateaGeometry-CreatingaCircle
//TODO This should ideally have a geodetic version
Circle circle = (Circle)shape;
if (circle.getBoundingBox().getCrossesDateLine())
throw new IllegalArgumentException("Doesn't support dateline cross yet: "+circle);//TODO
GeometricShapeFactory gsf = new GeometricShapeFactory(geometryFactory);
gsf.setSize(circle.getBoundingBox().getWidth());
gsf.setNumPoints(4*25);//multiple of 4 is best
gsf.setCentre(new Coordinate(circle.getCenter().getX(), circle.getCenter().getY()));
return gsf.createCircle();
}
//TODO add BufferedLineString
throw new InvalidShapeException("can't make Geometry from: " + shape);
}
/** Should {@link #makePoint(double, double)} return {@link JtsPoint}? */
public boolean useJtsPoint() {
return useJtsPoint;
}
@Override
public Point makePoint(double x, double y) {
if (!useJtsPoint())
return super.makePoint(x, y);
//A Jts Point is fairly heavyweight! TODO could/should we optimize this? SingleCoordinateSequence
verifyX(x);
verifyY(y);
Coordinate coord = Double.isNaN(x) ? null : new Coordinate(x, y);
return new JtsPoint(geometryFactory.createPoint(coord), this);
}
/** Should {@link #makeLineString(java.util.List)} return {@link JtsGeometry}? */
public boolean useJtsLineString() {
//BufferedLineString doesn't yet do dateline cross, and can't yet be relate()'ed with a
// JTS geometry
return useJtsLineString;
}
@Override
public Shape makeLineString(List points) {
if (!useJtsLineString())
return super.makeLineString(points);
//convert List to Coordinate[]
Coordinate[] coords = new Coordinate[points.size()];
for (int i = 0; i < coords.length; i++) {
Point p = points.get(i);
if (p instanceof JtsPoint) {
JtsPoint jtsPoint = (JtsPoint) p;
coords[i] = jtsPoint.getGeom().getCoordinate();
} else {
coords[i] = new Coordinate(p.getX(), p.getY());
}
}
LineString lineString = geometryFactory.createLineString(coords);
return makeShape(lineString);
}
/**
* INTERNAL
* @see #makeShape(com.vividsolutions.jts.geom.Geometry)
*
* @param geom Non-null
* @param dateline180Check if both this is true and {@link #isGeo()}, then JtsGeometry will check
* for adjacent coordinates greater than 180 degrees longitude apart, and
* it will do tricks to make that line segment (and the shape as a whole)
* cross the dateline even though JTS doesn't have geodetic support.
* @param allowMultiOverlap See {@link #isAllowMultiOverlap()}.
*/
public JtsGeometry makeShape(Geometry geom, boolean dateline180Check, boolean allowMultiOverlap) {
return new JtsGeometry(geom, this, dateline180Check, allowMultiOverlap);
}
/**
* INTERNAL: Creates a {@link Shape} from a JTS {@link Geometry}. Generally, this shouldn't be
* called when one of the other factory methods are available, such as for points. The caller
* needs to have done some verification/normalization of the coordinates by now, if any.
*/
public JtsGeometry makeShape(Geometry geom) {
return makeShape(geom, true/*dateline180Check*/, allowMultiOverlap);
}
public GeometryFactory getGeometryFactory() {
return geometryFactory;
}
@Override
public String toString() {
if (this.equals(GEO)) {
return GEO.getClass().getSimpleName()+".GEO";
} else {
return super.toString();
}
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/context/jts/JtsSpatialContextFactory.java 0000664 0000000 0000000 00000011466 12417676335 0032073 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.context.jts;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.io.jts.JtsBinaryCodec;
import com.spatial4j.core.io.jts.JtsWktShapeParser;
import com.vividsolutions.jts.geom.CoordinateSequenceFactory;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.geom.impl.CoordinateArraySequenceFactory;
import java.util.Map;
/**
* See {@link SpatialContextFactory#makeSpatialContext(java.util.Map, ClassLoader)}.
*
* The following keys are looked up in the args map, in addition to those in the
* superclass:
*
*
datelineRule
*
width180(default)|ccwRect|none
* -- see {@link com.spatial4j.core.io.jts.JtsWktShapeParser.DatelineRule}
*
validationRule
*
error(default)|none|repairConvexHull|repairBuffer0
* -- see {@link com.spatial4j.core.io.jts.JtsWktShapeParser.ValidationRule}
*
autoIndex
*
true|false(default) -- see {@link JtsWktShapeParser#isAutoIndex()}
*
allowMultiOverlap
*
true|false(default) -- see {@link JtsSpatialContext#isAllowMultiOverlap()}
*
precisionModel
*
floating(default) | floating_single | fixed
* -- see {@link com.vividsolutions.jts.geom.PrecisionModel}.
* If {@code fixed} then you must also provide {@code precisionScale}
* -- see {@link com.vividsolutions.jts.geom.PrecisionModel#getScale()}
*
*/
public class JtsSpatialContextFactory extends SpatialContextFactory {
protected static final PrecisionModel defaultPrecisionModel = new PrecisionModel();//floating
//These 3 are JTS defaults for new GeometryFactory()
public PrecisionModel precisionModel = defaultPrecisionModel;
public int srid = 0;
public CoordinateSequenceFactory coordinateSequenceFactory = CoordinateArraySequenceFactory.instance();
//ignored if geo=false
public JtsWktShapeParser.DatelineRule datelineRule = JtsWktShapeParser.DatelineRule.width180;
public JtsWktShapeParser.ValidationRule validationRule = JtsWktShapeParser.ValidationRule.error;
public boolean autoIndex = false;
public boolean allowMultiOverlap = false;//ignored if geo=false
//kinda advanced options:
public boolean useJtsPoint = true;
public boolean useJtsLineString = true;
public JtsSpatialContextFactory() {
super.wktShapeParserClass = JtsWktShapeParser.class;
super.binaryCodecClass = JtsBinaryCodec.class;
}
@Override
protected void init(Map args, ClassLoader classLoader) {
super.init(args, classLoader);
initField("datelineRule");
initField("validationRule");
initField("autoIndex");
initField("allowMultiOverlap");
initField("useJtsPoint");
initField("useJtsLineString");
String scaleStr = args.get("precisionScale");
String modelStr = args.get("precisionModel");
if (scaleStr != null) {
if (modelStr != null && !modelStr.equals("fixed"))
throw new RuntimeException("Since precisionScale was specified; precisionModel must be 'fixed' but got: "+modelStr);
precisionModel = new PrecisionModel(Double.parseDouble(scaleStr));
} else if (modelStr != null) {
if (modelStr.equals("floating")) {
precisionModel = new PrecisionModel(PrecisionModel.FLOATING);
} else if (modelStr.equals("floating_single")) {
precisionModel = new PrecisionModel(PrecisionModel.FLOATING_SINGLE);
} else if (modelStr.equals("fixed")) {
throw new RuntimeException("For fixed model, must specifiy 'precisionScale'");
} else {
throw new RuntimeException("Unknown precisionModel: "+modelStr);
}
}
}
public GeometryFactory getGeometryFactory() {
if (precisionModel == null || coordinateSequenceFactory == null)
throw new IllegalStateException("precision model or coord seq factory can't be null");
return new GeometryFactory(precisionModel, srid, coordinateSequenceFactory);
}
@Override
public JtsSpatialContext newSpatialContext() {
return new JtsSpatialContext(this);
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/context/package-info.java 0000664 0000000 0000000 00000001621 12417676335 0026634 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.
*/
/** SpatialContext implementations are the facade to the Spatial4j API. */
package com.spatial4j.core.context; spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/distance/ 0000775 0000000 0000000 00000000000 12417676335 0023553 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/distance/AbstractDistanceCalculator.java 0000664 0000000 0000000 00000002444 12417676335 0031652 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.distance;
import com.spatial4j.core.shape.Point;
/**
*/
public abstract class AbstractDistanceCalculator implements DistanceCalculator {
@Override
public double distance(Point from, Point to) {
return distance(from, to.getX(), to.getY());
}
@Override
public boolean within(Point from, double toX, double toY, double distance) {
return distance(from, toX, toY) <= distance;
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/distance/CartesianDistCalc.java 0000664 0000000 0000000 00000007562 12417676335 0027750 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.distance;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
/**
* Calculates based on Euclidean / Cartesian 2d plane.
*/
public class CartesianDistCalc extends AbstractDistanceCalculator {
private final boolean squared;
public CartesianDistCalc() {
this.squared = false;
}
/**
* @param squared Set to true to have {@link #distance(com.spatial4j.core.shape.Point, com.spatial4j.core.shape.Point)}
* return the square of the correct answer. This is a
* performance optimization used when sorting in which the
* actual distance doesn't matter so long as the sort order is
* consistent.
*/
public CartesianDistCalc(boolean squared) {
this.squared = squared;
}
@Override
public double distance(Point from, double toX, double toY) {
double deltaX = from.getX() - toX;
double deltaY = from.getY() - toY;
double xSquaredPlusYSquared = deltaX*deltaX + deltaY*deltaY;
if (squared)
return xSquaredPlusYSquared;
return Math.sqrt(xSquaredPlusYSquared);
}
@Override
public boolean within(Point from, double toX, double toY, double distance) {
double deltaX = from.getX() - toX;
double deltaY = from.getY() - toY;
return deltaX*deltaX + deltaY*deltaY <= distance*distance;
}
@Override
public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse) {
if (distDEG == 0) {
if (reuse == null)
return from;
reuse.reset(from.getX(), from.getY());
return reuse;
}
double bearingRAD = DistanceUtils.toRadians(bearingDEG);
double x = from.getX() + Math.sin(bearingRAD) * distDEG;
double y = from.getY() + Math.cos(bearingRAD) * distDEG;
if (reuse == null) {
return ctx.makePoint(x, y);
} else {
reuse.reset(x, y);
return reuse;
}
}
@Override
public Rectangle calcBoxByDistFromPt(Point from, double distDEG, SpatialContext ctx, Rectangle reuse) {
double minX = from.getX() - distDEG;
double maxX = from.getX() + distDEG;
double minY = from.getY() - distDEG;
double maxY = from.getY() + distDEG;
if (reuse == null) {
return ctx.makeRectangle(minX, maxX, minY, maxY);
} else {
reuse.reset(minX, maxX, minY, maxY);
return reuse;
}
}
@Override
public double calcBoxByDistFromPt_yHorizAxisDEG(Point from, double distDEG, SpatialContext ctx) {
return from.getY();
}
@Override
public double area(Rectangle rect) {
return rect.getArea(null);
}
@Override
public double area(Circle circle) {
return circle.getArea(null);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CartesianDistCalc that = (CartesianDistCalc) o;
if (squared != that.squared) return false;
return true;
}
@Override
public int hashCode() {
return (squared ? 1 : 0);
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/distance/DistanceCalculator.java 0000664 0000000 0000000 00000005170 12417676335 0030165 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.distance;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
/**
* Performs calculations relating to distance, such as the distance between a pair of points. A
* calculator might be based on Euclidean space, or a spherical model, or theoretically something
* else like an ellipsoid.
*/
public interface DistanceCalculator {
/** The distance between from and to. */
public double distance(Point from, Point to);
/** The distance between from and Point(toX,toY). */
public double distance(Point from, double toX, double toY);
/** Returns true if the distance between from and to is <= distance. */
public boolean within(Point from, double toX, double toY, double distance);
/**
* Calculates where a destination point is given an origin (from)
* distance, and bearing (given in degrees -- 0-360). If reuse is given, then
* this method may reset() it and return it.
*/
public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse);
/**
* Calculates the bounding box of a circle, as specified by its center point
* and distance.
*/
public Rectangle calcBoxByDistFromPt(Point from, double distDEG, SpatialContext ctx, Rectangle reuse);
/**
* The Y coordinate of the horizontal axis of a circle that has maximum width. On a
* 2D plane, this result is always from.getY() but, perhaps surprisingly, on a sphere
* it is going to be slightly different.
*/
public double calcBoxByDistFromPt_yHorizAxisDEG(Point from, double distDEG, SpatialContext ctx);
public double area(Rectangle rect);
public double area(Circle circle);
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/distance/DistanceUtils.java 0000664 0000000 0000000 00000047327 12417676335 0027206 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.distance;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
/**
* Various distance calculations and constants. To the extent possible, a {@link
* com.spatial4j.core.distance.DistanceCalculator}, retrieved from {@link
* com.spatial4j.core.context.SpatialContext#getDistCalc()} should be used in preference to calling
* these methods directly.
*
* This code came from Apache
* Lucene, LUCENE-1387, which in turn came from "LocalLucene".
*/
public class DistanceUtils {
//pre-compute some angles that are commonly used
@Deprecated
public static final double DEG_45_AS_RADS = Math.PI / 4;
@Deprecated
public static final double SIN_45_AS_RADS = Math.sin(DEG_45_AS_RADS);
public static final double DEG_90_AS_RADS = Math.PI / 2;
public static final double DEG_180_AS_RADS = Math.PI;
@Deprecated
public static final double DEG_225_AS_RADS = 5 * DEG_45_AS_RADS;
@Deprecated
public static final double DEG_270_AS_RADS = 3 * DEG_90_AS_RADS;
public static final double DEGREES_TO_RADIANS = Math.PI / 180;
public static final double RADIANS_TO_DEGREES = 1 / DEGREES_TO_RADIANS;
public static final double KM_TO_MILES = 0.621371192;
public static final double MILES_TO_KM = 1 / KM_TO_MILES;//1.609
/**
* The International Union of Geodesy and Geophysics says the Earth's mean radius in KM is:
*
* [1] http://en.wikipedia.org/wiki/Earth_radius
*/
public static final double EARTH_MEAN_RADIUS_KM = 6371.0087714;
public static final double EARTH_EQUATORIAL_RADIUS_KM = 6378.1370;
/** Equivalent to degrees2Dist(1, EARTH_MEAN_RADIUS_KM) */
public static final double DEG_TO_KM = DEGREES_TO_RADIANS * EARTH_MEAN_RADIUS_KM;
public static final double KM_TO_DEG = 1 / DEG_TO_KM;
public static final double EARTH_MEAN_RADIUS_MI = EARTH_MEAN_RADIUS_KM * KM_TO_MILES;
public static final double EARTH_EQUATORIAL_RADIUS_MI = EARTH_EQUATORIAL_RADIUS_KM * KM_TO_MILES;
private DistanceUtils() {}
/**
* Calculate the p-norm (i.e. length) between two vectors.
*
* See Lp space
*
* @param vec1 The first vector
* @param vec2 The second vector
* @param power The power (2 for cartesian distance, 1 for manhattan, etc.)
* @return The length.
*
* @see #vectorDistance(double[], double[], double, double)
*
*/
@Deprecated
public static double vectorDistance(double[] vec1, double[] vec2, double power) {
//only calc oneOverPower if it's needed
double oneOverPower = (power == 0 || power == 1.0 || power == 2.0) ? Double.NaN : 1.0 / power;
return vectorDistance(vec1, vec2, power, oneOverPower);
}
/**
* Calculate the p-norm (i.e. length) between two vectors.
*
* @param vec1 The first vector
* @param vec2 The second vector
* @param power The power (2 for cartesian distance, 1 for manhattan, etc.)
* @param oneOverPower If you've pre-calculated oneOverPower and cached it, use this method to save
* one division operation over {@link #vectorDistance(double[], double[], double)}.
* @return The length.
*/
@Deprecated
public static double vectorDistance(double[] vec1, double[] vec2, double power, double oneOverPower) {
double result = 0;
if (power == 0) {
for (int i = 0; i < vec1.length; i++) {
result += vec1[i] - vec2[i] == 0 ? 0 : 1;
}
} else if (power == 1.0) { // Manhattan
for (int i = 0; i < vec1.length; i++) {
result += Math.abs(vec1[i] - vec2[i]);
}
} else if (power == 2.0) { // Cartesian
result = Math.sqrt(distSquaredCartesian(vec1, vec2));
} else if (power == Integer.MAX_VALUE || Double.isInfinite(power)) {//infinite norm?
for (int i = 0; i < vec1.length; i++) {
result = Math.max(result, Math.max(vec1[i], vec2[i]));
}
} else {
for (int i = 0; i < vec1.length; i++) {
result += Math.pow(vec1[i] - vec2[i], power);
}
result = Math.pow(result, oneOverPower);
}
return result;
}
/**
* Return the coordinates of a vector that is the corner of a box (upper right or lower left), assuming a Rectangular
* coordinate system. Note, this does not apply for points on a sphere or ellipse (although it could be used as an approximation).
*
* @param center The center point
* @param result Holds the result, potentially resizing if needed.
* @param distance The d from the center to the corner
* @param upperRight If true, return the coords for the upper right corner, else return the lower left.
* @return The point, either the upperLeft or the lower right
*/
@Deprecated
public static double[] vectorBoxCorner(double[] center, double[] result, double distance, boolean upperRight) {
if (result == null || result.length != center.length) {
result = new double[center.length];
}
if (upperRight == false) {
distance = -distance;
}
//We don't care about the power here,
// b/c we are always in a rectangular coordinate system, so any norm can be used by
//using the definition of sine
distance = SIN_45_AS_RADS * distance; // sin(Pi/4) == (2^0.5)/2 == opp/hyp == opp/distance, solve for opp, similarly for cosine
for (int i = 0; i < center.length; i++) {
result[i] = center[i] + distance;
}
return result;
}
/**
* Given a start point (startLat, startLon) and a bearing on a sphere, return the destination point.
*
* @param startLat The starting point latitude, in radians
* @param startLon The starting point longitude, in radians
* @param distanceRAD The distance to travel along the bearing in radians.
* @param bearingRAD The bearing, in radians. North is a 0, moving clockwise till radians(360).
* @param ctx
* @param reuse A preallocated object to hold the results.
* @return The destination point, IN RADIANS.
*/
public static Point pointOnBearingRAD(double startLat, double startLon, double distanceRAD, double bearingRAD, SpatialContext ctx, Point reuse) {
/*
lat2 = asin(sin(lat1)*cos(d/R) + cos(lat1)*sin(d/R)*cos(θ))
lon2 = lon1 + atan2(sin(θ)*sin(d/R)*cos(lat1), cos(d/R)−sin(lat1)*sin(lat2))
*/
double cosAngDist = Math.cos(distanceRAD);
double cosStartLat = Math.cos(startLat);
double sinAngDist = Math.sin(distanceRAD);
double sinStartLat = Math.sin(startLat);
double sinLat2 = sinStartLat * cosAngDist +
cosStartLat * sinAngDist * Math.cos(bearingRAD);
double lat2 = Math.asin(sinLat2);
double lon2 = startLon + Math.atan2(Math.sin(bearingRAD) * sinAngDist * cosStartLat,
cosAngDist - sinStartLat * sinLat2);
// normalize lon first
if (lon2 > DEG_180_AS_RADS) {
lon2 = -1.0 * (DEG_180_AS_RADS - (lon2 - DEG_180_AS_RADS));
} else if (lon2 < -DEG_180_AS_RADS) {
lon2 = (lon2 + DEG_180_AS_RADS) + DEG_180_AS_RADS;
}
// normalize lat - could flip poles
if (lat2 > DEG_90_AS_RADS) {
lat2 = DEG_90_AS_RADS - (lat2 - DEG_90_AS_RADS);
if (lon2 < 0) {
lon2 = lon2 + DEG_180_AS_RADS;
} else {
lon2 = lon2 - DEG_180_AS_RADS;
}
} else if (lat2 < -DEG_90_AS_RADS) {
lat2 = -DEG_90_AS_RADS - (lat2 + DEG_90_AS_RADS);
if (lon2 < 0) {
lon2 = lon2 + DEG_180_AS_RADS;
} else {
lon2 = lon2 - DEG_180_AS_RADS;
}
}
if (reuse == null) {
return ctx.makePoint(lon2, lat2);
} else {
reuse.reset(lon2, lat2);//x y
return reuse;
}
}
/**
* Puts in range -180 <= lon_deg <= +180.
*/
public static double normLonDEG(double lon_deg) {
if (lon_deg >= -180 && lon_deg <= 180)
return lon_deg;//common case, and avoids slight double precision shifting
double off = (lon_deg + 180) % 360;
if (off < 0)
return 180 + off;
else if (off == 0 && lon_deg > 0)
return 180;
else
return -180 + off;
}
/**
* Puts in range -90 <= lat_deg <= 90.
*/
public static double normLatDEG(double lat_deg) {
if (lat_deg >= -90 && lat_deg <= 90)
return lat_deg;//common case, and avoids slight double precision shifting
double off = Math.abs((lat_deg + 90) % 360);
return (off <= 180 ? off : 360-off) - 90;
}
/**
* Calculates the bounding box of a circle, as specified by its center point
* and distance. reuse is an optional argument to store the
* results to avoid object creation.
*/
public static Rectangle calcBoxByDistFromPtDEG(double lat, double lon, double distDEG, SpatialContext ctx, Rectangle reuse) {
//See http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates Section 3.1, 3.2 and 3.3
double minX; double maxX; double minY; double maxY;
if (distDEG == 0) {
minX = lon; maxX = lon; minY = lat; maxY = lat;
} else if (distDEG >= 180) {//distance is >= opposite side of the globe
minX = -180; maxX = 180; minY = -90; maxY = 90;
} else {
//--calc latitude bounds
maxY = lat + distDEG;
minY = lat - distDEG;
if (maxY >= 90 || minY <= -90) {//touches either pole
//we have special logic for longitude
minX = -180; maxX = 180;//world wrap: 360 deg
if (maxY <= 90 && minY >= -90) {//doesn't pass either pole: 180 deg
minX = normLonDEG(lon - 90);
maxX = normLonDEG(lon + 90);
}
if (maxY > 90)
maxY = 90;
if (minY < -90)
minY = -90;
} else {
//--calc longitude bounds
double lon_delta_deg = calcBoxByDistFromPt_deltaLonDEG(lat, lon, distDEG);
minX = normLonDEG(lon - lon_delta_deg);
maxX = normLonDEG(lon + lon_delta_deg);
}
}
if (reuse == null) {
return ctx.makeRectangle(minX, maxX, minY, maxY);
} else {
reuse.reset(minX, maxX, minY, maxY);
return reuse;
}
}
/**
* The delta longitude of a point-distance. In other words, half the width of
* the bounding box of a circle.
*/
public static double calcBoxByDistFromPt_deltaLonDEG(double lat, double lon, double distDEG) {
//http://gis.stackexchange.com/questions/19221/find-tangent-point-on-circle-furthest-east-or-west
if (distDEG == 0)
return 0;
double lat_rad = toRadians(lat);
double dist_rad = toRadians(distDEG);
double result_rad = Math.asin(Math.sin(dist_rad) / Math.cos(lat_rad));
if (!Double.isNaN(result_rad))
return toDegrees(result_rad);
return 90;
}
/**
* The latitude of the horizontal axis (e.g. left-right line)
* of a circle. The horizontal axis of a circle passes through its furthest
* left-most and right-most edges. On a 2D plane, this result is always
* from.getY() but, perhaps surprisingly, on a sphere it is going
* to be slightly different.
*/
public static double calcBoxByDistFromPt_latHorizAxisDEG(double lat, double lon, double distDEG) {
//http://gis.stackexchange.com/questions/19221/find-tangent-point-on-circle-furthest-east-or-west
if (distDEG == 0)
return lat;
// if we don't do this when == 90 or -90, computed result can be (+/-)89.9999 when at pole.
// No biggie but more accurate.
else if (lat + distDEG >= 90)
return 90;
else if (lat - distDEG <= -90)
return -90;
double lat_rad = toRadians(lat);
double dist_rad = toRadians(distDEG);
double result_rad = Math.asin( Math.sin(lat_rad) / Math.cos(dist_rad));
if (!Double.isNaN(result_rad))
return toDegrees(result_rad);
//handle NaN (shouldn't happen due to checks earlier)
if (lat > 0)
return 90;
if (lat < 0)
return -90;
return lat;//0
}
/**
* Calculates the degrees longitude distance at latitude {@code lat} to cover
* a distance {@code dist}.
*
* Used to calculate a new expanded buffer distance to account for skewing
* effects for shapes that use the lat-lon space as a 2D plane instead of a
* sphere. The expanded buffer will be sure to cover the intended area, but
* the shape is still skewed and so it will cover a larger area. For latitude
* 0 (the equator) the result is the same buffer. At 60 (or -60) degrees, the
* result is twice the buffer, meaning that a shape at 60 degrees is twice as
* high as it is wide when projected onto a lat-lon plane even if in the real
* world it's equal all around.
*
* If the result added to abs({@code lat}) is >= 90 degrees, then skewing is
* so severe that the caller should consider tossing the shape and
* substituting a spherical cap instead.
*
* @param lat latitude in degrees
* @param dist distance in degrees
* @return longitudinal degrees (x delta) at input latitude that is >= dist
* distance. Will be >= dist and <= 90.
*/
public static double calcLonDegreesAtLat(double lat, double dist) {
//This code was pulled out of DistanceUtils.pointOnBearingRAD() and
// optimized
// for bearing = 90 degrees, and so we can get an intermediate calculation.
double distanceRAD = DistanceUtils.toRadians(dist);
double startLat = DistanceUtils.toRadians(lat);
double cosAngDist = Math.cos(distanceRAD);
double cosStartLat = Math.cos(startLat);
double sinAngDist = Math.sin(distanceRAD);
double sinStartLat = Math.sin(startLat);
double lonDelta = Math.atan2(sinAngDist * cosStartLat,
cosAngDist * (1 - sinStartLat * sinStartLat));
return DistanceUtils.toDegrees(lonDelta);
}
/**
* The square of the cartesian Distance. Not really a distance, but useful if all that matters is
* comparing the result to another one.
*
* @param vec1 The first point
* @param vec2 The second point
* @return The squared cartesian distance
*/
@Deprecated
public static double distSquaredCartesian(double[] vec1, double[] vec2) {
double result = 0;
for (int i = 0; i < vec1.length; i++) {
double v = vec1[i] - vec2[i];
result += v * v;
}
return result;
}
/**
*
* @param lat1 The y coordinate of the first point, in radians
* @param lon1 The x coordinate of the first point, in radians
* @param lat2 The y coordinate of the second point, in radians
* @param lon2 The x coordinate of the second point, in radians
* @return The distance between the two points, as determined by the Haversine formula, in radians.
*/
public static double distHaversineRAD(double lat1, double lon1, double lat2, double lon2) {
//TODO investigate slightly different formula using asin() and min() http://www.movable-type.co.uk/scripts/gis-faq-5.1.html
// Check for same position
if (lat1 == lat2 && lon1 == lon2)
return 0.0;
double hsinX = Math.sin((lon1 - lon2) * 0.5);
double hsinY = Math.sin((lat1 - lat2) * 0.5);
double h = hsinY * hsinY +
(Math.cos(lat1) * Math.cos(lat2) * hsinX * hsinX);
return 2 * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h));
}
/**
* Calculates the distance between two lat-lon's using the Law of Cosines. Due to numeric conditioning
* errors, it is not as accurate as the Haversine formula for small distances. But with
* double precision, it isn't that bad --
* allegedly 1 meter.
*
* See
* Why is law of cosines more preferable than haversine when calculating distance between two latitude-longitude points?
*
* The arguments and return value are in radians.
*/
public static double distLawOfCosinesRAD(double lat1, double lon1, double lat2, double lon2) {
//TODO validate formula
//(MIGRATED FROM org.apache.lucene.spatial.geometry.LatLng.arcDistance()) (Lucene 3x)
// Imported from mq java client. Variable references changed to match.
// Check for same position
if (lat1 == lat2 && lon1 == lon2)
return 0.0;
// Get the m_dLongitude difference. Don't need to worry about
// crossing 180 since cos(x) = cos(-x)
double dLon = lon2 - lon1;
double a = DEG_90_AS_RADS - lat1;
double c = DEG_90_AS_RADS - lat2;
double cosB = (Math.cos(a) * Math.cos(c))
+ (Math.sin(a) * Math.sin(c) * Math.cos(dLon));
// Find angle subtended (with some bounds checking) in radians
if (cosB < -1.0)
return Math.PI;
else if (cosB >= 1.0)
return 0;
else
return Math.acos(cosB);
}
/**
* Calculates the great circle distance using the Vincenty Formula, simplified for a spherical model. This formula
* is accurate for any pair of points. The equation
* was taken from Wikipedia.
*
* The arguments are in radians, and the result is in radians.
*/
public static double distVincentyRAD(double lat1, double lon1, double lat2, double lon2) {
// Check for same position
if (lat1 == lat2 && lon1 == lon2)
return 0.0;
double cosLat1 = Math.cos(lat1);
double cosLat2 = Math.cos(lat2);
double sinLat1 = Math.sin(lat1);
double sinLat2 = Math.sin(lat2);
double dLon = lon2 - lon1;
double cosDLon = Math.cos(dLon);
double sinDLon = Math.sin(dLon);
double a = cosLat2 * sinDLon;
double b = cosLat1*sinLat2 - sinLat1*cosLat2*cosDLon;
double c = sinLat1*sinLat2 + cosLat1*cosLat2*cosDLon;
return Math.atan2(Math.sqrt(a*a+b*b),c);
}
/**
* Converts a distance in the units of the radius to degrees (360 degrees are
* in a circle). A spherical earth model is assumed.
*/
public static double dist2Degrees(double dist, double radius) {
return toDegrees(dist2Radians(dist, radius));
}
/**
* Converts degrees (1/360th of circumference of a circle) into a
* distance as measured by the units of the radius. A spherical earth model
* is assumed.
*/
public static double degrees2Dist(double degrees, double radius) {
return radians2Dist(toRadians(degrees), radius);
}
/**
* Converts a distance in the units of radius (e.g. kilometers)
* to radians (multiples of the radius). A spherical earth model is assumed.
*/
public static double dist2Radians(double dist, double radius) {
return dist / radius;
}
/**
* Converts radians (multiples of the radius) to
* distance in the units of the radius (e.g. kilometers).
*/
public static double radians2Dist(double radians, double radius) {
return radians * radius;
}
/**
* Same as {@link Math#toRadians(double)} but 3x faster (multiply vs. divide).
* See CompareRadiansSnippet.java in tests.
*/
public static double toRadians(double degrees) {
return degrees * DEGREES_TO_RADIANS;
}
/**
* Same as {@link Math#toDegrees(double)} but 3x faster (multiply vs. divide).
* See CompareRadiansSnippet.java in tests.
*/
public static double toDegrees(double radians) {
return radians * RADIANS_TO_DEGREES;
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/distance/GeodesicSphereDistCalc.java 0000664 0000000 0000000 00000010252 12417676335 0030716 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.distance;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import static com.spatial4j.core.distance.DistanceUtils.toDegrees;
import static com.spatial4j.core.distance.DistanceUtils.toRadians;
/**
* A base class for a Distance Calculator that assumes a spherical earth model.
*/
public abstract class GeodesicSphereDistCalc extends AbstractDistanceCalculator {
private static final double radiusDEG = DistanceUtils.toDegrees(1);//in degrees
@Override
public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse) {
if (distDEG == 0) {
if (reuse == null)
return from;
reuse.reset(from.getX(), from.getY());
return reuse;
}
Point result = DistanceUtils.pointOnBearingRAD(
toRadians(from.getY()), toRadians(from.getX()),
toRadians(distDEG),
toRadians(bearingDEG), ctx, reuse);//output result is in radians
result.reset(toDegrees(result.getX()), toDegrees(result.getY()));
return result;
}
@Override
public Rectangle calcBoxByDistFromPt(Point from, double distDEG, SpatialContext ctx, Rectangle reuse) {
return DistanceUtils.calcBoxByDistFromPtDEG(from.getY(), from.getX(), distDEG, ctx, reuse);
}
@Override
public double calcBoxByDistFromPt_yHorizAxisDEG(Point from, double distDEG, SpatialContext ctx) {
return DistanceUtils.calcBoxByDistFromPt_latHorizAxisDEG(from.getY(), from.getX(), distDEG);
}
@Override
public double area(Rectangle rect) {
//From http://mathforum.org/library/drmath/view/63767.html
double lat1 = toRadians(rect.getMinY());
double lat2 = toRadians(rect.getMaxY());
return Math.PI / 180 * radiusDEG * radiusDEG *
Math.abs(Math.sin(lat1) - Math.sin(lat2)) *
rect.getWidth();
}
@Override
public double area(Circle circle) {
//formula is a simplified case of area(rect).
double lat = toRadians(90 - circle.getRadius());
return 2 * Math.PI * radiusDEG * radiusDEG * (1 - Math.sin(lat));
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
return getClass().equals(obj.getClass());
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public final double distance(Point from, double toX, double toY) {
return toDegrees(distanceLatLonRAD(toRadians(from.getY()), toRadians(from.getX()), toRadians(toY), toRadians(toX)));
}
protected abstract double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2);
public static class Haversine extends GeodesicSphereDistCalc {
@Override
protected double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2) {
return DistanceUtils.distHaversineRAD(lat1,lon1,lat2,lon2);
}
}
public static class LawOfCosines extends GeodesicSphereDistCalc {
@Override
protected double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2) {
return DistanceUtils.distLawOfCosinesRAD(lat1, lon1, lat2, lon2);
}
}
public static class Vincenty extends GeodesicSphereDistCalc {
@Override
protected double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2) {
return DistanceUtils.distVincentyRAD(lat1, lon1, lat2, lon2);
}
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/distance/package-info.java 0000664 0000000 0000000 00000001560 12417676335 0026744 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.
*/
/**
* Ways to calculate distance.
*/
package com.spatial4j.core.distance;
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/exception/ 0000775 0000000 0000000 00000000000 12417676335 0023757 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/exception/InvalidShapeException.java 0000664 0000000 0000000 00000002510 12417676335 0031046 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.exception;
/**
* A shape was constructed but failed because, based on the given parts, it's invalid. For example
* a rectangle's minimum Y was specified as greater than the maximum Y. This class is not used for
* parsing exceptions; that's usually {@link java.text.ParseException}.
*/
public class InvalidShapeException extends RuntimeException {
public InvalidShapeException(String reason, Throwable cause) {
super(reason, cause);
}
public InvalidShapeException(String reason) {
super(reason);
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/ 0000775 0000000 0000000 00000000000 12417676335 0022370 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/BinaryCodec.java 0000664 0000000 0000000 00000014625 12417676335 0025425 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.SpatialContext;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.ShapeCollection;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
/**
* A binary shape format. It is not designed to be a published standard, unlike Well Known
* Binary (WKB). The initial release is simple but it could get more optimized to use fewer bytes or
* to write & read pre-computed index structures.
*
* Immutable and thread-safe.
*/
public class BinaryCodec {
//type 0; reserved for unkonwn/generic; see readCollection
protected static final byte
TYPE_POINT = 1,
TYPE_RECT = 2,
TYPE_CIRCLE = 3,
TYPE_COLL = 4,
TYPE_GEOM = 5;
//TODO support BufferedLineString
protected final SpatialContext ctx;
//This constructor is mandated by SpatialContextFactory
public BinaryCodec(SpatialContext ctx, SpatialContextFactory factory) {
this.ctx = ctx;
}
public Shape readShape(DataInput dataInput) throws IOException {
byte type = dataInput.readByte();
Shape s = readShapeByTypeIfSupported(dataInput, type);
if (s == null)
throw new IllegalArgumentException("Unsupported shape byte "+type);
return s;
}
public void writeShape(DataOutput dataOutput, Shape s) throws IOException {
boolean written = writeShapeByTypeIfSupported(dataOutput, s);
if (!written)
throw new IllegalArgumentException("Unsupported shape "+s.getClass());
}
protected Shape readShapeByTypeIfSupported(DataInput dataInput, byte type) throws IOException {
switch (type) {
case TYPE_POINT: return readPoint(dataInput);
case TYPE_RECT: return readRect(dataInput);
case TYPE_CIRCLE: return readCircle(dataInput);
case TYPE_COLL: return readCollection(dataInput);
default: return null;
}
}
/** Note: writes the type byte even if not supported */
protected boolean writeShapeByTypeIfSupported(DataOutput dataOutput, Shape s) throws IOException {
byte type = typeForShape(s);
dataOutput.writeByte(type);
return writeShapeByTypeIfSupported(dataOutput, s, type);
//dataOutput.position(dataOutput.position() - 1);//reset putting type
}
protected boolean writeShapeByTypeIfSupported(DataOutput dataOutput, Shape s, byte type) throws IOException {
switch (type) {
case TYPE_POINT: writePoint(dataOutput, (Point) s); break;
case TYPE_RECT: writeRect(dataOutput, (Rectangle) s); break;
case TYPE_CIRCLE: writeCircle(dataOutput, (Circle) s); break;
case TYPE_COLL: writeCollection(dataOutput, (ShapeCollection) s); break;
default:
return false;
}
return true;
}
protected byte typeForShape(Shape s) {
if (s instanceof Point) {
return TYPE_POINT;
} else if (s instanceof Rectangle) {
return TYPE_RECT;
} else if (s instanceof Circle) {
return TYPE_CIRCLE;
} else if (s instanceof ShapeCollection) {
return TYPE_COLL;
} else {
return 0;
}
}
protected double readDim(DataInput dataInput) throws IOException {
return dataInput.readDouble();
}
protected void writeDim(DataOutput dataOutput, double v) throws IOException {
dataOutput.writeDouble(v);
}
public Point readPoint(DataInput dataInput) throws IOException {
return ctx.makePoint(readDim(dataInput), readDim(dataInput));
}
public void writePoint(DataOutput dataOutput, Point pt) throws IOException {
writeDim(dataOutput, pt.getX());
writeDim(dataOutput, pt.getY());
}
public Rectangle readRect(DataInput dataInput) throws IOException {
return ctx.makeRectangle(readDim(dataInput), readDim(dataInput), readDim(dataInput), readDim(dataInput));
}
public void writeRect(DataOutput dataOutput, Rectangle r) throws IOException {
writeDim(dataOutput, r.getMinX());
writeDim(dataOutput, r.getMaxX());
writeDim(dataOutput, r.getMinY());
writeDim(dataOutput, r.getMaxY());
}
public Circle readCircle(DataInput dataInput) throws IOException {
return ctx.makeCircle(readPoint(dataInput), readDim(dataInput));
}
public void writeCircle(DataOutput dataOutput, Circle c) throws IOException {
writePoint(dataOutput, c.getCenter());
writeDim(dataOutput, c.getRadius());
}
public ShapeCollection readCollection(DataInput dataInput) throws IOException {
byte type = dataInput.readByte();
int size = dataInput.readInt();
ArrayList shapes = new ArrayList(size);
for (int i = 0; i < size; i++) {
if (type == 0) {
shapes.add(readShape(dataInput));
} else {
Shape s = readShapeByTypeIfSupported(dataInput, type);
if (s == null)
throw new InvalidShapeException("Unsupported shape byte "+type);
shapes.add(s);
}
}
return ctx.makeCollection(shapes);
}
public void writeCollection(DataOutput dataOutput, ShapeCollection col) throws IOException {
byte type = (byte) 0;//TODO add type to ShapeCollection
dataOutput.writeByte(type);
dataOutput.writeInt(col.size());
for (int i = 0; i < col.size(); i++) {
Shape s = col.get(i);
if (type == 0) {
writeShape(dataOutput, s);
} else {
boolean written = writeShapeByTypeIfSupported(dataOutput, s, type);
if (!written)
throw new IllegalArgumentException("Unsupported shape type "+s.getClass());
}
}
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/GeohashUtils.java 0000664 0000000 0000000 00000015176 12417676335 0025644 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.SpatialContext;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import java.util.Arrays;
/**
* Utilities for encoding and decoding geohashes.
*
* This class isn't used by any other part of Spatial4j; it's included largely for convenience of
* software using Spatial4j. There are other open-source libraries that have more comprehensive
* geohash utilities but providing this one avoids an additional dependency for what's a small
* amount of code. If you're using Spatial4j just for this class, consider alternatives.
*
* This code originally came from
* Apache Lucene, LUCENE-1512.
*/
public class GeohashUtils {
private static final char[] BASE_32 = {'0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};//note: this is sorted
private static final int[] BASE_32_IDX;//sparse array of indexes from '0' to 'z'
public static final int MAX_PRECISION = 24;//DWS: I forget what level results in needless more precision but it's about this
private static final int[] BITS = {16, 8, 4, 2, 1};
static {
BASE_32_IDX = new int[BASE_32[BASE_32.length-1] - BASE_32[0] + 1];
assert BASE_32_IDX.length < 100;//reasonable length
Arrays.fill(BASE_32_IDX,-500);
for (int i = 0; i < BASE_32.length; i++) {
BASE_32_IDX[BASE_32[i] - BASE_32[0]] = i;
}
}
private GeohashUtils() {
}
/**
* Encodes the given latitude and longitude into a geohash
*
* @param latitude Latitude to encode
* @param longitude Longitude to encode
* @return Geohash encoding of the longitude and latitude
*/
public static String encodeLatLon(double latitude, double longitude) {
return encodeLatLon(latitude, longitude, 12);
}
public static String encodeLatLon(double latitude, double longitude, int precision) {
double[] latInterval = {-90.0, 90.0};
double[] lngInterval = {-180.0, 180.0};
final StringBuilder geohash = new StringBuilder(precision);
boolean isEven = true;
int bit = 0;
int ch = 0;
while (geohash.length() < precision) {
double mid = 0.0;
if (isEven) {
mid = (lngInterval[0] + lngInterval[1]) / 2D;
if (longitude > mid) {
ch |= BITS[bit];
lngInterval[0] = mid;
} else {
lngInterval[1] = mid;
}
} else {
mid = (latInterval[0] + latInterval[1]) / 2D;
if (latitude > mid) {
ch |= BITS[bit];
latInterval[0] = mid;
} else {
latInterval[1] = mid;
}
}
isEven = !isEven;
if (bit < 4) {
bit++;
} else {
geohash.append(BASE_32[ch]);
bit = 0;
ch = 0;
}
}
return geohash.toString();
}
/**
* Decodes the given geohash into a latitude and longitude
*
* @param geohash Geohash to deocde
* @return Array with the latitude at index 0, and longitude at index 1
*/
public static Point decode(String geohash, SpatialContext ctx) {
Rectangle rect = decodeBoundary(geohash,ctx);
double latitude = (rect.getMinY() + rect.getMaxY()) / 2D;
double longitude = (rect.getMinX() + rect.getMaxX()) / 2D;
return ctx.makePoint(longitude,latitude);
}
/** Returns min-max lat, min-max lon. */
public static Rectangle decodeBoundary(String geohash, SpatialContext ctx) {
double minY = -90, maxY = 90, minX = -180, maxX = 180;
boolean isEven = true;
for (int i = 0; i < geohash.length(); i++) {
char c = geohash.charAt(i);
if (c >= 'A' && c <= 'Z')
c -= ('A' - 'a');
final int cd = BASE_32_IDX[c - BASE_32[0]];//TODO check successful?
for (int mask : BITS) {
if (isEven) {
if ((cd & mask) != 0) {
minX = (minX + maxX) / 2D;
} else {
maxX = (minX + maxX) / 2D;
}
} else {
if ((cd & mask) != 0) {
minY = (minY + maxY) / 2D;
} else {
maxY = (minY + maxY) / 2D;
}
}
isEven = !isEven;
}
}
return ctx.makeRectangle(minX, maxX, minY, maxY);
}
/** Array of geohashes 1 level below the baseGeohash. Sorted. */
public static String[] getSubGeohashes(String baseGeohash) {
String[] hashes = new String[BASE_32.length];
for (int i = 0; i < BASE_32.length; i++) {//note: already sorted
char c = BASE_32[i];
hashes[i] = baseGeohash+c;
}
return hashes;
}
public static double[] lookupDegreesSizeForHashLen(int hashLen) {
return new double[]{hashLenToLatHeight[hashLen], hashLenToLonWidth[hashLen]};
}
/**
* Return the shortest geohash length that will have a width & height >= specified arguments.
*/
public static int lookupHashLenForWidthHeight(double lonErr, double latErr) {
//loop through hash length arrays from beginning till we find one.
for(int len = 1; len < MAX_PRECISION; len++) {
double latHeight = hashLenToLatHeight[len];
double lonWidth = hashLenToLonWidth[len];
if (latHeight < latErr && lonWidth < lonErr)
return len;
}
return MAX_PRECISION;
}
/** See the table at http://en.wikipedia.org/wiki/Geohash */
private static final double[] hashLenToLatHeight, hashLenToLonWidth;
static {
hashLenToLatHeight = new double[MAX_PRECISION +1];
hashLenToLonWidth = new double[MAX_PRECISION +1];
hashLenToLatHeight[0] = 90*2;
hashLenToLonWidth[0] = 180*2;
boolean even = false;
for(int i = 1; i <= MAX_PRECISION; i++) {
hashLenToLatHeight[i] = hashLenToLatHeight[i-1]/(even?8:4);
hashLenToLonWidth[i] = hashLenToLonWidth[i-1]/(even?4:8);
even = ! even;
}
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/LegacyShapeReadWriterFormat.java 0000664 0000000 0000000 00000014610 12417676335 0030564 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.SpatialContext;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.StringTokenizer;
/**
* Reads & writes a shape from a given string in the old format.
*
*
Point: X Y
* 1.23 4.56
*
*
Rect: XMin YMin XMax YMax
* 1.23 4.56 7.87 4.56
*
*
{CIRCLE} '(' {POINT} {DISTANCE} ')'
* CIRCLE is "CIRCLE" or "Circle" (no other case), and POINT is "X Y" order pair of doubles, or
* "Y,X" (lat,lon) pair of doubles, and DISTANCE is "d=RADIUS" or "distance=RADIUS" where RADIUS
* is a double that is the distance radius in degrees.
*
*
*/
@Deprecated
public class LegacyShapeReadWriterFormat {
private LegacyShapeReadWriterFormat() {
}
/**
* Writes a shape to a String, in a format that can be read by
* {@link #readShapeOrNull(String, com.spatial4j.core.context.SpatialContext)}.
* @param shape Not null.
* @return Not null.
*/
public static String writeShape(Shape shape) {
return writeShape(shape, makeNumberFormat(6));
}
/** Overloaded to provide a number format. */
public static String writeShape(Shape shape, NumberFormat nf) {
if (shape instanceof Point) {
Point point = (Point) shape;
return nf.format(point.getX()) + " " + nf.format(point.getY());
}
else if (shape instanceof Rectangle) {
Rectangle rect = (Rectangle)shape;
return
nf.format(rect.getMinX()) + " " +
nf.format(rect.getMinY()) + " " +
nf.format(rect.getMaxX()) + " " +
nf.format(rect.getMaxY());
}
else if (shape instanceof Circle) {
Circle c = (Circle) shape;
return "Circle(" +
nf.format(c.getCenter().getX()) + " " +
nf.format(c.getCenter().getY()) + " " +
"d=" + nf.format(c.getRadius()) +
")";
}
return shape.toString();
}
/**
* A convenience method to create a suitable NumberFormat for writing numbers.
*/
public static NumberFormat makeNumberFormat(int fractionDigits) {
NumberFormat nf = NumberFormat.getInstance(Locale.ROOT);//not thread-safe
nf.setGroupingUsed(false);
nf.setMaximumFractionDigits(fractionDigits);
nf.setMinimumFractionDigits(fractionDigits);
return nf;
}
/** Reads the shape specification as defined in the class javadocs. If the first character is
* a letter but it doesn't complete out "Circle" or "CIRCLE" then this method returns null,
* offering the caller the opportunity to potentially try additional parsing.
* If the first character is not a letter then it's assumed to be a point or rectangle. If that
* doesn't work out then an {@link com.spatial4j.core.exception.InvalidShapeException} is thrown.
*/
public static Shape readShapeOrNull(String str, SpatialContext ctx) throws InvalidShapeException {
if (str == null || str.length() == 0) {
throw new InvalidShapeException(str);
}
if (Character.isLetter(str.charAt(0))) {
if (str.startsWith("Circle(") || str.startsWith("CIRCLE(")) {
int idx = str.lastIndexOf(')');
if (idx > 0) {
String body = str.substring("Circle(".length(), idx);
StringTokenizer st = new StringTokenizer(body, " ");
String token = st.nextToken();
Point pt;
if (token.indexOf(',') != -1) {
pt = readLatCommaLonPoint(token, ctx);
} else {
double x = Double.parseDouble(token);
double y = Double.parseDouble(st.nextToken());
pt = ctx.makePoint(x, y);
}
Double d = null;
String arg = st.nextToken();
idx = arg.indexOf('=');
if (idx > 0) {
String k = arg.substring(0, idx);
if (k.equals("d") || k.equals("distance")) {
d = Double.parseDouble(arg.substring(idx + 1));
} else {
throw new InvalidShapeException("unknown arg: " + k + " :: " + str);
}
} else {
d = Double.parseDouble(arg);
}
if (st.hasMoreTokens()) {
throw new InvalidShapeException("Extra arguments: " + st.nextToken() + " :: " + str);
}
if (d == null) {
throw new InvalidShapeException("Missing Distance: " + str);
}
//NOTE: we are assuming the units of 'd' is the same as that of the spatial context.
return ctx.makeCircle(pt, d);
}
}
return null;//caller has opportunity to try other parsing
}
if (str.indexOf(',') != -1)
return readLatCommaLonPoint(str, ctx);
StringTokenizer st = new StringTokenizer(str, " ");
double p0 = Double.parseDouble(st.nextToken());
double p1 = Double.parseDouble(st.nextToken());
if (st.hasMoreTokens()) {
double p2 = Double.parseDouble(st.nextToken());
double p3 = Double.parseDouble(st.nextToken());
if (st.hasMoreTokens())
throw new InvalidShapeException("Only 4 numbers supported (rect) but found more: " + str);
return ctx.makeRectangle(p0, p2, p1, p3);
}
return ctx.makePoint(p0, p1);
}
/** Reads geospatial latitude then a comma then longitude. */
private static Point readLatCommaLonPoint(String value, SpatialContext ctx) throws InvalidShapeException {
double[] latLon = ParseUtils.parseLatitudeLongitude(value);
return ctx.makePoint(latLon[1], latLon[0]);
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/ParseUtils.java 0000664 0000000 0000000 00000015225 12417676335 0025333 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.exception.InvalidShapeException;
/**
* Utility methods related to parsing a series of numbers.
*
* This code came from DistanceUtils, which came from
* Apache
* Lucene, LUCENE-773, which in turn came from "LocalLucene".
*
* @deprecated Not useful; see https://github.com/spatial4j/spatial4j/issues/19
*/
@Deprecated
public class ParseUtils {
private ParseUtils() {
}
/**
* Given a string containing dimension values encoded in it, separated by commas, return a String array of length dimension
* containing the values.
*
* @param out A preallocated array. Must be size dimension. If it is not it will be resized.
* @param externalVal The value to parse
* @param dimension The expected number of values for the point
* @return An array of the values that make up the point (aka vector)
* @throws com.spatial4j.core.exception.InvalidShapeException if the dimension specified does not match the number of values in the externalValue.
*/
public static String[] parsePoint(String[] out, String externalVal, int dimension) throws InvalidShapeException {
//TODO: Should we support sparse vectors?
if (out == null || out.length != dimension) out = new String[dimension];
int idx = externalVal.indexOf(',');
int end = idx;
int start = 0;
int i = 0;
if (idx == -1 && dimension == 1 && externalVal.length() > 0) {//we have a single point, dimension better be 1
out[0] = externalVal.trim();
i = 1;
} else if (idx > 0) {//if it is zero, that is an error
//Parse out a comma separated list of point values, as in: 73.5,89.2,7773.4
for (; i < dimension; i++) {
while (start < end && externalVal.charAt(start) == ' ') start++;
while (end > start && externalVal.charAt(end - 1) == ' ') end--;
if (start == end) {
break;
}
out[i] = externalVal.substring(start, end);
start = idx + 1;
end = externalVal.indexOf(',', start);
idx = end;
if (end == -1) {
end = externalVal.length();
}
}
}
if (i != dimension) {
throw new InvalidShapeException("incompatible dimension (" + dimension +
") and values (" + externalVal + "). Only " + i + " values specified");
}
return out;
}
/**
* Given a string containing dimension values encoded in it, separated by commas, return a double array of length dimension
* containing the values.
*
* @param out A preallocated array. Must be size dimension. If it is not it will be resized.
* @param externalVal The value to parse
* @param dimension The expected number of values for the point
* @return An array of the values that make up the point (aka vector)
* @throws com.spatial4j.core.exception.InvalidShapeException if the dimension specified does not match the number of values in the externalValue.
*/
public static double[] parsePointDouble(double[] out, String externalVal, int dimension) throws InvalidShapeException{
if (out == null || out.length != dimension) out = new double[dimension];
int idx = externalVal.indexOf(',');
int end = idx;
int start = 0;
int i = 0;
if (idx == -1 && dimension == 1 && externalVal.length() > 0) {//we have a single point, dimension better be 1
out[0] = Double.parseDouble(externalVal.trim());
i = 1;
} else if (idx > 0) {//if it is zero, that is an error
//Parse out a comma separated list of point values, as in: 73.5,89.2,7773.4
for (; i < dimension; i++) {
//TODO: abstract common code with other parsePoint
while (start < end && externalVal.charAt(start) == ' ') start++;
while (end > start && externalVal.charAt(end - 1) == ' ') end--;
if (start == end) {
break;
}
out[i] = Double.parseDouble(externalVal.substring(start, end));
start = idx + 1;
end = externalVal.indexOf(',', start);
idx = end;
if (end == -1) {
end = externalVal.length();
}
}
}
if (i != dimension) {
throw new InvalidShapeException("incompatible dimension (" + dimension +
") and values (" + externalVal + "). Only " + i + " values specified");
}
return out;
}
/**
* Extract (by calling {@link #parsePoint(String[], String, int)} and validate the latitude and
* longitude contained in the String by making sure the latitude is between 90 & -90 and longitude
* is between -180 and 180.
*
* The latitude is assumed to be the first part of the string and the longitude the second part.
*
* @param latLonStr The string to parse. Latitude is the first value, longitude is the second.
* @return The lat long
*
* @throws com.spatial4j.core.exception.InvalidShapeException if there was an error parsing
*/
public static final double[] parseLatitudeLongitude(String latLonStr) throws InvalidShapeException {
return parseLatitudeLongitude(null, latLonStr);
}
/**
* A variation of {@link #parseLatitudeLongitude(String)} that re-uses an output array.
* @see #parseLatitudeLongitude(String)
*/
public static final double[] parseLatitudeLongitude(double[] outLatLon, String latLonStr) throws InvalidShapeException {
outLatLon = parsePointDouble(outLatLon, latLonStr, 2);
if (outLatLon[0] < -90.0 || outLatLon[0] > 90.0) {
throw new InvalidShapeException(
"Invalid latitude: latitudes are range -90 to 90: provided lat: ["
+ outLatLon[0] + "]");
}
if (outLatLon[1] < -180.0 || outLatLon[1] > 180.0) {
throw new InvalidShapeException(
"Invalid longitude: longitudes are range -180 to 180: provided lon: ["
+ outLatLon[1] + "]");
}
return outLatLon;
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/WktShapeParser.java 0000664 0000000 0000000 00000050036 12417676335 0026142 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.SpatialContext;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Shape;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* An extensible parser for
* Well Known Text (WKT).
* The shapes supported by this class are:
*
* 'EMPTY' is supported. Specifying 'Z', 'M', or any other dimensionality in the WKT is effectively
* ignored. Thus, you can specify any number of numbers in the coordinate points but only the first
* two take effect. The javadocs for the parse___Shape methods further describe these
* shapes, or you
*
*
* Most users of this class will call just one method: {@link #parse(String)}, or
* {@link #parseIfSupported(String)} to not fail if it isn't parse-able.
*
*
* To support more shapes, extend this class and override
* {@link #parseShapeByType(WktShapeParser.State, String)}. It's also possible to delegate to
* a WKTParser by also delegating {@link #newState(String)}.
*
*
* Note, instances of this base class are threadsafe.
*/
public class WktShapeParser {
//TODO support SRID: "SRID=4326;pointPOINT(1,2)
//TODO should reference proposed ShapeFactory instead of ctx, which is a point of indirection that
// might optionally do data validation & normalization
protected final SpatialContext ctx;
/** This constructor is required by {@link com.spatial4j.core.context.SpatialContextFactory#makeWktShapeParser(com.spatial4j.core.context.SpatialContext)}. */
public WktShapeParser(SpatialContext ctx, SpatialContextFactory factory) {
this.ctx = ctx;
}
public SpatialContext getCtx() {
return ctx;
}
/**
* Parses the wktString, returning the defined Shape.
*
* @return Non-null Shape defined in the String
* @throws ParseException Thrown if there is an error in the Shape definition
*/
public Shape parse(String wktString) throws ParseException {
Shape shape = parseIfSupported(wktString);//sets rawString & offset
if (shape != null)
return shape;
String shortenedString = (wktString.length() <= 128 ? wktString : wktString.substring(0, 128-3)+"...");
throw new ParseException("Unknown Shape definition [" + shortenedString + "]", 0);
}
/**
* Parses the wktString, returning the defined Shape. If it can't because the
* shape name is unknown or an empty or blank string was passed, then it returns null.
* If the WKT starts with a supported shape but contains an inner unsupported shape then
* it will result in a {@link ParseException}.
*
* @param wktString non-null, can be empty or have surrounding whitespace
* @return Shape, null if unknown / unsupported shape.
* @throws ParseException Thrown if there is an error in the Shape definition
*/
public Shape parseIfSupported(String wktString) throws ParseException {
State state = newState(wktString);
state.nextIfWhitespace();//leading
if (state.eof())
return null;
//shape types must start with a letter
if (!Character.isLetter(state.rawString.charAt(state.offset)))
return null;
String shapeType = state.nextWord();
Shape result = null;
try {
result = parseShapeByType(state, shapeType);
} catch (ParseException e) {
throw e;
} catch (Exception e) {//most likely InvalidShapeException
ParseException pe = new ParseException(e.toString(), state.offset);
pe.initCause(e);
throw pe;
}
if (result != null && !state.eof())
throw new ParseException("end of shape expected", state.offset);
return result;
}
/** (internal) Creates a new State with the given String. It's only called by
* {@link #parseIfSupported(String)}. This is an extension point for subclassing. */
protected State newState(String wktString) {
//NOTE: if we wanted to re-use old States to reduce object allocation, we might do that
// here. But in the scheme of things, it doesn't seem worth the bother as it complicates the
// thread-safety story of the API for too little of a gain.
return new State(wktString);
}
/**
* (internal) Parses the remainder of a shape definition following the shape's name
* given as {@code shapeType} already consumed via
* {@link State#nextWord()}. If
* it's able to parse the shape, {@link WktShapeParser.State#offset}
* should be advanced beyond
* it (e.g. to the ',' or ')' or EOF in general). The default implementation
* checks the name against some predefined names and calls corresponding
* parse methods to handle the rest. Overriding this method is an
* excellent extension point for additional shape types. Or, use this class by delegation to this
* method.
*
* When writing a parse method that reacts to a specific shape type, remember to handle the
* dimension and EMPTY token via
* {@link com.spatial4j.core.io.WktShapeParser.State#nextIfEmptyAndSkipZM()}.
*
* @param state
* @param shapeType Non-Null string; could have mixed case. The first character is a letter.
* @return The shape or null if not supported / unknown.
*/
protected Shape parseShapeByType(State state, String shapeType) throws ParseException {
assert Character.isLetter(shapeType.charAt(0)) : "Shape must start with letter: "+shapeType;
if (shapeType.equalsIgnoreCase("POINT")) {
return parsePointShape(state);
} else if (shapeType.equalsIgnoreCase("MULTIPOINT")) {
return parseMultiPointShape(state);
} else if (shapeType.equalsIgnoreCase("ENVELOPE")) {
return parseEnvelopeShape(state);
} else if (shapeType.equalsIgnoreCase("GEOMETRYCOLLECTION")) {
return parseGeometryCollectionShape(state);
} else if (shapeType.equalsIgnoreCase("LINESTRING")) {
return parseLineStringShape(state);
} else if (shapeType.equalsIgnoreCase("MULTILINESTRING")) {
return parseMultiLineStringShape(state);
}
//extension
if (shapeType.equalsIgnoreCase("BUFFER")) {
return parseBufferShape(state);
}
// HEY! Update class Javadocs if add more shapes
return null;
}
/**
* Parses the BUFFER operation applied to a parsed shape.
*
* '(' shape ',' number ')'
*
* Whereas 'number' is the distance to buffer the shape by.
*/
protected Shape parseBufferShape(State state) throws ParseException {
state.nextExpect('(');
Shape shape = shape(state);
state.nextExpect(',');
double distance = normDist(state.nextDouble());
state.nextExpect(')');
return shape.getBuffered(distance, ctx);
}
/** Called to normalize a value that isn't X or Y. X & Y or normalized via
* {@link com.spatial4j.core.context.SpatialContext#normX(double)} & normY.
*/
protected double normDist(double v) {//TODO should this be added to ctx?
return v;
}
/**
* Parses a POINT shape from the raw string.
*
* '(' coordinate ')'
*
*
* @see #point(WktShapeParser.State)
*/
protected Shape parsePointShape(State state) throws ParseException {
if (state.nextIfEmptyAndSkipZM())
return ctx.makePoint(Double.NaN, Double.NaN);
state.nextExpect('(');
Point coordinate = point(state);
state.nextExpect(')');
return coordinate;
}
/**
* Parses a MULTIPOINT shape from the raw string -- a collection of points.
*
* '(' coordinate (',' coordinate )* ')'
*
* Furthermore, coordinate can optionally be wrapped in parenthesis.
*
* @see #point(WktShapeParser.State)
*/
protected Shape parseMultiPointShape(State state) throws ParseException {
if (state.nextIfEmptyAndSkipZM())
return ctx.makeCollection(Collections.EMPTY_LIST);
List shapes = new ArrayList();
state.nextExpect('(');
do {
boolean openParen = state.nextIf('(');
Point coordinate = point(state);
if (openParen)
state.nextExpect(')');
shapes.add(coordinate);
} while (state.nextIf(','));
state.nextExpect(')');
return ctx.makeCollection(shapes);
}
/**
* Parses an ENVELOPE (aka Rectangle) shape from the raw string. The values are normalized.
*
* Source: OGC "Catalogue Services Specification", the "CQL" (Common Query Language) sub-spec.
* Note the inconsistent order of the min & max values between x & y!
*
* '(' x1 ',' x2 ',' y2 ',' y1 ')'
*
*/
protected Shape parseEnvelopeShape(State state) throws ParseException {
//FYI no dimension or EMPTY
state.nextExpect('(');
double x1 = state.nextDouble();
state.nextExpect(',');
double x2 = state.nextDouble();
state.nextExpect(',');
double y2 = state.nextDouble();
state.nextExpect(',');
double y1 = state.nextDouble();
state.nextExpect(')');
return ctx.makeRectangle(ctx.normX(x1), ctx.normX(x2), ctx.normY(y1), ctx.normY(y2));
}
/**
* Parses a LINESTRING shape from the raw string -- an ordered sequence of points.
*
* coordinateSequence
*
*
* @see #pointList(WktShapeParser.State)
*/
protected Shape parseLineStringShape(State state) throws ParseException {
if (state.nextIfEmptyAndSkipZM())
return ctx.makeLineString(Collections.emptyList());
List points = pointList(state);
return ctx.makeLineString(points);
}
/**
* Parses a MULTILINESTRING shape from the raw string -- a collection of line strings.
*
*
* @see #parseLineStringShape(com.spatial4j.core.io.WktShapeParser.State)
*/
protected Shape parseMultiLineStringShape(State state) throws ParseException {
if (state.nextIfEmptyAndSkipZM())
return ctx.makeCollection(Collections.EMPTY_LIST);
List shapes = new ArrayList();
state.nextExpect('(');
do {
shapes.add(parseLineStringShape(state));
} while (state.nextIf(','));
state.nextExpect(')');
return ctx.makeCollection(shapes);
}
/**
* Parses a GEOMETRYCOLLECTION shape from the raw string.
*
* '(' shape (',' shape )* ')'
*
*/
protected Shape parseGeometryCollectionShape(State state) throws ParseException {
if (state.nextIfEmptyAndSkipZM())
return ctx.makeCollection(Collections.EMPTY_LIST);
List shapes = new ArrayList();
state.nextExpect('(');
do {
shapes.add(shape(state));
} while (state.nextIf(','));
state.nextExpect(')');
return ctx.makeCollection(shapes);
}
/** Reads a shape from the current position, starting with the name of the shape. It
* calls {@link #parseShapeByType(com.spatial4j.core.io.WktShapeParser.State, String)}
* and throws an exception if the shape wasn't supported. */
protected Shape shape(State state) throws ParseException {
String type = state.nextWord();
Shape shape = parseShapeByType(state, type);
if (shape == null)
throw new ParseException("Shape of type "+type+" is unknown", state.offset);
return shape;
}
/**
* Reads a list of Points (AKA CoordinateSequence) from the current position.
*
* '(' coordinate (',' coordinate )* ')'
*
*
* @see #point(WktShapeParser.State)
*/
protected List pointList(State state) throws ParseException {
List sequence = new ArrayList();
state.nextExpect('(');
do {
sequence.add(point(state));
} while (state.nextIf(','));
state.nextExpect(')');
return sequence;
}
/**
* Reads a raw Point (AKA Coordinate) from the current position. Only the first 2 numbers are
* used. The values are normalized.
*
* number number number*
*
*/
protected Point point(State state) throws ParseException {
double x = state.nextDouble();
double y = state.nextDouble();
state.skipNextDoubles();
return ctx.makePoint(ctx.normX(x), ctx.normY(y));
}
/** The parse state. */
public class State {
/** Set in {@link #parseIfSupported(String)}. */
public String rawString;
/** Offset of the next char in {@link #rawString} to be read. */
public int offset;
/** Dimensionality specifier (e.g. 'Z', or 'M') following a shape type name. */
public String dimension;
public State(String rawString) {
this.rawString = rawString;
}
public SpatialContext getCtx() { return ctx; }
public WktShapeParser getParser() { return WktShapeParser.this; }
/**
* Reads the word starting at the current character position. The word
* terminates once {@link Character#isJavaIdentifierPart(char)} returns false (or EOF).
* {@link #offset} is advanced past whitespace.
*
* @return Non-null non-empty String.
*/
public String nextWord() throws ParseException {
int startOffset = offset;
while (offset < rawString.length() &&
Character.isJavaIdentifierPart(rawString.charAt(offset))) {
offset++;
}
if (startOffset == offset)
throw new ParseException("Word expected", startOffset);
String result = rawString.substring(startOffset, offset);
nextIfWhitespace();
return result;
}
/**
* Skips over a dimensionality token (e.g. 'Z' or 'M') if found, storing in
* {@link #dimension}, and then looks for EMPTY, consuming that and whitespace.
*
* dimensionToken? 'EMPTY'?
*
* @return True if EMPTY was found.
*/
public boolean nextIfEmptyAndSkipZM() throws ParseException {
if (eof())
return false;
char c = rawString.charAt(offset);
if (c == '(' || !Character.isJavaIdentifierPart(c))
return false;
String word = nextWord();
if (word.equalsIgnoreCase("EMPTY"))
return true;
//we figure this word is Z or ZM or some other dimensionality signifier. We skip it.
this.dimension = word;
if (eof())
return false;
c = rawString.charAt(offset);
if (c == '(' || !Character.isJavaIdentifierPart(c))
return false;
word = nextWord();
if (word.equalsIgnoreCase("EMPTY"))
return true;
throw new ParseException("Expected EMPTY because found dimension; but got ["+word+"]",
offset);
}
/**
* Reads in a double from the String. Parses digits with an optional decimal, sign, or exponent.
* NaN and Infinity are not supported.
* {@link #offset} is advanced past whitespace.
*
* @return Double value
*/
public double nextDouble() throws ParseException {
int startOffset = offset;
skipDouble();
if (startOffset == offset)
throw new ParseException("Expected a number", offset);
double result;
try {
result = Double.parseDouble(rawString.substring(startOffset, offset));
} catch (Exception e) {
throw new ParseException(e.toString(), offset);
}
nextIfWhitespace();
return result;
}
/** Advances offset forward until it points to a character that isn't part of a number. */
public void skipDouble() {
int startOffset = offset;
for (; offset < rawString.length(); offset++) {
char c = rawString.charAt(offset);
if (!(Character.isDigit(c) || c == '.' || c == '-' || c == '+')) {
//'e' is okay as long as it isn't first
if (offset != startOffset && (c == 'e' || c == 'E'))
continue;
break;
}
}
}
/** Advances past as many doubles as there are, with intervening whitespace. */
public void skipNextDoubles() {
while (!eof()) {
int startOffset = offset;
skipDouble();
if (startOffset == offset)
return;
nextIfWhitespace();
}
}
/**
* Verifies that the current character is of the expected value.
* If the character is the expected value, then it is consumed and
* {@link #offset} is advanced past whitespace.
*
* @param expected The expected char.
*/
public void nextExpect(char expected) throws ParseException {
if (eof())
throw new ParseException("Expected [" + expected + "] found EOF", offset);
char c = rawString.charAt(offset);
if (c != expected)
throw new ParseException("Expected [" + expected + "] found [" + c + "]", offset);
offset++;
nextIfWhitespace();
}
/** If the string is consumed, i.e. at end-of-file. */
public final boolean eof() {
return offset >= rawString.length();
}
/**
* If the current character is {@code expected}, then offset is advanced after it and any
* subsequent whitespace. Otherwise, false is returned.
*
* @param expected The expected char
* @return true if consumed
*/
public boolean nextIf(char expected) {
if (!eof() && rawString.charAt(offset) == expected) {
offset++;
nextIfWhitespace();
return true;
}
return false;
}
/**
* Moves offset to next non-whitespace character. Doesn't move if the offset is already at
* non-whitespace. There is very little reason for subclasses to call this because
* most other parsing methods call it.
*/
public void nextIfWhitespace() {
for (; offset < rawString.length(); offset++) {
if (!Character.isWhitespace(rawString.charAt(offset))) {
return;
}
}
}
/**
* Returns the next chunk of text till the next ',' or ')' (non-inclusive)
* or EOF. If a '(' is encountered, then it looks past its matching ')',
* taking care to handle nested matching parenthesis too. It's designed to be
* of use to subclasses that wish to get the entire subshape at the current
* position as a string so that it might be passed to other software that
* will parse it.
*
* Example:
*
* OUTER(INNER(3, 5))
*
* If this is called when offset is at the first character, then it will
* return this whole string. If called at the "I" then it will return
* "INNER(3, 5)". If called at "3", then it will return "3". In all cases,
* offset will be positioned at the next position following the returned
* substring.
*
* @return non-null substring.
*/
public String nextSubShapeString() throws ParseException {
int startOffset = offset;
int parenStack = 0;//how many parenthesis levels are we in?
for (; offset < rawString.length(); offset++) {
char c = rawString.charAt(offset);
if (c == ',') {
if (parenStack == 0)
break;
} else if (c == ')') {
if (parenStack == 0)
break;
parenStack--;
} else if (c == '(') {
parenStack++;
}
}
if (parenStack != 0)
throw new ParseException("Unbalanced parenthesis", startOffset);
return rawString.substring(startOffset, offset);
}
}//class State
} spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/jts/ 0000775 0000000 0000000 00000000000 12417676335 0023170 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/jts/JtsBinaryCodec.java 0000664 0000000 0000000 00000011574 12417676335 0026706 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.jts.JtsSpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.io.BinaryCodec;
import com.spatial4j.core.shape.Shape;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.io.InStream;
import com.vividsolutions.jts.io.OutStream;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKBConstants;
import com.vividsolutions.jts.io.WKBReader;
import com.vividsolutions.jts.io.WKBWriter;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* Writes shapes in WKB, if it isn't otherwise supported by the superclass.
*/
public class JtsBinaryCodec extends BinaryCodec {
protected final boolean useFloat;//instead of double
public JtsBinaryCodec(JtsSpatialContext ctx, JtsSpatialContextFactory factory) {
super(ctx, factory);
//note: ctx.geometryFactory hasn't been set yet
useFloat = (factory.precisionModel.getType() == PrecisionModel.FLOATING_SINGLE);
}
@Override
protected double readDim(DataInput dataInput) throws IOException {
if (useFloat)
return dataInput.readFloat();
return super.readDim(dataInput);
}
@Override
protected void writeDim(DataOutput dataOutput, double v) throws IOException {
if (useFloat)
dataOutput.writeFloat((float) v);
else
super.writeDim(dataOutput, v);
}
@Override
protected byte typeForShape(Shape s) {
byte type = super.typeForShape(s);
if (type == 0) {
type = TYPE_GEOM;//handles everything
}
return type;
}
@Override
protected Shape readShapeByTypeIfSupported(final DataInput dataInput, byte type) throws IOException {
if (type != TYPE_GEOM)
return super.readShapeByTypeIfSupported(dataInput, type);
return readJtsGeom(dataInput);
}
@Override
protected boolean writeShapeByTypeIfSupported(DataOutput dataOutput, Shape s, byte type) throws IOException {
if (type != TYPE_GEOM)
return super.writeShapeByTypeIfSupported(dataOutput, s, type);
writeJtsGeom(dataOutput, s);
return true;
}
public Shape readJtsGeom(final DataInput dataInput) throws IOException {
JtsSpatialContext ctx = (JtsSpatialContext)super.ctx;
WKBReader reader = new WKBReader(ctx.getGeometryFactory());
try {
InStream inStream = new InStream() {//a strange JTS abstraction
boolean first = true;
@Override
public void read(byte[] buf) throws IOException {
if (first) {//we don't write JTS's leading BOM so synthesize reading it
if (buf.length != 1)
throw new IllegalStateException("Expected initial read of one byte, not: " + buf.length);
buf[0] = WKBConstants.wkbXDR;//0
first = false;
} else {
//TODO for performance, specialize for common array lengths: 1, 4, 8
dataInput.readFully(buf);
}
}
};
Geometry geom = reader.read(inStream);
//false: don't check for dateline-180 cross or multi-polygon overlaps; this won't happen
// once it gets written, and we're reading it now
return ctx.makeShape(geom, false, false);
} catch (ParseException ex) {
throw new InvalidShapeException("error reading WKT", ex);
}
}
public void writeJtsGeom(final DataOutput dataOutput, Shape s) throws IOException {
JtsSpatialContext ctx = (JtsSpatialContext)super.ctx;
Geometry geom = ctx.getGeometryFrom(s);//might even translate it
new WKBWriter().write(geom, new OutStream() {//a strange JTS abstraction
boolean first = true;
@Override
public void write(byte[] buf, int len) throws IOException {
if (first) {
first = false;
//skip byte order mark
if (len != 1 || buf[0] != WKBConstants.wkbXDR)//the default
throw new IllegalStateException("Unexpected WKB byte order mark");
return;
}
dataOutput.write(buf, 0, len);
}
});
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/jts/JtsWKTReaderShapeParser.java 0000664 0000000 0000000 00000011017 12417676335 0030442 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.jts.JtsSpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.jts.JtsPoint;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.CoordinateSequenceFilter;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.WKTReader;
import java.text.ParseException;
/**
* This is an extension of {@link JtsWktShapeParser} that processes the entire
* string with JTS's {@link com.vividsolutions.jts.io.WKTReader}. Some differences:
*
*
No support for ENVELOPE and BUFFER
*
MULTI* shapes use JTS's {@link com.vividsolutions.jts.geom.GeometryCollection} subclasses,
* not {@link com.spatial4j.core.shape.ShapeCollection}
*
'Z' coordinates are saved into the geometry
*
*
*/
public class JtsWKTReaderShapeParser extends JtsWktShapeParser {
//Note: Historically, the code here originated from the defunct JtsShapeReadWriter.
public JtsWKTReaderShapeParser(JtsSpatialContext ctx, JtsSpatialContextFactory factory) {
super(ctx, factory);
}
@Override
public Shape parseIfSupported(String wktString) throws ParseException {
return parseIfSupported(wktString, new WKTReader(ctx.getGeometryFactory()));
}
/**
* Reads WKT from the {@code str} via JTS's {@link com.vividsolutions.jts.io.WKTReader}.
* @param str
* @param reader
new WKTReader(ctx.getGeometryFactory()))
* @return Non-Null
*/
protected Shape parseIfSupported(String str, WKTReader reader) throws ParseException {
try {
Geometry geom = reader.read(str);
//Normalizes & verifies coordinates
checkCoordinates(geom);
if (geom instanceof com.vividsolutions.jts.geom.Point) {
com.vividsolutions.jts.geom.Point ptGeom = (com.vividsolutions.jts.geom.Point) geom;
if (ctx.useJtsPoint())
return new JtsPoint(ptGeom, ctx);
else
return ctx.makePoint(ptGeom.getX(), ptGeom.getY());
} else if (geom.isRectangle()) {
return super.makeRectFromPoly(geom);
} else {
return super.makeShapeFromGeometry(geom);
}
} catch (InvalidShapeException e) {
throw e;
} catch (Exception e) {
throw new InvalidShapeException("error reading WKT: "+e.toString(), e);
}
}
protected void checkCoordinates(Geometry geom) {
// note: JTS WKTReader has already normalized coords with the JTS PrecisionModel.
geom.apply(new CoordinateSequenceFilter() {
boolean changed = false;
@Override
public void filter(CoordinateSequence seq, int i) {
double x = seq.getX(i);
double y = seq.getY(i);
//Note: we don't simply call ctx.normX & normY because
// those methods use the precisionModel, but WKTReader already
// used the precisionModel. It's be nice to turn that off somehow but alas.
if (ctx.isGeo() && ctx.isNormWrapLongitude()) {
double xNorm = DistanceUtils.normLonDEG(x);
if (Double.compare(x, xNorm) != 0) {//handles NaN
changed = true;
seq.setOrdinate(i, CoordinateSequence.X, xNorm);
}
// double yNorm = DistanceUtils.normLatDEG(y);
// if (y != yNorm) {
// changed = true;
// seq.setOrdinate(i,CoordinateSequence.Y,yNorm);
// }
}
ctx.verifyX(x);
ctx.verifyY(y);
}
@Override
public boolean isDone() { return false; }
@Override
public boolean isGeometryChanged() { return changed; }
});
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/jts/JtsWktShapeParser.java 0000664 0000000 0000000 00000030136 12417676335 0027422 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.jts.JtsSpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.io.WktShapeParser;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.vividsolutions.jts.algorithm.CGAlgorithms;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Polygon;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Extends {@link com.spatial4j.core.io.WktShapeParser} adding support for polygons, using JTS.
*/
public class JtsWktShapeParser extends WktShapeParser {
protected final JtsSpatialContext ctx;
protected final DatelineRule datelineRule;
protected final ValidationRule validationRule;
protected final boolean autoIndex;
public JtsWktShapeParser(JtsSpatialContext ctx, JtsSpatialContextFactory factory) {
super(ctx, factory);
this.ctx = ctx;
this.datelineRule = factory.datelineRule;
this.validationRule = factory.validationRule;
this.autoIndex = factory.autoIndex;
}
/** @see JtsWktShapeParser.ValidationRule */
public ValidationRule getValidationRule() {
return validationRule;
}
/**
* JtsGeometry shapes are automatically validated when {@link #getValidationRule()} isn't
* {@code none}.
*/
public boolean isAutoValidate() {
return validationRule != ValidationRule.none;
}
/**
* If JtsGeometry shapes should be automatically prepared (i.e. optimized) when read via WKT.
* @see com.spatial4j.core.shape.jts.JtsGeometry#index()
*/
public boolean isAutoIndex() {
return autoIndex;
}
/** @see DatelineRule */
public DatelineRule getDatelineRule() {
return datelineRule;
}
@Override
protected Shape parseShapeByType(WktShapeParser.State state, String shapeType) throws ParseException {
if (shapeType.equalsIgnoreCase("POLYGON")) {
return parsePolygonShape(state);
} else if (shapeType.equalsIgnoreCase("MULTIPOLYGON")) {
return parseMulitPolygonShape(state);
}
return super.parseShapeByType(state, shapeType);
}
/** Bypasses {@link JtsSpatialContext#makeLineString(java.util.List)} so that we can more
* efficiently get the LineString without creating a {@code List}.
*/
@Override
protected Shape parseLineStringShape(WktShapeParser.State state) throws ParseException {
if (!ctx.useJtsLineString())
return super.parseLineStringShape(state);
if (state.nextIfEmptyAndSkipZM())
return ctx.makeLineString(Collections.emptyList());
GeometryFactory geometryFactory = ctx.getGeometryFactory();
Coordinate[] coordinates = coordinateSequence(state);
return makeShapeFromGeometry(geometryFactory.createLineString(coordinates));
}
/**
* Parses a POLYGON shape from the raw string. It might return a {@link com.spatial4j.core.shape.Rectangle}
* if the polygon is one.
*
* coordinateSequenceList
*
*/
protected Shape parsePolygonShape(WktShapeParser.State state) throws ParseException {
Geometry geometry;
if (state.nextIfEmptyAndSkipZM()) {
GeometryFactory geometryFactory = ctx.getGeometryFactory();
geometry = geometryFactory.createPolygon(geometryFactory.createLinearRing(
new Coordinate[]{}), null);
} else {
geometry = polygon(state);
if (geometry.isRectangle()) {
//TODO although, might want to never convert if there's a semantic difference (e.g. geodetically)
return makeRectFromPoly(geometry);
}
}
return makeShapeFromGeometry(geometry);
}
protected Rectangle makeRectFromPoly(Geometry geometry) {
assert geometry.isRectangle();
Envelope env = geometry.getEnvelopeInternal();
boolean crossesDateline = false;
if (ctx.isGeo() && getDatelineRule() != DatelineRule.none) {
if (getDatelineRule() == DatelineRule.ccwRect) {
// If JTS says it is clockwise, then it's actually a dateline crossing rectangle.
crossesDateline = ! CGAlgorithms.isCCW(geometry.getCoordinates());
} else {
crossesDateline = env.getWidth() > 180;
}
}
if (crossesDateline)
return ctx.makeRectangle(env.getMaxX(), env.getMinX(), env.getMinY(), env.getMaxY());
else
return ctx.makeRectangle(env.getMinX(), env.getMaxX(), env.getMinY(), env.getMaxY());
}
/**
* Reads a polygon, returning a JTS polygon.
*/
protected Polygon polygon(WktShapeParser.State state) throws ParseException {
GeometryFactory geometryFactory = ctx.getGeometryFactory();
List coordinateSequenceList = coordinateSequenceList(state);
LinearRing shell = geometryFactory.createLinearRing
(coordinateSequenceList.get(0));
LinearRing[] holes = null;
if (coordinateSequenceList.size() > 1) {
holes = new LinearRing[coordinateSequenceList.size() - 1];
for (int i = 1; i < coordinateSequenceList.size(); i++) {
holes[i - 1] = geometryFactory.createLinearRing(coordinateSequenceList.get(i));
}
}
return geometryFactory.createPolygon(shell, holes);
}
/**
* Parses a MULTIPOLYGON shape from the raw string.
*
* '(' polygon (',' polygon )* ')'
*
*/
protected Shape parseMulitPolygonShape(WktShapeParser.State state) throws ParseException {
if (state.nextIfEmptyAndSkipZM())
return ctx.makeCollection(Collections.EMPTY_LIST);
List polygons = new ArrayList();
state.nextExpect('(');
do {
polygons.add(parsePolygonShape(state));
} while (state.nextIf(','));
state.nextExpect(')');
return ctx.makeCollection(polygons);
}
/**
* Reads a list of JTS Coordinate sequences from the current position.
*
*/
protected List coordinateSequenceList(WktShapeParser.State state) throws ParseException {
List sequenceList = new ArrayList();
state.nextExpect('(');
do {
sequenceList.add(coordinateSequence(state));
} while (state.nextIf(','));
state.nextExpect(')');
return sequenceList;
}
/**
* Reads a JTS Coordinate sequence from the current position.
*
* '(' coordinate (',' coordinate )* ')'
*
*/
protected Coordinate[] coordinateSequence(WktShapeParser.State state) throws ParseException {
List sequence = new ArrayList();
state.nextExpect('(');
do {
sequence.add(coordinate(state));
} while (state.nextIf(','));
state.nextExpect(')');
return sequence.toArray(new Coordinate[sequence.size()]);
}
/**
* Reads a {@link com.vividsolutions.jts.geom.Coordinate} from the current position.
* It's akin to {@link #point(com.spatial4j.core.io.WktShapeParser.State)} but for
* a JTS Coordinate. Only the first 2 numbers are parsed; any remaining are ignored.
*/
protected Coordinate coordinate(WktShapeParser.State state) throws ParseException {
double x = ctx.normX(state.nextDouble());
ctx.verifyX(x);
double y = ctx.normY(state.nextDouble());
ctx.verifyY(y);
state.skipNextDoubles();
return new Coordinate(x, y);
}
@Override
protected double normDist(double v) {
return ctx.getGeometryFactory().getPrecisionModel().makePrecise(v);
}
/** Creates the JtsGeometry, potentially validating, repairing, and preparing. */
protected JtsGeometry makeShapeFromGeometry(Geometry geometry) {
final boolean dateline180Check = getDatelineRule() != DatelineRule.none;
JtsGeometry jtsGeom;
try {
jtsGeom = ctx.makeShape(geometry, dateline180Check, ctx.isAllowMultiOverlap());
if (isAutoValidate())
jtsGeom.validate();
} catch (RuntimeException e) {
//repair:
if (validationRule == ValidationRule.repairConvexHull) {
jtsGeom = ctx.makeShape(geometry.convexHull(), dateline180Check, ctx.isAllowMultiOverlap());
} else if (validationRule == ValidationRule.repairBuffer0) {
jtsGeom = ctx.makeShape(geometry.buffer(0), dateline180Check, ctx.isAllowMultiOverlap());
} else {
//TODO there are other smarter things we could do like repairing inner holes and subtracting
// from outer repaired shell; but we needn't try too hard.
throw e;
}
}
if (isAutoIndex())
jtsGeom.index();
return jtsGeom;
}
/**
* Indicates the algorithm used to process JTS Polygons and JTS LineStrings for detecting dateline
* crossings. It only applies when geo=true.
*/
public enum DatelineRule {
/** No polygon will cross the dateline. */
none,
/** Adjacent points with an x (longitude) difference that spans more than half
* way around the globe will be interpreted as going the other (shorter) way, and thus cross the
* dateline.
*/
width180,//TODO is there a better name that doesn't have '180' in it?
/** For rectangular polygons, the point order is interpreted as being counter-clockwise (CCW).
* However, non-rectangular polygons or other shapes aren't processed this way; they use the
* {@link #width180} rule instead. The CCW rule is specified by OGC Simple Features
* Specification v. 1.2.0 section 6.1.11.1.
*/
ccwRect
}
/** Indicates how JTS geometries (notably polygons but applies to other geometries too) are
* validated (if at all) and repaired (if at all).
*/
public enum ValidationRule {
/** Geometries will not be validated (because it's kinda expensive to calculate). You may or may
* not ultimately get an error at some point; results are undefined. However, note that
* coordinates will still be validated for falling within the world boundaries.
* @see com.vividsolutions.jts.geom.Geometry#isValid(). */
none,
/** Geometries will be explicitly validated on creation, possibly resulting in an exception:
* {@link com.spatial4j.core.exception.InvalidShapeException}. */
error,
/** Invalid Geometries are repaired by taking the convex hull. The result will very likely be a
* larger shape that matches false-positives, but no false-negatives.
* See {@link com.vividsolutions.jts.geom.Geometry#convexHull()}. */
repairConvexHull,
/** Invalid polygons are repaired using the {@code buffer(0)} technique. From the JTS FAQ:
*
The buffer operation is fairly insensitive to topological invalidity, and the act of
* computing the buffer can often resolve minor issues such as self-intersecting rings. However,
* in some situations the computed result may not be what is desired (i.e. the buffer operation
* may be "confused" by certain topologies, and fail to produce a result which is close to the
* original. An example where this can happen is a "bow-tie: or "figure-8" polygon, with one
* very small lobe and one large one. Depending on the orientations of the lobes, the buffer(0)
* operation may keep the small lobe and discard the "valid" large lobe).
*
*/
repairBuffer0
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/io/package-info.java 0000664 0000000 0000000 00000001563 12417676335 0025564 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.
*/
/** Reading & writing shapes in various forms. */
package com.spatial4j.core.io; spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/package-info.java 0000664 0000000 0000000 00000001632 12417676335 0025152 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.
*/
/**
* This is the base package for Spatial4j from which the rest of it is organized.
*/
package com.spatial4j.core;
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/ 0000775 0000000 0000000 00000000000 12417676335 0023061 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/Circle.java 0000664 0000000 0000000 00000002631 12417676335 0025127 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.shape;
/**
* A circle, also known as a point-radius since that is what it is comprised of.
*/
public interface Circle extends Shape {
/**
* Expert: Resets the state of this shape given the arguments. This is a
* performance feature to avoid excessive Shape object allocation as well as
* some argument error checking. Mutable shapes is error-prone so use with
* care.
*/
void reset(double x, double y, double radiusDEG);
/**
* The distance from the point's center to its edge, measured in the same
* units as x & y (e.g. degrees if WGS84).
*/
double getRadius();
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/Point.java 0000664 0000000 0000000 00000002564 12417676335 0025024 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.shape;
/**
* A Point with X & Y coordinates.
*/
public interface Point extends Shape {
/**
* Expert: Resets the state of this shape given the arguments. This is a
* performance feature to avoid excessive Shape object allocation as well as
* some argument error checking. Mutable shapes is error-prone so use with
* care.
*/
public void reset(double x, double y);
/** The X coordinate, or Longitude in geospatial contexts. */
public double getX();
/** The Y coordinate, or Latitude in geospatial contexts. */
public double getY();
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/Rectangle.java 0000664 0000000 0000000 00000005132 12417676335 0025631 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.shape;
/**
* A rectangle aligned with the axis (i.e. it is not at an angle).
*
* In geospatial contexts, it may cross the international date line (-180
* longitude) if {@link #getCrossesDateLine()} however it cannot pass the poles
* although it may span the globe. It spans the globe if the X coordinate
* (Longitude) goes from -180 to 180 as seen from {@link #getMinX()} and {@link
* #getMaxX()}.
*/
public interface Rectangle extends Shape {
/**
* Expert: Resets the state of this shape given the arguments. This is a
* performance feature to avoid excessive Shape object allocation as well as
* some argument error checking. Mutable shapes is error-prone so use with
* care.
*/
public void reset(double minX, double maxX, double minY, double maxY);
/**
* The width. In geospatial contexts, this is generally in degrees longitude
* and is aware of the international dateline. It will always be >= 0.
*/
public double getWidth();
/**
* The height. In geospatial contexts, this is in degrees latitude. It will
* always be >= 0.
*/
public double getHeight();
/** The left edge of the X coordinate. */
public double getMinX();
/** The bottom edge of the Y coordinate. */
public double getMinY();
/** The right edge of the X coordinate. */
public double getMaxX();
/** The top edge of the Y coordinate. */
public double getMaxY();
/** Only meaningful for geospatial contexts. */
public boolean getCrossesDateLine();
/**
* A specialization of {@link Shape#relate(Shape)}
* for a vertical line.
*/
public SpatialRelation relateYRange(double minY, double maxY);
/**
* A specialization of {@link Shape#relate(Shape)}
* for a horizontal line.
*/
public SpatialRelation relateXRange(double minX, double maxX);
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/Shape.java 0000664 0000000 0000000 00000007732 12417676335 0024775 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.shape;
import com.spatial4j.core.context.SpatialContext;
/**
* The base interface defining a geometric shape. Shape instances should be
* instantiated via one of the create* methods on a {@link SpatialContext} or
* by reading WKT which calls those methods; they should not be
* created directly.
*
* Shapes are generally immutable and thread-safe. If a particular shape has a
* reset(...) method then its use means the shape is actually
* mutable. Mutating shape state is considered expert and should be done with care.
*/
public interface Shape {
/**
* Describe the relationship between the two objects. For example
*
*
this is WITHIN other
*
this CONTAINS other
*
this is DISJOINT other
*
this INTERSECTS other
*
* Note that a Shape implementation may choose to return INTERSECTS when the
* true answer is WITHIN or CONTAINS for performance reasons. If a shape does
* this then it must document when it does. Ideally the shape will not
* do this approximation in all circumstances, just sometimes.
*
* If the shapes are equal then the result is CONTAINS (preferred) or WITHIN.
*/
SpatialRelation relate(Shape other);
/**
* Get the bounding box for this Shape. This means the shape is within the
* bounding box and that it touches each side of the rectangle.
*
* Postcondition: this.getBoundingBox().relate(this) == CONTAINS
*/
Rectangle getBoundingBox();
/**
* Does the shape have area? This will be false for points and lines. It will
* also be false for shapes that normally have area but are constructed in a
* degenerate case as to not have area (e.g. a circle with 0 radius or
* rectangle with no height or no width).
*/
boolean hasArea();
/**
* Calculates the area of the shape, in square-degrees. If ctx is null then
* simple Euclidean calculations will be used. This figure can be an
* estimate.
*/
double getArea(SpatialContext ctx);
/**
* Returns the center point of this shape. This is usually the same as
* getBoundingBox().getCenter() but it doesn't have to be.
*
* Postcondition: this.relate(this.getCenter()) == CONTAINS
*/
Point getCenter();
/**
* Returns a buffered version of this shape. The buffer is usually a
* rounded-corner buffer, although some shapes might buffer differently. This
* is an optional operation.
*
*
* @param distance
* @return Not null, and the returned shape should contain the current shape.
*/
Shape getBuffered(double distance, SpatialContext ctx);
/**
* Shapes can be "empty", which is to say it exists nowhere. The underlying coordinates are
* typically NaN.
*/
boolean isEmpty();
/** The sub-classes of Shape generally implement the
* same contract for {@link Object#equals(Object)} and {@link Object#hashCode()}
* amongst the same sub-interface type. This means, for example, that multiple
* Point implementations of different classes are equal if they share the same x
* & y. */
@Override
public boolean equals(Object other);
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/ShapeCollection.java 0000664 0000000 0000000 00000017437 12417676335 0027014 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.shape;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.impl.Range;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.RandomAccess;
import static com.spatial4j.core.shape.SpatialRelation.CONTAINS;
import static com.spatial4j.core.shape.SpatialRelation.INTERSECTS;
/**
* A collection of Shape objects, analogous to an OGC GeometryCollection. The
* implementation demands a List (with random access) so that the order can be
* retained if an application requires it, although logically it's treated as an
* unordered Set, mostly.
*
* Ideally, {@link #relate(Shape)} should return the same result no matter what
* the shape order is, although the default implementation can be order
* dependent when the shapes overlap; see {@link #relateContainsShortCircuits()}.
* To improve performance slightly, the caller could order the shapes by
* largest first so that relate() will have a greater chance of
* short-circuit'ing sooner. As the Shape contract states; it may return
* intersects when the best answer is actually contains or within. If any shape
* intersects the provided shape then that is the answer.
*
* This implementation is not optimized for a large number of shapes; relate is
* O(N). A more sophisticated implementation might do an R-Tree based on
* bbox'es, for example.
*/
public class ShapeCollection extends AbstractList implements Shape {
protected final List shapes;
protected final Rectangle bbox;
/**
* WARNING: {@code shapes} is copied by reference.
* @param shapes Copied by reference! (make a defensive copy if caller modifies)
* @param ctx
*/
public ShapeCollection(List shapes, SpatialContext ctx) {
if (!(shapes instanceof RandomAccess))
throw new IllegalArgumentException("Shapes arg must implement RandomAccess: "+shapes.getClass());
this.shapes = shapes;
this.bbox = computeBoundingBox(shapes, ctx);
}
protected Rectangle computeBoundingBox(Collection extends Shape> shapes, SpatialContext ctx) {
if (shapes.isEmpty())
return ctx.makeRectangle(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
Range xRange = null;
double minY = Double.POSITIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
for (Shape geom : shapes) {
Rectangle r = geom.getBoundingBox();
Range xRange2 = Range.xRange(r, ctx);
if (xRange == null) {
xRange = xRange2;
} else {
xRange = xRange.expandTo(xRange2);
}
minY = Math.min(minY, r.getMinY());
maxY = Math.max(maxY, r.getMaxY());
}
return ctx.makeRectangle(xRange.getMin(), xRange.getMax(), minY, maxY);
}
public List getShapes() {
return shapes;
}
@Override
public S get(int index) {
return shapes.get(index);
}
@Override
public int size() {
return shapes.size();
}
@Override
public Rectangle getBoundingBox() {
return bbox;
}
@Override
public Point getCenter() {
return bbox.getCenter();
}
@Override
public boolean hasArea() {
for (Shape geom : shapes) {
if( geom.hasArea() ) {
return true;
}
}
return false;
}
@Override
public ShapeCollection getBuffered(double distance, SpatialContext ctx) {
List bufColl = new ArrayList(size());
for (Shape shape : shapes) {
bufColl.add(shape.getBuffered(distance, ctx));
}
return ctx.makeCollection(bufColl);
}
@Override
public SpatialRelation relate(Shape other) {
final SpatialRelation bboxSect = bbox.relate(other);
if (bboxSect == SpatialRelation.DISJOINT || bboxSect == SpatialRelation.WITHIN)
return bboxSect;
final boolean containsWillShortCircuit = (other instanceof Point) ||
relateContainsShortCircuits();
SpatialRelation sect = null;
for (Shape shape : shapes) {
SpatialRelation nextSect = shape.relate(other);
if (sect == null) {//first pass
sect = nextSect;
} else {
sect = sect.combine(nextSect);
}
if (sect == INTERSECTS)
return INTERSECTS;
if (sect == CONTAINS && containsWillShortCircuit)
return CONTAINS;
}
return sect;
}
/**
* Called by relate() to determine whether to return early if it finds
* CONTAINS, instead of checking the remaining shapes. It will do so without
* calling this method if the "other" shape is a Point. If a remaining shape
* finds INTERSECTS, then INTERSECTS will be returned. The only problem with
* this returning true is that if some of the shapes overlap, it's possible
* that the result of relate() could be dependent on the order of the shapes,
* which could be unexpected / wrong depending on the application. The default
* implementation returns true because it probably doesn't matter. If it
* does, a subclass could add a boolean flag that this method could return.
* That flag could be initialized to true only if the shapes are mutually
* disjoint.
*
* @see #computeMutualDisjoint(java.util.List) .
*/
protected boolean relateContainsShortCircuits() {
return true;
}
/**
* Computes whether the shapes are mutually disjoint. This is a utility method
* offered for use by a subclass implementing {@link #relateContainsShortCircuits()}.
* Beware: this is an O(N^2) algorithm.. Consequently, consider safely
* assuming non-disjoint if shapes.size() > 10 or something. And if all shapes
* are a Point then the result of this method doesn't ultimately matter.
*/
protected static boolean computeMutualDisjoint(List 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();
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/SpatialRelation.java 0000664 0000000 0000000 00000011216 12417676335 0027020 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.shape;
/**
* The set of spatial relationships. Naming is somewhat consistent with OGC spec
* conventions as seen in SQL/MM and others.
*
* There is no equality case. If two Shape instances are equal then the result
* might be CONTAINS (preferred) or WITHIN. Client logic may have to be aware
* of this edge condition; Spatial4j testing certainly does.
*
* The "CONTAINS" and "WITHIN" wording here is inconsistent with OGC; these here map to OGC
* "COVERS" and "COVERED BY", respectively. The distinction is in the boundaries; in Spatial4j
* there is no boundary distinction -- boundaries are part of the shape as if it was an "interior",
* with respect to OGC's terminology.
*/
public enum SpatialRelation {
//see http://docs.geotools.org/latest/userguide/library/jts/dim9.html#preparedgeometry
/**
* The shape is within the target geometry. It's the converse of {@link #CONTAINS}.
* Boundaries of shapes count too. OGC specs refer to this relation as "COVERED BY";
* WITHIN is differentiated there by not including boundaries.
*/
WITHIN,
/**
* The shape contains the target geometry. It's the converse of {@link #WITHIN}.
* Boundaries of shapes count too. OGC specs refer to this relation as "COVERS";
* CONTAINS is differentiated there by not including boundaries.
*/
CONTAINS,
/**
* The shape shares no point in common with the target shape.
*/
DISJOINT,
/**
* The shape shares some points/overlap with the target shape, and the relation is
* not more specifically {@link #WITHIN} or {@link #CONTAINS}.
*/
INTERSECTS;
//Don't have these: TOUCHES, CROSSES, OVERLAPS, nor distinction between CONTAINS/COVERS
/**
* Given the result of shapeA.relate(shapeB), transposing that
* result should yield the result of shapeB.relate(shapeA). There
* is a corner case is when the shapes are equal, in which case actually
* flipping the relate() call will result in the same value -- either CONTAINS
* or WITHIN; this method can't possible check for that so the caller might
* have to.
*/
public SpatialRelation transpose() {
switch(this) {
case CONTAINS: return SpatialRelation.WITHIN;
case WITHIN: return SpatialRelation.CONTAINS;
default: return this;
}
}
/**
* If you were to call aShape.relate(bShape) and aShape.relate(cShape), you
* could call this to merge the intersect results as if bShape & cShape were
* combined into {@link ShapeCollection}.
*/
public SpatialRelation combine(SpatialRelation other) {
// You can think of this algorithm as a state transition / automata.
// 1. The answer must be the same no matter what the order is.
// 2. If any INTERSECTS, then the result is INTERSECTS (done).
// 3. A DISJOINT + WITHIN == INTERSECTS (done).
// 4. A DISJOINT + CONTAINS == CONTAINS.
// 5. A CONTAINS + WITHIN == INTERSECTS (done). (weird scenario)
// 6. X + X == X.
if (other == this)
return this;
if (this == DISJOINT && other == CONTAINS
|| this == CONTAINS && other == DISJOINT)
return CONTAINS;
return INTERSECTS;
}
/** Not DISJOINT, i.e. there is some sort of intersection. */
public boolean intersects() {
return this != DISJOINT;
}
/**
* If aShape.relate(bShape) is r, then r.inverse()
* is inverse(aShape).relate(bShape) whereas
* inverse(shape) is theoretically the opposite area covered by a
* shape, i.e. everywhere but where the shape is.
*
* Note that it's not commutative! WITHIN.inverse().inverse() !=
* WITHIN.
*/
public SpatialRelation inverse() {
switch(this) {
case DISJOINT: return CONTAINS;
case CONTAINS: return DISJOINT;
case WITHIN: return INTERSECTS;//not commutative!
}
return INTERSECTS;
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/ 0000775 0000000 0000000 00000000000 12417676335 0024022 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/BufferedLine.java 0000664 0000000 0000000 00000020252 12417676335 0027220 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.shape.impl;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
import static com.spatial4j.core.shape.SpatialRelation.CONTAINS;
import static com.spatial4j.core.shape.SpatialRelation.DISJOINT;
import static com.spatial4j.core.shape.SpatialRelation.INTERSECTS;
import static com.spatial4j.core.shape.SpatialRelation.WITHIN;
/**
* INTERNAL: A line between two points with a buffer distance extending in every direction. By
* contrast, an un-buffered line covers no area and as such is extremely unlikely to intersect with
* a point. BufferedLine isn't yet aware of geodesics (e.g. the dateline); it operates in Euclidean
* space.
*/
public class BufferedLine implements Shape {
private final Point pA, pB;
private final double buf;
private final Rectangle bbox;
/**
* the primary line; passes through pA & pB
*/
private final InfBufLine linePrimary;
/**
* perpendicular to the primary line, centered between pA & pB
*/
private final InfBufLine linePerp;
/**
* Creates a buffered line from pA to pB. The buffer extends on both sides of
* the line, making the width 2x the buffer. The buffer extends out from
* pA & pB, making the line in effect 2x the buffer longer than pA to pB.
*
* @param pA start point
* @param pB end point
* @param buf the buffer distance in degrees
* @param ctx
*/
public BufferedLine(Point pA, Point pB, double buf, SpatialContext ctx) {
assert buf >= 0;//TODO support buf=0 via another class ?
/**
* If true, buf should bump-out from the pA & pB, in effect
* extending the line a little.
*/
final boolean bufExtend = true;//TODO support false and make this a
// parameter
this.pA = pA;
this.pB = pB;
this.buf = buf;
double deltaY = pB.getY() - pA.getY();
double deltaX = pB.getX() - pA.getX();
PointImpl center = new PointImpl(pA.getX() + deltaX / 2,
pA.getY() + deltaY / 2, null);
double perpExtent = bufExtend ? buf : 0;
if (deltaX == 0 && deltaY == 0) {
linePrimary = new InfBufLine(0, center, buf);
linePerp = new InfBufLine(Double.POSITIVE_INFINITY, center, buf);
} else {
linePrimary = new InfBufLine(deltaY / deltaX, center, buf);
double length = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
linePerp = new InfBufLine(-deltaX / deltaY, center,
length / 2 + perpExtent);
}
double minY, maxY;
double minX, maxX;
if (deltaX == 0) { // vertical
if (pA.getY() <= pB.getY()) {
minY = pA.getY();
maxY = pB.getY();
} else {
minY = pB.getY();
maxY = pA.getY();
}
minX = pA.getX() - buf;
maxX = pA.getX() + buf;
minY = minY - perpExtent;
maxY = maxY + perpExtent;
} else {
if (!bufExtend) {
throw new UnsupportedOperationException("TODO");
//solve for B & A (C=buf), one is buf-x, other is buf-y.
}
//Given a right triangle of A, B, C sides, C (hypotenuse) ==
// buf, and A + B == the bounding box offset from pA & pB in x & y.
double bboxBuf = buf * (1 + Math.abs(linePrimary.getSlope()))
* linePrimary.getDistDenomInv();
assert bboxBuf >= buf && bboxBuf <= buf * 1.5;
if (pA.getX() <= pB.getX()) {
minX = pA.getX() - bboxBuf;
maxX = pB.getX() + bboxBuf;
} else {
minX = pB.getX() - bboxBuf;
maxX = pA.getX() + bboxBuf;
}
if (pA.getY() <= pB.getY()) {
minY = pA.getY() - bboxBuf;
maxY = pB.getY() + bboxBuf;
} else {
minY = pB.getY() - bboxBuf;
maxY = pA.getY() + bboxBuf;
}
}
Rectangle bounds = ctx.getWorldBounds();
bbox = ctx.makeRectangle(
Math.max(bounds.getMinX(), minX),
Math.min(bounds.getMaxX(), maxX),
Math.max(bounds.getMinY(), minY),
Math.min(bounds.getMaxY(), maxY));
}
@Override
public boolean isEmpty() {
return pA.isEmpty();
}
@Override
public Shape getBuffered(double distance, SpatialContext ctx) {
return new BufferedLine(pA, pB, buf + distance, ctx);
}
/**
* Calls {@link DistanceUtils#calcLonDegreesAtLat(double, double)} given pA or pB's latitude;
* whichever is farthest. It's useful to expand a buffer of a line segment when used in
* a geospatial context to cover the desired area.
*/
public static double expandBufForLongitudeSkew(Point pA, Point pB,
double buf) {
double absA = Math.abs(pA.getY());
double absB = Math.abs(pB.getY());
double maxLat = Math.max(absA, absB);
double newBuf = DistanceUtils.calcLonDegreesAtLat(maxLat, buf);
// if (newBuf + maxLat >= 90) {
// //TODO substitute spherical cap ?
// }
assert newBuf >= buf;
return newBuf;
}
@Override
public SpatialRelation relate(Shape other) {
if (other instanceof Point)
return contains((Point) other) ? CONTAINS : DISJOINT;
if (other instanceof Rectangle)
return relate((Rectangle) other);
throw new UnsupportedOperationException();
}
public SpatialRelation relate(Rectangle r) {
//Check BBox for disjoint & within.
SpatialRelation bboxR = bbox.relate(r);
if (bboxR == DISJOINT || bboxR == WITHIN)
return bboxR;
//Either CONTAINS, INTERSECTS, or DISJOINT
Point scratch = new PointImpl(0, 0, null);
Point prC = r.getCenter();
SpatialRelation result = linePrimary.relate(r, prC, scratch);
if (result == DISJOINT)
return DISJOINT;
SpatialRelation resultOpp = linePerp.relate(r, prC, scratch);
if (resultOpp == DISJOINT)
return DISJOINT;
if (result == resultOpp)//either CONTAINS or INTERSECTS
return result;
return INTERSECTS;
}
public boolean contains(Point p) {
//TODO check bbox 1st?
return linePrimary.contains(p) && linePerp.contains(p);
}
public Rectangle getBoundingBox() {
return bbox;
}
@Override
public boolean hasArea() {
return buf > 0;
}
@Override
public double getArea(SpatialContext ctx) {
return linePrimary.getBuf() * linePerp.getBuf() * 4;
}
@Override
public Point getCenter() {
return getBoundingBox().getCenter();
}
public Point getA() {
return pA;
}
public Point getB() {
return pB;
}
public double getBuf() {
return buf;
}
/**
* INTERNAL
*/
public InfBufLine getLinePrimary() {
return linePrimary;
}
/**
* INTERNAL
*/
public InfBufLine getLinePerp() {
return linePerp;
}
@Override
public String toString() {
return "BufferedLine(" + pA + ", " + pB + " b=" + buf + ")";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BufferedLine that = (BufferedLine) o;
if (Double.compare(that.buf, buf) != 0) return false;
if (!pA.equals(that.pA)) return false;
if (!pB.equals(that.pB)) return false;
return true;
}
@Override
public int hashCode() {
int result;
long temp;
result = pA.hashCode();
result = 31 * result + pB.hashCode();
temp = buf != +0.0d ? Double.doubleToLongBits(buf) : 0L;
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/BufferedLineString.java 0000664 0000000 0000000 00000013176 12417676335 0030416 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.shape.impl;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.ShapeCollection;
import com.spatial4j.core.shape.SpatialRelation;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A BufferedLineString is a collection of {@link com.spatial4j.core.shape.impl.BufferedLine} shapes,
* resulting in what some call a "Track" or "Polyline" (ESRI terminology).
* The buffer can be 0. Note that BufferedLine isn't yet aware of geodesics (e.g. the dateline).
*/
public class BufferedLineString implements Shape {
//TODO add some geospatial awareness like:
// segment that spans at the dateline (split it at DL?).
private final ShapeCollection segments;
private final double buf;
/**
* Needs at least 1 point, usually more than that. If just one then it's
* internally treated like 2 points.
*/
public BufferedLineString(List points, double buf, SpatialContext ctx) {
this(points, buf, false, ctx);
}
/**
* @param points ordered control points. If empty then this shape is empty.
* @param buf Buffer >= 0
* @param expandBufForLongitudeSkew See {@link BufferedLine
* #expandBufForLongitudeSkew(com.spatial4j.core.shape.Point,
* com.spatial4j.core.shape.Point, double)}.
* If true then the buffer for each segment
* is computed.
* @param ctx
*/
public BufferedLineString(List points, double buf, boolean expandBufForLongitudeSkew,
SpatialContext ctx) {
this.buf = buf;
if (points.isEmpty()) {
this.segments = ctx.makeCollection(Collections.emptyList());
} else {
List segments = new ArrayList(points.size() - 1);
Point prevPoint = null;
for (Point point : points) {
if (prevPoint != null) {
double segBuf = buf;
if (expandBufForLongitudeSkew) {
//TODO this is faulty in that it over-buffers. See Issue#60.
segBuf = BufferedLine.expandBufForLongitudeSkew(prevPoint, point, buf);
}
segments.add(new BufferedLine(prevPoint, point, segBuf, ctx));
}
prevPoint = point;
}
if (segments.isEmpty()) {//TODO throw exception instead?
segments.add(new BufferedLine(prevPoint, prevPoint, buf, ctx));
}
this.segments = ctx.makeCollection(segments);
}
}
@Override
public boolean isEmpty() {
return segments.isEmpty();
}
@Override
public Shape getBuffered(double distance, SpatialContext ctx) {
return ctx.makeBufferedLineString(getPoints(), buf + distance);
}
public ShapeCollection getSegments() {
return segments;
}
public double getBuf() {
return buf;
}
@Override
public double getArea(SpatialContext ctx) {
return segments.getArea(ctx);
}
@Override
public SpatialRelation relate(Shape other) {
return segments.relate(other);
}
@Override
public boolean hasArea() {
return segments.hasArea();
}
@Override
public Point getCenter() {
return segments.getCenter();
}
@Override
public Rectangle getBoundingBox() {
return segments.getBoundingBox();
}
@Override
public String toString() {
StringBuilder str = new StringBuilder(100);
str.append("BufferedLineString(buf=").append(buf).append(" pts=");
boolean first = true;
for (Point point : getPoints()) {
if (first) {
first = false;
} else {
str.append(", ");
}
str.append(point.getX()).append(' ').append(point.getY());
}
str.append(')');
return str.toString();
}
public List getPoints() {
if (segments.isEmpty())
return Collections.emptyList();
final List lines = segments.getShapes();
return new AbstractList() {
@Override
public Point get(int index) {
if (index == 0)
return lines.get(0).getA();
return lines.get(index - 1).getB();
}
@Override
public int size() {
return lines.size() + 1;
}
};
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BufferedLineString that = (BufferedLineString) o;
if (Double.compare(that.buf, buf) != 0) return false;
if (!segments.equals(that.segments)) return false;
return true;
}
@Override
public int hashCode() {
int result;
long temp;
result = segments.hashCode();
temp = buf != +0.0d ? Double.doubleToLongBits(buf) : 0L;
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/CircleImpl.java 0000664 0000000 0000000 00000022127 12417676335 0026714 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.shape.impl;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
/**
* A circle, also known as a point-radius, based on a {@link
* com.spatial4j.core.distance.DistanceCalculator} which does all the work. This
* implementation should work for both cartesian 2D and geodetic sphere
* surfaces.
*/
public class CircleImpl implements Circle {
protected final SpatialContext ctx;
protected final Point point;
protected double radiusDEG;
// calculated & cached
protected Rectangle enclosingBox;
public CircleImpl(Point p, double radiusDEG, SpatialContext ctx) {
//We assume any validation of params already occurred (including bounding dist)
this.ctx = ctx;
this.point = p;
this.radiusDEG = point.isEmpty() ? Double.NaN : radiusDEG;
this.enclosingBox = point.isEmpty() ? ctx.makeRectangle(Double.NaN, Double.NaN, Double.NaN, Double.NaN) :
ctx.getDistCalc().calcBoxByDistFromPt(point, this.radiusDEG, ctx, null);
}
@Override
public void reset(double x, double y, double radiusDEG) {
assert ! isEmpty();
point.reset(x, y);
this.radiusDEG = radiusDEG;
this.enclosingBox = ctx.getDistCalc().calcBoxByDistFromPt(point, this.radiusDEG, ctx, enclosingBox);
}
@Override
public boolean isEmpty() {
return point.isEmpty();
}
@Override
public Point getCenter() {
return point;
}
@Override
public double getRadius() {
return radiusDEG;
}
@Override
public double getArea(SpatialContext ctx) {
if (ctx == null) {
return Math.PI * radiusDEG * radiusDEG;
} else {
return ctx.getDistCalc().area(this);
}
}
@Override
public Circle getBuffered(double distance, SpatialContext ctx) {
return ctx.makeCircle(point, distance + radiusDEG);
}
public boolean contains(double x, double y) {
return ctx.getDistCalc().within(point, x, y, radiusDEG);
}
@Override
public boolean hasArea() {
return radiusDEG > 0;
}
/**
* Note that the bounding box might contain a minX that is > maxX, due to WGS84 dateline.
*/
@Override
public Rectangle getBoundingBox() {
return enclosingBox;
}
@Override
public SpatialRelation relate(Shape other) {
//This shortcut was problematic in testing due to distinctions of CONTAINS/WITHIN for no-area shapes (lines, points).
// if (distance == 0) {
// return point.relate(other,ctx).intersects() ? SpatialRelation.WITHIN : SpatialRelation.DISJOINT;
// }
if (isEmpty() || other.isEmpty())
return SpatialRelation.DISJOINT;
if (other instanceof Point) {
return relate((Point) other);
}
if (other instanceof Rectangle) {
return relate((Rectangle) other);
}
if (other instanceof Circle) {
return relate((Circle) other);
}
return other.relate(this).transpose();
}
public SpatialRelation relate(Point point) {
return contains(point.getX(),point.getY()) ? SpatialRelation.CONTAINS : SpatialRelation.DISJOINT;
}
public SpatialRelation relate(Rectangle r) {
//Note: Surprisingly complicated!
//--We start by leveraging the fact we have a calculated bbox that is "cheaper" than use of DistanceCalculator.
final SpatialRelation bboxSect = enclosingBox.relate(r);
if (bboxSect == SpatialRelation.DISJOINT || bboxSect == SpatialRelation.WITHIN)
return bboxSect;
else if (bboxSect == SpatialRelation.CONTAINS && enclosingBox.equals(r))//nasty identity edge-case
return SpatialRelation.WITHIN;
//bboxSect is INTERSECTS or CONTAINS
//The result can be DISJOINT, CONTAINS, or INTERSECTS (not WITHIN)
return relateRectanglePhase2(r, bboxSect);
}
protected SpatialRelation relateRectanglePhase2(final Rectangle r, SpatialRelation bboxSect) {
// DOES NOT WORK WITH GEO CROSSING DATELINE OR WORLD-WRAP. Other methods handle such cases.
//At this point, the only thing we are certain of is that circle is *NOT* WITHIN r, since the
// bounding box of a circle MUST be within r for the circle to be within r.
//Quickly determine if they are DISJOINT or not.
// Find the closest & farthest point to the circle within the rectangle
final double closestX, farthestX;
final double xAxis = getXAxis();
if (xAxis < r.getMinX()) {
closestX = r.getMinX();
farthestX = r.getMaxX();
} else if (xAxis > r.getMaxX()) {
closestX = r.getMaxX();
farthestX = r.getMinX();
} else {
closestX = xAxis; //we don't really use this value but to check this condition
farthestX = r.getMaxX() - xAxis > xAxis - r.getMinX() ? r.getMaxX() : r.getMinX();
}
final double closestY, farthestY;
final double yAxis = getYAxis();
if (yAxis < r.getMinY()) {
closestY = r.getMinY();
farthestY = r.getMaxY();
} else if (yAxis > r.getMaxY()) {
closestY = r.getMaxY();
farthestY = r.getMinY();
} else {
closestY = yAxis; //we don't really use this value but to check this condition
farthestY = r.getMaxY() - yAxis > yAxis - r.getMinY() ? r.getMaxY() : r.getMinY();
}
//If r doesn't overlap an axis, then could be disjoint. Test closestXY
if (xAxis != closestX && yAxis != closestY) {
if (!contains(closestX, closestY))
return SpatialRelation.DISJOINT;
} // else CAN'T be disjoint if spans axis because earlier bbox check ruled that out
//Now, we know it's *NOT* DISJOINT and it's *NOT* WITHIN either.
// Does circle CONTAINS r or simply intersect it?
//If circle contains r, then its bbox MUST also CONTAIN r.
if (bboxSect != SpatialRelation.CONTAINS)
return SpatialRelation.INTERSECTS;
//If the farthest point of r away from the center of the circle is contained, then all of r is
// contained.
if (!contains(farthestX, farthestY))
return SpatialRelation.INTERSECTS;
//geodetic detection of farthest Y when rect crosses x axis can't be reliably determined, so
// check other corner too, which might actually be farthest
if (point.getY() != getYAxis()) {//geodetic
if (yAxis == closestY) {//r crosses north to south over x axis (confusing)
double otherY = (farthestY == r.getMaxY() ? r.getMinY() : r.getMaxY());
if (!contains(farthestX, otherY))
return SpatialRelation.INTERSECTS;
}
}
return SpatialRelation.CONTAINS;
}
/**
* The Y coordinate of where the circle axis intersect.
*/
protected double getYAxis() {
return point.getY();
}
/**
* The X coordinate of where the circle axis intersect.
*/
protected double getXAxis() {
return point.getX();
}
public SpatialRelation relate(Circle circle) {
double crossDist = ctx.getDistCalc().distance(point, circle.getCenter());
double aDist = radiusDEG, bDist = circle.getRadius();
if (crossDist > aDist + bDist)
return SpatialRelation.DISJOINT;
if (crossDist < aDist && crossDist + bDist <= aDist)
return SpatialRelation.CONTAINS;
if (crossDist < bDist && crossDist + aDist <= bDist)
return SpatialRelation.WITHIN;
return SpatialRelation.INTERSECTS;
}
@Override
public String toString() {
return "Circle(" + point + ", d=" + radiusDEG + "°)";
}
@Override
public boolean equals(Object obj) {
return equals(this,obj);
}
/**
* All {@link Circle} implementations should use this definition of {@link Object#equals(Object)}.
*/
public static boolean equals(Circle thiz, Object o) {
assert thiz != null;
if (thiz == o) return true;
if (!(o instanceof Circle)) return false;
Circle circle = (Circle) o;
if (!thiz.getCenter().equals(circle.getCenter())) return false;
if (Double.compare(circle.getRadius(), thiz.getRadius()) != 0) return false;
return true;
}
@Override
public int hashCode() {
return hashCode(this);
}
/**
* All {@link Circle} implementations should use this definition of {@link Object#hashCode()}.
*/
public static int hashCode(Circle thiz) {
int result;
long temp;
result = thiz.getCenter().hashCode();
temp = thiz.getRadius() != +0.0d ? Double.doubleToLongBits(thiz.getRadius()) : 0L;
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/GeoCircle.java 0000664 0000000 0000000 00000021635 12417676335 0026530 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.shape.impl;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.SpatialRelation;
import java.util.Formatter;
import java.util.Locale;
/**
* A circle as it exists on the surface of a sphere.
*/
public class GeoCircle extends CircleImpl {
private GeoCircle inverseCircle;//when distance reaches > 1/2 way around the world, cache the inverse.
private double horizAxisY;//see getYAxis
public GeoCircle(Point p, double radiusDEG, SpatialContext ctx) {
super(p, radiusDEG, ctx);
assert ctx.isGeo();
init();
}
@Override
public void reset(double x, double y, double radiusDEG) {
super.reset(x, y, radiusDEG);
init();
}
private void init() {
if (radiusDEG > 90) {
//--spans more than half the globe
assert enclosingBox.getWidth() == 360;
double backDistDEG = 180 - radiusDEG;
if (backDistDEG > 0) {
double backRadius = 180 - radiusDEG;
double backX = DistanceUtils.normLonDEG(getCenter().getX() + 180);
double backY = DistanceUtils.normLatDEG(getCenter().getY() + 180);
//Shrink inverseCircle as small as possible to avoid accidental overlap.
// Note that this is tricky business to come up with a value small enough
// but not too small or else numerical conditioning issues become a problem.
backRadius -= Math.max(Math.ulp(Math.abs(backY)+backRadius), Math.ulp(Math.abs(backX)+backRadius));
if (inverseCircle != null) {
inverseCircle.reset(backX, backY, backRadius);
} else {
inverseCircle = new GeoCircle(ctx.makePoint(backX, backY), backRadius, ctx);
}
} else {
inverseCircle = null;//whole globe
}
horizAxisY = getCenter().getY();//although probably not used
} else {
inverseCircle = null;
double _horizAxisY = ctx.getDistCalc().calcBoxByDistFromPt_yHorizAxisDEG(getCenter(), radiusDEG, ctx);
//some rare numeric conditioning cases can cause this to be barely beyond the box
if (_horizAxisY > enclosingBox.getMaxY()) {
horizAxisY = enclosingBox.getMaxY();
} else if (_horizAxisY < enclosingBox.getMinY()) {
horizAxisY = enclosingBox.getMinY();
} else {
horizAxisY = _horizAxisY;
}
//assert enclosingBox.relate_yRange(horizAxis,horizAxis,ctx).intersects();
}
}
@Override
protected double getYAxis() {
return horizAxisY;
}
/**
* Called after bounding box is intersected.
* @param bboxSect INTERSECTS or CONTAINS from enclosingBox's intersection
* @return DISJOINT, CONTAINS, or INTERSECTS (not WITHIN)
*/
@Override
protected SpatialRelation relateRectanglePhase2(Rectangle r, SpatialRelation bboxSect) {
if (inverseCircle != null) {
return inverseCircle.relate(r).inverse();
}
//if a pole is wrapped, we have a separate algorithm
if (enclosingBox.getWidth() == 360) {
return relateRectangleCircleWrapsPole(r, ctx);
}
//This is an optimization path for when there are no dateline or pole issues.
if (!enclosingBox.getCrossesDateLine() && !r.getCrossesDateLine()) {
return super.relateRectanglePhase2(r, bboxSect);
}
//Rectangle wraps around the world longitudinally creating a solid band; there are no corners to test intersection
if (r.getWidth() == 360) {
return SpatialRelation.INTERSECTS;
}
//do quick check to see if all corners are within this circle for CONTAINS
int cornersIntersect = numCornersIntersect(r);
if (cornersIntersect == 4) {
//ensure r's x axis is within c's. If it doesn't, r sneaks around the globe to touch the other side (intersect).
SpatialRelation xIntersect = r.relateXRange(enclosingBox.getMinX(), enclosingBox.getMaxX());
if (xIntersect == SpatialRelation.WITHIN)
return SpatialRelation.CONTAINS;
return SpatialRelation.INTERSECTS;
}
//INTERSECT or DISJOINT ?
if (cornersIntersect > 0)
return SpatialRelation.INTERSECTS;
//Now we check if one of the axis of the circle intersect with r. If so we have
// intersection.
/* x axis intersects */
if ( r.relateYRange(getYAxis(), getYAxis()).intersects() // at y vertical
&& r.relateXRange(enclosingBox.getMinX(), enclosingBox.getMaxX()).intersects() )
return SpatialRelation.INTERSECTS;
/* y axis intersects */
if (r.relateXRange(getXAxis(), getXAxis()).intersects()) { // at x horizontal
double yTop = getCenter().getY()+ radiusDEG;
assert yTop <= 90;
double yBot = getCenter().getY()- radiusDEG;
assert yBot >= -90;
if (r.relateYRange(yBot, yTop).intersects())//back bottom
return SpatialRelation.INTERSECTS;
}
return SpatialRelation.DISJOINT;
}
private SpatialRelation relateRectangleCircleWrapsPole(Rectangle r, SpatialContext ctx) {
//This method handles the case where the circle wraps ONE pole, but not both. For both,
// there is the inverseCircle case handled before now. The only exception is for the case where
// the circle covers the entire globe, and we'll check that first.
if (radiusDEG == 180)//whole globe
return SpatialRelation.CONTAINS;
//Check if r is within the pole wrap region:
double yTop = getCenter().getY() + radiusDEG;
if (yTop > 90) {
double yTopOverlap = yTop - 90;
assert yTopOverlap <= 90;
if (r.getMinY() >= 90 - yTopOverlap)
return SpatialRelation.CONTAINS;
} else {
double yBot = point.getY() - radiusDEG;
if (yBot < -90) {
double yBotOverlap = -90 - yBot;
assert yBotOverlap <= 90;
if (r.getMaxY() <= -90 + yBotOverlap)
return SpatialRelation.CONTAINS;
} else {
//This point is probably not reachable ??
assert yTop == 90 || yBot == -90;//we simply touch a pole
//continue
}
}
//If there are no corners to check intersection because r wraps completely...
if (r.getWidth() == 360)
return SpatialRelation.INTERSECTS;
//Check corners:
int cornersIntersect = numCornersIntersect(r);
// (It might be possible to reduce contains() calls within nCI() to exactly two, but this intersection
// code is complicated enough as it is.)
double frontX = getCenter().getX();
if (cornersIntersect == 4) {//all
double backX = frontX <= 0 ? frontX + 180 : frontX - 180;
if (r.relateXRange(backX, backX).intersects())
return SpatialRelation.INTERSECTS;
else
return SpatialRelation.CONTAINS;
} else if (cornersIntersect == 0) {//none
if (r.relateXRange(frontX, frontX).intersects())
return SpatialRelation.INTERSECTS;
else
return SpatialRelation.DISJOINT;
} else//partial
return SpatialRelation.INTERSECTS;
}
/** Returns either 0 for none, 1 for some, or 4 for all. */
private int numCornersIntersect(Rectangle r) {
//We play some logic games to avoid calling contains() which can be expensive.
boolean bool;//if true then all corners intersect, if false then no corners intersect
// for partial, we exit early with 1 and ignore bool.
bool = (contains(r.getMinX(),r.getMinY()));
if (contains(r.getMinX(),r.getMaxY())) {
if (!bool)
return 1;//partial
} else {
if (bool)
return 1;//partial
}
if (contains(r.getMaxX(),r.getMinY())) {
if (!bool)
return 1;//partial
} else {
if (bool)
return 1;//partial
}
if (contains(r.getMaxX(),r.getMaxY())) {
if (!bool)
return 1;//partial
} else {
if (bool)
return 1;//partial
}
return bool?4:0;
}
@Override
public String toString() {
//Add distance in km, which may be easier to recognize.
double distKm = DistanceUtils.degrees2Dist(radiusDEG, DistanceUtils.EARTH_MEAN_RADIUS_KM);
//instead of String.format() so that we get consistent output no matter the locale
String dStr = new Formatter(Locale.ROOT).format("%.1f\u00B0 %.2fkm", radiusDEG, distKm).toString();
return "Circle(" + point + ", d=" + dStr + ')';
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/InfBufLine.java 0000664 0000000 0000000 00000012025 12417676335 0026646 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.shape.impl;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.SpatialRelation;
import static com.spatial4j.core.shape.SpatialRelation.*;
/**
* INERNAL: A buffered line of infinite length.
* Public for test access.
*/
public class InfBufLine {
//TODO consider removing support for vertical line -- let caller
// do something else. BufferedLine could have a factory method
// that returns a rectangle, for example.
// line: y = slope * x + intercept
private final double slope;//can be infinite for vertical line
//if slope is infinite, this is x intercept, otherwise y intercept
private final double intercept;
private final double buf;
private final double distDenomInv;//cached: 1 / Math.sqrt(slope * slope + 1)
InfBufLine(double slope, Point point, double buf) {
assert !Double.isNaN(slope);
this.slope = slope;
if (Double.isInfinite(slope)) {
intercept = point.getX();
distDenomInv = Double.NaN;
} else {
intercept = point.getY() - slope * point.getX();
distDenomInv = 1 / Math.sqrt(slope * slope + 1);
}
this.buf = buf;
}
SpatialRelation relate(Rectangle r, Point prC, Point scratch) {
assert r.getCenter().equals(prC);
int cQuad = quadrant(prC);
Point nearestP = scratch;
cornerByQuadrant(r, oppositeQuad[cQuad], nearestP);
boolean nearestContains = contains(nearestP);
if (nearestContains) {
Point farthestP = scratch;
nearestP = null;//just to be safe (same scratch object)
cornerByQuadrant(r, cQuad, farthestP);
boolean farthestContains = contains(farthestP);
if (farthestContains)
return CONTAINS;
return INTERSECTS;
} else {// not nearestContains
if (quadrant(nearestP) == cQuad)
return DISJOINT;//out of buffer on same side as center
return INTERSECTS;//nearest & farthest points straddle the line
}
}
boolean contains(Point p) {
return (distanceUnbuffered(p) <= buf);
}
/** INTERNAL AKA lineToPointDistance */
public double distanceUnbuffered(Point c) {
if (Double.isInfinite(slope))
return Math.abs(c.getX() - intercept);
// http://math.ucsd.edu/~wgarner/math4c/derivations/distance/distptline.htm
double num = Math.abs(c.getY() - slope * c.getX() - intercept);
return num * distDenomInv;
}
// /** Amount to add or subtract to intercept to indicate where the
// * buffered line edges cross the y axis.
// * @return
// */
// double interceptBuffOffset() {
// if (Double.isInfinite(slope))
// return slope;
// if (buf == 0)
// return 0;
// double slopeDivBuf = slope / buf;
// return Math.sqrt(buf*buf + slopeDivBuf*slopeDivBuf);
// }
/** INTERNAL: AKA lineToPointQuadrant */
public int quadrant(Point c) {
//check vertical line case 1st
if (Double.isInfinite(slope)) {
//when slope is infinite, intercept is x intercept instead of y
return c.getX() > intercept ? 1 : 2; //4 : 3 would work too
}
//(below will work for slope==0 horizontal line too)
//is c above or below the line
double yAtCinLine = slope * c.getX() + intercept;
boolean above = c.getY() >= yAtCinLine;
if (slope > 0) {
//if slope is a forward slash, then result is 2 | 4
return above ? 2 : 4;
} else {
//if slope is a backward slash, then result is 1 | 3
return above ? 1 : 3;
}
}
//TODO ? Use an Enum for quadrant?
/* quadrants 1-4: NE, NW, SW, SE. */
private static final int[] oppositeQuad= {-1,3,4,1,2};
public static void cornerByQuadrant(Rectangle r, int cornerQuad, Point out) {
double x = (cornerQuad == 1 || cornerQuad == 4) ? r.getMaxX() : r.getMinX();
double y = (cornerQuad == 1 || cornerQuad == 2) ? r.getMaxY() : r.getMinY();
out.reset(x, y);
}
public double getSlope() {
return slope;
}
public double getIntercept() {
return intercept;
}
public double getBuf() {
return buf;
}
/** 1 / Math.sqrt(slope * slope + 1) */
public double getDistDenomInv() {
return distDenomInv;
}
@Override
public String toString() {
return "InfBufLine{" +
"buf=" + buf +
", intercept=" + intercept +
", slope=" + slope +
'}';
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/PointImpl.java 0000664 0000000 0000000 00000007055 12417676335 0026607 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.shape.impl;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
/** A basic 2D implementation of a Point. */
public class PointImpl implements Point {
private final SpatialContext ctx;
private double x;
private double y;
/** A simple constructor without normalization / validation. */
public PointImpl(double x, double y, SpatialContext ctx) {
this.ctx = ctx;
reset(x, y);
}
@Override
public boolean isEmpty() {
return Double.isNaN(x);
}
@Override
public void reset(double x, double y) {
assert ! isEmpty();
this.x = x;
this.y = y;
}
@Override
public double getX() {
return x;
}
@Override
public double getY() {
return y;
}
@Override
public Rectangle getBoundingBox() {
return ctx.makeRectangle(this, this);
}
@Override
public PointImpl getCenter() {
return this;
}
@Override
public Circle getBuffered(double distance, SpatialContext ctx) {
return ctx.makeCircle(this, distance);
}
@Override
public SpatialRelation relate(Shape other) {
if (isEmpty() || other.isEmpty())
return SpatialRelation.DISJOINT;
if (other instanceof Point)
return this.equals(other) ? SpatialRelation.INTERSECTS : SpatialRelation.DISJOINT;
return other.relate(this).transpose();
}
@Override
public boolean hasArea() {
return false;
}
@Override
public double getArea(SpatialContext ctx) {
return 0;
}
@Override
public String toString() {
return "Pt(x="+x+",y="+y+")";
}
@Override
public boolean equals(Object o) {
return equals(this,o);
}
/**
* All {@link Point} implementations should use this definition of {@link Object#equals(Object)}.
*/
public static boolean equals(Point thiz, Object o) {
assert thiz != null;
if (thiz == o) return true;
if (!(o instanceof Point)) return false;
Point point = (Point) o;
if (Double.compare(point.getX(), thiz.getX()) != 0) return false;
if (Double.compare(point.getY(), thiz.getY()) != 0) return false;
return true;
}
@Override
public int hashCode() {
return hashCode(this);
}
/**
* All {@link Point} implementations should use this definition of {@link Object#hashCode()}.
*/
public static int hashCode(Point thiz) {
int result;
long temp;
temp = thiz.getX() != +0.0d ? Double.doubleToLongBits(thiz.getX()) : 0L;
result = (int) (temp ^ (temp >>> 32));
temp = thiz.getY() != +0.0d ? Double.doubleToLongBits(thiz.getY()) : 0L;
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/Range.java 0000664 0000000 0000000 00000011462 12417676335 0025725 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.shape.impl;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Rectangle;
/**
* INTERNAL: A numeric range between a pair of numbers.
* Perhaps this class could become 1st class citizen extending Shape but not now.
* Only public so is accessible from tests in another package.
*/
public class Range {
protected final double min, max;
public static Range xRange(Rectangle rect, SpatialContext ctx) {
if (ctx.isGeo())
return new LongitudeRange(rect.getMinX(), rect.getMaxX());
else
return new Range(rect.getMinX(), rect.getMaxX());
}
public static Range yRange(Rectangle rect, SpatialContext ctx) {
return new Range(rect.getMinY(), rect.getMaxY());
}
public Range(double min, double max) {
this.min = min;
this.max = max;
}
public double getMin() {
return min;
}
public double getMax() {
return max;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Range range = (Range) o;
if (Double.compare(range.max, max) != 0) return false;
if (Double.compare(range.min, min) != 0) return false;
return true;
}
@Override
public int hashCode() {
int result;
long temp;
temp = min != +0.0d ? Double.doubleToLongBits(min) : 0L;
result = (int) (temp ^ (temp >>> 32));
temp = max != +0.0d ? Double.doubleToLongBits(max) : 0L;
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public String toString() {
return "Range{" + min + " TO " + max + '}';
}
public double getWidth() {
return max - min;
}
public boolean contains(double v) {
return v >= min && v <= max;
}
public double getCenter() {
return min + getWidth()/2;
}
public Range expandTo(Range other) {
assert this.getClass() == other.getClass();
return new Range(Math.min(min, other.min), Math.max(max, other.max));
}
public double deltaLen(Range other) {
double min3 = Math.max(min, other.min);
double max3 = Math.min(max, other.max);
return max3 - min3;
}
public static class LongitudeRange extends Range {
public static final LongitudeRange WORLD_180E180W = new LongitudeRange(-180, 180);
public LongitudeRange(double min, double max) {
super(min, max);
}
public LongitudeRange(Rectangle r) {
super(r.getMinX(), r.getMaxX());
}
@Override
public double getWidth() {
double w = super.getWidth();
if (w < 0)
w += 360;
return w;
}
@Override
public boolean contains(double v) {
if (!crossesDateline())
return super.contains(v);
return v >= min || v <= max;// the OR is the distinction from non-dateline cross
}
public boolean crossesDateline() {
return min > max;
}
public double getCenter() {
double ctr = super.getCenter();
if (ctr > 180)
ctr -= 360;
return ctr;
}
public double compareTo(LongitudeRange b) {
return diff(getCenter(), b.getCenter());
}
/** a - b (compareTo order). < 0 if a < b */
private static double diff(double a, double b) {
double diff = a - b;
if (diff <= 180) {
if (diff >= -180)
return diff;
return diff + 360;
} else {
return diff - 360;
}
}
@Override
public Range expandTo(Range other) {
return expandTo((LongitudeRange) other);
}
public LongitudeRange expandTo(LongitudeRange other) {
LongitudeRange a, b;// a.ctr <= b.ctr
if (this.compareTo(other) <= 0) {
a = this;
b = other;
} else {
a = other;
b = this;
}
LongitudeRange newMin = b.contains(a.min) ? b : a;//usually 'a'
LongitudeRange newMax = a.contains(b.max) ? a : b;//usually 'b'
if (newMin == newMax)
return newMin;
if (newMin == b && newMax == a)
return WORLD_180E180W;
return new LongitudeRange(newMin.min, newMax.max);
}
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/impl/RectangleImpl.java 0000664 0000000 0000000 00000024660 12417676335 0027423 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.shape.impl;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
/**
* A simple Rectangle implementation that also supports a longitudinal
* wrap-around. When minX > maxX, this will assume it is world coordinates that
* cross the date line using degrees. Immutable & threadsafe.
*/
public class RectangleImpl implements Rectangle {
private final SpatialContext ctx;
private double minX;
private double maxX;
private double minY;
private double maxY;
/** A simple constructor without normalization / validation. */
public RectangleImpl(double minX, double maxX, double minY, double maxY, SpatialContext ctx) {
//TODO change to West South East North to be more consistent with OGC?
this.ctx = ctx;
reset(minX, maxX, minY, maxY);
}
/** A convenience constructor which pulls out the coordinates. */
public RectangleImpl(Point lowerLeft, Point upperRight, SpatialContext ctx) {
this(lowerLeft.getX(), upperRight.getX(),
lowerLeft.getY(), upperRight.getY(), ctx);
}
/** Copy constructor. */
public RectangleImpl(Rectangle r, SpatialContext ctx) {
this(r.getMinX(), r.getMaxX(), r.getMinY(), r.getMaxY(), ctx);
}
@Override
public void reset(double minX, double maxX, double minY, double maxY) {
assert ! isEmpty();
this.minX = minX;
this.maxX = maxX;
this.minY = minY;
this.maxY = maxY;
assert minY <= maxY || Double.isNaN(minY) : "minY, maxY: "+minY+", "+maxY;
}
@Override
public boolean isEmpty() {
return Double.isNaN(minX);
}
@Override
public Rectangle getBuffered(double distance, SpatialContext ctx) {
if (ctx.isGeo()) {
//first check pole touching, triggering a world-wrap rect
if (maxY + distance >= 90) {
return ctx.makeRectangle(-180, 180, Math.max(-90, minY - distance), 90);
} else if (minY - distance <= -90) {
return ctx.makeRectangle(-180, 180, -90, Math.min(90, maxY + distance));
} else {
//doesn't touch pole
double latDistance = distance;
double closestToPoleY = (maxY - minY > 0) ? maxY : minY;
double lonDistance = DistanceUtils.calcBoxByDistFromPt_deltaLonDEG(
closestToPoleY, minX, distance);//lat,lon order
//could still wrap the world though...
if (lonDistance * 2 + getWidth() >= 360)
return ctx.makeRectangle(-180, 180, minY - latDistance, maxY + latDistance);
return ctx.makeRectangle(
DistanceUtils.normLonDEG(minX - lonDistance),
DistanceUtils.normLonDEG(maxX + lonDistance),
minY - latDistance, maxY + latDistance);
}
} else {
Rectangle worldBounds = ctx.getWorldBounds();
double newMinX = Math.max(worldBounds.getMinX(), minX - distance);
double newMaxX = Math.min(worldBounds.getMaxX(), maxX + distance);
double newMinY = Math.max(worldBounds.getMinY(), minY - distance);
double newMaxY = Math.min(worldBounds.getMaxY(), maxY + distance);
return ctx.makeRectangle(newMinX, newMaxX, newMinY, newMaxY);
}
}
@Override
public boolean hasArea() {
return maxX != minX && maxY != minY;
}
@Override
public double getArea(SpatialContext ctx) {
if (ctx == null) {
return getWidth() * getHeight();
} else {
return ctx.getDistCalc().area(this);
}
}
@Override
public boolean getCrossesDateLine() {
return (minX > maxX);
}
@Override
public double getHeight() {
return maxY - minY;
}
@Override
public double getWidth() {
double w = maxX - minX;
if (w < 0) {//only true when minX > maxX (WGS84 assumed)
w += 360;
assert w >= 0;
}
return w;
}
@Override
public double getMaxX() {
return maxX;
}
@Override
public double getMaxY() {
return maxY;
}
@Override
public double getMinX() {
return minX;
}
@Override
public double getMinY() {
return minY;
}
@Override
public Rectangle getBoundingBox() {
return this;
}
@Override
public SpatialRelation relate(Shape other) {
if (isEmpty() || other.isEmpty())
return SpatialRelation.DISJOINT;
if (other instanceof Point) {
return relate((Point) other);
}
if (other instanceof Rectangle) {
return relate((Rectangle) other);
}
return other.relate(this).transpose();
}
public SpatialRelation relate(Point point) {
if (point.getY() > getMaxY() || point.getY() < getMinY())
return SpatialRelation.DISJOINT;
// all the below logic is rather unfortunate but some dateline cases demand it
double minX = this.minX;
double maxX = this.maxX;
double pX = point.getX();
if (ctx.isGeo()) {
//unwrap dateline and normalize +180 to become -180
double rawWidth = maxX - minX;
if (rawWidth < 0) {
maxX = minX + (rawWidth + 360);
}
//shift to potentially overlap
if (pX < minX) {
pX += 360;
} else if (pX > maxX) {
pX -= 360;
} else {
return SpatialRelation.CONTAINS;//short-circuit
}
}
if (pX < minX || pX > maxX)
return SpatialRelation.DISJOINT;
return SpatialRelation.CONTAINS;
}
public SpatialRelation relate(Rectangle rect) {
SpatialRelation yIntersect = relateYRange(rect.getMinY(), rect.getMaxY());
if (yIntersect == SpatialRelation.DISJOINT)
return SpatialRelation.DISJOINT;
SpatialRelation xIntersect = relateXRange(rect.getMinX(), rect.getMaxX());
if (xIntersect == SpatialRelation.DISJOINT)
return SpatialRelation.DISJOINT;
if (xIntersect == yIntersect)//in agreement
return xIntersect;
//if one side is equal, return the other
if (getMinX() == rect.getMinX() && getMaxX() == rect.getMaxX())
return yIntersect;
if (getMinY() == rect.getMinY() && getMaxY() == rect.getMaxY())
return xIntersect;
return SpatialRelation.INTERSECTS;
}
//TODO might this utility move to SpatialRelation ?
private static SpatialRelation relate_range(double int_min, double int_max, double ext_min, double ext_max) {
if (ext_min > int_max || ext_max < int_min) {
return SpatialRelation.DISJOINT;
}
if (ext_min >= int_min && ext_max <= int_max) {
return SpatialRelation.CONTAINS;
}
if (ext_min <= int_min && ext_max >= int_max) {
return SpatialRelation.WITHIN;
}
return SpatialRelation.INTERSECTS;
}
@Override
public SpatialRelation relateYRange(double ext_minY, double ext_maxY) {
return relate_range(minY, maxY, ext_minY, ext_maxY);
}
@Override
public SpatialRelation relateXRange(double ext_minX, double ext_maxX) {
//For ext & this we have local minX and maxX variable pairs. We rotate them so that minX <= maxX
double minX = this.minX;
double maxX = this.maxX;
if (ctx.isGeo()) {
//unwrap dateline, plus do world-wrap short circuit
double rawWidth = maxX - minX;
if (rawWidth == 360)
return SpatialRelation.CONTAINS;
if (rawWidth < 0) {
maxX = minX + (rawWidth + 360);
}
double ext_rawWidth = ext_maxX - ext_minX;
if (ext_rawWidth == 360)
return SpatialRelation.WITHIN;
if (ext_rawWidth < 0) {
ext_maxX = ext_minX + (ext_rawWidth + 360);
}
//shift to potentially overlap
if (maxX < ext_minX) {
minX += 360;
maxX += 360;
} else if (ext_maxX < minX) {
ext_minX += 360;
ext_maxX += 360;
}
}
return relate_range(minX, maxX, ext_minX, ext_maxX);
}
@Override
public String toString() {
return "Rect(minX=" + minX + ",maxX=" + maxX + ",minY=" + minY + ",maxY=" + maxY + ")";
}
@Override
public Point getCenter() {
if (Double.isNaN(minX))
return ctx.makePoint(Double.NaN, Double.NaN);
final double y = getHeight() / 2 + minY;
double x = getWidth() / 2 + minX;
if (minX > maxX)//WGS84
x = DistanceUtils.normLonDEG(x);//in case falls outside the standard range
return new PointImpl(x, y, ctx);
}
@Override
public boolean equals(Object obj) {
return equals(this,obj);
}
/**
* All {@link Rectangle} implementations should use this definition of {@link Object#equals(Object)}.
*/
public static boolean equals(Rectangle thiz, Object o) {
assert thiz != null;
if (thiz == o) return true;
if (!(o instanceof Rectangle)) return false;
RectangleImpl rectangle = (RectangleImpl) o;
if (Double.compare(rectangle.getMaxX(), thiz.getMaxX()) != 0) return false;
if (Double.compare(rectangle.getMaxY(), thiz.getMaxY()) != 0) return false;
if (Double.compare(rectangle.getMinX(), thiz.getMinX()) != 0) return false;
if (Double.compare(rectangle.getMinY(), thiz.getMinY()) != 0) return false;
return true;
}
@Override
public int hashCode() {
return hashCode(this);
}
/**
* All {@link Rectangle} implementations should use this definition of {@link Object#hashCode()}.
*/
public static int hashCode(Rectangle thiz) {
int result;
long temp;
temp = thiz.getMinX() != +0.0d ? Double.doubleToLongBits(thiz.getMinX()) : 0L;
result = (int) (temp ^ (temp >>> 32));
temp = thiz.getMaxX() != +0.0d ? Double.doubleToLongBits(thiz.getMaxX()) : 0L;
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = thiz.getMinY() != +0.0d ? Double.doubleToLongBits(thiz.getMinY()) : 0L;
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = thiz.getMaxY() != +0.0d ? Double.doubleToLongBits(thiz.getMaxY()) : 0L;
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/jts/ 0000775 0000000 0000000 00000000000 12417676335 0023661 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/jts/JtsGeometry.java 0000775 0000000 0000000 00000046217 12417676335 0027015 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.shape.jts;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
import com.spatial4j.core.shape.impl.BufferedLineString;
import com.spatial4j.core.shape.impl.PointImpl;
import com.spatial4j.core.shape.impl.Range;
import com.spatial4j.core.shape.impl.RectangleImpl;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.CoordinateSequenceFilter;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFilter;
import com.vividsolutions.jts.geom.IntersectionMatrix;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Lineal;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.Puntal;
import com.vividsolutions.jts.geom.prep.PreparedGeometry;
import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory;
import com.vividsolutions.jts.operation.union.UnaryUnionOp;
import com.vividsolutions.jts.operation.valid.IsValidOp;
import java.util.ArrayList;
import java.util.List;
/**
* Wraps a JTS {@link Geometry} (i.e. may be a polygon or basically anything).
* JTS does a great deal of the hard work, but there is work here in handling
* dateline wrap.
*/
public class JtsGeometry implements Shape {
/** System property boolean that can disable auto validation in an assert. */
public static final String SYSPROP_ASSERT_VALIDATE = "spatial4j.JtsGeometry.assertValidate";
private final Geometry geom;//cannot be a direct instance of GeometryCollection as it doesn't support relate()
private final boolean hasArea;
private final Rectangle bbox;
protected final JtsSpatialContext ctx;
protected PreparedGeometry preparedGeometry;
protected boolean validated = false;
public JtsGeometry(Geometry geom, JtsSpatialContext ctx, boolean dateline180Check, boolean allowMultiOverlap) {
this.ctx = ctx;
//GeometryCollection isn't supported in relate()
if (geom.getClass().equals(GeometryCollection.class))
throw new IllegalArgumentException("JtsGeometry does not support GeometryCollection but does support its subclasses.");
//NOTE: All this logic is fairly expensive. There are some short-circuit checks though.
if (ctx.isGeo()) {
//Unwraps the geometry across the dateline so it exceeds the standard geo bounds (-180 to +180).
if (dateline180Check)
unwrapDateline(geom);//potentially modifies geom
//If given multiple overlapping polygons, fix it by union
if (allowMultiOverlap)
geom = unionGeometryCollection(geom);//returns same or new geom
//Cuts an unwrapped geometry back into overlaid pages in the standard geo bounds.
geom = cutUnwrappedGeomInto360(geom);//returns same or new geom
assert geom.getEnvelopeInternal().getWidth() <= 360;
assert ! geom.getClass().equals(GeometryCollection.class) : "GeometryCollection unsupported";//double check
//Compute bbox
bbox = computeGeoBBox(geom);
} else {//not geo
//If given multiple overlapping polygons, fix it by union
if (allowMultiOverlap)
geom = unionGeometryCollection(geom);//returns same or new geom
Envelope env = geom.getEnvelopeInternal();
bbox = new RectangleImpl(env.getMinX(), env.getMaxX(), env.getMinY(), env.getMaxY(), ctx);
}
geom.getEnvelopeInternal();//ensure envelope is cached internally, which is lazy evaluated. Keeps this thread-safe.
this.geom = geom;
assert assertValidate();//kinda expensive but caches valid state
this.hasArea = !((geom instanceof Lineal) || (geom instanceof Puntal));
}
/** called via assertion */
private boolean assertValidate() {
String assertValidate = System.getProperty(SYSPROP_ASSERT_VALIDATE);
if (assertValidate == null || Boolean.parseBoolean(assertValidate))
validate();
return true;
}
/**
* Validates the shape, throwing a descriptive error if it isn't valid. Note that this
* is usually called automatically by default, but that can be disabled.
*
* @throws InvalidShapeException with descriptive error if the shape isn't valid
*/
public void validate() throws InvalidShapeException {
if (!validated) {
IsValidOp isValidOp = new IsValidOp(geom);
if (!isValidOp.isValid())
throw new InvalidShapeException(isValidOp.getValidationError().toString());
validated = true;
}
}
/**
* Adds an index to this class internally to compute spatial relations faster. In JTS this
* is called a {@link com.vividsolutions.jts.geom.prep.PreparedGeometry}. This
* isn't done by default because it takes some time to do the optimization, and it uses more
* memory. Calling this method isn't thread-safe so be careful when this is done. If it was
* already indexed then nothing happens.
*/
public void index() {
if (preparedGeometry == null)
preparedGeometry = PreparedGeometryFactory.prepare(geom);
}
@Override
public boolean isEmpty() {
return geom.isEmpty();
}
/** Given {@code geoms} which has already been checked for being in world
* bounds, return the minimal longitude range of the bounding box.
*/
protected Rectangle computeGeoBBox(Geometry geoms) {
if (geoms.isEmpty())
return new RectangleImpl(Double.NaN, Double.NaN, Double.NaN, Double.NaN, ctx);
final Envelope env = geoms.getEnvelopeInternal();//for minY & maxY (simple)
if (env.getWidth() > 180 && geoms.getNumGeometries() > 1) {
// This is ShapeCollection's bbox algorithm
Range xRange = null;
for (int i = 0; i < geoms.getNumGeometries(); i++ ) {
Envelope envI = geoms.getGeometryN(i).getEnvelopeInternal();
Range xRange2 = new Range.LongitudeRange(envI.getMinX(), envI.getMaxX());
if (xRange == null) {
xRange = xRange2;
} else {
xRange = xRange.expandTo(xRange2);
}
if (xRange == Range.LongitudeRange.WORLD_180E180W)
break; // can't grow any bigger
}
return new RectangleImpl(xRange.getMin(), xRange.getMax(), env.getMinY(), env.getMaxY(), ctx);
} else {
return new RectangleImpl(env.getMinX(), env.getMaxX(), env.getMinY(), env.getMaxY(), ctx);
}
}
@Override
public JtsGeometry getBuffered(double distance, SpatialContext ctx) {
//TODO doesn't work correctly across the dateline. The buffering needs to happen
// when it's transiently unrolled, prior to being sliced.
return this.ctx.makeShape(geom.buffer(distance), true, true);
}
@Override
public boolean hasArea() {
return hasArea;
}
@Override
public double getArea(SpatialContext ctx) {
double geomArea = geom.getArea();
if (ctx == null || geomArea == 0)
return geomArea;
//Use the area proportional to how filled the bbox is.
double bboxArea = getBoundingBox().getArea(null);//plain 2d area
assert bboxArea >= geomArea;
double filledRatio = geomArea / bboxArea;
return getBoundingBox().getArea(ctx) * filledRatio;
// (Future: if we know we use an equal-area projection then we don't need to
// estimate)
}
@Override
public Rectangle getBoundingBox() {
return bbox;
}
@Override
public JtsPoint getCenter() {
if (isEmpty()) //geom.getCentroid == null
return new JtsPoint(ctx.getGeometryFactory().createPoint((Coordinate)null), ctx);
return new JtsPoint(geom.getCentroid(), ctx);
}
@Override
public SpatialRelation relate(Shape other) {
if (other instanceof Point)
return relate((Point)other);
else if (other instanceof Rectangle)
return relate((Rectangle) other);
else if (other instanceof Circle)
return relate((Circle) other);
else if (other instanceof JtsGeometry)
return relate((JtsGeometry) other);
else if (other instanceof BufferedLineString)
throw new UnsupportedOperationException("Can't use BufferedLineString with JtsGeometry");
return other.relate(this).transpose();
}
public SpatialRelation relate(Point pt) {
if (!getBoundingBox().relate(pt).intersects())
return SpatialRelation.DISJOINT;
Geometry ptGeom;
if (pt instanceof JtsPoint)
ptGeom = ((JtsPoint)pt).getGeom();
else
ptGeom = ctx.getGeometryFactory().createPoint(new Coordinate(pt.getX(), pt.getY()));
return relate(ptGeom);//is point-optimized
}
public SpatialRelation relate(Rectangle rectangle) {
SpatialRelation bboxR = bbox.relate(rectangle);
if (bboxR == SpatialRelation.WITHIN || bboxR == SpatialRelation.DISJOINT)
return bboxR;
// FYI, the right answer could still be DISJOINT or WITHIN, but we don't know yet.
return relate(ctx.getGeometryFrom(rectangle));
}
public SpatialRelation relate(Circle circle) {
SpatialRelation bboxR = bbox.relate(circle);
if (bboxR == SpatialRelation.WITHIN || bboxR == SpatialRelation.DISJOINT)
return bboxR;
//Test each point to see how many of them are outside of the circle.
//TODO consider instead using geom.apply(CoordinateSequenceFilter) -- maybe faster since avoids Coordinate[] allocation
Coordinate[] coords = geom.getCoordinates();
int outside = 0;
int i = 0;
for (Coordinate coord : coords) {
i++;
SpatialRelation sect = circle.relate(new PointImpl(coord.x, coord.y, ctx));
if (sect == SpatialRelation.DISJOINT)
outside++;
if (i != outside && outside != 0)//short circuit: partially outside, partially inside
return SpatialRelation.INTERSECTS;
}
if (i == outside) {
return (relate(circle.getCenter()) == SpatialRelation.DISJOINT)
? SpatialRelation.DISJOINT : SpatialRelation.CONTAINS;
}
assert outside == 0;
return SpatialRelation.WITHIN;
}
public SpatialRelation relate(JtsGeometry jtsGeometry) {
//don't bother checking bbox since geom.relate() does this already
return relate(jtsGeometry.geom);
}
protected SpatialRelation relate(Geometry oGeom) {
//see http://docs.geotools.org/latest/userguide/library/jts/dim9.html#preparedgeometry
if (oGeom instanceof com.vividsolutions.jts.geom.Point) {
if (preparedGeometry != null)
return preparedGeometry.disjoint(oGeom) ? SpatialRelation.DISJOINT : SpatialRelation.CONTAINS;
return geom.disjoint(oGeom) ? SpatialRelation.DISJOINT : SpatialRelation.CONTAINS;
}
if (preparedGeometry == null)
return intersectionMatrixToSpatialRelation(geom.relate(oGeom));
else if (preparedGeometry.covers(oGeom))
return SpatialRelation.CONTAINS;
else if (preparedGeometry.coveredBy(oGeom))
return SpatialRelation.WITHIN;
else if (preparedGeometry.intersects(oGeom))
return SpatialRelation.INTERSECTS;
return SpatialRelation.DISJOINT;
}
public static SpatialRelation intersectionMatrixToSpatialRelation(IntersectionMatrix matrix) {
//As indicated in SpatialRelation javadocs, Spatial4j CONTAINS & WITHIN are
// OGC's COVERS & COVEREDBY
if (matrix.isCovers())
return SpatialRelation.CONTAINS;
else if (matrix.isCoveredBy())
return SpatialRelation.WITHIN;
else if (matrix.isDisjoint())
return SpatialRelation.DISJOINT;
return SpatialRelation.INTERSECTS;
}
@Override
public String toString() {
return geom.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JtsGeometry that = (JtsGeometry) o;
return geom.equalsExact(that.geom);//fast equality for normalized geometries
}
@Override
public int hashCode() {
//FYI if geometry.equalsExact(that.geometry), then their envelopes are the same.
return geom.getEnvelopeInternal().hashCode();
}
public Geometry getGeom() {
return geom;
}
/**
* If geom spans the dateline, then this modifies it to be a
* valid JTS geometry that extends to the right of the standard -180 to +180
* width such that some points are greater than +180 but some remain less.
* Takes care to invoke {@link com.vividsolutions.jts.geom.Geometry#geometryChanged()}
* if needed.
*
* @return The number of times the geometry spans the dateline. >= 0
*/
private static int unwrapDateline(Geometry geom) {
if (geom.getEnvelopeInternal().getWidth() < 180)
return 0;//can't possibly cross the dateline
final int[] crossings = {0};//an array so that an inner class can modify it.
geom.apply(new GeometryFilter() {
@Override
public void filter(Geometry geom) {
int cross = 0;
if (geom instanceof LineString) {//note: LinearRing extends LineString
if (geom.getEnvelopeInternal().getWidth() < 180)
return;//can't possibly cross the dateline
cross = unwrapDateline((LineString) geom);
} else if (geom instanceof Polygon) {
if (geom.getEnvelopeInternal().getWidth() < 180)
return;//can't possibly cross the dateline
cross = unwrapDateline((Polygon) geom);
} else
return;
crossings[0] = Math.max(crossings[0], cross);
}
});//geom.apply()
return crossings[0];
}
/** See {@link #unwrapDateline(Geometry)}. */
private static int unwrapDateline(Polygon poly) {
LineString exteriorRing = poly.getExteriorRing();
int cross = unwrapDateline(exteriorRing);
if (cross > 0) {
//TODO TEST THIS! Maybe bug if doesn't cross but is in another page?
for(int i = 0; i < poly.getNumInteriorRing(); i++) {
LineString innerLineString = poly.getInteriorRingN(i);
unwrapDateline(innerLineString);
for(int shiftCount = 0; ! exteriorRing.contains(innerLineString); shiftCount++) {
if (shiftCount > cross)
throw new IllegalArgumentException("The inner ring doesn't appear to be within the exterior: "
+exteriorRing+" inner: "+innerLineString);
shiftGeomByX(innerLineString, 360);
}
}
poly.geometryChanged();
}
return cross;
}
/** See {@link #unwrapDateline(Geometry)}. */
private static int unwrapDateline(LineString lineString) {
CoordinateSequence cseq = lineString.getCoordinateSequence();
int size = cseq.size();
if (size <= 1)
return 0;
int shiftX = 0;//invariant: == shiftXPage*360
int shiftXPage = 0;
int shiftXPageMin = 0/* <= 0 */, shiftXPageMax = 0; /* >= 0 */
double prevX = cseq.getX(0);
for(int i = 1; i < size; i++) {
double thisX_orig = cseq.getX(i);
assert thisX_orig >= -180 && thisX_orig <= 180 : "X not in geo bounds";
double thisX = thisX_orig + shiftX;
if (prevX - thisX > 180) {//cross dateline from left to right
thisX += 360;
shiftX += 360;
shiftXPage += 1;
shiftXPageMax = Math.max(shiftXPageMax,shiftXPage);
} else if (thisX - prevX > 180) {//cross dateline from right to left
thisX -= 360;
shiftX -= 360;
shiftXPage -= 1;
shiftXPageMin = Math.min(shiftXPageMin,shiftXPage);
}
if (shiftXPage != 0)
cseq.setOrdinate(i, CoordinateSequence.X, thisX);
prevX = thisX;
}
if (lineString instanceof LinearRing) {
assert cseq.getCoordinate(0).equals(cseq.getCoordinate(size-1));
assert shiftXPage == 0;//starts and ends at 0
}
assert shiftXPageMax >= 0 && shiftXPageMin <= 0;
//Unfortunately we are shifting again; it'd be nice to be smarter and shift once
shiftGeomByX(lineString, shiftXPageMin * -360);
int crossings = shiftXPageMax - shiftXPageMin;
if (crossings > 0)
lineString.geometryChanged();
return crossings;
}
private static void shiftGeomByX(Geometry geom, final int xShift) {
if (xShift == 0)
return;
geom.apply(new CoordinateSequenceFilter() {
@Override
public void filter(CoordinateSequence seq, int i) {
seq.setOrdinate(i, CoordinateSequence.X, seq.getX(i) + xShift );
}
@Override public boolean isDone() { return false; }
@Override public boolean isGeometryChanged() { return true; }
});
}
private static Geometry unionGeometryCollection(Geometry geom) {
if (geom instanceof GeometryCollection) {
return geom.union();
}
return geom;
}
/**
* This "pages" through standard geo boundaries offset by multiples of 360
* longitudinally that intersect geom, and the intersecting results of a page
* and the geom are shifted into the standard -180 to +180 and added to a new
* geometry that is returned.
*/
private static Geometry cutUnwrappedGeomInto360(Geometry geom) {
Envelope geomEnv = geom.getEnvelopeInternal();
if (geomEnv.getMinX() >= -180 && geomEnv.getMaxX() <= 180)
return geom;
assert geom.isValid() : "geom";
//TODO opt: support geom's that start at negative pages --
// ... will avoid need to previously shift in unwrapDateline(geom).
List geomList = new ArrayList();
//page 0 is the standard -180 to 180 range
for (int page = 0; true; page++) {
double minX = -180 + page * 360;
if (geomEnv.getMaxX() <= minX)
break;
Geometry rect = geom.getFactory().toGeometry(new Envelope(minX, minX + 360, -90, 90));
assert rect.isValid() : "rect";
Geometry pageGeom = rect.intersection(geom);//JTS is doing some hard work
assert pageGeom.isValid() : "pageGeom";
shiftGeomByX(pageGeom, page * -360);
geomList.add(pageGeom);
}
return UnaryUnionOp.union(geomList);
}
// private static Geometry removePolyHoles(Geometry geom) {
// //TODO this does a deep copy of geom even if no changes needed; be smarter
// GeometryTransformer gTrans = new GeometryTransformer() {
// @Override
// protected Geometry transformPolygon(Polygon geom, Geometry parent) {
// if (geom.getNumInteriorRing() == 0)
// return geom;
// return factory.createPolygon((LinearRing) geom.getExteriorRing(),null);
// }
// };
// return gTrans.transform(geom);
// }
//
// private static Geometry snapAndClean(Geometry geom) {
// return new GeometrySnapper(geom).snapToSelf(GeometrySnapper.computeOverlaySnapTolerance(geom), true);
// }
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/jts/JtsPoint.java 0000664 0000000 0000000 00000006515 12417676335 0026305 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.shape.jts;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
import com.spatial4j.core.shape.impl.PointImpl;
import com.vividsolutions.jts.geom.CoordinateSequence;
/** Wraps a {@link com.vividsolutions.jts.geom.Point}. */
public class JtsPoint implements Point {
private final SpatialContext ctx;
private com.vividsolutions.jts.geom.Point pointGeom;
private final boolean empty;//cached
/** A simple constructor without normalization / validation. */
public JtsPoint(com.vividsolutions.jts.geom.Point pointGeom, SpatialContext ctx) {
this.ctx = ctx;
this.pointGeom = pointGeom;
this.empty = pointGeom.isEmpty();
}
public com.vividsolutions.jts.geom.Point getGeom() {
return pointGeom;
}
@Override
public boolean isEmpty() {
return empty;
}
@Override
public com.spatial4j.core.shape.Point getCenter() {
return this;
}
@Override
public boolean hasArea() {
return false;
}
@Override
public double getArea(SpatialContext ctx) {
return 0;
}
@Override
public Rectangle getBoundingBox() {
return ctx.makeRectangle(this, this);
}
@Override
public Circle getBuffered(double distance, SpatialContext ctx) {
return ctx.makeCircle(this, distance);
}
@Override
public SpatialRelation relate(Shape other) {
// ** NOTE ** the overall order of logic is kept consistent here with simple.PointImpl.
if (isEmpty() || other.isEmpty())
return SpatialRelation.DISJOINT;
if (other instanceof com.spatial4j.core.shape.Point)
return this.equals(other) ? SpatialRelation.INTERSECTS : SpatialRelation.DISJOINT;
return other.relate(this).transpose();
}
@Override
public double getX() {
return isEmpty() ? Double.NaN : pointGeom.getX();
}
@Override
public double getY() {
return isEmpty() ? Double.NaN : pointGeom.getY();
}
@Override
public void reset(double x, double y) {
assert ! isEmpty();
CoordinateSequence cSeq = pointGeom.getCoordinateSequence();
cSeq.setOrdinate(0, CoordinateSequence.X, x);
cSeq.setOrdinate(0, CoordinateSequence.Y, y);
}
@Override
public String toString() {
return "Pt(x="+getX()+",y="+getY()+")";
}
@Override
public boolean equals(Object o) {
return PointImpl.equals(this,o);
}
@Override
public int hashCode() {
return PointImpl.hashCode(this);
}
}
spatial4j-0.4-0.4.1/src/main/java/com/spatial4j/core/shape/package-info.java 0000664 0000000 0000000 00000002070 12417676335 0026247 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.
*/
/** Shapes are the core geometry objects that Spatial4j provides. The types
* exposed in this package should be considered public. Remember not to
* instantiate them directly, use the SpatialContext which acts as a
* factory for shapes. */
package com.spatial4j.core.shape; spatial4j-0.4-0.4.1/src/main/java/overview.html 0000664 0000000 0000000 00000001735 12417676335 0021122 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-0.4-0.4.1/src/test/ 0000775 0000000 0000000 00000000000 12417676335 0015472 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/test/java/ 0000775 0000000 0000000 00000000000 12417676335 0016413 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/test/java/com/ 0000775 0000000 0000000 00000000000 12417676335 0017171 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/ 0000775 0000000 0000000 00000000000 12417676335 0021064 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/ 0000775 0000000 0000000 00000000000 12417676335 0022014 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/TestLog.java 0000664 0000000 0000000 00000004736 12417676335 0024252 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;
import com.carrotsearch.randomizedtesting.rules.TestRuleAdapter;
import org.slf4j.helpers.MessageFormatter;
import java.util.ArrayList;
import java.util.List;
/**
* A utility logger for tests in which log statements are logged following
* test failure only. Add this to a JUnit based test class with a {@link org.junit.Rule}
* annotation.
*/
public class TestLog extends TestRuleAdapter {
//TODO does this need to be threadsafe (such as via thread-local state)?
private static ArrayList logStack = new ArrayList();
private static final int MAX_LOGS = 1000;
public static final TestLog instance = new TestLog();
private TestLog() {}
@Override
protected void before() throws Throwable {
logStack.clear();
}
@Override
protected void afterAlways(List errors) throws Throwable {
if (!errors.isEmpty())
logThenClear();
}
private void logThenClear() {
for (LogEntry entry : logStack) {
System.out.println(MessageFormatter.arrayFormat(entry.msg, entry.args).getMessage());
}
logStack.clear();
}
public static void clear() {
logStack.clear();
}
/**
* Enqueues a log message with substitution arguments ala SLF4J (i.e. {} syntax).
* If the test fails then it'll be logged then, otherwise it'll be forgotten.
*/
public static void log(String msg, Object... args) {
if (logStack.size() > MAX_LOGS) {
throw new RuntimeException("Too many log statements: "+logStack.size() + " > "+MAX_LOGS);
}
LogEntry entry = new LogEntry();
entry.msg = msg;
entry.args = args;
logStack.add(entry);
}
private static class LogEntry { String msg; Object[] args; }
}
spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/context/ 0000775 0000000 0000000 00000000000 12417676335 0023500 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/context/SpatialContextFactoryTest.java 0000664 0000000 0000000 00000011701 12417676335 0031475 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.context;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.distance.CartesianDistCalc;
import com.spatial4j.core.distance.GeodesicSphereDistCalc;
import com.spatial4j.core.io.jts.JtsWktShapeParser;
import com.spatial4j.core.shape.impl.RectangleImpl;
import org.junit.After;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class SpatialContextFactoryTest {
public static final String PROP = "SpatialContextFactory";
@After
public void tearDown() {
System.getProperties().remove(PROP);
}
private SpatialContext call(String... argsStr) {
Map args = new HashMap();
for (int i = 0; i < argsStr.length; i+=2) {
String key = argsStr[i];
String val = argsStr[i+1];
args.put(key,val);
}
return SpatialContextFactory.makeSpatialContext(args, getClass().getClassLoader());
}
@Test
public void testDefault() {
SpatialContext ctx = SpatialContext.GEO;
SpatialContext ctx2 = call();//default
assertEquals(ctx.getClass(), ctx2.getClass());
assertEquals(ctx.isGeo(), ctx2.isGeo());
assertEquals(ctx.getDistCalc(),ctx2.getDistCalc());
assertEquals(ctx.getWorldBounds(), ctx2.getWorldBounds());
}
@Test
public void testCustom() {
SpatialContext ctx = call("geo","false");
assertTrue(!ctx.isGeo());
assertEquals(new CartesianDistCalc(), ctx.getDistCalc());
ctx = call("geo","false",
"distCalculator","cartesian^2",
"worldBounds","ENVELOPE(-100, 75, 200, 0)");//xMin, xMax, yMax, yMin
assertEquals(new CartesianDistCalc(true),ctx.getDistCalc());
assertEquals(new RectangleImpl(-100, 75, 0, 200, ctx), ctx.getWorldBounds());
ctx = call("geo","true",
"distCalculator","lawOfCosines");
assertTrue(ctx.isGeo());
assertEquals(new GeodesicSphereDistCalc.LawOfCosines(),
ctx.getDistCalc());
}
@Test
public void testJtsContextFactory() {
JtsSpatialContext ctx = (JtsSpatialContext) call(
"spatialContextFactory", JtsSpatialContextFactory.class.getName(),
"geo", "true",
"normWrapLongitude", "true",
"precisionScale", "2.0",
"wktShapeParserClass", CustomWktShapeParser.class.getName(),
"datelineRule", "ccwRect",
"validationRule", "repairConvexHull",
"autoIndex", "true");
assertTrue(ctx.isNormWrapLongitude());
assertEquals(2.0, ctx.getGeometryFactory().getPrecisionModel().getScale(), 0.0);
assertTrue(CustomWktShapeParser.once);//cheap way to test it was created
assertEquals(JtsWktShapeParser.DatelineRule.ccwRect,
((JtsWktShapeParser)ctx.getWktShapeParser()).getDatelineRule());
assertEquals(JtsWktShapeParser.ValidationRule.repairConvexHull,
((JtsWktShapeParser)ctx.getWktShapeParser()).getValidationRule());
//ensure geo=false with worldbounds works -- fixes #72
ctx = (JtsSpatialContext) call(
"spatialContextFactory", JtsSpatialContextFactory.class.getName(),
"geo", "false",//set to false
"worldBounds", "ENVELOPE(-500,500,300,-300)",
"normWrapLongitude", "true",
"precisionScale", "2.0",
"wktShapeParserClass", CustomWktShapeParser.class.getName(),
"datelineRule", "ccwRect",
"validationRule", "repairConvexHull",
"autoIndex", "true");
assertEquals(300, ctx.getWorldBounds().getMaxY(), 0.0);
}
@Test
public void testSystemPropertyLookup() {
System.setProperty(PROP,DSCF.class.getName());
assertTrue(!call().isGeo());//DSCF returns this
}
public static class DSCF extends SpatialContextFactory {
@Override
public SpatialContext newSpatialContext() {
geo = false;
return new SpatialContext(this);
}
}
public static class CustomWktShapeParser extends JtsWktShapeParser {
static boolean once = false;//cheap way to test it was created
public CustomWktShapeParser(JtsSpatialContext ctx, JtsSpatialContextFactory factory) {
super(ctx, factory);
once = true;
}
}
}
spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/distance/ 0000775 0000000 0000000 00000000000 12417676335 0023606 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/distance/CompareRadiansSnippet.java 0000775 0000000 0000000 00000002627 12417676335 0030716 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.distance;
//import static com.spatial4j.core.distance.DistanceUtils.toRadians;
import static java.lang.Math.toRadians;
/**
* On my machine, using
* Math.toRadians: 2090
* DistanceUtils: 626
*/
public class CompareRadiansSnippet {
public static void main(String[] args) {
long start = System.currentTimeMillis();
double x = 1.12345;
for (int i=0; i<100000000; i++) {
x += toRadians(x) - toRadians(x+1);
}
System.out.println(x); // need to use the result... otherwise JVM may skip everything
System.out.println(System.currentTimeMillis()-start);
}
}
spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/distance/TestDistances.java 0000664 0000000 0000000 00000027406 12417676335 0027237 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.distance;
import com.carrotsearch.randomizedtesting.RandomizedTest;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.SpatialRelation;
import com.spatial4j.core.shape.impl.PointImpl;
import org.junit.Before;
import org.junit.Test;
import static com.spatial4j.core.distance.DistanceUtils.DEG_TO_KM;
import static com.spatial4j.core.distance.DistanceUtils.KM_TO_DEG;
public class TestDistances extends RandomizedTest {
//NOTE! These are sometimes modified by tests.
private SpatialContext ctx;
private double EPS;
@Before
public void beforeTest() {
ctx = SpatialContext.GEO;
EPS = 10e-4;//delta when doing double assertions. Geo eps is not that small.
}
private DistanceCalculator dc() {
return ctx.getDistCalc();
}
@Test
public void testSomeDistances() {
//See to verify: from http://www.movable-type.co.uk/scripts/latlong.html
Point ctr = pLL(0,100);
assertEquals(11100, dc().distance(ctr, pLL(10, 0)) * DEG_TO_KM, 3);
double deg = dc().distance(ctr, pLL(10, -160));
assertEquals(11100, deg * DEG_TO_KM, 3);
assertEquals(314.40338, dc().distance(pLL(1, 2), pLL(3, 4)) * DEG_TO_KM, EPS);
}
@Test
public void testCalcBoxByDistFromPt() {
//first test regression
{
double d = 6894.1 * KM_TO_DEG;
Point pCtr = pLL(-20, 84);
Point pTgt = pLL(-42, 15);
assertTrue(dc().distance(pCtr, pTgt) < d);
//since the pairwise distance is less than d, a bounding box from ctr with d should contain pTgt.
Rectangle r = dc().calcBoxByDistFromPt(pCtr, d, ctx, null);
assertEquals(SpatialRelation.CONTAINS,r.relate(pTgt));
checkBBox(pCtr,d);
}
assertEquals("0 dist, horiz line",
-45,dc().calcBoxByDistFromPt_yHorizAxisDEG(ctx.makePoint(-180, -45), 0, ctx),0);
double MAXDIST = (double) 180 * DEG_TO_KM;
checkBBox(ctx.makePoint(0,0), MAXDIST);
checkBBox(ctx.makePoint(0,0), MAXDIST *0.999999);
checkBBox(ctx.makePoint(0,0),0);
checkBBox(ctx.makePoint(0,0),0.000001);
checkBBox(ctx.makePoint(0,90),0.000001);
checkBBox(ctx.makePoint(-32.7,-5.42),9829);
checkBBox(ctx.makePoint(0,90-20), (double) 20 * DEG_TO_KM);
{
double d = 0.010;//10m
checkBBox(ctx.makePoint(0,90- (d + 0.001) * KM_TO_DEG),d);
}
for (int T = 0; T < 100; T++) {
double lat = -90 + randomDouble()*180;
double lon = -180 + randomDouble()*360;
Point ctr = ctx.makePoint(lon, lat);
double dist = MAXDIST*randomDouble();
checkBBox(ctr, dist);
}
}
private void checkBBox(Point ctr, double distKm) {
String msg = "ctr: "+ctr+" distKm: "+distKm;
double dist = distKm * KM_TO_DEG;
Rectangle r = dc().calcBoxByDistFromPt(ctr, dist, ctx, null);
double horizAxisLat = dc().calcBoxByDistFromPt_yHorizAxisDEG(ctr, dist, ctx);
if (!Double.isNaN(horizAxisLat))
assertTrue(r.relateYRange(horizAxisLat, horizAxisLat).intersects());
//horizontal
if (r.getWidth() >= 180) {
double deg = dc().distance(ctr, r.getMinX(), r.getMaxY() == 90 ? 90 : -90);
double calcDistKm = deg * DEG_TO_KM;
assertTrue(msg, calcDistKm <= distKm + EPS);
//horizAxisLat is meaningless in this context
} else {
Point tPt = findClosestPointOnVertToPoint(r.getMinX(), r.getMinY(), r.getMaxY(), ctr);
double calcDistKm = dc().distance(ctr, tPt) * DEG_TO_KM;
assertEquals(msg, distKm, calcDistKm, EPS);
assertEquals(msg, tPt.getY(), horizAxisLat, EPS);
}
//vertical
double topDistKm = dc().distance(ctr, ctr.getX(), r.getMaxY()) * DEG_TO_KM;
if (r.getMaxY() == 90)
assertTrue(msg, topDistKm <= distKm + EPS);
else
assertEquals(msg, distKm, topDistKm, EPS);
double botDistKm = dc().distance(ctr, ctr.getX(), r.getMinY()) * DEG_TO_KM;
if (r.getMinY() == -90)
assertTrue(msg, botDistKm <= distKm + EPS);
else
assertEquals(msg, distKm, botDistKm, EPS);
}
private Point findClosestPointOnVertToPoint(double lon, double lowLat, double highLat, Point ctr) {
//A binary search algorithm to find the point along the vertical lon between lowLat & highLat that is closest
// to ctr, and returns the distance.
double midLat = (highLat - lowLat)/2 + lowLat;
double midLatDist = ctx.getDistCalc().distance(ctr,lon,midLat);
for(int L = 0; L < 100 && (highLat - lowLat > 0.001|| L < 20); L++) {
boolean bottom = (midLat - lowLat > highLat - midLat);
double newMid = bottom ? (midLat - lowLat)/2 + lowLat : (highLat - midLat)/2 + midLat;
double newMidDist = ctx.getDistCalc().distance(ctr,lon,newMid);
if (newMidDist < midLatDist) {
if (bottom) {
highLat = midLat;
} else {
lowLat = midLat;
}
midLat = newMid;
midLatDist = newMidDist;
} else {
if (bottom) {
lowLat = newMid;
} else {
highLat = newMid;
}
}
}
return ctx.makePoint(lon,midLat);
}
@Test
public void testDistCalcPointOnBearing_cartesian() {
ctx = new SpatialContext(false);
EPS = 10e-6;//tighter epsilon (aka delta)
for(int i = 0; i < 1000; i++) {
testDistCalcPointOnBearing(randomInt(100));
}
}
@Test
public void testDistCalcPointOnBearing_geo() {
//The haversine formula has a higher error if the points are near antipodal. We adjust EPS tolerance for this case.
//TODO Eventually we should add the Vincenty formula for improved accuracy, or try some other cleverness.
//test known high delta
// {
// Point c = ctx.makePoint(-103,-79);
// double angRAD = Math.toRadians(236);
// double dist = 20025;
// Point p2 = dc().pointOnBearingRAD(c, dist, angRAD, ctx);
// //Pt(x=76.61200011750923,y=79.04946929870962)
// double calcDist = dc().distance(c, p2);
// assertEqualsRatio(dist, calcDist);
// }
double maxDistKm = (double) 180 * DEG_TO_KM;
for(int i = 0; i < 1000; i++) {
int distKm = randomInt((int) maxDistKm);
EPS = (distKm < maxDistKm*0.75 ? 10e-6 : 10e-3);
testDistCalcPointOnBearing(distKm);
}
}
private void testDistCalcPointOnBearing(double distKm) {
for(int angDEG = 0; angDEG < 360; angDEG += randomIntBetween(1,20)) {
Point c = ctx.makePoint(
DistanceUtils.normLonDEG(randomInt(359)),
randomIntBetween(-90,90));
//0 distance means same point
Point p2 = dc().pointOnBearing(c, 0, angDEG, ctx, null);
assertEquals(c,p2);
p2 = dc().pointOnBearing(c, distKm * KM_TO_DEG, angDEG, ctx, null);
double calcDistKm = dc().distance(c, p2) * DEG_TO_KM;
assertEqualsRatio(distKm, calcDistKm);
}
}
private void assertEqualsRatio(double expected, double actual) {
double delta = Math.abs(actual - expected);
double base = Math.min(actual, expected);
double deltaRatio = base==0 ? delta : Math.min(delta,delta / base);
assertEquals(0,deltaRatio, EPS);
}
@Test
public void testNormLat() {
double[][] lats = new double[][] {
{1.23,1.23},//1.23 might become 1.2299999 after some math and we want to ensure that doesn't happen
{-90,-90},{90,90},{0,0}, {-100,-80},
{-90-180,90},{-90-360,-90},{90+180,-90},{90+360,90},
{-12+180,12}};
for (double[] pair : lats) {
assertEquals("input "+pair[0], pair[1], DistanceUtils.normLatDEG(pair[0]), 0);
}
for(int i = -1000; i < 1000; i += randomInt(9)*10) {
double d = DistanceUtils.normLatDEG(i);
assertTrue(i + " " + d, d >= -90 && d <= 90);
}
}
@Test
public void testNormLon() {
double[][] lons = new double[][] {
{1.23,1.23},//1.23 might become 1.2299999 after some math and we want to ensure that doesn't happen
{-180,-180},{180,+180},{0,0}, {-190,170},{181,-179},
{-180-360,-180},{-180-720,-180},
{180+360,+180},{180+720,+180}};
for (double[] pair : lons) {
assertEquals("input " + pair[0], pair[1], DistanceUtils.normLonDEG(pair[0]), 0);
}
for(int i = -1000; i < 1000; i += randomInt(9)*10) {
double d = DistanceUtils.normLonDEG(i);
assertTrue(i + " " + d, d >= -180 && d <= 180);
}
}
@Test
public void assertDistanceConversion() {
assertDistanceConversion(0);
assertDistanceConversion(500);
assertDistanceConversion(DistanceUtils.EARTH_MEAN_RADIUS_KM);
}
private void assertDistanceConversion(double dist) {
double radius = DistanceUtils.EARTH_MEAN_RADIUS_KM;
//test back & forth conversion for both
double distRAD = DistanceUtils.dist2Radians(dist, radius);
assertEquals(dist, DistanceUtils.radians2Dist(distRAD, radius), EPS);
double distDEG = DistanceUtils.dist2Degrees(dist, radius);
assertEquals(dist, DistanceUtils.degrees2Dist(distDEG, radius), EPS);
//test across rad & deg
assertEquals(distDEG,DistanceUtils.toDegrees(distRAD),EPS);
//test point on bearing
assertEquals(
DistanceUtils.pointOnBearingRAD(0, 0, DistanceUtils.dist2Radians(dist, radius), DistanceUtils.DEG_90_AS_RADS, ctx, new PointImpl(0, 0, ctx)).getX(),
distRAD, 10e-5);
}
private Point pLL(double lat, double lon) {
return ctx.makePoint(lon,lat);
}
@Test
public void testArea() {
double radius = DistanceUtils.EARTH_MEAN_RADIUS_KM * KM_TO_DEG;
//surface of a sphere is 4 * pi * r^2
final double earthArea = 4 * Math.PI * radius * radius;
Circle c = ctx.makeCircle(randomIntBetween(-180,180), randomIntBetween(-90,90),
180);//180 means whole earth
assertEquals(earthArea, c.getArea(ctx), 1.0);
assertEquals(earthArea, ctx.getWorldBounds().getArea(ctx), 1.0);
//now check half earth
Circle cHalf = ctx.makeCircle(c.getCenter(), 90);
assertEquals(earthArea/2, cHalf.getArea(ctx), 1.0);
//circle with same radius at +20 lat with one at -20 lat should have same area as well as bbox with same area
Circle c2 = ctx.makeCircle(c.getCenter(), 30);
Circle c3 = ctx.makeCircle(c.getCenter().getX(), 20, 30);
assertEquals(c2.getArea(ctx), c3.getArea(ctx), 0.01);
Circle c3Opposite = ctx.makeCircle(c.getCenter().getX(), -20, 30);
assertEquals(c3.getArea(ctx), c3Opposite.getArea(ctx), 0.01);
assertEquals(c3.getBoundingBox().getArea(ctx), c3Opposite.getBoundingBox().getArea(ctx), 0.01);
//small shapes near the equator should have similar areas to euclidean rectangle
Rectangle smallRect = ctx.makeRectangle(0, 1, 0, 1);
assertEquals(1.0, smallRect.getArea(null), 0.0);
double smallDelta = smallRect.getArea(null) - smallRect.getArea(ctx);
assertTrue(smallDelta > 0 && smallDelta < 0.0001);
Circle smallCircle = ctx.makeCircle(0,0,1);
smallDelta = smallCircle.getArea(null) - smallCircle.getArea(ctx);
assertTrue(smallDelta > 0 && smallDelta < 0.0001);
//bigger, but still fairly similar
//c2 = ctx.makeCircle(c.getCenter(), 30);
double areaRatio = c2.getArea(null) / c2.getArea(ctx);
assertTrue(areaRatio > 1 && areaRatio < 1.1);
}
}
spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/io/ 0000775 0000000 0000000 00000000000 12417676335 0022423 5 ustar 00root root 0000000 0000000 spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/io/BinaryCodecTest.java 0000664 0000000 0000000 00000006120 12417676335 0026307 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.carrotsearch.randomizedtesting.RandomizedTest;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.ShapeCollection;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.text.ParseException;
import java.util.Arrays;
public class BinaryCodecTest extends RandomizedTest {
final SpatialContext ctx;
private BinaryCodec binaryCodec;
protected BinaryCodecTest(SpatialContext ctx) {
this.ctx = ctx;
binaryCodec = ctx.getBinaryCodec();//stateless
}
public BinaryCodecTest() {
this(SpatialContext.GEO);
}
//This test uses WKT to specify the shapes because the Jts based subclass tests will test
// using floats instead of doubles, and WKT is normalized whereas ctx.makeXXX is not.
@Test
public void testPoint() {
assertRoundTrip(wkt("POINT(-10 80.3)"));
}
@Test
public void testRect() {
assertRoundTrip(wkt("ENVELOPE(-10, 180, 42.3, 0)"));
}
@Test
public void testCircle() {
assertRoundTrip(wkt("BUFFER(POINT(-10 30), 5.2)"));
}
@Test
public void testCollection() {
ShapeCollection s = ctx.makeCollection(
Arrays.asList(
randomShape(),
randomShape(),
randomShape()
)
);
assertRoundTrip(s);
}
protected Shape wkt(String wkt) {
try {
return ctx.readShapeFromWkt(wkt);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
protected Shape randomShape() {
switch (randomInt(2)) {//inclusive
case 0: return wkt("POINT(-10 80.3)");
case 1: return wkt("ENVELOPE(-10, 180, 42.3, 0)");
case 2: return wkt("BUFFER(POINT(-10 30), 5.2)");
default: throw new Error();
}
}
protected void assertRoundTrip(Shape shape) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
binaryCodec.writeShape(new DataOutputStream(baos), shape);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
assertEquals(shape, binaryCodec.readShape(new DataInputStream(bais)));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
spatial4j-0.4-0.4.1/src/test/java/com/spatial4j/core/io/JtsBinaryCodecTest.java 0000664 0000000 0000000 00000005063 12417676335 0026775 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.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import com.spatial4j.core.context.jts.JtsSpatialContext;
import com.spatial4j.core.context.jts.JtsSpatialContextFactory;
import com.spatial4j.core.shape.Shape;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.util.GeometricShapeFactory;
import org.junit.Test;
import java.util.Arrays;
public class JtsBinaryCodecTest extends BinaryCodecTest {
@ParametersFactory
public static Iterable