LUCENE-3795: Replace contrib/spatial with modules/spatial (merge from branch)

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1300409 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Ryan McKinley 2012-03-13 23:28:17 +00:00
commit 2ac613aef9
115 changed files with 30665 additions and 5115 deletions

View File

@ -14,10 +14,8 @@
<classpathentry kind="src" path="lucene/contrib/misc/src/test"/>
<classpathentry kind="src" path="lucene/contrib/sandbox/src/java"/>
<classpathentry kind="src" path="lucene/contrib/sandbox/src/test"/>
<classpathentry kind="src" path="lucene/contrib/spatial/src/java"/>
<classpathentry kind="src" path="lucene/contrib/spatial/src/test"/>
<classpathentry kind="src" path="lucene/test-framework/src/java"/>
<classpathentry kind="src" output="bin.tests-framework" path="lucene/test-framework/src/resources"/>
<classpathentry kind="src" output="bin.tests-framework" path="lucene/test-framework/src/resources"/>
<classpathentry kind="src" path="modules/analysis/common/src/java"/>
<classpathentry kind="src" path="modules/analysis/common/src/resources"/>
<classpathentry kind="src" path="modules/analysis/common/src/test"/>
@ -55,6 +53,10 @@
<classpathentry kind="src" path="modules/queryparser/src/test"/>
<classpathentry kind="src" path="modules/suggest/src/java"/>
<classpathentry kind="src" path="modules/suggest/src/test"/>
<classpathentry kind="src" path="modules/spatial/src/java"/>
<classpathentry kind="src" path="modules/spatial/src/test"/>
<classpathentry kind="src" path="modules/spatial/src/test-files"/>
<classpathentry kind="lib" path="modules/spatial/lib/spatial4j-0.2.jar"/>
<classpathentry kind="src" path="solr/core/src/java"/>
<classpathentry kind="src" path="solr/core/src/test"/>
<classpathentry kind="src" path="solr/core/src/test-files"/>

View File

@ -8,7 +8,6 @@
<buildFile url="file://$PROJECT_DIR$/lucene/contrib/memory/build.xml" />
<buildFile url="file://$PROJECT_DIR$/lucene/contrib/misc/build.xml" />
<buildFile url="file://$PROJECT_DIR$/lucene/contrib/sandbox/build.xml" />
<buildFile url="file://$PROJECT_DIR$/lucene/contrib/spatial/build.xml" />
<buildFile url="file://$PROJECT_DIR$/lucene/core/build.xml" />
<buildFile url="file://$PROJECT_DIR$/lucene/tools/build.xml" />
<buildFile url="file://$PROJECT_DIR$/lucene/test-framework/build.xml" />
@ -26,6 +25,7 @@
<buildFile url="file://$PROJECT_DIR$/modules/join/build.xml" />
<buildFile url="file://$PROJECT_DIR$/modules/queries/build.xml" />
<buildFile url="file://$PROJECT_DIR$/modules/queryparser/build.xml" />
<buildFile url="file://$PROJECT_DIR$/modules/spatial/build.xml" />
<buildFile url="file://$PROJECT_DIR$/modules/suggest/build.xml" />
<buildFile url="file://$PROJECT_DIR$/solr/build.xml" />
<buildFile url="file://$PROJECT_DIR$/solr/core/build.xml" />

View File

@ -9,7 +9,6 @@
<module filepath="$PROJECT_DIR$/lucene/contrib/memory/memory.iml" />
<module filepath="$PROJECT_DIR$/lucene/contrib/misc/misc.iml" />
<module filepath="$PROJECT_DIR$/lucene/contrib/sandbox/sandbox.iml" />
<module filepath="$PROJECT_DIR$/lucene/contrib/spatial/spatial.iml" />
<module filepath="$PROJECT_DIR$/modules/analysis/common/analysis-common.iml" />
<module filepath="$PROJECT_DIR$/modules/analysis/icu/icu.iml" />
<module filepath="$PROJECT_DIR$/modules/analysis/kuromoji/kuromoji.iml" />
@ -24,6 +23,7 @@
<module filepath="$PROJECT_DIR$/modules/join/join.iml" />
<module filepath="$PROJECT_DIR$/modules/queries/queries.iml" />
<module filepath="$PROJECT_DIR$/modules/queryparser/queryparser.iml" />
<module filepath="$PROJECT_DIR$/modules/spatial/spatial.iml" />
<module filepath="$PROJECT_DIR$/modules/suggest/suggest.iml" />
<module filepath="$PROJECT_DIR$/solr/solr.iml" />
<module filepath="$PROJECT_DIR$/solr/contrib/analysis-extras/analysis-extras.iml" />

View File

@ -172,10 +172,10 @@
<option name="VM_PARAMETERS" value="-ea -Dtests.luceneMatchVersion=4.0 -DtempDir=temp -Djetty.testMode=1 -Djetty.insecurerandom=1 -Dsolr.directoryFactory=org.apache.solr.core.MockDirectoryFactory" />
<option name="TEST_SEARCH_SCOPE"><value defaultName="singleModule" /></option>
</configuration>
<configuration default="false" name="spatial contrib" type="JUnit" factoryName="JUnit">
<configuration default="false" name="spatial module" type="JUnit" factoryName="JUnit">
<module name="spatial" />
<option name="TEST_OBJECT" value="package" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/lucene/build/contrib/spatial" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/modules/spatial/build" />
<option name="VM_PARAMETERS" value="-ea -DtempDir=temp" />
<option name="TEST_SEARCH_SCOPE"><value defaultName="singleModule" /></option>
</configuration>

View File

@ -1,19 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/../../build/contrib/spatial/classes/java" />
<output-test url="file://$MODULE_DIR$/../../build/contrib/spatial/classes/test" />
<output url="file://$MODULE_DIR$/build/classes/java" />
<output-test url="file://$MODULE_DIR$/build/classes/test" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test-files" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library" exported="">
<library>
<CLASSES>
<root url="file://$MODULE_DIR$/lib" />
</CLASSES>
<JAVADOC />
<SOURCES />
<jarDirectory url="file://$MODULE_DIR$/lib" recursive="false" />
</library>
</orderEntry>
<orderEntry type="library" scope="TEST" name="JUnit" level="project" />
<orderEntry type="module" module-name="misc" />
<orderEntry type="module" module-name="lucene" />
<orderEntry type="module" module-name="lucene" exported="" />
<orderEntry type="module" module-name="queries" />
<orderEntry type="module" module-name="analysis-common" />
</component>
</module>

View File

@ -36,7 +36,6 @@
<module>memory</module>
<module>misc</module>
<module>sandbox</module>
<module>spatial</module>
</modules>
<build>
<directory>build/lucene-contrib-aggregator</directory>

View File

@ -38,6 +38,7 @@
<module>join</module>
<module>queries</module>
<module>queryparser</module>
<module>spatial</module>
<module>suggest</module>
</modules>
<build>

View File

@ -1,83 +1,80 @@
<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">
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-parent</artifactId>
<version>@version@</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-spatial</artifactId>
<packaging>jar</packaging>
<name>Lucene Spatial</name>
<description>Spatial search package</description>
<properties>
<module-directory>lucene/contrib/spatial</module-directory>
<build-directory>../../build/contrib/spatial</build-directory>
</properties>
<scm>
<connection>
scm:svn:http://svn.apache.org/repos/asf/lucene/dev/trunk/${module-directory}
</connection>
<developerConnection>
scm:svn:https://svn.apache.org/repos/asf/lucene/dev/trunk/${module-directory}
</developerConnection>
<url>
http://svn.apache.org/viewvc/lucene/dev/trunk/${module-directory}
</url>
</scm>
<dependencies>
<dependency>
<!-- lucene-test-framework dependency must be declared before lucene-core -->
<groupId>${project.groupId}</groupId>
<artifactId>lucene-test-framework</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>lucene-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>lucene-queries</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<directory>${build-directory}</directory>
<outputDirectory>${build-directory}/classes/java</outputDirectory>
<testOutputDirectory>${build-directory}/classes/test</testOutputDirectory>
<sourceDirectory>src/java</sourceDirectory>
<testSourceDirectory>src/test</testSourceDirectory>
<testResources>
<testResource>
<directory>${project.build.testSourceDirectory}</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</testResource>
</testResources>
</build>
</project>
<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">
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-parent</artifactId>
<version>@version@</version>
<relativePath>../../../lucene/pom.xml</relativePath>
</parent>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-spatial</artifactId>
<packaging>jar</packaging>
<name>Lucene Spatial</name>
<description>
Spatial Strategies for Apache Lucene
</description>
<properties>
<module-directory>modules/spatial</module-directory>
<build-directory>../build</build-directory>
</properties>
<dependencies>
<dependency>
<!-- lucene-test-framework dependency must be declared before lucene-core -->
<groupId>${project.groupId}</groupId>
<artifactId>lucene-test-framework</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.spatial4j</groupId>
<artifactId>spatial4j</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>lucene-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>lucene-queries</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<directory>${build-directory}</directory>
<outputDirectory>${build-directory}/classes/java</outputDirectory>
<testOutputDirectory>${build-directory}/classes/test</testOutputDirectory>
<sourceDirectory>src/java</sourceDirectory>
<testSourceDirectory>src/test</testSourceDirectory>
<testResources>
<testResource>
<directory>src/test-files</directory>
</testResource>
</testResources>
</build>
</project>

View File

@ -344,6 +344,11 @@
<artifactId>javax.servlet</artifactId>
<version>3.0.0.v201112011016</version>
</dependency>
<dependency>
<groupId>com.spatial4j</groupId>
<artifactId>spatial4j</artifactId>
<version>0.2</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>

View File

@ -682,6 +682,10 @@ Optimizations
should be rounded up for better performance. This option only applies for docvalues types bytes fixed sorted
and bytes var sorted. (Simon Willnauer, Martijn van Groningen)
* LUCENE-3795: Replace contrib/spatial with modules/spatial. This includes
a basic spatial strategy interface. (David Smiley, Chris Male, ryan)
Bug fixes
* LUCENE-2803: The FieldCache can miss values if an entry for a reader

View File

@ -264,7 +264,6 @@
<packageset dir="contrib/memory/src/java"/>
<packageset dir="contrib/misc/src/java"/>
<packageset dir="contrib/sandbox/src/java"/>
<packageset dir="contrib/spatial/src/java"/>
<!-- end alpha sort -->
<!-- If the main javadoc Group listing includes an "Other -->
@ -279,7 +278,6 @@
<group title="contrib: Memory" packages="org.apache.lucene.index.memory*"/>
<group title="contrib: Misc " packages="org.apache.lucene.misc*"/>
<group title="contrib: Sandbox" packages="org.apache.lucene.sandbox*"/>
<group title="contrib: Spatial" packages="org.apache.lucene.spatial*"/>
</sources>
</invoke-javadoc>

View File

@ -228,12 +228,12 @@
<property name="sandbox.uptodate" value="true"/>
</target>
<property name="spatial.jar" value="${common.dir}/build/contrib/spatial/lucene-spatial-${version}.jar"/>
<property name="spatial.jar" value="${common.dir}/../modules/spatial/build/lucene-spatial-${version}.jar"/>
<target name="check-spatial-uptodate" unless="spatial.uptodate">
<contrib-uptodate name="spatial" jarfile="${spatial.jar}" property="spatial.uptodate"/>
<module-uptodate name="spatial" jarfile="${spatial.jar}" property="spatial.uptodate"/>
</target>
<target name="jar-spatial" unless="spatial.uptodate" depends="check-spatial-uptodate">
<ant dir="${common.dir}/contrib/spatial" target="jar-core" inheritAll="false">
<ant dir="${common.dir}/../modules/spatial" target="jar-core" inheritAll="false">
<propertyset refid="uptodate.and.compiled.properties"/>
</ant>
<property name="spatial.uptodate" value="true"/>

View File

@ -1,34 +0,0 @@
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT 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 name="spatial" default="default">
<description>
Lucene Spatial Indexing
</description>
<import file="../contrib-build.xml"/>
<path id="classpath">
<pathelement path="${queries.jar}"/>
<path refid="base.classpath"/>
</path>
<target name="compile-core" depends="jar-queries, common.compile-core" />
</project>

View File

@ -1,465 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.geometry.DistanceUnits;
import org.apache.lucene.spatial.geometry.FloatLatLng;
import org.apache.lucene.spatial.geometry.LatLng;
import org.apache.lucene.spatial.geometry.shape.LLRect;
import org.apache.lucene.spatial.geometry.shape.Rectangle;
import org.apache.lucene.spatial.tier.InvalidGeoException;
/**
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class DistanceUtils {
public static final double DEGREES_TO_RADIANS = Math.PI / 180.0;
public static final double RADIANS_TO_DEGREES = 180.0 / Math.PI;
//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.609344;
/**
* 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.009;
public static final double EARTH_MEAN_RADIUS_MI = EARTH_MEAN_RADIUS_KM / MILES_TO_KM;
public static final double EARTH_EQUATORIAL_RADIUS_MI = 3963.205;
public static final double EARTH_EQUATORIAL_RADIUS_KM = EARTH_EQUATORIAL_RADIUS_MI * MILES_TO_KM;
public static double getDistanceMi(double x1, double y1, double x2, double y2) {
return getLLMDistance(x1, y1, x2, y2);
}
/**
*
* @param x1
* @param y1
* @param miles
* @return boundary rectangle where getY/getX is top left, getMinY/getMinX is bottom right
*/
public static Rectangle getBoundary (double x1, double y1, double miles) {
LLRect box = LLRect.createBox( new FloatLatLng( x1, y1 ), miles, miles );
//System.out.println("Box: "+maxX+" | "+ maxY+" | "+ minX + " | "+ minY);
return box.toRectangle();
}
public static double getLLMDistance (double x1, double y1, double x2, double y2) {
LatLng p1 = new FloatLatLng( x1, y1 );
LatLng p2 = new FloatLatLng( x2, y2 );
return p1.arcDistance( p2, DistanceUnits.MILES );
}
/**
* distance/radius.
* @param distance The distance travelled
* @param radius The radius of the sphere
* @return The angular distance, in radians
*/
public static double angularDistance(double distance, double radius){
return distance/radius;
}
/**
* Calculate the p-norm (i.e. length) beteen two vectors
*
* @param vec1 The first vector
* @param vec2 The second vector
* @param power The power (2 for Euclidean 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 Euclidean 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(squaredEuclideanDistance(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 approximatation).
*
* @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, similarily for cosine
for (int i = 0; i < center.length; i++) {
result[i] = center[i] + distance;
}
return result;
}
/**
* @param latCenter In degrees
* @param lonCenter In degrees
* @param distance The distance
* @param result A preallocated array to hold the results. If null, a new one is constructed.
* @param upperRight If true, calculate the upper right corner, else the lower left
* @param sphereRadius The radius of the sphere to use.
* @return The Lat/Lon in degrees
*
* @see #latLonCorner(double, double, double, double[], boolean, double)
*/
public static double[] latLonCornerDegs(double latCenter, double lonCenter,
double distance, double [] result,
boolean upperRight, double sphereRadius) {
result = latLonCorner(latCenter * DEGREES_TO_RADIANS,
lonCenter * DEGREES_TO_RADIANS, distance, result, upperRight, sphereRadius);
result[0] = result[0] * RADIANS_TO_DEGREES;
result[1] = result[1] * RADIANS_TO_DEGREES;
return result;
}
/**
* Uses Haversine to calculate the corner of a box (upper right or lower left) that is the <i>distance</i> away, given a sphere of the specified <i>radius</i>.
*
* NOTE: This is not the same as calculating a box that transcribes a circle of the given distance.
*
* @param latCenter In radians
* @param lonCenter In radians
* @param distance The distance
* @param result A preallocated array to hold the results. If null, a new one is constructed.
* @param upperRight If true, give lat/lon for the upper right corner, else lower left
* @param sphereRadius The radius to use for the calculation
* @return The Lat/Lon in Radians
*/
public static double[] latLonCorner(double latCenter, double lonCenter,
double distance, double [] result, boolean upperRight, double sphereRadius) {
// Haversine formula
double brng = upperRight ? DEG_45_AS_RADS : DEG_225_AS_RADS;
result = pointOnBearing(latCenter, lonCenter, distance, brng, result, sphereRadius);
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 distance The distance to travel along the bearing. The units are assumed to be the same as the sphereRadius units, both of which is up to the caller to know
* @param bearing The bearing, in radians. North is a 0 deg. bearing, east is 90 deg, south is 180 deg, west is 270 deg.
* @param result A preallocated array to hold the results. If null, a new one is constructed.
* @param sphereRadius The radius of the sphere to use for the calculation.
* @return The destination point, in radians. First entry is latitude, second is longitude
*/
public static double[] pointOnBearing(double startLat, double startLon, double distance, double bearing, double[] result, double sphereRadius) {
/*
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(distance / sphereRadius);
double cosStartLat = Math.cos(startLat);
double sinAngDist = Math.sin(distance / sphereRadius);
double lat2 = Math.asin(Math.sin(startLat) * cosAngDist +
cosStartLat * sinAngDist * Math.cos(bearing));
double lon2 = startLon + Math.atan2(Math.sin(bearing) * sinAngDist * cosStartLat,
cosAngDist - Math.sin(startLat) * Math.sin(lat2));
/*lat2 = (lat2*180)/Math.PI;
lon2 = (lon2*180)/Math.PI;*/
//From Lucene. Move back to Lucene when synced
// normalize long first
if (result == null || result.length != 2){
result = new double[2];
}
result[0] = lat2;
result[1] = lon2;
normLng(result);
// normalize lat - could flip poles
normLat(result);
return result;
}
/**
* @param latLng The lat/lon, in radians. lat in position 0, long in position 1
*/
public static void normLat(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, long in position 1
*/
public static void normLng(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;
}
}
/**
* The square of the Euclidean 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 Euclidean distance
*/
public static double squaredEuclideanDistance(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;
}
/**
* Computes the haversine distance between two points. The arguments are in radians and provided in lat,lon order.
* @param y1 The y coordinate of the first point, in radians
* @param x1 The x coordinate of the first point, in radians
* @param y2 The y coordinate of the second point, in radians
* @param x2 The x coordinate of the second point, in radians
* @param radius The radius of the sphere
* @return The distance between the two points, as determined by the haversine formula.
*/
public static double haversine(double y1, double x1, double y2, double x2, double radius) {
double result = 0;
//make sure they aren't all the same, as then we can just return 0
if ((x1 != x2) || (y1 != y2)) {
double diffX = x1 - x2;
double diffY = y1 - y2;
double hsinX = Math.sin(diffX * 0.5);
double hsinY = Math.sin(diffY * 0.5);
double h = hsinY * hsinY +
(Math.cos(y1) * Math.cos(y2) * hsinX * hsinX);
result = (radius * 2 * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h)));
}
return result;
}
/**
* 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.tier.InvalidGeoException 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 InvalidGeoException {
//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 InvalidGeoException("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 InvalidGeoException 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 InvalidGeoException{
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 InvalidGeoException("incompatible dimension (" + dimension +
") and values (" + externalVal + "). Only " + i + " values specified");
}
return out;
}
public static final double[] parseLatitudeLongitude(String latLonStr) throws InvalidGeoException {
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 InvalidGeoException if there was an error parsing
*/
public static final double[] parseLatitudeLongitude(double[] latLon, String latLonStr) throws InvalidGeoException {
if (latLon == null) {
latLon = new double[2];
}
double[] toks = parsePointDouble(null, latLonStr, 2);
if (toks[0] < -90.0 || toks[0] > 90.0) {
throw new InvalidGeoException(
"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 InvalidGeoException(
"Invalid longitude: longitudes are range -180 to 180: provided lon: ["
+ toks[1] + "]");
}
latLon[1] = toks[1];
return latLon;
}
}

