mirror of https://github.com/apache/lucene.git
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/branches/lucene3795_lsp_spatial_module@1291499 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
c94f72c7f3
commit
66a852234d
|
@ -0,0 +1,202 @@
|
|||
|
||||
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.
|
|
@ -0,0 +1,105 @@
|
|||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<artifactId>lucene-spatial-parent</artifactId>
|
||||
<groupId>org.apache.lucene.spatial</groupId>
|
||||
<version>0.1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>org.apache.lucene.spatial</groupId>
|
||||
<artifactId>spatial-lucene</artifactId>
|
||||
<version>0.1-SNAPSHOT</version>
|
||||
|
||||
<name>Spatial - Lucene</name>
|
||||
<description>
|
||||
lucene stuff
|
||||
</description>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- LUCENE -->
|
||||
<!--
|
||||
We must put lucene-test-framework before lucene-core.
|
||||
https://issues.apache.org/jira/browse/SOLR-3048
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-test-framework</artifactId>
|
||||
<version>${lucene.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-core</artifactId>
|
||||
<version>${lucene.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-queries</artifactId>
|
||||
<version>${lucene.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-analyzers-common</artifactId>
|
||||
<version>${lucene.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-benchmark</artifactId>
|
||||
<version>${lucene.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.3.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>test-jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.context;
|
||||
|
||||
import org.apache.lucene.spatial.base.exception.InvalidShapeException;
|
||||
|
||||
/**
|
||||
* Utility methods related to parsing shapes.
|
||||
* Methods here were formerly in DistanceUtils.
|
||||
*/
|
||||
public class ParseUtils {
|
||||
private ParseUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a string containing <i>dimension</i> values encoded in it, separated by commas, return a String array of length <i>dimension</i>
|
||||
* containing the values.
|
||||
*
|
||||
* @param out A preallocated array. Must be size dimension. If it is not it will be resized.
|
||||
* @param externalVal The value to parse
|
||||
* @param dimension The expected number of values for the point
|
||||
* @return An array of the values that make up the point (aka vector)
|
||||
* @throws org.apache.lucene.spatial.base.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 <i>dimension</i> values encoded in it, separated by commas, return a double array of length <i>dimension</i>
|
||||
* containing the values.
|
||||
*
|
||||
* @param out A preallocated array. Must be size dimension. If it is not it will be resized.
|
||||
* @param externalVal The value to parse
|
||||
* @param dimension The expected number of values for the point
|
||||
* @return An array of the values that make up the point (aka vector)
|
||||
* @throws org.apache.lucene.spatial.base.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;
|
||||
}
|
||||
|
||||
public static final double[] parseLatitudeLongitude(String latLonStr) throws InvalidShapeException {
|
||||
return parseLatitudeLongitude(null, latLonStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <p/>
|
||||
* The latitude is assumed to be the first part of the string and the longitude the second part.
|
||||
*
|
||||
* @param latLon A preallocated array to hold the result
|
||||
* @param latLonStr The string to parse. Latitude is the first value, longitude is the second.
|
||||
* @return The lat long
|
||||
* @throws org.apache.lucene.spatial.base.exception.InvalidShapeException if there was an error parsing
|
||||
*/
|
||||
public static final double[] parseLatitudeLongitude(double[] latLon, String latLonStr) throws InvalidShapeException {
|
||||
if (latLon == null) {
|
||||
latLon = new double[2];
|
||||
}
|
||||
double[] toks = parsePointDouble(null, latLonStr, 2);
|
||||
|
||||
if (toks[0] < -90.0 || toks[0] > 90.0) {
|
||||
throw new InvalidShapeException(
|
||||
"Invalid latitude: latitudes are range -90 to 90: provided lat: ["
|
||||
+ toks[0] + "]");
|
||||
}
|
||||
latLon[0] = toks[0];
|
||||
|
||||
|
||||
if (toks[1] < -180.0 || toks[1] > 180.0) {
|
||||
|
||||
throw new InvalidShapeException(
|
||||
"Invalid longitude: longitudes are range -180 to 180: provided lon: ["
|
||||
+ toks[1] + "]");
|
||||
}
|
||||
latLon[1] = toks[1];
|
||||
|
||||
return latLon;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.context;
|
||||
|
||||
import org.apache.lucene.spatial.base.distance.*;
|
||||
import org.apache.lucene.spatial.base.exception.InvalidShapeException;
|
||||
import org.apache.lucene.spatial.base.shape.Circle;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
import org.apache.lucene.spatial.base.shape.simple.RectangleImpl;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* This holds things like distance units, distance calculator, and world bounds.
|
||||
* Threadsafe & immutable.
|
||||
*/
|
||||
public abstract class SpatialContext {
|
||||
|
||||
//These are non-null
|
||||
private final DistanceUnits units;
|
||||
private final DistanceCalculator calculator;
|
||||
private final Rectangle worldBounds;
|
||||
|
||||
public static RectangleImpl GEO_WORLDBOUNDS = new RectangleImpl(-180,180,-90,90);
|
||||
public static RectangleImpl MAX_WORLDBOUNDS;
|
||||
static {
|
||||
double v = Double.MAX_VALUE;
|
||||
MAX_WORLDBOUNDS = new RectangleImpl(-v, v, -v, v);
|
||||
}
|
||||
|
||||
protected final Double maxCircleDistance;//only for geo
|
||||
protected final boolean NUDGE = false;//TODO document
|
||||
|
||||
/**
|
||||
*
|
||||
* @param units Required; and establishes geo vs cartesian.
|
||||
* @param calculator Optional; defaults to Haversine or cartesian depending on units.
|
||||
* @param worldBounds Optional; defaults to GEO_WORLDBOUNDS or MAX_WORLDBOUNDS depending on units.
|
||||
*/
|
||||
protected SpatialContext(DistanceUnits units, DistanceCalculator calculator, Rectangle worldBounds) {
|
||||
if (units == null)
|
||||
throw new IllegalArgumentException("units can't be null");
|
||||
this.units = units;
|
||||
|
||||
if (calculator == null) {
|
||||
calculator = isGeo()
|
||||
? new GeodesicSphereDistCalc.Haversine(units.earthRadius())
|
||||
: new CartesianDistCalc();
|
||||
}
|
||||
this.calculator = calculator;
|
||||
|
||||
if (worldBounds == null) {
|
||||
worldBounds = isGeo() ? GEO_WORLDBOUNDS : MAX_WORLDBOUNDS;
|
||||
} else {
|
||||
if (isGeo())
|
||||
assert new RectangleImpl(worldBounds).equals(GEO_WORLDBOUNDS);
|
||||
if (worldBounds.getCrossesDateLine())
|
||||
throw new IllegalArgumentException("worldBounds shouldn't cross dateline: "+worldBounds);
|
||||
}
|
||||
//copy so we can ensure we have the right implementation
|
||||
worldBounds = makeRect(worldBounds.getMinX(),worldBounds.getMaxX(),worldBounds.getMinY(),worldBounds.getMaxY());
|
||||
this.worldBounds = worldBounds;
|
||||
|
||||
this.maxCircleDistance = isGeo() ? calculator.degreesToDistance(180) : null;
|
||||
}
|
||||
|
||||
public DistanceUnits getUnits() {
|
||||
return units;
|
||||
}
|
||||
|
||||
public DistanceCalculator getDistCalc() {
|
||||
return calculator;
|
||||
}
|
||||
|
||||
public Rectangle getWorldBounds() {
|
||||
return worldBounds;
|
||||
}
|
||||
|
||||
public double normX(double x) {
|
||||
if (isGeo()) {
|
||||
return DistanceUtils.normLonDEG(x);
|
||||
} else {
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
public double normY(double y) {
|
||||
if (isGeo()) {
|
||||
y = DistanceUtils.normLatDEG(y);
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a geospatial context (true) or simply 2d spatial (false)
|
||||
* @return
|
||||
*/
|
||||
public boolean isGeo() {
|
||||
return getUnits().isGeo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a shape from a given string (ie, X Y, XMin XMax... WKT)
|
||||
*
|
||||
* (1) Point: X Y
|
||||
* 1.23 4.56
|
||||
*
|
||||
* (2) BOX: XMin YMin XMax YMax
|
||||
* 1.23 4.56 7.87 4.56
|
||||
*
|
||||
* (3) WKT
|
||||
* POLYGON( ... )
|
||||
* http://en.wikipedia.org/wiki/Well-known_text
|
||||
*
|
||||
*/
|
||||
public abstract Shape readShape(String value) throws InvalidShapeException;
|
||||
|
||||
public Point readLatCommaLonPoint(String value) throws InvalidShapeException {
|
||||
double[] latLon = ParseUtils.parseLatitudeLongitude(value);
|
||||
return makePoint(latLon[1],latLon[0]);
|
||||
}
|
||||
|
||||
public abstract String toString(Shape shape);
|
||||
|
||||
/** Construct a point. The parameters will be normalized. */
|
||||
public abstract Point makePoint( double x, double y );
|
||||
|
||||
/** Construct a rectangle. The parameters will be normalized. */
|
||||
public abstract Rectangle makeRect(double minX, double maxX, double minY, double maxY);
|
||||
|
||||
/** Construct a circle. The parameters will be normalized. */
|
||||
public Circle makeCircle(double x, double y, double distance) {
|
||||
return makeCircle(makePoint(x,y),distance);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ctr
|
||||
* @param distance The units of "distance" should be the same as {@link #getUnits()}.
|
||||
* @return
|
||||
*/
|
||||
public abstract Circle makeCircle(Point ctr, double distance);
|
||||
|
||||
protected Shape readStandardShape(String str) {
|
||||
if (str.length() < 1) {
|
||||
throw new InvalidShapeException(str);
|
||||
}
|
||||
|
||||
if(Character.isLetter(str.charAt(0))) {
|
||||
if( 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);
|
||||
} else {
|
||||
double x = Double.parseDouble(token);
|
||||
double y = Double.parseDouble(st.nextToken());
|
||||
pt = 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 makeCircle(pt, d);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (str.indexOf(',') != -1)
|
||||
return readLatCommaLonPoint(str);
|
||||
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 makeRect(p0, p2, p1, p3);
|
||||
}
|
||||
return makePoint(p0, p1);
|
||||
}
|
||||
|
||||
public String writeRect(Rectangle rect) {
|
||||
NumberFormat nf = NumberFormat.getInstance(Locale.US);
|
||||
nf.setGroupingUsed(false);
|
||||
nf.setMaximumFractionDigits(6);
|
||||
nf.setMinimumFractionDigits(6);
|
||||
|
||||
return
|
||||
nf.format(rect.getMinX()) + " " +
|
||||
nf.format(rect.getMinY()) + " " +
|
||||
nf.format(rect.getMaxX()) + " " +
|
||||
nf.format(rect.getMaxY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName()+"{" +
|
||||
"units=" + units +
|
||||
", calculator=" + calculator +
|
||||
", worldBounds=" + worldBounds +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.context;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.simple.SimpleSpatialContext;
|
||||
import org.apache.lucene.spatial.base.context.simple.SimpleSpatialContextFactory;
|
||||
import org.apache.lucene.spatial.base.distance.*;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Factory for a SpatialContext.
|
||||
* is
|
||||
* @author dsmiley
|
||||
*/
|
||||
public abstract class SpatialContextFactory {
|
||||
protected Map<String, String> args;
|
||||
protected ClassLoader classLoader;
|
||||
|
||||
protected DistanceUnits units;
|
||||
protected DistanceCalculator calculator;
|
||||
protected Rectangle worldBounds;
|
||||
|
||||
/**
|
||||
* The factory class is lookuped up via "spatialContextFactory" in args
|
||||
* then falling back to a Java system property (with initial caps). If neither are specified
|
||||
* then {@link SimpleSpatialContextFactory} is chosen.
|
||||
* @param args
|
||||
* @param classLoader
|
||||
* @return
|
||||
*/
|
||||
public static SpatialContext makeSpatialContext(Map<String,String> args, ClassLoader classLoader) {
|
||||
SpatialContextFactory instance;
|
||||
String cname = args.get("spatialContextFactory");
|
||||
if (cname == null)
|
||||
cname = System.getProperty("SpatialContextFactory");
|
||||
if (cname == null)
|
||||
instance = new SimpleSpatialContextFactory();
|
||||
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<String, String> args, ClassLoader classLoader) {
|
||||
this.args = args;
|
||||
this.classLoader = classLoader;
|
||||
initUnits();
|
||||
initCalculator();
|
||||
initWorldBounds();
|
||||
}
|
||||
|
||||
protected void initUnits() {
|
||||
String unitsStr = args.get("units");
|
||||
if (unitsStr != null)
|
||||
units = DistanceUnits.findDistanceUnit(unitsStr);
|
||||
if (units == null)
|
||||
units = DistanceUnits.KILOMETERS;
|
||||
}
|
||||
|
||||
protected void initCalculator() {
|
||||
String calcStr = args.get("distCalculator");
|
||||
if (calcStr == null)
|
||||
return;
|
||||
if (calcStr.equalsIgnoreCase("haversine")) {
|
||||
calculator = new GeodesicSphereDistCalc.Haversine(units.earthRadius());
|
||||
} else if (calcStr.equalsIgnoreCase("lawOfCosines")) {
|
||||
calculator = new GeodesicSphereDistCalc.LawOfCosines(units.earthRadius());
|
||||
} else if (calcStr.equalsIgnoreCase("vincentySphere")) {
|
||||
calculator = new GeodesicSphereDistCalc.Vincenty(units.earthRadius());
|
||||
} else if (calcStr.equalsIgnoreCase("cartesian")) {
|
||||
calculator = new CartesianDistCalc();
|
||||
} else if (calcStr.equalsIgnoreCase("cartesian^2")) {
|
||||
calculator = 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
|
||||
SimpleSpatialContext simpleCtx = new SimpleSpatialContext(units, calculator, null);
|
||||
worldBounds = (Rectangle) simpleCtx.readShape(worldBoundsStr);
|
||||
}
|
||||
|
||||
/** Subclasses should simply construct the instance from the initialized configuration. */
|
||||
protected abstract SpatialContext newSpatialContext();
|
||||
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.context.simple;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.distance.DistanceCalculator;
|
||||
import org.apache.lucene.spatial.base.distance.DistanceUnits;
|
||||
import org.apache.lucene.spatial.base.exception.InvalidShapeException;
|
||||
import org.apache.lucene.spatial.base.shape.Circle;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
import org.apache.lucene.spatial.base.shape.simple.CircleImpl;
|
||||
import org.apache.lucene.spatial.base.shape.simple.GeoCircleImpl;
|
||||
import org.apache.lucene.spatial.base.shape.simple.PointImpl;
|
||||
import org.apache.lucene.spatial.base.shape.simple.RectangleImpl;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
public class SimpleSpatialContext extends SpatialContext {
|
||||
|
||||
public static SimpleSpatialContext GEO_KM = new SimpleSpatialContext(DistanceUnits.KILOMETERS);
|
||||
|
||||
public SimpleSpatialContext(DistanceUnits units) {
|
||||
this(units, null, null);
|
||||
}
|
||||
|
||||
public SimpleSpatialContext(DistanceUnits units, DistanceCalculator calculator, Rectangle worldBounds) {
|
||||
super(units, calculator, worldBounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape readShape(String value) throws InvalidShapeException {
|
||||
Shape s = super.readStandardShape( value );
|
||||
if( s == null ) {
|
||||
throw new InvalidShapeException( "Unable to read: "+value );
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Shape shape) {
|
||||
if (Point.class.isInstance(shape)) {
|
||||
NumberFormat nf = NumberFormat.getInstance(Locale.US);
|
||||
nf.setGroupingUsed(false);
|
||||
nf.setMaximumFractionDigits(6);
|
||||
nf.setMinimumFractionDigits(6);
|
||||
Point point = (Point) shape;
|
||||
return nf.format(point.getX()) + " " + nf.format(point.getY());
|
||||
} else if (Rectangle.class.isInstance(shape)) {
|
||||
return writeRect((Rectangle) shape);
|
||||
}
|
||||
return shape.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Circle makeCircle(Point point, double distance) {
|
||||
if (distance < 0)
|
||||
throw new InvalidShapeException("distance must be >= 0; got "+distance);
|
||||
if (isGeo())
|
||||
return new GeoCircleImpl( point, Math.min(distance,maxCircleDistance), this );
|
||||
else
|
||||
return new CircleImpl( point, distance, this );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle makeRect(double minX, double maxX, double minY, double maxY) {
|
||||
//--Normalize parameters
|
||||
if (isGeo()) {
|
||||
double delta = calcWidth(minX,maxX);
|
||||
if (delta >= 360) {
|
||||
//The only way to officially support complete longitude wrap-around is via western longitude = -180. We can't
|
||||
// support any point because 0 is undifferentiated in sign.
|
||||
minX = -180;
|
||||
maxX = 180;
|
||||
} else {
|
||||
minX = normX(minX);
|
||||
maxX = normX(maxX);
|
||||
assert Math.abs(delta - calcWidth(minX,maxX)) < 0.0001;//recompute delta; should be the same
|
||||
}
|
||||
if (minY > maxY) {
|
||||
throw new IllegalArgumentException("maxY must be >= minY");
|
||||
}
|
||||
if (minY < -90 || minY > 90 || maxY < -90 || maxY > 90)
|
||||
throw new IllegalArgumentException("minY or maxY is outside of -90 to 90 bounds. What did you mean?");
|
||||
// debatable what to do in this situation.
|
||||
// if (minY < -90) {
|
||||
// minX = -180;
|
||||
// maxX = 180;
|
||||
// maxY = Math.min(90,Math.max(maxY,-90 + (-90 - minY)));
|
||||
// minY = -90;
|
||||
// }
|
||||
// if (maxY > 90) {
|
||||
// minX = -180;
|
||||
// maxX = 180;
|
||||
// minY = Math.max(-90,Math.min(minY,90 - (maxY - 90)));
|
||||
// maxY = 90;
|
||||
// }
|
||||
|
||||
} else {
|
||||
//these normalizations probably won't do anything since it's not geo but should probably call them any way.
|
||||
minX = normX(minX);
|
||||
maxX = normX(maxX);
|
||||
minY = normY(minY);
|
||||
maxY = normY(maxY);
|
||||
}
|
||||
return new RectangleImpl( minX, maxX, minY, maxY );
|
||||
}
|
||||
|
||||
private double calcWidth(double minX,double maxX) {
|
||||
double w = maxX - minX;
|
||||
if (w < 0) {//only true when minX > maxX (WGS84 assumed)
|
||||
w += 360;
|
||||
assert w >= 0;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point makePoint(double x, double y) {
|
||||
return new PointImpl(normX(x),normY(y));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.context.simple;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.context.SpatialContextFactory;
|
||||
|
||||
/**
|
||||
* @author dsmiley
|
||||
*/
|
||||
public class SimpleSpatialContextFactory extends SpatialContextFactory {
|
||||
@Override
|
||||
protected SpatialContext newSpatialContext() {
|
||||
return new SimpleSpatialContext(units,calculator,worldBounds);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.distance;
|
||||
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
|
||||
/**
|
||||
* @author David Smiley - dsmiley@mitre.org
|
||||
*/
|
||||
public abstract class AbstractDistanceCalculator implements DistanceCalculator {
|
||||
|
||||
@Override
|
||||
public double distance(Point from, Point to) {
|
||||
return distance(from, to.getX(), to.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.distance;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
|
||||
public class CartesianDistCalc extends AbstractDistanceCalculator {
|
||||
|
||||
private final boolean squared;
|
||||
|
||||
public CartesianDistCalc() {
|
||||
this.squared = false;
|
||||
}
|
||||
|
||||
public CartesianDistCalc(boolean squared) {
|
||||
this.squared = squared;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double distance(Point from, double toX, double toY) {
|
||||
double result = 0;
|
||||
|
||||
double v = from.getX() - toX;
|
||||
result += (v * v);
|
||||
|
||||
v = from.getY() - toY;
|
||||
result += (v * v);
|
||||
|
||||
if( squared )
|
||||
return result;
|
||||
|
||||
return Math.sqrt(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point pointOnBearing(Point from, double dist, double bearingDEG, SpatialContext ctx) {
|
||||
if (dist == 0)
|
||||
return from;
|
||||
double bearingRAD = Math.toDegrees(bearingDEG);
|
||||
double x = Math.sin(bearingRAD) * dist;
|
||||
double y = Math.cos(bearingRAD) * dist;
|
||||
return ctx.makePoint(from.getX()+x, from.getY()+y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double distanceToDegrees(double distance) {
|
||||
throw new UnsupportedOperationException("no geo!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public double degreesToDistance(double degrees) {
|
||||
throw new UnsupportedOperationException("no geo!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle calcBoxByDistFromPt(Point from, double distance, SpatialContext ctx) {
|
||||
return ctx.makeRect(from.getX()-distance,from.getX()+distance,from.getY()-distance,from.getY()+distance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double calcBoxByDistFromPtHorizAxis(Point from, double distance, SpatialContext ctx) {
|
||||
return from.getY();
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.distance;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
|
||||
public interface DistanceCalculator {
|
||||
|
||||
public double distance(Point from, Point to);
|
||||
public double distance(Point from, double toX, double toY);
|
||||
|
||||
public Point pointOnBearing(Point from, double dist, double bearingDEG, SpatialContext ctx);
|
||||
|
||||
/**
|
||||
* Converts a distance to radians (multiples of the radius). A spherical
|
||||
* earth model is assumed for geospatial, and non-geospatial is the identity function.
|
||||
*/
|
||||
public double distanceToDegrees(double distance);
|
||||
|
||||
public double degreesToDistance(double degrees);
|
||||
|
||||
//public Point pointOnBearing(Point from, double angle);
|
||||
|
||||
public Rectangle calcBoxByDistFromPt(Point from, double distance, SpatialContext ctx);
|
||||
|
||||
public double calcBoxByDistFromPtHorizAxis(Point from, double distance, SpatialContext ctx);
|
||||
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.distance;
|
||||
|
||||
/**
|
||||
* Enum representing difference distance units, currently only kilometers and
|
||||
* miles
|
||||
*/
|
||||
public enum DistanceUnits {
|
||||
|
||||
//TODO do we need circumference?
|
||||
KILOMETERS("km", DistanceUtils.EARTH_MEAN_RADIUS_KM, 40076),
|
||||
MILES("miles", DistanceUtils.EARTH_MEAN_RADIUS_MI, 24902),
|
||||
RADIANS("radians", 1, Math.PI * 2),//experimental
|
||||
CARTESIAN("u", -1, -1);
|
||||
|
||||
|
||||
private final String units;
|
||||
|
||||
private final double earthCircumference;
|
||||
|
||||
private final double earthRadius;
|
||||
|
||||
/**
|
||||
* Creates a new DistanceUnit that represents the given unit
|
||||
*
|
||||
* @param units Distance unit in String form
|
||||
* @param earthRadius Radius of the Earth in the specific distance unit
|
||||
* @param earthCircumfence Circumference of the Earth in the specific distance unit
|
||||
*/
|
||||
DistanceUnits(String units, double earthRadius, double earthCircumfence) {
|
||||
this.units = units;
|
||||
this.earthCircumference = earthCircumfence;
|
||||
this.earthRadius = earthRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DistanceUnit which represents the given unit
|
||||
*
|
||||
* @param unit Unit whose DistanceUnit should be found
|
||||
* @return DistanceUnit representing the unit
|
||||
* @throws IllegalArgumentException if no DistanceUnit which represents the given unit is found
|
||||
*/
|
||||
public static DistanceUnits findDistanceUnit(String unit) {
|
||||
if (MILES.getUnits().equalsIgnoreCase(unit) || unit.equalsIgnoreCase("mi")) {
|
||||
return MILES;
|
||||
}
|
||||
if (KILOMETERS.getUnits().equalsIgnoreCase(unit)) {
|
||||
return KILOMETERS;
|
||||
}
|
||||
if (CARTESIAN.getUnits().equalsIgnoreCase(unit) || unit.length()==0) {
|
||||
return CARTESIAN;
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown distance unit " + unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given distance in given DistanceUnit, to a distance in the unit represented by {@code this}
|
||||
*
|
||||
* @param distance Distance to convert
|
||||
* @param from Unit to convert the distance from
|
||||
* @return Given distance converted to the distance in the given unit
|
||||
*/
|
||||
public double convert(double distance, DistanceUnits from) {
|
||||
if (from == this) {
|
||||
return distance;
|
||||
}
|
||||
if (this == CARTESIAN || from == CARTESIAN) {
|
||||
throw new IllegalStateException("Can't convert cartesian distances: "+from+" -> "+this);
|
||||
}
|
||||
return (this == MILES) ? distance * DistanceUtils.KM_TO_MILES : distance * DistanceUtils.MILES_TO_KM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representation of the distance unit
|
||||
*
|
||||
* @return String representation of the distance unit
|
||||
*/
|
||||
public String getUnits() {
|
||||
return units;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <a href="http://en.wikipedia.org/wiki/Earth_radius">average earth radius</a>
|
||||
*
|
||||
* @return the average earth radius
|
||||
*/
|
||||
public double earthRadius() {
|
||||
return earthRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <a href="http://www.lyberty.com/encyc/articles/earth.html">circumference of the Earth</a>
|
||||
*
|
||||
* @return the circumference of the Earth
|
||||
*/
|
||||
public double earthCircumference() {
|
||||
return earthCircumference;
|
||||
}
|
||||
|
||||
public boolean isGeo() {
|
||||
return earthRadius > 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,427 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.distance;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
|
||||
import static java.lang.Math.toRadians;
|
||||
|
||||
/**
|
||||
* Various distance calculations and constants.
|
||||
* Originally from Lucene 3x's old spatial module. It has been modified here.
|
||||
*/
|
||||
public class DistanceUtils {
|
||||
|
||||
//pre-compute some angles that are commonly used
|
||||
public static final double DEG_45_AS_RADS = Math.PI / 4.0;
|
||||
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;
|
||||
public static final double DEG_225_AS_RADS = 5 * DEG_45_AS_RADS;
|
||||
public static final double DEG_270_AS_RADS = 3 * DEG_90_AS_RADS;
|
||||
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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.)
|
||||
* @return The length.
|
||||
* <p/>
|
||||
* See http://en.wikipedia.org/wiki/Lp_space
|
||||
* @see #vectorDistance(double[], double[], double, double)
|
||||
*/
|
||||
public static double vectorDistance(double[] vec1, double[] vec2, double power) {
|
||||
return vectorDistance(vec1, vec2, power, 1.0 / power);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 precalculated oneOverPower and cached it, use this method to save one division operation over {@link #vectorDistance(double[], double[], double)}.
|
||||
* @return The length.
|
||||
*/
|
||||
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) {
|
||||
for (int i = 0; i < vec1.length; i++) {
|
||||
result += vec1[i] - vec2[i];
|
||||
}
|
||||
} else if (power == 2.0) {
|
||||
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
|
||||
*/
|
||||
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 of radius <i>sphereRadius</i>, 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 result A preallocated array to hold the results. If null, a new one is constructed.
|
||||
* @return The destination point, in radians. First entry is latitude, second is longitude
|
||||
*/
|
||||
public static double[] pointOnBearingRAD(double startLat, double startLon, double distanceRAD, double bearingRAD, double[] result) {
|
||||
/*
|
||||
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 lat2 = Math.asin(sinStartLat * cosAngDist +
|
||||
cosStartLat * sinAngDist * Math.cos(bearingRAD));
|
||||
|
||||
double lon2 = startLon + Math.atan2(Math.sin(bearingRAD) * sinAngDist * cosStartLat,
|
||||
cosAngDist - sinStartLat * Math.sin(lat2));
|
||||
|
||||
/*lat2 = (lat2*180)/Math.PI;
|
||||
lon2 = (lon2*180)/Math.PI;*/
|
||||
//From Lucene. Move back to Lucene when synced
|
||||
// normalize lon first
|
||||
if (result == null || result.length != 2){
|
||||
result = new double[2];
|
||||
}
|
||||
result[0] = lat2;
|
||||
result[1] = lon2;
|
||||
normLngRAD(result);
|
||||
|
||||
// normalize lat - could flip poles
|
||||
normLatRAD(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param latLng The lat/lon, in radians. lat in position 0, lon in position 1
|
||||
*/
|
||||
public static void normLatRAD(double[] latLng) {
|
||||
|
||||
if (latLng[0] > DEG_90_AS_RADS) {
|
||||
latLng[0] = DEG_90_AS_RADS - (latLng[0] - DEG_90_AS_RADS);
|
||||
if (latLng[1] < 0) {
|
||||
latLng[1] = latLng[1] + DEG_180_AS_RADS;
|
||||
} else {
|
||||
latLng[1] = latLng[1] - DEG_180_AS_RADS;
|
||||
}
|
||||
} else if (latLng[0] < -DEG_90_AS_RADS) {
|
||||
latLng[0] = -DEG_90_AS_RADS - (latLng[0] + DEG_90_AS_RADS);
|
||||
if (latLng[1] < 0) {
|
||||
latLng[1] = latLng[1] + DEG_180_AS_RADS;
|
||||
} else {
|
||||
latLng[1] = latLng[1] - DEG_180_AS_RADS;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a normalized Lng rectangle shape for the bounding box
|
||||
*
|
||||
* @param latLng The lat/lon, in radians, lat in position 0, lon in position 1
|
||||
*/
|
||||
@Deprecated
|
||||
public static void normLngRAD(double[] latLng) {
|
||||
if (latLng[1] > DEG_180_AS_RADS) {
|
||||
latLng[1] = -1.0 * (DEG_180_AS_RADS - (latLng[1] - DEG_180_AS_RADS));
|
||||
} else if (latLng[1] < -DEG_180_AS_RADS) {
|
||||
latLng[1] = (latLng[1] + DEG_180_AS_RADS) + DEG_180_AS_RADS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
return off < 0 ? 180 + off : -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;
|
||||
}
|
||||
|
||||
public static Rectangle calcBoxByDistFromPtDEG(double lat, double lon, double distance, SpatialContext ctx) {
|
||||
//See http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates Section 3.1, 3.2 and 3.3
|
||||
|
||||
double radius = ctx.getUnits().earthRadius();
|
||||
double dist_rad = distance / radius;
|
||||
double dist_deg = Math.toDegrees(dist_rad);
|
||||
|
||||
if (dist_deg == 0)
|
||||
return ctx.makeRect(lon,lon,lat,lat);
|
||||
|
||||
if (dist_deg >= 180)//distance is >= opposite side of the globe
|
||||
return ctx.getWorldBounds();
|
||||
|
||||
//--calc latitude bounds
|
||||
double latN_deg = lat + dist_deg;
|
||||
double latS_deg = lat - dist_deg;
|
||||
|
||||
if (latN_deg >= 90 || latS_deg <= -90) {//touches either pole
|
||||
//we have special logic for longitude
|
||||
double lonW_deg = -180, lonE_deg = 180;//world wrap: 360 deg
|
||||
if (latN_deg <= 90 && latS_deg >= -90) {//doesn't pass either pole: 180 deg
|
||||
lonW_deg = lon -90;
|
||||
lonE_deg = lon +90;
|
||||
}
|
||||
if (latN_deg > 90)
|
||||
latN_deg = 90;
|
||||
if (latS_deg < -90)
|
||||
latS_deg = -90;
|
||||
|
||||
return ctx.makeRect(lonW_deg, lonE_deg, latS_deg, latN_deg);
|
||||
} else {
|
||||
//--calc longitude bounds
|
||||
double lon_delta_deg = calcBoxByDistFromPtVertAxisOffsetDEG(lat, lon, distance, radius);
|
||||
|
||||
double lonW_deg = lon -lon_delta_deg;
|
||||
double lonE_deg = lon +lon_delta_deg;
|
||||
|
||||
return ctx.makeRect(lonW_deg, lonE_deg, latS_deg, latN_deg);//ctx will normalize longitude
|
||||
}
|
||||
}
|
||||
|
||||
public static double calcBoxByDistFromPtVertAxisOffsetDEG(double lat, double lon, double distance, double radius) {
|
||||
//http://gis.stackexchange.com/questions/19221/find-tangent-point-on-circle-furthest-east-or-west
|
||||
if (distance == 0)
|
||||
return 0;
|
||||
double lat_rad = toRadians(lat);
|
||||
double dist_rad = distance / radius;
|
||||
double result_rad = Math.asin(Math.sin(dist_rad) / Math.cos(lat_rad));
|
||||
|
||||
if (!Double.isNaN(result_rad))
|
||||
return Math.toDegrees(result_rad);
|
||||
return 90;
|
||||
}
|
||||
|
||||
public static double calcBoxByDistFromPtHorizAxisDEG(double lat, double lon, double distance, double radius) {
|
||||
//http://gis.stackexchange.com/questions/19221/find-tangent-point-on-circle-furthest-east-or-west
|
||||
if (distance == 0)
|
||||
return lat;
|
||||
double lat_rad = toRadians(lat);
|
||||
double dist_rad = distance / radius;
|
||||
double result_rad = Math.asin( Math.sin(lat_rad) / Math.cos(dist_rad));
|
||||
if (!Double.isNaN(result_rad))
|
||||
return Math.toDegrees(result_rad);
|
||||
//TODO should we use use ctx.getBoundaryNudgeDegrees() offsets here or let caller?
|
||||
if (lat > 0)
|
||||
return 90;
|
||||
if (lat < 0)
|
||||
return -90;
|
||||
return lat;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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/lng'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 -- <a href="http://www.movable-type.co.uk/scripts/latlong.html">
|
||||
* allegedly 1 meter</a>.
|
||||
* <p/>
|
||||
* See <a href="http://gis.stackexchange.com/questions/4906/why-is-law-of-cosines-more-preferable-than-haversine-when-calculating-distance-b">
|
||||
* Why is law of cosines more preferable than haversine when calculating distance between two latitude-longitude points?</a>
|
||||
* <p/>
|
||||
* 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())
|
||||
// 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 <a href="http://en.wikipedia.org/wiki/Great-circle_distance">Wikipedia</a>.
|
||||
* <p/>
|
||||
* 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 Math.toDegrees(dist2Radians(dist, radius));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a distance in the units of the radius to radians (multiples of the radius). A spherical
|
||||
* earth model is assumed.
|
||||
*/
|
||||
public static double dist2Radians(double dist, double radius) {
|
||||
return dist / radius;
|
||||
}
|
||||
|
||||
public static double radians2Dist(double radians, double radius) {
|
||||
return radians * radius;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.distance;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
|
||||
import static java.lang.Math.toRadians;
|
||||
|
||||
/**
|
||||
* A base class for a Distance Calculator that assumes a spherical earth model.
|
||||
* @author dsmiley
|
||||
*/
|
||||
public abstract class GeodesicSphereDistCalc extends AbstractDistanceCalculator {
|
||||
protected final double radius;
|
||||
|
||||
public GeodesicSphereDistCalc(double radius) {
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double distanceToDegrees(double distance) {
|
||||
return DistanceUtils.dist2Degrees(distance, radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double degreesToDistance(double degrees) {
|
||||
return DistanceUtils.radians2Dist(toRadians(degrees), radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point pointOnBearing(Point from, double dist, double bearingDEG, SpatialContext ctx) {
|
||||
//TODO avoid unnecessary double[] intermediate object
|
||||
if (dist == 0)
|
||||
return from;
|
||||
double[] latLon = DistanceUtils.pointOnBearingRAD(
|
||||
toRadians(from.getY()), toRadians(from.getX()),
|
||||
DistanceUtils.dist2Radians(dist,ctx.getUnits().earthRadius()),
|
||||
toRadians(bearingDEG), null);
|
||||
return ctx.makePoint(Math.toDegrees(latLon[1]), Math.toDegrees(latLon[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle calcBoxByDistFromPt(Point from, double distance, SpatialContext ctx) {
|
||||
assert radius == ctx.getUnits().earthRadius();
|
||||
if (distance == 0)
|
||||
return from.getBoundingBox();
|
||||
return DistanceUtils.calcBoxByDistFromPtDEG(from.getY(), from.getX(), distance, ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double calcBoxByDistFromPtHorizAxis(Point from, double distance, SpatialContext ctx) {
|
||||
return DistanceUtils.calcBoxByDistFromPtHorizAxisDEG(from.getY(), from.getX(), distance, radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
GeodesicSphereDistCalc that = (GeodesicSphereDistCalc) o;
|
||||
|
||||
if (Double.compare(that.radius, radius) != 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
long temp = radius != +0.0d ? Double.doubleToLongBits(radius) : 0L;
|
||||
return (int) (temp ^ (temp >>> 32));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final double distance(Point from, double toX, double toY) {
|
||||
return distanceLatLonRAD(toRadians(from.getY()), toRadians(from.getX()), toRadians(toY), toRadians(toX)) * radius;
|
||||
}
|
||||
|
||||
protected abstract double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2);
|
||||
|
||||
public static class Haversine extends GeodesicSphereDistCalc {
|
||||
|
||||
public Haversine(double radius) {
|
||||
super(radius);
|
||||
}
|
||||
|
||||
@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 {
|
||||
|
||||
public LawOfCosines(double radius) {
|
||||
super(radius);
|
||||
}
|
||||
|
||||
@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 {
|
||||
public Vincenty(double radius) {
|
||||
super(radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double distanceLatLonRAD(double lat1, double lon1, double lat2, double lon2) {
|
||||
return DistanceUtils.distVincentyRAD(lat1, lon1, lat2, lon2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 org.apache.lucene.spatial.base.distance;
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.exception;
|
||||
|
||||
public class InvalidShapeException extends RuntimeException {
|
||||
|
||||
public InvalidShapeException(String reason, Throwable cause) {
|
||||
super(reason, cause);
|
||||
}
|
||||
|
||||
public InvalidShapeException(String reason) {
|
||||
super(reason);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.exception;
|
||||
|
||||
public class InvalidSpatialArgument extends RuntimeException {
|
||||
|
||||
public InvalidSpatialArgument(String reason, Throwable cause) {
|
||||
super(reason, cause);
|
||||
}
|
||||
|
||||
public InvalidSpatialArgument(String reason) {
|
||||
super(reason);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.exception;
|
||||
|
||||
import org.apache.lucene.spatial.base.query.SpatialOperation;
|
||||
|
||||
public class UnsupportedSpatialOperation extends UnsupportedOperationException {
|
||||
|
||||
public UnsupportedSpatialOperation(SpatialOperation op) {
|
||||
super(op.getName());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.io;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.Iterator;
|
||||
|
||||
public abstract class LineReader<T> implements Iterator<T> {
|
||||
|
||||
private int count = 0;
|
||||
private int lineNumber = 0;
|
||||
private BufferedReader reader;
|
||||
private String nextLine;
|
||||
|
||||
public abstract T parseLine( String line );
|
||||
|
||||
protected void readComment( String line ) {
|
||||
|
||||
}
|
||||
|
||||
public LineReader(InputStream in) throws IOException {
|
||||
reader = new BufferedReader(
|
||||
new InputStreamReader( in, "UTF-8" ) );
|
||||
next();
|
||||
}
|
||||
|
||||
public LineReader(Reader r) throws IOException {
|
||||
if (r instanceof BufferedReader) {
|
||||
reader = (BufferedReader) r;
|
||||
} else {
|
||||
reader = new BufferedReader(r);
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
public LineReader(File f) throws IOException {
|
||||
reader = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8"));
|
||||
next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return nextLine != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
T val = null;
|
||||
if (nextLine != null) {
|
||||
val = parseLine(nextLine);
|
||||
count++;
|
||||
}
|
||||
|
||||
if (reader != null) {
|
||||
try {
|
||||
while( reader != null ) {
|
||||
nextLine = reader.readLine();
|
||||
lineNumber++;
|
||||
if (nextLine == null ) {
|
||||
reader.close();
|
||||
reader = null;
|
||||
}
|
||||
else if( nextLine.startsWith( "#" ) ) {
|
||||
readComment( nextLine );
|
||||
}
|
||||
else {
|
||||
nextLine = nextLine.trim();
|
||||
if( nextLine.length() > 0 ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("IOException thrown while reading/closing reader", ioe);
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public int getLineNumber() {
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.io.geonames;
|
||||
|
||||
import java.sql.Date;
|
||||
|
||||
public class Geoname {
|
||||
public int id;
|
||||
public String name; // name of geographical point (utf8) varchar(200)
|
||||
public String nameASCII; // name of geographical point in plain ascii characters, varchar(200)
|
||||
public String[] alternateNames; // alternatenames, comma separated varchar(5000)
|
||||
public double latitude;
|
||||
public double longitude;
|
||||
public char featureClass;
|
||||
public String featureCode; // 10
|
||||
public String countryCode; // 2
|
||||
public String[] countryCode2; // alternate country codes, comma separated, ISO-3166 2-letter country code, 60 characters
|
||||
public String adminCode1; // fipscode (subject to change to iso code), see exceptions below, see file admin1Codes.txt for display names of this code; varchar(20)
|
||||
public String adminCode2; // code for the second administrative division, a county in the US, see file admin2Codes.txt; varchar(80)
|
||||
public String adminCode3; // code for third level administrative division, varchar(20)
|
||||
public String adminCode4; // code for fourth level administrative division, varchar(20)
|
||||
public Long population;
|
||||
public Integer elevation; // in meters, integer
|
||||
public Integer gtopo30; // average elevation of 30'x30' (ca 900mx900m) area in meters, integer
|
||||
public String timezone;
|
||||
public Date modified; // date of last modification in yyyy-MM-dd format
|
||||
|
||||
public Geoname(String line) {
|
||||
String[] vals = line.split("\t");
|
||||
id = Integer.parseInt(vals[0]);
|
||||
name = vals[1];
|
||||
nameASCII = vals[2];
|
||||
alternateNames = vals[3].split(",");
|
||||
latitude = Double.parseDouble(vals[4]);
|
||||
longitude = Double.parseDouble(vals[5]);
|
||||
featureClass = vals[6].length() > 0 ? vals[6].charAt(0) : 'S';
|
||||
featureCode = vals[7];
|
||||
countryCode = vals[8];
|
||||
countryCode2 = vals[9].split(",");
|
||||
adminCode1 = vals[10];
|
||||
adminCode2 = vals[11];
|
||||
adminCode3 = vals[12];
|
||||
adminCode4 = vals[13];
|
||||
if (vals[14].length() > 0) {
|
||||
population = Long.decode(vals[14]);
|
||||
}
|
||||
if (vals[15].length() > 0) {
|
||||
elevation = Integer.decode(vals[15]);
|
||||
}
|
||||
if (vals[16].length() > 0) {
|
||||
gtopo30 = Integer.decode(vals[16]);
|
||||
}
|
||||
timezone = vals[17];
|
||||
if (vals[18].length() > 0) {
|
||||
modified = Date.valueOf(vals[18]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.io.geonames;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
|
||||
import org.apache.lucene.spatial.base.io.LineReader;
|
||||
|
||||
public class GeonamesReader extends LineReader<Geoname> {
|
||||
|
||||
public GeonamesReader(Reader r) throws IOException {
|
||||
super( r );
|
||||
}
|
||||
|
||||
public GeonamesReader(File f) throws IOException {
|
||||
super( f );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Geoname parseLine(String line) {
|
||||
return new Geoname( line );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.io.sample;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
|
||||
public class SampleData {
|
||||
public String id;
|
||||
public String name;
|
||||
public String shape;
|
||||
|
||||
public SampleData(String line) {
|
||||
String[] vals = line.split("\t");
|
||||
id = vals[0];
|
||||
name = vals[1];
|
||||
shape = vals[2];
|
||||
}
|
||||
|
||||
public static Comparator<SampleData> NAME_ORDER = new Comparator<SampleData>() {
|
||||
@Override
|
||||
public int compare(SampleData o1, SampleData o2) {
|
||||
return o1.name.compareTo( o2.name );
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.io.sample;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
|
||||
import org.apache.lucene.spatial.base.io.LineReader;
|
||||
|
||||
public class SampleDataReader extends LineReader<SampleData> {
|
||||
|
||||
public SampleDataReader(InputStream r) throws IOException {
|
||||
super( r );
|
||||
}
|
||||
|
||||
public SampleDataReader(Reader r) throws IOException {
|
||||
super( r );
|
||||
}
|
||||
|
||||
public SampleDataReader(File f) throws IOException {
|
||||
super( f );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleData parseLine(String line) {
|
||||
return new SampleData( line );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 package is spatal stuff without any lucene dependencies
|
||||
* Things implemented in this package could be calculated on the client side
|
||||
*/
|
||||
package org.apache.lucene.spatial.base;
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.prefix;
|
||||
|
||||
import org.apache.lucene.spatial.base.shape.SpatialRelation;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a grid cell. These are not necessarily threadsafe, although new Cell("") (world cell) must be.
|
||||
*/
|
||||
public abstract class Node implements Comparable<Node> {
|
||||
public static final byte LEAF_BYTE = '+';//NOTE: must sort before letters & numbers
|
||||
|
||||
/*
|
||||
Holds a byte[] and/or String representation of the cell. Both are lazy constructed from the other.
|
||||
Neither contains the trailing leaf byte.
|
||||
*/
|
||||
private byte[] bytes;
|
||||
private int b_off;
|
||||
private int b_len;
|
||||
|
||||
private String token;//this is the only part of equality
|
||||
|
||||
protected SpatialRelation shapeRel;//set in getSubCells(filter), and via setLeaf().
|
||||
private SpatialPrefixTree spatialPrefixTree;
|
||||
|
||||
protected Node(SpatialPrefixTree spatialPrefixTree, String token) {
|
||||
this.spatialPrefixTree = spatialPrefixTree;
|
||||
this.token = token;
|
||||
if (token.length() > 0 && token.charAt(token.length() - 1) == (char) LEAF_BYTE) {
|
||||
this.token = token.substring(0, token.length() - 1);
|
||||
setLeaf();
|
||||
}
|
||||
|
||||
if (getLevel() == 0)
|
||||
getShape();//ensure any lazy instantiation completes to make this threadsafe
|
||||
}
|
||||
|
||||
protected Node(SpatialPrefixTree spatialPrefixTree, byte[] bytes, int off, int len) {
|
||||
this.spatialPrefixTree = spatialPrefixTree;
|
||||
this.bytes = bytes;
|
||||
this.b_off = off;
|
||||
this.b_len = len;
|
||||
b_fixLeaf();
|
||||
}
|
||||
|
||||
public void reset(byte[] bytes, int off, int len) {
|
||||
assert getLevel() != 0;
|
||||
token = null;
|
||||
shapeRel = null;
|
||||
this.bytes = bytes;
|
||||
this.b_off = off;
|
||||
this.b_len = len;
|
||||
b_fixLeaf();
|
||||
}
|
||||
|
||||
private void b_fixLeaf() {
|
||||
if (bytes[b_off + b_len - 1] == LEAF_BYTE) {
|
||||
b_len--;
|
||||
setLeaf();
|
||||
} else if (getLevel() == spatialPrefixTree.getMaxLevels()) {
|
||||
setLeaf();
|
||||
}
|
||||
}
|
||||
|
||||
public SpatialRelation getShapeRel() {
|
||||
return shapeRel;
|
||||
}
|
||||
|
||||
public boolean isLeaf() {
|
||||
return shapeRel == SpatialRelation.WITHIN;
|
||||
}
|
||||
|
||||
public void setLeaf() {
|
||||
assert getLevel() != 0;
|
||||
shapeRel = SpatialRelation.WITHIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: doesn't contain a trailing leaf byte.
|
||||
*/
|
||||
public String getTokenString() {
|
||||
if (token == null) {
|
||||
token = new String(bytes, b_off, b_len, SpatialPrefixTree.UTF8);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: doesn't contain a trailing leaf byte.
|
||||
*/
|
||||
public byte[] getTokenBytes() {
|
||||
if (bytes != null) {
|
||||
if (b_off != 0 || b_len != bytes.length) {
|
||||
throw new IllegalStateException("Not supported if byte[] needs to be recreated.");
|
||||
}
|
||||
} else {
|
||||
bytes = token.getBytes(SpatialPrefixTree.UTF8);
|
||||
b_off = 0;
|
||||
b_len = bytes.length;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return token != null ? token.length() : b_len;
|
||||
}
|
||||
|
||||
//TODO add getParent() and update some algorithms to use this?
|
||||
//public Cell getParent();
|
||||
|
||||
/**
|
||||
* Like {@link #getSubCells()} but with the results filtered by a shape. If that shape is a {@link org.apache.lucene.spatial.base.shape.Point} then it
|
||||
* must call {@link #getSubCell(org.apache.lucene.spatial.base.shape.Point)};
|
||||
* Precondition: Never called when getLevel() == maxLevel.
|
||||
*
|
||||
* @param shapeFilter an optional filter for the returned cells.
|
||||
* @return A set of cells (no dups), sorted. Not Modifiable.
|
||||
*/
|
||||
public Collection<Node> getSubCells(Shape shapeFilter) {
|
||||
//Note: Higher-performing subclasses might override to consider the shape filter to generate fewer cells.
|
||||
if (shapeFilter instanceof Point) {
|
||||
return Collections.singleton(getSubCell((Point) shapeFilter));
|
||||
}
|
||||
Collection<Node> cells = getSubCells();
|
||||
|
||||
if (shapeFilter == null) {
|
||||
return cells;
|
||||
}
|
||||
List<Node> copy = new ArrayList<Node>(cells.size());//copy since cells contractually isn't modifiable
|
||||
for (Node cell : cells) {
|
||||
SpatialRelation rel = cell.getShape().relate(shapeFilter, spatialPrefixTree.ctx);
|
||||
if (rel == SpatialRelation.DISJOINT)
|
||||
continue;
|
||||
cell.shapeRel = rel;
|
||||
copy.add(cell);
|
||||
}
|
||||
cells = copy;
|
||||
return cells;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performant implementations are expected to implement this efficiently by considering the current
|
||||
* cell's boundary.
|
||||
* Precondition: Never called when getLevel() == maxLevel.
|
||||
* Precondition: this.getShape().relate(p) != DISJOINT.
|
||||
*
|
||||
* @param p
|
||||
* @return
|
||||
*/
|
||||
public abstract Node getSubCell(Point p);
|
||||
|
||||
//TODO Cell getSubCell(byte b)
|
||||
|
||||
/**
|
||||
* Gets the cells at the next grid cell level that cover this cell.
|
||||
* Precondition: Never called when getLevel() == maxLevel.
|
||||
*
|
||||
* @return A set of cells (no dups), sorted. Not Modifiable.
|
||||
*/
|
||||
protected abstract Collection<Node> getSubCells();
|
||||
|
||||
/**
|
||||
* {@link #getSubCells()}.size() -- usually a constant. Should be >=2
|
||||
*/
|
||||
public abstract int getSubCellsSize();
|
||||
|
||||
public abstract Shape getShape();
|
||||
|
||||
public Point getCenter() {
|
||||
return getShape().getCenter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Node o) {
|
||||
return getTokenString().compareTo(o.getTokenString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return !(obj == null || !(obj instanceof Node)) && getTokenString().equals(((Node) obj).getTokenString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getTokenString().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getTokenString() + (isLeaf() ? (char) LEAF_BYTE : "");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.prefix;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A Spatial Prefix Tree, or Trie, which decomposes shapes into prefixed strings at variable lengths corresponding to
|
||||
* variable precision. Each string corresponds to a spatial region.
|
||||
*
|
||||
* Implementations of this class should be thread-safe and immutable once initialized.
|
||||
*/
|
||||
public abstract class SpatialPrefixTree {
|
||||
|
||||
protected static final Charset UTF8 = Charset.forName("UTF-8");
|
||||
|
||||
protected final int maxLevels;
|
||||
|
||||
protected final SpatialContext ctx;
|
||||
|
||||
public SpatialPrefixTree(SpatialContext ctx, int maxLevels) {
|
||||
assert maxLevels > 0;
|
||||
this.ctx = ctx;
|
||||
this.maxLevels = maxLevels;
|
||||
}
|
||||
|
||||
public SpatialContext getSpatialContext() {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public int getMaxLevels() {
|
||||
return maxLevels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "(maxLevels:" + maxLevels + ",ctx:" + ctx + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link org.apache.lucene.spatial.base.query.SpatialArgs#getDistPrecision()}.
|
||||
* A grid level looked up via {@link #getLevelForDistance(double)} is returned.
|
||||
*
|
||||
* @param shape
|
||||
* @param precision 0-0.5
|
||||
* @return 1-maxLevels
|
||||
*/
|
||||
public int getMaxLevelForPrecision(Shape shape, double precision) {
|
||||
if (precision < 0 || precision > 0.5) {
|
||||
throw new IllegalArgumentException("Precision " + precision + " must be between [0-0.5]");
|
||||
}
|
||||
if (precision == 0 || shape instanceof Point) {
|
||||
return maxLevels;
|
||||
}
|
||||
double bboxArea = shape.getBoundingBox().getArea();
|
||||
if (bboxArea == 0) {
|
||||
return maxLevels;
|
||||
}
|
||||
double avgSideLenFromCenter = Math.sqrt(bboxArea) / 2;
|
||||
return getLevelForDistance(avgSideLenFromCenter * precision);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the level of the smallest grid size with a side length that is greater or equal to the provided
|
||||
* distance.
|
||||
*
|
||||
* @param dist >= 0
|
||||
* @return level [1-maxLevels]
|
||||
*/
|
||||
public abstract int getLevelForDistance(double dist);
|
||||
|
||||
//TODO double getDistanceForLevel(int level)
|
||||
|
||||
private transient Node worldNode;//cached
|
||||
|
||||
/**
|
||||
* Returns the level 0 cell which encompasses all spatial data. Equivalent to {@link #getNode(String)} with "".
|
||||
* This cell is threadsafe, just like a spatial prefix grid is, although cells aren't
|
||||
* generally threadsafe.
|
||||
* TODO rename to getTopCell or is this fine?
|
||||
*/
|
||||
public Node getWorldNode() {
|
||||
if (worldNode == null) {
|
||||
worldNode = getNode("");
|
||||
}
|
||||
return worldNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cell for the specified token. The empty string should be equal to {@link #getWorldNode()}.
|
||||
* Precondition: Never called when token length > maxLevel.
|
||||
*/
|
||||
public abstract Node getNode(String token);
|
||||
|
||||
public abstract Node getNode(byte[] bytes, int offset, int len);
|
||||
|
||||
public final Node getNode(byte[] bytes, int offset, int len, Node target) {
|
||||
if (target == null) {
|
||||
return getNode(bytes, offset, len);
|
||||
}
|
||||
|
||||
target.reset(bytes, offset, len);
|
||||
return target;
|
||||
}
|
||||
|
||||
protected Node getNode(Point p, int level) {
|
||||
return getNodes(p, level, false).get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the intersecting & including cells for the specified shape, without exceeding detail level.
|
||||
* The result is a set of cells (no dups), sorted. Unmodifiable.
|
||||
* <p/>
|
||||
* This implementation checks if shape is a Point and if so uses an implementation that
|
||||
* recursively calls {@link Node#getSubCell(org.apache.lucene.spatial.base.shape.Point)}. Cell subclasses
|
||||
* ideally implement that method with a quick implementation, otherwise, subclasses should
|
||||
* override this method to invoke {@link #getNodesAltPoint(org.apache.lucene.spatial.base.shape.Point, int, boolean)}.
|
||||
* TODO consider another approach returning an iterator -- won't build up all cells in memory.
|
||||
*/
|
||||
public List<Node> getNodes(Shape shape, int detailLevel, boolean inclParents) {
|
||||
if (detailLevel > maxLevels) {
|
||||
throw new IllegalArgumentException("detailLevel > maxLevels");
|
||||
}
|
||||
|
||||
List<Node> cells;
|
||||
if (shape instanceof Point) {
|
||||
//optimized point algorithm
|
||||
final int initialCapacity = inclParents ? 1 + detailLevel : 1;
|
||||
cells = new ArrayList<Node>(initialCapacity);
|
||||
recursiveGetNodes(getWorldNode(), (Point) shape, detailLevel, true, cells);
|
||||
assert cells.size() == initialCapacity;
|
||||
} else {
|
||||
cells = new ArrayList<Node>(inclParents ? 1024 : 512);
|
||||
recursiveGetNodes(getWorldNode(), shape, detailLevel, inclParents, cells);
|
||||
}
|
||||
if (inclParents) {
|
||||
Node c = cells.remove(0);//remove getWorldNode()
|
||||
assert c.getLevel() == 0;
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
private void recursiveGetNodes(Node node, Shape shape, int detailLevel, boolean inclParents,
|
||||
Collection<Node> result) {
|
||||
if (node.isLeaf()) {//cell is within shape
|
||||
result.add(node);
|
||||
return;
|
||||
}
|
||||
final Collection<Node> subCells = node.getSubCells(shape);
|
||||
if (node.getLevel() == detailLevel - 1) {
|
||||
if (subCells.size() < node.getSubCellsSize()) {
|
||||
if (inclParents)
|
||||
result.add(node);
|
||||
for (Node subCell : subCells) {
|
||||
subCell.setLeaf();
|
||||
}
|
||||
result.addAll(subCells);
|
||||
} else {//a bottom level (i.e. detail level) optimization where all boxes intersect, so use parent cell.
|
||||
node.setLeaf();
|
||||
result.add(node);
|
||||
}
|
||||
} else {
|
||||
if (inclParents) {
|
||||
result.add(node);
|
||||
}
|
||||
for (Node subCell : subCells) {
|
||||
recursiveGetNodes(subCell, shape, detailLevel, inclParents, result);//tail call
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void recursiveGetNodes(Node node, Point point, int detailLevel, boolean inclParents,
|
||||
Collection<Node> result) {
|
||||
if (inclParents) {
|
||||
result.add(node);
|
||||
}
|
||||
final Node pCell = node.getSubCell(point);
|
||||
if (node.getLevel() == detailLevel - 1) {
|
||||
pCell.setLeaf();
|
||||
result.add(pCell);
|
||||
} else {
|
||||
recursiveGetNodes(pCell, point, detailLevel, inclParents, result);//tail call
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses might override {@link #getNodes(org.apache.lucene.spatial.base.shape.Shape, int, boolean)}
|
||||
* and check if the argument is a shape and if so, delegate
|
||||
* to this implementation, which calls {@link #getNode(org.apache.lucene.spatial.base.shape.Point, int)} and
|
||||
* then calls {@link #getNode(String)} repeatedly if inclParents is true.
|
||||
*/
|
||||
protected final List<Node> getNodesAltPoint(Point p, int detailLevel, boolean inclParents) {
|
||||
Node cell = getNode(p, detailLevel);
|
||||
if (!inclParents) {
|
||||
return Collections.singletonList(cell);
|
||||
}
|
||||
|
||||
String endToken = cell.getTokenString();
|
||||
assert endToken.length() == detailLevel;
|
||||
List<Node> cells = new ArrayList<Node>(detailLevel);
|
||||
for (int i = 1; i < detailLevel; i++) {
|
||||
cells.add(getNode(endToken.substring(0, i)));
|
||||
}
|
||||
cells.add(cell);
|
||||
return cells;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will add the trailing leaf byte for leaves. This isn't particularly efficient.
|
||||
*/
|
||||
public static List<String> nodesToTokenStrings(Collection<Node> nodes) {
|
||||
List<String> tokens = new ArrayList<String>((nodes.size()));
|
||||
for (Node node : nodes) {
|
||||
final String token = node.getTokenString();
|
||||
if (node.isLeaf()) {
|
||||
tokens.add(token + (char) Node.LEAF_BYTE);
|
||||
} else {
|
||||
tokens.add(token);
|
||||
}
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.prefix;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.distance.DistanceUnits;
|
||||
import org.apache.lucene.spatial.base.distance.DistanceUtils;
|
||||
import org.apache.lucene.spatial.base.prefix.geohash.GeohashPrefixTree;
|
||||
import org.apache.lucene.spatial.base.prefix.quad.QuadPrefixTree;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author dsmiley
|
||||
*/
|
||||
public abstract class SpatialPrefixTreeFactory {
|
||||
|
||||
private static final double DEFAULT_GEO_MAX_DETAIL_KM = 0.001;//1m
|
||||
|
||||
protected Map<String, String> args;
|
||||
protected SpatialContext ctx;
|
||||
protected Integer maxLevels;
|
||||
|
||||
/**
|
||||
* The factory is looked up via "prefixTree" in args, expecting "geohash" or "quad".
|
||||
* If its neither of these, then "geohash" is chosen for a geo context, otherwise "quad" is chosen.
|
||||
*/
|
||||
public static SpatialPrefixTree makeSPT(Map<String,String> args, ClassLoader classLoader, SpatialContext ctx) {
|
||||
SpatialPrefixTreeFactory instance;
|
||||
String cname = args.get("prefixTree");
|
||||
if (cname == null)
|
||||
cname = ctx.isGeo() ? "geohash" : "quad";
|
||||
if ("geohash".equalsIgnoreCase(cname))
|
||||
instance = new GeohashPrefixTree.Factory();
|
||||
else if ("quad".equalsIgnoreCase(cname))
|
||||
instance = new QuadPrefixTree.Factory();
|
||||
else {
|
||||
try {
|
||||
Class c = classLoader.loadClass(cname);
|
||||
instance = (SpatialPrefixTreeFactory) c.newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
instance.init(args,ctx);
|
||||
return instance.newSPT();
|
||||
}
|
||||
|
||||
protected void init(Map<String, String> args, SpatialContext ctx) {
|
||||
this.args = args;
|
||||
this.ctx = ctx;
|
||||
initMaxLevels();
|
||||
}
|
||||
|
||||
protected void initMaxLevels() {
|
||||
String mlStr = args.get("maxLevels");
|
||||
if (mlStr != null) {
|
||||
maxLevels = Integer.valueOf(mlStr);
|
||||
return;
|
||||
}
|
||||
|
||||
double degrees;
|
||||
String maxDetailDistStr = args.get("maxDetailDist");
|
||||
if (maxDetailDistStr == null) {
|
||||
if (!ctx.isGeo()) {
|
||||
return;//let default to max
|
||||
}
|
||||
degrees = DistanceUtils.dist2Degrees(DEFAULT_GEO_MAX_DETAIL_KM, DistanceUnits.KILOMETERS.earthRadius());
|
||||
} else {
|
||||
degrees = DistanceUtils.dist2Degrees(Double.parseDouble(maxDetailDistStr), ctx.getUnits().earthRadius());
|
||||
}
|
||||
maxLevels = getLevelForDistance(degrees) + 1;//returns 1 greater
|
||||
}
|
||||
|
||||
/** Calls {@link SpatialPrefixTree#getLevelForDistance(double)}. */
|
||||
protected abstract int getLevelForDistance(double degrees);
|
||||
|
||||
protected abstract SpatialPrefixTree newSPT();
|
||||
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.prefix.geohash;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.prefix.Node;
|
||||
import org.apache.lucene.spatial.base.prefix.SpatialPrefixTree;
|
||||
import org.apache.lucene.spatial.base.prefix.SpatialPrefixTreeFactory;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A SpatialPrefixGrid based on Geohashes. Uses {@link GeohashUtils} to do all the geohash work.
|
||||
*/
|
||||
public class GeohashPrefixTree extends SpatialPrefixTree {
|
||||
|
||||
public static class Factory extends SpatialPrefixTreeFactory {
|
||||
|
||||
@Override
|
||||
protected int getLevelForDistance(double degrees) {
|
||||
GeohashPrefixTree grid = new GeohashPrefixTree(ctx, GeohashPrefixTree.getMaxLevelsPossible());
|
||||
return grid.getLevelForDistance(degrees) + 1;//returns 1 greater
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SpatialPrefixTree newSPT() {
|
||||
return new GeohashPrefixTree(ctx,
|
||||
maxLevels != null ? maxLevels : GeohashPrefixTree.getMaxLevelsPossible());
|
||||
}
|
||||
}
|
||||
|
||||
public GeohashPrefixTree(SpatialContext ctx, int maxLevels) {
|
||||
super(ctx, maxLevels);
|
||||
Rectangle bounds = ctx.getWorldBounds();
|
||||
if (bounds.getMinX() != -180)
|
||||
throw new IllegalArgumentException("Geohash only supports lat-lon world bounds. Got "+bounds);
|
||||
int MAXP = getMaxLevelsPossible();
|
||||
if (maxLevels <= 0 || maxLevels > MAXP)
|
||||
throw new IllegalArgumentException("maxLen must be [1-"+MAXP+"] but got "+ maxLevels);
|
||||
}
|
||||
|
||||
/** Any more than this and there's no point (double lat & lon are the same). */
|
||||
public static int getMaxLevelsPossible() {
|
||||
return GeohashUtils.MAX_PRECISION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLevelForDistance(double dist) {
|
||||
final int level = GeohashUtils.lookupHashLenForWidthHeight(dist, dist);
|
||||
return Math.max(Math.min(level, maxLevels), 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getNode(Point p, int level) {
|
||||
return new GhCell(GeohashUtils.encodeLatLon(p.getY(), p.getX(), level));//args are lat,lon (y,x)
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getNode(String token) {
|
||||
return new GhCell(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getNode(byte[] bytes, int offset, int len) {
|
||||
return new GhCell(bytes, offset, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Node> getNodes(Shape shape, int detailLevel, boolean inclParents) {
|
||||
return shape instanceof Point ? super.getNodesAltPoint((Point) shape, detailLevel, inclParents) :
|
||||
super.getNodes(shape, detailLevel, inclParents);
|
||||
}
|
||||
|
||||
class GhCell extends Node {
|
||||
GhCell(String token) {
|
||||
super(GeohashPrefixTree.this, token);
|
||||
}
|
||||
|
||||
GhCell(byte[] bytes, int off, int len) {
|
||||
super(GeohashPrefixTree.this, bytes, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset(byte[] bytes, int off, int len) {
|
||||
super.reset(bytes, off, len);
|
||||
shape = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Node> getSubCells() {
|
||||
String[] hashes = GeohashUtils.getSubGeohashes(getGeohash());//sorted
|
||||
List<Node> cells = new ArrayList<Node>(hashes.length);
|
||||
for (String hash : hashes) {
|
||||
cells.add(new GhCell(hash));
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSubCellsSize() {
|
||||
return 32;//8x4
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getSubCell(Point p) {
|
||||
return GeohashPrefixTree.this.getNode(p,getLevel()+1);//not performant!
|
||||
}
|
||||
|
||||
private Shape shape;//cache
|
||||
|
||||
@Override
|
||||
public Shape getShape() {
|
||||
if (shape == null) {
|
||||
shape = GeohashUtils.decodeBoundary(getGeohash(), ctx);
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point getCenter() {
|
||||
return GeohashUtils.decode(getGeohash(), ctx);
|
||||
}
|
||||
|
||||
private String getGeohash() {
|
||||
return getTokenString();
|
||||
}
|
||||
|
||||
}//class GhCell
|
||||
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.prefix.geohash;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Utilities for encoding and decoding geohashes. Based on
|
||||
* <a href="http://en.wikipedia.org/wiki/Geohash">http://en.wikipedia.org/wiki/Geohash</a>.
|
||||
*/
|
||||
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.makeRect(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 longest geohash length that will have a width & height >= specified arguments.
|
||||
*/
|
||||
public static int lookupHashLenForWidthHeight(double width, double height) {
|
||||
//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 < height || lonWidth < width)
|
||||
return len-1;//previous length is big enough to encompass specified width & height
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The Spatial Prefix package supports spatial indexing by index-time tokens
|
||||
* where adding characters to a string gives greater resolution.
|
||||
*
|
||||
* Potential Implementations include:
|
||||
* * http://en.wikipedia.org/wiki/Quadtree
|
||||
* * http://en.wikipedia.org/wiki/Geohash
|
||||
* * http://healpix.jpl.nasa.gov/
|
||||
*/
|
||||
package org.apache.lucene.spatial.base.prefix;
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.prefix.quad;
|
||||
|
||||
import org.apache.lucene.spatial.base.prefix.SpatialPrefixTreeFactory;
|
||||
import org.apache.lucene.spatial.base.shape.SpatialRelation;
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.prefix.Node;
|
||||
import org.apache.lucene.spatial.base.prefix.SpatialPrefixTree;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
import org.apache.lucene.spatial.base.shape.simple.PointImpl;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class QuadPrefixTree extends SpatialPrefixTree {
|
||||
|
||||
public static class Factory extends SpatialPrefixTreeFactory {
|
||||
|
||||
@Override
|
||||
protected int getLevelForDistance(double degrees) {
|
||||
QuadPrefixTree grid = new QuadPrefixTree(ctx, MAX_LEVELS_POSSIBLE);
|
||||
return grid.getLevelForDistance(degrees) + 1;//returns 1 greater
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SpatialPrefixTree newSPT() {
|
||||
return new QuadPrefixTree(ctx,
|
||||
maxLevels != null ? maxLevels : MAX_LEVELS_POSSIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public static final int MAX_LEVELS_POSSIBLE = 50;//not really sure how big this should be
|
||||
|
||||
public static final int DEFAULT_MAX_LEVELS = 12;
|
||||
private final double xmin;
|
||||
private final double xmax;
|
||||
private final double ymin;
|
||||
private final double ymax;
|
||||
private final double xmid;
|
||||
private final double ymid;
|
||||
|
||||
private final double gridW;
|
||||
public final double gridH;
|
||||
|
||||
final double[] levelW;
|
||||
final double[] levelH;
|
||||
final int[] levelS; // side
|
||||
final int[] levelN; // number
|
||||
|
||||
public QuadPrefixTree(
|
||||
SpatialContext ctx, Rectangle bounds, int maxLevels) {
|
||||
super(ctx, maxLevels);
|
||||
this.xmin = bounds.getMinX();
|
||||
this.xmax = bounds.getMaxX();
|
||||
this.ymin = bounds.getMinY();
|
||||
this.ymax = bounds.getMaxY();
|
||||
|
||||
levelW = new double[maxLevels];
|
||||
levelH = new double[maxLevels];
|
||||
levelS = new int[maxLevels];
|
||||
levelN = new int[maxLevels];
|
||||
|
||||
gridW = xmax - xmin;
|
||||
gridH = ymax - ymin;
|
||||
this.xmid = xmin + gridW/2.0;
|
||||
this.ymid = ymin + gridH/2.0;
|
||||
levelW[0] = gridW/2.0;
|
||||
levelH[0] = gridH/2.0;
|
||||
levelS[0] = 2;
|
||||
levelN[0] = 4;
|
||||
|
||||
for (int i = 1; i < levelW.length; i++) {
|
||||
levelW[i] = levelW[i - 1] / 2.0;
|
||||
levelH[i] = levelH[i - 1] / 2.0;
|
||||
levelS[i] = levelS[i - 1] * 2;
|
||||
levelN[i] = levelN[i - 1] * 4;
|
||||
}
|
||||
}
|
||||
|
||||
public QuadPrefixTree(SpatialContext ctx) {
|
||||
this(ctx, DEFAULT_MAX_LEVELS);
|
||||
}
|
||||
|
||||
public QuadPrefixTree(
|
||||
SpatialContext ctx, int maxLevels) {
|
||||
this(ctx, ctx.getWorldBounds(), maxLevels);
|
||||
}
|
||||
|
||||
public void printInfo() {
|
||||
NumberFormat nf = NumberFormat.getNumberInstance();
|
||||
nf.setMaximumFractionDigits(5);
|
||||
nf.setMinimumFractionDigits(5);
|
||||
nf.setMinimumIntegerDigits(3);
|
||||
|
||||
for (int i = 0; i < maxLevels; i++) {
|
||||
System.out.println(i + "]\t" + nf.format(levelW[i]) + "\t" + nf.format(levelH[i]) + "\t" +
|
||||
levelS[i] + "\t" + (levelS[i] * levelS[i]));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLevelForDistance(double dist) {
|
||||
for (int i = 1; i < maxLevels; i++) {
|
||||
//note: level[i] is actually a lookup for level i+1
|
||||
if(dist > levelW[i] || dist > levelH[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return maxLevels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getNode(Point p, int level) {
|
||||
List<Node> cells = new ArrayList<Node>(1);
|
||||
build(xmid, ymid, 0, cells, new StringBuilder(), new PointImpl(p.getX(),p.getY()), level);
|
||||
return cells.get(0);//note cells could be longer if p on edge
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getNode(String token) {
|
||||
return new QuadCell(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getNode(byte[] bytes, int offset, int len) {
|
||||
return new QuadCell(bytes, offset, len);
|
||||
}
|
||||
|
||||
@Override //for performance
|
||||
public List<Node> getNodes(Shape shape, int detailLevel, boolean inclParents) {
|
||||
if (shape instanceof Point)
|
||||
return super.getNodesAltPoint((Point) shape, detailLevel, inclParents);
|
||||
else
|
||||
return super.getNodes(shape, detailLevel, inclParents);
|
||||
}
|
||||
|
||||
private void build(
|
||||
double x,
|
||||
double y,
|
||||
int level,
|
||||
List<Node> matches,
|
||||
StringBuilder str,
|
||||
Shape shape,
|
||||
int maxLevel) {
|
||||
assert str.length() == level;
|
||||
double w = levelW[level] / 2;
|
||||
double h = levelH[level] / 2;
|
||||
|
||||
// Z-Order
|
||||
// http://en.wikipedia.org/wiki/Z-order_%28curve%29
|
||||
checkBattenberg('A', x - w, y + h, level, matches, str, shape, maxLevel);
|
||||
checkBattenberg('B', x + w, y + h, level, matches, str, shape, maxLevel);
|
||||
checkBattenberg('C', x - w, y - h, level, matches, str, shape, maxLevel);
|
||||
checkBattenberg('D', x + w, y - h, level, matches, str, shape, maxLevel);
|
||||
|
||||
// possibly consider hilbert curve
|
||||
// http://en.wikipedia.org/wiki/Hilbert_curve
|
||||
// http://blog.notdot.net/2009/11/Damn-Cool-Algorithms-Spatial-indexing-with-Quadtrees-and-Hilbert-Curves
|
||||
// if we actually use the range property in the query, this could be useful
|
||||
}
|
||||
|
||||
private void checkBattenberg(
|
||||
char c,
|
||||
double cx,
|
||||
double cy,
|
||||
int level,
|
||||
List<Node> matches,
|
||||
StringBuilder str,
|
||||
Shape shape,
|
||||
int maxLevel) {
|
||||
assert str.length() == level;
|
||||
double w = levelW[level] / 2;
|
||||
double h = levelH[level] / 2;
|
||||
|
||||
int strlen = str.length();
|
||||
Rectangle rectangle = ctx.makeRect(cx - w, cx + w, cy - h, cy + h);
|
||||
SpatialRelation v = shape.relate(rectangle, ctx);
|
||||
if (SpatialRelation.CONTAINS == v) {
|
||||
str.append(c);
|
||||
//str.append(SpatialPrefixGrid.COVER);
|
||||
matches.add(new QuadCell(str.toString(),v.transpose()));
|
||||
} else if (SpatialRelation.DISJOINT == v) {
|
||||
// nothing
|
||||
} else { // SpatialRelation.WITHIN, SpatialRelation.INTERSECTS
|
||||
str.append(c);
|
||||
|
||||
int nextLevel = level+1;
|
||||
if (nextLevel >= maxLevel) {
|
||||
//str.append(SpatialPrefixGrid.INTERSECTS);
|
||||
matches.add(new QuadCell(str.toString(),v.transpose()));
|
||||
} else {
|
||||
build(cx, cy, nextLevel, matches, str, shape, maxLevel);
|
||||
}
|
||||
}
|
||||
str.setLength(strlen);
|
||||
}
|
||||
|
||||
class QuadCell extends Node {
|
||||
|
||||
public QuadCell(String token) {
|
||||
super(QuadPrefixTree.this, token);
|
||||
}
|
||||
|
||||
public QuadCell(String token, SpatialRelation shapeRel) {
|
||||
super(QuadPrefixTree.this, token);
|
||||
this.shapeRel = shapeRel;
|
||||
}
|
||||
|
||||
QuadCell(byte[] bytes, int off, int len) {
|
||||
super(QuadPrefixTree.this, bytes, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset(byte[] bytes, int off, int len) {
|
||||
super.reset(bytes, off, len);
|
||||
shape = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Node> getSubCells() {
|
||||
List<Node> cells = new ArrayList<Node>(4);
|
||||
cells.add(new QuadCell(getTokenString()+"A"));
|
||||
cells.add(new QuadCell(getTokenString()+"B"));
|
||||
cells.add(new QuadCell(getTokenString()+"C"));
|
||||
cells.add(new QuadCell(getTokenString()+"D"));
|
||||
return cells;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSubCellsSize() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getSubCell(Point p) {
|
||||
return QuadPrefixTree.this.getNode(p,getLevel()+1);//not performant!
|
||||
}
|
||||
|
||||
private Shape shape;//cache
|
||||
|
||||
@Override
|
||||
public Shape getShape() {
|
||||
if (shape == null)
|
||||
shape = makeShape();
|
||||
return shape;
|
||||
}
|
||||
|
||||
private Rectangle makeShape() {
|
||||
String token = getTokenString();
|
||||
double xmin = QuadPrefixTree.this.xmin;
|
||||
double ymin = QuadPrefixTree.this.ymin;
|
||||
|
||||
for (int i = 0; i < token.length(); i++) {
|
||||
char c = token.charAt(i);
|
||||
if ('A' == c || 'a' == c) {
|
||||
ymin += levelH[i];
|
||||
} else if ('B' == c || 'b' == c) {
|
||||
xmin += levelW[i];
|
||||
ymin += levelH[i];
|
||||
} else if ('C' == c || 'c' == c) {
|
||||
// nothing really
|
||||
}
|
||||
else if('D' == c || 'd' == c) {
|
||||
xmin += levelW[i];
|
||||
} else {
|
||||
throw new RuntimeException("unexpected char: " + c);
|
||||
}
|
||||
}
|
||||
int len = token.length();
|
||||
double width, height;
|
||||
if (len > 0) {
|
||||
width = levelW[len-1];
|
||||
height = levelH[len-1];
|
||||
} else {
|
||||
width = gridW;
|
||||
height = gridH;
|
||||
}
|
||||
return ctx.makeRect(xmin, xmin + width, ymin, ymin + height);
|
||||
}
|
||||
}//QuadCell
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.query;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.context.simple.SimpleSpatialContext;
|
||||
import org.apache.lucene.spatial.base.distance.DistanceUnits;
|
||||
import org.apache.lucene.spatial.base.exception.InvalidSpatialArgument;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
|
||||
public class SpatialArgs {
|
||||
|
||||
public static final double DEFAULT_DIST_PRECISION = 0.025d;
|
||||
|
||||
private SpatialOperation operation;
|
||||
private Shape shape;
|
||||
private double distPrecision = DEFAULT_DIST_PRECISION;
|
||||
|
||||
// Useful for 'distance' calculations
|
||||
private Double min;
|
||||
private Double max;
|
||||
|
||||
public SpatialArgs(SpatialOperation operation) {
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
public SpatialArgs(SpatialOperation operation, Shape shape) {
|
||||
this.operation = operation;
|
||||
this.shape = shape;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the arguments make sense -- throw an exception if not
|
||||
*/
|
||||
public void validate() throws InvalidSpatialArgument {
|
||||
if (operation.isTargetNeedsArea() && !shape.hasArea()) {
|
||||
throw new InvalidSpatialArgument(operation + " only supports geometry with area");
|
||||
}
|
||||
}
|
||||
|
||||
public String toString( SpatialContext context ) {
|
||||
StringBuilder str = new StringBuilder();
|
||||
str.append( operation.getName() ).append( '(' );
|
||||
str.append( context.toString( shape ) );
|
||||
if( min != null ) {
|
||||
str.append(" min=").append(min);
|
||||
}
|
||||
if( max != null ) {
|
||||
str.append(" max=").append(max);
|
||||
}
|
||||
str.append(" distPrec=").append(String.format("%.2f%%", distPrecision/100d));
|
||||
str.append( ')' );
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return toString( new SimpleSpatialContext(DistanceUnits.KILOMETERS) );
|
||||
}
|
||||
|
||||
//------------------------------------------------
|
||||
// Getters & Setters
|
||||
//------------------------------------------------
|
||||
|
||||
public SpatialOperation getOperation() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
public void setOperation(SpatialOperation operation) {
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Considers {@link SpatialOperation#BBoxWithin} in returning the shape.
|
||||
* @return
|
||||
*/
|
||||
public Shape getShape() {
|
||||
if (shape != null && (operation == SpatialOperation.BBoxWithin || operation == SpatialOperation.BBoxIntersects))
|
||||
return shape.getBoundingBox();
|
||||
return shape;
|
||||
}
|
||||
|
||||
public void setShape(Shape shape) {
|
||||
this.shape = shape;
|
||||
}
|
||||
|
||||
/**
|
||||
* The fraction of the distance from the center of the query shape to its nearest edge that is considered acceptable
|
||||
* error. The algorithm for computing the distance to the nearest edge is actually a little different. It normalizes
|
||||
* the shape to a square given it's bounding box area:
|
||||
* <pre>sqrt(shape.bbox.area)/2</pre>
|
||||
* And the error distance is beyond the shape such that the shape is a minimum shape.
|
||||
*/
|
||||
public Double getDistPrecision() {
|
||||
return distPrecision;
|
||||
}
|
||||
|
||||
public void setDistPrecision(Double distPrecision) {
|
||||
if (distPrecision != null)
|
||||
this.distPrecision = distPrecision;
|
||||
}
|
||||
|
||||
public Double getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
public void setMin(Double min) {
|
||||
this.min = min;
|
||||
}
|
||||
|
||||
public Double getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
public void setMax(Double max) {
|
||||
this.max = max;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.query;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.exception.InvalidShapeException;
|
||||
import org.apache.lucene.spatial.base.exception.InvalidSpatialArgument;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
public class SpatialArgsParser
|
||||
{
|
||||
public SpatialArgs parse(String v, SpatialContext ctx) throws InvalidSpatialArgument, InvalidShapeException {
|
||||
int idx = v.indexOf('(');
|
||||
int edx = v.lastIndexOf(')');
|
||||
|
||||
if (idx < 0 || idx > edx) {
|
||||
throw new InvalidSpatialArgument("missing parens: " + v, null);
|
||||
}
|
||||
|
||||
SpatialOperation op = SpatialOperation.get(v.substring(0, idx).trim());
|
||||
|
||||
String body = v.substring(idx + 1, edx).trim();
|
||||
if (body.length() < 1) {
|
||||
throw new InvalidSpatialArgument("missing body : " + v, null);
|
||||
}
|
||||
|
||||
Shape shape = ctx.readShape(body);
|
||||
SpatialArgs args = new SpatialArgs(op,shape);
|
||||
|
||||
if (v.length() > (edx + 1)) {
|
||||
body = v.substring( edx+1 ).trim();
|
||||
if (body.length() > 0) {
|
||||
Map<String,String> aa = parseMap(body);
|
||||
args.setMin(readDouble(aa.remove("min")) );
|
||||
args.setMax(readDouble(aa.remove("max")));
|
||||
args.setDistPrecision(readDouble(aa.remove("distPrec")));
|
||||
if (!aa.isEmpty()) {
|
||||
throw new InvalidSpatialArgument("unused parameters: " + aa, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
protected static Double readDouble(String v) {
|
||||
return v == null ? null : Double.valueOf(v);
|
||||
}
|
||||
|
||||
protected static boolean readBool(String v, boolean defaultValue) {
|
||||
return v == null ? defaultValue : Boolean.parseBoolean(v);
|
||||
}
|
||||
|
||||
protected static Map<String,String> parseMap(String body) {
|
||||
Map<String,String> map = new HashMap<String,String>();
|
||||
StringTokenizer st = new StringTokenizer(body, " \n\t");
|
||||
while (st.hasMoreTokens()) {
|
||||
String a = st.nextToken();
|
||||
int idx = a.indexOf('=');
|
||||
if (idx > 0) {
|
||||
String k = a.substring(0, idx);
|
||||
String v = a.substring(idx + 1);
|
||||
map.put(k, v);
|
||||
} else {
|
||||
map.put(a, a);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
/* See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* Esri Inc. licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.spatial.base.query;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.lucene.spatial.base.exception.InvalidSpatialArgument;
|
||||
|
||||
/**
|
||||
* A clause that compares a stored geometry to a supplied geometry.
|
||||
*/
|
||||
public class SpatialOperation implements Serializable {
|
||||
// Private registry
|
||||
private static final Map<String, SpatialOperation> registry = new HashMap<String, SpatialOperation>();
|
||||
private static final List<SpatialOperation> list = new ArrayList<SpatialOperation>();
|
||||
|
||||
// Geometry Operations
|
||||
public static final SpatialOperation BBoxIntersects = new SpatialOperation("BBoxIntersects", true, false, false);
|
||||
public static final SpatialOperation BBoxWithin = new SpatialOperation("BBoxWithin", true, false, false);
|
||||
public static final SpatialOperation Contains = new SpatialOperation("Contains", true, true, false);
|
||||
public static final SpatialOperation Intersects = new SpatialOperation("Intersects", true, false, false);
|
||||
public static final SpatialOperation IsEqualTo = new SpatialOperation("IsEqualTo", false, false, false);
|
||||
public static final SpatialOperation IsDisjointTo = new SpatialOperation("IsDisjointTo", false, false, false);
|
||||
public static final SpatialOperation IsWithin = new SpatialOperation("IsWithin", true, false, true);
|
||||
public static final SpatialOperation Overlaps = new SpatialOperation("Overlaps", true, false, true);
|
||||
|
||||
// Member variables
|
||||
private final boolean scoreIsMeaningful;
|
||||
private final boolean sourceNeedsArea;
|
||||
private final boolean targetNeedsArea;
|
||||
private final String name;
|
||||
|
||||
protected SpatialOperation(String name, boolean scoreIsMeaningful, boolean sourceNeedsArea, boolean targetNeedsArea) {
|
||||
this.name = name;
|
||||
this.scoreIsMeaningful = scoreIsMeaningful;
|
||||
this.sourceNeedsArea = sourceNeedsArea;
|
||||
this.targetNeedsArea = targetNeedsArea;
|
||||
registry.put(name, this);
|
||||
registry.put(name.toUpperCase(Locale.US), this);
|
||||
list.add( this );
|
||||
}
|
||||
|
||||
public static SpatialOperation get( String v ) {
|
||||
SpatialOperation op = registry.get( v );
|
||||
if( op == null ) {
|
||||
op = registry.get(v.toUpperCase(Locale.US));
|
||||
}
|
||||
if( op == null ) {
|
||||
throw new InvalidSpatialArgument("Unknown Operation: " + v );
|
||||
}
|
||||
return op;
|
||||
}
|
||||
|
||||
public static List<SpatialOperation> values() {
|
||||
return list;
|
||||
}
|
||||
|
||||
public static boolean is( SpatialOperation op, SpatialOperation ... tst ) {
|
||||
for( SpatialOperation t : tst ) {
|
||||
if( op == t ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// ================================================= Getters / Setters =============================================
|
||||
|
||||
public boolean isScoreIsMeaningful() {
|
||||
return scoreIsMeaningful;
|
||||
}
|
||||
|
||||
public boolean isSourceNeedsArea() {
|
||||
return sourceNeedsArea;
|
||||
}
|
||||
|
||||
public boolean isTargetNeedsArea() {
|
||||
return targetNeedsArea;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Lucene spatial Query options
|
||||
* <ul>
|
||||
* <li>useful for client side requets</li>
|
||||
* </ul>
|
||||
*/
|
||||
package org.apache.lucene.spatial.base.query;
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.shape;
|
||||
|
||||
/**
|
||||
* This is basically a circle.
|
||||
*/
|
||||
public interface Circle extends Shape {
|
||||
double getDistance();
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.shape;
|
||||
|
||||
import org.apache.commons.lang.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang.builder.HashCodeBuilder;
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A collection of Shape objects.
|
||||
*/
|
||||
public class MultiShape implements Shape {
|
||||
private final Collection<Shape> geoms;
|
||||
private final Rectangle bbox;
|
||||
|
||||
public MultiShape(Collection<Shape> geoms, SpatialContext ctx) {
|
||||
this.geoms = geoms;
|
||||
double minX = Double.MAX_VALUE;
|
||||
double minY = Double.MAX_VALUE;
|
||||
double maxX = Double.MIN_VALUE;
|
||||
double maxY = Double.MIN_VALUE;
|
||||
for (Shape geom : geoms) {
|
||||
Rectangle r = geom.getBoundingBox();
|
||||
minX = Math.min(minX,r.getMinX());
|
||||
minY = Math.min(minY,r.getMinY());
|
||||
maxX = Math.max(maxX,r.getMaxX());
|
||||
maxY = Math.max(maxY,r.getMaxY());
|
||||
}
|
||||
this.bbox = ctx.makeRect(minX, maxX, minY, maxY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle getBoundingBox() {
|
||||
return bbox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point getCenter() {
|
||||
return bbox.getCenter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArea() {
|
||||
for (Shape geom : geoms) {
|
||||
if( geom.hasArea() ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpatialRelation relate(Shape other, SpatialContext ctx) {
|
||||
boolean allOutside = true;
|
||||
boolean allContains = true;
|
||||
for (Shape geom : geoms) {
|
||||
SpatialRelation sect = geom.relate(other, ctx);
|
||||
if (sect != SpatialRelation.DISJOINT)
|
||||
allOutside = false;
|
||||
if (sect != SpatialRelation.CONTAINS)
|
||||
allContains = false;
|
||||
if (!allContains && !allOutside)
|
||||
return SpatialRelation.INTERSECTS;//short circuit
|
||||
}
|
||||
if (allOutside)
|
||||
return SpatialRelation.DISJOINT;
|
||||
if (allContains)
|
||||
return SpatialRelation.CONTAINS;
|
||||
return SpatialRelation.INTERSECTS;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) { return false; }
|
||||
if (obj == this) { return true; }
|
||||
if (obj.getClass() != getClass()) {
|
||||
return false;
|
||||
}
|
||||
MultiShape rhs = (MultiShape) obj;
|
||||
return new EqualsBuilder()
|
||||
.append(geoms, rhs.geoms)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder(83, 29).append(geoms.hashCode()).
|
||||
toHashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.shape;
|
||||
|
||||
public interface Point extends Shape {
|
||||
|
||||
public double getX();
|
||||
public double getY();
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.shape;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
|
||||
public interface Rectangle extends Shape {
|
||||
|
||||
public double getWidth();
|
||||
public double getHeight();
|
||||
|
||||
public double getMinX();
|
||||
public double getMinY();
|
||||
public double getMaxX();
|
||||
public double getMaxY();
|
||||
|
||||
/** If {@link #hasArea()} then this returns the area, otherwise it returns 0. */
|
||||
public double getArea();
|
||||
/** Only meaningful for geospatial contexts. */
|
||||
public boolean getCrossesDateLine();
|
||||
|
||||
/* There is no axis line shape, and this is more efficient then creating a flat Rectangle for intersect(). */
|
||||
public SpatialRelation relate_yRange(double minY, double maxY, SpatialContext ctx);
|
||||
public SpatialRelation relate_xRange(double minX, double maxX, SpatialContext ctx);
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.shape;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
|
||||
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
|
||||
*
|
||||
* The context object is optional -- it may include spatial reference.
|
||||
*/
|
||||
SpatialRelation relate(Shape other, SpatialContext ctx);
|
||||
|
||||
/**
|
||||
* Get the bounding box for this Shape
|
||||
*/
|
||||
Rectangle getBoundingBox();
|
||||
|
||||
/**
|
||||
* @return true if the shape has area. This will be false for points and lines
|
||||
*/
|
||||
boolean hasArea();
|
||||
|
||||
Point getCenter();
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.shape;
|
||||
|
||||
/**
|
||||
* The set of spatial relationships. Naming is consistent with OGC spec conventions as seen in SQL/MM and others.
|
||||
* No equality case. If two Shape instances are equal then the result might be CONTAINS or WITHIN, and
|
||||
* some logic might fail under this edge condition when it's not careful to check.
|
||||
* Client code must be written to detect this and act accordingly. In RectangleImpl.relate(), it checks
|
||||
* for this explicitly, for example. TestShapes2D.assertRelation() checks too.
|
||||
*/
|
||||
public enum SpatialRelation {
|
||||
WITHIN,
|
||||
CONTAINS,
|
||||
DISJOINT,
|
||||
INTERSECTS;
|
||||
//Don't have these: TOUCHES, CROSSES, OVERLAPS
|
||||
|
||||
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 MultiShape}.
|
||||
* @param other
|
||||
* @return
|
||||
*/
|
||||
public SpatialRelation combine(SpatialRelation other) {
|
||||
if (this == other)
|
||||
return this;
|
||||
if (this == WITHIN || other == WITHIN)
|
||||
return WITHIN;
|
||||
return INTERSECTS;
|
||||
}
|
||||
|
||||
public boolean intersects() {
|
||||
return this != DISJOINT;
|
||||
}
|
||||
|
||||
/** 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.shape.simple;
|
||||
|
||||
import org.apache.commons.lang.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang.builder.HashCodeBuilder;
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.shape.*;
|
||||
|
||||
/**
|
||||
* A circle, also known as a point-radius, based on a
|
||||
* {@link org.apache.lucene.spatial.base.distance.DistanceCalculator} which does all the work. This implementation
|
||||
* should work for both cartesian 2D and geodetic sphere surfaces.
|
||||
* Threadsafe & immutable.
|
||||
*/
|
||||
public class CircleImpl implements Circle {
|
||||
|
||||
protected final Point point;
|
||||
protected final double distance;
|
||||
|
||||
protected final SpatialContext ctx;
|
||||
|
||||
/* below is calculated & cached: */
|
||||
|
||||
protected final Rectangle enclosingBox;
|
||||
|
||||
//we don't have a line shape so we use a rectangle for these axis
|
||||
|
||||
public CircleImpl(Point p, double dist, SpatialContext ctx) {
|
||||
//We assume any normalization / validation of params already occurred (including bounding dist)
|
||||
this.point = p;
|
||||
this.distance = dist;
|
||||
this.ctx = ctx;
|
||||
this.enclosingBox = ctx.getDistCalc().calcBoxByDistFromPt(point, distance, ctx);
|
||||
}
|
||||
|
||||
public Point getCenter() {
|
||||
return point;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
public boolean contains(double x, double y) {
|
||||
return ctx.getDistCalc().distance(point, x, y) <= distance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArea() {
|
||||
return distance > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that the bounding box might contain a minX that is > maxX, due to WGS84 dateline.
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Rectangle getBoundingBox() {
|
||||
return enclosingBox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpatialRelation relate(Shape other, SpatialContext ctx) {
|
||||
assert this.ctx == ctx;
|
||||
//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 (other instanceof Point) {
|
||||
return relate((Point) other, ctx);
|
||||
}
|
||||
if (other instanceof Rectangle) {
|
||||
return relate((Rectangle) other, ctx);
|
||||
}
|
||||
if (other instanceof Circle) {
|
||||
return relate((Circle) other, ctx);
|
||||
}
|
||||
return other.relate(this, ctx).transpose();
|
||||
}
|
||||
|
||||
public SpatialRelation relate(Point point, SpatialContext ctx) {
|
||||
return contains(point.getX(),point.getY()) ? SpatialRelation.CONTAINS : SpatialRelation.DISJOINT;
|
||||
}
|
||||
|
||||
public SpatialRelation relate(Rectangle r, SpatialContext ctx) {
|
||||
//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, ctx);
|
||||
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, ctx);
|
||||
}
|
||||
|
||||
protected SpatialRelation relateRectanglePhase2(final Rectangle r, SpatialRelation bboxSect, SpatialContext ctx) {
|
||||
/*
|
||||
!! DOES NOT WORK WITH GEO CROSSING DATELINE OR WORLD-WRAP.
|
||||
TODO upgrade to handle crossing dateline, but not world-wrap; use some x-shifting code from RectangleImpl.
|
||||
*/
|
||||
|
||||
//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.
|
||||
//see http://stackoverflow.com/questions/401847/circle-rectangle-collision-detection-intersection/1879223#1879223
|
||||
final double closestX;
|
||||
double ctr_x = getXAxis();
|
||||
if ( ctr_x < r.getMinX() )
|
||||
closestX = r.getMinX();
|
||||
else if (ctr_x > r.getMaxX())
|
||||
closestX = r.getMaxX();
|
||||
else
|
||||
closestX = ctr_x;
|
||||
|
||||
final double closestY;
|
||||
double ctr_y = getYAxis();
|
||||
if ( ctr_y < r.getMinY() )
|
||||
closestY = r.getMinY();
|
||||
else if (ctr_y > r.getMaxY())
|
||||
closestY = r.getMaxY();
|
||||
else
|
||||
closestY = ctr_y;
|
||||
|
||||
//Check if there is an intersection from this circle to closestXY
|
||||
boolean didContainOnClosestXY = false;
|
||||
if (ctr_x == closestX) {
|
||||
double deltaY = Math.abs(ctr_y - closestY);
|
||||
double distYCirc = (ctr_y < closestY ? enclosingBox.getMaxY() - ctr_y : ctr_y - enclosingBox.getMinY());
|
||||
if (deltaY > distYCirc)
|
||||
return SpatialRelation.DISJOINT;
|
||||
} else if (ctr_y == closestY) {
|
||||
double deltaX = Math.abs(ctr_x - closestX);
|
||||
double distXCirc = (ctr_x < closestX ? enclosingBox.getMaxX() - ctr_x : ctr_x - enclosingBox.getMinX());
|
||||
if (deltaX > distXCirc)
|
||||
return SpatialRelation.DISJOINT;
|
||||
} else {
|
||||
//fallback on more expensive calculation
|
||||
didContainOnClosestXY = true;
|
||||
if(! contains(closestX,closestY) )
|
||||
return SpatialRelation.DISJOINT;
|
||||
}
|
||||
|
||||
//At this point we know that it's *NOT* DISJOINT, so there is some level of intersection. It's *NOT* WITHIN either.
|
||||
// The only question left is whether circle CONTAINS r or simply intersects it.
|
||||
|
||||
//If circle contains r, then its bbox MUST also CONTAIN r.
|
||||
if (bboxSect != SpatialRelation.CONTAINS)
|
||||
return SpatialRelation.INTERSECTS;
|
||||
|
||||
//Find the farthest point of r away from the center of the circle. If that point is contained, then all of r is
|
||||
// contained.
|
||||
double farthestX = r.getMaxX() - ctr_x > ctr_x - r.getMinX() ? r.getMaxX() : r.getMinX();
|
||||
double farthestY = r.getMaxY() - ctr_y > ctr_y - r.getMinY() ? r.getMaxY() : r.getMinY();
|
||||
if (contains(farthestX,farthestY))
|
||||
return SpatialRelation.CONTAINS;
|
||||
return SpatialRelation.INTERSECTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* The y axis horizontal of maximal left-right extent of the circle.
|
||||
*/
|
||||
protected double getYAxis() {
|
||||
return point.getY();
|
||||
}
|
||||
|
||||
protected double getXAxis() {
|
||||
return point.getX();
|
||||
}
|
||||
|
||||
public SpatialRelation relate(Circle circle, SpatialContext ctx) {
|
||||
double crossDist = ctx.getDistCalc().distance(point, circle.getCenter());
|
||||
double aDist = distance, bDist = circle.getDistance();
|
||||
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=" + distance + ')';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) { return false; }
|
||||
if (obj == this) { return true; }
|
||||
if (obj.getClass() != getClass()) {
|
||||
return false;
|
||||
}
|
||||
CircleImpl rhs = (CircleImpl) obj;
|
||||
return new EqualsBuilder()
|
||||
.append(point, rhs.point)
|
||||
.append(distance, rhs.distance)
|
||||
.append(ctx, rhs.ctx)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder(11, 97).
|
||||
append(point).
|
||||
append(distance).
|
||||
append(ctx).
|
||||
toHashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.shape.simple;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.shape.SpatialRelation;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
|
||||
/**
|
||||
* @author David Smiley - dsmiley@mitre.org
|
||||
*/
|
||||
public class GeoCircleImpl extends CircleImpl {
|
||||
private final double distDEG;// [0 TO 180]
|
||||
private final GeoCircleImpl inverseCircle;//when distance reaches > 1/2 way around the world, cache the inverse.
|
||||
private final double horizAxisY;//see getYAxis
|
||||
|
||||
public GeoCircleImpl(Point p, double dist, SpatialContext ctx) {
|
||||
super(p, dist, ctx);
|
||||
assert ctx.isGeo();
|
||||
|
||||
//In the direction of latitude (N,S), distance is the same number of degrees.
|
||||
distDEG = ctx.getDistCalc().distanceToDegrees(distance);
|
||||
|
||||
if (distDEG > 90) {
|
||||
assert enclosingBox.getWidth() == 360;
|
||||
double backDistDEG = 180 - distDEG;
|
||||
if (backDistDEG >= 0) {
|
||||
double backDistance = ctx.getDistCalc().degreesToDistance(backDistDEG);
|
||||
Point backPoint = ctx.makePoint(getCenter().getX() + 180, getCenter().getY() + 180);
|
||||
inverseCircle = new GeoCircleImpl(backPoint,backDistance,ctx);
|
||||
} else
|
||||
inverseCircle = null;//whole globe
|
||||
horizAxisY = getCenter().getY();//although probably not used
|
||||
} else {
|
||||
inverseCircle = null;
|
||||
double _horizAxisY = ctx.getDistCalc().calcBoxByDistFromPtHorizAxis(getCenter(), distance, 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.
|
||||
* @bboxSect INTERSECTS or CONTAINS from enclosingBox's intersection
|
||||
* @result DISJOINT, CONTAINS, or INTERSECTS (not WITHIN)
|
||||
*/
|
||||
@Override
|
||||
protected SpatialRelation relateRectanglePhase2(Rectangle r, SpatialRelation bboxSect, SpatialContext ctx) {
|
||||
|
||||
//Rectangle wraps around the world longitudinally creating a solid band; there are no corners to test intersection
|
||||
if (r.getWidth() == 360) {
|
||||
return SpatialRelation.INTERSECTS;
|
||||
}
|
||||
|
||||
if (inverseCircle != null) {
|
||||
return inverseCircle.relate(r, ctx).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, ctx);
|
||||
}
|
||||
|
||||
//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.relate_xRange(enclosingBox.getMinX(), enclosingBox.getMaxX(), ctx);
|
||||
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.relate_yRange(getYAxis(), getYAxis(), ctx).intersects() // at y vertical
|
||||
&& r.relate_xRange(enclosingBox.getMinX(), enclosingBox.getMaxX(), ctx).intersects() )
|
||||
return SpatialRelation.INTERSECTS;
|
||||
|
||||
/* y axis intersects */
|
||||
if (r.relate_xRange(getXAxis(), getXAxis(), ctx).intersects()) { // at x horizontal
|
||||
double yTop = getCenter().getY()+ distDEG;
|
||||
assert yTop <= 90;
|
||||
double yBot = getCenter().getY()- distDEG;
|
||||
assert yBot >= -90;
|
||||
if (r.relate_yRange(yBot, yTop, ctx).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 (distDEG == 180)//whole globe
|
||||
return SpatialRelation.CONTAINS;
|
||||
|
||||
//Check if r is within the pole wrap region:
|
||||
double yTop = getCenter().getY()+ distDEG;
|
||||
if (yTop > 90) {
|
||||
double yTopOverlap = yTop - 90;
|
||||
assert yTopOverlap <= 90;
|
||||
if (r.getMinY() >= 90 - yTopOverlap)
|
||||
return SpatialRelation.CONTAINS;
|
||||
} else {
|
||||
double yBot = point.getY() - distDEG;
|
||||
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.)
|
||||
if (cornersIntersect == 4) {//all
|
||||
double backX = ctx.normX(getCenter().getX()+180);
|
||||
if (r.relate_xRange(backX, backX, ctx).intersects())
|
||||
return SpatialRelation.INTERSECTS;
|
||||
else
|
||||
return SpatialRelation.CONTAINS;
|
||||
} else if (cornersIntersect == 0) {//none
|
||||
double frontX = getCenter().getX();
|
||||
if (r.relate_xRange(frontX, frontX, ctx).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() {
|
||||
//I'm deliberately making this look basic and not fully detailed with class name & misc fields.
|
||||
//Add distance in degrees, which is easier to recognize, and earth radius agnostic.
|
||||
String dStr = String.format("%.1f",distance);
|
||||
if (ctx.isGeo()) {
|
||||
double distDEG = ctx.getDistCalc().distanceToDegrees(distance);
|
||||
dStr += String.format("=%.1f\u00B0",distDEG);
|
||||
}
|
||||
return "Circle(" + point + ",d=" + dStr + ')';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.shape.simple;
|
||||
|
||||
import org.apache.commons.lang.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang.builder.HashCodeBuilder;
|
||||
import org.apache.lucene.spatial.base.shape.SpatialRelation;
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
|
||||
|
||||
public class PointImpl implements Point {
|
||||
|
||||
private final double x;
|
||||
private final double y;
|
||||
|
||||
public PointImpl(double x, double y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getY() {
|
||||
return y;
|
||||
}
|
||||
@Override
|
||||
public Rectangle getBoundingBox() {
|
||||
return new RectangleImpl(x, x, y, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointImpl getCenter() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpatialRelation relate(Shape other, SpatialContext ctx) {
|
||||
if (other instanceof Point)
|
||||
return this.equals(other) ? SpatialRelation.INTERSECTS : SpatialRelation.DISJOINT;
|
||||
return other.relate(this, ctx).transpose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArea() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Pt(x="+x+",y="+y+")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) { return false; }
|
||||
if (obj == this) { return true; }
|
||||
if (obj.getClass() != getClass()) {
|
||||
return false;
|
||||
}
|
||||
PointImpl rhs = (PointImpl) obj;
|
||||
return new EqualsBuilder()
|
||||
.append(x, rhs.x)
|
||||
.append(y, rhs.y)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder(5, 89).
|
||||
append(x).
|
||||
append(y).
|
||||
toHashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.shape.simple;
|
||||
|
||||
import org.apache.commons.lang.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang.builder.HashCodeBuilder;
|
||||
import org.apache.lucene.spatial.base.shape.SpatialRelation;
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.distance.DistanceUtils;
|
||||
import org.apache.lucene.spatial.base.shape.*;
|
||||
|
||||
/**
|
||||
* 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 double minX;
|
||||
private final double maxX;
|
||||
private final double minY;
|
||||
private final double maxY;
|
||||
|
||||
//TODO change to West South East North to be more consistent with OGC?
|
||||
public RectangleImpl(double minX, double maxX, double minY, double maxY) {
|
||||
//We assume any normalization / validation of params already occurred.
|
||||
this.minX = minX;
|
||||
this.maxX = maxX;
|
||||
this.minY = minY;
|
||||
this.maxY = maxY;
|
||||
assert minY <= maxY;
|
||||
}
|
||||
|
||||
/** Copy constructor. */
|
||||
public RectangleImpl(Rectangle r) {
|
||||
this(r.getMinX(),r.getMaxX(),r.getMinY(),r.getMaxY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArea() {
|
||||
return maxX != minX && maxY != minY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getArea() {
|
||||
return getWidth() * getHeight();
|
||||
}
|
||||
|
||||
@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, SpatialContext ctx) {
|
||||
if (other instanceof Point) {
|
||||
return relate((Point) other, ctx);
|
||||
}
|
||||
if (other instanceof Rectangle) {
|
||||
return relate((Rectangle) other, ctx);
|
||||
}
|
||||
return other.relate(this, ctx).transpose();
|
||||
}
|
||||
|
||||
public SpatialRelation relate(Point point, SpatialContext ctx) {
|
||||
if (point.getY() > getMaxY() || point.getY() < getMinY() ||
|
||||
(getCrossesDateLine() ?
|
||||
(point.getX() < minX && point.getX() > maxX)
|
||||
: (point.getX() < minX || point.getX() > maxX) ))
|
||||
return SpatialRelation.DISJOINT;
|
||||
return SpatialRelation.CONTAINS;
|
||||
}
|
||||
|
||||
public SpatialRelation relate(Rectangle rect, SpatialContext ctx) {
|
||||
SpatialRelation yIntersect = relate_yRange(rect.getMinY(), rect.getMaxY(), ctx);
|
||||
if (yIntersect == SpatialRelation.DISJOINT)
|
||||
return SpatialRelation.DISJOINT;
|
||||
|
||||
SpatialRelation xIntersect = relate_xRange(rect.getMinX(), rect.getMaxX(), ctx);
|
||||
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;
|
||||
}
|
||||
|
||||
public SpatialRelation relate_yRange(double ext_minY, double ext_maxY, SpatialContext ctx) {
|
||||
if (ext_minY > maxY || ext_maxY < minY) {
|
||||
return SpatialRelation.DISJOINT;
|
||||
}
|
||||
|
||||
if (ext_minY >= minY && ext_maxY <= maxY) {
|
||||
return SpatialRelation.CONTAINS;
|
||||
}
|
||||
|
||||
if (ext_minY <= minY && ext_maxY >= maxY) {
|
||||
return SpatialRelation.WITHIN;
|
||||
}
|
||||
return SpatialRelation.INTERSECTS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpatialRelation relate_xRange(double ext_minX, double ext_maxX, SpatialContext ctx) {
|
||||
//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()) {
|
||||
//the 360 check is an edge-case for complete world-wrap
|
||||
double ext_width = ext_maxX - ext_minX;
|
||||
if (ext_width < 0)//this logic unfortunately duplicates getWidth()
|
||||
ext_width += 360;
|
||||
|
||||
if (ext_width < 360) {
|
||||
ext_maxX = ext_minX + ext_width;
|
||||
} else {
|
||||
ext_maxX = 180+360;
|
||||
}
|
||||
|
||||
if (getWidth() < 360) {
|
||||
maxX = minX + getWidth();
|
||||
} else {
|
||||
maxX = 180+360;
|
||||
}
|
||||
|
||||
if (maxX < ext_minX) {
|
||||
minX += 360;
|
||||
maxX += 360;
|
||||
} else if (ext_maxX < minX) {
|
||||
ext_minX += 360;
|
||||
ext_maxX += 360;
|
||||
}
|
||||
}
|
||||
|
||||
if (ext_minX > maxX || ext_maxX < minX ) {
|
||||
return SpatialRelation.DISJOINT;
|
||||
}
|
||||
|
||||
if (ext_minX >= minX && ext_maxX <= maxX ) {
|
||||
return SpatialRelation.CONTAINS;
|
||||
}
|
||||
|
||||
if (ext_minX <= minX && ext_maxX >= maxX ) {
|
||||
return SpatialRelation.WITHIN;
|
||||
}
|
||||
return SpatialRelation.INTERSECTS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Rect(minX=" + minX + ",maxX=" + maxX + ",minY=" + minY + ",maxY=" + maxY + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point getCenter() {
|
||||
final double y = getHeight() / 2 + minY;
|
||||
double x = getWidth() / 2 + minX;
|
||||
if (minX > maxX)//WGS84
|
||||
x = DistanceUtils.normLonDEG(x);
|
||||
return new PointImpl(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) { return false; }
|
||||
if (obj == this) { return true; }
|
||||
if (obj.getClass() != getClass()) {
|
||||
return false;
|
||||
}
|
||||
RectangleImpl rhs = (RectangleImpl) obj;
|
||||
return new EqualsBuilder()
|
||||
.append(minX, rhs.minX)
|
||||
.append(minY, rhs.minY)
|
||||
.append(maxX, rhs.maxX)
|
||||
.append(maxY, rhs.maxY)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder(41, 37).
|
||||
append(minX).append(minY).
|
||||
append(maxX).append(maxY).
|
||||
toHashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.benchmark;
|
||||
|
||||
import org.apache.lucene.benchmark.byTask.PerfRunData;
|
||||
import org.apache.lucene.benchmark.byTask.tasks.PerfTask;
|
||||
import org.apache.lucene.benchmark.byTask.utils.Config;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.index.IndexableField;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
import org.apache.lucene.spatial.strategy.SpatialFieldInfo;
|
||||
import org.apache.lucene.spatial.strategy.SpatialStrategy;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class IndexShapeTask<T extends SpatialFieldInfo> extends PerfTask implements StrategyAware<T> {
|
||||
|
||||
private ShapeGenerator shapeGenerator;
|
||||
private int numShapes;
|
||||
|
||||
public IndexShapeTask(PerfRunData runData) {
|
||||
super(runData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() throws Exception {
|
||||
Config config = getRunData().getConfig();
|
||||
String shapeGeneratorName = config.get("index.shapegenerator", ""); // TODO (cmale) - Setup default shape generator
|
||||
shapeGenerator = (ShapeGenerator) Class.forName(shapeGeneratorName)
|
||||
.getConstructor(Config.class)
|
||||
.newInstance(config);
|
||||
numShapes = config.get("index.numshapes", 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int doLogic() throws Exception {
|
||||
SpatialStrategy<T> spatialStrategy = createSpatialStrategy();
|
||||
T fieldInfo = createFieldInfo();
|
||||
for (int i = 0; i < numShapes; i++) {
|
||||
Shape shape = shapeGenerator.generate();
|
||||
IndexableField[] fields = spatialStrategy.createFields(fieldInfo, shape, true, true);
|
||||
if (fields == null) {
|
||||
continue;
|
||||
}
|
||||
Document document = new Document();
|
||||
document.add(new Field("id",UUID.randomUUID().toString(),StringField.TYPE_STORED));
|
||||
for (IndexableField field : fields) {
|
||||
document.add(field);
|
||||
}
|
||||
getRunData().getIndexWriter().addDocument(document);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.benchmark;
|
||||
|
||||
import org.apache.lucene.benchmark.byTask.PerfRunData;
|
||||
import org.apache.lucene.benchmark.byTask.tasks.PerfTask;
|
||||
import org.apache.lucene.benchmark.byTask.utils.Config;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.TopDocs;
|
||||
import org.apache.lucene.spatial.base.query.SpatialArgs;
|
||||
import org.apache.lucene.spatial.base.query.SpatialArgsParser;
|
||||
import org.apache.lucene.spatial.strategy.SpatialFieldInfo;
|
||||
|
||||
|
||||
public abstract class QueryShapeTask<T extends SpatialFieldInfo> extends PerfTask implements StrategyAware<T> {
|
||||
|
||||
private SpatialArgs spatialArgs;
|
||||
|
||||
public QueryShapeTask(PerfRunData runData) {
|
||||
super(runData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() {
|
||||
Config config = getRunData().getConfig();
|
||||
String rawQuery = config.get("query.shapequery", ""); // TODO (cmale) - Come up with default query
|
||||
this.spatialArgs = new SpatialArgsParser().parse(rawQuery, getSpatialContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int doLogic() throws Exception {
|
||||
Query query = createSpatialStrategy().makeQuery(spatialArgs, createFieldInfo());
|
||||
TopDocs topDocs = getRunData().getIndexSearcher().search(query, 10);
|
||||
System.out.println("Numfound: " + topDocs.totalHits);
|
||||
return 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.benchmark;
|
||||
|
||||
import org.apache.lucene.benchmark.byTask.utils.Config;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
|
||||
|
||||
public abstract class ShapeGenerator {
|
||||
|
||||
private Config config;
|
||||
|
||||
protected ShapeGenerator(Config config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public abstract Shape generate();
|
||||
|
||||
protected Config getConfig() {
|
||||
return config;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.benchmark;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.strategy.SpatialFieldInfo;
|
||||
import org.apache.lucene.spatial.strategy.SpatialStrategy;
|
||||
|
||||
|
||||
public interface StrategyAware<T extends SpatialFieldInfo> {
|
||||
|
||||
T createFieldInfo();
|
||||
|
||||
SpatialStrategy<T> createSpatialStrategy();
|
||||
|
||||
SpatialContext getSpatialContext();
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy;
|
||||
|
||||
|
||||
public class SimpleSpatialFieldInfo implements SpatialFieldInfo {
|
||||
|
||||
private final String fieldName;
|
||||
|
||||
public SimpleSpatialFieldInfo(String fieldName) {
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy;
|
||||
|
||||
/**
|
||||
* Information the strategy needs for the lucene fields
|
||||
*/
|
||||
public interface SpatialFieldInfo {
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy;
|
||||
|
||||
import org.apache.lucene.index.IndexableField;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.query.SpatialArgs;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
|
||||
/**
|
||||
* must be thread safe
|
||||
*/
|
||||
public abstract class SpatialStrategy<T extends SpatialFieldInfo> {
|
||||
|
||||
protected boolean ignoreIncompatibleGeometry = false;
|
||||
protected final SpatialContext ctx;
|
||||
|
||||
public SpatialStrategy(SpatialContext ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
public SpatialContext getSpatialContext() {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/** Corresponds with Solr's FieldType.isPolyField(). */
|
||||
public boolean isPolyField() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Corresponds with Solr's FieldType.createField().
|
||||
*
|
||||
* This may return a null field if it does not want to make anything.
|
||||
* This is reasonable behavior if 'ignoreIncompatibleGeometry=true' and the
|
||||
* geometry is incompatible
|
||||
*/
|
||||
public abstract IndexableField createField(T fieldInfo, Shape shape, boolean index, boolean store);
|
||||
|
||||
/** Corresponds with Solr's FieldType.createFields(). */
|
||||
public IndexableField[] createFields(T fieldInfo, Shape shape, boolean index, boolean store) {
|
||||
return new IndexableField[] { createField(fieldInfo, shape, index, store) };
|
||||
}
|
||||
|
||||
public abstract ValueSource makeValueSource(SpatialArgs args, T fieldInfo);
|
||||
|
||||
/**
|
||||
* Make a query
|
||||
*/
|
||||
public abstract Query makeQuery(SpatialArgs args, T fieldInfo);
|
||||
|
||||
/**
|
||||
* Make a Filter
|
||||
*/
|
||||
public abstract Filter makeFilter(SpatialArgs args, T fieldInfo);
|
||||
|
||||
public boolean isIgnoreIncompatibleGeometry() {
|
||||
return ignoreIncompatibleGeometry;
|
||||
}
|
||||
|
||||
public void setIgnoreIncompatibleGeometry(boolean ignoreIncompatibleGeometry) {
|
||||
this.ignoreIncompatibleGeometry = ignoreIncompatibleGeometry;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Lucene spatial search
|
||||
*
|
||||
* Check:
|
||||
* http://blog.notdot.net/2009/11/Damn-Cool-Algorithms-Spatial-indexing-with-Quadtrees-and-Hilbert-Curves
|
||||
*/
|
||||
package org.apache.lucene.spatial.strategy;
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.prefix;
|
||||
|
||||
import org.apache.lucene.spatial.base.prefix.Node;
|
||||
import org.apache.lucene.spatial.base.prefix.SpatialPrefixTree;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.strategy.util.ShapeFieldCacheProvider;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
|
||||
public class PointPrefixTreeFieldCacheProvider extends ShapeFieldCacheProvider<Point> {
|
||||
|
||||
final SpatialPrefixTree grid; //
|
||||
|
||||
public PointPrefixTreeFieldCacheProvider(SpatialPrefixTree grid, String shapeField, int defaultSize) {
|
||||
super( shapeField, defaultSize );
|
||||
this.grid = grid;
|
||||
}
|
||||
|
||||
//A kluge that this is a field
|
||||
private Node scanCell = null;
|
||||
|
||||
@Override
|
||||
protected Point readShape(BytesRef term) {
|
||||
scanCell = grid.getNode(term.bytes, term.offset, term.length, scanCell);
|
||||
return scanCell.isLeaf() ? scanCell.getShape().getCenter() : null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.prefix;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
|
||||
import org.apache.lucene.analysis.Tokenizer;
|
||||
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class PrefixCellsTokenizer extends Tokenizer {
|
||||
public PrefixCellsTokenizer(Reader input) {
|
||||
super(input);
|
||||
}
|
||||
|
||||
private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
|
||||
|
||||
@Override
|
||||
public final boolean incrementToken() throws IOException {
|
||||
clearAttributes();
|
||||
int length = 0;
|
||||
char[] buffer = termAtt.buffer();
|
||||
while (true) {
|
||||
char c = (char) input.read();
|
||||
if (c < 0) break;
|
||||
if (c == 'a' || c == 'A') {
|
||||
buffer[length++] = 'A';
|
||||
continue;
|
||||
}
|
||||
if (c == 'b' || c == 'B') {
|
||||
buffer[length++] = 'B';
|
||||
continue;
|
||||
}
|
||||
if (c == 'c' || c == 'C') {
|
||||
buffer[length++] = 'C';
|
||||
continue;
|
||||
}
|
||||
if (c == 'd' || c == 'D') {
|
||||
buffer[length++] = 'D';
|
||||
continue;
|
||||
}
|
||||
if (c == '*') {
|
||||
buffer[length++] = '*';
|
||||
continue;
|
||||
}
|
||||
if (c == '+') {
|
||||
buffer[length++] = '+';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (length > 0) {
|
||||
// Skip any other character
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
termAtt.setLength(length);
|
||||
return length > 0; // should only happen at the end
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void end() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset(Reader input) throws IOException {
|
||||
super.reset(input);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.prefix;
|
||||
|
||||
import org.apache.lucene.analysis.TokenStream;
|
||||
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.FieldType;
|
||||
import org.apache.lucene.document.StoredField;
|
||||
import org.apache.lucene.index.IndexableField;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.spatial.base.distance.DistanceCalculator;
|
||||
import org.apache.lucene.spatial.base.prefix.Node;
|
||||
import org.apache.lucene.spatial.base.prefix.SpatialPrefixTree;
|
||||
import org.apache.lucene.spatial.base.query.SpatialArgs;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
import org.apache.lucene.spatial.strategy.SimpleSpatialFieldInfo;
|
||||
import org.apache.lucene.spatial.strategy.SpatialStrategy;
|
||||
import org.apache.lucene.spatial.strategy.util.CachedDistanceValueSource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public abstract class PrefixTreeStrategy extends SpatialStrategy<SimpleSpatialFieldInfo> {
|
||||
protected final SpatialPrefixTree grid;
|
||||
private final Map<String, PointPrefixTreeFieldCacheProvider> provider = new ConcurrentHashMap<String, PointPrefixTreeFieldCacheProvider>();
|
||||
protected int defaultFieldValuesArrayLen = 2;
|
||||
protected double distErrPct = SpatialArgs.DEFAULT_DIST_PRECISION;
|
||||
|
||||
public PrefixTreeStrategy(SpatialPrefixTree grid) {
|
||||
super(grid.getSpatialContext());
|
||||
this.grid = grid;
|
||||
}
|
||||
|
||||
/** Used in the in-memory ValueSource as a default ArrayList length for this field's array of values, per doc. */
|
||||
public void setDefaultFieldValuesArrayLen(int defaultFieldValuesArrayLen) {
|
||||
this.defaultFieldValuesArrayLen = defaultFieldValuesArrayLen;
|
||||
}
|
||||
|
||||
/** See {@link SpatialPrefixTree#getMaxLevelForPrecision(org.apache.lucene.spatial.base.shape.Shape, double)}. */
|
||||
public void setDistErrPct(double distErrPct) {
|
||||
this.distErrPct = distErrPct;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexableField createField(SimpleSpatialFieldInfo fieldInfo, Shape shape, boolean index, boolean store) {
|
||||
int detailLevel = grid.getMaxLevelForPrecision(shape,distErrPct);
|
||||
List<Node> cells = grid.getNodes(shape, detailLevel, true);//true=intermediates cells
|
||||
//If shape isn't a point, add a full-resolution center-point so that
|
||||
// PrefixFieldCacheProvider has the center-points.
|
||||
// TODO index each center of a multi-point? Yes/no?
|
||||
if (!(shape instanceof Point)) {
|
||||
Point ctr = shape.getCenter();
|
||||
//TODO should be smarter; don't index 2 tokens for this in CellTokenizer. Harmless though.
|
||||
cells.add(grid.getNodes(ctr,grid.getMaxLevels(),false).get(0));
|
||||
}
|
||||
|
||||
String fname = fieldInfo.getFieldName();
|
||||
if( store ) {
|
||||
//TODO figure out how to re-use original string instead of reconstituting it.
|
||||
String wkt = grid.getSpatialContext().toString(shape);
|
||||
if( index ) {
|
||||
Field f = new Field(fname,wkt,TYPE_STORED);
|
||||
f.setTokenStream(new CellTokenStream(cells.iterator()));
|
||||
return f;
|
||||
}
|
||||
return new StoredField(fname,wkt);
|
||||
}
|
||||
|
||||
if( index ) {
|
||||
return new Field(fname,new CellTokenStream(cells.iterator()),TYPE_UNSTORED);
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Fields need to be indexed or store ["+fname+"]");
|
||||
}
|
||||
|
||||
/* Indexed, tokenized, not stored. */
|
||||
public static final FieldType TYPE_UNSTORED = new FieldType();
|
||||
|
||||
/* Indexed, tokenized, stored. */
|
||||
public static final FieldType TYPE_STORED = new FieldType();
|
||||
|
||||
static {
|
||||
TYPE_UNSTORED.setIndexed(true);
|
||||
TYPE_UNSTORED.setTokenized(true);
|
||||
TYPE_UNSTORED.setOmitNorms(true);
|
||||
TYPE_UNSTORED.freeze();
|
||||
|
||||
TYPE_STORED.setStored(true);
|
||||
TYPE_STORED.setIndexed(true);
|
||||
TYPE_STORED.setTokenized(true);
|
||||
TYPE_STORED.setOmitNorms(true);
|
||||
TYPE_STORED.freeze();
|
||||
}
|
||||
|
||||
/** Outputs the tokenString of a cell, and if its a leaf, outputs it again with the leaf byte. */
|
||||
final static class CellTokenStream extends TokenStream {
|
||||
|
||||
private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
|
||||
|
||||
private Iterator<Node> iter = null;
|
||||
|
||||
public CellTokenStream(Iterator<Node> tokens) {
|
||||
this.iter = tokens;
|
||||
}
|
||||
|
||||
CharSequence nextTokenStringNeedingLeaf = null;
|
||||
|
||||
@Override
|
||||
public boolean incrementToken() throws IOException {
|
||||
clearAttributes();
|
||||
if (nextTokenStringNeedingLeaf != null) {
|
||||
termAtt.append(nextTokenStringNeedingLeaf);
|
||||
termAtt.append((char) Node.LEAF_BYTE);
|
||||
nextTokenStringNeedingLeaf = null;
|
||||
return true;
|
||||
}
|
||||
if (iter.hasNext()) {
|
||||
Node cell = iter.next();
|
||||
CharSequence token = cell.getTokenString();
|
||||
termAtt.append(token);
|
||||
if (cell.isLeaf())
|
||||
nextTokenStringNeedingLeaf = token;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueSource makeValueSource(SpatialArgs args, SimpleSpatialFieldInfo fieldInfo) {
|
||||
DistanceCalculator calc = grid.getSpatialContext().getDistCalc();
|
||||
return makeValueSource(args, fieldInfo, calc);
|
||||
}
|
||||
|
||||
public ValueSource makeValueSource(SpatialArgs args, SimpleSpatialFieldInfo fieldInfo, DistanceCalculator calc) {
|
||||
PointPrefixTreeFieldCacheProvider p = provider.get( fieldInfo.getFieldName() );
|
||||
if( p == null ) {
|
||||
synchronized (this) {//double checked locking idiom is okay since provider is threadsafe
|
||||
p = provider.get( fieldInfo.getFieldName() );
|
||||
if (p == null) {
|
||||
p = new PointPrefixTreeFieldCacheProvider(grid, fieldInfo.getFieldName(), defaultFieldValuesArrayLen);
|
||||
provider.put(fieldInfo.getFieldName(),p);
|
||||
}
|
||||
}
|
||||
}
|
||||
Point point = args.getShape().getCenter();
|
||||
return new CachedDistanceValueSource(point, calc, p);
|
||||
}
|
||||
|
||||
public SpatialPrefixTree getGrid() {
|
||||
return grid;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.prefix;
|
||||
|
||||
import org.apache.lucene.index.*;
|
||||
import org.apache.lucene.search.DocIdSet;
|
||||
import org.apache.lucene.search.DocIdSetIterator;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.spatial.base.shape.SpatialRelation;
|
||||
import org.apache.lucene.spatial.base.prefix.Node;
|
||||
import org.apache.lucene.spatial.base.prefix.SpatialPrefixTree;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
import org.apache.lucene.util.Bits;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.OpenBitSet;
|
||||
import org.apache.lucene.util.StringHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Performs a spatial intersection filter against a field indexed with {@link SpatialPrefixTree}, a Trie.
|
||||
* SPT yields terms (grids) at length 1 and at greater lengths corresponding to greater precisions.
|
||||
* This filter recursively traverses each grid length and uses methods on {@link Shape} to efficiently know
|
||||
* that all points at a prefix fit in the shape or not to either short-circuit unnecessary traversals or to efficiently
|
||||
* load all enclosed points.
|
||||
*/
|
||||
public class RecursivePrefixTreeFilter extends Filter {
|
||||
|
||||
/* TODOs for future:
|
||||
|
||||
Can a polygon query shape be optimized / made-simpler at recursive depths (e.g. intersection of shape + cell box)
|
||||
|
||||
RE "scan" threshold:
|
||||
// IF configured to do so, we could use term.freq() as an estimate on the number of places at this depth. OR, perhaps
|
||||
// make estimates based on the total known term count at this level?
|
||||
if (!scan) {
|
||||
//Make some estimations on how many points there are at this level and how few there would need to be to set
|
||||
// !scan to false.
|
||||
long termsThreshold = (long) estimateNumberIndexedTerms(cell.length(),queryShape.getDocFreqExpenseThreshold(cell));
|
||||
long thisOrd = termsEnum.ord();
|
||||
scan = (termsEnum.seek(thisOrd+termsThreshold+1) == TermsEnum.SeekStatus.END
|
||||
|| !cell.contains(termsEnum.term()));
|
||||
termsEnum.seek(thisOrd);//return to last position
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
private final String fieldName;
|
||||
private final SpatialPrefixTree grid;
|
||||
private final Shape queryShape;
|
||||
private final int prefixGridScanLevel;//at least one less than grid.getMaxLevels()
|
||||
private final int detailLevel;
|
||||
|
||||
public RecursivePrefixTreeFilter(String fieldName, SpatialPrefixTree grid, Shape queryShape, int prefixGridScanLevel,
|
||||
int detailLevel) {
|
||||
this.fieldName = fieldName;
|
||||
this.grid = grid;
|
||||
this.queryShape = queryShape;
|
||||
this.prefixGridScanLevel = Math.max(1,Math.min(prefixGridScanLevel,grid.getMaxLevels()-1));
|
||||
this.detailLevel = detailLevel;
|
||||
assert detailLevel <= grid.getMaxLevels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocIdSet getDocIdSet(AtomicReaderContext ctx, Bits acceptDocs) throws IOException {
|
||||
AtomicReader reader = ctx.reader();
|
||||
OpenBitSet bits = new OpenBitSet(reader.maxDoc());
|
||||
Terms terms = reader.terms(fieldName);
|
||||
if (terms == null)
|
||||
return null;
|
||||
TermsEnum termsEnum = terms.iterator(null);
|
||||
DocsEnum docsEnum = null;//cached for termsEnum.docs() calls
|
||||
Node scanCell = null;
|
||||
|
||||
//cells is treated like a stack. LinkedList conveniently has bulk add to beginning. It's in sorted order so that we
|
||||
// always advance forward through the termsEnum index.
|
||||
LinkedList<Node> cells = new LinkedList<Node>(
|
||||
grid.getWorldNode().getSubCells(queryShape) );
|
||||
|
||||
//This is a recursive algorithm that starts with one or more "big" cells, and then recursively dives down into the
|
||||
// first such cell that intersects with the query shape. It's a depth first traversal because we don't move onto
|
||||
// the next big cell (breadth) until we're completely done considering all smaller cells beneath it. For a given
|
||||
// cell, if it's *within* the query shape then we can conveniently short-circuit the depth traversal and
|
||||
// grab all documents assigned to this cell/term. For an intersection of the cell and query shape, we either
|
||||
// recursively step down another grid level or we decide heuristically (via prefixGridScanLevel) that there aren't
|
||||
// that many points, and so we scan through all terms within this cell (i.e. the term starts with the cell's term),
|
||||
// seeing which ones are within the query shape.
|
||||
while(!cells.isEmpty()) {
|
||||
final Node cell = cells.removeFirst();
|
||||
final BytesRef cellTerm = new BytesRef(cell.getTokenBytes());
|
||||
TermsEnum.SeekStatus seekStat = termsEnum.seekCeil(cellTerm);
|
||||
if (seekStat == TermsEnum.SeekStatus.END)
|
||||
break;
|
||||
if (seekStat == TermsEnum.SeekStatus.NOT_FOUND)
|
||||
continue;
|
||||
if (cell.getLevel() == detailLevel || cell.isLeaf()) {
|
||||
docsEnum = termsEnum.docs(acceptDocs, docsEnum, false);
|
||||
addDocs(docsEnum,bits);
|
||||
} else {//any other intersection
|
||||
//If the next indexed term is the leaf marker, then add all of them
|
||||
BytesRef nextCellTerm = termsEnum.next();
|
||||
assert StringHelper.startsWith(nextCellTerm, cellTerm);
|
||||
scanCell = grid.getNode(nextCellTerm.bytes, nextCellTerm.offset, nextCellTerm.length, scanCell);
|
||||
if (scanCell.isLeaf()) {
|
||||
docsEnum = termsEnum.docs(acceptDocs, docsEnum, false);
|
||||
addDocs(docsEnum,bits);
|
||||
termsEnum.next();//move pointer to avoid potential redundant addDocs() below
|
||||
}
|
||||
|
||||
//Decide whether to continue to divide & conquer, or whether it's time to scan through terms beneath this cell.
|
||||
// Scanning is a performance optimization trade-off.
|
||||
boolean scan = cell.getLevel() >= prefixGridScanLevel;//simple heuristic
|
||||
|
||||
if (!scan) {
|
||||
//Divide & conquer
|
||||
cells.addAll(0, cell.getSubCells(queryShape));//add to beginning
|
||||
} else {
|
||||
//Scan through all terms within this cell to see if they are within the queryShape. No seek()s.
|
||||
for(BytesRef term = termsEnum.term(); term != null && StringHelper.startsWith(term,cellTerm); term = termsEnum.next()) {
|
||||
scanCell = grid.getNode(term.bytes, term.offset, term.length, scanCell);
|
||||
int termLevel = scanCell.getLevel();
|
||||
if (termLevel > detailLevel)
|
||||
continue;
|
||||
if (termLevel == detailLevel || scanCell.isLeaf()) {
|
||||
//TODO should put more thought into implications of box vs point
|
||||
Shape cShape = termLevel == grid.getMaxLevels() ? scanCell.getCenter() : scanCell.getShape();
|
||||
if(queryShape.relate(cShape, grid.getSpatialContext()) == SpatialRelation.DISJOINT)
|
||||
continue;
|
||||
|
||||
docsEnum = termsEnum.docs(acceptDocs, docsEnum, false);
|
||||
addDocs(docsEnum,bits);
|
||||
}
|
||||
}//term loop
|
||||
}
|
||||
}
|
||||
}//cell loop
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
private void addDocs(DocsEnum docsEnum, OpenBitSet bits) throws IOException {
|
||||
int docid;
|
||||
while ((docid = docsEnum.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
|
||||
bits.fastSet(docid);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GeoFilter{fieldName='" + fieldName + '\'' + ", shape=" + queryShape + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
RecursivePrefixTreeFilter that = (RecursivePrefixTreeFilter) o;
|
||||
|
||||
if (!fieldName.equals(that.fieldName)) return false;
|
||||
//note that we don't need to look at grid since for the same field it should be the same
|
||||
if (prefixGridScanLevel != that.prefixGridScanLevel) return false;
|
||||
if (detailLevel != that.detailLevel) return false;
|
||||
if (!queryShape.equals(that.queryShape)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = fieldName.hashCode();
|
||||
result = 31 * result + queryShape.hashCode();
|
||||
result = 31 * result + detailLevel;
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.prefix;
|
||||
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.search.FilteredQuery;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.queries.function.FunctionQuery;
|
||||
import org.apache.lucene.spatial.base.exception.UnsupportedSpatialOperation;
|
||||
import org.apache.lucene.spatial.base.prefix.SpatialPrefixTree;
|
||||
import org.apache.lucene.spatial.base.query.SpatialArgs;
|
||||
import org.apache.lucene.spatial.base.query.SpatialOperation;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
import org.apache.lucene.spatial.strategy.SimpleSpatialFieldInfo;
|
||||
|
||||
|
||||
public class RecursivePrefixTreeStrategy extends PrefixTreeStrategy {
|
||||
|
||||
private int prefixGridScanLevel;//TODO how is this customized?
|
||||
|
||||
public RecursivePrefixTreeStrategy(SpatialPrefixTree grid) {
|
||||
super(grid);
|
||||
prefixGridScanLevel = grid.getMaxLevels() - 4;//TODO this default constant is dependent on the prefix grid size
|
||||
}
|
||||
|
||||
public void setPrefixGridScanLevel(int prefixGridScanLevel) {
|
||||
this.prefixGridScanLevel = prefixGridScanLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName()+"(prefixGridScanLevel:"+prefixGridScanLevel+",SPG:("+ grid +"))";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query makeQuery(SpatialArgs args, SimpleSpatialFieldInfo fieldInfo) {
|
||||
Filter f = makeFilter(args, fieldInfo);
|
||||
|
||||
ValueSource vs = makeValueSource(args, fieldInfo);
|
||||
return new FilteredQuery( new FunctionQuery(vs), f );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter makeFilter(SpatialArgs args, SimpleSpatialFieldInfo fieldInfo) {
|
||||
final SpatialOperation op = args.getOperation();
|
||||
if (! SpatialOperation.is(op, SpatialOperation.IsWithin, SpatialOperation.Intersects, SpatialOperation.BBoxWithin))
|
||||
throw new UnsupportedSpatialOperation(op);
|
||||
|
||||
Shape qshape = args.getShape();
|
||||
|
||||
int detailLevel = grid.getMaxLevelForPrecision(qshape,args.getDistPrecision());
|
||||
|
||||
return new RecursivePrefixTreeFilter(
|
||||
fieldInfo.getFieldName(), grid,qshape, prefixGridScanLevel, detailLevel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.prefix;
|
||||
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.search.*;
|
||||
import org.apache.lucene.spatial.base.exception.UnsupportedSpatialOperation;
|
||||
import org.apache.lucene.spatial.base.prefix.Node;
|
||||
import org.apache.lucene.spatial.base.prefix.SpatialPrefixTree;
|
||||
import org.apache.lucene.spatial.base.query.SpatialArgs;
|
||||
import org.apache.lucene.spatial.base.query.SpatialOperation;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
import org.apache.lucene.spatial.strategy.SimpleSpatialFieldInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TermQueryPrefixTreeStrategy extends PrefixTreeStrategy {
|
||||
|
||||
public TermQueryPrefixTreeStrategy(SpatialPrefixTree grid) {
|
||||
super(grid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter makeFilter(SpatialArgs args, SimpleSpatialFieldInfo fieldInfo) {
|
||||
return new QueryWrapperFilter( makeQuery(args, fieldInfo) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query makeQuery(SpatialArgs args, SimpleSpatialFieldInfo fieldInfo) {
|
||||
if (args.getOperation() != SpatialOperation.Intersects &&
|
||||
args.getOperation() != SpatialOperation.IsWithin &&
|
||||
args.getOperation() != SpatialOperation.Overlaps ){
|
||||
// TODO -- can translate these other query types
|
||||
throw new UnsupportedSpatialOperation(args.getOperation());
|
||||
}
|
||||
Shape qshape = args.getShape();
|
||||
int detailLevel = grid.getMaxLevelForPrecision(qshape, args.getDistPrecision());
|
||||
List<Node> cells = grid.getNodes(qshape, detailLevel, false);
|
||||
|
||||
BooleanQuery booleanQuery = new BooleanQuery();
|
||||
for (Node cell : cells) {
|
||||
booleanQuery.add(new TermQuery(new Term(fieldInfo.getFieldName(), cell.getTokenString())), BooleanClause.Occur.SHOULD);
|
||||
}
|
||||
return booleanQuery;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Prefix Tree Strategy
|
||||
*/
|
||||
package org.apache.lucene.spatial.strategy.prefix;
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.util;
|
||||
|
||||
import org.apache.commons.lang.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang.builder.HashCodeBuilder;
|
||||
import org.apache.lucene.index.AtomicReaderContext;
|
||||
import org.apache.lucene.queries.function.FunctionValues;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.spatial.base.distance.DistanceCalculator;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* An implementation of the Lucene ValueSource model to support spatial relevance ranking.
|
||||
*
|
||||
*/
|
||||
public class CachedDistanceValueSource extends ValueSource {
|
||||
|
||||
private final ShapeFieldCacheProvider<Point> provider;
|
||||
private final DistanceCalculator calculator;
|
||||
private final Point from;
|
||||
|
||||
public CachedDistanceValueSource(Point from, DistanceCalculator calc, ShapeFieldCacheProvider<Point> provider) {
|
||||
this.from = from;
|
||||
this.provider = provider;
|
||||
this.calculator = calc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "DistanceValueSource("+calculator+")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionValues getValues(Map context, AtomicReaderContext readerContext) throws IOException {
|
||||
final ShapeFieldCache<Point> cache =
|
||||
provider.getCache(readerContext.reader());
|
||||
|
||||
return new FunctionValues() {
|
||||
@Override
|
||||
public float floatVal(int doc) {
|
||||
return (float) doubleVal(doc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleVal(int doc) {
|
||||
List<Point> vals = cache.getShapes( doc );
|
||||
if( vals != null ) {
|
||||
double v = calculator.distance(from, vals.get(0));
|
||||
for( int i=1; i<vals.size(); i++ ) {
|
||||
v = Math.min(v, calculator.distance(from, vals.get(i)));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
return Double.NaN; // ?? maybe max?
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(int doc) {
|
||||
return description() + "=" + floatVal(doc);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) { return false; }
|
||||
if (obj == this) { return true; }
|
||||
if (obj.getClass() != getClass()) {
|
||||
return false;
|
||||
}
|
||||
CachedDistanceValueSource rhs = (CachedDistanceValueSource) obj;
|
||||
return new EqualsBuilder()
|
||||
.append(calculator, rhs.calculator)
|
||||
.append(from, rhs.from)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder(31, 97).
|
||||
append(calculator).
|
||||
append(from).
|
||||
toHashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.util;
|
||||
|
||||
import org.apache.commons.lang.builder.HashCodeBuilder;
|
||||
import org.apache.lucene.index.AtomicReaderContext;
|
||||
import org.apache.lucene.queries.function.FunctionValues;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CachingDoubleValueSource extends ValueSource {
|
||||
|
||||
final ValueSource source;
|
||||
final Map<Integer, Double> cache;
|
||||
|
||||
public CachingDoubleValueSource( ValueSource source )
|
||||
{
|
||||
this.source = source;
|
||||
cache = new HashMap<Integer, Double>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Cached["+source.description()+"]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionValues getValues(Map context, AtomicReaderContext readerContext) throws IOException {
|
||||
final int base = readerContext.docBase;
|
||||
final FunctionValues vals = source.getValues(context,readerContext);
|
||||
return new FunctionValues() {
|
||||
|
||||
@Override
|
||||
public double doubleVal(int doc) {
|
||||
Integer key = Integer.valueOf( base+doc );
|
||||
Double v = cache.get( key );
|
||||
if( v == null ) {
|
||||
v = Double.valueOf( vals.doubleVal(doc) );
|
||||
cache.put( key, v );
|
||||
}
|
||||
return v.doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatVal(int doc) {
|
||||
return (float)doubleVal(doc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(int doc) {
|
||||
return doubleVal(doc)+"";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) { return false; }
|
||||
if (obj == this) { return true; }
|
||||
if (obj.getClass() != getClass()) {
|
||||
return false;
|
||||
}
|
||||
CachingDoubleValueSource rhs = (CachingDoubleValueSource) obj;
|
||||
return source.equals( rhs.source );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder(61, 23).
|
||||
append(source).
|
||||
toHashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.util;
|
||||
|
||||
import org.apache.lucene.analysis.NumericTokenStream;
|
||||
import org.apache.lucene.document.DoubleField;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.FieldType;
|
||||
import org.apache.lucene.index.IndexableField;
|
||||
|
||||
/**
|
||||
* Hold some of the parameters used by solr...
|
||||
*/
|
||||
public class NumericFieldInfo {
|
||||
public int precisionStep = 8; // same as solr default
|
||||
public boolean store = true;
|
||||
public boolean index = true;
|
||||
|
||||
public void setPrecisionStep( int p ) {
|
||||
precisionStep = p;
|
||||
if (precisionStep<=0 || precisionStep>=64)
|
||||
precisionStep=Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
public IndexableField createDouble( String name, double v ) {
|
||||
if (!store && !index)
|
||||
throw new IllegalArgumentException("field must be indexed or stored");
|
||||
|
||||
FieldType fieldType = new FieldType(DoubleField.TYPE);
|
||||
fieldType.setStored(store);
|
||||
fieldType.setIndexed(index);
|
||||
fieldType.setNumericPrecisionStep(precisionStep);
|
||||
return new DoubleField(name,v,fieldType);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
|
||||
public class ShapeFieldCache<T extends Shape> {
|
||||
private List<T>[] cache;
|
||||
public int defaultLength;
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public ShapeFieldCache( int length, int defaultLength ) {
|
||||
cache = new List[length];
|
||||
this.defaultLength= defaultLength;
|
||||
}
|
||||
|
||||
public void add( int docid, T s ) {
|
||||
List<T> list = cache[docid];
|
||||
if( list == null ) {
|
||||
list = cache[docid] = new ArrayList<T>(defaultLength);
|
||||
}
|
||||
list.add( s );
|
||||
}
|
||||
|
||||
public List<T> getShapes( int docid ) {
|
||||
return cache[docid];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.util;
|
||||
|
||||
import org.apache.lucene.index.*;
|
||||
import org.apache.lucene.search.DocIdSetIterator;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
|
||||
public abstract class ShapeFieldCacheProvider<T extends Shape> {
|
||||
static final Logger log = LoggerFactory.getLogger(ShapeFieldCacheProvider.class);
|
||||
|
||||
// it may be a List<T> or T
|
||||
WeakHashMap<IndexReader, ShapeFieldCache<T>> sidx = new WeakHashMap<IndexReader, ShapeFieldCache<T>>();
|
||||
|
||||
protected final int defaultSize;
|
||||
protected final String shapeField;
|
||||
|
||||
public ShapeFieldCacheProvider(String shapeField, int defaultSize) {
|
||||
this.shapeField = shapeField;
|
||||
this.defaultSize = defaultSize;
|
||||
}
|
||||
|
||||
protected abstract T readShape( BytesRef term );
|
||||
|
||||
public synchronized ShapeFieldCache<T> getCache(AtomicReader reader) throws IOException {
|
||||
ShapeFieldCache<T> idx = sidx.get(reader);
|
||||
if (idx != null) {
|
||||
return idx;
|
||||
}
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
log.info("Building Cache [" + reader.maxDoc() + "]");
|
||||
idx = new ShapeFieldCache<T>(reader.maxDoc(),defaultSize);
|
||||
int count = 0;
|
||||
DocsEnum docs = null;
|
||||
Terms terms = reader.terms(shapeField);
|
||||
TermsEnum te = null;
|
||||
if (terms != null) {
|
||||
te = terms.iterator(te);
|
||||
BytesRef term = te.next();
|
||||
while (term != null) {
|
||||
T shape = readShape(term);
|
||||
if( shape != null ) {
|
||||
docs = te.docs(null, docs, false);
|
||||
Integer docid = docs.nextDoc();
|
||||
while (docid != DocIdSetIterator.NO_MORE_DOCS) {
|
||||
idx.add( docid, shape );
|
||||
docid = docs.nextDoc();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
term = te.next();
|
||||
}
|
||||
}
|
||||
sidx.put(reader, idx);
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
log.info("Cached: [" + count + " in " + elapsed + "ms] " + idx);
|
||||
return idx;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.lucene.analysis.TokenStream;
|
||||
import org.apache.lucene.analysis.Tokenizer;
|
||||
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
|
||||
|
||||
/**
|
||||
* Put a list of strings directly into the token stream
|
||||
*/
|
||||
public final class StringListTokenizer extends TokenStream {
|
||||
|
||||
private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
|
||||
|
||||
private final Iterable<String> tokens;
|
||||
private Iterator<String> iter = null;
|
||||
|
||||
public StringListTokenizer(Iterable<String> tokens) {
|
||||
this.tokens = tokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incrementToken() {
|
||||
if (iter.hasNext()) {
|
||||
clearAttributes();
|
||||
String t = iter.next();
|
||||
termAtt.append(t);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws IOException {
|
||||
super.reset();
|
||||
iter = tokens.iterator();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.util;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.lucene.analysis.TokenFilter;
|
||||
import org.apache.lucene.analysis.TokenStream;
|
||||
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
|
||||
|
||||
public class TruncateFilter extends TokenFilter {
|
||||
|
||||
private final int maxTokenLength;
|
||||
|
||||
private final CharTermAttribute termAttr = addAttribute(CharTermAttribute.class);
|
||||
|
||||
public TruncateFilter(TokenStream in, int maxTokenLength) {
|
||||
super(in);
|
||||
this.maxTokenLength = maxTokenLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean incrementToken() throws IOException {
|
||||
if (!input.incrementToken()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (termAttr.length() > maxTokenLength) {
|
||||
termAttr.setLength(maxTokenLength);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.util;
|
||||
|
||||
import org.apache.lucene.index.AtomicReaderContext;
|
||||
import org.apache.lucene.queries.function.FunctionValues;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.search.DocIdSet;
|
||||
import org.apache.lucene.search.Filter;
|
||||
import org.apache.lucene.search.FilteredDocIdSet;
|
||||
import org.apache.lucene.util.Bits;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ValueSourceFilter extends Filter {
|
||||
|
||||
final Filter startingFilter;
|
||||
final ValueSource source;
|
||||
final double min;
|
||||
final double max;
|
||||
|
||||
public ValueSourceFilter( Filter startingFilter, ValueSource source, double min, double max )
|
||||
{
|
||||
if (startingFilter == null) {
|
||||
throw new IllegalArgumentException("please provide a non-null startingFilter; you can use QueryWrapperFilter(MatchAllDocsQuery) as a no-op filter");
|
||||
}
|
||||
this.startingFilter = startingFilter;
|
||||
this.source = source;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocIdSet getDocIdSet(AtomicReaderContext context, Bits acceptDocs) throws IOException {
|
||||
final FunctionValues values = source.getValues( null, context );
|
||||
return new FilteredDocIdSet(startingFilter.getDocIdSet(context, acceptDocs)) {
|
||||
@Override
|
||||
public boolean match(int doc) {
|
||||
double val = values.doubleVal( doc );
|
||||
return val > min && val < max;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.vector;
|
||||
|
||||
import org.apache.commons.lang.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang.builder.HashCodeBuilder;
|
||||
import org.apache.lucene.index.AtomicReader;
|
||||
import org.apache.lucene.index.AtomicReaderContext;
|
||||
import org.apache.lucene.queries.function.FunctionValues;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.search.FieldCache;
|
||||
import org.apache.lucene.search.FieldCache.DoubleParser;
|
||||
import org.apache.lucene.spatial.base.distance.DistanceCalculator;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.base.shape.simple.PointImpl;
|
||||
import org.apache.lucene.util.Bits;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* An implementation of the Lucene ValueSource model to support spatial relevance ranking.
|
||||
*
|
||||
*/
|
||||
public class DistanceValueSource extends ValueSource {
|
||||
|
||||
private final TwoDoublesFieldInfo fields;
|
||||
private final DistanceCalculator calculator;
|
||||
private final Point from;
|
||||
private final DoubleParser parser;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public DistanceValueSource(Point from, DistanceCalculator calc, TwoDoublesFieldInfo fields, DoubleParser parser) {
|
||||
this.from = from;
|
||||
this.fields = fields;
|
||||
this.calculator = calc;
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ValueSource description.
|
||||
*/
|
||||
@Override
|
||||
public String description() {
|
||||
return "DistanceValueSource("+calculator+")";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the FunctionValues used by the function query.
|
||||
*/
|
||||
@Override
|
||||
public FunctionValues getValues(Map context, AtomicReaderContext readerContext) throws IOException {
|
||||
AtomicReader reader = readerContext.reader();
|
||||
|
||||
final double[] ptX = FieldCache.DEFAULT.getDoubles(reader, fields.getFieldNameX(), true);
|
||||
final double[] ptY = FieldCache.DEFAULT.getDoubles(reader, fields.getFieldNameY(), true);
|
||||
final Bits validX = FieldCache.DEFAULT.getDocsWithField(reader, fields.getFieldNameX());
|
||||
final Bits validY = FieldCache.DEFAULT.getDocsWithField(reader, fields.getFieldNameY());
|
||||
|
||||
return new FunctionValues() {
|
||||
@Override
|
||||
public float floatVal(int doc) {
|
||||
return (float) doubleVal(doc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleVal(int doc) {
|
||||
// make sure it has minX and area
|
||||
if (validX.get(doc) && validY.get(doc)) {
|
||||
PointImpl pt = new PointImpl( ptX[doc], ptY[doc] );
|
||||
return calculator.distance(from, pt);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(int doc) {
|
||||
return description() + "=" + floatVal(doc);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this ValueSource is equal to another.
|
||||
* @param obj the ValueSource to compare
|
||||
* @return <code>true</code> if the two objects are based upon the same query envelope
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) { return false; }
|
||||
if (obj == this) { return true; }
|
||||
if (obj.getClass() != getClass()) {
|
||||
return false;
|
||||
}
|
||||
DistanceValueSource rhs = (DistanceValueSource) obj;
|
||||
return new EqualsBuilder()
|
||||
.append(calculator, rhs.calculator)
|
||||
.append(from, rhs.from)
|
||||
.append(fields, rhs.fields)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder(59, 7).
|
||||
append(calculator).
|
||||
append(from).
|
||||
append(fields).
|
||||
toHashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.vector;
|
||||
|
||||
import org.apache.lucene.spatial.strategy.SpatialFieldInfo;
|
||||
|
||||
public class TwoDoublesFieldInfo implements SpatialFieldInfo {
|
||||
|
||||
public static final String SUFFIX_X = "__x";
|
||||
public static final String SUFFIX_Y = "__y";
|
||||
|
||||
private final String fieldName;
|
||||
private final String fieldNameX;
|
||||
private final String fieldNameY;
|
||||
|
||||
public TwoDoublesFieldInfo(String fieldNamePrefix) {
|
||||
fieldName = fieldNamePrefix;
|
||||
fieldNameX = fieldNamePrefix + SUFFIX_X;
|
||||
fieldNameY = fieldNamePrefix + SUFFIX_Y;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
public String getFieldNameX() {
|
||||
return fieldNameX;
|
||||
}
|
||||
|
||||
public String getFieldNameY() {
|
||||
return fieldNameY;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.vector;
|
||||
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.FieldType;
|
||||
import org.apache.lucene.index.IndexableField;
|
||||
import org.apache.lucene.queries.function.FunctionQuery;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.search.*;
|
||||
import org.apache.lucene.search.FieldCache.DoubleParser;
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.exception.InvalidShapeException;
|
||||
import org.apache.lucene.spatial.base.exception.UnsupportedSpatialOperation;
|
||||
import org.apache.lucene.spatial.base.query.SpatialArgs;
|
||||
import org.apache.lucene.spatial.base.query.SpatialOperation;
|
||||
import org.apache.lucene.spatial.base.shape.Circle;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
import org.apache.lucene.spatial.strategy.SpatialStrategy;
|
||||
import org.apache.lucene.spatial.strategy.util.CachingDoubleValueSource;
|
||||
import org.apache.lucene.spatial.strategy.util.NumericFieldInfo;
|
||||
import org.apache.lucene.spatial.strategy.util.ValueSourceFilter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
public class TwoDoublesStrategy extends SpatialStrategy<TwoDoublesFieldInfo> {
|
||||
|
||||
static final Logger log = LoggerFactory.getLogger(TwoDoublesStrategy.class);
|
||||
|
||||
private final NumericFieldInfo finfo;
|
||||
private final DoubleParser parser;
|
||||
|
||||
public TwoDoublesStrategy(SpatialContext ctx, NumericFieldInfo finfo, DoubleParser parser) {
|
||||
super(ctx);
|
||||
this.finfo = finfo;
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPolyField() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexableField[] createFields(TwoDoublesFieldInfo fieldInfo,
|
||||
Shape shape, boolean index, boolean store) {
|
||||
if( shape instanceof Point ) {
|
||||
Point point = (Point)shape;
|
||||
|
||||
IndexableField[] f = new IndexableField[(index ? 2 : 0) + (store ? 1 : 0)];
|
||||
if (index) {
|
||||
f[0] = finfo.createDouble( fieldInfo.getFieldNameX(), point.getX() );
|
||||
f[1] = finfo.createDouble( fieldInfo.getFieldNameY(), point.getY() );
|
||||
}
|
||||
if(store) {
|
||||
FieldType customType = new FieldType();
|
||||
customType.setStored(true);
|
||||
f[f.length-1] = new Field( fieldInfo.getFieldName(), ctx.toString( shape ), customType );
|
||||
}
|
||||
return f;
|
||||
}
|
||||
if( !ignoreIncompatibleGeometry ) {
|
||||
throw new IllegalArgumentException( "TwoDoublesStrategy can not index: "+shape );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexableField createField(TwoDoublesFieldInfo indexInfo, Shape shape,
|
||||
boolean index, boolean store) {
|
||||
throw new UnsupportedOperationException("Point is poly field");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueSource makeValueSource(SpatialArgs args, TwoDoublesFieldInfo fieldInfo) {
|
||||
Point p = args.getShape().getCenter();
|
||||
return new DistanceValueSource(p, ctx.getDistCalc(), fieldInfo, parser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter makeFilter(SpatialArgs args, TwoDoublesFieldInfo fieldInfo) {
|
||||
if( args.getShape() instanceof Circle) {
|
||||
if( SpatialOperation.is( args.getOperation(),
|
||||
SpatialOperation.Intersects,
|
||||
SpatialOperation.IsWithin )) {
|
||||
Circle circle = (Circle)args.getShape();
|
||||
Query bbox = makeWithin(circle.getBoundingBox(), fieldInfo);
|
||||
|
||||
// Make the ValueSource
|
||||
ValueSource valueSource = makeValueSource(args, fieldInfo);
|
||||
|
||||
return new ValueSourceFilter(
|
||||
new QueryWrapperFilter( bbox ), valueSource, 0, circle.getDistance() );
|
||||
}
|
||||
}
|
||||
return new QueryWrapperFilter( makeQuery(args, fieldInfo) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query makeQuery(SpatialArgs args, TwoDoublesFieldInfo fieldInfo) {
|
||||
// For starters, just limit the bbox
|
||||
Shape shape = args.getShape();
|
||||
if (!(shape instanceof Rectangle)) {
|
||||
throw new InvalidShapeException("A rectangle is the only supported at this time, not "+shape.getClass());//TODO
|
||||
}
|
||||
Rectangle bbox = (Rectangle) shape;
|
||||
if (bbox.getCrossesDateLine()) {
|
||||
throw new UnsupportedOperationException( "Crossing dateline not yet supported" );
|
||||
}
|
||||
|
||||
ValueSource valueSource = null;
|
||||
|
||||
Query spatial = null;
|
||||
SpatialOperation op = args.getOperation();
|
||||
|
||||
if( SpatialOperation.is( op,
|
||||
SpatialOperation.BBoxWithin,
|
||||
SpatialOperation.BBoxIntersects ) ) {
|
||||
spatial = makeWithin(bbox, fieldInfo);
|
||||
}
|
||||
else if( SpatialOperation.is( op,
|
||||
SpatialOperation.Intersects,
|
||||
SpatialOperation.IsWithin ) ) {
|
||||
spatial = makeWithin(bbox, fieldInfo);
|
||||
if( args.getShape() instanceof Circle) {
|
||||
Circle circle = (Circle)args.getShape();
|
||||
|
||||
// Make the ValueSource
|
||||
valueSource = makeValueSource(args, fieldInfo);
|
||||
|
||||
ValueSourceFilter vsf = new ValueSourceFilter(
|
||||
new QueryWrapperFilter( spatial ), valueSource, 0, circle.getDistance() );
|
||||
|
||||
spatial = new FilteredQuery( new MatchAllDocsQuery(), vsf );
|
||||
}
|
||||
}
|
||||
else if( op == SpatialOperation.IsDisjointTo ) {
|
||||
spatial = makeDisjoint(bbox, fieldInfo);
|
||||
}
|
||||
|
||||
if( spatial == null ) {
|
||||
throw new UnsupportedSpatialOperation(args.getOperation());
|
||||
}
|
||||
|
||||
try {
|
||||
if( valueSource != null ) {
|
||||
valueSource = new CachingDoubleValueSource(valueSource);
|
||||
}
|
||||
else {
|
||||
valueSource = makeValueSource(args, fieldInfo);
|
||||
}
|
||||
Query spatialRankingQuery = new FunctionQuery(valueSource);
|
||||
BooleanQuery bq = new BooleanQuery();
|
||||
bq.add(spatial,BooleanClause.Occur.MUST);
|
||||
bq.add(spatialRankingQuery,BooleanClause.Occur.MUST);
|
||||
return bq;
|
||||
} catch(Exception ex) {
|
||||
log.warn("error making score", ex);
|
||||
}
|
||||
return spatial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a query to retrieve documents that fully contain the input envelope.
|
||||
* @return the spatial query
|
||||
*/
|
||||
private Query makeWithin(Rectangle bbox, TwoDoublesFieldInfo fieldInfo) {
|
||||
Query qX = NumericRangeQuery.newDoubleRange(
|
||||
fieldInfo.getFieldNameX(),
|
||||
finfo.precisionStep,
|
||||
bbox.getMinX(),
|
||||
bbox.getMaxX(),
|
||||
true,
|
||||
true);
|
||||
Query qY = NumericRangeQuery.newDoubleRange(
|
||||
fieldInfo.getFieldNameY(),
|
||||
finfo.precisionStep,
|
||||
bbox.getMinY(),
|
||||
bbox.getMaxY(),
|
||||
true,
|
||||
true);
|
||||
|
||||
BooleanQuery bq = new BooleanQuery();
|
||||
bq.add(qX,BooleanClause.Occur.MUST);
|
||||
bq.add(qY,BooleanClause.Occur.MUST);
|
||||
return bq;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a query to retrieve documents that fully contain the input envelope.
|
||||
* @return the spatial query
|
||||
*/
|
||||
Query makeDisjoint(Rectangle bbox, TwoDoublesFieldInfo fieldInfo) {
|
||||
Query qX = NumericRangeQuery.newDoubleRange(
|
||||
fieldInfo.getFieldNameX(),
|
||||
finfo.precisionStep,
|
||||
bbox.getMinX(),
|
||||
bbox.getMaxX(),
|
||||
true,
|
||||
true);
|
||||
Query qY = NumericRangeQuery.newDoubleRange(
|
||||
fieldInfo.getFieldNameY(),
|
||||
finfo.precisionStep,
|
||||
bbox.getMinY(),
|
||||
bbox.getMaxY(),
|
||||
true,
|
||||
true);
|
||||
|
||||
BooleanQuery bq = new BooleanQuery();
|
||||
bq.add(qX,BooleanClause.Occur.MUST_NOT);
|
||||
bq.add(qY,BooleanClause.Occur.MUST_NOT);
|
||||
return bq;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial;
|
||||
|
||||
/**
|
||||
* Reads "tests.seed" system property to initialized a global final constant.
|
||||
* @author David Smiley - dsmiley@mitre.org
|
||||
*/
|
||||
public class RandomSeed {
|
||||
private static final long _seed;
|
||||
static {
|
||||
_seed = Long.parseLong(System.getProperty("tests.seed", "" + System.currentTimeMillis()));
|
||||
System.out.println("tests.seed="+_seed);
|
||||
}
|
||||
public static long seed() {
|
||||
return _seed;
|
||||
}
|
||||
private RandomSeed() {}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial;
|
||||
|
||||
public class SpatialMatchConcern {
|
||||
public final boolean orderIsImportant;
|
||||
public final boolean resultsAreSuperset; // if the strategy can not give exact answers, but used to limit results
|
||||
|
||||
private SpatialMatchConcern( boolean order, boolean superset ) {
|
||||
this.orderIsImportant = order;
|
||||
this.resultsAreSuperset = superset;
|
||||
}
|
||||
|
||||
public static final SpatialMatchConcern EXACT = new SpatialMatchConcern( true, false );
|
||||
public static final SpatialMatchConcern FILTER = new SpatialMatchConcern( false, false );
|
||||
public static final SpatialMatchConcern SUPERSET = new SpatialMatchConcern( false, true );
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial;
|
||||
|
||||
import org.apache.lucene.analysis.core.WhitespaceAnalyzer;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.index.DirectoryReader;
|
||||
import org.apache.lucene.index.IndexWriter;
|
||||
import org.apache.lucene.index.IndexWriterConfig;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.ScoreDoc;
|
||||
import org.apache.lucene.search.TopDocs;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class SpatialTestCase extends LuceneTestCase {
|
||||
|
||||
private DirectoryReader indexReader;
|
||||
private IndexWriter indexWriter;
|
||||
private Directory directory;
|
||||
private IndexSearcher indexSearcher;
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
directory = newDirectory();
|
||||
|
||||
IndexWriterConfig writerConfig = newIndexWriterConfig(random, TEST_VERSION_CURRENT, new WhitespaceAnalyzer(TEST_VERSION_CURRENT));
|
||||
indexWriter = new IndexWriter(directory, writerConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (indexWriter != null) {
|
||||
indexWriter.close();
|
||||
}
|
||||
if (indexReader != null) {
|
||||
indexReader.close();
|
||||
}
|
||||
if (directory != null) {
|
||||
directory.close();
|
||||
}
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
// ================================================= Helper Methods ================================================
|
||||
|
||||
protected void addDocument(Document doc) throws IOException {
|
||||
indexWriter.addDocument(doc);
|
||||
}
|
||||
|
||||
protected void addDocumentsAndCommit(List<Document> documents) throws IOException {
|
||||
for (Document document : documents) {
|
||||
indexWriter.addDocument(document);
|
||||
}
|
||||
commit();
|
||||
}
|
||||
|
||||
protected void deleteAll() throws IOException {
|
||||
indexWriter.deleteAll();
|
||||
}
|
||||
|
||||
protected void commit() throws IOException {
|
||||
indexWriter.commit();
|
||||
if (indexReader == null) {
|
||||
indexReader = DirectoryReader.open(directory);
|
||||
} else {
|
||||
indexReader = DirectoryReader.openIfChanged(indexReader);
|
||||
}
|
||||
indexSearcher = newSearcher(indexReader);
|
||||
}
|
||||
|
||||
protected void verifyDocumentsIndexed(int numDocs) {
|
||||
assertEquals(numDocs, indexReader.numDocs());
|
||||
}
|
||||
|
||||
protected SearchResults executeQuery(Query query, int numDocs) {
|
||||
try {
|
||||
TopDocs topDocs = indexSearcher.search(query, numDocs);
|
||||
|
||||
List<SearchResult> results = new ArrayList<SearchResult>();
|
||||
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
|
||||
results.add(new SearchResult(scoreDoc.score, indexSearcher.doc(scoreDoc.doc)));
|
||||
}
|
||||
return new SearchResults(topDocs.totalHits, results);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("IOException thrown while executing query", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================= Inner Classes =================================================
|
||||
|
||||
protected static class SearchResults {
|
||||
|
||||
public int numFound;
|
||||
public List<SearchResult> results;
|
||||
|
||||
public SearchResults(int numFound, List<SearchResult> results) {
|
||||
this.numFound = numFound;
|
||||
this.results = results;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class SearchResult {
|
||||
|
||||
public float score;
|
||||
public Document document;
|
||||
|
||||
public SearchResult(float score, Document document) {
|
||||
this.score = score;
|
||||
this.document = document;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.io.LineReader;
|
||||
import org.apache.lucene.spatial.base.query.SpatialArgs;
|
||||
import org.apache.lucene.spatial.base.query.SpatialArgsParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* Helper class to execute queries
|
||||
*/
|
||||
public class SpatialTestQuery {
|
||||
public String testname;
|
||||
public String line;
|
||||
public int lineNumber = -1;
|
||||
public SpatialArgs args;
|
||||
public List<String> ids = new ArrayList<String>();
|
||||
|
||||
/**
|
||||
* Get Test Queries
|
||||
*/
|
||||
public static Iterator<SpatialTestQuery> getTestQueries(
|
||||
final SpatialArgsParser parser,
|
||||
final SpatialContext ctx,
|
||||
final String name,
|
||||
final InputStream in ) throws IOException {
|
||||
return new LineReader<SpatialTestQuery>(new InputStreamReader(in,"UTF-8")) {
|
||||
|
||||
@Override
|
||||
public SpatialTestQuery parseLine(String line) {
|
||||
SpatialTestQuery test = new SpatialTestQuery();
|
||||
test.line = line;
|
||||
test.lineNumber = getLineNumber();
|
||||
|
||||
try {
|
||||
// skip a comment
|
||||
if( line.startsWith( "[" ) ) {
|
||||
int idx = line.indexOf( ']' );
|
||||
if( idx > 0 ) {
|
||||
line = line.substring( idx+1 );
|
||||
}
|
||||
}
|
||||
|
||||
int idx = line.indexOf('@');
|
||||
StringTokenizer st = new StringTokenizer(line.substring(0, idx));
|
||||
while (st.hasMoreTokens()) {
|
||||
test.ids.add(st.nextToken().trim());
|
||||
}
|
||||
test.args = parser.parse(line.substring(idx + 1).trim(), ctx);
|
||||
return test;
|
||||
}
|
||||
catch( Exception ex ) {
|
||||
throw new RuntimeException( "invalid query line: "+test.line, ex );
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return line;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial;
|
||||
|
||||
|
||||
import junit.framework.Assert;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.index.IndexableField;
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.io.sample.SampleData;
|
||||
import org.apache.lucene.spatial.base.io.sample.SampleDataReader;
|
||||
import org.apache.lucene.spatial.base.query.SpatialArgsParser;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
import org.apache.lucene.spatial.strategy.SpatialFieldInfo;
|
||||
import org.apache.lucene.spatial.strategy.SpatialStrategy;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
|
||||
public abstract class StrategyTestCase<T extends SpatialFieldInfo> extends SpatialTestCase {
|
||||
|
||||
public static final String DATA_STATES_POLY = "states-poly.txt";
|
||||
public static final String DATA_STATES_BBOX = "states-bbox.txt";
|
||||
public static final String DATA_COUNTRIES_POLY = "countries-poly.txt";
|
||||
public static final String DATA_COUNTRIES_BBOX = "countries-bbox.txt";
|
||||
public static final String DATA_WORLD_CITIES_POINTS = "world-cities-points.txt";
|
||||
|
||||
public static final String QTEST_States_IsWithin_BBox = "states-IsWithin-BBox.txt";
|
||||
public static final String QTEST_States_Intersects_BBox = "states-Intersects-BBox.txt";
|
||||
|
||||
public static final String QTEST_Cities_IsWithin_BBox = "cities-IsWithin-BBox.txt";
|
||||
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
protected final SpatialArgsParser argsParser = new SpatialArgsParser();
|
||||
|
||||
protected SpatialStrategy<T> strategy;
|
||||
protected SpatialContext ctx;
|
||||
protected T fieldInfo;
|
||||
protected boolean storeShape = true;
|
||||
|
||||
protected void executeQueries(SpatialMatchConcern concern, String... testQueryFile) throws IOException {
|
||||
log.info("testing queried for strategy "+strategy);
|
||||
for( String path : testQueryFile ) {
|
||||
Iterator<SpatialTestQuery> testQueryIterator = getTestQueries(path, ctx);
|
||||
runTestQueries(testQueryIterator, concern);
|
||||
}
|
||||
}
|
||||
|
||||
protected void getAddAndVerifyIndexedDocuments(String testDataFile) throws IOException {
|
||||
List<Document> testDocuments = getDocuments(testDataFile);
|
||||
addDocumentsAndCommit(testDocuments);
|
||||
verifyDocumentsIndexed(testDocuments.size());
|
||||
}
|
||||
|
||||
protected List<Document> getDocuments(String testDataFile) throws IOException {
|
||||
Iterator<SampleData> sampleData = getSampleData(testDataFile);
|
||||
List<Document> documents = new ArrayList<Document>();
|
||||
while (sampleData.hasNext()) {
|
||||
SampleData data = sampleData.next();
|
||||
Document document = new Document();
|
||||
document.add(new Field("id", data.id, StringField.TYPE_STORED));
|
||||
document.add(new Field("name", data.name, StringField.TYPE_STORED));
|
||||
Shape shape = ctx.readShape(data.shape);
|
||||
for (IndexableField f : strategy.createFields(fieldInfo, shape, true, storeShape)) {
|
||||
if( f != null ) { // null if incompatibleGeometry && ignore
|
||||
document.add(f);
|
||||
}
|
||||
}
|
||||
documents.add(document);
|
||||
}
|
||||
return documents;
|
||||
}
|
||||
|
||||
protected Iterator<SampleData> getSampleData(String testDataFile) throws IOException {
|
||||
return new SampleDataReader(
|
||||
getClass().getClassLoader().getResourceAsStream("data/"+testDataFile) );
|
||||
}
|
||||
|
||||
protected Iterator<SpatialTestQuery> getTestQueries(String testQueryFile, SpatialContext ctx) throws IOException {
|
||||
InputStream in = getClass().getClassLoader().getResourceAsStream(testQueryFile);
|
||||
return SpatialTestQuery.getTestQueries(
|
||||
argsParser, ctx, testQueryFile, in );
|
||||
}
|
||||
|
||||
public void runTestQueries(
|
||||
Iterator<SpatialTestQuery> queries,
|
||||
SpatialMatchConcern concern) {
|
||||
while (queries.hasNext()) {
|
||||
SpatialTestQuery q = queries.next();
|
||||
|
||||
String msg = q.line; //"Query: " + q.args.toString(ctx);
|
||||
SearchResults got = executeQuery(strategy.makeQuery(q.args, fieldInfo), 100);
|
||||
if (concern.orderIsImportant) {
|
||||
Iterator<String> ids = q.ids.iterator();
|
||||
for (SearchResult r : got.results) {
|
||||
String id = r.document.get("id");
|
||||
Assert.assertEquals( "out of order: " + msg, ids.next(), id);
|
||||
}
|
||||
if (ids.hasNext()) {
|
||||
Assert.fail(msg + " :: expect more results then we got: " + ids.next());
|
||||
}
|
||||
} else {
|
||||
// We are looking at how the results overlap
|
||||
if( concern.resultsAreSuperset ) {
|
||||
Set<String> found = new HashSet<String>();
|
||||
for (SearchResult r : got.results) {
|
||||
found.add(r.document.get("id"));
|
||||
}
|
||||
for( String s : q.ids ) {
|
||||
if( !found.contains( s ) ) {
|
||||
Assert.fail( "Results are mising id: "+s + " :: " + found );
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
List<String> found = new ArrayList<String>();
|
||||
for (SearchResult r : got.results) {
|
||||
found.add(r.document.get("id"));
|
||||
}
|
||||
|
||||
// sort both so that the order is not important
|
||||
Collections.sort(q.ids);
|
||||
Collections.sort(found);
|
||||
Assert.assertEquals(msg, q.ids.toString(), found.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.context.simple.SimpleSpatialContext;
|
||||
import org.apache.lucene.spatial.base.query.SpatialArgsParser;
|
||||
import org.apache.lucene.spatial.base.query.SpatialOperation;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Make sure we are reading the tests as expected
|
||||
*/
|
||||
public class TestTestFramework {
|
||||
|
||||
@Test
|
||||
public void testQueries() throws IOException {
|
||||
String name = StrategyTestCase.QTEST_Cities_IsWithin_BBox;
|
||||
|
||||
InputStream in = getClass().getClassLoader().getResourceAsStream(name);
|
||||
SpatialContext ctx = SimpleSpatialContext.GEO_KM;
|
||||
Iterator<SpatialTestQuery> iter = SpatialTestQuery.getTestQueries(
|
||||
new SpatialArgsParser(), ctx, name, in );
|
||||
List<SpatialTestQuery> tests = new ArrayList<SpatialTestQuery>();
|
||||
while( iter.hasNext() ) {
|
||||
tests.add( iter.next() );
|
||||
}
|
||||
Assert.assertEquals( 3, tests.size() );
|
||||
|
||||
SpatialTestQuery sf = tests.get(0);
|
||||
// assert
|
||||
Assert.assertEquals( 1, sf.ids.size() );
|
||||
Assert.assertTrue( sf.ids.get(0).equals( "G5391959" ) );
|
||||
Assert.assertTrue( sf.args.getShape() instanceof Rectangle);
|
||||
Assert.assertEquals( SpatialOperation.IsWithin, sf.args.getOperation() );
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.context;
|
||||
|
||||
import org.apache.lucene.spatial.base.query.SpatialArgs;
|
||||
import org.apache.lucene.spatial.base.query.SpatialArgsParser;
|
||||
import org.apache.lucene.spatial.base.query.SpatialOperation;
|
||||
import org.apache.lucene.spatial.base.shape.MultiShape;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
import org.apache.lucene.spatial.base.shape.simple.CircleImpl;
|
||||
import org.apache.lucene.spatial.base.shape.simple.PointImpl;
|
||||
import org.apache.lucene.spatial.base.shape.simple.RectangleImpl;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public abstract class BaseSpatialContextTestCase {
|
||||
|
||||
protected abstract SpatialContext getSpatialContext();
|
||||
|
||||
public static void checkArgParser(SpatialContext ctx) {
|
||||
SpatialArgsParser parser = new SpatialArgsParser();
|
||||
|
||||
String arg = SpatialOperation.IsWithin + "(-10 -20 10 20)";
|
||||
SpatialArgs out = parser.parse(arg, ctx);
|
||||
assertEquals(SpatialOperation.IsWithin, out.getOperation());
|
||||
Rectangle bounds = (Rectangle) out.getShape();
|
||||
assertEquals(-10.0, bounds.getMinX(), 0D);
|
||||
assertEquals(10.0, bounds.getMaxX(), 0D);
|
||||
|
||||
// Disjoint should not be scored
|
||||
arg = SpatialOperation.IsDisjointTo + " (-10 10 -20 20)";
|
||||
out = parser.parse(arg, ctx);
|
||||
assertEquals(SpatialOperation.IsDisjointTo, out.getOperation());
|
||||
|
||||
try {
|
||||
parser.parse(SpatialOperation.IsDisjointTo + "[ ]", ctx);
|
||||
fail("spatial operations need args");
|
||||
}
|
||||
catch (Exception ex) {//expected
|
||||
}
|
||||
|
||||
try {
|
||||
parser.parse("XXXX(-10 10 -20 20)", ctx);
|
||||
fail("unknown operation!");
|
||||
}
|
||||
catch (Exception ex) {//expected
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkShapesImplementEquals( Class[] classes ) {
|
||||
|
||||
for( Class clazz : classes ) {
|
||||
try {
|
||||
clazz.getDeclaredMethod( "equals", Object.class );
|
||||
} catch (Exception e) {
|
||||
Assert.fail( "Shape needs to define 'equals' : " + clazz.getName() );
|
||||
}
|
||||
try {
|
||||
clazz.getDeclaredMethod( "hashCode" );
|
||||
} catch (Exception e) {
|
||||
Assert.fail( "Shape needs to define 'hashCode' : " + clazz.getName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static interface WriteReader {
|
||||
Shape writeThenRead( Shape s ) throws IOException;
|
||||
}
|
||||
|
||||
public static void checkBasicShapeIO( SpatialContext ctx, WriteReader help ) throws Exception {
|
||||
|
||||
// Simple Point
|
||||
Shape s = ctx.readShape("10 20");
|
||||
assertEquals(s,ctx.readShape("20,10"));//check comma for y,x format
|
||||
assertEquals(s,ctx.readShape("20, 10"));//test space
|
||||
Point p = (Point) s;
|
||||
assertEquals(10.0, p.getX(), 0D);
|
||||
assertEquals(20.0, p.getY(), 0D);
|
||||
p = (Point) help.writeThenRead(s);
|
||||
assertEquals(10.0, p.getX(), 0D);
|
||||
assertEquals(20.0, p.getY(), 0D);
|
||||
Assert.assertFalse(s.hasArea());
|
||||
|
||||
// BBOX
|
||||
s = ctx.readShape("-10 -20 10 20");
|
||||
Rectangle b = (Rectangle) s;
|
||||
assertEquals(-10.0, b.getMinX(), 0D);
|
||||
assertEquals(-20.0, b.getMinY(), 0D);
|
||||
assertEquals(10.0, b.getMaxX(), 0D);
|
||||
assertEquals(20.0, b.getMaxY(), 0D);
|
||||
b = (Rectangle) help.writeThenRead(s);
|
||||
assertEquals(-10.0, b.getMinX(), 0D);
|
||||
assertEquals(-20.0, b.getMinY(), 0D);
|
||||
assertEquals(10.0, b.getMaxX(), 0D);
|
||||
assertEquals(20.0, b.getMaxY(), 0D);
|
||||
Assert.assertTrue(s.hasArea());
|
||||
|
||||
// Point/Distance
|
||||
s = ctx.readShape("Circle( 1.23 4.56 distance=7.89)");
|
||||
CircleImpl circle = (CircleImpl)s;
|
||||
assertEquals(1.23, circle.getCenter().getX(), 0D);
|
||||
assertEquals(4.56, circle.getCenter().getY(), 0D);
|
||||
assertEquals(7.89, circle.getDistance(), 0D);
|
||||
Assert.assertTrue(s.hasArea());
|
||||
|
||||
Shape s2 = ctx.readShape("Circle( 4.56,1.23 d=7.89 )"); // use lat,lon and use 'd' abbreviation
|
||||
assertEquals(s,s2);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
// Actual tests
|
||||
//--------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
public void testArgsParser() throws Exception {
|
||||
checkArgParser( getSpatialContext() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImplementsEqualsAndHash() throws Exception {
|
||||
checkShapesImplementEquals( new Class[] {
|
||||
PointImpl.class,
|
||||
CircleImpl.class,
|
||||
RectangleImpl.class,
|
||||
MultiShape.class,
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleShapeIO() throws Exception {
|
||||
final SpatialContext io = getSpatialContext();
|
||||
checkBasicShapeIO( io, new WriteReader() {
|
||||
@Override
|
||||
public Shape writeThenRead(Shape s) {
|
||||
String buff = io.toString( s );
|
||||
return io.readShape( buff );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Looking for more tests? Shapes are tested in TestShapes2D.
|
||||
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package org.apache.lucene.spatial.base.context;/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import org.apache.lucene.spatial.base.context.simple.SimpleSpatialContext;
|
||||
import org.apache.lucene.spatial.base.distance.DistanceUnits;
|
||||
import org.apache.lucene.spatial.base.distance.CartesianDistCalc;
|
||||
import org.apache.lucene.spatial.base.distance.GeodesicSphereDistCalc;
|
||||
import org.apache.lucene.spatial.base.shape.simple.RectangleImpl;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author dsmiley
|
||||
*/
|
||||
public class SpatialContextFactoryTest {
|
||||
public static final String PROP = "SpatialContextFactory";
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
System.getProperties().remove(PROP);
|
||||
}
|
||||
|
||||
private SpatialContext call(String... argsStr) {
|
||||
Map<String,String> args = new HashMap<String,String>();
|
||||
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 s = SimpleSpatialContext.GEO_KM;
|
||||
SpatialContext t = call();//default
|
||||
assertEquals(s.getClass(),t.getClass());
|
||||
assertEquals(s.getUnits(),t.getUnits());
|
||||
assertEquals(s.getDistCalc(),t.getDistCalc());
|
||||
assertEquals(s.getWorldBounds(),t.getWorldBounds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustom() {
|
||||
SpatialContext sc = call("units","u");
|
||||
assertEquals(DistanceUnits.CARTESIAN,sc.getUnits());
|
||||
assertEquals(new CartesianDistCalc(),sc.getDistCalc());
|
||||
|
||||
sc = call("units","u",
|
||||
"distCalculator","cartesian^2",
|
||||
"worldBounds","-100 0 75 200");//West South East North
|
||||
assertEquals(new CartesianDistCalc(true),sc.getDistCalc());
|
||||
assertEquals(new RectangleImpl(-100,75,0,200),sc.getWorldBounds());
|
||||
|
||||
sc = call("units","miles",
|
||||
"distCalculator","lawOfCosines");
|
||||
assertEquals(DistanceUnits.MILES,sc.getUnits());
|
||||
assertEquals(new GeodesicSphereDistCalc.LawOfCosines(sc.getUnits().earthRadius()),
|
||||
sc.getDistCalc());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSystemPropertyLookup() {
|
||||
System.setProperty(PROP,DSCF.class.getName());
|
||||
assertEquals(DistanceUnits.CARTESIAN,call().getUnits());//DSCF returns this
|
||||
}
|
||||
|
||||
public static class DSCF extends SpatialContextFactory {
|
||||
|
||||
@Override
|
||||
protected SpatialContext newSpatialContext() {
|
||||
return new SimpleSpatialContext(DistanceUnits.CARTESIAN);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.context;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.simple.SimpleSpatialContext;
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
public class SpatialContextTestCase extends BaseSpatialContextTestCase {
|
||||
|
||||
@Override
|
||||
protected SpatialContext getSpatialContext() {
|
||||
return SimpleSpatialContext.GEO_KM;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.distance;
|
||||
|
||||
import org.apache.lucene.spatial.RandomSeed;
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.context.simple.SimpleSpatialContext;
|
||||
import org.apache.lucene.spatial.base.shape.SpatialRelation;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author David Smiley - dsmiley@mitre.org
|
||||
*/
|
||||
public class TestDistances {
|
||||
|
||||
private final Random random = new Random(RandomSeed.seed());
|
||||
//NOTE! These are sometimes modified by tests.
|
||||
private SpatialContext ctx;
|
||||
private double EPS;
|
||||
|
||||
@Before
|
||||
public void beforeTest() {
|
||||
ctx = new SimpleSpatialContext(DistanceUnits.KILOMETERS);
|
||||
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)),3);
|
||||
assertEquals(11100, dc().distance(ctr, pLL(10, -160)),3);
|
||||
|
||||
assertEquals(314.40338, dc().distance(pLL(1, 2), pLL(3, 4)),EPS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCalcBoxByDistFromPt() {
|
||||
//first test regression
|
||||
{
|
||||
double d = 6894.1;
|
||||
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);
|
||||
assertEquals(SpatialRelation.CONTAINS,r.relate(pTgt, ctx));
|
||||
checkBBox(pCtr,d);
|
||||
}
|
||||
|
||||
assertEquals("0 dist, horiz line",
|
||||
-45,dc().calcBoxByDistFromPtHorizAxis(ctx.makePoint(-180,-45),0,ctx),0);
|
||||
|
||||
double MAXDIST = ctx.getUnits().earthCircumference() / 2;
|
||||
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),ctx.getDistCalc().degreesToDistance(20));
|
||||
{
|
||||
double d = 0.010;//10m
|
||||
checkBBox(ctx.makePoint(0,90-ctx.getDistCalc().distanceToDegrees(d+0.001)),d);
|
||||
}
|
||||
|
||||
for (int T = 0; T < 100; T++) {
|
||||
double lat = -90 + random.nextDouble()*180;
|
||||
double lon = -180 + random.nextDouble()*360;
|
||||
Point ctr = ctx.makePoint(lon, lat);
|
||||
double dist = MAXDIST*random.nextDouble();
|
||||
checkBBox(ctr, dist);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void checkBBox(Point ctr, double dist) {
|
||||
String msg = "ctr: "+ctr+" dist: "+dist;
|
||||
|
||||
Rectangle r = dc().calcBoxByDistFromPt(ctr, dist, ctx);
|
||||
double horizAxisLat = dc().calcBoxByDistFromPtHorizAxis(ctr,dist, ctx);
|
||||
if (!Double.isNaN(horizAxisLat))
|
||||
assertTrue(r.relate_yRange(horizAxisLat, horizAxisLat, ctx).intersects());
|
||||
|
||||
//horizontal
|
||||
if (r.getWidth() >= 180) {
|
||||
double calcDist = dc().distance(ctr,r.getMinX(), r.getMaxY() == 90 ? 90 : -90 );
|
||||
assertTrue(msg,calcDist <= dist+EPS);
|
||||
//horizAxisLat is meaningless in this context
|
||||
} else {
|
||||
Point tPt = findClosestPointOnVertToPoint(r.getMinX(), r.getMinY(), r.getMaxY(), ctr);
|
||||
double calcDist = dc().distance(ctr,tPt);
|
||||
assertEquals(msg,dist,calcDist,EPS);
|
||||
assertEquals(msg,tPt.getY(),horizAxisLat,EPS);
|
||||
}
|
||||
|
||||
//vertical
|
||||
double topDist = dc().distance(ctr,ctr.getX(),r.getMaxY());
|
||||
if (r.getMaxY() == 90)
|
||||
assertTrue(msg,topDist <= dist+EPS);
|
||||
else
|
||||
assertEquals(msg,dist,topDist,EPS);
|
||||
double botDist = dc().distance(ctr,ctr.getX(),r.getMinY());
|
||||
if (r.getMinY() == -90)
|
||||
assertTrue(msg,botDist <= dist+EPS);
|
||||
else
|
||||
assertEquals(msg,dist,botDist,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 SimpleSpatialContext(DistanceUnits.CARTESIAN);
|
||||
EPS = 10e-6;//tighter epsilon (aka delta)
|
||||
for(int i = 0; i < 1000; i++) {
|
||||
testDistCalcPointOnBearing(random.nextInt(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 maxDist = ctx.getUnits().earthCircumference() / 2;
|
||||
for(int i = 0; i < 1000; i++) {
|
||||
int dist = random.nextInt((int) maxDist);
|
||||
EPS = (dist < maxDist*0.75 ? 10e-6 : 10e-3);
|
||||
testDistCalcPointOnBearing(dist);
|
||||
}
|
||||
}
|
||||
|
||||
private void testDistCalcPointOnBearing(double dist) {
|
||||
for(int angDEG = 0; angDEG < 360; angDEG += random.nextInt(20)+1) {
|
||||
Point c = ctx.makePoint(random.nextInt(360),-90+random.nextInt(181));
|
||||
|
||||
//0 distance means same point
|
||||
Point p2 = dc().pointOnBearing(c, 0, angDEG, ctx);
|
||||
assertEquals(c,p2);
|
||||
|
||||
p2 = dc().pointOnBearing(c, dist, angDEG, ctx);
|
||||
double calcDist = dc().distance(c, p2);
|
||||
assertEqualsRatio(dist, calcDist);
|
||||
}
|
||||
}
|
||||
|
||||
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],ctx.normY(pair[0]),0);
|
||||
}
|
||||
Random random = new Random(RandomSeed.seed());
|
||||
for(int i = -1000; i < 1000; i += random.nextInt(10)*10) {
|
||||
double d = ctx.normY(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},
|
||||
{-180-360,-180},{-180-720,-180},{180+360,-180},{180+720,-180}};
|
||||
for (double[] pair : lons) {
|
||||
assertEquals("input "+pair[0],pair[1],ctx.normX(pair[0]),0);
|
||||
}
|
||||
Random random = new Random(RandomSeed.seed());
|
||||
for(int i = -1000; i < 1000; i += random.nextInt(10)*10) {
|
||||
double d = ctx.normX(i);
|
||||
assertTrue(i + " " + d, d >= -180 && d < 180);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistToRadians() {
|
||||
assertDistToRadians(0);
|
||||
assertDistToRadians(500);
|
||||
assertDistToRadians(ctx.getUnits().earthRadius());
|
||||
}
|
||||
|
||||
private void assertDistToRadians(double dist) {
|
||||
double radius = ctx.getUnits().earthRadius();
|
||||
assertEquals(
|
||||
DistanceUtils.pointOnBearingRAD(0, 0, DistanceUtils.dist2Radians(dist, radius), DistanceUtils.DEG_90_AS_RADS, null)[1],
|
||||
DistanceUtils.dist2Radians(dist, radius),10e-5);
|
||||
}
|
||||
|
||||
private Point pLL(double lat, double lon) {
|
||||
return ctx.makePoint(lon,lat);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.prefix;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.simple.SimpleSpatialContext;
|
||||
import org.apache.lucene.spatial.base.prefix.geohash.GeohashPrefixTree;
|
||||
import org.apache.lucene.spatial.base.shape.Rectangle;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author David Smiley - dsmiley@mitre.org
|
||||
*/
|
||||
public class SpatialPrefixTreeTest {
|
||||
|
||||
//TODO plug in others and test them
|
||||
private SimpleSpatialContext ctx;
|
||||
private SpatialPrefixTree trie;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
ctx = SimpleSpatialContext.GEO_KM;
|
||||
trie = new GeohashPrefixTree(ctx,4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNodeTraverse() {
|
||||
Node prevN = null;
|
||||
Node n = trie.getWorldNode();
|
||||
assertEquals(0,n.getLevel());
|
||||
assertEquals(ctx.getWorldBounds(),n.getShape());
|
||||
while(n.getLevel() < trie.getMaxLevels()) {
|
||||
prevN = n;
|
||||
n = n.getSubCells().iterator().next();//TODO random which one?
|
||||
|
||||
assertEquals(prevN.getLevel()+1,n.getLevel());
|
||||
Rectangle prevNShape = (Rectangle) prevN.getShape();
|
||||
Shape s = n.getShape();
|
||||
Rectangle sbox = s.getBoundingBox();
|
||||
assertTrue(prevNShape.getWidth() > sbox.getWidth());
|
||||
assertTrue(prevNShape.getHeight() > sbox.getHeight());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.prefix;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.context.simple.SimpleSpatialContext;
|
||||
import org.apache.lucene.spatial.base.distance.DistanceUnits;
|
||||
import org.apache.lucene.spatial.base.prefix.quad.QuadPrefixTree;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
import org.apache.lucene.spatial.base.shape.simple.PointImpl;
|
||||
import org.apache.lucene.spatial.base.shape.simple.RectangleImpl;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
public class TestGridMatchInfo {
|
||||
|
||||
@Test @Ignore
|
||||
public void testMatchInfo() {
|
||||
// Check Validation
|
||||
SpatialContext ctx = new SimpleSpatialContext(DistanceUnits.CARTESIAN,null,new RectangleImpl(0,10,0,10));
|
||||
QuadPrefixTree grid = new QuadPrefixTree(ctx, 2);
|
||||
|
||||
|
||||
// GeometricShapeFactory gsf = new GeometricShapeFactory();
|
||||
// gsf.setCentre( new com.vividsolutions.jts.geom.Coordinate( 5,5 ) );
|
||||
// gsf.setSize( 9.5 );
|
||||
// Shape shape = new JtsGeometry( gsf.createCircle() );
|
||||
|
||||
Shape shape = new RectangleImpl(0, 6, 5, 10);
|
||||
|
||||
shape = new PointImpl(3, 3);
|
||||
|
||||
//TODO UPDATE BASED ON NEW API
|
||||
List<String> m = SpatialPrefixTree.nodesToTokenStrings(grid.getNodes(shape,3,false));
|
||||
System.out.println(m);
|
||||
|
||||
for (CharSequence s : m) {
|
||||
System.out.println(s);
|
||||
}
|
||||
|
||||
|
||||
// // query should intersect everything one level down
|
||||
// ArrayList<String> descr = new ArrayList<String>();
|
||||
// descr.add( "AAA*" );
|
||||
// descr.add( "AABC*" );
|
||||
// System.out.println( descr );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.prefix.geohash;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.context.simple.SimpleSpatialContext;
|
||||
import org.apache.lucene.spatial.base.distance.DistanceUnits;
|
||||
import org.apache.lucene.spatial.base.shape.Point;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Tests for {@link GeohashUtils}
|
||||
*/
|
||||
public class TestGeohashUtils {
|
||||
SpatialContext ctx = new SimpleSpatialContext( DistanceUnits.KILOMETERS );
|
||||
|
||||
/**
|
||||
* Pass condition: lat=42.6, lng=-5.6 should be encoded as "ezs42e44yx96",
|
||||
* lat=57.64911 lng=10.40744 should be encoded as "u4pruydqqvj8"
|
||||
*/
|
||||
@Test
|
||||
public void testEncode() {
|
||||
String hash = GeohashUtils.encodeLatLon(42.6, -5.6);
|
||||
assertEquals("ezs42e44yx96", hash);
|
||||
|
||||
hash = GeohashUtils.encodeLatLon(57.64911, 10.40744);
|
||||
assertEquals("u4pruydqqvj8", hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass condition: lat=52.3738007, lng=4.8909347 should be encoded and then
|
||||
* decoded within 0.00001 of the original value
|
||||
*/
|
||||
@Test
|
||||
public void testDecodePreciseLongitudeLatitude() {
|
||||
String hash = GeohashUtils.encodeLatLon(52.3738007, 4.8909347);
|
||||
|
||||
Point point = GeohashUtils.decode(hash,ctx);
|
||||
|
||||
assertEquals(52.3738007, point.getY(), 0.00001D);
|
||||
assertEquals(4.8909347, point.getX(), 0.00001D);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass condition: lat=84.6, lng=10.5 should be encoded and then decoded
|
||||
* within 0.00001 of the original value
|
||||
*/
|
||||
@Test
|
||||
public void testDecodeImpreciseLongitudeLatitude() {
|
||||
String hash = GeohashUtils.encodeLatLon(84.6, 10.5);
|
||||
|
||||
Point point = GeohashUtils.decode(hash, ctx);
|
||||
|
||||
assertEquals(84.6, point.getY(), 0.00001D);
|
||||
assertEquals(10.5, point.getX(), 0.00001D);
|
||||
}
|
||||
|
||||
/*
|
||||
* see https://issues.apache.org/jira/browse/LUCENE-1815 for details
|
||||
*/
|
||||
@Test
|
||||
public void testDecodeEncode() {
|
||||
String geoHash = "u173zq37x014";
|
||||
assertEquals(geoHash, GeohashUtils.encodeLatLon(52.3738007, 4.8909347));
|
||||
Point point = GeohashUtils.decode(geoHash,ctx);
|
||||
assertEquals(52.37380061d, point.getY(), 0.000001d);
|
||||
assertEquals(4.8909343d, point.getX(), 0.000001d);
|
||||
|
||||
assertEquals(geoHash, GeohashUtils.encodeLatLon(point.getY(), point.getX()));
|
||||
|
||||
geoHash = "u173";
|
||||
point = GeohashUtils.decode("u173",ctx);
|
||||
geoHash = GeohashUtils.encodeLatLon(point.getY(), point.getX());
|
||||
final Point point2 = GeohashUtils.decode(geoHash, ctx);
|
||||
assertEquals(point.getY(), point2.getY(), 0.000001d);
|
||||
assertEquals(point.getX(), point2.getX(), 0.000001d);
|
||||
}
|
||||
|
||||
/** see the table at http://en.wikipedia.org/wiki/Geohash */
|
||||
@Test
|
||||
public void testHashLenToWidth() {
|
||||
double[] box = GeohashUtils.lookupDegreesSizeForHashLen(3);
|
||||
assertEquals(1.40625,box[0],0.0001);
|
||||
assertEquals(1.40625,box[1],0.0001);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.shape;
|
||||
|
||||
import org.apache.lucene.spatial.RandomSeed;
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.distance.DistanceCalculator;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static org.apache.lucene.spatial.base.shape.SpatialRelation.CONTAINS;
|
||||
import static org.apache.lucene.spatial.base.shape.SpatialRelation.DISJOINT;
|
||||
import static org.apache.lucene.spatial.base.shape.SpatialRelation.WITHIN;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* @author David Smiley - dsmiley@mitre.org
|
||||
*/
|
||||
public abstract class AbstractTestShapes {
|
||||
protected Random random;
|
||||
|
||||
protected SpatialContext ctx;
|
||||
private static final double EPS = 10e-9;
|
||||
|
||||
@Before
|
||||
public void beforeClass() {
|
||||
random = new Random(RandomSeed.seed());
|
||||
ctx = getContext();
|
||||
}
|
||||
|
||||
protected void assertRelation(String msg, SpatialRelation expected, Shape a, Shape b) {
|
||||
msg = a+" intersect "+b;//use different msg
|
||||
_assertIntersect(msg,expected,a,b);
|
||||
//check flipped a & b w/ transpose(), while we're at it
|
||||
_assertIntersect("(transposed) " + msg, expected.transpose(), b, a);
|
||||
}
|
||||
|
||||
private void _assertIntersect(String msg, SpatialRelation expected, Shape a, Shape b) {
|
||||
SpatialRelation sect = a.relate(b, ctx);
|
||||
if (sect == expected)
|
||||
return;
|
||||
if (expected == WITHIN || expected == CONTAINS) {
|
||||
if (a.getClass().equals(b.getClass())) // they are the same shape type
|
||||
assertEquals(msg,a,b);
|
||||
else {
|
||||
//they are effectively points or lines that are the same location
|
||||
assertTrue(msg,!a.hasArea());
|
||||
assertTrue(msg,!b.hasArea());
|
||||
|
||||
Rectangle aBBox = a.getBoundingBox();
|
||||
Rectangle bBBox = b.getBoundingBox();
|
||||
if (aBBox.getHeight() == 0 && bBBox.getHeight() == 0
|
||||
&& (aBBox.getMaxY() == 90 && bBBox.getMaxY() == 90
|
||||
|| aBBox.getMinY() == -90 && bBBox.getMinY() == -90))
|
||||
;//== a point at the pole
|
||||
else
|
||||
assertEquals(msg, aBBox, bBBox);
|
||||
}
|
||||
} else {
|
||||
assertEquals(msg,expected,sect);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertEqualsRatio(String msg, double expected, double actual) {
|
||||
double delta = Math.abs(actual - expected);
|
||||
double base = Math.min(actual, expected);
|
||||
double deltaRatio = base==0 ? delta : Math.min(delta,delta / base);
|
||||
assertEquals(msg,0,deltaRatio, EPS);
|
||||
}
|
||||
|
||||
protected void testRectangle(double minX, double width, double minY, double height) {
|
||||
Rectangle r = ctx.makeRect(minX, minX + width, minY, minY+height);
|
||||
//test equals & hashcode of duplicate
|
||||
Rectangle r2 = ctx.makeRect(minX, minX + width, minY, minY+height);
|
||||
assertEquals(r,r2);
|
||||
assertEquals(r.hashCode(),r2.hashCode());
|
||||
|
||||
String msg = r.toString();
|
||||
|
||||
assertEquals(msg, width != 0 && height != 0, r.hasArea());
|
||||
assertEquals(msg, width != 0 && height != 0, r.getArea() > 0);
|
||||
|
||||
assertEqualsRatio(msg, height, r.getHeight());
|
||||
assertEqualsRatio(msg, width, r.getWidth());
|
||||
Point center = r.getCenter();
|
||||
msg += " ctr:"+center;
|
||||
//System.out.println(msg);
|
||||
assertRelation(msg, CONTAINS, r, center);
|
||||
|
||||
DistanceCalculator dc = ctx.getDistCalc();
|
||||
double dUR = dc.distance(center, r.getMaxX(), r.getMaxY());
|
||||
double dLR = dc.distance(center, r.getMaxX(), r.getMinY());
|
||||
double dUL = dc.distance(center, r.getMinX(), r.getMaxY());
|
||||
double dLL = dc.distance(center, r.getMinX(), r.getMinY());
|
||||
|
||||
assertEquals(msg,width != 0 || height != 0, dUR != 0);
|
||||
if (dUR != 0)
|
||||
assertTrue(dUR > 0 && dLL > 0);
|
||||
assertEqualsRatio(msg, dUR, dUL);
|
||||
assertEqualsRatio(msg, dLR, dLL);
|
||||
if (!ctx.isGeo() || center.getY() == 0)
|
||||
assertEqualsRatio(msg, dUR, dLL);
|
||||
}
|
||||
|
||||
protected void testRectIntersect() {
|
||||
final double INCR = 45;
|
||||
final double Y = 10;
|
||||
for(double left = -180; left <= 180; left += INCR) {
|
||||
for(double right = left; right - left <= 360; right += INCR) {
|
||||
Rectangle r = ctx.makeRect(left,right,-Y,Y);
|
||||
|
||||
//test contains (which also tests within)
|
||||
for(double left2 = left; left2 <= right; left2 += INCR) {
|
||||
for(double right2 = left2; right2 <= right; right2 += INCR) {
|
||||
Rectangle r2 = ctx.makeRect(left2,right2,-Y,Y);
|
||||
assertRelation(null, SpatialRelation.CONTAINS, r, r2);
|
||||
}
|
||||
}
|
||||
//test point contains
|
||||
assertRelation(null, SpatialRelation.CONTAINS, r, ctx.makePoint(left, Y));
|
||||
|
||||
//test disjoint
|
||||
for(double left2 = right+INCR; left2 - left < 360; left2 += INCR) {
|
||||
for(double right2 = left2; right2 - left < 360; right2 += INCR) {
|
||||
Rectangle r2 = ctx.makeRect(left2,right2,-Y,Y);
|
||||
assertRelation(null, SpatialRelation.DISJOINT, r, r2);
|
||||
|
||||
//test point disjoint
|
||||
assertRelation(null, SpatialRelation.DISJOINT, r, ctx.makePoint(left2, Y));
|
||||
}
|
||||
}
|
||||
//test intersect
|
||||
for(double left2 = left+INCR; left2 <= right; left2 += INCR) {
|
||||
for(double right2 = right+INCR; right2 - left < 360; right2 += INCR) {
|
||||
Rectangle r2 = ctx.makeRect(left2,right2,-Y,Y);
|
||||
assertRelation(null, SpatialRelation.INTERSECTS, r, r2);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void testCircle(double x, double y, double dist) {
|
||||
Circle c = ctx.makeCircle(x, y, dist);
|
||||
String msg = c.toString();
|
||||
final Circle c2 = ctx.makeCircle(ctx.makePoint(x, y), dist);
|
||||
assertEquals(c, c2);
|
||||
assertEquals(c.hashCode(),c2.hashCode());
|
||||
|
||||
assertEquals(msg,dist > 0, c.hasArea());
|
||||
final Rectangle bbox = c.getBoundingBox();
|
||||
assertEquals(msg,dist > 0, bbox.getArea() > 0);
|
||||
if (!ctx.isGeo()) {
|
||||
//if not geo then units of dist == units of x,y
|
||||
assertEqualsRatio(msg, bbox.getHeight(), dist * 2);
|
||||
assertEqualsRatio(msg, bbox.getWidth(), dist * 2);
|
||||
}
|
||||
assertRelation(msg, CONTAINS, c, c.getCenter());
|
||||
assertRelation(msg, CONTAINS, bbox, c);
|
||||
}
|
||||
|
||||
protected void testCircleIntersect() {
|
||||
//Now do some randomized tests:
|
||||
int i_C = 0, i_I = 0, i_W = 0, i_O = 0;//counters for the different intersection cases
|
||||
int laps = 0;
|
||||
int MINLAPSPERCASE = 20;
|
||||
while(i_C < MINLAPSPERCASE || i_I < MINLAPSPERCASE || i_W < MINLAPSPERCASE || i_O < MINLAPSPERCASE) {
|
||||
laps++;
|
||||
double cX = randRange(-180,179);
|
||||
double cY = randRange(-90,90);
|
||||
double cR = randRange(0, 180);
|
||||
double cR_dist = ctx.getDistCalc().distance(ctx.makePoint(0, 0), 0, cR);
|
||||
Circle c = ctx.makeCircle(cX, cY, cR_dist);
|
||||
|
||||
double rX = randRange(-180,179);
|
||||
double rW = randRange(0,360);
|
||||
double rY1 = randRange(-90,90);
|
||||
double rY2 = randRange(-90,90);
|
||||
double rYmin = Math.min(rY1,rY2);
|
||||
double rYmax = Math.max(rY1,rY2);
|
||||
Rectangle r = ctx.makeRect(rX, rX+rW, rYmin, rYmax);
|
||||
|
||||
SpatialRelation ic = c.relate(r, ctx);
|
||||
|
||||
Point p;
|
||||
switch (ic) {
|
||||
case CONTAINS:
|
||||
i_C++;
|
||||
p = randomPointWithin(random,r,ctx);
|
||||
assertEquals(CONTAINS,c.relate(p, ctx));
|
||||
break;
|
||||
case INTERSECTS:
|
||||
i_I++;
|
||||
//hard to test anything here; instead we'll test it separately
|
||||
break;
|
||||
case WITHIN:
|
||||
i_W++;
|
||||
p = randomPointWithin(random,c,ctx);
|
||||
assertEquals(CONTAINS,r.relate(p, ctx));
|
||||
break;
|
||||
case DISJOINT:
|
||||
i_O++;
|
||||
p = randomPointWithin(random,r,ctx);
|
||||
assertEquals(DISJOINT,c.relate(p, ctx));
|
||||
break;
|
||||
default: fail(""+ic);
|
||||
}
|
||||
}
|
||||
System.out.println("Laps: "+laps);
|
||||
|
||||
//TODO deliberately test INTERSECTS based on known intersection point
|
||||
}
|
||||
|
||||
/** Returns a random integer between [start, end] with a limited number of possibilities instead of end-start+1. */
|
||||
private int randRange(int start, int end) {
|
||||
//I tested this.
|
||||
double r = random.nextDouble();
|
||||
final int BUCKETS = 91;
|
||||
int ir = (int) Math.round(r*(BUCKETS-1));//put into buckets
|
||||
int result = (int)((double)((end - start) * ir) / (double)(BUCKETS-1) + (double)start);
|
||||
assert result >= start && result <= end;
|
||||
return result;
|
||||
}
|
||||
|
||||
private Point randomPointWithin(Random random, Circle c, SpatialContext ctx) {
|
||||
double d = c.getDistance() * random.nextDouble();
|
||||
double angleDEG = 360*random.nextDouble();
|
||||
Point p = ctx.getDistCalc().pointOnBearing(c.getCenter(), d, angleDEG, ctx);
|
||||
assertEquals(CONTAINS,c.relate(p, ctx));
|
||||
return p;
|
||||
}
|
||||
|
||||
private Point randomPointWithin(Random random, Rectangle r, SpatialContext ctx) {
|
||||
double x = r.getMinX() + random.nextDouble()*r.getWidth();
|
||||
double y = r.getMinY() + random.nextDouble()*r.getHeight();
|
||||
Point p = ctx.makePoint(x,y);
|
||||
assertEquals(CONTAINS,r.relate(p, ctx));
|
||||
return p;
|
||||
}
|
||||
|
||||
protected abstract SpatialContext getContext();
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.shape;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.context.simple.SimpleSpatialContext;
|
||||
import org.apache.lucene.spatial.base.distance.DistanceUnits;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.apache.lucene.spatial.base.shape.SpatialRelation.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author dsmiley
|
||||
*/
|
||||
public class TestShapes2D extends AbstractTestShapes {
|
||||
|
||||
@Override
|
||||
protected SpatialContext getContext() {
|
||||
return new SimpleSpatialContext(DistanceUnits.CARTESIAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimplePoint() {
|
||||
Point pt = ctx.makePoint(0,0);
|
||||
String msg = pt.toString();
|
||||
|
||||
//test equals & hashcode
|
||||
Point pt2 = ctx.makePoint(0,0);
|
||||
assertEquals(msg, pt, pt2);
|
||||
assertEquals(msg, pt.hashCode(), pt2.hashCode());
|
||||
|
||||
assertFalse(msg,pt.hasArea());
|
||||
assertEquals(msg,pt.getCenter(),pt);
|
||||
Rectangle bbox = pt.getBoundingBox();
|
||||
assertFalse(msg,bbox.hasArea());
|
||||
assertEquals(msg,pt,bbox.getCenter());
|
||||
|
||||
assertRelation(msg, CONTAINS, pt, pt2);
|
||||
assertRelation(msg, DISJOINT, pt, ctx.makePoint(0, 1));
|
||||
assertRelation(msg, DISJOINT, pt, ctx.makePoint(1, 0));
|
||||
assertRelation(msg, DISJOINT, pt, ctx.makePoint(1, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleRectangle() {
|
||||
double[] minXs = new double[]{-1000,-360,-180,-20,0,20,180,1000};
|
||||
for (double minX : minXs) {
|
||||
double[] widths = new double[]{0,10,180,360,400};
|
||||
for (double width : widths) {
|
||||
testRectangle(minX, width, 0, 0);
|
||||
testRectangle(minX, width, -10, 10);
|
||||
testRectangle(minX, width, 5, 10);
|
||||
}
|
||||
}
|
||||
|
||||
testRectIntersect();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleCircle() {
|
||||
double[] theXs = new double[]{-10,0,10};
|
||||
for (double x : theXs) {
|
||||
double[] theYs = new double[]{-20,0,20};
|
||||
for (double y : theYs) {
|
||||
testCircle(x, y, 0);
|
||||
testCircle(x, y, 5);
|
||||
}
|
||||
}
|
||||
//INTERSECTION:
|
||||
//Start with some static tests that have shown to cause failures at some point:
|
||||
assertEquals("getX not getY",INTERSECTS,ctx.makeCircle(107,-81,147).relate(ctx.makeRect(92, 121, -89, 74), ctx));
|
||||
|
||||
testCircleIntersect();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.base.shape;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.context.simple.SimpleSpatialContext;
|
||||
import org.apache.lucene.spatial.base.distance.*;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.apache.lucene.spatial.base.shape.SpatialRelation.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author David Smiley - dsmiley@mitre.org
|
||||
*/
|
||||
public abstract class TestShapesGeo extends AbstractTestShapes {
|
||||
|
||||
@Test
|
||||
public void testGeoRectangle() {
|
||||
double[] lons = new double[]{0,45,160,180,-45,-175, -180};//minX
|
||||
for (double lon : lons) {
|
||||
double[] lonWs = new double[]{0,20,180,200,355, 360};//width
|
||||
for (double lonW : lonWs) {
|
||||
testRectangle(lon, lonW, 0, 0);
|
||||
testRectangle(lon, lonW, -10, 10);
|
||||
testRectangle(lon, lonW, 80, 10);//polar cap
|
||||
testRectangle(lon, lonW, -90, 180);//full lat range
|
||||
}
|
||||
}
|
||||
|
||||
//Test geo rectangle intersections
|
||||
testRectIntersect();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGeoCircle() {
|
||||
//--Start with some static tests that once failed:
|
||||
|
||||
//Bug: numeric edge at pole, fails to init
|
||||
ctx.makeCircle(
|
||||
110,-12,ctx.getDistCalc().degreesToDistance(90 + 12));
|
||||
|
||||
//Bug: horizXAxis not in enclosing rectangle, assertion
|
||||
ctx.makeCircle(-44,16,degToDist(106));
|
||||
ctx.makeCircle(-36,-76,degToDist(14));
|
||||
ctx.makeCircle(107,82,degToDist(172));
|
||||
|
||||
// TODO need to update this test to be valid
|
||||
// {
|
||||
// //Bug in which distance was being confused as being in the same coordinate system as x,y.
|
||||
// double distDeltaToPole = 0.001;//1m
|
||||
// double distDeltaToPoleDEG = ctx.getDistCalc().distanceToDegrees(distDeltaToPole);
|
||||
// double dist = 1;//1km
|
||||
// double distDEG = ctx.getDistCalc().distanceToDegrees(dist);
|
||||
// Circle c = ctx.makeCircle(0,90-distDeltaToPoleDEG-distDEG,dist);
|
||||
// Rectangle cBBox = c.getBoundingBox();
|
||||
// Rectangle r = ctx.makeRect(cBBox.getMaxX()*0.99,cBBox.getMaxX()+1,c.getCenter().getY(),c.getCenter().getY());
|
||||
// assertEquals(INTERSECTS,c.getBoundingBox().relate(r, ctx));
|
||||
// assertEquals("dist != xy space",INTERSECTS,c.relate(r,ctx));//once failed here
|
||||
// }
|
||||
|
||||
assertEquals("wrong estimate", DISJOINT,ctx.makeCircle(-166,59,5226.2).relate(ctx.makeRect(36, 66, 23, 23), ctx));
|
||||
|
||||
assertEquals("bad CONTAINS (dateline)",INTERSECTS,ctx.makeCircle(56,-50,12231.5).relate(ctx.makeRect(108, 26, 39, 48), ctx));
|
||||
|
||||
assertEquals("bad CONTAINS (backwrap2)",INTERSECTS,
|
||||
ctx.makeCircle(112,-3,degToDist(91)).relate(ctx.makeRect(-163, 29, -38, 10), ctx));
|
||||
|
||||
assertEquals("bad CONTAINS (r x-wrap)",INTERSECTS,
|
||||
ctx.makeCircle(-139,47,degToDist(80)).relate(ctx.makeRect(-180, 180, -3, 12), ctx));
|
||||
|
||||
assertEquals("bad CONTAINS (pwrap)",INTERSECTS,
|
||||
ctx.makeCircle(-139,47,degToDist(80)).relate(ctx.makeRect(-180, 179, -3, 12), ctx));
|
||||
|
||||
assertEquals("no-dist 1",WITHIN,
|
||||
ctx.makeCircle(135,21,0).relate(ctx.makeRect(-103, -154, -47, 52), ctx));
|
||||
|
||||
assertEquals("bbox <= >= -90 bug",CONTAINS,
|
||||
ctx.makeCircle(-64,-84,degToDist(124)).relate(ctx.makeRect(-96, 96, -10, -10), ctx));
|
||||
|
||||
//The horizontal axis line of a geo circle doesn't necessarily pass through c's ctr.
|
||||
assertEquals("c's horiz axis doesn't pass through ctr",INTERSECTS,
|
||||
ctx.makeCircle(71,-44,degToDist(40)).relate(ctx.makeRect(15, 27, -62, -34), ctx));
|
||||
|
||||
assertEquals("pole boundary",INTERSECTS,
|
||||
ctx.makeCircle(-100,-12,degToDist(102)).relate(ctx.makeRect(143, 175, 4, 32), ctx));
|
||||
|
||||
assertEquals("full circle assert",CONTAINS,
|
||||
ctx.makeCircle(-64,32,degToDist(180)).relate(ctx.makeRect(47, 47, -14, 90), ctx));
|
||||
|
||||
//--Now proceed with systematic testing:
|
||||
|
||||
double distToOpposeSide = ctx.getUnits().earthRadius()*Math.PI;
|
||||
assertEquals(ctx.getWorldBounds(),ctx.makeCircle(0,0,distToOpposeSide).getBoundingBox());
|
||||
//assertEquals(ctx.makeCircle(0,0,distToOpposeSide/2 - 500).getBoundingBox());
|
||||
|
||||
double[] theXs = new double[]{-180,-45,90};
|
||||
for (double x : theXs) {
|
||||
double[] theYs = new double[]{-90,-45,0,45,90};
|
||||
for (double y : theYs) {
|
||||
testCircle(x, y, 0);
|
||||
testCircle(x, y, 500);
|
||||
testCircle(x, y, degToDist(90));
|
||||
testCircle(x, y, ctx.getUnits().earthRadius()*6);
|
||||
}
|
||||
}
|
||||
|
||||
testCircleIntersect();
|
||||
}
|
||||
|
||||
private double degToDist(int deg) {
|
||||
return ctx.getDistCalc().degreesToDistance(deg);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
public static class TestLawOfCosines extends TestShapesGeo {
|
||||
|
||||
@Override
|
||||
protected SpatialContext getContext() {
|
||||
DistanceUnits units = DistanceUnits.KILOMETERS;
|
||||
return new SimpleSpatialContext(units,
|
||||
new GeodesicSphereDistCalc.LawOfCosines(units.earthRadius()),
|
||||
SpatialContext.GEO_WORLDBOUNDS);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestHaversine extends TestShapesGeo {
|
||||
|
||||
@Override
|
||||
protected SpatialContext getContext() {
|
||||
DistanceUnits units = DistanceUnits.KILOMETERS;
|
||||
return new SimpleSpatialContext(units,
|
||||
new GeodesicSphereDistCalc.Haversine(units.earthRadius()),
|
||||
SpatialContext.GEO_WORLDBOUNDS);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestVincentySphere extends TestShapesGeo {
|
||||
|
||||
@Override
|
||||
protected SpatialContext getContext() {
|
||||
DistanceUnits units = DistanceUnits.KILOMETERS;
|
||||
return new SimpleSpatialContext(units,
|
||||
new GeodesicSphereDistCalc.Vincenty(units.earthRadius()),
|
||||
SpatialContext.GEO_WORLDBOUNDS);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.prefix;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.prefix.geohash.GeohashPrefixTree;
|
||||
import org.apache.lucene.spatial.strategy.SimpleSpatialFieldInfo;
|
||||
import org.apache.lucene.spatial.SpatialMatchConcern;
|
||||
import org.apache.lucene.spatial.StrategyTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
public abstract class BaseRecursivePrefixTreeStrategyTestCase extends StrategyTestCase<SimpleSpatialFieldInfo> {
|
||||
|
||||
private int maxLength;
|
||||
|
||||
protected abstract SpatialContext getSpatialContext();
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
maxLength = GeohashPrefixTree.getMaxLevelsPossible();
|
||||
// SimpleIO
|
||||
this.ctx = getSpatialContext();
|
||||
this.strategy = new RecursivePrefixTreeStrategy(new GeohashPrefixTree(
|
||||
ctx, maxLength ));
|
||||
this.fieldInfo = new SimpleSpatialFieldInfo( getClass().getSimpleName() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilterWithVariableScanLevel() throws IOException {
|
||||
getAddAndVerifyIndexedDocuments(DATA_WORLD_CITIES_POINTS);
|
||||
|
||||
//execute queries for each prefix grid scan level
|
||||
for(int i = 0; i <= maxLength; i++) {
|
||||
((RecursivePrefixTreeStrategy)strategy).setPrefixGridScanLevel(i);
|
||||
executeQueries(SpatialMatchConcern.FILTER, QTEST_Cities_IsWithin_BBox);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.prefix;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.context.simple.SimpleSpatialContext;
|
||||
import org.junit.Before;
|
||||
|
||||
|
||||
public class RecursivePrefixTreeStrategyTestCase extends BaseRecursivePrefixTreeStrategyTestCase {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SpatialContext getSpatialContext() {
|
||||
return SimpleSpatialContext.GEO_KM;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.prefix;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
/**
|
||||
* This is just a quick idea for *simple* tests
|
||||
*/
|
||||
public class TestSpatialPrefixField {
|
||||
|
||||
@Test
|
||||
public void testRawTokens() {
|
||||
// Ignoring geometry for now, and focus on what tokens need to match
|
||||
|
||||
List<String> docA = Arrays.asList(
|
||||
"AAAAAA*",
|
||||
"AAAAAB+"
|
||||
);
|
||||
|
||||
List<String> docB = Arrays.asList(
|
||||
"A*",
|
||||
"BB*"
|
||||
);
|
||||
|
||||
// Assumptions:
|
||||
checkQuery("AAAAA", "docA", "docB");
|
||||
checkQuery("AAAAA*", "docA", "docB"); // for now * and + are essentially identical
|
||||
checkQuery("AAAAA+", "docA", "docB"); // down the road, there may be a difference between 'covers' and an edge
|
||||
|
||||
checkQuery("AA*", "docB", "docA"); // Bigger input query
|
||||
|
||||
checkQuery("AAAAAAAAAAAA*", "docA", "docB"); // small
|
||||
|
||||
checkQuery("BC"); // nothing
|
||||
checkQuery("XX"); // nothing
|
||||
|
||||
// match only B
|
||||
checkQuery("B", "docB");
|
||||
checkQuery("BBBB", "docB");
|
||||
checkQuery("B*", "docB");
|
||||
checkQuery("BBBB*", "docB");
|
||||
}
|
||||
|
||||
void checkQuery(String query, String... expect) {
|
||||
// TODO, check that the query returns the docs in order
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.prefix;
|
||||
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.context.simple.SimpleSpatialContext;
|
||||
import org.apache.lucene.spatial.base.prefix.quad.QuadPrefixTree;
|
||||
import org.apache.lucene.spatial.base.query.SpatialArgsParser;
|
||||
import org.apache.lucene.spatial.base.shape.Shape;
|
||||
import org.apache.lucene.spatial.base.shape.simple.PointImpl;
|
||||
import org.apache.lucene.spatial.strategy.SimpleSpatialFieldInfo;
|
||||
import org.apache.lucene.spatial.SpatialTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
public class TestTermQueryPrefixGridStrategy extends SpatialTestCase {
|
||||
|
||||
@Test
|
||||
public void testNGramPrefixGridLosAngeles() throws IOException {
|
||||
SimpleSpatialFieldInfo fieldInfo = new SimpleSpatialFieldInfo("geo");
|
||||
SpatialContext ctx = SimpleSpatialContext.GEO_KM;
|
||||
TermQueryPrefixTreeStrategy prefixGridStrategy = new TermQueryPrefixTreeStrategy(new QuadPrefixTree(ctx));
|
||||
|
||||
Shape point = new PointImpl(-118.243680, 34.052230);
|
||||
|
||||
Document losAngeles = new Document();
|
||||
losAngeles.add(new Field("name", "Los Angeles", StringField.TYPE_STORED));
|
||||
losAngeles.add(prefixGridStrategy.createField(fieldInfo, point, true, true));
|
||||
|
||||
addDocumentsAndCommit(Arrays.asList(losAngeles));
|
||||
|
||||
// This won't work with simple spatial context...
|
||||
SpatialArgsParser spatialArgsParser = new SpatialArgsParser();
|
||||
// TODO... use a non polygon query
|
||||
// SpatialArgs spatialArgs = spatialArgsParser.parse(
|
||||
// "IsWithin(POLYGON((-127.00390625 39.8125,-112.765625 39.98828125,-111.53515625 31.375,-125.94921875 30.14453125,-127.00390625 39.8125)))",
|
||||
// new SimpleSpatialContext());
|
||||
|
||||
// Query query = prefixGridStrategy.makeQuery(spatialArgs, fieldInfo);
|
||||
// SearchResults searchResults = executeQuery(query, 1);
|
||||
// assertEquals(1, searchResults.numFound);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.vector;
|
||||
|
||||
import org.apache.lucene.search.FieldCache;
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.strategy.util.NumericFieldInfo;
|
||||
import org.apache.lucene.spatial.SpatialMatchConcern;
|
||||
import org.apache.lucene.spatial.StrategyTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
public abstract class BaseTwoDoublesStrategyTestCase extends StrategyTestCase<TwoDoublesFieldInfo> {
|
||||
|
||||
protected abstract SpatialContext getSpatialContext();
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
this.ctx = getSpatialContext();
|
||||
this.strategy = new TwoDoublesStrategy(ctx,
|
||||
new NumericFieldInfo(), FieldCache.NUMERIC_UTILS_DOUBLE_PARSER);
|
||||
this.fieldInfo = new TwoDoublesFieldInfo(getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCitiesWithinBBox() throws IOException {
|
||||
getAddAndVerifyIndexedDocuments(DATA_WORLD_CITIES_POINTS);
|
||||
executeQueries(SpatialMatchConcern.FILTER, QTEST_Cities_IsWithin_BBox);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.lucene.spatial.strategy.vector;
|
||||
|
||||
import org.apache.lucene.spatial.base.context.SpatialContext;
|
||||
import org.apache.lucene.spatial.base.context.simple.SimpleSpatialContext;
|
||||
|
||||
public class TwoDoublesStrategyTestCase extends BaseTwoDoublesStrategyTestCase {
|
||||
|
||||
@Override
|
||||
protected SpatialContext getSpatialContext() {
|
||||
return SimpleSpatialContext.GEO_KM;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
[San Francisco] G5391959 @ IsWithin(-122.524918 37.674973 -122.360123 37.817108)
|
||||
[Wellington] G2179537 @ IsWithin(174.711456 -41.360779 174.854279 -41.213837)
|
||||
[Barcelona] G6544100 G3128760 @ IsWithin(2.127228 41.333313 2.226105 41.408844)
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,249 @@
|
|||
#id name shape
|
||||
FLK Falkland Is. -61.148055 -52.343055 -57.733200 -51.249455
|
||||
GUF French Guiana -54.603782 2.113473 -51.648055 5.755418
|
||||
GUY Guyana -61.389727 1.186873 -56.470636 8.535273
|
||||
PCN Pitcairn Is. -130.105055 -25.082227 -128.286118 -24.325836
|
||||
SGS South Georgia & the South Sandwich Is. -38.023755 -58.498609 -26.241391 -53.989727
|
||||
SHN St. Helena -5.792782 -16.021946 -5.645282 -15.903755
|
||||
SUR Suriname -58.071400 1.836245 -53.986118 6.001809
|
||||
TTO Trinidad & Tobago -61.921600 10.040345 -60.520836 11.345554
|
||||
VEN Venezuela -73.378064 0.649164 -59.803055 12.197500
|
||||
ASM American Samoa -170.823227 -14.375555 -170.561873 -14.254309
|
||||
COK Cook Is. -165.848345 -21.940836 -157.703764 -10.881318
|
||||
PYF French Polynesia -151.497773 -17.870836 -138.809755 -8.778191
|
||||
UMI Jarvis I. -160.045164 -0.398055 -160.009464 -0.374309
|
||||
NIU Niue -169.952236 -19.145555 -169.781555 -18.963336
|
||||
WSM Samoa -172.780027 -14.057500 -171.429200 -13.460555
|
||||
TKL Tokelau -171.862718 -9.218891 -171.843764 -9.170627
|
||||
TON Tonga -175.360000 -21.268064 -173.906827 -18.568055
|
||||
WLF Wallis & Futuna -178.190273 -14.323891 -176.121936 -13.214864
|
||||
ARG Argentina -73.582300 -55.051673 -53.650009 -21.780518
|
||||
BOL Bolivia -69.656191 -22.901109 -57.521118 -9.679191
|
||||
BRA Brazil -74.004591 -33.741118 -34.792918 5.272709
|
||||
CHL Chile -109.446109 -55.902227 -66.420627 -17.505282
|
||||
ECU Ecuador -91.663891 -5.000309 -75.216846 1.437782
|
||||
PRY Paraguay -62.643773 -27.584727 -54.243900 -19.296809
|
||||
PER Peru -81.355146 -18.348546 -68.673909 -0.036873
|
||||
URY Uruguay -58.438609 -34.943818 -53.098300 -30.096673
|
||||
UMI Baker I. -176.467655 0.215282 -176.455855 0.222573
|
||||
CAN Canada -141.002991 41.675554 -52.617364 83.113873
|
||||
GTM Guatemala -92.246782 13.745836 -88.214736 17.821109
|
||||
UMI Howland I. -176.643082 0.790282 -176.631091 0.808609
|
||||
UMI Johnston Atoll -169.538936 16.724164 -169.523927 16.730273
|
||||
MEX Mexico -118.404164 14.550545 -86.738618 32.718454
|
||||
UMI Midway Is. -177.395845 28.184154 -177.360545 28.221518
|
||||
BRB Barbados -59.659446 13.050554 -59.427082 13.337082
|
||||
DMA Dominica -61.491391 15.198054 -61.250700 15.631945
|
||||
GRD Grenada -61.785182 11.996945 -61.596391 12.237154
|
||||
GLP Guadeloupe -61.796109 15.870000 -61.187082 16.512918
|
||||
MTQ Martinique -61.231536 14.402773 -60.816946 14.880136
|
||||
LCA St. Lucia -61.079582 13.709445 -60.878064 14.109309
|
||||
SPM St. Pierre & Miquelon -56.397782 46.747191 -56.145500 47.135827
|
||||
VCT St. Vincent & the Grenadines -61.280146 13.130282 -61.120282 13.383191
|
||||
ABW Aruba -70.059664 12.411109 -69.874864 12.627773
|
||||
BMU Bermuda -64.823064 32.260554 -64.676809 32.379509
|
||||
DOM Dominican Republic -72.003064 17.604164 -68.322927 19.930827
|
||||
HTI Haiti -74.467791 18.022782 -71.629182 20.091454
|
||||
JAM Jamaica -78.373900 17.697218 -76.221118 18.522500
|
||||
ANT Netherlands Antilles -69.163618 12.020554 -68.192927 12.383891
|
||||
BHS The Bahamas -78.978900 20.915273 -72.738891 26.929164
|
||||
TCA Turks & Caicos Is. -72.031464 21.429918 -71.127573 21.957773
|
||||
BLZ Belize -89.216400 15.889854 -87.779591 18.489900
|
||||
CYM Cayman Is. -81.400836 19.265000 -81.093064 19.354164
|
||||
COL Colombia -81.720146 -4.236873 -66.870455 12.590273
|
||||
CRI Costa Rica -85.911391 8.025673 -82.561400 11.212845
|
||||
CUB Cuba -84.952927 19.821945 -74.131255 23.194027
|
||||
SLV El Salvador -90.108064 13.156391 -87.694673 14.431982
|
||||
HND Honduras -89.350491 12.985173 -83.131855 16.435827
|
||||
NIC Nicaragua -87.689827 10.709691 -83.131855 15.022218
|
||||
PAN Panama -83.030291 7.206109 -77.198336 9.620136
|
||||
AIA Anguilla -63.167782 18.164445 -62.972709 18.272982
|
||||
ATG Antigua & Barbuda -61.891109 16.989718 -61.666946 17.724300
|
||||
VGB British Virgin Is. -64.698482 18.383891 -64.324527 18.504854
|
||||
MSR Montserrat -62.236946 16.671391 -62.138891 16.812354
|
||||
PRI Puerto Rico -67.266400 17.922218 -65.301118 18.519445
|
||||
KNA St. Kitts & Nevis -62.862782 17.208882 -62.622509 17.410136
|
||||
VIR Virgin Is. -65.023509 17.676664 -64.562573 18.387673
|
||||
FRO Faroe Is. -7.433473 61.388327 -6.389718 62.357500
|
||||
GRL Greenland -73.053609 59.790273 -12.157637 83.623600
|
||||
XGK Guernsey -2.668609 49.422491 -2.500973 49.508191
|
||||
ISL Iceland -24.538400 63.390000 -13.499446 66.536100
|
||||
IRL Ireland -10.474727 51.445545 -6.013055 55.379991
|
||||
XIM Isle of Man -4.787155 54.055545 -4.308682 54.416382
|
||||
SJM Jan Mayen -9.119909 70.803863 -7.928509 71.180818
|
||||
XJE Jersey -2.247364 49.167773 -2.015000 49.261109
|
||||
GBR United Kingdom -8.171664 49.955273 1.749445 60.843327
|
||||
CPV Cape Verde -25.360555 14.811109 -22.666109 17.192364
|
||||
CIV Cote d'Ivoire -8.606382 4.344718 -2.487782 10.735254
|
||||
GHA Ghana -3.248891 4.727082 1.202782 11.155691
|
||||
GIB Gibraltar -5.356173 36.112073 -5.334509 36.163309
|
||||
LBR Liberia -11.492327 4.343609 -7.368400 8.512782
|
||||
MAR Morocco -13.174964 27.664236 -1.011809 35.919164
|
||||
PRT Portugal -31.289027 32.637500 -6.190455 42.150673
|
||||
ESP Spain -18.169864 27.637500 4.316945 43.764300
|
||||
ESH Western Sahara -17.101527 20.764100 -8.666391 27.666954
|
||||
BFA Burkina Faso -5.520837 9.395691 2.397927 15.082773
|
||||
GIN Guinea -15.080837 7.193927 -7.653373 12.677500
|
||||
GNB Guinea-Bissau -16.717773 10.925100 -13.643891 12.684718
|
||||
MLI Mali -12.244837 10.142154 4.251391 25.000273
|
||||
MRT Mauritania -17.075555 14.725636 -4.806109 27.290454
|
||||
SEN Senegal -17.532782 12.301745 -11.369927 16.690618
|
||||
SLE Sierra Leone -13.295609 6.923609 -10.264309 9.997500
|
||||
GMB The Gambia -16.821664 13.059973 -13.798609 13.826391
|
||||
DJI Djibouti 41.759854 10.942218 43.420409 12.708327
|
||||
ERI Eritrea 36.443282 12.363891 43.121382 17.994882
|
||||
ETH Ethiopia 32.991800 3.406664 47.988245 14.883609
|
||||
MNG Mongolia 87.761100 41.586654 119.931509 52.142773
|
||||
SDN Sudan 21.829100 3.493391 38.607500 22.232218
|
||||
UGA Uganda 29.574300 -1.476109 35.009718 4.222782
|
||||
ISR Gaza Strip 34.216663 31.216545 34.558891 31.596100
|
||||
IRQ Iraq 38.794700 29.061664 48.560691 37.383673
|
||||
ISR Israel 34.267582 29.486709 35.681109 33.270273
|
||||
JOR Jordan 34.960418 29.188891 39.301109 33.377591
|
||||
KAZ Kazakhstan 46.499163 40.594436 87.348209 55.442627
|
||||
NOR Norway 4.789582 57.987918 31.073536 71.154709
|
||||
RUS Russia -180.000000 41.196582 180.000000 81.851927
|
||||
SWE Sweden 11.113336 55.339164 24.167009 69.060300
|
||||
ISR West Bank 34.888191 31.350691 35.570609 32.546391
|
||||
DZA Algeria -8.667218 18.976391 11.986473 37.089854
|
||||
AND Andorra 1.421391 42.436382 1.781718 42.655964
|
||||
CMR Cameroon 8.502363 1.654164 16.207000 13.085000
|
||||
CAF Central African Republic 14.418891 2.221264 27.459718 11.000836
|
||||
LBY Libya 9.311391 19.499064 25.151663 33.171136
|
||||
MCO Monaco 7.390900 43.727545 7.439291 43.768300
|
||||
TUN Tunisia 7.492218 30.234391 11.581663 37.340409
|
||||
BEN Benin 0.776663 6.218718 3.855000 12.396654
|
||||
TCD Chad 13.461945 7.458536 24.002745 23.450554
|
||||
GNQ Equatorial Guinea 8.424163 0.930154 11.353891 3.763336
|
||||
KIR Kiribati -157.581700 1.335991 172.947509 2.033054
|
||||
NER Niger 0.166663 11.693273 15.996663 23.522309
|
||||
NGA Nigeria 2.692500 4.272845 14.649654 13.891500
|
||||
STP Sao Tome & Principe 6.465136 0.018336 7.463473 1.701245
|
||||
TGO Togo -0.149764 6.100545 1.797800 11.138536
|
||||
ALB Albania 19.288536 39.645000 21.053327 42.660345
|
||||
BIH Bosnia & Herzegovina 15.740591 42.565827 19.619782 45.265945
|
||||
HRV Croatia 13.504791 42.399991 19.425000 46.535827
|
||||
ITA Italy 6.623963 36.649164 18.514445 47.094582
|
||||
MKD Macedonia 20.458818 40.855891 23.030973 42.358954
|
||||
MLT Malta 14.329100 35.800000 14.570000 35.991936
|
||||
SMR San Marino 12.406945 43.898682 12.511109 43.986873
|
||||
SMN Serbia & Montenegro 18.453327 41.849000 23.005000 46.181109
|
||||
VTC Vatican City 12.444473 41.900891 12.457718 41.908391
|
||||
BGR Bulgaria 22.365273 41.243045 28.605136 44.224718
|
||||
CYP Cyprus 32.269863 34.640273 34.586036 35.688609
|
||||
EGY Egypt 24.706800 21.994164 36.895827 31.646945
|
||||
GEO Georgia 40.002963 41.048045 46.710818 43.584718
|
||||
GRC Greece 19.640000 34.930545 28.238045 41.747773
|
||||
LBN Lebanon 35.100827 33.062082 36.623745 34.647500
|
||||
SYR Syria 35.614463 32.313609 42.378327 37.290545
|
||||
TUR Turkey 25.665827 35.818445 44.820545 42.109991
|
||||
AUT Austria 9.533573 46.407491 17.166382 49.018745
|
||||
CZE Czech Republic 12.093700 48.581382 18.852218 51.052491
|
||||
DNK Denmark 8.092918 54.561936 15.149163 57.745973
|
||||
HUN Hungary 16.111800 45.748327 22.894800 48.576173
|
||||
POL Poland 14.147636 49.002918 24.143473 54.836036
|
||||
SVK Slovakia 16.844718 47.737500 22.558054 49.600827
|
||||
SVN Slovenia 13.383473 45.425818 16.607873 46.876245
|
||||
SJM Svalbard 10.487918 74.343045 33.637500 80.764163
|
||||
BEL Belgium 2.541663 49.508882 6.398200 51.501245
|
||||
FRA France -4.790282 41.364927 9.562218 51.091109
|
||||
DEU Germany 5.865000 47.274718 15.033818 55.056527
|
||||
LIE Liechtenstein 9.474636 47.057454 9.633891 47.274545
|
||||
LUX Luxembourg 5.734445 49.448464 6.524027 50.181809
|
||||
NLD Netherlands 3.370863 50.753882 7.210973 53.465827
|
||||
CHE Switzerland 5.967009 45.829436 10.488209 47.806664
|
||||
USA United States -178.216555 18.925482 179.775936 71.351436
|
||||
BLR Belarus 23.165400 51.251845 32.740054 56.167491
|
||||
EST Estonia 21.837354 57.522636 28.194091 59.664718
|
||||
FIN Finland 19.511391 59.806800 31.581963 70.088609
|
||||
LVA Latvia 20.968609 55.674836 28.235963 58.083254
|
||||
LTU Lithuania 20.942836 53.890336 26.813054 56.449854
|
||||
MDA Moldova 26.634991 45.448645 30.128709 48.468318
|
||||
ROM Romania 20.261027 43.623309 29.672218 48.263882
|
||||
UKR Ukraine 22.151445 44.379154 40.178745 52.378600
|
||||
IND India 68.144227 6.745827 97.380536 35.505618
|
||||
MDV Maldives 72.863391 -0.641664 73.637272 7.027773
|
||||
OMN Oman 51.999291 16.642782 59.847082 26.368709
|
||||
SOM Somalia 40.988609 -1.674873 51.411318 11.979164
|
||||
LKA Sri Lanka 79.696091 5.918054 81.891663 9.828191
|
||||
TKM Turkmenistan 51.250182 35.145991 66.670882 42.796173
|
||||
UZB Uzbekistan 55.997491 37.184991 73.167545 45.570591
|
||||
YEM Yemen 42.555973 12.144718 54.473473 18.999345
|
||||
ARM Armenia 43.454163 38.841145 46.620536 41.297054
|
||||
AZE Azerbaijan 44.778863 38.262809 51.677009 42.710754
|
||||
BHR Bahrain 50.453327 25.571945 50.796391 26.288891
|
||||
IRN Iran 44.034954 25.075973 63.330273 39.779154
|
||||
KWT Kuwait 46.546945 28.538882 48.416591 30.084164
|
||||
QAT Qatar 50.751936 24.556045 51.615827 26.152500
|
||||
SAU Saudi Arabia 34.572145 16.377500 55.666109 32.154945
|
||||
ARE United Arab Emirates 51.583327 22.633327 56.381663 26.083882
|
||||
AFG Afghanistan 60.504163 29.406109 74.915736 38.471982
|
||||
KGZ Kyrgyzstan 69.249500 39.195473 80.281582 43.216900
|
||||
NPL Nepal 80.052200 26.368364 88.194554 30.424718
|
||||
PAK Pakistan 60.866300 23.688045 77.823927 37.060791
|
||||
TJK Tajikistan 67.364700 36.671845 75.187482 41.049254
|
||||
BGD Bangladesh 88.043872 20.744818 92.669345 26.626136
|
||||
BTN Bhutan 88.751936 26.703609 92.114218 28.325000
|
||||
BRN Brunei 114.095082 4.018191 115.360263 5.053054
|
||||
CHN China 73.620045 18.168882 134.768463 53.553745
|
||||
JPN Japan 123.678863 24.251391 145.812409 45.486382
|
||||
PRK North Korea 124.323954 37.671382 130.697418 43.006100
|
||||
PLW Palau 134.452482 7.305254 134.658872 7.729445
|
||||
PHL Philippines 116.950000 5.049164 126.598036 19.391109
|
||||
KOR South Korea 126.099018 33.192209 129.586872 38.625245
|
||||
KHM Cambodia 102.346509 10.422736 107.636382 14.708618
|
||||
LAO Laos 100.091372 13.926664 107.695254 22.499927
|
||||
MYS Malaysia 99.641936 0.852782 119.275818 7.352918
|
||||
MMR Myanmar 92.204991 9.839582 101.169427 28.546527
|
||||
SGP Singapore 103.640945 1.259027 103.997945 1.445282
|
||||
THA Thailand 97.347272 5.633473 105.639291 20.454582
|
||||
VNM Vietnam 102.140745 8.559236 109.464845 23.324164
|
||||
GUM Guam 144.634154 13.235000 144.953309 13.652291
|
||||
MHL Marshall Is. 162.324963 5.600273 171.378063 14.594027
|
||||
FSM Micronesia 158.120100 5.261664 163.042891 6.977636
|
||||
MNP Northern Mariana Is. 145.572682 14.908054 145.818082 15.268191
|
||||
UMI Wake I. 166.608981 19.279445 166.662200 19.324582
|
||||
BWA Botswana 19.996109 -26.875555 29.373618 -17.782082
|
||||
BDI Burundi 28.985000 -4.448055 30.853191 -2.301564
|
||||
ATF French Southern & Antarctic Lands 51.650836 -49.725009 70.567491 -46.327645
|
||||
HMD Heard I. & McDonald Is. 73.234709 -53.199445 73.773882 -52.965145
|
||||
KEN Kenya 33.907218 -4.669618 41.905163 4.622500
|
||||
RWA Rwanda 28.854445 -2.825491 30.893263 -1.054446
|
||||
TZA Tanzania 29.340827 -11.740418 40.436809 -0.997218
|
||||
ZMB Zambia 21.996391 -18.074918 33.702282 -8.191664
|
||||
ZWE Zimbabwe 25.237918 -22.414764 33.071591 -15.616527
|
||||
ATA Antarctica -180.000000 -90.000000 180.000000 -60.503336
|
||||
NOR Bouvet I. 3.342363 -54.462782 3.484163 -54.383609
|
||||
COM Comoros 43.214027 -12.383055 44.530418 -11.366946
|
||||
REU Juan De Nova I. 42.723818 -17.076118 42.760900 -17.052018
|
||||
LSO Lesotho 27.013973 -30.650527 29.455554 -28.570691
|
||||
MWI Malawi 32.681873 -17.135282 35.920963 -9.376673
|
||||
MOZ Mozambique 30.213018 -26.860282 40.846109 -10.471109
|
||||
ZAF South Africa 16.483327 -46.969727 37.892218 -22.136391
|
||||
SWZ Swaziland 30.798336 -27.316391 32.133400 -25.728336
|
||||
AGO Angola 11.731245 -18.016391 24.084445 -4.388991
|
||||
COG Congo 11.140663 -5.015000 18.643609 3.711109
|
||||
ZAR Congo, DRC 12.214554 -13.458055 31.302773 5.380691
|
||||
FJI Fiji -180.000000 -19.162782 180.000000 -16.153473
|
||||
GAB Gabon 8.700836 -3.925282 14.519582 2.317900
|
||||
NAM Namibia 11.716391 -28.961873 25.264427 -16.954173
|
||||
NZL New Zealand -176.848755 -52.578055 178.841063 -34.414718
|
||||
IOT British Indian Ocean Territory 72.357900 -7.436246 72.494282 -7.233473
|
||||
REU Glorioso Is. 47.279091 -11.577782 47.303054 -11.554100
|
||||
MDG Madagascar 43.236827 -25.588336 50.501391 -11.945555
|
||||
MUS Mauritius 57.306309 -20.520555 63.495754 -19.673336
|
||||
MYT Mayotte 45.039163 -12.992500 45.293345 -12.662500
|
||||
REU Reunion 55.220554 -21.373891 55.853054 -20.856527
|
||||
SYC Seychelles 46.205691 -9.463055 55.540554 -4.551664
|
||||
CXR Christmas I. 105.629000 -10.510973 105.751900 -10.384082
|
||||
CCK Cocos Is. 96.817491 -12.199446 96.864845 -12.130418
|
||||
IDN Indonesia 95.210945 -10.929655 141.007018 5.913473
|
||||
TLS Timor Leste 124.046100 -9.463627 127.308591 -8.140000
|
||||
AUS Australia 112.907209 -54.753891 158.960372 -10.135691
|
||||
NRU Nauru 166.904418 -0.552218 166.957045 -0.493336
|
||||
NCL New Caledonia 163.982745 -22.673891 168.130509 -20.087918
|
||||
NFK Norfolk I. 167.910945 -29.081109 167.998872 -29.000555
|
||||
PNG Papua New Guinea 140.858854 -11.642500 155.966845 -1.355282
|
||||
SLB Solomon Is. 155.671300 -11.845836 166.931836 -6.605518
|
||||
TUV Tuvalu 176.295254 -8.561291 179.232281 -6.089446
|
||||
VUT Vanuatu 166.521636 -20.254173 169.893863 -13.707218
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,52 @@
|
|||
#id name shape
|
||||
HI Hawaii -160.242406 18.921786 -154.791096 22.229120
|
||||
WA Washington -124.732769 45.543092 -116.919132 48.999931
|
||||
MT Montana -116.063531 44.353639 -104.043072 49.000026
|
||||
ME Maine -71.087509 43.091050 -66.969271 47.453334
|
||||
ND North Dakota -104.062991 45.930822 -96.551931 49.000026
|
||||
SD South Dakota -104.061036 42.488459 -96.439394 45.943547
|
||||
WY Wyoming -111.053428 40.994289 -104.051705 45.002793
|
||||
WI Wisconsin -92.885397 42.489152 -86.967712 46.952479
|
||||
ID Idaho -117.236921 41.994599 -111.046771 48.999950
|
||||
VT Vermont -73.436000 42.725852 -71.505372 45.013351
|
||||
MN Minnesota -97.229436 43.498102 -89.530673 49.371730
|
||||
OR Oregon -124.559617 41.987672 -116.470418 46.236091
|
||||
NH New Hampshire -72.553428 42.698603 -70.734139 45.301469
|
||||
IA Iowa -96.640709 40.371946 -90.142796 43.501457
|
||||
MA Massachusetts -73.498840 41.238279 -69.917780 42.886877
|
||||
NE Nebraska -104.056219 39.992595 -95.308697 43.003062
|
||||
NY New York -79.763235 40.506003 -71.869986 45.006138
|
||||
PA Pennsylvania -80.526045 39.719313 -74.700062 42.267327
|
||||
CT Connecticut -73.725237 40.998392 -71.788249 42.047428
|
||||
RI Rhode Island -71.866678 41.322769 -71.117132 42.013713
|
||||
NJ New Jersey -75.570234 38.956682 -73.896148 41.350573
|
||||
IN Indiana -88.101490 37.776224 -84.787446 41.765540
|
||||
NV Nevada -119.996324 34.998914 -114.037392 41.996637
|
||||
UT Utah -114.047273 36.991746 -109.043206 42.002300
|
||||
CA California -124.392638 32.535781 -114.125230 42.002191
|
||||
OH Ohio -84.812070 38.400511 -80.519996 41.986872
|
||||
IL Illinois -91.516284 36.986822 -87.507909 42.509363
|
||||
DC District of Columbia -77.122328 38.788234 -76.910904 38.993541
|
||||
DE Delaware -75.791094 38.449602 -75.045623 39.840119
|
||||
WV West Virginia -82.647158 37.204910 -77.727467 40.637203
|
||||
MD Maryland -79.489865 37.970255 -75.045623 39.725461
|
||||
CO Colorado -109.055861 36.988994 -102.037207 41.003375
|
||||
KY Kentucky -89.568231 36.496570 -81.959575 39.142063
|
||||
KS Kansas -102.051535 36.988875 -94.601224 40.002987
|
||||
VA Virginia -83.675177 36.541623 -75.242219 39.456998
|
||||
MO Missouri -95.767479 35.989656 -89.105034 40.609784
|
||||
AZ Arizona -114.821761 31.335634 -109.045615 37.003926
|
||||
OK Oklahoma -102.997709 33.621136 -94.428552 37.001478
|
||||
NC North Carolina -84.323773 33.882164 -75.456580 36.589767
|
||||
TN Tennessee -90.305448 34.988759 -81.652272 36.679683
|
||||
TX Texas -106.650062 25.845557 -93.507389 36.493912
|
||||
NM New Mexico -109.051346 31.343453 -102.997401 36.999760
|
||||
AL Alabama -88.472952 30.233604 -84.894016 35.016033
|
||||
MS Mississippi -91.643682 30.194935 -88.090468 35.005041
|
||||
GA Georgia -85.608960 30.361291 -80.894753 35.000366
|
||||
SC South Carolina -83.350685 32.068173 -78.579453 35.208356
|
||||
AR Arkansas -94.617257 33.010151 -89.645479 36.492811
|
||||
LA Louisiana -94.041785 28.939655 -89.021803 33.023422
|
||||
FL Florida -87.625711 24.956376 -80.050911 31.003157
|
||||
MI Michigan -90.408200 41.697494 -82.419836 48.173795
|
||||
AK Alaska -178.217598 51.583032 -129.992235 71.406235
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,3 @@
|
|||
WY CO @ Intersects(-106.964844 39.460938 -105.734375 42.800781)
|
||||
TX @ Intersects(-99.669922 30.583984 -98.439453 32.253906)
|
||||
MS TX LA @ Intersects(-95.363281 29.792969 -90.133789 32.473633)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue