Return-Path: Delivered-To: apmail-lucene-commits-archive@www.apache.org Received: (qmail 52477 invoked from network); 10 Jul 2010 00:13:48 -0000 Received: from unknown (HELO mail.apache.org) (140.211.11.3) by 140.211.11.9 with SMTP; 10 Jul 2010 00:13:48 -0000 Received: (qmail 42070 invoked by uid 500); 10 Jul 2010 00:13:47 -0000 Mailing-List: contact commits-help@lucene.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@lucene.apache.org Delivered-To: mailing list commits@lucene.apache.org Received: (qmail 42063 invoked by uid 99); 10 Jul 2010 00:13:47 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 10 Jul 2010 00:13:47 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 10 Jul 2010 00:13:38 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 7D0DE23888A6; Sat, 10 Jul 2010 00:12:43 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: svn commit: r962727 [1/2] - in /lucene/dev/trunk: lucene/contrib/spatial/src/java/org/apache/lucene/spatial/geohash/ lucene/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/ lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/ luc... Date: Sat, 10 Jul 2010 00:12:42 -0000 To: commits@lucene.apache.org From: gsingers@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20100710001243.7D0DE23888A6@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: gsingers Date: Sat Jul 10 00:12:41 2010 New Revision: 962727 URL: http://svn.apache.org/viewvc?rev=962727&view=rev Log: SOLR-1568: Spatial filtering support Added: lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/DistanceUtilsTest.java (with props) lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/projections/ lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/projections/SinusoidalProjectorTest.java (with props) lucene/dev/trunk/solr/src/common/org/apache/solr/common/params/SpatialParams.java (with props) lucene/dev/trunk/solr/src/java/org/apache/solr/schema/LatLonType.java (with props) lucene/dev/trunk/solr/src/java/org/apache/solr/schema/SpatialQueryable.java (with props) lucene/dev/trunk/solr/src/java/org/apache/solr/search/SpatialFilterQParser.java (with props) lucene/dev/trunk/solr/src/java/org/apache/solr/search/SpatialFilterQParserPlugin.java (with props) lucene/dev/trunk/solr/src/java/org/apache/solr/search/SpatialOptions.java (with props) lucene/dev/trunk/solr/src/test/org/apache/solr/search/SpatialFilterTest.java (with props) Modified: lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashDistanceFilter.java lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceHandler.java lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceUtils.java lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/LatLongDistanceFilter.java lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/SinusoidalProjector.java lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/DistanceCheck.java lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesian.java lucene/dev/trunk/solr/CHANGES.txt lucene/dev/trunk/solr/example/solr/conf/schema.xml lucene/dev/trunk/solr/src/java/org/apache/solr/core/SolrResourceLoader.java lucene/dev/trunk/solr/src/java/org/apache/solr/schema/GeoHashField.java lucene/dev/trunk/solr/src/java/org/apache/solr/schema/PointType.java lucene/dev/trunk/solr/src/java/org/apache/solr/schema/SpatialTileField.java lucene/dev/trunk/solr/src/java/org/apache/solr/search/QParserPlugin.java lucene/dev/trunk/solr/src/java/org/apache/solr/search/ValueSourceParser.java lucene/dev/trunk/solr/src/java/org/apache/solr/search/function/distance/Constants.java lucene/dev/trunk/solr/src/java/org/apache/solr/search/function/distance/DistanceUtils.java lucene/dev/trunk/solr/src/java/org/apache/solr/search/function/distance/GeohashHaversineFunction.java lucene/dev/trunk/solr/src/java/org/apache/solr/search/function/distance/HaversineFunction.java lucene/dev/trunk/solr/src/java/org/apache/solr/search/function/distance/SquaredEuclideanFunction.java lucene/dev/trunk/solr/src/java/org/apache/solr/search/function/distance/VectorDistanceFunction.java lucene/dev/trunk/solr/src/test/org/apache/solr/schema/PolyFieldTest.java lucene/dev/trunk/solr/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java lucene/dev/trunk/solr/src/test/test-files/solr/conf/schema.xml Modified: lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashDistanceFilter.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashDistanceFilter.java?rev=962727&r1=962726&r2=962727&view=diff ============================================================================== --- lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashDistanceFilter.java (original) +++ lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashDistanceFilter.java Sat Jul 10 00:12:41 2010 @@ -90,7 +90,7 @@ public class GeoHashDistanceFilter exten if (cachedDistance != null) { d = cachedDistance.doubleValue(); } else { - d = DistanceUtils.getInstance().getDistanceMi(lat, lng, x, y); + d = DistanceUtils.getDistanceMi(lat, lng, x, y); distanceLookupCache.put(geoHash, d); } Modified: lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java?rev=962727&r1=962726&r2=962727&view=diff ============================================================================== --- lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java (original) +++ lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/geometry/DistanceUnits.java Sat Jul 10 00:12:41 2010 @@ -55,11 +55,11 @@ public enum DistanceUnits { * @throws IllegalArgumentException if no DistanceUnit which represents the given unit is found */ public static DistanceUnits findDistanceUnit(String unit) { - if (MILES.getUnit().equals(unit)) { + if (MILES.getUnit().equalsIgnoreCase(unit) || unit.equalsIgnoreCase("mi")) { return MILES; } - if (KILOMETERS.getUnit().equals(unit)) { + if (KILOMETERS.getUnit().equalsIgnoreCase(unit)) { return KILOMETERS; } Modified: lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceHandler.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceHandler.java?rev=962727&r1=962726&r2=962727&view=diff ============================================================================== --- lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceHandler.java (original) +++ lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceHandler.java Sat Jul 10 00:12:41 2010 @@ -68,7 +68,7 @@ public class DistanceHandler { // check to see if we have distances // if not calculate the distance if(distances == null){ - return DistanceUtils.getInstance().getDistanceMi(centerLat, centerLng, lat, lng); + return DistanceUtils.getDistanceMi(centerLat, centerLng, lat, lng); } // check to see if the doc id has a cached distance @@ -93,7 +93,7 @@ public class DistanceHandler { } //all else fails calculate the distances - return DistanceUtils.getInstance().getDistanceMi(centerLat, centerLng, lat, lng); + return DistanceUtils.getDistanceMi(centerLat, centerLng, lat, lng); } Modified: lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceUtils.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceUtils.java?rev=962727&r1=962726&r2=962727&view=diff ============================================================================== --- lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceUtils.java (original) +++ lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/DistanceUtils.java Sat Jul 10 00:12:41 2010 @@ -28,17 +28,32 @@ import org.apache.lucene.spatial.geometr * flux and might change in incompatible ways in the next * release. */ +//TODO: Move this up one package level public class DistanceUtils { - static DistanceUtils instance = new DistanceUtils(); - - public static DistanceUtils getInstance() - { - return instance; - } + public static final double DEGREES_TO_RADIANS = Math.PI / 180.0; + public static final double RADIANS_TO_DEGREES = 180.0 / Math.PI; + public static final double DEG_45 = Math.PI / 4.0; + public static final double DEG_225 = 5 * DEG_45; + public static final double DEG_90 = Math.PI / 2; + public static final double DEG_180 = Math.PI; + public static final double SIN_45 = Math.sin(DEG_45); + 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 double getDistanceMi(double x1, double y1, double x2, double y2) { + 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); } @@ -49,7 +64,7 @@ public class DistanceUtils { * @param miles * @return boundary rectangle where getY/getX is top left, getMinY/getMinX is bottom right */ - public Rectangle getBoundary (double x1, double y1, double miles) { + public static Rectangle getBoundary (double x1, double y1, double miles) { LLRect box = LLRect.createBox( new FloatLatLng( x1, y1 ), miles, miles ); @@ -58,10 +73,362 @@ public class DistanceUtils { } - public double getLLMDistance (double x1, double y1, double x2, double y2) { + 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. + *