View File

@ -1,131 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.geohash;
import java.io.IOException;
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.search.FieldCache;
import org.apache.lucene.search.FieldCache.DocTerms;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.DocIdSet;
import org.apache.lucene.search.FilteredDocIdSet;
import org.apache.lucene.spatial.DistanceUtils;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.spatial.tier.DistanceFilter;
/** <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class GeoHashDistanceFilter extends DistanceFilter {
private double lat;
private double lng;
private String geoHashField;
/**
* Provide a distance filter based from a center point with a radius
* in miles
* @param startingFilter
* @param lat
* @param lng
* @param miles
*/
public GeoHashDistanceFilter(Filter startingFilter, double lat, double lng, double miles, String geoHashField) {
super(startingFilter, miles);
this.lat = lat;
this.lng = lng;
this.geoHashField = geoHashField;
}
@Override
public DocIdSet getDocIdSet(AtomicReaderContext context, Bits acceptDocs) throws IOException {
final DocTerms geoHashValues = FieldCache.DEFAULT.getTerms(context.reader(), geoHashField);
final BytesRef br = new BytesRef();
final int docBase = nextDocBase;
nextDocBase += context.reader().maxDoc();
return new FilteredDocIdSet(startingFilter.getDocIdSet(context, acceptDocs)) {
@Override
public boolean match(int doc) {
// TODO: cutover to BytesRef so we don't have to
// make String here
String geoHash = geoHashValues.getTerm(doc, br).utf8ToString();
double[] coords = GeoHashUtils.decode(geoHash);
double x = coords[0];
double y = coords[1];
// round off lat / longs if necessary
// x = DistanceHandler.getPrecision(x, precise);
// y = DistanceHandler.getPrecision(y, precise);
Double cachedDistance = distanceLookupCache.get(geoHash);
double d;
if (cachedDistance != null) {
d = cachedDistance.doubleValue();
} else {
d = DistanceUtils.getDistanceMi(lat, lng, x, y);
distanceLookupCache.put(geoHash, d);
}
if (d < distance){
distances.put(doc+docBase, d);
return true;
} else {
return false;
}
}
};
}
/** Returns true if <code>o</code> is equal to this. */
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof GeoHashDistanceFilter)) return false;
GeoHashDistanceFilter other = (GeoHashDistanceFilter) o;
if (!this.startingFilter.equals(other.startingFilter) ||
this.distance != other.distance ||
this.lat != other.lat ||
this.lng != other.lng ||
!this.geoHashField.equals(other.geoHashField) ) {
return false;
}
return true;
}
/** Returns a hash code value for this object.*/
@Override
public int hashCode() {
int h = Double.valueOf(distance).hashCode();
h ^= startingFilter.hashCode();
h ^= Double.valueOf(lat).hashCode();
h ^= Double.valueOf(lng).hashCode();
h ^= geoHashField.hashCode();
return h;
}
}

View File

@ -1,139 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.geohash;
import java.util.HashMap;
import java.util.Map;
/**
* 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'};
private final static Map<Character,Integer> DECODE_MAP = new HashMap<Character,Integer>();
private static final int PRECISION = 12;
private static final int[] BITS = {16, 8, 4, 2, 1};
static {
for (int i = 0; i < BASE_32.length; i++) {
DECODE_MAP.put(Character.valueOf(BASE_32[i]), Integer.valueOf(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 encode(double latitude, double longitude) {
double[] latInterval = {-90.0, 90.0};
double[] lngInterval = {-180.0, 180.0};
final StringBuilder geohash = new StringBuilder();
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 double[] decode(String geohash) {
final double[] latInterval = {-90.0, 90.0};
final double[] lngInterval = {-180.0, 180.0};
boolean isEven = true;
double latitude;
double longitude;
for (int i = 0; i < geohash.length(); i++) {
final int cd = DECODE_MAP.get(Character.valueOf(
geohash.charAt(i))).intValue();
for (int mask : BITS) {
if (isEven) {
if ((cd & mask) != 0) {
lngInterval[0] = (lngInterval[0] + lngInterval[1]) / 2D;
} else {
lngInterval[1] = (lngInterval[0] + lngInterval[1]) / 2D;
}
} else {
if ((cd & mask) != 0) {
latInterval[0] = (latInterval[0] + latInterval[1]) / 2D;
} else {
latInterval[1] = (latInterval[0] + latInterval[1]) / 2D;
}
}
isEven = !isEven;
}
}
latitude = (latInterval[0] + latInterval[1]) / 2D;
longitude = (lngInterval[0] + lngInterval[1]) / 2D;
return new double[] {latitude, longitude};
}
}

View File

@ -1,22 +0,0 @@
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<body>
Support for <a href="http://en.wikipedia.org/wiki/Geohash">Geohash</a> encoding, decoding, and filtering.
</body>
</html>

View File

@ -1,83 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.geometry;
/**
* Represents lat/lngs as fixed point numbers translated so that all
* world coordinates are in the first quadrant. The same fixed point
* scale as is used for FixedLatLng is employed.
*
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class CartesianPoint {
private int x;
private int y;
public CartesianPoint(int x, int y) {
this.x=x;
this.y=y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public String toString() {
return "Point(" + x + "," + y + ")";
}
/**
* Return a new point translated in the x and y dimensions
*/
public CartesianPoint translate(int deltaX, int deltaY) {
return new CartesianPoint(this.x+deltaX, this.y+deltaY);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CartesianPoint other = (CartesianPoint) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
}

View File

@ -1,110 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.geometry;
/**
* Enum representing difference distance units, currently only kilometers and
* miles
*/
public enum DistanceUnits {
MILES("miles", 3959, 24902),
KILOMETERS("km", 6371, 40076);
private static final double MILES_KILOMETRES_RATIO = 1.609344;
private final String unit;
private final double earthCircumference;
private final double earthRadius;
/**
* Creates a new DistanceUnit that represents the given unit
*
* @param unit 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 unit, double earthRadius, double earthCircumfence) {
this.unit = unit;
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.getUnit().equalsIgnoreCase(unit) || unit.equalsIgnoreCase("mi")) {
return MILES;
}
if (KILOMETERS.getUnit().equalsIgnoreCase(unit)) {
return KILOMETERS;
}
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;
}
return (this == MILES) ? distance / MILES_KILOMETRES_RATIO : distance * MILES_KILOMETRES_RATIO;
}
/**
* Returns the string representation of the distance unit
*
* @return String representation of the distance unit
*/
public String getUnit() {
return unit;
}
/**
* 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;
}
}

View File

@ -1,160 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.geometry;
/**
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class FixedLatLng extends LatLng {
public static final double SCALE_FACTOR=1000000;
public static final int SCALE_FACTOR_INT=1000000;
private int lat, lng;
private boolean normalized;
public FixedLatLng(int lat, int lng) {
setLat(lat);
setLng(lng);
}
public FixedLatLng(LatLng ll) {
this.lat=ll.getFixedLat();
this.lng=ll.getFixedLng();
}
protected void setLat(int lat) {
if (lat>90*SCALE_FACTOR || lat<-90*SCALE_FACTOR) {
throw new IllegalArgumentException("Illegal lattitude");
}
this.lat=lat;
}
protected void setLng(int lng) {
this.lng=lng;
}
public static double fixedToDouble(int fixed) {
return (fixed)/SCALE_FACTOR;
}
public static int doubleToFixed(double d) {
return (int)(d*SCALE_FACTOR);
}
@Override
public LatLng copy() {
return new FixedLatLng(this);
}
@Override
public int getFixedLat() {
return lat;
}
@Override
public int getFixedLng() {
return lng;
}
@Override
public double getLat() {
return fixedToDouble(lat);
}
@Override
public double getLng() {
return fixedToDouble(lng);
}
@Override
public boolean isFixedPoint() {
return true;
}
@Override
public FixedLatLng toFixed() {
return this;
}
@Override
public FloatLatLng toFloat() {
return new FloatLatLng(this);
}
@Override
public boolean isNormalized() {
return
normalized || (
(lng>=-180*SCALE_FACTOR_INT) &&
(lng<=180*SCALE_FACTOR_INT)
);
}
@Override
public LatLng normalize() {
if (isNormalized()) return this;
int delta=0;
if (lng<0) delta=360*SCALE_FACTOR_INT;
if (lng>=0) delta=-360*SCALE_FACTOR_INT;
int newLng=lng;
while (newLng<=-180*SCALE_FACTOR_INT || newLng>=180*SCALE_FACTOR_INT) {
newLng+=delta;
}
FixedLatLng ret=new FixedLatLng(lat, newLng);
ret.normalized=true;
return ret;
}
@Override
public LatLng calculateMidpoint(LatLng other) {
return new FixedLatLng(
(lat+other.getFixedLat())/2,
(lng+other.getFixedLng())/2);
}
@Override
public int hashCode() {
final int prime = 31;
int result = prime + lat;
result = prime * result + lng;
result = prime * result + (normalized ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (getClass() != obj.getClass())
return false;
FixedLatLng other = (FixedLatLng) obj;
if (lat != other.lat)
return false;
if (lng != other.lng)
return false;
if (normalized != other.normalized)
return false;
return true;
}
}

View File

@ -1,143 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.geometry;
/**
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class FloatLatLng extends LatLng {
private double lat;
private double lng;
private boolean normalized;
public FloatLatLng(double lat, double lng) {
if (lat>90.0 || lat<-90.0) throw new IllegalArgumentException("Illegal latitude value " + lat);
this.lat=lat;
this.lng=lng;
}
public FloatLatLng(LatLng ll) {
this.lat=ll.getLat();
this.lng=ll.getLng();
}
@Override
public LatLng copy() {
return new FloatLatLng(this);
}
@Override
public int getFixedLat() {
return FixedLatLng.doubleToFixed(this.lat);
}
@Override
public int getFixedLng() {
return FixedLatLng.doubleToFixed(this.lng);
}
@Override
public double getLat() {
return this.lat;
}
@Override
public double getLng() {
return this.lng;
}
@Override
public boolean isFixedPoint() {
return false;
}
@Override
public FixedLatLng toFixed() {
return new FixedLatLng(this);
}
@Override
public FloatLatLng toFloat() {
return this;
}
@Override
public boolean isNormalized() {
return
normalized || (
(lng>=-180) &&
(lng<=180)
);
}
@Override
public LatLng normalize() {
if (isNormalized()) return this;
double delta=0;
if (lng<0) delta=360;
if (lng>=0) delta=-360;
double newLng=lng;
while (newLng<=-180 || newLng>=180) {
newLng+=delta;
}
FloatLatLng ret=new FloatLatLng(lat, newLng);
ret.normalized=true;
return ret;
}
@Override
public LatLng calculateMidpoint(LatLng other) {
return new FloatLatLng(
(lat+other.getLat())/2.0,
(lng+other.getLng())/2.0);
}
@Override
public int hashCode() {
final int prime = 31;
long temp;
temp = Double.doubleToLongBits(lat);
int result = prime + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(lng);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + (normalized ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (getClass() != obj.getClass())
return false;
FloatLatLng other = (FloatLatLng) obj;
if (Double.doubleToLongBits(lat) != Double.doubleToLongBits(other.lat))
return false;
if (Double.doubleToLongBits(lng) != Double.doubleToLongBits(other.lng))
return false;
if (normalized != other.normalized)
return false;
return true;
}
}

View File

@ -1,160 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.geometry;
/**
* Abstract base lat-lng class which can manipulate fixed point or floating
* point based coordinates. Instances are immutable.
*
* @see FloatLatLng
*
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public abstract class LatLng {
public abstract boolean isNormalized();
public abstract boolean isFixedPoint();
public abstract LatLng normalize();
public abstract int getFixedLat();
public abstract int getFixedLng();
public abstract double getLat();
public abstract double getLng();
public abstract LatLng copy();
public abstract FixedLatLng toFixed();
public abstract FloatLatLng toFloat();
/**
* Convert the lat/lng into the cartesian coordinate plane such that all
* world coordinates are represented in the first quadrant.
* The x dimension corresponds to latitude and y corresponds to longitude.
* The translation starts with the normalized latlng and adds 180 to the latitude and
* 90 to the longitude (subject to fixed point scaling).
*/
public CartesianPoint toCartesian() {
LatLng ll=normalize();
int lat=ll.getFixedLat();
int lng=ll.getFixedLng();
return new CartesianPoint(
lng+180*FixedLatLng.SCALE_FACTOR_INT,
lat+90*FixedLatLng.SCALE_FACTOR_INT
);
}
/**
* The inverse of toCartesian(). Always returns a FixedLatLng.
* @param pt
*/
public static LatLng fromCartesian(CartesianPoint pt) {
int lat=pt.getY() - 90 * FixedLatLng.SCALE_FACTOR_INT;
int lng=pt.getX() - 180 * FixedLatLng.SCALE_FACTOR_INT;
return new FixedLatLng(lat, lng);
}
/**
* Calculates the distance between two lat/lng's in miles.
* Imported from mq java client.
*
* @param ll2
* Second lat,lng position to calculate distance to.
*
* @return Returns the distance in miles.
*/
public double arcDistance(LatLng ll2) {
return arcDistance(ll2, DistanceUnits.MILES);
}
/**
* Calculates the distance between two lat/lng's in miles or meters.
* Imported from mq java client. Variable references changed to match.
*
* @param ll2
* Second lat,lng position to calculate distance to.
* @param lUnits
* Units to calculate distance, defaults to miles
*
* @return Returns the distance in meters or miles.
*/
public double arcDistance(LatLng ll2, DistanceUnits lUnits) {
LatLng ll1 = normalize();
ll2 = ll2.normalize();
double lat1 = ll1.getLat(), lng1 = ll1.getLng();
double lat2 = ll2.getLat(), lng2 = ll2.getLng();
// Check for same position
if (lat1 == lat2 && lng1 == lng2)
return 0.0;
// Get the m_dLongitude difference. Don't need to worry about
// crossing 180 since cos(x) = cos(-x)
double dLon = lng2 - lng1;
double a = radians(90.0 - lat1);
double c = radians(90.0 - lat2);
double cosB = (Math.cos(a) * Math.cos(c))
+ (Math.sin(a) * Math.sin(c) * Math.cos(radians(dLon)));
double radius = (lUnits == DistanceUnits.MILES) ? 3963.205/* MILERADIUSOFEARTH */
: 6378.160187/* KMRADIUSOFEARTH */;
// Find angle subtended (with some bounds checking) in radians and
// multiply by earth radius to find the arc distance
if (cosB < -1.0)
return 3.14159265358979323846/* PI */* radius;
else if (cosB >= 1.0)
return 0;
else
return Math.acos(cosB) * radius;
}
private double radians(double a) {
return a * 0.01745329251994;
}
@Override
public String toString() {
return "[" + getLat() + "," + getLng() + "]";
}
/**
* Calculate the midpoint between this point an another. Respects fixed vs floating point
* @param other
*/
public abstract LatLng calculateMidpoint(LatLng other);
@Override
public abstract int hashCode();
@Override
public abstract boolean equals(Object obj);
}

