David Wayne Smiley 2012-02-20 22:45:32 +00:00
parent c94f72c7f3
commit 66a852234d
101 changed files with 35321 additions and 0 deletions

View File

@ -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.

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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 +
'}';
}
}

View File

@ -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();
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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]);
}
}
}

View File

@ -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 );
}
}

View File

@ -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 );
}
};
}

View File

@ -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 );
}
}

View File

@ -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;

View File

@ -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 : "");
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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
}

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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 + ')';
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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 {
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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];
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
};
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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() {}
}

View File

@ -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 );
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}
}
}
}

View File

@ -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() );
}
}

View File

@ -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.
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}
}

View File

@ -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 );
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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