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