View File

@ -1,234 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.geometry.shape;
/**
* Ellipse shape. From C++ gl.
*
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class Ellipse implements Geometry2D {
private Point2D center;
/**
* Half length of major axis
*/
private double a;
/**
* Half length of minor axis
*/
private double b;
private double k1, k2, k3;
/**
* sin of rotation angle
*/
private double s;
/**
* cos of rotation angle
*/
private double c;
public Ellipse() {
center = new Point2D(0, 0);
}
private double SQR(double d) {
return d * d;
}
/**
* Constructor given bounding rectangle and a rotation.
*/
public Ellipse(Point2D p1, Point2D p2, double angle) {
center = new Point2D();
// Set the center
center.x((p1.x() + p2.x()) * 0.5f);
center.y((p1.y() + p2.y()) * 0.5f);
// Find sin and cos of the angle
double angleRad = Math.toRadians(angle);
c = Math.cos(angleRad);
s = Math.sin(angleRad);
// Find the half lengths of the semi-major and semi-minor axes
double dx = Math.abs(p2.x() - p1.x()) * 0.5;
double dy = Math.abs(p2.y() - p1.y()) * 0.5;
if (dx >= dy) {
a = dx;
b = dy;
} else {
a = dy;
b = dx;
}
// Find k1, k2, k3 - define when a point x,y is on the ellipse
k1 = SQR(c / a) + SQR(s / b);
k2 = 2 * s * c * ((1 / SQR(a)) - (1 / SQR(b)));
k3 = SQR(s / a) + SQR(c / b);
}
/**
* Determines if a line segment intersects the ellipse and if so finds the
* point(s) of intersection.
*
* @param seg
* Line segment to test for intersection
* @param pt0
* OUT - intersection point (if it exists)
* @param pt1
* OUT - second intersection point (if it exists)
*
* @return Returns the number of intersection points (0, 1, or 2).
*/
public int intersect(LineSegment seg, Point2D pt0, Point2D pt1) {
if (pt0 == null)
pt0 = new Point2D();
if (pt1 == null)
pt1 = new Point2D();
// Solution is found by parameterizing the line segment and
// substituting those values into the ellipse equation.
// Results in a quadratic equation.
double x1 = center.x();
double y1 = center.y();
double u1 = seg.A.x();
double v1 = seg.A.y();
double u2 = seg.B.x();
double v2 = seg.B.y();
double dx = u2 - u1;
double dy = v2 - v1;
double q0 = k1 * SQR(u1 - x1) + k2 * (u1 - x1) * (v1 - y1) + k3
* SQR(v1 - y1) - 1;
double q1 = (2 * k1 * dx * (u1 - x1)) + (k2 * dx * (v1 - y1))
+ (k2 * dy * (u1 - x1)) + (2 * k3 * dy * (v1 - y1));
double q2 = (k1 * SQR(dx)) + (k2 * dx * dy) + (k3 * SQR(dy));
// Compare q1^2 to 4*q0*q2 to see how quadratic solves
double d = SQR(q1) - (4 * q0 * q2);
if (d < 0) {
// Roots are complex valued. Line containing the segment does
// not intersect the ellipse
return 0;
}
if (d == 0) {
// One real-valued root - line is tangent to the ellipse
double t = -q1 / (2 * q2);
if (0 <= t && t <= 1) {
// Intersection occurs along line segment
pt0.x(u1 + t * dx);
pt0.y(v1 + t * dy);
return 1;
} else
return 0;
} else {
// Two distinct real-valued roots. Solve for the roots and see if
// they fall along the line segment
int n = 0;
double q = Math.sqrt(d);
double t = (-q1 - q) / (2 * q2);
if (0 <= t && t <= 1) {
// Intersection occurs along line segment
pt0.x(u1 + t * dx);
pt0.y(v1 + t * dy);
n++;
}
// 2nd root
t = (-q1 + q) / (2 * q2);
if (0 <= t && t <= 1) {
if (n == 0) {
pt0.x(u1 + t * dx);
pt0.y(v1 + t * dy);
n++;
} else {
pt1.x(u1 + t * dx);
pt1.y(v1 + t * dy);
n++;
}
}
return n;
}
}
public IntersectCase intersect(Rectangle r) {
// Test if all 4 corners of the rectangle are inside the ellipse
Point2D ul = new Point2D(r.MinPt().x(), r.MaxPt().y());
Point2D ur = new Point2D(r.MaxPt().x(), r.MaxPt().y());
Point2D ll = new Point2D(r.MinPt().x(), r.MinPt().y());
Point2D lr = new Point2D(r.MaxPt().x(), r.MinPt().y());
if (contains(ul) && contains(ur) && contains(ll) && contains(lr))
return IntersectCase.CONTAINS;
// Test if any of the rectangle edges intersect
Point2D pt0 = new Point2D(), pt1 = new Point2D();
LineSegment bottom = new LineSegment(ll, lr);
if (intersect(bottom, pt0, pt1) > 0)
return IntersectCase.INTERSECTS;
LineSegment top = new LineSegment(ul, ur);
if (intersect(top, pt0, pt1) > 0)
return IntersectCase.INTERSECTS;
LineSegment left = new LineSegment(ll, ul);
if (intersect(left, pt0, pt1) > 0)
return IntersectCase.INTERSECTS;
LineSegment right = new LineSegment(lr, ur);
if (intersect(right, pt0, pt1) > 0)
return IntersectCase.INTERSECTS;
// Ellipse does not intersect any edge : since the case for the ellipse
// containing the rectangle was considered above then if the center
// is inside the ellipse is fully inside and if center is outside
// the ellipse is fully outside
return (r.contains(center)) ? IntersectCase.WITHIN
: IntersectCase.OUTSIDE;
}
public double area() {
throw new UnsupportedOperationException();
}
public Point2D centroid() {
throw new UnsupportedOperationException();
}
public boolean contains(Point2D pt) {
// Plug in equation for ellipse, If evaluates to <= 0 then the
// point is in or on the ellipse.
double dx = pt.x() - center.x();
double dy = pt.y() - center.y();
double eq=(((k1 * SQR(dx)) + (k2 * dx * dy) + (k3 * SQR(dy)) - 1));
return eq<=0;
}
public void translate(Vector2D v) {
throw new UnsupportedOperationException();
}
}

View File

@ -1,191 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.geometry.shape;
import org.apache.lucene.spatial.geometry.FloatLatLng;
import org.apache.lucene.spatial.geometry.LatLng;
/**
* Lat-long rect. Instances are mutable.
*
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class LLRect {
private LatLng ll, ur;
public LLRect(LatLng ll, LatLng ur) {
this.ll=ll;
this.ur=ur;
}
public LLRect(LLRect other) {
this.ll=other.ll;
this.ur=other.ur;
}
/**
* Return the area in units of lat-lng squared. This is a contrived unit
* that only has value when comparing to something else.
*/
public double area() {
return Math.abs((ll.getLat()-ur.getLat()) * (ll.getLng()-ur.getLng()));
}
public LatLng getLowerLeft() {
return ll;
}
public LatLng getUpperRight() {
return ur;
}
@Override
public String toString() {
return "{" + ll + ", " + ur + "}";
}
public LatLng getMidpoint() {
return ll.calculateMidpoint(ur);
}
/**
* Approximates a box centered at the given point with the given width and height in miles.
* @param center
* @param widthMi
* @param heightMi
*/
public static LLRect createBox(LatLng center, double widthMi, double heightMi) {
double d = widthMi;
LatLng ur = boxCorners(center, d, 45.0); // assume right angles
LatLng ll = boxCorners(center, d, 225.0);
//System.err.println("boxCorners: ur " + ur.getLat() + ',' + ur.getLng());
//System.err.println("boxCorners: cnt " + center.getLat() + ',' + center.getLng());
//System.err.println("boxCorners: ll " + ll.getLat() + ',' + ll.getLng());
return new LLRect(ll, ur);
}
/**
* Returns a rectangle shape for the bounding box
*/
public Rectangle toRectangle() {
return new Rectangle(ll.getLng(), ll.getLat(), ur.getLng(), ur.getLat());
}
private static LatLng boxCorners(LatLng center, double d, double brngdeg) {
double a = center.getLat();
double b = center.getLng();
double R = 3963.0; // radius of earth in miles
double brng = (Math.PI*brngdeg/180);
double lat1 = (Math.PI*a/180);
double lon1 = (Math.PI*b/180);
// Haversine formula
double lat2 = Math.asin( Math.sin(lat1)*Math.cos(d/R) +
Math.cos(lat1)*Math.sin(d/R)*Math.cos(brng) );
double lon2 = lon1 + Math.atan2(Math.sin(brng)*Math.sin(d/R)*Math.cos(lat1),
Math.cos(d/R)-Math.sin(lat1)*Math.sin(lat2));
lat2 = (lat2*180)/Math.PI;
lon2 = (lon2*180)/Math.PI;
// normalize long first
LatLng ll = normLng(lat2,lon2);
// normalize lat - could flip poles
ll = normLat(ll.getLat(),ll.getLng());
return ll;
}
/**
* Returns a normalized Lat rectangle shape for the bounding box
* If you go over the poles, you need to flip the lng value too
*/
private static LatLng normLat(double lat, double lng) {
if (lat > 90.0) {
lat = 90.0 - (lat - 90.0);
if (lng < 0) {
lng = lng+180;
} else {
lng=lng-180;
}
}
else if (lat < -90.0) {
lat = -90.0 - (lat + 90.0);
if (lng < 0) {
lng = lng+180;
} else {
lng=lng-180;
}
}
LatLng ll=new FloatLatLng(lat, lng);
return ll;
}
/**
* Returns a normalized Lng rectangle shape for the bounding box
*/
private static LatLng normLng(double lat,double lng) {
if (lng > 180.0) {
lng = -1.0*(180.0 - (lng - 180.0));
}
else if (lng < -180.0) {
lng = (lng + 180.0)+180.0;
}
LatLng ll=new FloatLatLng(lat, lng);
return ll;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((ll == null) ? 0 : ll.hashCode());
result = prime * result + ((ur == null) ? 0 : ur.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
LLRect other = (LLRect) obj;
if (ll == null) {
if (other.ll != null)
return false;
} else if (!ll.equals(other.ll))
return false;
if (ur == null) {
if (other.ur != null)
return false;
} else if (!ur.equals(other.ur))
return false;
return true;
}
}

View File

@ -1,117 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.geometry.shape;
/**
* 2d line segment.
*
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class LineSegment {
public final Point2D A = new Point2D();
public final Point2D B = new Point2D();
public LineSegment() {
A.set(0, 0);
B.set(0, 0);
}
public LineSegment(Point2D p1, Point2D p2) {
A.set(p1);
B.set(p2);
}
/**
* Finds the distance of a specified point from the line segment and the
* closest point on the segment to the specified point.
*
* @param P
* Test point.
* @param closestPt
* (Return) Closest point on the segment to c.
*
* @return Returns the distance from P to the closest point on the segment.
*/
public double distance(Point2D P, Point2D /* out */closestPt) {
if (closestPt == null)
closestPt = new Point2D();
// Construct vector v (AB) and w (AP)
Vector2D v = new Vector2D(A, B);
Vector2D w = new Vector2D(A, P);
// Numerator of the component of w onto v. If <= 0 then A
// is the closest point. By separating into the numerator
// and denominator of the component we avoid a division unless
// it is necessary.
double n = w.dot(v);
if (n <= 0.0f) {
closestPt.set(A);
return w.norm();
}
// Get the denominator of the component. If the component >= 1
// (d <= n) then point B is the closest point
double d = v.dot(v);
if (d <= n) {
closestPt.set(B);
return new Vector2D(B, P).norm();
}
// Closest point is along the segment. The point is the projection of
// w onto v.
closestPt.set(v.mult(n / d));
closestPt.add(A);
return new Vector2D(closestPt, P).norm();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((A == null) ? 0 : A.hashCode());
result = prime * result + ((B == null) ? 0 : B.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
LineSegment other = (LineSegment) obj;
if (A == null) {
if (other.A != null)
return false;
} else if (!A.equals(other.A))
return false;
if (B == null) {
if (other.B != null)
return false;
} else if (!B.equals(other.B))
return false;
return true;
}
}

View File

@ -1,137 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.geometry.shape;
/**
* Point class. This type is mutable.
*
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class Point2D {
private double x;
private double y;
public Point2D(double x, double y) {
this.x=x;
this.y=y;
}
public Point2D() {
this.x=0;
this.y=0;
}
public Point2D(Point2D other) {
this.x=other.x;
this.y=other.y;
}
@Override
public String toString() {
return "(" + x + "," + y + ")";
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double x() {
return x;
}
public double y() {
return y;
}
public void x(double x) {
this.x=x;
}
public void y(double y) {
this.y=y;
}
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
public void set(double x, double y) {
this.x=x;
this.y=y;
}
public void add(Vector2D v) {
this.x+=v.getX();
this.y+=v.getY();
}
public void set(Point2D p1) {
this.x=p1.getX();
this.y=p1.getY();
}
public void add(Point2D a) {
this.x+=a.getX();
this.y+=a.getY();
}
public void set(Vector2D v) {
this.x=v.getX();
this.y=v.getY();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(x);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(y);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Point2D other = (Point2D) obj;
if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x))
return false;
if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y))
return false;
return true;
}
}

View File

@ -1,128 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.geometry.shape;
/**
* Rectangle shape.
*
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class Rectangle implements Geometry2D {
private Point2D ptMin, ptMax;
public Rectangle() {
ptMin=new Point2D(-1, 1);
ptMax=new Point2D(1, 1);
}
public Rectangle(Point2D ptMin, Point2D ptMax) {
this.ptMin=new Point2D(ptMin);
this.ptMax=new Point2D(ptMax);
}
public Rectangle(double x1, double y1, double x2, double y2) {
set(x1, y1, x2, y2);
}
@Override
public String toString() {
return "[" + ptMin + "," + ptMax + "]";
}
private void set(double x1, double y1, double x2, double y2) {
this.ptMin=new Point2D(Math.min(x1, x2), Math.min(y1, y2));
this.ptMax=new Point2D(Math.max(x1, x2), Math.max(y1, y2));
}
public double area() {
return (ptMax.getX() - ptMin.getX()) * (ptMax.getY() - ptMin.getY());
}
public Point2D centroid() {
return new Point2D( (ptMin.getX() + ptMax.getX()) / 2,
(ptMin.getY() + ptMax.getY()) / 2);
}
public boolean contains(Point2D p) {
return p.getX() >= ptMin.getX() &&
p.getX() <= ptMax.getX() &&
p.getY() >= ptMin.getY() &&
p.getY() <= ptMax.getY();
}
public void translate(Vector2D v) {
ptMin.add(v);
ptMax.add(v);
}
Point2D MinPt() {
return ptMin;
}
Point2D MaxPt() {
return ptMax;
}
public IntersectCase intersect(Rectangle r) {
throw new UnsupportedOperationException();
// TODO
}
public Point2D getMaxPoint() {
return ptMax;
}
public Point2D getMinPoint() {
return ptMin;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((ptMax == null) ? 0 : ptMax.hashCode());
result = prime * result + ((ptMin == null) ? 0 : ptMin.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Rectangle other = (Rectangle) obj;
if (ptMax == null) {
if (other.ptMax != null)
return false;
} else if (!ptMax.equals(other.ptMax))
return false;
if (ptMin == null) {
if (other.ptMin != null)
return false;
} else if (!ptMin.equals(other.ptMin))
return false;
return true;
}
}

View File

@ -1,145 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.geometry.shape;
/**
* 2D vector
*
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class Vector2D {
private double x;
private double y;
/**
* Create a vector from the origin of the coordinate system to the given
* point
*
* @param x
* @param y
*/
public Vector2D(double x, double y) {
this.x = x;
this.y = y;
}
/**
* Create a vector from the origin of the coordinate system to the given
* point
*/
public Vector2D(Point2D p) {
this(p.getX(), p.getY());
}
/**
* Create a vector from one point to another
*
* @param from
* @param to
*/
public Vector2D(Point2D from, Point2D to) {
this(to.getX() - from.getX(), to.getY() - from.getY());
}
public Vector2D() {
this.x = 0;
this.y = 0;
}
public Vector2D(Vector2D other) {
this.x = other.x;
this.y = other.y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
public void set(double x, double y) {
this.x = x;
this.y = y;
}
public boolean equals(Vector2D other) {
return other != null && x == other.x && y == other.y;
}
public double dot(Vector2D in) {
return ((x) * in.x) + (y * in.y);
}
/**
* Vector length (magnitude) squared
*/
public double normSqr() {
// Cast to F to prevent overflows
return (x * x) + (y * y);
}
public double norm() {
return Math.sqrt(normSqr());
}
public Vector2D mult(double d) {
return new Vector2D(x*d, y*d);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(x);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(y);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Vector2D other = (Vector2D) obj;
if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x))
return false;
if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y))
return false;
return true;
}
}