+ * 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 * 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 radius 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 radius) { + result = latLonCorner(latCenter * DEGREES_TO_RADIANS, + lonCenter * DEGREES_TO_RADIANS, distance, result, upperRight, radius); + result[0] = result[0] * RADIANS_TO_DEGREES; + result[1] = result[1] * RADIANS_TO_DEGREES; + return result; + } + + /** + * Uses Haversine to calculate the corner + * + * @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 radius 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 radius) { + // Haversine formula + double brng = upperRight ? DEG_45 : DEG_225; + double lat2 = Math.asin(Math.sin(latCenter) * Math.cos(distance / radius) + + Math.cos(latCenter) * Math.sin(distance / radius) * Math.cos(brng)); + double lon2 = lonCenter + Math.atan2(Math.sin(brng) * Math.sin(distance / radius) * Math.cos(latCenter), + Math.cos(distance / radius) - Math.sin(latCenter) * 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) { + latLng[0] = DEG_90 - (latLng[0] - DEG_90); + if (latLng[1] < 0) { + latLng[1] = latLng[1] + DEG_180; + } else { + latLng[1] = latLng[1] - DEG_180; + } + } else if (latLng[0] < -DEG_90) { + latLng[0] = -DEG_90 - (latLng[0] + DEG_90); + if (latLng[1] < 0) { + latLng[1] = latLng[1] + DEG_180; + } else { + latLng[1] = latLng[1] - DEG_180; + } + } + + } + + /** + * 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) { + latLng[1] = -1.0 * (DEG_180 - (latLng[1] - DEG_180)); + } else if (latLng[1] < -DEG_180) { + latLng[1] = (latLng[1] + DEG_180) + DEG_180; + } + } + + /** + * 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; + } + + /** + * @param x1 The x coordinate of the first point, in radians + * @param y1 The y coordinate of the first point, in radians + * @param x2 The x coordinate of the second point, in radians + * @param y2 The y 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 x1, double y1, double x2, double y2, 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 = hsinX * hsinX + + (Math.cos(x1) * Math.cos(x2) * hsinY * hsinY); + result = (radius * 2 * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h))); + } + return result; + } + + /** + * Given a string containing dimension values encoded in it, separated by commas, return a String array of length dimension + * containing the values. + * + * @param out A preallocated array. Must be size dimension. If it is not it will be resized. + * @param externalVal The value to parse + * @param dimension The expected number of values for the point + * @return An array of the values that make up the point (aka vector) + * @throws 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 dimension values encoded in it, separated by commas, return a double array of length dimension + * containing the values. + * + * @param out A preallocated array. Must be size dimension. If it is not it will be resized. + * @param externalVal The value to parse + * @param dimension The expected number of values for the point + * @return An array of the values that make up the point (aka vector) + * @throws 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. + *

+ * 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; + } } Modified: lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/LatLongDistanceFilter.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/LatLongDistanceFilter.java?rev=962727&r1=962726&r2=962727&view=diff ============================================================================== --- lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/LatLongDistanceFilter.java (original) +++ lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/LatLongDistanceFilter.java Sat Jul 10 00:12:41 2010 @@ -88,7 +88,7 @@ public class LatLongDistanceFilter exten if (cachedDistance != null){ d = cachedDistance.doubleValue(); } else { - d = DistanceUtils.getInstance().getDistanceMi(lat, lng, x, y); + d = DistanceUtils.getDistanceMi(lat, lng, x, y); distanceLookupCache.put(ck, d); } Modified: lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/SinusoidalProjector.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/SinusoidalProjector.java?rev=962727&r1=962726&r2=962727&view=diff ============================================================================== --- lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/SinusoidalProjector.java (original) +++ lucene/dev/trunk/lucene/contrib/spatial/src/java/org/apache/lucene/spatial/tier/projections/SinusoidalProjector.java Sat Jul 10 00:12:41 2010 @@ -17,21 +17,28 @@ package org.apache.lucene.spatial.tier.projections; +import org.apache.lucene.spatial.tier.DistanceUtils; + /** * Based on Sinusoidal Projections * Project a latitude / longitude on a 2D cartesian map + *

+ * 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. * *

NOTE: This API is still in * flux and might change in incompatible ways in the next * release. + * + * @deprecated Until we can put in place proper tests and a proper fix. */ 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); @@ -42,3 +49,39 @@ public class SinusoidalProjector impleme } } + +/* +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 + * + *

NOTE: This API is still in + * flux and might change in incompatible ways in the next + * release. + */ +/* +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}; + + } + +} +*/ + + + Added: lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/DistanceUtilsTest.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/DistanceUtilsTest.java?rev=962727&view=auto ============================================================================== --- lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/DistanceUtilsTest.java (added) +++ lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/DistanceUtilsTest.java Sat Jul 10 00:12:41 2010 @@ -0,0 +1,234 @@ +package org.apache.lucene.spatial; + +import junit.framework.TestCase; +import org.apache.lucene.spatial.tier.DistanceUtils; +import org.apache.lucene.spatial.tier.InvalidGeoException; + + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 TestCase { + + 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]); + assertEquals(1.0, result[1]); + + result = DistanceUtils.vectorBoxCorner(zero, null, Math.sqrt(2), false); + assertEquals(-1.0, result[0]); + assertEquals(-1.0, result[1]); + + result = DistanceUtils.vectorBoxCorner(oneOne, null, Math.sqrt(2), true); + assertEquals(2.0, result[0]); + assertEquals(2.0, result[1]); + + result = DistanceUtils.vectorBoxCorner(zeroOne, null, Math.sqrt(2), true); + assertEquals(1.0, result[0]); + assertEquals(2.0, result[1]); + + result = DistanceUtils.vectorBoxCorner(pt1, null, Math.sqrt(2), true); + assertEquals(2.5, result[0]); + assertEquals(111.3, result[1]); + + result = DistanceUtils.vectorBoxCorner(pt1, null, Math.sqrt(2), false); + assertEquals(0.5, result[0]); + assertEquals(109.3, result[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}; + double[] result; + // 00°38′09″N, 000°38′09″E + //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°38′09″N, 000°38′09″E + 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°38′09″N, 000°38′09″E + assertEquals(-0.63583, result[0], 0.001); + assertEquals(-0.63583, result[1], 0.001); + + //test some edge cases + //89°16′02″N, 060°12′35″E + 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 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); + distance = DistanceUtils.vectorDistance(zero, oneZero, 2); + assertEquals(1.0, distance); + 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 = org.apache.lucene.spatial.tier.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]); + assertEquals(73.2, dbls[1]); + assertEquals(-92.3, dbls[2]); + + 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]); + assertEquals(73.2, dbls[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) { + + } + + } + +} Propchange: lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/DistanceUtilsTest.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/DistanceCheck.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/DistanceCheck.java?rev=962727&r1=962726&r2=962727&view=diff ============================================================================== --- lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/DistanceCheck.java (original) +++ lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/DistanceCheck.java Sat Jul 10 00:12:41 2010 @@ -31,7 +31,7 @@ public class DistanceCheck { double long2 = 0; for (int i =0; i < 90; i++){ - double dis = DistanceUtils.getInstance().getDistanceMi(lat1, long1, lat2, long2); + double dis = DistanceUtils.getDistanceMi(lat1, long1, lat2, long2); lat1 +=1; lat2 = lat1 + 0.001; Modified: lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesian.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesian.java?rev=962727&r1=962726&r2=962727&view=diff ============================================================================== --- lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesian.java (original) +++ lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesian.java Sat Jul 10 00:12:41 2010 @@ -278,8 +278,8 @@ public class TestCartesian extends Lucen double rsLng = Double.parseDouble(d.get(lngField)); Double geo_distance = distances.get(scoreDocs[i].doc); - double distance = DistanceUtils.getInstance().getDistanceMi(lat, lng, rsLat, rsLng); - double llm = DistanceUtils.getInstance().getLLMDistance(lat, lng, rsLat, rsLng); + 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 )); @@ -372,8 +372,8 @@ public class TestCartesian extends Lucen double rsLng = Double.parseDouble(d.get(lngField)); Double geo_distance = distances.get(scoreDocs[i].doc); - double distance = DistanceUtils.getInstance().getDistanceMi(lat, lng, rsLat, rsLng); - double llm = DistanceUtils.getInstance().getLLMDistance(lat, lng, rsLat, rsLng); + 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); @@ -467,8 +467,8 @@ public class TestCartesian extends Lucen double rsLng = Double.parseDouble(d.get(lngField)); Double geo_distance = distances.get(scoreDocs[i].doc); - double distance = DistanceUtils.getInstance().getDistanceMi(lat, lng, rsLat, rsLng); - double llm = DistanceUtils.getInstance().getLLMDistance(lat, lng, rsLat, rsLng); + 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 )); @@ -561,8 +561,8 @@ public class TestCartesian extends Lucen double rsLng = Double.parseDouble(d.get(lngField)); Double geo_distance = distances.get(scoreDocs[i].doc); - double distance = DistanceUtils.getInstance().getDistanceMi(lat, lng, rsLat, rsLng); - double llm = DistanceUtils.getInstance().getLLMDistance(lat, lng, rsLat, rsLng); + 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 )); Added: lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/projections/SinusoidalProjectorTest.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/projections/SinusoidalProjectorTest.java?rev=962727&view=auto ============================================================================== --- lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/projections/SinusoidalProjectorTest.java (added) +++ lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/projections/SinusoidalProjectorTest.java Sat Jul 10 00:12:41 2010 @@ -0,0 +1,67 @@ +package org.apache.lucene.spatial.tier.projections; + +import org.apache.lucene.spatial.tier.DistanceUtils; +import org.junit.Test; + +import static junit.framework.Assert.*; + + +/** + * + * + **/ +public class SinusoidalProjectorTest { + + @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] + "]"); + } + + } + */ \ No newline at end of file Propchange: lucene/dev/trunk/lucene/contrib/spatial/src/test/org/apache/lucene/spatial/tier/projections/SinusoidalProjectorTest.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: lucene/dev/trunk/solr/CHANGES.txt URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/CHANGES.txt?rev=962727&r1=962726&r2=962727&view=diff ============================================================================== --- lucene/dev/trunk/solr/CHANGES.txt (original) +++ lucene/dev/trunk/solr/CHANGES.txt Sat Jul 10 00:12:41 2010 @@ -122,7 +122,7 @@ New Features * SOLR-1131: FieldTypes can now output multiple Fields per Type and still be searched. This can be handy for hiding the details of a particular implementation such as in the spatial case. (Chris Mattmann, shalin, noble, gsingers, yonik) -* SOLR-1586: Add support for Geohash and Spatial Tile FieldType (Chris Mattmann, gsingers) +* SOLR-1586: Add support for Geohash FieldType (Chris Mattmann, gsingers) * SOLR-1697: PluginInfo should load plugins w/o class attribute also (noble) @@ -198,6 +198,10 @@ New Features * SOLR-1985: FastVectorHighlighter: add wrapper class for Lucene's SingleFragListBuilder (koji) * SOLR-1984: Add HyphenationCompoundWordTokenFilterFactory. (PB via rmuir) + +* SOLR-1568: Added "native" filtering support for PointType, GeohashField. Added LatLonType with filtering support too. See + http://wiki.apache.org/solr/SpatialSearch and the example. Refactored some items in Lucene spatial. + Removed SpatialTileField as the underlying CartesianTier is broken beyond repair and is going to be moved. (gsingers) Optimizations ---------------------- Modified: lucene/dev/trunk/solr/example/solr/conf/schema.xml URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/example/solr/conf/schema.xml?rev=962727&r1=962726&r2=962727&view=diff ============================================================================== --- lucene/dev/trunk/solr/example/solr/conf/schema.xml (original) +++ lucene/dev/trunk/solr/example/solr/conf/schema.xml Sat Jul 10 00:12:41 2010 @@ -416,15 +416,11 @@ --> + - + @@ -467,9 +463,14 @@ + + - + - + @@ -525,7 +526,7 @@ - + @@ -569,7 +570,7 @@ - + Added: lucene/dev/trunk/solr/src/common/org/apache/solr/common/params/SpatialParams.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/common/org/apache/solr/common/params/SpatialParams.java?rev=962727&view=auto ============================================================================== --- lucene/dev/trunk/solr/src/common/org/apache/solr/common/params/SpatialParams.java (added) +++ lucene/dev/trunk/solr/src/common/org/apache/solr/common/params/SpatialParams.java Sat Jul 10 00:12:41 2010 @@ -0,0 +1,40 @@ +package org.apache.solr.common.params; +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 interface SpatialParams { + public static final String POINT = "pt"; + public static final String DISTANCE = "d"; + /** + * km - kilometers + * mi - miles + */ + public static final String UNITS = "units"; + /** + * The distance measure to use. + */ + public static final String MEASURE = "meas"; + /** + * The radius of the sphere to use to in calculating spherical distances like Haversine + */ + public static final String SPHERE_RADIUS = "sphere_radius"; +} Propchange: lucene/dev/trunk/solr/src/common/org/apache/solr/common/params/SpatialParams.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: lucene/dev/trunk/solr/src/java/org/apache/solr/core/SolrResourceLoader.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/java/org/apache/solr/core/SolrResourceLoader.java?rev=962727&r1=962726&r2=962727&view=diff ============================================================================== --- lucene/dev/trunk/solr/src/java/org/apache/solr/core/SolrResourceLoader.java (original) +++ lucene/dev/trunk/solr/src/java/org/apache/solr/core/SolrResourceLoader.java Sat Jul 10 00:12:41 2010 @@ -53,6 +53,7 @@ import org.apache.solr.schema.FieldType; import org.apache.solr.update.processor.UpdateRequestProcessorFactory; import org.apache.solr.util.plugin.ResourceLoaderAware; import org.apache.solr.util.plugin.SolrCoreAware; +import org.apache.solr.search.QParserPlugin; /** * @since solr 1.3 @@ -645,6 +646,7 @@ public class SolrResourceLoader implemen CharFilterFactory.class, TokenFilterFactory.class, TokenizerFactory.class, + QParserPlugin.class, FieldType.class } ); Modified: lucene/dev/trunk/solr/src/java/org/apache/solr/schema/GeoHashField.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/java/org/apache/solr/schema/GeoHashField.java?rev=962727&r1=962726&r2=962727&view=diff ============================================================================== --- lucene/dev/trunk/solr/src/java/org/apache/solr/schema/GeoHashField.java (original) +++ lucene/dev/trunk/solr/src/java/org/apache/solr/schema/GeoHashField.java Sat Jul 10 00:12:41 2010 @@ -18,13 +18,22 @@ package org.apache.solr.schema; import org.apache.lucene.document.Fieldable; +import org.apache.lucene.search.Query; import org.apache.lucene.search.SortField; import org.apache.lucene.spatial.geohash.GeoHashUtils; +import org.apache.lucene.spatial.tier.DistanceUtils; +import org.apache.lucene.spatial.tier.InvalidGeoException; +import org.apache.solr.common.SolrException; import org.apache.solr.response.TextResponseWriter; import org.apache.solr.response.XMLWriter; import org.apache.solr.search.QParser; +import org.apache.solr.search.SolrConstantScoreQuery; +import org.apache.solr.search.SpatialOptions; +import org.apache.solr.search.function.LiteralValueSource; import org.apache.solr.search.function.ValueSource; -import org.apache.solr.search.function.distance.DistanceUtils; +import org.apache.solr.search.function.ValueSourceRangeFilter; +import org.apache.solr.search.function.distance.GeohashHaversineFunction; + import java.io.IOException; @@ -33,9 +42,9 @@ import java.io.IOException; * href="http://en.wikipedia.org/wiki/Geohash">Geohash field. The field is * provided as a lat/lon pair and is internally represented as a string. * - * @see org.apache.solr.search.function.distance.DistanceUtils#parseLatitudeLongitude(double[], String) + * @see org.apache.lucene.spatial.tier.DistanceUtils#parseLatitudeLongitude(double[], String) */ -public class GeoHashField extends FieldType { +public class GeoHashField extends FieldType implements SpatialQueryable { @Override @@ -43,6 +52,22 @@ public class GeoHashField extends FieldT return getStringSort(field, top); } + //QUESTION: Should we do a fast and crude one? Or actually check distances + //Fast and crude could use EdgeNGrams, but that would require a different + //encoding. Plus there are issues around the Equator/Prime Meridian + public Query createSpatialQuery(QParser parser, SpatialOptions options) { + double [] point = new double[0]; + try { + point = DistanceUtils.parsePointDouble(null, options.pointStr, 2); + } catch (InvalidGeoException e) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e); + } + String geohash = GeoHashUtils.encode(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)); + } + @Override public void write(XMLWriter xmlWriter, String name, Fieldable f) throws IOException { @@ -67,7 +92,12 @@ public class GeoHashField extends FieldT public String toInternal(String val) { // validate that the string is of the form // latitude, longitude - double[] latLon = DistanceUtils.parseLatitudeLongitude(null, val); + double[] latLon = new double[0]; + try { + latLon = DistanceUtils.parseLatitudeLongitude(null, val); + } catch (InvalidGeoException e) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e); + } return GeoHashUtils.encode(latLon[0], latLon[1]); } Added: lucene/dev/trunk/solr/src/java/org/apache/solr/schema/LatLonType.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/java/org/apache/solr/schema/LatLonType.java?rev=962727&view=auto ============================================================================== --- lucene/dev/trunk/solr/src/java/org/apache/solr/schema/LatLonType.java (added) +++ lucene/dev/trunk/solr/src/java/org/apache/solr/schema/LatLonType.java Sat Jul 10 00:12:41 2010 @@ -0,0 +1,306 @@ +package org.apache.solr.schema; +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.document.Field; +import org.apache.lucene.document.Fieldable; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.SortField; +import org.apache.lucene.spatial.tier.DistanceUtils; +import org.apache.lucene.spatial.tier.InvalidGeoException; +import org.apache.lucene.spatial.tier.projections.CartesianTierPlotter; +import org.apache.solr.common.SolrException; +import org.apache.solr.response.TextResponseWriter; +import org.apache.solr.response.XMLWriter; +import org.apache.solr.search.QParser; +import org.apache.solr.search.SpatialOptions; +import org.apache.solr.search.function.ValueSource; +import org.apache.solr.search.function.VectorValueSource; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + + +/** + * Represents a Latitude/Longitude as a 2 dimensional point. Latitude is always specified first. + * Can also, optionally, integrate in Spatial Tile capabilities. The default is for tile fields from 4 - 15, + * just as in the SpatialTileField that we are extending. + */ +public class LatLonType extends AbstractSubTypeFieldType implements SpatialQueryable { + protected static final int LAT = 0; + protected static final int LONG = 1; + + @Override + protected void init(IndexSchema schema, Map args) { + super.init(schema, args); + //TODO: refactor this, as we are creating the suffix cache twice, since the super.init does it too + createSuffixCache(3);//we need three extra fields: one for the storage field, two for the lat/lon + } + + @Override + public Fieldable[] createFields(SchemaField field, String externalVal, float boost) { + //we could have tileDiff + 3 fields (two for the lat/lon, one for storage) + Fieldable[] f = new Fieldable[(field.indexed() ? 2 : 0) + (field.stored() ? 1 : 0)]; + if (field.indexed()) { + int i = 0; + double[] latLon = new double[0]; + try { + latLon = DistanceUtils.parseLatitudeLongitude(null, externalVal); + } catch (InvalidGeoException e) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e); + } + //latitude + f[i] = subField(field, i).createField(String.valueOf(latLon[LAT]), boost); + i++; + //longitude + f[i] = subField(field, i).createField(String.valueOf(latLon[LONG]), boost); + + } + + if (field.stored()) { + f[f.length - 1] = createField(field.getName(), externalVal, + getFieldStore(field, externalVal), Field.Index.NO, Field.TermVector.NO, + false, false, boost); + } + return f; + } + + @Override + public Query createSpatialQuery(QParser parser, SpatialOptions options) { + BooleanQuery result = new BooleanQuery(); + double[] point = new double[0]; + try { + point = DistanceUtils.parseLatitudeLongitude(options.pointStr); + } catch (InvalidGeoException e) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e); + } + + //Get the distance + double[] ur; + double[] ll; + if (options.measStr == null || options.measStr.equals("hsin")) { + ur = DistanceUtils.latLonCornerDegs(point[LAT], point[LONG], options.distance, null, true, options.radius); + ll = DistanceUtils.latLonCornerDegs(point[LAT], point[LONG], options.distance, null, false, options.radius); + } else { + ur = DistanceUtils.vectorBoxCorner(point, null, options.distance, true); + ll = DistanceUtils.vectorBoxCorner(point, null, options.distance, false); + } + + SchemaField subSF; + Query range; + + double angDistDegs = DistanceUtils.angularDistance(options.distance, + DistanceUtils.EARTH_MEAN_RADIUS_MI) * DistanceUtils.RADIANS_TO_DEGREES; + //for the poles, do something slightly different + if (point[LAT] + angDistDegs > 90.0) { //we cross the north pole + //we don't need a longitude boundary at all + + double minLat = Math.min(ll[LAT], ur[LAT]); + subSF = subField(options.field, LAT); + range = subSF.getType().getRangeQuery(parser, subSF, + String.valueOf(minLat), + "90", true, true); + result.add(range, BooleanClause.Occur.MUST); + } else if (point[LAT] - angDistDegs < -90.0) {//we cross the south pole + subSF = subField(options.field, LAT); + double maxLat = Math.max(ll[LAT], ur[LAT]); + range = subSF.getType().getRangeQuery(parser, subSF, + "-90", String.valueOf(maxLat), true, true); + result.add(range, BooleanClause.Occur.MUST); + } else{ + //Latitude + //we may need to generate multiple queries depending on the range + //Are we crossing the 180 deg. longitude, if so, we need to do some special things + if (ll[LONG] > 0.0 && ur[LONG] < 0.0) { + //TODO: refactor into common code, etc. + //Now check other side of the Equator + if (ll[LAT] < 0.0 && ur[LAT] > 0.0) { + addEquatorialBoundary(parser, options, result, ur[LAT], ll[LAT]); + } //check poles + else { + subSF = subField(options.field, LAT); + //not crossing the equator + range = subSF.getType().getRangeQuery(parser, subSF, + String.valueOf(ll[LAT]), + String.valueOf(ur[LAT]), true, true); + result.add(range, BooleanClause.Occur.MUST); + } + //Longitude + addMeridianBoundary(parser, options, result, ur[LONG], ll[LONG], "180.0", "-180.0"); + + } else if (ll[LONG] < 0.0 && ur[LONG] > 0.0) {//prime meridian (0 degrees + //Now check other side of the Equator + if (ll[LAT] < 0.0 && ur[LAT] > 0.0) { + addEquatorialBoundary(parser, options, result, ur[LAT], ll[LAT]); + } else { + subSF = subField(options.field, LAT); + //not crossing the equator + range = subSF.getType().getRangeQuery(parser, subSF, + String.valueOf(ll[LAT]), + String.valueOf(ur[LAT]), true, true); + result.add(range, BooleanClause.Occur.MUST); + } + //Longitude + addMeridianBoundary(parser, options, result, ur[LONG], ll[LONG], "0.0", ".0"); + + } else {// we are all in the Eastern or Western hemi + //Now check other side of the Equator + if (ll[LAT] < 0.0 && ur[LAT] > 0.0) { + addEquatorialBoundary(parser, options, result, ur[LAT], ll[LAT]); + } else {//we are all in either the Northern or the Southern Hemi. + //TODO: nice to move this up so that it is the first thing and we can avoid the extra checks since + //this is actually the most likely case + subSF = subField(options.field, LAT); + range = subSF.getType().getRangeQuery(parser, subSF, + String.valueOf(ll[LAT]), + String.valueOf(ur[LAT]), true, true); + result.add(range, BooleanClause.Occur.MUST); + + } + //Longitude, all in the same hemi + subSF = subField(options.field, LONG); + range = subSF.getType().getRangeQuery(parser, subSF, + String.valueOf(ll[LONG]), + String.valueOf(ur[LONG]), true, true); + result.add(range, BooleanClause.Occur.MUST); + } + } + + return result; + } + + /** + * Add a boundary condition around a meridian + * @param parser + * @param options + * @param result + * @param upperRightLon + * @param lowerLeftLon + * @param eastern + * @param western + */ + + private void addMeridianBoundary(QParser parser, SpatialOptions options, BooleanQuery result, double upperRightLon, + double lowerLeftLon, String eastern, String western) { + SchemaField subSF; + Query range; + BooleanQuery lonQ = new BooleanQuery(); + subSF = subField(options.field, LONG); + //Eastern Hemisphere + range = subSF.getType().getRangeQuery(parser, subSF, + String.valueOf(lowerLeftLon), + eastern, true, true); + lonQ.add(range, BooleanClause.Occur.SHOULD); + //Western hemi + range = subSF.getType().getRangeQuery(parser, subSF, + western, + String.valueOf(upperRightLon), true, true); + lonQ.add(range, BooleanClause.Occur.SHOULD); + //One or the other must occur + result.add(lonQ, BooleanClause.Occur.MUST); + } + + /** + * Add query conditions for boundaries like the equator, poles and meridians + * + * @param parser + * @param options + * @param result + * @param upperRight + * @param lowerLeft + */ + protected void addEquatorialBoundary(QParser parser, SpatialOptions options, BooleanQuery result, double upperRight, double lowerLeft) { + SchemaField subSF; + Query range; + BooleanQuery tmpQ = new BooleanQuery(); + subSF = subField(options.field, LAT); + //southern hemi. + range = subSF.getType().getRangeQuery(parser, subSF, + String.valueOf(lowerLeft), + "0", true, true); + tmpQ.add(range, BooleanClause.Occur.SHOULD); + //northern hemi + range = subSF.getType().getRangeQuery(parser, subSF, + "0", String.valueOf(upperRight), true, true); + tmpQ.add(range, BooleanClause.Occur.SHOULD); + //One or the other must occur + result.add(tmpQ, BooleanClause.Occur.MUST); + } + + @Override + public ValueSource getValueSource(SchemaField field, QParser parser) { + ArrayList vs = new ArrayList(2); + for (int i = 0; i < 2; i++) { + SchemaField sub = subField(field, i); + vs.add(sub.getType().getValueSource(sub, parser)); + } + return new LatLonValueSource(field, vs); + } + + @Override + public boolean isPolyField() { + return true; + } + + @Override + public void write(XMLWriter xmlWriter, String name, Fieldable f) throws IOException { + xmlWriter.writeStr(name, f.stringValue()); + } + + @Override + public void write(TextResponseWriter writer, String name, Fieldable f) throws IOException { + writer.writeStr(name, f.stringValue(), false); + } + + @Override + public SortField getSortField(SchemaField field, boolean top) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Sorting not supported on SpatialTileField " + field.getName()); + } + + + + //It never makes sense to create a single field, so make it impossible to happen + + @Override + public Field createField(SchemaField field, String externalVal, float boost) { + throw new UnsupportedOperationException("SpatialTileField uses multiple fields. field=" + field.getName()); + } + +} + +class LatLonValueSource extends VectorValueSource { + private final SchemaField sf; + + public LatLonValueSource(SchemaField sf, List sources) { + super(sources); + this.sf = sf; + } + + @Override + public String name() { + return "latlon"; + } + + @Override + public String description() { + return name() + "(" + sf.getName() + ")"; + } +} Propchange: lucene/dev/trunk/solr/src/java/org/apache/solr/schema/LatLonType.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: lucene/dev/trunk/solr/src/java/org/apache/solr/schema/PointType.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/java/org/apache/solr/schema/PointType.java?rev=962727&r1=962726&r2=962727&view=diff ============================================================================== --- lucene/dev/trunk/solr/src/java/org/apache/solr/schema/PointType.java (original) +++ lucene/dev/trunk/solr/src/java/org/apache/solr/schema/PointType.java Sat Jul 10 00:12:41 2010 @@ -23,15 +23,17 @@ import org.apache.lucene.search.BooleanC import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.SortField; +import org.apache.lucene.spatial.tier.DistanceUtils; +import org.apache.lucene.spatial.tier.InvalidGeoException; import org.apache.solr.common.SolrException; import org.apache.solr.common.params.MapSolrParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.response.TextResponseWriter; import org.apache.solr.response.XMLWriter; import org.apache.solr.search.QParser; +import org.apache.solr.search.SpatialOptions; import org.apache.solr.search.function.VectorValueSource; import org.apache.solr.search.function.ValueSource; -import org.apache.solr.search.function.distance.DistanceUtils; import java.io.IOException; import java.util.Map; @@ -45,7 +47,7 @@ import java.util.ArrayList; *

* NOTE: There can only be one sub type */ -public class PointType extends CoordinateFieldType { +public class PointType extends CoordinateFieldType implements SpatialQueryable { @Override protected void init(IndexSchema schema, Map args) { @@ -71,7 +73,12 @@ public class PointType extends Coordinat @Override public Fieldable[] createFields(SchemaField field, String externalVal, float boost) { - String[] point = DistanceUtils.parsePoint(null, externalVal, dimension); + String[] point = new String[0]; + try { + point = DistanceUtils.parsePoint(null, externalVal, dimension); + } catch (InvalidGeoException e) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e); + } // TODO: this doesn't currently support polyFields as sub-field types Fieldable[] f = new Fieldable[ (field.indexed() ? dimension : 0) + (field.stored() ? 1 : 0) ]; @@ -103,7 +110,11 @@ public class PointType extends Coordinat } - //It never makes sense to create a single field, so make it impossible to happen + /** + * It never makes sense to create a single field, so make it impossible to happen by + * throwing UnsupportedOperationException + * + */ @Override public Field createField(SchemaField field, String externalVal, float boost) { throw new UnsupportedOperationException("PointType uses multiple fields. field=" + field.getName()); @@ -131,8 +142,14 @@ public class PointType extends Coordinat public Query getRangeQuery(QParser parser, SchemaField field, String part1, String part2, boolean minInclusive, boolean maxInclusive) { //Query could look like: [x1,y1 TO x2,y2] for 2 dimension, but could look like: [x1,y1,z1 TO x2,y2,z2], and can be extrapolated to n-dimensions //thus, this query essentially creates a box, cube, etc. - String[] p1 = DistanceUtils.parsePoint(null, part1, dimension); - String[] p2 = DistanceUtils.parsePoint(null, part2, dimension); + String[] p1; + String[] p2; + try { + p1 = DistanceUtils.parsePoint(null, part1, dimension); + p2 = DistanceUtils.parsePoint(null, part2, dimension); + } catch (InvalidGeoException e) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e); + } BooleanQuery result = new BooleanQuery(true); for (int i = 0; i < dimension; i++) { SchemaField subSF = subField(field, i); @@ -144,7 +161,12 @@ public class PointType extends Coordinat @Override public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) { - String[] p1 = DistanceUtils.parsePoint(null, externalVal, dimension); + String[] p1 = new String[0]; + try { + p1 = DistanceUtils.parsePoint(null, externalVal, dimension); + } catch (InvalidGeoException e) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e); + } //TODO: should we assert that p1.length == dimension? BooleanQuery bq = new BooleanQuery(true); for (int i = 0; i < dimension; i++) { @@ -154,6 +176,43 @@ public class PointType extends Coordinat } return bq; } + + /** + * Calculates the range and creates a RangeQuery (bounding box) wrapped in a BooleanQuery (unless the dimension is 1, one range for every dimension, AND'd together by a Boolean + * @param parser The parser + * @param options The {@link org.apache.solr.search.SpatialOptions} for this filter. + * @return The Query representing the bounding box around the point. + */ + public Query createSpatialQuery(QParser parser, SpatialOptions options) { + Query result = null; + double [] point = new double[0]; + try { + point = DistanceUtils.parsePointDouble(null, options.pointStr, dimension); + } catch (InvalidGeoException e) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e); + } + if (dimension == 1){ + //TODO: Handle distance measures + String lower = String.valueOf(point[0] - options.distance); + String upper = String.valueOf(point[0] + options.distance); + SchemaField subSF = subField(options.field, 0); + // points must currently be ordered... should we support specifying any two opposite corner points? + result = subSF.getType().getRangeQuery(parser, subSF, lower, upper, true, true); + } else { + BooleanQuery tmp = new BooleanQuery(); + //TODO: Handle distance measures, as this assumes Euclidean + double [] ur = org.apache.lucene.spatial.tier.DistanceUtils.vectorBoxCorner(point, null, options.distance, true); + double [] ll = org.apache.lucene.spatial.tier.DistanceUtils.vectorBoxCorner(point, null, options.distance, false); + for (int i = 0; i < ur.length; i++) { + SchemaField subSF = subField(options.field, i); + Query range = subSF.getType().getRangeQuery(parser, subSF, String.valueOf(ll[i]), String.valueOf(ur[i]), true, true); + tmp.add(range, BooleanClause.Occur.MUST); + + } + result = tmp; + } + return result; + } } Added: lucene/dev/trunk/solr/src/java/org/apache/solr/schema/SpatialQueryable.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/java/org/apache/solr/schema/SpatialQueryable.java?rev=962727&view=auto ============================================================================== --- lucene/dev/trunk/solr/src/java/org/apache/solr/schema/SpatialQueryable.java (added) +++ lucene/dev/trunk/solr/src/java/org/apache/solr/schema/SpatialQueryable.java Sat Jul 10 00:12:41 2010 @@ -0,0 +1,35 @@ +package org.apache.solr.schema; +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.search.Filter; +import org.apache.lucene.search.Query; +import org.apache.solr.search.QParser; +import org.apache.solr.search.SpatialOptions; + + +/** + * Indicate that the implementing class is capable of generating a Query against spatial resources. + * For example, the PointType is capable of creating a query that restricts the document space down + * to documents that are within a certain distance of a given point. * + * + **/ +public interface SpatialQueryable { + + public Query createSpatialQuery(QParser parser, SpatialOptions options); +} Propchange: lucene/dev/trunk/solr/src/java/org/apache/solr/schema/SpatialQueryable.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: lucene/dev/trunk/solr/src/java/org/apache/solr/schema/SpatialTileField.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/java/org/apache/solr/schema/SpatialTileField.java?rev=962727&r1=962726&r2=962727&view=diff ============================================================================== --- lucene/dev/trunk/solr/src/java/org/apache/solr/schema/SpatialTileField.java (original) +++ lucene/dev/trunk/solr/src/java/org/apache/solr/schema/SpatialTileField.java Sat Jul 10 00:12:41 2010 @@ -1,190 +0,0 @@ -package org.apache.solr.schema; -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.document.Field; -import org.apache.lucene.document.Fieldable; -import org.apache.lucene.search.BooleanClause; -import org.apache.lucene.search.BooleanQuery; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.SortField; -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.solr.common.ResourceLoader; -import org.apache.solr.common.SolrException; -import org.apache.solr.common.params.MapSolrParams; -import org.apache.solr.common.params.SolrParams; -import org.apache.solr.response.TextResponseWriter; -import org.apache.solr.response.XMLWriter; -import org.apache.solr.search.QParser; -import org.apache.solr.search.function.ValueSource; -import org.apache.solr.search.function.distance.DistanceUtils; -import org.apache.solr.util.plugin.ResourceLoaderAware; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - - -/** - * Represents a Tiling system for spatial data representation (lat/lon). A Tile is like a zoom level on an - * interactive map. - *

- * Specify a lower and upper tile, and this will create tiles for all the levels in between, inclusive of the upper tile. - *

- * Querying directly against this field is probably not all that useful unless you specifically know the box id - *

- *

- * See http://wiki.apache.org/solr/SpatialSearch - */ -public class SpatialTileField extends AbstractSubTypeFieldType implements ResourceLoaderAware { - - public static final String START_LEVEL = "start"; - public static final String END_LEVEL = "end"; - public static final String PROJECTOR_CLASS = "projector"; - - private static final int DEFAULT_END_LEVEL = 15; - - private static final int DEFAULT_START_LEVEL = 4; - - private int start = DEFAULT_START_LEVEL, end = DEFAULT_END_LEVEL; - private int tileDiff;//we're going to need this over and over, so cache it. - private String projectorName; - protected List plotters; - - - @Override - protected void init(IndexSchema schema, Map args) { - SolrParams p = new MapSolrParams(args); - start = p.getInt(START_LEVEL, DEFAULT_START_LEVEL); - end = p.getInt(END_LEVEL, DEFAULT_END_LEVEL); - if (end < start) { - //flip them around - int tmp = start; - start = end; - end = tmp; - } - args.remove(START_LEVEL); - args.remove(END_LEVEL); - projectorName = p.get(PROJECTOR_CLASS, SinusoidalProjector.class.getName()); - args.remove(PROJECTOR_CLASS); - super.init(schema, args); - tileDiff = (end - start) + 1;//add one since we are inclusive of the upper tier - createSuffixCache(tileDiff); - - - } - - public void inform(ResourceLoader loader) { - IProjector projector = (IProjector) loader.newInstance(projectorName); - if (projector != null) { - plotters = new ArrayList(tileDiff); - for (int i = start; i <= end; i++) { - plotters.add(new CartesianTierPlotter(i, projector, "")); - } - } else { - throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Could not instantiate a Projector Instance for: " - + projectorName + ". Make sure the " + PROJECTOR_CLASS + " attribute is set properly in the schema"); - } - - } - - @Override - public Fieldable[] createFields(SchemaField field, String externalVal, float boost) { - Fieldable[] f = new Fieldable[(field.indexed() ? tileDiff : 0) + (field.stored() ? 1 : 0)]; - if (field.indexed()) { - int i = 0; - double[] latLon = DistanceUtils.parseLatitudeLongitude(null, externalVal); - for (CartesianTierPlotter plotter : plotters) { - double boxId = plotter.getTierBoxId(latLon[0], latLon[1]); - f[i] = subField(field, i).createField(String.valueOf(boxId), boost); - i++; - } - } - - if (field.stored()) { - String storedVal = externalVal; // normalize or not? - f[f.length - 1] = createField(field.getName(), storedVal, - getFieldStore(field, storedVal), Field.Index.NO, Field.TermVector.NO, - false, false, boost); - } - return f; - } - - //The externalVal here is a box id, as it doesn't make sense to pick a specific tile since that requires a distance - //so, just OR together a search against all the tile - - @Override - public Query getRangeQuery(QParser parser, SchemaField field, String part1, String part2, boolean minInclusive, - boolean maxInclusive) { - BooleanQuery bq = new BooleanQuery(true); - for (int i = 0; i < tileDiff; i++) { - SchemaField sf = subField(field, i); - Query tq = sf.getType().getRangeQuery(parser, sf, part1, part2, minInclusive, maxInclusive); - bq.add(tq, BooleanClause.Occur.SHOULD); - } - return bq; - } - - @Override - public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) { - //The externalVal here is a box id, as it doesn't make sense to pick a specific tile since that requires a distance - //so, just OR together a search against all the tiles - BooleanQuery bq = new BooleanQuery(true); - for (int i = 0; i < tileDiff; i++) { - SchemaField sf = subField(field, i); - Query tq = sf.getType().getFieldQuery(parser, sf, externalVal); - bq.add(tq, BooleanClause.Occur.SHOULD); - } - return bq; - } - - @Override - public boolean isPolyField() { - return true; - } - - @Override - public void write(XMLWriter xmlWriter, String name, Fieldable f) throws IOException { - xmlWriter.writeStr(name, f.stringValue()); - } - - @Override - public void write(TextResponseWriter writer, String name, Fieldable f) throws IOException { - writer.writeStr(name, f.stringValue(), false); - } - - @Override - public SortField getSortField(SchemaField field, boolean top) { - throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Sorting not supported on SpatialTileField " + field.getName()); - } - - @Override - public ValueSource getValueSource(SchemaField field, QParser parser) { - //TODO: Should this really throw UOE? What does it mean for a function to use the values of a tier? Let's leave it unsupported for now - throw new UnsupportedOperationException("SpatialTileField uses multiple fields and does not support ValueSource"); - } - - //It never makes sense to create a single field, so make it impossible to happen - - @Override - public Field createField(SchemaField field, String externalVal, float boost) { - throw new UnsupportedOperationException("SpatialTileField uses multiple fields. field=" + field.getName()); - } -} Modified: lucene/dev/trunk/solr/src/java/org/apache/solr/search/QParserPlugin.java URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/java/org/apache/solr/search/QParserPlugin.java?rev=962727&r1=962726&r2=962727&view=diff ============================================================================== --- lucene/dev/trunk/solr/src/java/org/apache/solr/search/QParserPlugin.java (original) +++ lucene/dev/trunk/solr/src/java/org/apache/solr/search/QParserPlugin.java Sat Jul 10 00:12:41 2010 @@ -37,6 +37,7 @@ public abstract class QParserPlugin impl RawQParserPlugin.NAME, RawQParserPlugin.class, NestedQParserPlugin.NAME, NestedQParserPlugin.class, FunctionRangeQParserPlugin.NAME, FunctionRangeQParserPlugin.class, + SpatialFilterQParserPlugin.NAME, SpatialFilterQParserPlugin.class, }; /** return a {@link QParser} */