View File

@ -1,181 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.tier;
import org.apache.lucene.search.Filter;
import org.apache.lucene.spatial.geometry.FloatLatLng;
import org.apache.lucene.spatial.geometry.LatLng;
import org.apache.lucene.spatial.geometry.shape.LLRect;
import org.apache.lucene.spatial.tier.projections.CartesianTierPlotter;
import org.apache.lucene.spatial.tier.projections.IProjector;
import org.apache.lucene.spatial.tier.projections.SinusoidalProjector;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class CartesianPolyFilterBuilder {
// Finer granularity than 1 mile isn't accurate with
// standard java math. Also, there's already a 2nd
// precise filter, if needed, in DistanceQueryBuilder,
// that will make the filtering exact.
public static final double MILES_FLOOR = 1.0;
private IProjector projector = new SinusoidalProjector();
private final String tierPrefix;
private int minTier;
private int maxTier;
/**
* @param tierPrefix The prefix for the name of the fields containing the tier info
* @param minTierIndexed The minimum tier level indexed
* @param maxTierIndexed The maximum tier level indexed
*/
public CartesianPolyFilterBuilder(String tierPrefix, int minTierIndexed, int maxTierIndexed) {
this.tierPrefix = tierPrefix;
this.minTier = minTierIndexed;
this.maxTier = maxTierIndexed;
}
public Shape getBoxShape(double latitude, double longitude, double miles) {
if (miles < MILES_FLOOR) {
miles = MILES_FLOOR;
}
LLRect box1 = LLRect.createBox(new FloatLatLng(latitude, longitude), miles, miles);
LatLng ll = box1.getLowerLeft();
LatLng ur = box1.getUpperRight();
double latY = ur.getLat();
double latX = ll.getLat();
double longY = ur.getLng();
double longX = ll.getLng();
double longX2 = 0.0;
//These two if checks setup us up to deal with issues around the prime meridian and the 180th meridian
//In these two cases, we need to get tiles (tiers) from the lower left up to the meridian and then
//from the meridan to the upper right
//Are we crossing the 180 deg. longitude, if so, we need to do some special things
if (ur.getLng() < 0.0 && ll.getLng() > 0.0) {
longX2 = ll.getLng();
longX = -180.0;
}
//are we crossing the prime meridian (0 degrees)? If so, we need to account for it and boxes on both sides
if (ur.getLng() > 0.0 && ll.getLng() < 0.0) {
longX2 = ll.getLng();
longX = 0.0;
}
//System.err.println("getBoxShape:"+latY+"," + longY);
//System.err.println("getBoxShape:"+latX+"," + longX);
CartesianTierPlotter ctp = new CartesianTierPlotter(2, projector, tierPrefix);
int bestFit = ctp.bestFit(miles);
if (bestFit < minTier) {
bestFit = minTier;
} else if (bestFit > maxTier) {
bestFit = maxTier;
}
ctp = new CartesianTierPlotter(bestFit, projector, tierPrefix);
Shape shape = new Shape(ctp.getTierFieldName());
// generate shape
// iterate from startX->endX
// iterate from startY -> endY
// shape.add(currentLat.currentLong);
//for the edge cases (prime meridian and the 180th meridian), this call handles all tiles East of the meridian
//for all other cases, it handles the whole set of tiles
shape = getShapeLoop(shape, ctp, latX, longX, latY, longY);
if (longX2 != 0.0) {
if (longX == 0.0) {
longX = longX2;
longY = 0.0;
//handles the lower left longitude to the prime meridian
//shape = getShapeLoop(shape, ctp, latX, longX, latY, longY);
} else {
//this clause handles the lower left longitude up to the 180 meridian
longX = longX2;
longY = 180.0;
}
shape = getShapeLoop(shape, ctp, latX, longX, latY, longY);
//System.err.println("getBoxShape2:"+latY+"," + longY);
//System.err.println("getBoxShape2:"+latX+"," + longX);
}
return shape;
}
public Shape getShapeLoop(Shape shape, CartesianTierPlotter ctp, double latX, double longX, double latY, double longY) {
//System.err.println("getShapeLoop:"+latY+"," + longY);
//System.err.println("getShapeLoop:"+latX+"," + longX);
double beginAt = ctp.getTierBoxId(latX, longX);
double endAt = ctp.getTierBoxId(latY, longY);
if (beginAt > endAt) {
double tmp = beginAt;
beginAt = endAt;
endAt = tmp;
}
double tierVert = ctp.getTierVerticalPosDivider();
//System.err.println(" | "+ beginAt+" | "+ endAt);
double startX = beginAt - (beginAt % 1);
double startY = beginAt - startX; //should give a whole number
double endX = endAt - (endAt % 1);
double endY = endAt - endX; //should give a whole number
int scale = (int) Math.log10(tierVert);
endY = new BigDecimal(endY).setScale(scale, RoundingMode.HALF_EVEN).doubleValue();
startY = new BigDecimal(startY).setScale(scale, RoundingMode.HALF_EVEN).doubleValue();
double xInc = 1.0d / tierVert;
xInc = new BigDecimal(xInc).setScale(scale, RoundingMode.HALF_EVEN).doubleValue();
//System.err.println("go from startX:"+startX+" to:" + endX);
for (; startX <= endX; startX++) {
double itY = startY;
//System.err.println("go from startY:"+startY+" to:" + endY);
while (itY <= endY) {
//create a boxId
// startX.startY
double boxId = startX + itY;
shape.addBox(boxId);
//System.err.println("----"+startX+" and "+itY);
//System.err.println("----"+boxId);
itY += xInc;
// java keeps 0.0001 as 1.0E-1
// which ends up as 0.00011111
itY = new BigDecimal(itY).setScale(scale, RoundingMode.HALF_EVEN).doubleValue();
}
}
return shape;
}
public Filter getBoundingArea(double latitude, double longitude, double miles) {
Shape shape = getBoxShape(latitude, longitude, miles);
return new CartesianShapeFilter(shape, shape.getTierId());
}
}

View File

@ -1,85 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.tier;
import java.io.IOException;
import java.util.List;
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.index.DocsEnum;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.DocIdSet;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.FixedBitSet;
/**
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class CartesianShapeFilter extends Filter {
private final Shape shape;
private final String fieldName;
CartesianShapeFilter(final Shape shape, final String fieldName){
this.shape = shape;
this.fieldName = fieldName;
}
@Override
public DocIdSet getDocIdSet(final AtomicReaderContext context, final Bits acceptDocs) throws IOException {
final List<Double> area = shape.getArea();
final int sz = area.size();
// iterate through each boxid
final BytesRef bytesRef = new BytesRef(NumericUtils.BUF_SIZE_LONG);
if (sz == 1) {
double boxId = area.get(0).doubleValue();
NumericUtils.longToPrefixCoded(NumericUtils.doubleToSortableLong(boxId), 0, bytesRef);
return new DocIdSet() {
@Override
public DocIdSetIterator iterator() throws IOException {
return context.reader().termDocsEnum(acceptDocs, fieldName, bytesRef, false);
}
@Override
public boolean isCacheable() {
return false;
}
};
} else {
final FixedBitSet bits = new FixedBitSet(context.reader().maxDoc());
for (int i =0; i< sz; i++) {
double boxId = area.get(i).doubleValue();
NumericUtils.longToPrefixCoded(NumericUtils.doubleToSortableLong(boxId), 0, bytesRef);
final DocsEnum docsEnum = context.reader().termDocsEnum(acceptDocs, fieldName, bytesRef, false);
if (docsEnum == null) continue;
// iterate through all documents
// which have this boxId
int doc;
while ((doc = docsEnum.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
bits.set(doc);
}
}
return bits;
}
}
}

View File

@ -1,121 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.tier;
import java.io.IOException;
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.FieldComparator;
import org.apache.lucene.search.FieldComparatorSource;
/**
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class DistanceFieldComparatorSource extends FieldComparatorSource {
private DistanceFilter distanceFilter;
private DistanceScoreDocLookupComparator dsdlc;
public DistanceFieldComparatorSource(Filter distanceFilter) {
this.distanceFilter = (DistanceFilter) distanceFilter;
}
public void cleanUp() {
distanceFilter = null;
if (dsdlc != null) {
dsdlc.cleanUp();
}
dsdlc = null;
}
@Override
public FieldComparator newComparator(String fieldname, int numHits,
int sortPos, boolean reversed) throws IOException {
dsdlc = new DistanceScoreDocLookupComparator(numHits);
return dsdlc;
}
private class DistanceScoreDocLookupComparator extends FieldComparator<Double> {
private double[] values;
private double bottom;
private int offset =0;
public DistanceScoreDocLookupComparator(int numHits) {
values = new double[numHits];
return;
}
@Override
public int compare(int slot1, int slot2) {
double a = values[slot1];
double b = values[slot2];
if (a > b)
return 1;
if (a < b)
return -1;
return 0;
}
public void cleanUp() {
distanceFilter = null;
}
@Override
public int compareBottom(int doc) {
double v2 = distanceFilter.getDistance(doc+ offset);
if (bottom > v2) {
return 1;
} else if (bottom < v2) {
return -1;
}
return 0;
}
@Override
public void copy(int slot, int doc) {
values[slot] = distanceFilter.getDistance(doc + offset);
}
@Override
public void setBottom(int slot) {
this.bottom = values[slot];
}
@Override
public FieldComparator setNextReader(AtomicReaderContext context)
throws IOException {
// each reader in a segmented base
// has an offset based on the maxDocs of previous readers
offset = context.docBase;
return this;
}
@Override
public Double value(int slot) {
return values[slot];
}
}
}

View File

@ -1,103 +0,0 @@
/** Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.tier;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.HashMap;
import org.apache.lucene.search.Filter;
import org.apache.lucene.spatial.tier.DistanceHandler.Precision;
/**
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public abstract class DistanceFilter extends Filter {
final protected Filter startingFilter;
protected Precision precise;
protected Map<Integer,Double> distances;
protected double distance;
protected int nextDocBase;
protected transient WeakHashMap<String,Double> distanceLookupCache;
/** Filters the startingFilter by precise distance
* checking filter */
public DistanceFilter(Filter startingFilter, double distance) {
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.distance = distance;
// NOTE: neither of the distance filters use precision
// now - if we turn that on, we'll need to pass top
// reader into here
// setPrecision(reader.maxDoc());
/* store calculated distances for reuse by other components */
distances = new HashMap<Integer,Double>();
// create an intermediate cache to avoid recomputing
// distances for the same point
// TODO: Why is this a WeakHashMap?
distanceLookupCache = new WeakHashMap<String,Double>();
}
public Map<Integer,Double> getDistances(){
return distances;
}
public Double getDistance(int docid){
return distances.get(docid);
}
public void setDistances(Map<Integer, Double> distances) {
this.distances = distances;
}
/** You must call this before re-using this DistanceFilter
* across searches */
public void reset() {
nextDocBase = 0;
}
/** Returns true if <code>o</code> is equal to this. */
@Override
public abstract boolean equals(Object o);
/** Returns a hash code value for this object.*/
@Override
public abstract int hashCode();
/*
private void setPrecision(int maxDocs) {
precise = Precision.EXACT;
if (maxDocs > 1000 && distance > 10) {
precise = Precision.TWENTYFEET;
}
if (maxDocs > 10000 && distance > 10){
precise = Precision.TWOHUNDREDFEET;
}
}
*/
}

View File

@ -1,99 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.tier;
import org.apache.lucene.spatial.DistanceUtils;
import java.util.Map;
/**
* Provide a high level access point to distances
* Used by DistanceSortSource and DistanceQuery
*
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*
*/
public class DistanceHandler {
public enum Precision {EXACT, TWOFEET, TWENTYFEET, TWOHUNDREDFEET}
private Map<Integer,Double> distances;
private Map<String, Double> distanceLookupCache;
private Precision precise;
public DistanceHandler (Map<Integer,Double> distances, Map<String, Double> distanceLookupCache, Precision precise){
this.distances = distances;
this.distanceLookupCache = distanceLookupCache;
this.precise = precise;
}
public static double getPrecision(double x, Precision thisPrecise){
if(thisPrecise != null){
double dif = 0;
switch(thisPrecise) {
case EXACT: return x;
case TWOFEET: dif = x % 0.0001; break;
case TWENTYFEET: dif = x % 0.001; break;
case TWOHUNDREDFEET: dif = x % 0.01; break;
}
return x - dif;
}
return x;
}
public Precision getPrecision() {
return precise;
}
public double getDistance(int docid, double centerLat, double centerLng, double lat, double lng){
// check to see if we have distances
// if not calculate the distance
if(distances == null){
return DistanceUtils.getDistanceMi(centerLat, centerLng, lat, lng);
}
// check to see if the doc id has a cached distance
Double docd = distances.get( docid );
if (docd != null){
return docd.doubleValue();
}
//check to see if we have a precision code
// and if another lat/long has been calculated at
// that rounded location
if (precise != null) {
double xLat = getPrecision(lat, precise);
double xLng = getPrecision(lng, precise);
String k = Double.valueOf(xLat).toString() +","+ Double.valueOf(xLng).toString();
Double d = (distanceLookupCache.get(k));
if (d != null){
return d.doubleValue();
}
}
//all else fails calculate the distances
return DistanceUtils.getDistanceMi(centerLat, centerLng, lat, lng);
}
}

View File

@ -1,149 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.tier;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.queries.ChainedFilter;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryWrapperFilter;
import org.apache.lucene.spatial.geohash.GeoHashDistanceFilter;
/**
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class DistanceQueryBuilder {
private final double lat;
private final double lng;
private final double miles;
private final Filter filter;
final DistanceFilter distanceFilter;
/**
* Create a distance query using
* a boundary box wrapper around a more precise
* DistanceFilter.
*
* @param lat The latitude to search around
* @param lng the Longitude to search around
* @param miles The radius to search within
* @param latField The name of the field containing the latitude
* @param lngField The name of the field containing the longitude
* @param tierFieldPrefix The prefix of the tier
* @param needPrecise if true, then distance is calculated in addition to tier info
* @param minTierIndexed The minimum tier level indexed
* @param maxTierIndexed The maximum tier level indexed
*/
public DistanceQueryBuilder (double lat, double lng, double miles,
String latField, String lngField, String tierFieldPrefix, boolean needPrecise, int minTierIndexed, int maxTierIndexed) {
this.lat = lat;
this.lng = lng;
this.miles = miles;
CartesianPolyFilterBuilder cpf = new CartesianPolyFilterBuilder(tierFieldPrefix, minTierIndexed, maxTierIndexed);
Filter cartesianFilter = cpf.getBoundingArea(lat, lng, miles);
/* create precise distance filter */
if (needPrecise) {
filter = distanceFilter = new LatLongDistanceFilter(cartesianFilter, lat, lng, miles, latField, lngField);
} else {
filter = cartesianFilter;
distanceFilter = null;
}
}
/**
* Create a distance query using
* a boundary box wrapper around a more precise
* DistanceFilter.
*
* @param lat
* @param lng
* @param miles
*/
public DistanceQueryBuilder (double lat, double lng, double miles,
String geoHashFieldPrefix, String tierFieldPrefix, boolean needPrecise, int minTierIndexed, int maxTierIndexed){
this.lat = lat;
this.lng = lng;
this.miles = miles;
CartesianPolyFilterBuilder cpf = new CartesianPolyFilterBuilder(tierFieldPrefix, minTierIndexed, maxTierIndexed);
Filter cartesianFilter = cpf.getBoundingArea(lat, lng, miles);
/* create precise distance filter */
if (needPrecise) {
filter = distanceFilter = new GeoHashDistanceFilter(cartesianFilter, lat, lng, miles, geoHashFieldPrefix);
} else {
filter = cartesianFilter;
distanceFilter = null;
}
}
/**
* Create a distance query using
* a boundary box wrapper around a more precise
* DistanceFilter.
*/
public Filter getFilter() {
if (distanceFilter != null) {
distanceFilter.reset();
}
return filter;
}
public Filter getFilter(Query query) {
// Chain the Query (as filter) with our distance filter
if (distanceFilter != null) {
distanceFilter.reset();
}
QueryWrapperFilter qf = new QueryWrapperFilter(query);
return new ChainedFilter(new Filter[] {qf, filter},
ChainedFilter.AND);
}
public DistanceFilter getDistanceFilter() {
return distanceFilter;
}
public Query getQuery(Query query){
return new ConstantScoreQuery(getFilter(query));
}
public double getLat() {
return lat;
}
public double getLng() {
return lng;
}
public double getMiles() {
return miles;
}
@Override
public String toString() {
return "DistanceQuery lat: " + lat + " lng: " + lng + " miles: "+ miles;
}
}

View File

@ -1,134 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.tier;
import java.io.IOException;
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.search.FilteredDocIdSet;
import org.apache.lucene.search.FieldCache;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.DocIdSet;
import org.apache.lucene.util.Bits;
import org.apache.lucene.spatial.DistanceUtils;
/**
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class LatLongDistanceFilter extends DistanceFilter {
double lat;
double lng;
String latField;
String lngField;
int nextOffset = 0;
/**
* Provide a distance filter based from a center point with a radius
* in miles.
* @param startingFilter Filter to start from
* @param lat
* @param lng
* @param miles
* @param latField
* @param lngField
*/
public LatLongDistanceFilter(Filter startingFilter, double lat, double lng, double miles, String latField, String lngField) {
super(startingFilter, miles);
this.lat = lat;
this.lng = lng;
this.latField = latField;
this.lngField = lngField;
}
@Override
public DocIdSet getDocIdSet(AtomicReaderContext context, Bits acceptDocs) throws IOException {
final double[] latIndex = FieldCache.DEFAULT.getDoubles(context.reader(), latField, false);
final double[] lngIndex = FieldCache.DEFAULT.getDoubles(context.reader(), lngField, false);
final int docBase = nextDocBase;
nextDocBase += context.reader().maxDoc();
return new FilteredDocIdSet(startingFilter.getDocIdSet(context, acceptDocs)) {
@Override
protected boolean match(int doc) {
double x = latIndex[doc];
double y = lngIndex[doc];
// round off lat / longs if necessary
// x = DistanceHandler.getPrecision(x, precise);
// y = DistanceHandler.getPrecision(y, precise);
String ck = Double.toString(x)+","+Double.toString(y);
Double cachedDistance = distanceLookupCache.get(ck);
double d;
if (cachedDistance != null){
d = cachedDistance.doubleValue();
} else {
d = DistanceUtils.getDistanceMi(lat, lng, x, y);
distanceLookupCache.put(ck, d);
}
if (d < distance) {
// Save distances, so they can be pulled for
// sorting after filtering is done:
distances.put(doc+docBase, d);
return true;
} else {
return false;
}
}
};
}
/** Returns true if <code>o</code> is equal to this. */
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof LatLongDistanceFilter)) return false;
LatLongDistanceFilter other = (LatLongDistanceFilter) o;
if (!this.startingFilter.equals(other.startingFilter) ||
this.distance != other.distance ||
this.lat != other.lat ||
this.lng != other.lng ||
!this.latField.equals(other.latField) ||
!this.lngField.equals(other.lngField)) {
return false;
}
return true;
}
/** Returns a hash code value for this object.*/
@Override
public int hashCode() {
int h = Double.valueOf(distance).hashCode();
h ^= startingFilter.hashCode();
h ^= Double.valueOf(lat).hashCode();
h ^= Double.valueOf(lng).hashCode();
h ^= latField.hashCode();
h ^= lngField.hashCode();
return h;
}
}

View File

@ -1,176 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.tier.projections;
/**
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class CartesianTierPlotter {
public static final String DEFALT_FIELD_PREFIX = "_tier_";
private static final double LOG_2 = Math.log(2);
final int tierLevel;
int tierLength;
int tierBoxes;
int tierVerticalPosDivider;
final IProjector projector;
final String fieldPrefix;
Double idd = Double.valueOf(180);
public CartesianTierPlotter (int tierLevel, IProjector projector, String fieldPrefix) {
this.tierLevel = tierLevel;
this.projector = projector;
this.fieldPrefix = fieldPrefix;
setTierLength();
setTierBoxes();
setTierVerticalPosDivider();
}
private void setTierLength (){
this.tierLength = (int) Math.pow(2 , this.tierLevel);
}
private void setTierBoxes () {
this.tierBoxes = (int)Math.pow(this.tierLength, 2);
}
/**
* Get nearest max power of 10 greater than
* the tierlen
* e.g
* tierId of 13 has tierLen 8192
* nearest max power of 10 greater than tierLen
* would be 10,000
*/
private void setTierVerticalPosDivider() {
// ceiling of log base 10 of tierLen
tierVerticalPosDivider = Double.valueOf(Math.ceil(
Math.log10(Integer.valueOf(this.tierLength).doubleValue()))).intValue();
//
tierVerticalPosDivider = (int)Math.pow(10, tierVerticalPosDivider );
}
public double getTierVerticalPosDivider(){
return tierVerticalPosDivider;
}
/**
* TierBoxId is latitude box id + longitude box id
* where latitude box id, and longitude box id are transposed in to position
* coordinates.
*
* @param latitude
* @param longitude
*/
public double getTierBoxId (double latitude, double longitude) {
double[] coords = projector.coords(latitude, longitude);
double id = getBoxId(coords[0]) + (getBoxId(coords[1]) / tierVerticalPosDivider);
return id ;
}
private double getBoxId (double coord){
return Math.floor(coord / (idd / this.tierLength));
}
@SuppressWarnings("unused")
private double getBoxId (double coord, int tierLen){
return Math.floor(coord / (idd / tierLen) );
}
/**
* get the string name representing current tier
* _localTier&lt;tiedId&gt;
*/
public String getTierFieldName (){
return fieldPrefix + this.tierLevel;
}
/**
* get the string name representing tierId
* _localTier&lt;tierId&gt;
* @param tierId
*/
public String getTierFieldName (int tierId){
return fieldPrefix + tierId;
}
/**
* Find the tier with the best fit for a bounding box
* Best fit is defined as the ceiling of
* log2 (circumference of earth / distance)
* distance is defined as the smallest box fitting
* the corner between a radius and a bounding box.
*
* Distances less than a mile return 15, finer granularity is
* in accurate
*/
public int bestFit(double miles){
//28,892 a rough circumference of the earth
int circ = 28892;
double r = miles / 2.0;
double corner = r - Math.sqrt(Math.pow(r, 2) / 2.0d);
double times = circ / corner;
int bestFit = (int)Math.ceil(log2(times)) + 1;
if (bestFit > 15) {
// 15 is the granularity of about 1 mile
// finer granularity isn't accurate with standard java math
return 15;
}
return bestFit;
}
/**
* a log to the base 2 formula
* <code>Math.log(value) / Math.log(2)</code>
* @param value
*/
public static double log2(double value) {
return Math.log(value) / LOG_2;
}
/**
* Returns the ID of the tier level plotting is occuring at
*
* @return ID of the tier level plotting is occuring at
*/
public int getTierLevelId() {
return this.tierLevel;
}
}

View File

@ -1,86 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.tier.projections;
/**
* Based on Sinusoidal Projections
* Project a latitude / longitude on a 2D cartesian map
* <p/>
* THIS PROJECTION IS WRONG, but it's not going to be fixed b/c it will break a lot of existing tests, plus we are deprecating
* most of the existing spatial and replacing with a more reliable approach.
*
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*
* @deprecated (3.1) Until we can put in place proper tests and a proper fix.
*/
@Deprecated
public class SinusoidalProjector implements IProjector {
public String coordsAsString(double latitude, double longitude) {
return null;
}
public double[] coords(double latitude, double longitude) {
double rlat = Math.toRadians(latitude);
double rlong = Math.toRadians(longitude);
double nlat = rlong * Math.cos(rlat);
double r[] = {nlat, rlong};
return r;
}
}
/*
This whole file should really be:*/
/**
* Based on Sinusoidal Projections
* Project a latitude / longitude on a 2D cartesian map using the Prime Meridian as the "central meridian"
*
* See http://en.wikipedia.org/wiki/Sinusoidal_projection
*
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
/*
public class SinusoidalProjector implements IProjector {
public String coordsAsString(double latitude, double longitude) {
double [] coords = coords(latitude, longitude);
return coords[0] + "," + coords[1];
}
public double[] coords(double latitude, double longitude) {
double rlat = latitude * DistanceUtils.DEGREES_TO_RADIANS;
double rlong = longitude * DistanceUtils.DEGREES_TO_RADIANS;
double x = rlong * Math.cos(rlat);
return new double[]{x, rlat};
}
}
*/

View File

@ -1,47 +0,0 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<head>
<title>
Geographical filtering & sorting with Lucene
</title>
</head>
<body>
<p>This package makes it possible to filter and sort according to
geographical constraints. For example, filter to include only
restaurants within 2 miles of a specified latitude/longitude, sorting
by distance ascending.</p>
<p>See <a href="http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html">here</a>
for details on the technical approach.</p>
<p>Unfortunately, this package is still very new, and has little to no
documentation. It's best to ask for pointers on
java-user@lucene.apache.org, and look at the unit tests included in
the source distribution.</p>
<p>There are also known issues, eg at
least <a href="https://issues.apache.org/jira/browse/LUCENE-1815">LUCENE-1781</a>
and <a href="https://issues.apache.org/jira/browse/LUCENE-1815">LUCENE-1815</a>.</p>
<p><font color="red"><b>NOTE:</b> This package is still in flux and
might change in incompatible ways in the next release.</font>
</body>
</html>

View File

@ -1,283 +0,0 @@
package org.apache.lucene.spatial;
import org.apache.lucene.spatial.tier.InvalidGeoException;
import org.apache.lucene.util.LuceneTestCase;
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
*
*
**/
public class DistanceUtilsTest extends LuceneTestCase {
public void testBoxCorner() throws Exception {
double[] zero = new double[]{0, 0};
double[] zeroOne = new double[]{0, 1};
double[] oneOne = new double[]{1, 1};
double[] pt1 = new double[]{1.5, 110.3};
double[] result = DistanceUtils.vectorBoxCorner(zero, null, Math.sqrt(2), true);
assertEquals(1.0, result[0], 0);
assertEquals(1.0, result[1], 0);
result = DistanceUtils.vectorBoxCorner(zero, null, Math.sqrt(2), false);
assertEquals(-1.0, result[0], 0);
assertEquals(-1.0, result[1], 0);
result = DistanceUtils.vectorBoxCorner(oneOne, null, Math.sqrt(2), true);
assertEquals(2.0, result[0], 0);
assertEquals(2.0, result[1], 0);
result = DistanceUtils.vectorBoxCorner(zeroOne, null, Math.sqrt(2), true);
assertEquals(1.0, result[0], 0);
assertEquals(2.0, result[1], 0);
result = DistanceUtils.vectorBoxCorner(pt1, null, Math.sqrt(2), true);
assertEquals(2.5, result[0], 0.1);
assertEquals(111.3, result[1], 0.1);
result = DistanceUtils.vectorBoxCorner(pt1, null, Math.sqrt(2), false);
assertEquals(0.5, result[0], 0.1);
assertEquals(109.3, result[1], 0.1);
}
public void testNormLatLon() throws Exception {
}
public void testLatLonCorner() throws Exception {
double[] zero = new double[]{0, 0};
double[] zero45 = new double[]{0, DistanceUtils.DEG_45_AS_RADS};
double[] result;
// 00°3809N, 000°3809E
//Verify at http://www.movable-type.co.uk/scripts/latlong.html
result = DistanceUtils.latLonCorner(zero[0], zero[1], 100, null, true, DistanceUtils.EARTH_MEAN_RADIUS_KM);
assertEquals(0.63583 * DistanceUtils.DEGREES_TO_RADIANS, result[0], 0.001);
assertEquals(0.63583 * DistanceUtils.DEGREES_TO_RADIANS, result[1], 0.001);
result = DistanceUtils.latLonCornerDegs(zero[0], zero[1], 100, null, true, DistanceUtils.EARTH_MEAN_RADIUS_KM);
// 00°3809N, 000°3809E
assertEquals(0.63583, result[0], 0.001);
assertEquals(0.63583, result[1], 0.001);
result = DistanceUtils.latLonCornerDegs(zero[0], zero[1], 100, null, false, DistanceUtils.EARTH_MEAN_RADIUS_KM);
// 00°3809N, 000°3809E
assertEquals(-0.63583, result[0], 0.001);
assertEquals(-0.63583, result[1], 0.001);
//test some edge cases
//89°1602N, 060°1235E
result = DistanceUtils.latLonCornerDegs(89.0, 0, 100, null, true, DistanceUtils.EARTH_MEAN_RADIUS_KM);
assertEquals(89.26722, result[0], 0.001);
assertEquals(60.20972, result[1], 0.001);
result = DistanceUtils.latLonCornerDegs(0, -179.0, 100, null, true, DistanceUtils.EARTH_MEAN_RADIUS_KM);
assertEquals(0.63583, result[0], 0.001);
assertEquals(-178.36417, result[1], 0.001);
}
public void testPointBearing() throws Exception {
double[] zero = new double[]{0, 0};
double[] zero45 = new double[]{40 * DistanceUtils.DEGREES_TO_RADIANS, DistanceUtils.DEG_45_AS_RADS};
double[] result;
// 00°3809N, 000°3809E
//Verify at http://www.movable-type.co.uk/scripts/latlong.html
result = DistanceUtils.pointOnBearing(zero[0], zero[1], 100, DistanceUtils.DEG_45_AS_RADS, null, DistanceUtils.EARTH_MEAN_RADIUS_KM);
assertEquals(0.63583 * DistanceUtils.DEGREES_TO_RADIANS, result[0], 0.001);
assertEquals(0.63583 * DistanceUtils.DEGREES_TO_RADIANS, result[1], 0.001);
//should be above the current point at 0.8994°,0.0000°
result = DistanceUtils.pointOnBearing(zero[0], zero[1], 100, 0, null, DistanceUtils.EARTH_MEAN_RADIUS_KM);
assertEquals(0.8994 * DistanceUtils.DEGREES_TO_RADIANS, result[0], 0.001);
assertEquals(0, result[1], 0.001);
//directly below
result = DistanceUtils.pointOnBearing(zero[0], zero[1], 100, DistanceUtils.DEG_180_AS_RADS, null, DistanceUtils.EARTH_MEAN_RADIUS_KM);
assertEquals(-0.8994 * DistanceUtils.DEGREES_TO_RADIANS, result[0], 0.001);
assertEquals(0, result[1], 0.001);
//0.7183°,0.5414° -- 37 deg bearing
result = DistanceUtils.pointOnBearing(zero[0], zero[1], 100, 37 * DistanceUtils.DEGREES_TO_RADIANS, null, DistanceUtils.EARTH_MEAN_RADIUS_KM);
assertEquals(0.7183 * DistanceUtils.DEGREES_TO_RADIANS, result[0], 0.001);
assertEquals(0.5414 * DistanceUtils.DEGREES_TO_RADIANS, result[1], 0.001);
result = DistanceUtils.pointOnBearing(zero45[0], zero45[1], 100, DistanceUtils.DEG_45_AS_RADS, null, DistanceUtils.EARTH_MEAN_RADIUS_KM);
//40.6328°,45.8381°
assertEquals(40.6328 * DistanceUtils.DEGREES_TO_RADIANS, result[0], 0.001);
assertEquals(45.8381 * DistanceUtils.DEGREES_TO_RADIANS, result[1], 0.001);
result = DistanceUtils.pointOnBearing(1 * DistanceUtils.DEGREES_TO_RADIANS, 1 * DistanceUtils.DEGREES_TO_RADIANS, 100, DistanceUtils.DEG_90_AS_RADS, null, DistanceUtils.EARTH_MEAN_RADIUS_KM);
//0.9997°,1.8994°
assertEquals(0.9997 * DistanceUtils.DEGREES_TO_RADIANS, result[0], 0.001);
assertEquals(1.8994 * DistanceUtils.DEGREES_TO_RADIANS, result[1], 0.001);
result = DistanceUtils.pointOnBearing(-10 * DistanceUtils.DEGREES_TO_RADIANS, -150 * DistanceUtils.DEGREES_TO_RADIANS, 15, 205*DistanceUtils.DEGREES_TO_RADIANS, null, DistanceUtils.EARTH_MEAN_RADIUS_KM);
//-10.1222°,-150.0578°
assertEquals(-10.1222 * DistanceUtils.DEGREES_TO_RADIANS, result[0], 0.001);
assertEquals(-150.0578 * DistanceUtils.DEGREES_TO_RADIANS, result[1], 0.001);
result = DistanceUtils.pointOnBearing(-10 * DistanceUtils.DEGREES_TO_RADIANS, -150 * DistanceUtils.DEGREES_TO_RADIANS, 200, 63*DistanceUtils.DEGREES_TO_RADIANS, null, DistanceUtils.EARTH_MEAN_RADIUS_KM);
//-9.1797°,-148.3767°
assertEquals(-9.1797 * DistanceUtils.DEGREES_TO_RADIANS, result[0], 0.001);
assertEquals(-148.3767 * DistanceUtils.DEGREES_TO_RADIANS, result[1], 0.001);
result = DistanceUtils.pointOnBearing(-10 * DistanceUtils.DEGREES_TO_RADIANS, -150 * DistanceUtils.DEGREES_TO_RADIANS, 3000, 63*DistanceUtils.DEGREES_TO_RADIANS, null, DistanceUtils.EARTH_MEAN_RADIUS_KM);
//2.7561°,-126.1281°
assertEquals(2.7561 * DistanceUtils.DEGREES_TO_RADIANS, result[0], 0.001);
assertEquals(-126.1281 * DistanceUtils.DEGREES_TO_RADIANS, result[1], 0.001);
}
public void testVectorDistance() throws Exception {
double[] zero = new double[]{0, 0};
double[] zeroOne = new double[]{0, 1};
double[] oneZero = new double[]{1, 0};
double[] oneOne = new double[]{1, 1};
double distance;
distance = DistanceUtils.vectorDistance(zero, zeroOne, 2);
assertEquals(1.0, distance, 0);
distance = DistanceUtils.vectorDistance(zero, oneZero, 2);
assertEquals(1.0, distance, 0);
distance = DistanceUtils.vectorDistance(zero, oneOne, 2);
assertEquals(Math.sqrt(2), distance, 0.001);
distance = DistanceUtils.squaredEuclideanDistance(zero, oneOne);
assertEquals(2, distance, 0.001);
}
public void testHaversine() throws Exception {
double distance;
//compare to http://www.movable-type.co.uk/scripts/latlong.html
distance = DistanceUtils.haversine(0, 0, Math.PI / 4.0, Math.PI / 4.0, DistanceUtils.EARTH_MEAN_RADIUS_KM);
assertEquals(6672.0, distance, 0.5);
distance = DistanceUtils.haversine(0, 0, Math.toRadians(20), Math.toRadians(20), DistanceUtils.EARTH_MEAN_RADIUS_KM);
assertEquals(3112, distance, 0.5);
distance = DistanceUtils.haversine(0, 0, Math.toRadians(1), Math.toRadians(1), DistanceUtils.EARTH_MEAN_RADIUS_KM);
assertEquals(157.2, distance, 0.5);
//Try some around stuff
distance = DistanceUtils.haversine(Math.toRadians(1), Math.toRadians(-1),
Math.toRadians(1), Math.toRadians(1), DistanceUtils.EARTH_MEAN_RADIUS_KM);
assertEquals(222.4, distance, 0.5);
distance = DistanceUtils.haversine(Math.toRadians(89), Math.toRadians(-1),
Math.toRadians(89), Math.toRadians(179), DistanceUtils.EARTH_MEAN_RADIUS_KM);
assertEquals(222.4, distance, 0.5);
distance = DistanceUtils.haversine(Math.toRadians(89), Math.toRadians(-1),
Math.toRadians(49), Math.toRadians(179), DistanceUtils.EARTH_MEAN_RADIUS_KM);
assertEquals(4670, distance, 0.5);
distance = DistanceUtils.haversine(Math.toRadians(0), Math.toRadians(-179),
Math.toRadians(0), Math.toRadians(179), DistanceUtils.EARTH_MEAN_RADIUS_KM);
assertEquals(222.4, distance, 0.5);
}
public void testParse() throws Exception {
String[] parse;
parse = DistanceUtils.parsePoint(null, "89.0,73.2", 2);
assertEquals(2, parse.length);
assertEquals("89.0", parse[0]);
assertEquals("73.2", parse[1]);
parse = DistanceUtils.parsePoint(null, "89.0,73.2,-92.3", 3);
assertEquals(3, parse.length);
assertEquals("89.0", parse[0]);
assertEquals("73.2", parse[1]);
assertEquals("-92.3", parse[2]);
parse = DistanceUtils.parsePoint(null, " 89.0 , 73.2 , -92.3 ", 3);
assertEquals(3, parse.length);
assertEquals("89.0", parse[0]);
assertEquals("73.2", parse[1]);
assertEquals("-92.3", parse[2]);
String[] foo = DistanceUtils.parsePoint(parse, "89.0 , 73.2 , -92.3", 3);
//should be same piece of memory
assertTrue(foo == parse);
assertEquals(3, parse.length);
assertEquals("89.0", parse[0]);
assertEquals("73.2", parse[1]);
assertEquals("-92.3", parse[2]);
//array should get automatically resized
parse = DistanceUtils.parsePoint(new String[1], "89.0 , 73.2 , -92.3", 3);
assertEquals(3, parse.length);
assertEquals("89.0", parse[0]);
assertEquals("73.2", parse[1]);
assertEquals("-92.3", parse[2]);
try {
parse = DistanceUtils.parsePoint(null, "89.0 , ", 3);
assertTrue(false);
} catch (InvalidGeoException e) {
}
try {
parse = DistanceUtils.parsePoint(null, " , 89.0 ", 3);
assertTrue(false);
} catch (InvalidGeoException e) {
}
try {
parse = DistanceUtils.parsePoint(null, "", 3);
assertTrue(false);
} catch (InvalidGeoException e) {
}
double[] dbls = DistanceUtils.parsePointDouble(null, "89.0 , 73.2 , -92.3", 3);
assertEquals(3, dbls.length);
assertEquals(89.0, dbls[0], 0);
assertEquals(73.2, dbls[1], 0.1);
assertEquals(-92.3, dbls[2], 0.1);
try {
dbls = DistanceUtils.parsePointDouble(null, "89.0 , foo , -92.3", 3);
assertTrue(false);
} catch (NumberFormatException e) {
}
dbls = DistanceUtils.parseLatitudeLongitude(null, "89.0 , 73.2 ");
assertEquals(2, dbls.length);
assertEquals(89.0, dbls[0], 0.1);
assertEquals(73.2, dbls[1], 0.1);
//test some bad lat/long pairs
try {
dbls = DistanceUtils.parseLatitudeLongitude(null, "189.0 , 73.2 ");
assertTrue(false);
} catch (InvalidGeoException e) {
}
try {
dbls = DistanceUtils.parseLatitudeLongitude(null, "89.0 , 273.2 ");
assertTrue(false);
} catch (InvalidGeoException e) {
}
}
}

View File

@ -1,88 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.geohash;
import org.apache.lucene.util.LuceneTestCase;
import org.junit.Test;
/**
* Tests for {@link GeoHashUtils}
*/
public class TestGeoHashUtils extends LuceneTestCase {
/**
* 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.encode(42.6, -5.6);
assertEquals("ezs42e44yx96", hash);
hash = GeoHashUtils.encode(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.encode(52.3738007, 4.8909347);
double[] latitudeLongitude = GeoHashUtils.decode(hash);
assertEquals(52.3738007, latitudeLongitude[0], 0.00001D);
assertEquals(4.8909347, latitudeLongitude[1], 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.encode(84.6, 10.5);
double[] latitudeLongitude = GeoHashUtils.decode(hash);
assertEquals(84.6, latitudeLongitude[0], 0.00001D);
assertEquals(10.5, latitudeLongitude[1], 0.00001D);
}
/*
* see https://issues.apache.org/jira/browse/LUCENE-1815 for details
*/
@Test
public void testDecodeEncode() {
String geoHash = "u173zq37x014";
assertEquals(geoHash, GeoHashUtils.encode(52.3738007, 4.8909347));
double[] decode = GeoHashUtils.decode(geoHash);
assertEquals(52.37380061d, decode[0], 0.000001d);
assertEquals(4.8909343d, decode[1], 0.000001d);
assertEquals(geoHash, GeoHashUtils.encode(decode[0], decode[1]));
geoHash = "u173";
decode = GeoHashUtils.decode("u173");
geoHash = GeoHashUtils.encode(decode[0], decode[1]);
assertEquals(decode[0], GeoHashUtils.decode(geoHash)[0], 0.000001d);
assertEquals(decode[1], GeoHashUtils.decode(geoHash)[1], 0.000001d);
}
}

View File

@ -1,62 +0,0 @@
package org.apache.lucene.spatial.geometry;
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.util.LuceneTestCase;
import org.junit.Test;
/**
* Tests for {@link org.apache.lucene.spatial.geometry.DistanceUnits}
*/
public class TestDistanceUnits extends LuceneTestCase {
/**
* Pass condition: When finding the DistanceUnit for "km", KILOMETRES is found. When finding the DistanceUnit for
* "miles", MILES is found.
*/
@Test
public void testFindDistanceUnit() {
assertEquals(DistanceUnits.KILOMETERS, DistanceUnits.findDistanceUnit("km"));
assertEquals(DistanceUnits.MILES, DistanceUnits.findDistanceUnit("miles"));
}
/**
* Pass condition: Searching for the DistanceUnit of an unknown unit "mls" should throw an IllegalArgumentException.
*/
@Test
public void testFindDistanceUnit_unknownUnit() {
try {
DistanceUnits.findDistanceUnit("mls");
assertTrue("IllegalArgumentException should have been thrown", false);
} catch (IllegalArgumentException iae) {
// Expected
}
}
/**
* Pass condition: Converting between the same units should not change the value. Converting from MILES to KILOMETRES
* involves multiplying the distance by the ratio, and converting from KILOMETRES to MILES involves dividing by the ratio
*/
@Test
public void testConvert() {
assertEquals(10.5, DistanceUnits.MILES.convert(10.5, DistanceUnits.MILES), 0D);
assertEquals(10.5, DistanceUnits.KILOMETERS.convert(10.5, DistanceUnits.KILOMETERS), 0D);
assertEquals(10.5 * 1.609344, DistanceUnits.KILOMETERS.convert(10.5, DistanceUnits.MILES), 0D);
assertEquals(10.5 / 1.609344, DistanceUnits.MILES.convert(10.5, DistanceUnits.KILOMETERS), 0D);
}
}

View File

@ -1,493 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.tier;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.FieldType.NumericType;
import org.apache.lucene.document.DoubleField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.FieldInfo.IndexOptions;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.spatial.DistanceUtils;
import org.apache.lucene.spatial.geohash.GeoHashUtils;
import org.apache.lucene.spatial.geometry.DistanceUnits;
import org.apache.lucene.spatial.geometry.FloatLatLng;
import org.apache.lucene.spatial.geometry.LatLng;
import org.apache.lucene.spatial.tier.projections.CartesianTierPlotter;
import org.apache.lucene.spatial.tier.projections.IProjector;
import org.apache.lucene.spatial.tier.projections.SinusoidalProjector;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.LuceneTestCase;
public class TestCartesian extends LuceneTestCase {
private Directory directory;
private IndexSearcher searcher;
// reston va
private double lat = 38.969398;
private double lng= -77.386398;
private String latField = "lat";
private String lngField = "lng";
private List<CartesianTierPlotter> ctps = new LinkedList<CartesianTierPlotter>();
private String geoHashPrefix = "_geoHash_";
private IProjector project = new SinusoidalProjector();
@Override
public void setUp() throws Exception {
super.setUp();
directory = newDirectory();
IndexWriter writer = new IndexWriter(directory, newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)));
setUpPlotter( 2, 15);
addData(writer);
}
@Override
public void tearDown() throws Exception {
directory.close();
super.tearDown();
}
private void setUpPlotter(int base, int top) {
for (; base <= top; base ++){
ctps.add(new CartesianTierPlotter(base,project,
CartesianTierPlotter.DEFALT_FIELD_PREFIX));
}
}
private static final FieldType latLongType = new FieldType();
static {
latLongType.setIndexed(true);
latLongType.setStored(true);
latLongType.setTokenized(true);
latLongType.setOmitNorms(true);
latLongType.setIndexOptions(IndexOptions.DOCS_ONLY);
latLongType.setNumericType(NumericType.DOUBLE);
latLongType.setNumericPrecisionStep(Integer.MAX_VALUE);
latLongType.freeze();
}
private void addPoint(IndexWriter writer, String name, double lat, double lng) throws IOException{
Document doc = new Document();
doc.add(newField("name", name, TextField.TYPE_STORED));
// convert the lat / long to lucene fields
doc.add(new DoubleField(latField, lat, latLongType));
doc.add(new DoubleField(lngField, lng, latLongType));
// add a default meta field to make searching all documents easy
doc.add(newField("metafile", "doc", TextField.TYPE_STORED));
int ctpsize = ctps.size();
for (int i =0; i < ctpsize; i++){
CartesianTierPlotter ctp = ctps.get(i);
doc.add(new DoubleField(ctp.getTierFieldName(), ctp.getTierBoxId(lat, lng), latLongType));
doc.add(newField(geoHashPrefix, GeoHashUtils.encode(lat,lng), StringField.TYPE_STORED));
}
writer.addDocument(doc);
}
private void addData(IndexWriter writer) throws IOException {
addPoint(writer,"McCormick &amp; Schmick's Seafood Restaurant",38.9579000,-77.3572000);
addPoint(writer,"Jimmy's Old Town Tavern",38.9690000,-77.3862000);
addPoint(writer,"Ned Devine's",38.9510000,-77.4107000);
addPoint(writer,"Old Brogue Irish Pub",38.9955000,-77.2884000);
addPoint(writer,"Alf Laylah Wa Laylah",38.8956000,-77.4258000);
addPoint(writer,"Sully's Restaurant &amp; Supper",38.9003000,-77.4467000);
addPoint(writer,"TGIFriday",38.8725000,-77.3829000);
addPoint(writer,"Potomac Swing Dance Club",38.9027000,-77.2639000);
addPoint(writer,"White Tiger Restaurant",38.9027000,-77.2638000);
addPoint(writer,"Jammin' Java",38.9039000,-77.2622000);
addPoint(writer,"Potomac Swing Dance Club",38.9027000,-77.2639000);
addPoint(writer,"WiseAcres Comedy Club",38.9248000,-77.2344000);
addPoint(writer,"Glen Echo Spanish Ballroom",38.9691000,-77.1400000);
addPoint(writer,"Whitlow's on Wilson",38.8889000,-77.0926000);
addPoint(writer,"Iota Club and Cafe",38.8890000,-77.0923000);
addPoint(writer,"Hilton Washington Embassy Row",38.9103000,-77.0451000);
addPoint(writer,"HorseFeathers, Bar & Grill", 39.01220000000001, -77.3942);
addPoint(writer,"Marshall Island Airfield",7.06, 171.2);
addPoint(writer, "Wonga Wongue Reserve, Gabon", -0.546562,9.459229);
addPoint(writer,"Midway Island",25.7, -171.7);
addPoint(writer,"North Pole Way",55.0, 4.0);
writer.commit();
// TODO: fix CustomScoreQuery usage in testRange/testGeoHashRange so we don't need this.
writer.forceMerge(1);
writer.close();
}
public void testDistances() throws IOException, InvalidGeoException {
LatLng p1 = new FloatLatLng( 7.06, 171.2 );
LatLng p2 = new FloatLatLng( 21.6032207, -158.0 );
double miles = p1.arcDistance( p2, DistanceUnits.MILES );
if (VERBOSE) {
System.out.println("testDistances");
System.out.println("miles:" + miles);
}
assertEquals(2288.82495932794, miles, 0.001);
LatLng p3 = new FloatLatLng( 41.6032207, -73.087749);
LatLng p4 = new FloatLatLng( 55.0, 4.0 );
miles = p3.arcDistance( p4, DistanceUnits.MILES );
if (VERBOSE) System.out.println("miles:" + miles);
assertEquals(3474.331719997617, miles, 0.001);
}
/*public void testCartesianPolyFilterBuilder() throws Exception {
CartesianPolyFilterBuilder cpfb = new CartesianPolyFilterBuilder(CartesianTierPlotter.DEFALT_FIELD_PREFIX, 2, 15);
//try out some shapes
final double miles = 20.0;
// Hawaii
// 2300 miles to Marshall Island Airfield
//Hawaii to Midway is 911 miles
lat = 0;
lng = -179.9;
Shape shape;
shape = cpfb.getBoxShape(lat, lng, miles);
System.out.println("Tier: " + shape.getTierLevel());
System.out.println("area: " + shape.getArea().size());
lat = 30;
lng = -100;
shape = cpfb.getBoxShape(lat, lng, miles);
System.out.println("Tier: " + shape.getTierLevel());
System.out.println("area: " + shape.getArea().size());
lat = 30;
lng = 100;
shape = cpfb.getBoxShape(lat, lng, miles);
System.out.println("Tier: " + shape.getTierLevel());
System.out.println("area: " + shape.getArea().size());
}
*/
public void testAntiM() throws IOException, InvalidGeoException {
IndexReader reader = IndexReader.open(directory);
searcher = new IndexSearcher(reader);
final double miles = 2800.0;
// Hawaii
// 2300 miles to Marshall Island Airfield
//Hawaii to Midway is 911 miles
lat = 21.6032207;
lng = -158.0;
if (VERBOSE) System.out.println("testAntiM");
// create a distance query
final DistanceQueryBuilder dq = new DistanceQueryBuilder(lat, lng, miles,
latField, lngField, CartesianTierPlotter.DEFALT_FIELD_PREFIX, true, 2, 15);
if (VERBOSE) System.out.println(dq);
//create a term query to search against all documents
Query tq = new TermQuery(new Term("metafile", "doc"));
// Create a distance sort
// As the radius filter has performed the distance calculations
// already, pass in the filter to reuse the results.
//
DistanceFieldComparatorSource dsort = new DistanceFieldComparatorSource(dq.distanceFilter);
Sort sort = new Sort(new SortField("foo", dsort,false));
// Perform the search, using the term query, the serial chain filter, and the
// distance sort
TopDocs hits = searcher.search(dq.getQuery(tq),null, 1000, sort);
int results = hits.totalHits;
ScoreDoc[] scoreDocs = hits.scoreDocs;
// Get a list of distances
Map<Integer,Double> distances = dq.distanceFilter.getDistances();
// distances calculated from filter first pass must be less than total
// docs, from the above test of 20 items, 12 will come from the boundary box
// filter, but only 5 are actually in the radius of the results.
// Note Boundary Box filtering, is not accurate enough for most systems.
if (VERBOSE) {
System.out.println("Distance Filter filtered: " + distances.size());
System.out.println("Results: " + results);
System.out.println("=============================");
System.out.println("Distances should be 2 "+ distances.size());
System.out.println("Results should be 2 "+ results);
}
assertEquals(2, distances.size()); // fixed a store of only needed distances
assertEquals(2, results);
double lastDistance = 0;
for(int i =0 ; i < results; i++){
Document d = searcher.doc(scoreDocs[i].doc);
String name = d.get("name");
double rsLat = d.getField(latField).numericValue().doubleValue();
double rsLng = d.getField(lngField).numericValue().doubleValue();
Double geo_distance = distances.get(scoreDocs[i].doc);
double distance = DistanceUtils.getDistanceMi(lat, lng, rsLat, rsLng);
double llm = DistanceUtils.getLLMDistance(lat, lng, rsLat, rsLng);
if (VERBOSE) System.out.println("Name: "+ name +", Distance "+ distance); //(res, ortho, harvesine):"+ distance +" |"+ geo_distance +"|"+ llm +" | score "+ hits.score(i));
assertTrue(Math.abs((distance - llm)) < 1);
assertTrue((distance < miles ));
assertTrue(geo_distance >= lastDistance);
lastDistance = geo_distance;
}
reader.close();
}
public void testPoleFlipping() throws IOException, InvalidGeoException {
IndexReader reader = IndexReader.open(directory);
searcher = new IndexSearcher(reader);
final double miles = 3500.0;
lat = 41.6032207;
lng = -73.087749;
if (VERBOSE) System.out.println("testPoleFlipping");
// create a distance query
final DistanceQueryBuilder dq = new DistanceQueryBuilder(lat, lng, miles,
latField, lngField, CartesianTierPlotter.DEFALT_FIELD_PREFIX, true, 2, 15);
if (VERBOSE) System.out.println(dq);
//create a term query to search against all documents
Query tq = new TermQuery(new Term("metafile", "doc"));
// Create a distance sort
// As the radius filter has performed the distance calculations
// already, pass in the filter to reuse the results.
//
DistanceFieldComparatorSource dsort = new DistanceFieldComparatorSource(dq.distanceFilter);
Sort sort = new Sort(new SortField("foo", dsort,false));
// Perform the search, using the term query, the serial chain filter, and the
// distance sort
TopDocs hits = searcher.search(dq.getQuery(tq),null, 1000, sort);
int results = hits.totalHits;
ScoreDoc[] scoreDocs = hits.scoreDocs;
// Get a list of distances
Map<Integer,Double> distances = dq.distanceFilter.getDistances();
// distances calculated from filter first pass must be less than total
// docs, from the above test of 20 items, 12 will come from the boundary box
// filter, but only 5 are actually in the radius of the results.
// Note Boundary Box filtering, is not accurate enough for most systems.
if (VERBOSE) {
System.out.println("Distance Filter filtered: " + distances.size());
System.out.println("Results: " + results);
System.out.println("=============================");
System.out.println("Distances should be 18 "+ distances.size());
System.out.println("Results should be 18 "+ results);
}
assertEquals(18, distances.size()); // fixed a store of only needed distances
assertEquals(18, results);
double lastDistance = 0;
for(int i =0 ; i < results; i++){
Document d = searcher.doc(scoreDocs[i].doc);
String name = d.get("name");
double rsLat = d.getField(latField).numericValue().doubleValue();
double rsLng = d.getField(lngField).numericValue().doubleValue();
Double geo_distance = distances.get(scoreDocs[i].doc);
double distance = DistanceUtils.getDistanceMi(lat, lng, rsLat, rsLng);
double llm = DistanceUtils.getLLMDistance(lat, lng, rsLat, rsLng);
if (VERBOSE) System.out.println("Name: "+ name +", Distance "+ distance); //(res, ortho, harvesine):"+ distance +" |"+ geo_distance +"|"+ llm +" | score "+ hits.score(i));
assertTrue(Math.abs((distance - llm)) < 1);
if (VERBOSE) System.out.println("checking limit "+ distance + " < " + miles);
assertTrue((distance < miles ));
if (VERBOSE) System.out.println("checking sort "+ geo_distance + " >= " + lastDistance);
assertTrue(geo_distance >= lastDistance);
lastDistance = geo_distance;
}
reader.close();
}
public void testRange() throws IOException, InvalidGeoException {
IndexReader reader = IndexReader.open(directory);
searcher = new IndexSearcher(reader);
final double[] milesToTest = new double[] {6.0, 0.5, 0.001, 0.0};
final int[] expected = new int[] {7, 1, 0, 0};
for(int x=0;x<expected.length;x++) {
final double miles = milesToTest[x];
// create a distance query
final DistanceQueryBuilder dq = new DistanceQueryBuilder(lat, lng, miles,
latField, lngField, CartesianTierPlotter.DEFALT_FIELD_PREFIX, true, 2, 15);
if (VERBOSE) System.out.println(dq);
//create a term query to search against all documents
Query tq = new TermQuery(new Term("metafile", "doc"));
// Create a distance sort
// As the radius filter has performed the distance calculations
// already, pass in the filter to reuse the results.
//
DistanceFieldComparatorSource dsort = new DistanceFieldComparatorSource(dq.distanceFilter);
Sort sort = new Sort(new SortField("foo", dsort,false));
// Perform the search, using the term query, the serial chain filter, and the
// distance sort
TopDocs hits = searcher.search(dq.getQuery(tq),null, 1000, sort);
int results = hits.totalHits;
ScoreDoc[] scoreDocs = hits.scoreDocs;
// Get a list of distances
Map<Integer,Double> distances = dq.distanceFilter.getDistances();
// distances calculated from filter first pass must be less than total
// docs, from the above test of 20 items, 12 will come from the boundary box
// filter, but only 5 are actually in the radius of the results.
// Note Boundary Box filtering, is not accurate enough for most systems.
if (VERBOSE) {
System.out.println("Distance Filter filtered: " + distances.size());
System.out.println("Results: " + results);
System.out.println("=============================");
System.out.println("Distances should be 7 "+ expected[x] + ":" + distances.size());
System.out.println("Results should be 7 "+ expected[x] + ":" + results);
}
assertEquals(expected[x], distances.size()); // fixed a store of only needed distances
assertEquals(expected[x], results);
double lastDistance = 0;
for(int i =0 ; i < results; i++){
Document d = searcher.doc(scoreDocs[i].doc);
String name = d.get("name");
double rsLat = d.getField(latField).numericValue().doubleValue();
double rsLng = d.getField(lngField).numericValue().doubleValue();
Double geo_distance = distances.get(scoreDocs[i].doc);
double distance = DistanceUtils.getDistanceMi(lat, lng, rsLat, rsLng);
double llm = DistanceUtils.getLLMDistance(lat, lng, rsLat, rsLng);
if (VERBOSE) System.out.println("Name: "+ name +", Distance "+ distance); //(res, ortho, harvesine):"+ distance +" |"+ geo_distance +"|"+ llm +" | score "+ hits.score(i));
assertTrue(Math.abs((distance - llm)) < 1);
assertTrue((distance < miles ));
assertTrue(geo_distance > lastDistance);
lastDistance = geo_distance;
}
}
reader.close();
}
public void testGeoHashRange() throws IOException, InvalidGeoException {
IndexReader reader = IndexReader.open(directory);
searcher = new IndexSearcher(reader);
final double[] milesToTest = new double[] {6.0, 0.5, 0.001, 0.0};
final int[] expected = new int[] {7, 1, 0, 0};
for(int x=0;x<expected.length;x++) {
final double miles = milesToTest[x];
// create a distance query
final DistanceQueryBuilder dq = new DistanceQueryBuilder(lat, lng, miles,
geoHashPrefix, CartesianTierPlotter.DEFALT_FIELD_PREFIX, true, 2, 15);
if (VERBOSE) System.out.println(dq);
//create a term query to search against all documents
Query tq = new TermQuery(new Term("metafile", "doc"));
// Create a distance sort
// As the radius filter has performed the distance calculations
// already, pass in the filter to reuse the results.
//
//DistanceFieldComparatorSource dsort = new DistanceFieldComparatorSource(dq.distanceFilter);
//Sort sort = new Sort(new SortField("foo", dsort));
// Perform the search, using the term query, the serial chain filter, and the
// distance sort
TopDocs hits = searcher.search(tq,dq.getFilter(), 1000); //,sort);
int results = hits.totalHits;
ScoreDoc[] scoreDocs = hits.scoreDocs;
// Get a list of distances
Map<Integer,Double> distances = dq.distanceFilter.getDistances();
// distances calculated from filter first pass must be less than total
// docs, from the above test of 20 items, 12 will come from the boundary box
// filter, but only 5 are actually in the radius of the results.
// Note Boundary Box filtering, is not accurate enough for most systems.
if (VERBOSE) {
System.out.println("Distance Filter filtered: " + distances.size());
System.out.println("Results: " + results);
System.out.println("=============================");
System.out.println("Distances should be 14 "+ expected[x] + ":" + distances.size());
System.out.println("Results should be 7 "+ expected[x] + ":" + results);
}
assertEquals(expected[x], distances.size());
assertEquals(expected[x], results);
for(int i =0 ; i < results; i++){
Document d = searcher.doc(scoreDocs[i].doc);
String name = d.get("name");
double rsLat = d.getField(latField).numericValue().doubleValue();
double rsLng = d.getField(lngField).numericValue().doubleValue();
Double geo_distance = distances.get(scoreDocs[i].doc);
double distance = DistanceUtils.getDistanceMi(lat, lng, rsLat, rsLng);
double llm = DistanceUtils.getLLMDistance(lat, lng, rsLat, rsLng);
if (VERBOSE) System.out.println("Name: "+ name +", Distance (res, ortho, harvesine):"+ distance +" |"+ geo_distance +"|"+ llm +" | score "+ scoreDocs[i].score);
assertTrue(Math.abs((distance - llm)) < 1);
assertTrue((distance < miles ));
}
}
reader.close();
}
}

View File

@ -1,158 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.tier;
import java.io.IOException;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.FieldType.NumericType;
import org.apache.lucene.document.DoubleField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.index.FieldInfo.IndexOptions;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.QueryWrapperFilter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.LuceneTestCase;
public class TestDistance extends LuceneTestCase {
private Directory directory;
// reston va
private double lat = 38.969398;
private double lng= -77.386398;
private String latField = "lat";
private String lngField = "lng";
private IndexWriter writer;
@Override
public void setUp() throws Exception {
super.setUp();
directory = newDirectory();
writer = new IndexWriter(directory, newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)));
addData(writer);
}
@Override
public void tearDown() throws Exception {
writer.close();
directory.close();
super.tearDown();
}
private static final FieldType latLongType = new FieldType();
static {
latLongType.setIndexed(true);
latLongType.setStored(true);
latLongType.setTokenized(true);
latLongType.setOmitNorms(true);
latLongType.setIndexOptions(IndexOptions.DOCS_ONLY);
latLongType.setNumericType(NumericType.DOUBLE);
latLongType.setNumericPrecisionStep(Integer.MAX_VALUE);
latLongType.freeze();
}
private void addPoint(IndexWriter writer, String name, double lat, double lng) throws IOException{
Document doc = new Document();
doc.add(newField("name", name, TextField.TYPE_STORED));
// convert the lat / long to lucene fields
doc.add(new DoubleField(latField, lat, latLongType));
doc.add(new DoubleField(lngField, lng, latLongType));
// add a default meta field to make searching all documents easy
doc.add(newField("metafile", "doc", TextField.TYPE_STORED));
writer.addDocument(doc);
}
private void addData(IndexWriter writer) throws IOException {
addPoint(writer,"McCormick &amp; Schmick's Seafood Restaurant",38.9579000,-77.3572000);
addPoint(writer,"Jimmy's Old Town Tavern",38.9690000,-77.3862000);
addPoint(writer,"Ned Devine's",38.9510000,-77.4107000);
addPoint(writer,"Old Brogue Irish Pub",38.9955000,-77.2884000);
addPoint(writer,"Alf Laylah Wa Laylah",38.8956000,-77.4258000);
addPoint(writer,"Sully's Restaurant &amp; Supper",38.9003000,-77.4467000);
addPoint(writer,"TGIFriday",38.8725000,-77.3829000);
addPoint(writer,"Potomac Swing Dance Club",38.9027000,-77.2639000);
addPoint(writer,"White Tiger Restaurant",38.9027000,-77.2638000);
addPoint(writer,"Jammin' Java",38.9039000,-77.2622000);
addPoint(writer,"Potomac Swing Dance Club",38.9027000,-77.2639000);
addPoint(writer,"WiseAcres Comedy Club",38.9248000,-77.2344000);
addPoint(writer,"Glen Echo Spanish Ballroom",38.9691000,-77.1400000);
addPoint(writer,"Whitlow's on Wilson",38.8889000,-77.0926000);
addPoint(writer,"Iota Club and Cafe",38.8890000,-77.0923000);
addPoint(writer,"Hilton Washington Embassy Row",38.9103000,-77.0451000);
addPoint(writer,"HorseFeathers, Bar & Grill", 39.01220000000001, -77.3942);
writer.commit();
}
public void testLatLongFilterOnDeletedDocs() throws Exception {
writer.deleteDocuments(new Term("name", "Potomac"));
IndexReader r = IndexReader.open(writer, true);
LatLongDistanceFilter f = new LatLongDistanceFilter(new QueryWrapperFilter(new MatchAllDocsQuery()),
lat, lng, 1.0, latField, lngField);
AtomicReaderContext[] leaves = r.getTopReaderContext().leaves();
for (int i = 0; i < leaves.length; i++) {
f.getDocIdSet(leaves[i], leaves[i].reader().getLiveDocs());
}
r.close();
}
/* these tests do not test anything, as no assertions:
public void testMiles() {
double LLM = DistanceUtils.getInstance().getLLMDistance(lat, lng,39.012200001, -77.3942);
System.out.println(LLM);
System.out.println("-->"+DistanceUtils.getInstance().getDistanceMi(lat, lng, 39.0122, -77.3942));
}
public void testMiles2(){
System.out.println("Test Miles 2");
double LLM = DistanceUtils.getInstance().getLLMDistance(44.30073, -78.32131,43.687267, -79.39842);
System.out.println(LLM);
System.out.println("-->"+DistanceUtils.getInstance().getDistanceMi(44.30073, -78.32131, 43.687267, -79.39842));
}
*/
// public void testDistanceQueryCacheable() throws IOException {
//
// // create two of the same distance queries
// double miles = 6.0;
// DistanceQuery dq1 = new DistanceQuery(lat, lng, miles, latField, lngField, true);
// DistanceQuery dq2 = new DistanceQuery(lat, lng, miles, latField, lngField, true);
//
// /* ensure that they hash to the same code, which will cause a cache hit in solr */
// System.out.println("hash differences?");
// assertEquals(dq1.getQuery().hashCode(), dq2.getQuery().hashCode());
//
// /* ensure that changing the radius makes a different hash code, creating a cache miss in solr */
// DistanceQuery widerQuery = new DistanceQuery(lat, lng, miles + 5.0, latField, lngField, false);
// assertTrue(dq1.getQuery().hashCode() != widerQuery.getQuery().hashCode());
// }
}

View File

@ -1,82 +0,0 @@
package org.apache.lucene.spatial.tier.projections;
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.util.LuceneTestCase;
import org.junit.Test;
/**
*
*
**/
public class SinusoidalProjectorTest extends LuceneTestCase {
@Test
public void testProjection() throws Exception {
SinusoidalProjector prj = new SinusoidalProjector();
//TODO: uncomment once SinusoidalProjector is fixed. Unfortunately, fixing it breaks a lot of other stuff
/*double[] doubles;
doubles = prj.coords(-89.0, 10);
assertEquals(0.003, doubles[0], 0.001);//x
assertEquals(-89.0 * DistanceUtils.DEGREES_TO_RADIANS, doubles[1]);
doubles = prj.coords(89.0, 0);
assertEquals(0.0, doubles[0]);//x
assertEquals(89.0 * DistanceUtils.DEGREES_TO_RADIANS, doubles[1]);
doubles = prj.coords(89.0, 10);
assertEquals(0.003, doubles[0], 0.001);//x
assertEquals(89.0 * DistanceUtils.DEGREES_TO_RADIANS, doubles[1]);
doubles = prj.coords(-89.0, 0);
assertEquals(0.0, doubles[0]);//x
assertEquals(-89.0 * DistanceUtils.DEGREES_TO_RADIANS, doubles[1]);*/
}
}
//This code demonstrates that the SinusoidalProjector is incorrect
/*@Test
public void testFoo() throws Exception {
CartesianTierPlotter plotter = new CartesianTierPlotter(11, new SinusoidalProjector(), "foo");
SinusoidalProjector prj = new SinusoidalProjector();
System.out.println("---- Equator ---");
printValues(plotter, prj, 0);
System.out.println("---- North ---");
printValues(plotter, prj, 89.0);
System.out.println("---- South ---");
printValues(plotter, prj, -89.0);
}
private void printValues(CartesianTierPlotter plotter, SinusoidalProjector prj, double latitude){
for (int i = 0; i <= 10; i++){
double boxId = plotter.getTierBoxId(latitude, i);
double[] doubles = prj.coords(latitude, i);
System.out.println("Box[" + latitude + ", " + i + "] = " + boxId + " sinusoidal: [" + doubles[0] + ", " + doubles[1] + "]");
}
for (int i = -10; i <= 0; i++){
double boxId = plotter.getTierBoxId(latitude, i);
double[] doubles = prj.coords(latitude, i);
System.out.println("Box[" + latitude + ", " + i + "] = " + boxId + " sinusoidal: [" + doubles[0] + ", " + doubles[1] + "]");
}
}
*/

202
modules/spatial/LICENSE.txt Normal file
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.

41
modules/spatial/build.xml Normal file
View File

@ -0,0 +1,41 @@
<?xml version="1.0"?>
<project name="spatial" default="default">
<description>
Lucene Spatial
</description>
<property name="build.dir" location="build/" />
<property name="dist.dir" location="dist/" />
<property name="maven.dist.dir" location="../dist/maven" />
<path id="additional.dependencies">
<fileset dir="lib" includes="*.jar"/>
</path>
<pathconvert property="project.classpath"
targetos="unix"
refid="additional.dependencies"
/>
<import file="../../lucene/contrib/contrib-build.xml"/>
<path id="classpath">
<path refid="base.classpath"/>
<pathelement path="${spatial-base.jar}" />
<pathelement path="${queries.jar}" />
</path>
<path id="test.classpath">
<path refid="test.base.classpath" />
<path refid="base.classpath"/>
<pathelement path="${analyzers-common.jar}" />
<pathelement path="src/test-files" />
</path>
<target name="init" depends="contrib-build.init"/>
<target name="dist-maven" depends="jar-core,javadocs,common.dist-maven"/>
<target name="compile" depends="jar-queries,common.compile-core" />
<target name="test" depends="jar-analyzers-common,compile-test,validate,junit-mkdir,junit-sequential,junit-parallel" description="Runs unit tests"/>
</project>

View File

@ -0,0 +1,2 @@
AnyObjectId[82d4eadc1a5301bb86440e1eac81834fea8cba49] was removed in git history.
Apache SVN contains full history.

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,5 @@
Apache Commons Lang
Copyright 2001-2008 The Apache Software Foundation
This product includes software developed by
The Apache Software Foundation (http://www.apache.org/).

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 SimpleSpatialFieldInfo implements SpatialFieldInfo {
private final String fieldName;
public SimpleSpatialFieldInfo(String fieldName) {
this.fieldName = fieldName;
}
public String getFieldName() {
return fieldName;
}
}

View File

@ -1,4 +1,4 @@
/**
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
@ -15,16 +15,10 @@
* limitations under the License.
*/
package org.apache.lucene.spatial.tier;
package org.apache.lucene.spatial;
/**
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
* Information the strategy needs for the lucene fields
*/
public class InvalidGeoException extends Exception {
public InvalidGeoException(String message){
super(message);
}
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;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.query.SpatialArgs;
import com.spatial4j.core.shape.Shape;
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;
/**
* 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

@ -1,4 +1,4 @@
/**
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
@ -15,17 +15,12 @@
* limitations under the License.
*/
package org.apache.lucene.spatial.geometry.shape;
/**
* Lucene spatial search
*
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
* Check:
* http://blog.notdot.net/2009/11/Damn-Cool-Algorithms-Spatial-indexing-with-Quadtrees-and-Hilbert-Curves
*/
public enum IntersectCase {
WITHIN,
CONTAINS,
OUTSIDE,
INTERSECTS;
}
package org.apache.lucene.spatial;

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.prefix;
import com.spatial4j.core.shape.Point;
import org.apache.lucene.spatial.prefix.tree.Node;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.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.prefix;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import java.io.IOException;
import java.io.Reader;
/**
*
*/
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.prefix;
import com.spatial4j.core.distance.DistanceCalculator;
import com.spatial4j.core.query.SpatialArgs;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Shape;
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.SimpleSpatialFieldInfo;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.lucene.spatial.prefix.tree.Node;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.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(com.spatial4j.core.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.prefix;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
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.prefix.tree.Node;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
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.prefix;
import com.spatial4j.core.exception.UnsupportedSpatialOperation;
import com.spatial4j.core.query.SpatialArgs;
import com.spatial4j.core.query.SpatialOperation;
import com.spatial4j.core.shape.Shape;
import org.apache.lucene.queries.function.FunctionQuery;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.FilteredQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.SimpleSpatialFieldInfo;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
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.prefix;
import com.spatial4j.core.exception.UnsupportedSpatialOperation;
import com.spatial4j.core.query.SpatialArgs;
import com.spatial4j.core.query.SpatialOperation;
import com.spatial4j.core.shape.Shape;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
import org.apache.lucene.spatial.SimpleSpatialFieldInfo;
import org.apache.lucene.spatial.prefix.tree.Node;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
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

@ -1,28 +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.
*/
package org.apache.lucene.spatial.tier.projections;
/**
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public interface IProjector {
public String coordsAsString(double latitude, double longitude);
public double[] coords(double latitude, double longitude);
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.prefix;

View File

@ -0,0 +1,149 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.prefix.tree;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.util.GeohashUtils;
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,212 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.prefix.tree;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
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 com.spatial4j.core.shape.Point} then it
* must call {@link #getSubCell(com.spatial4j.core.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.
*/
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,299 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.prefix.tree;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.SpatialRelation;
import com.spatial4j.core.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,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.prefix.tree;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.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 com.spatial4j.core.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(com.spatial4j.core.shape.Point)}. Cell subclasses
* ideally implement that method with a quick implementation, otherwise, subclasses should
* override this method to invoke {@link #getNodesAltPoint(com.spatial4j.core.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(com.spatial4j.core.shape.Shape, int, boolean)}
* and check if the argument is a shape and if so, delegate
* to this implementation, which calls {@link #getNode(com.spatial4j.core.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,93 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.prefix.tree;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUnits;
import com.spatial4j.core.distance.DistanceUtils;
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,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.prefix.tree;

View File

@ -0,0 +1,102 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.util;
import com.spatial4j.core.distance.DistanceCalculator;
import com.spatial4j.core.shape.Point;
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.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 o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CachedDistanceValueSource that = (CachedDistanceValueSource) o;
if (calculator != null ? !calculator.equals(that.calculator) : that.calculator != null) return false;
if (from != null ? !from.equals(that.from) : that.from != null) return false;
return true;
}
@Override
public int hashCode() {
int result = calculator != null ? calculator.hashCode() : 0;
result = 31 * result + (from != null ? from.hashCode() : 0);
return result;
}
}

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.util;
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 o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CachingDoubleValueSource that = (CachingDoubleValueSource) o;
if (source != null ? !source.equals(that.source) : that.source != null) return false;
return true;
}
@Override
public int hashCode() {
return source != null ? source.hashCode() : 0;
}
}

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.util;
import org.apache.lucene.document.DoubleField;
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

@ -1,4 +1,4 @@
/**
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
@ -15,38 +15,32 @@
* limitations under the License.
*/
package org.apache.lucene.spatial.tier;
package org.apache.lucene.spatial.util;
import com.spatial4j.core.shape.Shape;
import java.util.ArrayList;
import java.util.List;
/**
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public class Shape {
public class ShapeFieldCache<T extends Shape> {
private List<T>[] cache;
public int defaultLength;
private List<Double> area = new ArrayList<Double>();
private String tierId;
public Shape (String tierId){
this.tierId = tierId;
@SuppressWarnings({"unchecked"})
public ShapeFieldCache( int length, int defaultLength ) {
cache = new List[length];
this.defaultLength= defaultLength;
}
public void addBox(double boxId){
area.add(boxId);
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<Double> getArea(){
return area;
}
public String getTierId(){
return tierId;
}
public boolean isInside(double boxId){
return area.contains(boxId);
public List<T> getShapes( int docid ) {
return cache[docid];
}
}

View File

@ -0,0 +1,80 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.util;
import com.spatial4j.core.shape.Shape;
import org.apache.lucene.index.*;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.util.BytesRef;
import java.io.IOException;
import java.util.WeakHashMap;
import java.util.logging.Logger;
public abstract class ShapeFieldCacheProvider<T extends Shape> {
private Logger log = Logger.getLogger(getClass().getName());
// 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.fine("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.fine("Cached: [" + count + " in " + elapsed + "ms] " + idx);
return idx;
}
}

View File

@ -0,0 +1,56 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.util;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import java.io.IOException;
import java.util.Iterator;
/**
* 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

@ -1,4 +1,4 @@
/**
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
@ -15,43 +15,35 @@
* limitations under the License.
*/
package org.apache.lucene.spatial.geometry.shape;
package org.apache.lucene.spatial.util;
/**
* Common set of operations available on 2d shapes.
*
* <p><font color="red"><b>NOTE:</b> This API is still in
* flux and might change in incompatible ways in the next
* release.</font>
*/
public interface Geometry2D {
/**
* Translate according to the vector
* @param v
*/
public void translate(Vector2D v);
/**
* Does the shape contain the given point
* @param p
*/
public boolean contains(Point2D p);
/**
* Return the area
*/
public double area();
/**
* Return the centroid
*/
public Point2D centroid();
/**
* Returns information about how this shape intersects the given rectangle
* @param r
*/
public IntersectCase intersect(Rectangle r);
import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import java.io.IOException;
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.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,126 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.vector;
import com.spatial4j.core.distance.DistanceCalculator;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.simple.PointImpl;
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.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 o the ValueSource to compare
* @return <code>true</code> if the two objects are based upon the same query envelope
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DistanceValueSource that = (DistanceValueSource) o;
if (calculator != null ? !calculator.equals(that.calculator) : that.calculator != null) return false;
if (fields != null ? !fields.equals(that.fields) : that.fields != null) return false;
if (from != null ? !from.equals(that.from) : that.from != null) return false;
return true;
}
@Override
public int hashCode() {
int result = fields != null ? fields.hashCode() : 0;
result = 31 * result + (calculator != null ? calculator.hashCode() : 0);
result = 31 * result + (from != null ? from.hashCode() : 0);
return result;
}
}

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.vector;
import org.apache.lucene.spatial.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,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.vector;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.exception.UnsupportedSpatialOperation;
import com.spatial4j.core.query.SpatialArgs;
import com.spatial4j.core.query.SpatialOperation;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import 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.SpatialStrategy;
import org.apache.lucene.spatial.util.CachingDoubleValueSource;
import org.apache.lucene.spatial.util.NumericFieldInfo;
import org.apache.lucene.spatial.util.ValueSourceFilter;
public class TwoDoublesStrategy extends SpatialStrategy<TwoDoublesFieldInfo> {
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 shape (so far), 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());
}
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;
}
/**
* 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

@ -1,22 +1,23 @@
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<body>
Support for filtering based upon geographic location.
</body>
</html>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<head>
<title>Apache Lucene Spatial Strategies</title>
</head>
<body>
</body>
</html>

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)

View File

@ -0,0 +1,4 @@
KS @ IsWithin(-103.493164 36.208984 -93.825195 41.086914)
WA @ IsWithin(-126.916016 44.36084 -115.314453 50.688965)
MA CT RI @ IsWithin(-73.894043 40.825195 -69.521484 43.198242)
AL GA @ IsWithin(-89.472656 29.311523 -80.244141 35.90332)

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 com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.io.LineReader;
import com.spatial4j.core.query.SpatialArgs;
import com.spatial4j.core.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,147 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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 com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.io.sample.SampleData;
import com.spatial4j.core.io.sample.SampleDataReader;
import com.spatial4j.core.query.SpatialArgsParser;
import com.spatial4j.core.shape.Shape;
import org.junit.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 java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.logging.Logger;
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";
private Logger log = Logger.getLogger(getClass().getName());
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 com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.simple.SimpleSpatialContext;
import com.spatial4j.core.query.SpatialArgsParser;
import com.spatial4j.core.query.SpatialOperation;
import com.spatial4j.core.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,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.prefix;
import com.spatial4j.core.context.SpatialContext;
import org.apache.lucene.spatial.SimpleSpatialFieldInfo;
import org.apache.lucene.spatial.SpatialMatchConcern;
import org.apache.lucene.spatial.StrategyTestCase;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
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.prefix;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.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.prefix;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
/**
* 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.prefix;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.simple.SimpleSpatialContext;
import com.spatial4j.core.query.SpatialArgsParser;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.simple.PointImpl;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.spatial.SimpleSpatialFieldInfo;
import org.apache.lucene.spatial.SpatialTestCase;
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
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,60 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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.prefix.tree;
import com.spatial4j.core.context.simple.SimpleSpatialContext;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
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,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.vector;
import com.spatial4j.core.context.SpatialContext;
import org.apache.lucene.search.FieldCache;
import org.apache.lucene.spatial.SpatialMatchConcern;
import org.apache.lucene.spatial.StrategyTestCase;
import org.apache.lucene.spatial.util.NumericFieldInfo;
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.vector;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.simple.SimpleSpatialContext;
public class TwoDoublesStrategyTestCase extends BaseTwoDoublesStrategyTestCase {
@Override
protected SpatialContext getSpatialContext() {
return SimpleSpatialContext.GEO_KM;
}
}

View File

@ -22,9 +22,12 @@ import org.apache.lucene.queries.function.valuesource.LiteralValueSource;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.SortField;
import org.apache.lucene.spatial.geohash.GeoHashUtils;
import org.apache.lucene.spatial.DistanceUtils;
import org.apache.lucene.spatial.tier.InvalidGeoException;
import com.spatial4j.core.context.ParseUtils;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.simple.SimpleSpatialContext;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.util.GeohashUtils;
import com.spatial4j.core.shape.Point;
import org.apache.solr.common.SolrException;
import org.apache.solr.response.TextResponseWriter;
import org.apache.solr.search.QParser;
@ -41,11 +44,13 @@ import java.io.IOException;
* href="http://en.wikipedia.org/wiki/Geohash">Geohash</a> field. The field is
* provided as a lat/lon pair and is internally represented as a string.
*
* @see org.apache.lucene.spatial.DistanceUtils#parseLatitudeLongitude(double[], String)
* @see com.spatial4j.core.context.ParseUtils#parseLatitudeLongitude(double[], String)
*/
public class GeoHashField extends FieldType implements SpatialQueryable {
private final SpatialContext ctx = SimpleSpatialContext.GEO_KM;
@Override
public SortField getSortField(SchemaField field, boolean top) {
return getStringSort(field, top);
@ -57,11 +62,11 @@ public class GeoHashField extends FieldType implements SpatialQueryable {
public Query createSpatialQuery(QParser parser, SpatialOptions options) {
double [] point = new double[0];
try {
point = DistanceUtils.parsePointDouble(null, options.pointStr, 2);
} catch (InvalidGeoException e) {
point = ParseUtils.parsePointDouble(null, options.pointStr, 2);
} catch (InvalidShapeException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
}
String geohash = GeoHashUtils.encode(point[0], point[1]);
String geohash = GeohashUtils.encodeLatLon(point[0], point[1]);
//TODO: optimize this
return new SolrConstantScoreQuery(new ValueSourceRangeFilter(new GeohashHaversineFunction(getValueSource(options.field, parser),
new LiteralValueSource(geohash), options.radius), "0", String.valueOf(options.distance), true, true));
@ -76,8 +81,8 @@ public class GeoHashField extends FieldType implements SpatialQueryable {
@Override
public String toExternal(IndexableField f) {
double[] latLon = GeoHashUtils.decode(f.stringValue());
return latLon[0] + "," + latLon[1];
Point p = GeohashUtils.decode(f.stringValue(),ctx);
return p.getY() + "," + p.getX();
}
@ -87,11 +92,11 @@ public class GeoHashField extends FieldType implements SpatialQueryable {
// latitude, longitude
double[] latLon = new double[0];
try {
latLon = DistanceUtils.parseLatitudeLongitude(null, val);
} catch (InvalidGeoException e) {
latLon = ParseUtils.parseLatitudeLongitude(null, val);
} catch (InvalidShapeException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
}
return GeoHashUtils.encode(latLon[0], latLon[1]);
return GeohashUtils.encodeLatLon(latLon[0], latLon[1]);
}

Some files were not shown because too many files have changed in this diff Show More