You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by nk...@apache.org on 2016/02/05 18:11:32 UTC
[1/6] lucene-solr git commit: LUCENE-6997: refactor sandboxed
GeoPointField and query classes to lucene-spatial module
Repository: lucene-solr
Updated Branches:
refs/heads/lucene-6997 [created] 665041c52
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java b/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java
new file mode 100644
index 0000000..7dc9318
--- /dev/null
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java
@@ -0,0 +1,546 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.lucene.util.LuceneTestCase;
+import org.junit.BeforeClass;
+
+import com.carrotsearch.randomizedtesting.generators.RandomInts;
+
+import static org.apache.lucene.spatial.util.GeoDistanceUtils.DISTANCE_PCT_ERR;
+
+/**
+ * Tests class for methods in GeoUtils
+ *
+ * @lucene.experimental
+ */
+public class TestGeoUtils extends LuceneTestCase {
+
+ private static final double LON_SCALE = (0x1L<<GeoUtils.BITS)/360.0D;
+ private static final double LAT_SCALE = (0x1L<<GeoUtils.BITS)/180.0D;
+
+ // Global bounding box we will "cover" in the random test; we have to make this "smallish" else the queries take very long:
+ private static double originLat;
+ private static double originLon;
+ // private static double range;
+ private static double lonRange;
+ private static double latRange;
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ // Between 1.0 and 3.0:
+ lonRange = 2 * (random().nextDouble() + 0.5);
+ latRange = 2 * (random().nextDouble() + 0.5);
+
+ originLon = GeoUtils.MIN_LON_INCL + lonRange + (GeoUtils.MAX_LON_INCL - GeoUtils.MIN_LON_INCL - 2 * lonRange) * random().nextDouble();
+ originLon = GeoUtils.normalizeLon(originLon);
+ originLat = GeoUtils.MIN_LAT_INCL + latRange + (GeoUtils.MAX_LAT_INCL - GeoUtils.MIN_LAT_INCL - 2 * latRange) * random().nextDouble();
+ originLat = GeoUtils.normalizeLat(originLat);
+
+ if (VERBOSE) {
+ System.out.println("TEST: originLon=" + originLon + " lonRange= " + lonRange + " originLat=" + originLat + " latRange=" + latRange);
+ }
+ }
+
+ public void testGeoHash() {
+ int numPoints = atLeast(100);
+ String randomGeoHashString;
+ String mortonGeoHash;
+ long mortonLongFromGHLong, geoHashLong, mortonLongFromGHString;
+ int randomLevel;
+ for (int i = 0; i < numPoints; ++i) {
+ // random point
+ double lat = randomLat(false);
+ double lon = randomLon(false);
+
+ // compute geohash straight from lat/lon and from morton encoded value to ensure they're the same
+ randomGeoHashString = GeoHashUtils.stringEncode(lon, lat, randomLevel = random().nextInt(12 - 1) + 1);
+ mortonGeoHash = GeoHashUtils.stringEncodeFromMortonLong(GeoUtils.mortonHash(lon, lat), randomLevel);
+ assertEquals(randomGeoHashString, mortonGeoHash);
+
+ // v&v conversion from lat/lon or geohashstring to geohash long and back to geohash string
+ geoHashLong = (random().nextBoolean()) ? GeoHashUtils.longEncode(lon, lat, randomLevel) : GeoHashUtils.longEncode(randomGeoHashString);
+ assertEquals(randomGeoHashString, GeoHashUtils.stringEncode(geoHashLong));
+
+ // v&v conversion from geohash long to morton long
+ mortonLongFromGHString = GeoHashUtils.mortonEncode(randomGeoHashString);
+ mortonLongFromGHLong = GeoHashUtils.mortonEncode(geoHashLong);
+ assertEquals(mortonLongFromGHLong, mortonLongFromGHString);
+
+ // v&v lat/lon from geohash string and geohash long
+ assertEquals(GeoUtils.mortonUnhashLat(mortonLongFromGHString), GeoUtils.mortonUnhashLat(mortonLongFromGHLong), 0);
+ assertEquals(GeoUtils.mortonUnhashLon(mortonLongFromGHString), GeoUtils.mortonUnhashLon(mortonLongFromGHLong), 0);
+ }
+ }
+
+ /**
+ * Pass condition: lat=42.6, lng=-5.6 should be encoded as "ezs42e44yx96",
+ * lat=57.64911 lng=10.40744 should be encoded as "u4pruydqqvj8"
+ */
+ public void testEncode() {
+ String hash = GeoHashUtils.stringEncode(-5.6, 42.6, 12);
+ assertEquals("ezs42e44yx96", hash);
+
+ hash = GeoHashUtils.stringEncode(10.40744, 57.64911, 12);
+ assertEquals("u4pruydqqvj8", hash);
+ }
+
+ /**
+ * Pass condition: lat=52.3738007, lng=4.8909347 should be encoded and then
+ * decoded within 0.00001 of the original value
+ */
+ public void testDecodePreciseLongitudeLatitude() {
+ final String geohash = GeoHashUtils.stringEncode(4.8909347, 52.3738007);
+ final long hash = GeoHashUtils.mortonEncode(geohash);
+
+ assertEquals(52.3738007, GeoUtils.mortonUnhashLat(hash), 0.00001D);
+ assertEquals(4.8909347, GeoUtils.mortonUnhashLon(hash), 0.00001D);
+ }
+
+ /**
+ * Pass condition: lat=84.6, lng=10.5 should be encoded and then decoded
+ * within 0.00001 of the original value
+ */
+ public void testDecodeImpreciseLongitudeLatitude() {
+ final String geohash = GeoHashUtils.stringEncode(10.5, 84.6);
+
+ final long hash = GeoHashUtils.mortonEncode(geohash);
+
+ assertEquals(84.6, GeoUtils.mortonUnhashLat(hash), 0.00001D);
+ assertEquals(10.5, GeoUtils.mortonUnhashLon(hash), 0.00001D);
+ }
+
+ public void testDecodeEncode() {
+ final String geoHash = "u173zq37x014";
+ assertEquals(geoHash, GeoHashUtils.stringEncode(4.8909347, 52.3738007));
+ final long mortonHash = GeoHashUtils.mortonEncode(geoHash);
+ final double lon = GeoUtils.mortonUnhashLon(mortonHash);
+ final double lat = GeoUtils.mortonUnhashLat(mortonHash);
+ assertEquals(52.37380061d, GeoUtils.mortonUnhashLat(mortonHash), 0.000001d);
+ assertEquals(4.8909343d, GeoUtils.mortonUnhashLon(mortonHash), 0.000001d);
+
+ assertEquals(geoHash, GeoHashUtils.stringEncode(lon, lat));
+ }
+
+ public void testNeighbors() {
+ String geohash = "gcpv";
+ List<String> expectedNeighbors = new ArrayList<>();
+ expectedNeighbors.add("gcpw");
+ expectedNeighbors.add("gcpy");
+ expectedNeighbors.add("u10n");
+ expectedNeighbors.add("gcpt");
+ expectedNeighbors.add("u10j");
+ expectedNeighbors.add("gcps");
+ expectedNeighbors.add("gcpu");
+ expectedNeighbors.add("u10h");
+ Collection<? super String> neighbors = new ArrayList<>();
+ GeoHashUtils.addNeighbors(geohash, neighbors );
+ assertEquals(expectedNeighbors, neighbors);
+
+ // Border odd geohash
+ geohash = "u09x";
+ expectedNeighbors = new ArrayList<>();
+ expectedNeighbors.add("u0c2");
+ expectedNeighbors.add("u0c8");
+ expectedNeighbors.add("u0cb");
+ expectedNeighbors.add("u09r");
+ expectedNeighbors.add("u09z");
+ expectedNeighbors.add("u09q");
+ expectedNeighbors.add("u09w");
+ expectedNeighbors.add("u09y");
+ neighbors = new ArrayList<>();
+ GeoHashUtils.addNeighbors(geohash, neighbors);
+ assertEquals(expectedNeighbors, neighbors);
+
+ // Border even geohash
+ geohash = "u09tv";
+ expectedNeighbors = new ArrayList<>();
+ expectedNeighbors.add("u09wh");
+ expectedNeighbors.add("u09wj");
+ expectedNeighbors.add("u09wn");
+ expectedNeighbors.add("u09tu");
+ expectedNeighbors.add("u09ty");
+ expectedNeighbors.add("u09ts");
+ expectedNeighbors.add("u09tt");
+ expectedNeighbors.add("u09tw");
+ neighbors = new ArrayList<>();
+ GeoHashUtils.addNeighbors(geohash, neighbors );
+ assertEquals(expectedNeighbors, neighbors);
+
+ // Border even and odd geohash
+ geohash = "ezzzz";
+ expectedNeighbors = new ArrayList<>();
+ expectedNeighbors.add("gbpbn");
+ expectedNeighbors.add("gbpbp");
+ expectedNeighbors.add("u0000");
+ expectedNeighbors.add("ezzzy");
+ expectedNeighbors.add("spbpb");
+ expectedNeighbors.add("ezzzw");
+ expectedNeighbors.add("ezzzx");
+ expectedNeighbors.add("spbp8");
+ neighbors = new ArrayList<>();
+ GeoHashUtils.addNeighbors(geohash, neighbors );
+ assertEquals(expectedNeighbors, neighbors);
+ }
+
+ public void testClosestPointOnBBox() {
+ double[] result = new double[2];
+ GeoDistanceUtils.closestPointOnBBox(20, 30, 40, 50, 70, 70, result);
+ assertEquals(40.0, result[0], 0.0);
+ assertEquals(50.0, result[1], 0.0);
+
+ GeoDistanceUtils.closestPointOnBBox(-20, -20, 0, 0, 70, 70, result);
+ assertEquals(0.0, result[0], 0.0);
+ assertEquals(0.0, result[1], 0.0);
+ }
+
+ private static class Cell {
+ static int nextCellID;
+
+ final Cell parent;
+ final int cellID;
+ final double minLon, maxLon;
+ final double minLat, maxLat;
+ final int splitCount;
+
+ public Cell(Cell parent,
+ double minLon, double minLat,
+ double maxLon, double maxLat,
+ int splitCount) {
+ assert maxLon >= minLon;
+ assert maxLat >= minLat;
+ this.parent = parent;
+ this.minLon = minLon;
+ this.minLat = minLat;
+ this.maxLon = maxLon;
+ this.maxLat = maxLat;
+ this.cellID = nextCellID++;
+ this.splitCount = splitCount;
+ }
+
+ /** Returns true if the quantized point lies within this cell, inclusive on all bounds. */
+ public boolean contains(double lon, double lat) {
+ return lon >= minLon && lon <= maxLon && lat >= minLat && lat <= maxLat;
+ }
+
+ @Override
+ public String toString() {
+ return "cell=" + cellID + (parent == null ? "" : " parentCellID=" + parent.cellID) + " lon: " + minLon + " TO " + maxLon + ", lat: " + minLat + " TO " + maxLat + ", splits: " + splitCount;
+ }
+ }
+
+ public long scaleLon(final double val) {
+ return (long) ((val-GeoUtils.MIN_LON_INCL) * LON_SCALE);
+ }
+
+ public long scaleLat(final double val) {
+ return (long) ((val-GeoUtils.MIN_LAT_INCL) * LAT_SCALE);
+ }
+
+ public double unscaleLon(final long val) {
+ return (val / LON_SCALE) + GeoUtils.MIN_LON_INCL;
+ }
+
+ public double unscaleLat(final long val) {
+ return (val / LAT_SCALE) + GeoUtils.MIN_LAT_INCL;
+ }
+
+ public double randomLat(boolean small) {
+ double result;
+ if (small) {
+ result = GeoUtils.normalizeLat(originLat + latRange * (random().nextDouble() - 0.5));
+ } else {
+ result = -90 + 180.0 * random().nextDouble();
+ }
+ return result;
+ }
+
+ public double randomLon(boolean small) {
+ double result;
+ if (small) {
+ result = GeoUtils.normalizeLon(originLon + lonRange * (random().nextDouble() - 0.5));
+ } else {
+ result = -180 + 360.0 * random().nextDouble();
+ }
+ return result;
+ }
+
+ private void findMatches(Set<Integer> hits, PrintWriter log, Cell root,
+ double centerLon, double centerLat, double radiusMeters,
+ double[] docLons, double[] docLats) {
+
+ if (VERBOSE) {
+ log.println(" root cell: " + root);
+ }
+
+ List<Cell> queue = new ArrayList<>();
+ queue.add(root);
+
+ int recurseDepth = RandomInts.randomIntBetween(random(), 5, 15);
+
+ while (queue.size() > 0) {
+ Cell cell = queue.get(queue.size()-1);
+ queue.remove(queue.size()-1);
+ if (VERBOSE) {
+ log.println(" cycle: " + cell + " queue.size()=" + queue.size());
+ }
+
+ if (random().nextInt(10) == 7 || cell.splitCount > recurseDepth) {
+ if (VERBOSE) {
+ log.println(" leaf");
+ }
+ // Leaf cell: brute force check all docs that fall within this cell:
+ for(int docID=0;docID<docLons.length;docID++) {
+ if (cell.contains(docLons[docID], docLats[docID])) {
+ double distanceMeters = GeoDistanceUtils.haversin(centerLat, centerLon, docLats[docID], docLons[docID]);
+ if (distanceMeters <= radiusMeters) {
+ if (VERBOSE) {
+ log.println(" check doc=" + docID + ": match!");
+ }
+ hits.add(docID);
+ } else {
+ if (VERBOSE) {
+ log.println(" check doc=" + docID + ": no match");
+ }
+ }
+ }
+ }
+ } else {
+
+ if (GeoRelationUtils.rectWithinCircle(cell.minLon, cell.minLat, cell.maxLon, cell.maxLat, centerLon, centerLat, radiusMeters)) {
+ // Query circle fully contains this cell, just addAll:
+ if (VERBOSE) {
+ log.println(" circle fully contains cell: now addAll");
+ }
+ for(int docID=0;docID<docLons.length;docID++) {
+ if (cell.contains(docLons[docID], docLats[docID])) {
+ if (VERBOSE) {
+ log.println(" addAll doc=" + docID);
+ }
+ hits.add(docID);
+ }
+ }
+ continue;
+ } else if (GeoRelationUtils.rectWithin(root.minLon, root.minLat, root.maxLon, root.maxLat,
+ cell.minLon, cell.minLat, cell.maxLon, cell.maxLat)) {
+ // Fall through below to "recurse"
+ if (VERBOSE) {
+ log.println(" cell fully contains circle: keep splitting");
+ }
+ } else if (GeoRelationUtils.rectCrossesCircle(cell.minLon, cell.minLat, cell.maxLon, cell.maxLat,
+ centerLon, centerLat, radiusMeters)) {
+ // Fall through below to "recurse"
+ if (VERBOSE) {
+ log.println(" cell overlaps circle: keep splitting");
+ }
+ } else {
+ if (VERBOSE) {
+ log.println(" no overlap: drop this cell");
+ for(int docID=0;docID<docLons.length;docID++) {
+ if (cell.contains(docLons[docID], docLats[docID])) {
+ if (VERBOSE) {
+ log.println(" skip doc=" + docID);
+ }
+ }
+ }
+ }
+ continue;
+ }
+
+ // Randomly split:
+ if (random().nextBoolean()) {
+
+ // Split on lon:
+ double splitValue = cell.minLon + (cell.maxLon - cell.minLon) * random().nextDouble();
+ if (VERBOSE) {
+ log.println(" now split on lon=" + splitValue);
+ }
+ Cell cell1 = new Cell(cell,
+ cell.minLon, cell.minLat,
+ splitValue, cell.maxLat,
+ cell.splitCount+1);
+ Cell cell2 = new Cell(cell,
+ splitValue, cell.minLat,
+ cell.maxLon, cell.maxLat,
+ cell.splitCount+1);
+ if (VERBOSE) {
+ log.println(" split cell1: " + cell1);
+ log.println(" split cell2: " + cell2);
+ }
+ queue.add(cell1);
+ queue.add(cell2);
+ } else {
+
+ // Split on lat:
+ double splitValue = cell.minLat + (cell.maxLat - cell.minLat) * random().nextDouble();
+ if (VERBOSE) {
+ log.println(" now split on lat=" + splitValue);
+ }
+ Cell cell1 = new Cell(cell,
+ cell.minLon, cell.minLat,
+ cell.maxLon, splitValue,
+ cell.splitCount+1);
+ Cell cell2 = new Cell(cell,
+ cell.minLon, splitValue,
+ cell.maxLon, cell.maxLat,
+ cell.splitCount+1);
+ if (VERBOSE) {
+ log.println(" split cells:\n " + cell1 + "\n " + cell2);
+ }
+ queue.add(cell1);
+ queue.add(cell2);
+ }
+ }
+ }
+ }
+
+ /** Tests consistency of GeoUtils.rectWithinCircle, .rectCrossesCircle, .rectWithin and SloppyMath.haversine distance check */
+ public void testGeoRelations() throws Exception {
+
+ int numDocs = atLeast(1000);
+
+ boolean useSmallRanges = random().nextBoolean();
+
+ if (VERBOSE) {
+ System.out.println("TEST: " + numDocs + " docs useSmallRanges=" + useSmallRanges);
+ }
+
+ double[] docLons = new double[numDocs];
+ double[] docLats = new double[numDocs];
+ for(int docID=0;docID<numDocs;docID++) {
+ docLons[docID] = randomLon(useSmallRanges);
+ docLats[docID] = randomLat(useSmallRanges);
+ if (VERBOSE) {
+ System.out.println(" doc=" + docID + ": lon=" + docLons[docID] + " lat=" + docLats[docID]);
+ }
+ }
+
+ int iters = atLeast(10);
+
+ iters = atLeast(50);
+
+ for(int iter=0;iter<iters;iter++) {
+
+ Cell.nextCellID = 0;
+
+ double centerLon = randomLon(useSmallRanges);
+ double centerLat = randomLat(useSmallRanges);
+
+ // So the circle covers at most 50% of the earth's surface:
+
+ double radiusMeters;
+
+ // TODO: large exotic rectangles created by BKD may be inaccurate up to 2 times DISTANCE_PCT_ERR.
+ // restricting size until LUCENE-6994 can be addressed
+ if (true || useSmallRanges) {
+ // Approx 3 degrees lon at the equator:
+ radiusMeters = random().nextDouble() * 333000;
+ } else {
+ radiusMeters = random().nextDouble() * GeoProjectionUtils.SEMIMAJOR_AXIS * Math.PI / 2.0;
+ }
+
+ StringWriter sw = new StringWriter();
+ PrintWriter log = new PrintWriter(sw, true);
+
+ if (VERBOSE) {
+ log.println("\nTEST: iter=" + iter + " radiusMeters=" + radiusMeters + " centerLon=" + centerLon + " centerLat=" + centerLat);
+ }
+
+ GeoRect bbox = GeoUtils.circleToBBox(centerLon, centerLat, radiusMeters);
+
+ Set<Integer> hits = new HashSet<>();
+
+ if (bbox.maxLon < bbox.minLon) {
+ // Crosses dateline
+ log.println(" circle crosses dateline; first left query");
+ double unwrappedLon = centerLon;
+ if (unwrappedLon > bbox.maxLon) {
+ // unwrap left
+ unwrappedLon += -360.0D;
+ }
+ findMatches(hits, log,
+ new Cell(null,
+ -180, bbox.minLat,
+ bbox.maxLon, bbox.maxLat,
+ 0),
+ unwrappedLon, centerLat, radiusMeters, docLons, docLats);
+ log.println(" circle crosses dateline; now right query");
+ if (unwrappedLon < bbox.maxLon) {
+ // unwrap right
+ unwrappedLon += 360.0D;
+ }
+ findMatches(hits, log,
+ new Cell(null,
+ bbox.minLon, bbox.minLat,
+ 180, bbox.maxLat,
+ 0),
+ unwrappedLon, centerLat, radiusMeters, docLons, docLats);
+ } else {
+ // Start with the root cell that fully contains the shape:
+ findMatches(hits, log,
+ new Cell(null,
+ bbox.minLon, bbox.minLat,
+ bbox.maxLon, bbox.maxLat,
+ 0),
+ centerLon, centerLat, radiusMeters,
+ docLons, docLats);
+ }
+
+ if (VERBOSE) {
+ log.println(" " + hits.size() + " hits");
+ }
+
+ int failCount = 0;
+
+ // Done matching, now verify:
+ for(int docID=0;docID<numDocs;docID++) {
+ double distanceMeters = GeoDistanceUtils.haversin(centerLat, centerLon, docLats[docID], docLons[docID]);
+ final Boolean expected;
+ final double percentError = Math.abs(distanceMeters - radiusMeters) / distanceMeters;
+ if (percentError <= DISTANCE_PCT_ERR) {
+ expected = null;
+ } else {
+ expected = distanceMeters <= radiusMeters;
+ }
+
+ boolean actual = hits.contains(docID);
+ if (expected != null && actual != expected) {
+ if (actual) {
+ log.println("doc=" + docID + " matched but should not with distance error " + percentError + " on iteration " + iter);
+ } else {
+ log.println("doc=" + docID + " did not match but should with distance error " + percentError + " on iteration " + iter);
+ }
+ log.println(" lon=" + docLons[docID] + " lat=" + docLats[docID] + " distanceMeters=" + distanceMeters + " vs radiusMeters=" + radiusMeters);
+ failCount++;
+ }
+ }
+
+ if (failCount != 0) {
+ System.out.print(sw.toString());
+ fail(failCount + " incorrect hits (see above)");
+ }
+ }
+ }
+}
[2/6] lucene-solr git commit: LUCENE-6997: refactor sandboxed
GeoPointField and query classes to lucene-spatial module
Posted by nk...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRelationUtils.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRelationUtils.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRelationUtils.java
new file mode 100644
index 0000000..1911720
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRelationUtils.java
@@ -0,0 +1,520 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.util.SloppyMath;
+
+/**
+ * Reusable geo-relation utility methods
+ */
+public class GeoRelationUtils {
+
+ // No instance:
+ private GeoRelationUtils() {
+ }
+
+ /**
+ * Determine if a bbox (defined by minLon, minLat, maxLon, maxLat) contains the provided point (defined by lon, lat)
+ * NOTE: this is a basic method that does not handle dateline or pole crossing. Unwrapping must be done before
+ * calling this method.
+ */
+ public static boolean pointInRectPrecise(final double lon, final double lat, final double minLon,
+ final double minLat, final double maxLon, final double maxLat) {
+ return lon >= minLon && lon <= maxLon && lat >= minLat && lat <= maxLat;
+ }
+
+ /**
+ * simple even-odd point in polygon computation
+ * 1. Determine if point is contained in the longitudinal range
+ * 2. Determine whether point crosses the edge by computing the latitudinal delta
+ * between the end-point of a parallel vector (originating at the point) and the
+ * y-component of the edge sink
+ *
+ * NOTE: Requires polygon point (x,y) order either clockwise or counter-clockwise
+ */
+ public static boolean pointInPolygon(double[] x, double[] y, double lat, double lon) {
+ assert x.length == y.length;
+ boolean inPoly = false;
+ /**
+ * Note: This is using a euclidean coordinate system which could result in
+ * upwards of 110KM error at the equator.
+ * TODO convert coordinates to cylindrical projection (e.g. mercator)
+ */
+ for (int i = 1; i < x.length; i++) {
+ if (x[i] <= lon && x[i-1] >= lon || x[i-1] <= lon && x[i] >= lon) {
+ if (y[i] + (lon - x[i]) / (x[i-1] - x[i]) * (y[i-1] - y[i]) <= lat) {
+ inPoly = !inPoly;
+ }
+ }
+ }
+ return inPoly;
+ }
+
+ /////////////////////////
+ // Rectangle relations
+ /////////////////////////
+
+ /**
+ * Computes whether two rectangles are disjoint
+ */
+ public static boolean rectDisjoint(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
+ final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
+ return (aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY);
+ }
+
+ /**
+ * Computes whether the first (a) rectangle is wholly within another (b) rectangle (shared boundaries allowed)
+ */
+ public static boolean rectWithin(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
+ final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
+ return !(aMinX < bMinX || aMinY < bMinY || aMaxX > bMaxX || aMaxY > bMaxY);
+ }
+
+ /**
+ * Computes whether two rectangles cross
+ */
+ public static boolean rectCrosses(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
+ final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
+ return !(rectDisjoint(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY) ||
+ rectWithin(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY));
+ }
+
+ /**
+ * Computes whether rectangle a contains rectangle b (touching allowed)
+ */
+ public static boolean rectContains(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
+ final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
+ return !(bMinX < aMinX || bMinY < aMinY || bMaxX > aMaxX || bMaxY > aMaxY);
+ }
+
+ /**
+ * Computes whether a rectangle intersects another rectangle (crosses, within, touching, etc)
+ */
+ public static boolean rectIntersects(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
+ final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
+ return !((aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY) );
+ }
+
+ /////////////////////////
+ // Polygon relations
+ /////////////////////////
+
+ /**
+ * Convenience method for accurately computing whether a rectangle crosses a poly
+ */
+ public static boolean rectCrossesPolyPrecise(final double rMinX, final double rMinY, final double rMaxX,
+ final double rMaxY, final double[] shapeX, final double[] shapeY,
+ final double sMinX, final double sMinY, final double sMaxX,
+ final double sMaxY) {
+ // short-circuit: if the bounding boxes are disjoint then the shape does not cross
+ if (rectDisjoint(rMinX, rMinY, rMaxX, rMaxY, sMinX, sMinY, sMaxX, sMaxY)) {
+ return false;
+ }
+ return rectCrossesPoly(rMinX, rMinY, rMaxX, rMaxY, shapeX, shapeY);
+ }
+
+ /**
+ * Compute whether a rectangle crosses a shape. (touching not allowed) Includes a flag for approximating the
+ * relation.
+ */
+ public static boolean rectCrossesPolyApprox(final double rMinX, final double rMinY, final double rMaxX,
+ final double rMaxY, final double[] shapeX, final double[] shapeY,
+ final double sMinX, final double sMinY, final double sMaxX,
+ final double sMaxY) {
+ // short-circuit: if the bounding boxes are disjoint then the shape does not cross
+ if (rectDisjoint(rMinX, rMinY, rMaxX, rMaxY, sMinX, sMinY, sMaxX, sMaxY)) {
+ return false;
+ }
+
+ final int polyLength = shapeX.length-1;
+ for (short p=0; p<polyLength; ++p) {
+ if (lineCrossesRect(shapeX[p], shapeY[p], shapeX[p+1], shapeY[p+1], rMinX, rMinY, rMaxX, rMaxY) == true) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Accurately compute (within restrictions of cartesian decimal degrees) whether a rectangle crosses a polygon
+ */
+ private static boolean rectCrossesPoly(final double rMinX, final double rMinY, final double rMaxX,
+ final double rMaxY, final double[] shapeX, final double[] shapeY) {
+ final double[][] bbox = new double[][] { {rMinX, rMinY}, {rMaxX, rMinY}, {rMaxX, rMaxY}, {rMinX, rMaxY}, {rMinX, rMinY} };
+ final int polyLength = shapeX.length-1;
+ double d, s, t, a1, b1, c1, a2, b2, c2;
+ double x00, y00, x01, y01, x10, y10, x11, y11;
+
+ // computes the intersection point between each bbox edge and the polygon edge
+ for (short b=0; b<4; ++b) {
+ a1 = bbox[b+1][1]-bbox[b][1];
+ b1 = bbox[b][0]-bbox[b+1][0];
+ c1 = a1*bbox[b+1][0] + b1*bbox[b+1][1];
+ for (int p=0; p<polyLength; ++p) {
+ a2 = shapeY[p+1]-shapeY[p];
+ b2 = shapeX[p]-shapeX[p+1];
+ // compute determinant
+ d = a1*b2 - a2*b1;
+ if (d != 0) {
+ // lines are not parallel, check intersecting points
+ c2 = a2*shapeX[p+1] + b2*shapeY[p+1];
+ s = (1/d)*(b2*c1 - b1*c2);
+ t = (1/d)*(a1*c2 - a2*c1);
+ x00 = StrictMath.min(bbox[b][0], bbox[b+1][0]) - GeoUtils.TOLERANCE;
+ x01 = StrictMath.max(bbox[b][0], bbox[b+1][0]) + GeoUtils.TOLERANCE;
+ y00 = StrictMath.min(bbox[b][1], bbox[b+1][1]) - GeoUtils.TOLERANCE;
+ y01 = StrictMath.max(bbox[b][1], bbox[b+1][1]) + GeoUtils.TOLERANCE;
+ x10 = StrictMath.min(shapeX[p], shapeX[p+1]) - GeoUtils.TOLERANCE;
+ x11 = StrictMath.max(shapeX[p], shapeX[p+1]) + GeoUtils.TOLERANCE;
+ y10 = StrictMath.min(shapeY[p], shapeY[p+1]) - GeoUtils.TOLERANCE;
+ y11 = StrictMath.max(shapeY[p], shapeY[p+1]) + GeoUtils.TOLERANCE;
+ // check whether the intersection point is touching one of the line segments
+ boolean touching = ((x00 == s && y00 == t) || (x01 == s && y01 == t))
+ || ((x10 == s && y10 == t) || (x11 == s && y11 == t));
+ // if line segments are not touching and the intersection point is within the range of either segment
+ if (!(touching || x00 > s || x01 < s || y00 > t || y01 < t || x10 > s || x11 < s || y10 > t || y11 < t)) {
+ return true;
+ }
+ }
+ } // for each poly edge
+ } // for each bbox edge
+ return false;
+ }
+
+ private static boolean lineCrossesRect(double aX1, double aY1, double aX2, double aY2,
+ final double rMinX, final double rMinY, final double rMaxX, final double rMaxY) {
+ // short-circuit: if one point inside rect, other outside
+ if (pointInRectPrecise(aX1, aY1, rMinX, rMinY, rMaxX, rMaxY) ?
+ !pointInRectPrecise(aX2, aY2, rMinX, rMinY, rMaxX, rMaxY) : pointInRectPrecise(aX2, aY2, rMinX, rMinY, rMaxX, rMaxY)) {
+ return true;
+ }
+
+ return lineCrossesLine(aX1, aY1, aX2, aY2, rMinX, rMinY, rMaxX, rMaxY)
+ || lineCrossesLine(aX1, aY1, aX2, aY2, rMaxX, rMinY, rMinX, rMaxY);
+ }
+
+ private static boolean lineCrossesLine(final double aX1, final double aY1, final double aX2, final double aY2,
+ final double bX1, final double bY1, final double bX2, final double bY2) {
+ // determine if three points are ccw (right-hand rule) by computing the determinate
+ final double aX2X1d = aX2 - aX1;
+ final double aY2Y1d = aY2 - aY1;
+ final double bX2X1d = bX2 - bX1;
+ final double bY2Y1d = bY2 - bY1;
+
+ final double t1B = aX2X1d * (bY2 - aY1) - aY2Y1d * (bX2 - aX1);
+ final double test1 = (aX2X1d * (bY1 - aY1) - aY2Y1d * (bX1 - aX1)) * t1B;
+ final double t2B = bX2X1d * (aY2 - bY1) - bY2Y1d * (aX2 - bX1);
+ final double test2 = (bX2X1d * (aY1 - bY1) - bY2Y1d * (aX1 - bX1)) * t2B;
+
+ if (test1 < 0 && test2 < 0) {
+ return true;
+ }
+
+ if (test1 == 0 || test2 == 0) {
+ // vertically collinear
+ if (aX1 == aX2 || bX1 == bX2) {
+ final double minAy = Math.min(aY1, aY2);
+ final double maxAy = Math.max(aY1, aY2);
+ final double minBy = Math.min(bY1, bY2);
+ final double maxBy = Math.max(bY1, bY2);
+
+ return !(minBy >= maxAy || maxBy <= minAy);
+ }
+ // horizontally collinear
+ final double minAx = Math.min(aX1, aX2);
+ final double maxAx = Math.max(aX1, aX2);
+ final double minBx = Math.min(bX1, bX2);
+ final double maxBx = Math.max(bX1, bX2);
+
+ return !(minBx >= maxAx || maxBx <= minAx);
+ }
+ return false;
+ }
+
+ public static boolean rectWithinPolyPrecise(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+ final double[] shapeX, final double[] shapeY, final double sMinX,
+ final double sMinY, final double sMaxX, final double sMaxY) {
+ // check if rectangle crosses poly (to handle concave/pacman polys), then check that all 4 corners
+ // are contained
+ return !(rectCrossesPolyPrecise(rMinX, rMinY, rMaxX, rMaxY, shapeX, shapeY, sMinX, sMinY, sMaxX, sMaxY) ||
+ !pointInPolygon(shapeX, shapeY, rMinY, rMinX) || !pointInPolygon(shapeX, shapeY, rMinY, rMaxX) ||
+ !pointInPolygon(shapeX, shapeY, rMaxY, rMaxX) || !pointInPolygon(shapeX, shapeY, rMaxY, rMinX));
+ }
+
+ /**
+ * Computes whether a rectangle is within a given polygon (shared boundaries allowed)
+ */
+ public static boolean rectWithinPolyApprox(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+ final double[] shapeX, final double[] shapeY, final double sMinX,
+ final double sMinY, final double sMaxX, final double sMaxY) {
+ // approximation: check if rectangle crosses poly (to handle concave/pacman polys), then check one of the corners
+ // are contained
+
+ // short-cut: if bounding boxes cross, rect is not within
+ if (rectCrosses(rMinX, rMinY, rMaxX, rMaxY, sMinX, sMinY, sMaxX, sMaxY) == true) {
+ return false;
+ }
+
+ return !(rectCrossesPolyApprox(rMinX, rMinY, rMaxX, rMaxY, shapeX, shapeY, sMinX, sMinY, sMaxX, sMaxY)
+ || !pointInPolygon(shapeX, shapeY, rMinY, rMinX));
+ }
+
+ /////////////////////////
+ // Circle relations
+ /////////////////////////
+
+ private static boolean rectAnyCornersInCircle(final double rMinX, final double rMinY, final double rMaxX,
+ final double rMaxY, final double centerLon, final double centerLat,
+ final double radiusMeters, final boolean approx) {
+ if (approx == true) {
+ return rectAnyCornersInCircleSloppy(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters);
+ }
+ double w = Math.abs(rMaxX - rMinX);
+ if (w <= 90.0) {
+ return GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMinX) <= radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMinX) <= radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMaxX) <= radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMaxX) <= radiusMeters;
+ }
+ // partition
+ w /= 4;
+ final double p1 = rMinX + w;
+ final double p2 = p1 + w;
+ final double p3 = p2 + w;
+
+ return GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMinX) <= radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMinX) <= radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, p1) <= radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, p1) <= radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, p2) <= radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, p2) <= radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, p3) <= radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, p3) <= radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMaxX) <= radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMaxX) <= radiusMeters;
+ }
+
+ private static boolean rectAnyCornersInCircleSloppy(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+ final double centerLon, final double centerLat, final double radiusMeters) {
+ return SloppyMath.haversin(centerLat, centerLon, rMinY, rMinX)*1000.0 <= radiusMeters
+ || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMinX)*1000.0 <= radiusMeters
+ || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMaxX)*1000.0 <= radiusMeters
+ || SloppyMath.haversin(centerLat, centerLon, rMinY, rMaxX)*1000.0 <= radiusMeters;
+ }
+
+ /**
+ * Compute whether any of the 4 corners of the rectangle (defined by min/max X/Y) are outside the circle (defined
+ * by centerLon, centerLat, radiusMeters)
+ *
+ * Note: exotic rectangles at the poles (e.g., those whose lon/lat distance ratios greatly deviate from 1) can not
+ * be determined by using distance alone. For this reason the approx flag may be set to false, in which case the
+ * space will be further divided to more accurately compute whether the rectangle crosses the circle
+ */
+ private static boolean rectAnyCornersOutsideCircle(final double rMinX, final double rMinY, final double rMaxX,
+ final double rMaxY, final double centerLon, final double centerLat,
+ final double radiusMeters, final boolean approx) {
+ if (approx == true) {
+ return rectAnyCornersOutsideCircleSloppy(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters);
+ }
+ // if span is less than 70 degrees we can approximate using distance alone
+ if (Math.abs(rMaxX - rMinX) <= 70.0) {
+ return GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMinX) > radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMinX) > radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMaxX) > radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMaxX) > radiusMeters;
+ }
+ return rectCrossesOblateCircle(centerLon, centerLat, radiusMeters, rMinX, rMinY, rMaxX, rMaxY);
+ }
+
+ /**
+ * Compute whether the rectangle (defined by min/max Lon/Lat) crosses a potentially oblate circle
+ *
+ * TODO benchmark for replacing existing rectCrossesCircle.
+ */
+ public static boolean rectCrossesOblateCircle(double centerLon, double centerLat, double radiusMeters, double rMinLon, double rMinLat, double rMaxLon, double rMaxLat) {
+ double w = Math.abs(rMaxLon - rMinLon);
+ final int segs = (int)Math.ceil(w / 45.0);
+ w /= segs;
+ short i = 1;
+ double p1 = rMinLon;
+ double maxLon, midLon;
+ double[] pt = new double[2];
+
+ do {
+ maxLon = (i == segs) ? rMaxLon : p1 + w;
+
+ final double d1, d2;
+ // short-circuit if we find a corner outside the circle
+ if ( (d1 = GeoDistanceUtils.haversin(centerLat, centerLon, rMinLat, p1)) > radiusMeters
+ || (d2 = GeoDistanceUtils.haversin(centerLat, centerLon, rMinLat, maxLon)) > radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxLat, p1) > radiusMeters
+ || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxLat, maxLon) > radiusMeters) {
+ return true;
+ }
+
+ // else we treat as an oblate circle by slicing the longitude space and checking the azimuthal range
+ // OPTIMIZATION: this is only executed for latitude values "closeTo" the poles (e.g., 88.0 > lat < -88.0)
+ if ( (rMaxLat > 88.0 || rMinLat < -88.0)
+ && (pt = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(p1, rMinLat,
+ GeoProjectionUtils.bearingGreatCircle(p1, rMinLat, p1, rMaxLat), radiusMeters - d1, pt))[1] < rMinLat || pt[1] < rMaxLat
+ || (pt = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(maxLon, rMinLat,
+ GeoProjectionUtils.bearingGreatCircle(maxLon, rMinLat, maxLon, rMaxLat), radiusMeters - d2, pt))[1] < rMinLat || pt[1] < rMaxLat
+ || (pt = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(maxLon, rMinLat,
+ GeoProjectionUtils.bearingGreatCircle(maxLon, rMinLat, (midLon = p1 + 0.5*(maxLon - p1)), rMaxLat),
+ radiusMeters - GeoDistanceUtils.haversin(centerLat, centerLon, rMinLat, midLon), pt))[1] < rMinLat
+ || pt[1] < rMaxLat == false ) {
+ return true;
+ }
+ p1 += w;
+ } while (++i <= segs);
+ return false;
+ }
+
+ private static boolean rectAnyCornersOutsideCircleSloppy(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+ final double centerLon, final double centerLat, final double radiusMeters) {
+ return SloppyMath.haversin(centerLat, centerLon, rMinY, rMinX)*1000.0 > radiusMeters
+ || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMinX)*1000.0 > radiusMeters
+ || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMaxX)*1000.0 > radiusMeters
+ || SloppyMath.haversin(centerLat, centerLon, rMinY, rMaxX)*1000.0 > radiusMeters;
+ }
+
+ /**
+ * Convenience method for computing whether a rectangle is within a circle using additional precision checks
+ */
+ public static boolean rectWithinCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+ final double centerLon, final double centerLat, final double radiusMeters) {
+ return rectWithinCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, false);
+ }
+
+ /**
+ * Computes whether a rectangle is within a circle. Note: approx == true will be faster but less precise and may
+ * fail on large rectangles
+ */
+ public static boolean rectWithinCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+ final double centerLon, final double centerLat, final double radiusMeters,
+ final boolean approx) {
+ return rectAnyCornersOutsideCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx) == false;
+ }
+
+ /**
+ * Determine if a bbox (defined by minLon, minLat, maxLon, maxLat) contains the provided point (defined by lon, lat)
+ * NOTE: this is basic method that does not handle dateline or pole crossing. Unwrapping must be done before
+ * calling this method.
+ */
+ public static boolean rectCrossesCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+ final double centerLon, final double centerLat, final double radiusMeters) {
+ return rectCrossesCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, false);
+ }
+
+ /**
+ * Computes whether a rectangle crosses a circle. Note: approx == true will be faster but less precise and may
+ * fail on large rectangles
+ */
+ public static boolean rectCrossesCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+ final double centerLon, final double centerLat, final double radiusMeters,
+ final boolean approx) {
+ if (approx == true) {
+ return rectAnyCornersInCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx)
+ || isClosestPointOnRectWithinRange(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx);
+ }
+
+ return (rectAnyCornersInCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx) &&
+ rectAnyCornersOutsideCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx))
+ || isClosestPointOnRectWithinRange(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx);
+ }
+
+ private static boolean isClosestPointOnRectWithinRange(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+ final double centerLon, final double centerLat, final double radiusMeters,
+ final boolean approx) {
+ double[] closestPt = {0, 0};
+ GeoDistanceUtils.closestPointOnBBox(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, closestPt);
+ boolean haverShortCut = GeoDistanceUtils.haversin(centerLat, centerLon, closestPt[1], closestPt[0]) <= radiusMeters;
+ if (approx == true || haverShortCut == true) {
+ return haverShortCut;
+ }
+ double lon1 = rMinX;
+ double lon2 = rMaxX;
+ double lat1 = rMinY;
+ double lat2 = rMaxY;
+ if (closestPt[0] == rMinX || closestPt[0] == rMaxX) {
+ lon1 = closestPt[0];
+ lon2 = lon1;
+ } else if (closestPt[1] == rMinY || closestPt[1] == rMaxY) {
+ lat1 = closestPt[1];
+ lat2 = lat1;
+ }
+
+ return lineCrossesSphere(lon1, lat1, 0, lon2, lat2, 0, centerLon, centerLat, 0, radiusMeters);
+ }
+
+ /**
+ * Computes whether or a 3dimensional line segment intersects or crosses a sphere
+ *
+ * @param lon1 longitudinal location of the line segment start point (in degrees)
+ * @param lat1 latitudinal location of the line segment start point (in degrees)
+ * @param alt1 altitude of the line segment start point (in degrees)
+ * @param lon2 longitudinal location of the line segment end point (in degrees)
+ * @param lat2 latitudinal location of the line segment end point (in degrees)
+ * @param alt2 altitude of the line segment end point (in degrees)
+ * @param centerLon longitudinal location of center search point (in degrees)
+ * @param centerLat latitudinal location of center search point (in degrees)
+ * @param centerAlt altitude of the center point (in meters)
+ * @param radiusMeters search sphere radius (in meters)
+ * @return whether the provided line segment is a secant of the
+ */
+ private static boolean lineCrossesSphere(double lon1, double lat1, double alt1, double lon2,
+ double lat2, double alt2, double centerLon, double centerLat,
+ double centerAlt, double radiusMeters) {
+ // convert to cartesian 3d (in meters)
+ double[] ecf1 = GeoProjectionUtils.llaToECF(lon1, lat1, alt1, null);
+ double[] ecf2 = GeoProjectionUtils.llaToECF(lon2, lat2, alt2, null);
+ double[] cntr = GeoProjectionUtils.llaToECF(centerLon, centerLat, centerAlt, null);
+
+ // convert radius from arc radius to cartesian radius
+ double[] oneEighty = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(centerLon, centerLat, 180.0d, radiusMeters, new double[3]);
+ GeoProjectionUtils.llaToECF(oneEighty[0], oneEighty[1], 0, oneEighty);
+
+ radiusMeters = GeoDistanceUtils.linearDistance(oneEighty, cntr);// Math.sqrt(oneEighty[0]*cntr[0] + oneEighty[1]*cntr[1] + oneEighty[2]*cntr[2]);
+
+ final double dX = ecf2[0] - ecf1[0];
+ final double dY = ecf2[1] - ecf1[1];
+ final double dZ = ecf2[2] - ecf1[2];
+ final double fX = ecf1[0] - cntr[0];
+ final double fY = ecf1[1] - cntr[1];
+ final double fZ = ecf1[2] - cntr[2];
+
+ final double a = dX*dX + dY*dY + dZ*dZ;
+ final double b = 2 * (fX*dX + fY*dY + fZ*dZ);
+ final double c = (fX*fX + fY*fY + fZ*fZ) - (radiusMeters*radiusMeters);
+
+ double discrim = (b*b)-(4*a*c);
+ if (discrim < 0) {
+ return false;
+ }
+
+ discrim = StrictMath.sqrt(discrim);
+ final double a2 = 2*a;
+ final double t1 = (-b - discrim)/a2;
+ final double t2 = (-b + discrim)/a2;
+
+ if ( (t1 < 0 || t1 > 1) ) {
+ return !(t2 < 0 || t2 > 1);
+ }
+
+ return true;
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoUtils.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoUtils.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoUtils.java
new file mode 100644
index 0000000..8899170
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoUtils.java
@@ -0,0 +1,248 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.spatial.util;
+
+import java.util.ArrayList;
+
+import org.apache.lucene.util.BitUtil;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+import static java.lang.Math.PI;
+import static java.lang.Math.abs;
+
+import static org.apache.lucene.util.SloppyMath.asin;
+import static org.apache.lucene.util.SloppyMath.cos;
+import static org.apache.lucene.util.SloppyMath.sin;
+import static org.apache.lucene.util.SloppyMath.TO_DEGREES;
+import static org.apache.lucene.util.SloppyMath.TO_RADIANS;
+import static org.apache.lucene.spatial.util.GeoProjectionUtils.MAX_LAT_RADIANS;
+import static org.apache.lucene.spatial.util.GeoProjectionUtils.MAX_LON_RADIANS;
+import static org.apache.lucene.spatial.util.GeoProjectionUtils.MIN_LAT_RADIANS;
+import static org.apache.lucene.spatial.util.GeoProjectionUtils.MIN_LON_RADIANS;
+import static org.apache.lucene.spatial.util.GeoProjectionUtils.pointFromLonLatBearingGreatCircle;
+import static org.apache.lucene.spatial.util.GeoProjectionUtils.SEMIMAJOR_AXIS;
+
+/**
+ * Basic reusable geo-spatial utility methods
+ *
+ * @lucene.experimental
+ */
+public final class GeoUtils {
+ /** number of bits used for quantizing latitude and longitude values */
+ public static final short BITS = 31;
+ private static final double LON_SCALE = (0x1L<<BITS)/360.0D;
+ private static final double LAT_SCALE = (0x1L<<BITS)/180.0D;
+ /** rounding error for quantized latitude and longitude values */
+ public static final double TOLERANCE = 1E-6;
+
+ /** Minimum longitude value. */
+ public static final double MIN_LON_INCL = -180.0D;
+
+ /** Maximum longitude value. */
+ public static final double MAX_LON_INCL = 180.0D;
+
+ /** Minimum latitude value. */
+ public static final double MIN_LAT_INCL = -90.0D;
+
+ /** Maximum latitude value. */
+ public static final double MAX_LAT_INCL = 90.0D;
+
+ // No instance:
+ private GeoUtils() {
+ }
+
+ /**
+ * encode longitude, latitude geopoint values using morton encoding method
+ * https://en.wikipedia.org/wiki/Z-order_curve
+ */
+ public static final Long mortonHash(final double lon, final double lat) {
+ return BitUtil.interleave(scaleLon(lon), scaleLat(lat));
+ }
+
+ /** decode longitude value from morton encoded geo point */
+ public static final double mortonUnhashLon(final long hash) {
+ return unscaleLon(BitUtil.deinterleave(hash));
+ }
+
+ /** decode latitude value from morton encoded geo point */
+ public static final double mortonUnhashLat(final long hash) {
+ return unscaleLat(BitUtil.deinterleave(hash >>> 1));
+ }
+
+ private static final long scaleLon(final double val) {
+ return (long) ((val-MIN_LON_INCL) * LON_SCALE);
+ }
+
+ private static final long scaleLat(final double val) {
+ return (long) ((val-MIN_LAT_INCL) * LAT_SCALE);
+ }
+
+ private static final double unscaleLon(final long val) {
+ return (val / LON_SCALE) + MIN_LON_INCL;
+ }
+
+ private static final double unscaleLat(final long val) {
+ return (val / LAT_SCALE) + MIN_LAT_INCL;
+ }
+
+ /** Compare two position values within a {@link GeoUtils#TOLERANCE} factor */
+ public static double compare(final double v1, final double v2) {
+ final double delta = v1-v2;
+ return abs(delta) <= TOLERANCE ? 0 : delta;
+ }
+
+ /** Puts longitude in range of -180 to +180. */
+ public static double normalizeLon(double lon_deg) {
+ if (lon_deg >= -180 && lon_deg <= 180) {
+ return lon_deg; //common case, and avoids slight double precision shifting
+ }
+ double off = (lon_deg + 180) % 360;
+ if (off < 0) {
+ return 180 + off;
+ } else if (off == 0 && lon_deg > 0) {
+ return 180;
+ } else {
+ return -180 + off;
+ }
+ }
+
+ /** Puts latitude in range of -90 to 90. */
+ public static double normalizeLat(double lat_deg) {
+ if (lat_deg >= -90 && lat_deg <= 90) {
+ return lat_deg; //common case, and avoids slight double precision shifting
+ }
+ double off = abs((lat_deg + 90) % 360);
+ return (off <= 180 ? off : 360-off) - 90;
+ }
+
+ /** Converts long value to bit string (useful for debugging) */
+ public static String geoTermToString(long term) {
+ StringBuilder s = new StringBuilder(64);
+ final int numberOfLeadingZeros = Long.numberOfLeadingZeros(term);
+ for (int i = 0; i < numberOfLeadingZeros; i++) {
+ s.append('0');
+ }
+ if (term != 0) {
+ s.append(Long.toBinaryString(term));
+ }
+ return s.toString();
+ }
+
+ /**
+ * Converts a given circle (defined as a point/radius) to an approximated line-segment polygon
+ *
+ * @param lon longitudinal center of circle (in degrees)
+ * @param lat latitudinal center of circle (in degrees)
+ * @param radiusMeters distance radius of circle (in meters)
+ * @return a list of lon/lat points representing the circle
+ */
+ @SuppressWarnings({"unchecked","rawtypes"})
+ public static ArrayList<double[]> circleToPoly(final double lon, final double lat, final double radiusMeters) {
+ double angle;
+ // a little under-sampling (to limit the number of polygonal points): using archimedes estimation of pi
+ final int sides = 25;
+ ArrayList<double[]> geometry = new ArrayList();
+ double[] lons = new double[sides];
+ double[] lats = new double[sides];
+
+ double[] pt = new double[2];
+ final int sidesLen = sides-1;
+ for (int i=0; i<sidesLen; ++i) {
+ angle = (i*360/sides);
+ pt = pointFromLonLatBearingGreatCircle(lon, lat, angle, radiusMeters, pt);
+ lons[i] = pt[0];
+ lats[i] = pt[1];
+ }
+ // close the poly
+ lons[sidesLen] = lons[0];
+ lats[sidesLen] = lats[0];
+ geometry.add(lons);
+ geometry.add(lats);
+
+ return geometry;
+ }
+
+ /** Compute Bounding Box for a circle using WGS-84 parameters */
+ public static GeoRect circleToBBox(final double centerLon, final double centerLat, final double radiusMeters) {
+ final double radLat = TO_RADIANS * centerLat;
+ final double radLon = TO_RADIANS * centerLon;
+ double radDistance = radiusMeters / SEMIMAJOR_AXIS;
+ double minLat = radLat - radDistance;
+ double maxLat = radLat + radDistance;
+ double minLon;
+ double maxLon;
+
+ if (minLat > MIN_LAT_RADIANS && maxLat < MAX_LAT_RADIANS) {
+ double deltaLon = asin(sin(radDistance) / cos(radLat));
+ minLon = radLon - deltaLon;
+ if (minLon < MIN_LON_RADIANS) {
+ minLon += 2d * PI;
+ }
+ maxLon = radLon + deltaLon;
+ if (maxLon > MAX_LON_RADIANS) {
+ maxLon -= 2d * PI;
+ }
+ } else {
+ // a pole is within the distance
+ minLat = max(minLat, MIN_LAT_RADIANS);
+ maxLat = min(maxLat, MAX_LAT_RADIANS);
+ minLon = MIN_LON_RADIANS;
+ maxLon = MAX_LON_RADIANS;
+ }
+
+ return new GeoRect(TO_DEGREES * minLon, TO_DEGREES * maxLon, TO_DEGREES * minLat, TO_DEGREES * maxLat);
+ }
+
+ /** Compute Bounding Box for a polygon using WGS-84 parameters */
+ public static GeoRect polyToBBox(double[] polyLons, double[] polyLats) {
+ if (polyLons.length != polyLats.length) {
+ throw new IllegalArgumentException("polyLons and polyLats must be equal length");
+ }
+
+ double minLon = Double.POSITIVE_INFINITY;
+ double maxLon = Double.NEGATIVE_INFINITY;
+ double minLat = Double.POSITIVE_INFINITY;
+ double maxLat = Double.NEGATIVE_INFINITY;
+
+ for (int i=0;i<polyLats.length;i++) {
+ if (GeoUtils.isValidLon(polyLons[i]) == false) {
+ throw new IllegalArgumentException("invalid polyLons[" + i + "]=" + polyLons[i]);
+ }
+ if (GeoUtils.isValidLat(polyLats[i]) == false) {
+ throw new IllegalArgumentException("invalid polyLats[" + i + "]=" + polyLats[i]);
+ }
+ minLon = min(polyLons[i], minLon);
+ maxLon = max(polyLons[i], maxLon);
+ minLat = min(polyLats[i], minLat);
+ maxLat = max(polyLats[i], maxLat);
+ }
+ // expand bounding box by TOLERANCE factor to handle round-off error
+ return new GeoRect(max(minLon - TOLERANCE, MIN_LON_INCL), min(maxLon + TOLERANCE, MAX_LON_INCL),
+ max(minLat - TOLERANCE, MIN_LAT_INCL), min(maxLat + TOLERANCE, MAX_LAT_INCL));
+ }
+
+ /** validates latitude value is within standard +/-90 coordinate bounds */
+ public static boolean isValidLat(double lat) {
+ return Double.isNaN(lat) == false && lat >= MIN_LAT_INCL && lat <= MAX_LAT_INCL;
+ }
+
+ /** validates longitude value is within standard +/-180 coordinate bounds */
+ public static boolean isValidLon(double lon) {
+ return Double.isNaN(lon) == false && lon >= MIN_LON_INCL && lon <= MAX_LON_INCL;
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/test/org/apache/lucene/spatial/search/TestGeoPointQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/search/TestGeoPointQuery.java b/lucene/spatial/src/test/org/apache/lucene/spatial/search/TestGeoPointQuery.java
new file mode 100644
index 0000000..4002531
--- /dev/null
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/search/TestGeoPointQuery.java
@@ -0,0 +1,389 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.spatial.search;
+
+import org.apache.lucene.analysis.MockAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FieldType;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.spatial.document.GeoPointField;
+import org.apache.lucene.spatial.util.BaseGeoPointTestCase;
+import org.apache.lucene.spatial.util.GeoRect;
+import org.apache.lucene.spatial.util.GeoRelationUtils;
+import org.apache.lucene.spatial.util.GeoUtils;
+import org.apache.lucene.util.SloppyMath;
+import org.apache.lucene.util.TestUtil;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+import static org.apache.lucene.spatial.util.GeoDistanceUtils.DISTANCE_PCT_ERR;
+
+/**
+ * Unit testing for basic GeoPoint query logic
+ *
+ * @lucene.experimental
+ */
+
+public class TestGeoPointQuery extends BaseGeoPointTestCase {
+
+ private static Directory directory = null;
+ private static IndexReader reader = null;
+ private static IndexSearcher searcher = null;
+
+ @Override
+ protected boolean forceSmall() {
+ return false;
+ }
+
+ @Override
+ protected void addPointToDoc(String field, Document doc, double lat, double lon) {
+ doc.add(new GeoPointField(field, lon, lat, Field.Store.NO));
+ }
+
+ @Override
+ protected Query newRectQuery(String field, GeoRect rect) {
+ return new GeoPointInBBoxQuery(field, rect.minLon, rect.minLat, rect.maxLon, rect.maxLat);
+ }
+
+ @Override
+ protected Query newDistanceQuery(String field, double centerLat, double centerLon, double radiusMeters) {
+ return new GeoPointDistanceQuery(field, centerLon, centerLat, radiusMeters);
+ }
+
+ @Override
+ protected Query newDistanceRangeQuery(String field, double centerLat, double centerLon, double minRadiusMeters, double radiusMeters) {
+ return new GeoPointDistanceRangeQuery(field, centerLon, centerLat, minRadiusMeters, radiusMeters);
+ }
+
+ @Override
+ protected Query newPolygonQuery(String field, double[] lats, double[] lons) {
+ return new GeoPointInPolygonQuery(field, lons, lats);
+ }
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ directory = newDirectory();
+
+ RandomIndexWriter writer = new RandomIndexWriter(random(), directory,
+ newIndexWriterConfig(new MockAnalyzer(random()))
+ .setMaxBufferedDocs(TestUtil.nextInt(random(), 100, 1000))
+ .setMergePolicy(newLogMergePolicy()));
+
+ // create some simple geo points
+ final FieldType storedPoint = new FieldType(GeoPointField.TYPE_STORED);
+ // this is a simple systematic test
+ GeoPointField[] pts = new GeoPointField[] {
+ new GeoPointField(FIELD_NAME, -96.774, 32.763420, storedPoint),
+ new GeoPointField(FIELD_NAME, -96.7759895324707, 32.7559529921407, storedPoint),
+ new GeoPointField(FIELD_NAME, -96.77701950073242, 32.77866942010977, storedPoint),
+ new GeoPointField(FIELD_NAME, -96.7706036567688, 32.7756745755423, storedPoint),
+ new GeoPointField(FIELD_NAME, -139.73458170890808, 27.703618681345585, storedPoint),
+ new GeoPointField(FIELD_NAME, -96.4538113027811, 32.94823588839368, storedPoint),
+ new GeoPointField(FIELD_NAME, -96.65084838867188, 33.06047141970814, storedPoint),
+ new GeoPointField(FIELD_NAME, -96.7772, 32.778650, storedPoint),
+ new GeoPointField(FIELD_NAME, -177.23537676036358, -88.56029371730983, storedPoint),
+ new GeoPointField(FIELD_NAME, -26.779373834241003, 33.541429799076354, storedPoint),
+ new GeoPointField(FIELD_NAME, -77.35379276106497, 26.774024500421728, storedPoint),
+ new GeoPointField(FIELD_NAME, -14.796283808944777, -90.0, storedPoint),
+ new GeoPointField(FIELD_NAME, -178.8538113027811, 32.94823588839368, storedPoint),
+ new GeoPointField(FIELD_NAME, 178.8538113027811, 32.94823588839368, storedPoint),
+ new GeoPointField(FIELD_NAME, -73.998776, 40.720611, storedPoint),
+ new GeoPointField(FIELD_NAME, -179.5, -44.5, storedPoint)};
+
+ for (GeoPointField p : pts) {
+ Document doc = new Document();
+ doc.add(p);
+ writer.addDocument(doc);
+ }
+
+ // add explicit multi-valued docs
+ for (int i=0; i<pts.length; i+=2) {
+ Document doc = new Document();
+ doc.add(pts[i]);
+ doc.add(pts[i+1]);
+ writer.addDocument(doc);
+ }
+
+ // index random string documents
+ for (int i=0; i<random().nextInt(10); ++i) {
+ Document doc = new Document();
+ doc.add(new StringField("string", Integer.toString(i), Field.Store.NO));
+ writer.addDocument(doc);
+ }
+
+ reader = writer.getReader();
+ searcher = newSearcher(reader);
+ writer.close();
+ }
+
+ @AfterClass
+ public static void afterClass() throws Exception {
+ searcher = null;
+ reader.close();
+ reader = null;
+ directory.close();
+ directory = null;
+ }
+
+ private TopDocs bboxQuery(double minLon, double minLat, double maxLon, double maxLat, int limit) throws Exception {
+ GeoPointInBBoxQuery q = new GeoPointInBBoxQuery(FIELD_NAME, minLon, minLat, maxLon, maxLat);
+ return searcher.search(q, limit);
+ }
+
+ private TopDocs polygonQuery(double[] lon, double[] lat, int limit) throws Exception {
+ GeoPointInPolygonQuery q = new GeoPointInPolygonQuery(FIELD_NAME, lon, lat);
+ return searcher.search(q, limit);
+ }
+
+ private TopDocs geoDistanceQuery(double lon, double lat, double radius, int limit) throws Exception {
+ GeoPointDistanceQuery q = new GeoPointDistanceQuery(FIELD_NAME, lon, lat, radius);
+ return searcher.search(q, limit);
+ }
+
+ @Override
+ protected Boolean rectContainsPoint(GeoRect rect, double pointLat, double pointLon) {
+ if (GeoUtils.compare(pointLon, rect.minLon) == 0.0 ||
+ GeoUtils.compare(pointLon, rect.maxLon) == 0.0 ||
+ GeoUtils.compare(pointLat, rect.minLat) == 0.0 ||
+ GeoUtils.compare(pointLat, rect.maxLat) == 0.0) {
+ // Point is very close to rect boundary
+ return null;
+ }
+
+ if (rect.minLon < rect.maxLon) {
+ return GeoRelationUtils.pointInRectPrecise(pointLon, pointLat, rect.minLon, rect.minLat, rect.maxLon, rect.maxLat);
+ } else {
+ // Rect crosses dateline:
+ return GeoRelationUtils.pointInRectPrecise(pointLon, pointLat, -180.0, rect.minLat, rect.maxLon, rect.maxLat)
+ || GeoRelationUtils.pointInRectPrecise(pointLon, pointLat, rect.minLon, rect.minLat, 180.0, rect.maxLat);
+ }
+ }
+
+ @Override
+ protected Boolean polyRectContainsPoint(GeoRect rect, double pointLat, double pointLon) {
+ return rectContainsPoint(rect, pointLat, pointLon);
+ }
+
+ @Override
+ protected Boolean circleContainsPoint(double centerLat, double centerLon, double radiusMeters, double pointLat, double pointLon) {
+ if (radiusQueryCanBeWrong(centerLat, centerLon, pointLon, pointLat, radiusMeters)) {
+ return null;
+ } else {
+ return SloppyMath.haversin(centerLat, centerLon, pointLat, pointLon)*1000.0 <= radiusMeters;
+ }
+ }
+
+ @Override
+ protected Boolean distanceRangeContainsPoint(double centerLat, double centerLon, double minRadiusMeters, double radiusMeters, double pointLat, double pointLon) {
+ if (radiusQueryCanBeWrong(centerLat, centerLon, pointLon, pointLat, minRadiusMeters)
+ || radiusQueryCanBeWrong(centerLat, centerLon, pointLon, pointLat, radiusMeters)) {
+ return null;
+ } else {
+ final double d = SloppyMath.haversin(centerLat, centerLon, pointLat, pointLon)*1000.0;
+ return d >= minRadiusMeters && d <= radiusMeters;
+ }
+ }
+
+ private static boolean radiusQueryCanBeWrong(double centerLat, double centerLon, double ptLon, double ptLat,
+ final double radius) {
+ final long hashedCntr = GeoUtils.mortonHash(centerLon, centerLat);
+ centerLon = GeoUtils.mortonUnhashLon(hashedCntr);
+ centerLat = GeoUtils.mortonUnhashLat(hashedCntr);
+ final long hashedPt = GeoUtils.mortonHash(ptLon, ptLat);
+ ptLon = GeoUtils.mortonUnhashLon(hashedPt);
+ ptLat = GeoUtils.mortonUnhashLat(hashedPt);
+
+ double ptDistance = SloppyMath.haversin(centerLat, centerLon, ptLat, ptLon)*1000.0;
+ double delta = StrictMath.abs(ptDistance - radius);
+
+ // if its within the distance error then it can be wrong
+ return delta < (ptDistance*DISTANCE_PCT_ERR);
+ }
+
+ public void testRectCrossesCircle() throws Exception {
+ assertTrue(GeoRelationUtils.rectCrossesCircle(-180, -90, 180, 0.0, 0.667, 0.0, 88000.0));
+ }
+
+ private TopDocs geoDistanceRangeQuery(double lon, double lat, double minRadius, double maxRadius, int limit)
+ throws Exception {
+ GeoPointDistanceRangeQuery q = new GeoPointDistanceRangeQuery(FIELD_NAME, lon, lat, minRadius, maxRadius);
+ return searcher.search(q, limit);
+ }
+
+ public void testBBoxQuery() throws Exception {
+ TopDocs td = bboxQuery(-96.7772, 32.778650, -96.77690000, 32.778950, 5);
+ assertEquals("GeoBoundingBoxQuery failed", 4, td.totalHits);
+ }
+
+ public void testPolyQuery() throws Exception {
+ TopDocs td = polygonQuery(new double[]{-96.7682647, -96.8280029, -96.6288757, -96.4929199,
+ -96.6041564, -96.7449188, -96.76826477, -96.7682647},
+ new double[]{33.073130, 32.9942669, 32.938386, 33.0374494,
+ 33.1369762, 33.1162747, 33.073130, 33.073130}, 5);
+ assertEquals("GeoPolygonQuery failed", 2, td.totalHits);
+ }
+
+ public void testPacManPolyQuery() throws Exception {
+ // pacman
+ double[] px = {0, 10, 10, 0, -8, -10, -8, 0, 10, 10, 0};
+ double[] py = {0, 5, 9, 10, 9, 0, -9, -10, -9, -5, 0};
+
+ // shape bbox
+ double xMinA = -10;
+ double xMaxA = 10;
+ double yMinA = -10;
+ double yMaxA = 10;
+
+ // candidate crosses cell
+ double xMin = 2;//-5;
+ double xMax = 11;//0.000001;
+ double yMin = -1;//0;
+ double yMax = 1;//5;
+
+ // test cell crossing poly
+ assertTrue(GeoRelationUtils.rectCrossesPolyApprox(xMin, yMin, xMax, yMax, px, py, xMinA, yMinA, xMaxA, yMaxA));
+ assertFalse(GeoRelationUtils.rectCrossesPolyApprox(-5, 0, 0.000001, 5, px, py, xMin, yMin, xMax, yMax));
+ assertTrue(GeoRelationUtils.rectWithinPolyApprox(-5, 0, -2, 5, px, py, xMin, yMin, xMax, yMax));
+ }
+
+ public void testBBoxCrossDateline() throws Exception {
+ TopDocs td = bboxQuery(179.0, -45.0, -179.0, -44.0, 20);
+ assertEquals("BBoxCrossDateline query failed", 2, td.totalHits);
+ }
+
+ public void testWholeMap() throws Exception {
+ TopDocs td = bboxQuery(GeoUtils.MIN_LON_INCL, GeoUtils.MIN_LAT_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MAX_LAT_INCL, 20);
+ assertEquals("testWholeMap failed", 24, td.totalHits);
+ td = polygonQuery(new double[] {GeoUtils.MIN_LON_INCL, GeoUtils.MIN_LON_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MIN_LON_INCL},
+ new double[] {GeoUtils.MIN_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MIN_LAT_INCL, GeoUtils.MIN_LAT_INCL}, 20);
+ assertEquals("testWholeMap failed", 24, td.totalHits);
+ }
+
+ public void smallTest() throws Exception {
+ TopDocs td = geoDistanceQuery(-73.998776, 40.720611, 1, 20);
+ assertEquals("smallTest failed", 2, td.totalHits);
+ }
+
+ public void testInvalidBBox() throws Exception {
+ try {
+ bboxQuery(179.0, -92.0, 181.0, -91.0, 20);
+ } catch(Exception e) {
+ return;
+ }
+ throw new Exception("GeoBoundingBox should not accept invalid lat/lon");
+ }
+
+ public void testGeoDistanceQuery() throws Exception {
+ TopDocs td = geoDistanceQuery(-96.4538113027811, 32.94823588839368, 6000, 20);
+ assertEquals("GeoDistanceQuery failed", 2, td.totalHits);
+ }
+
+ /** see https://issues.apache.org/jira/browse/LUCENE-6905 */
+ public void testNonEmptyTermsEnum() throws Exception {
+ TopDocs td = geoDistanceQuery(-177.23537676036358, -88.56029371730983, 7757.999232959935, 20);
+ assertEquals("GeoDistanceQuery failed", 2, td.totalHits);
+ }
+
+ public void testMultiValuedQuery() throws Exception {
+ TopDocs td = bboxQuery(-96.4538113027811, 32.7559529921407, -96.7706036567688, 32.7756745755423, 20);
+ // 3 single valued docs + 2 multi-valued docs
+ assertEquals("testMultiValuedQuery failed", 5, td.totalHits);
+ }
+
+ public void testTooBigRadius() throws Exception {
+ try {
+ geoDistanceQuery(0.0, 85.0, 4000000, 20);
+ } catch (IllegalArgumentException e) {
+ e.getMessage().contains("exceeds maxRadius");
+ }
+ }
+
+ /**
+ * Explicitly large
+ */
+ public void testGeoDistanceQueryHuge() throws Exception {
+ TopDocs td = geoDistanceQuery(-96.4538113027811, 32.94823588839368, 6000000, 20);
+ assertEquals("GeoDistanceQuery failed", 16, td.totalHits);
+ }
+
+ public void testGeoDistanceQueryCrossDateline() throws Exception {
+ TopDocs td = geoDistanceQuery(-179.9538113027811, 32.94823588839368, 120000, 20);
+ assertEquals("GeoDistanceQuery failed", 3, td.totalHits);
+ }
+
+ public void testInvalidGeoDistanceQuery() throws Exception {
+ try {
+ geoDistanceQuery(181.0, 92.0, 120000, 20);
+ } catch (Exception e) {
+ return;
+ }
+ throw new Exception("GeoDistanceQuery should not accept invalid lat/lon as origin");
+ }
+
+ public void testMaxDistanceRangeQuery() throws Exception {
+ TopDocs td = geoDistanceRangeQuery(0.0, 0.0, 10, 20000000, 20);
+ assertEquals("GeoDistanceRangeQuery failed", 24, td.totalHits);
+ }
+
+ public void testMortonEncoding() throws Exception {
+ long hash = GeoUtils.mortonHash(180, 90);
+ assertEquals(180.0, GeoUtils.mortonUnhashLon(hash), 0);
+ assertEquals(90.0, GeoUtils.mortonUnhashLat(hash), 0);
+ }
+
+ public void testEncodeDecode() throws Exception {
+ int iters = atLeast(10000);
+ boolean small = random().nextBoolean();
+ for(int iter=0;iter<iters;iter++) {
+ double lat = randomLat(small);
+ double lon = randomLon(small);
+
+ long enc = GeoUtils.mortonHash(lon, lat);
+ double latEnc = GeoUtils.mortonUnhashLat(enc);
+ double lonEnc = GeoUtils.mortonUnhashLon(enc);
+
+ assertEquals("lat=" + lat + " latEnc=" + latEnc + " diff=" + (lat - latEnc), lat, latEnc, GeoUtils.TOLERANCE);
+ assertEquals("lon=" + lon + " lonEnc=" + lonEnc + " diff=" + (lon - lonEnc), lon, lonEnc, GeoUtils.TOLERANCE);
+ }
+ }
+
+ public void testScaleUnscaleIsStable() throws Exception {
+ int iters = atLeast(1000);
+ boolean small = random().nextBoolean();
+ for(int iter=0;iter<iters;iter++) {
+ double lat = randomLat(small);
+ double lon = randomLon(small);
+
+ long enc = GeoUtils.mortonHash(lon, lat);
+ double latEnc = GeoUtils.mortonUnhashLat(enc);
+ double lonEnc = GeoUtils.mortonUnhashLon(enc);
+
+ long enc2 = GeoUtils.mortonHash(lon, lat);
+ double latEnc2 = GeoUtils.mortonUnhashLat(enc2);
+ double lonEnc2 = GeoUtils.mortonUnhashLon(enc2);
+ assertEquals(latEnc, latEnc2, 0.0);
+ assertEquals(lonEnc, lonEnc2, 0.0);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java b/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java
new file mode 100644
index 0000000..9f6edda
--- /dev/null
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java
@@ -0,0 +1,769 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 java.io.IOException;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.NumericDocValuesField;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.MultiDocValues;
+import org.apache.lucene.index.NumericDocValues;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.SimpleCollector;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.MockDirectoryWrapper;
+import org.apache.lucene.util.FixedBitSet;
+import org.apache.lucene.util.IOUtils;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.SloppyMath;
+import org.apache.lucene.util.TestUtil;
+import org.junit.BeforeClass;
+
+// TODO: cutover TestGeoUtils too?
+
+public abstract class BaseGeoPointTestCase extends LuceneTestCase {
+
+ protected static final String FIELD_NAME = "point";
+
+ private static final double LON_SCALE = (0x1L<<GeoUtils.BITS)/360.0D;
+ private static final double LAT_SCALE = (0x1L<<GeoUtils.BITS)/180.0D;
+
+ private static double originLat;
+ private static double originLon;
+ private static double lonRange;
+ private static double latRange;
+
+ @BeforeClass
+ public static void beforeClassBase() throws Exception {
+ // Between 1.0 and 3.0:
+ lonRange = 2 * (random().nextDouble() + 0.5);
+ latRange = 2 * (random().nextDouble() + 0.5);
+
+ originLon = GeoUtils.normalizeLon(GeoUtils.MIN_LON_INCL + lonRange + (GeoUtils.MAX_LON_INCL - GeoUtils.MIN_LON_INCL - 2 * lonRange) * random().nextDouble());
+ originLat = GeoUtils.normalizeLat(GeoUtils.MIN_LAT_INCL + latRange + (GeoUtils.MAX_LAT_INCL - GeoUtils.MIN_LAT_INCL - 2 * latRange) * random().nextDouble());
+ }
+
+ /** Return true when testing on a non-small region may be too slow (GeoPoint*Query) */
+ protected boolean forceSmall() {
+ return false;
+ }
+
+ // A particularly tricky adversary for BKD tree:
+ public void testSamePointManyTimes() throws Exception {
+
+ // For GeoPointQuery, only run this test nightly:
+ assumeTrue("GeoPoint*Query is too slow otherwise", TEST_NIGHTLY || forceSmall() == false);
+
+ int numPoints = atLeast(1000);
+ boolean small = random().nextBoolean();
+
+ // Every doc has 2 points:
+ double theLat = randomLat(small);
+ double theLon = randomLon(small);
+
+ double[] lats = new double[numPoints];
+ Arrays.fill(lats, theLat);
+
+ double[] lons = new double[numPoints];
+ Arrays.fill(lons, theLon);
+
+ verify(small, lats, lons);
+ }
+
+ public void testAllLatEqual() throws Exception {
+
+ // For GeoPointQuery, only run this test nightly:
+ assumeTrue("GeoPoint*Query is too slow otherwise", TEST_NIGHTLY || forceSmall() == false);
+
+ int numPoints = atLeast(10000);
+ boolean small = forceSmall() || random().nextBoolean();
+ double lat = randomLat(small);
+ double[] lats = new double[numPoints];
+ double[] lons = new double[numPoints];
+
+ boolean haveRealDoc = false;
+
+ for(int docID=0;docID<numPoints;docID++) {
+ int x = random().nextInt(20);
+ if (x == 17) {
+ // Some docs don't have a point:
+ lats[docID] = Double.NaN;
+ if (VERBOSE) {
+ System.out.println(" doc=" + docID + " is missing");
+ }
+ continue;
+ }
+
+ if (docID > 0 && x == 14 && haveRealDoc) {
+ int oldDocID;
+ while (true) {
+ oldDocID = random().nextInt(docID);
+ if (Double.isNaN(lats[oldDocID]) == false) {
+ break;
+ }
+ }
+
+ // Fully identical point:
+ lons[docID] = lons[oldDocID];
+ if (VERBOSE) {
+ System.out.println(" doc=" + docID + " lat=" + lat + " lon=" + lons[docID] + " (same lat/lon as doc=" + oldDocID + ")");
+ }
+ } else {
+ lons[docID] = randomLon(small);
+ haveRealDoc = true;
+ if (VERBOSE) {
+ System.out.println(" doc=" + docID + " lat=" + lat + " lon=" + lons[docID]);
+ }
+ }
+ lats[docID] = lat;
+ }
+
+ verify(small, lats, lons);
+ }
+
+ public void testAllLonEqual() throws Exception {
+
+ // For GeoPointQuery, only run this test nightly:
+ assumeTrue("GeoPoint*Query is too slow otherwise", TEST_NIGHTLY || forceSmall() == false);
+
+ int numPoints = atLeast(10000);
+ boolean small = forceSmall() || random().nextBoolean();
+ double theLon = randomLon(small);
+ double[] lats = new double[numPoints];
+ double[] lons = new double[numPoints];
+
+ boolean haveRealDoc = false;
+
+ //System.out.println("theLon=" + theLon);
+
+ for(int docID=0;docID<numPoints;docID++) {
+ int x = random().nextInt(20);
+ if (x == 17) {
+ // Some docs don't have a point:
+ lats[docID] = Double.NaN;
+ if (VERBOSE) {
+ System.out.println(" doc=" + docID + " is missing");
+ }
+ continue;
+ }
+
+ if (docID > 0 && x == 14 && haveRealDoc) {
+ int oldDocID;
+ while (true) {
+ oldDocID = random().nextInt(docID);
+ if (Double.isNaN(lats[oldDocID]) == false) {
+ break;
+ }
+ }
+
+ // Fully identical point:
+ lats[docID] = lats[oldDocID];
+ if (VERBOSE) {
+ System.out.println(" doc=" + docID + " lat=" + lats[docID] + " lon=" + theLon + " (same lat/lon as doc=" + oldDocID + ")");
+ }
+ } else {
+ lats[docID] = randomLat(small);
+ haveRealDoc = true;
+ if (VERBOSE) {
+ System.out.println(" doc=" + docID + " lat=" + lats[docID] + " lon=" + theLon);
+ }
+ }
+ lons[docID] = theLon;
+ }
+
+ verify(small, lats, lons);
+ }
+
+ public void testMultiValued() throws Exception {
+
+ // For GeoPointQuery, only run this test nightly:
+ assumeTrue("GeoPoint*Query is too slow otherwise", TEST_NIGHTLY || forceSmall() == false);
+
+ int numPoints = atLeast(10000);
+ // Every doc has 2 points:
+ double[] lats = new double[2*numPoints];
+ double[] lons = new double[2*numPoints];
+ Directory dir = newDirectory();
+ noVirusChecker(dir);
+ IndexWriterConfig iwc = newIndexWriterConfig();
+ initIndexWriterConfig(FIELD_NAME, iwc);
+
+ // We rely on docID order:
+ iwc.setMergePolicy(newLogMergePolicy());
+ RandomIndexWriter w = new RandomIndexWriter(random(), dir, iwc);
+
+ boolean small = random().nextBoolean();
+
+ for (int id=0;id<numPoints;id++) {
+ Document doc = new Document();
+ lats[2*id] = randomLat(small);
+ lons[2*id] = randomLon(small);
+ doc.add(newStringField("id", ""+id, Field.Store.YES));
+ addPointToDoc(FIELD_NAME, doc, lats[2*id], lons[2*id]);
+ lats[2*id+1] = randomLat(small);
+ lons[2*id+1] = randomLon(small);
+ addPointToDoc(FIELD_NAME, doc, lats[2*id+1], lons[2*id+1]);
+
+ if (VERBOSE) {
+ System.out.println("id=" + id);
+ System.out.println(" lat=" + lats[2*id] + " lon=" + lons[2*id]);
+ System.out.println(" lat=" + lats[2*id+1] + " lon=" + lons[2*id+1]);
+ }
+ w.addDocument(doc);
+ }
+
+ // TODO: share w/ verify; just need parallel array of the expected ids
+ if (random().nextBoolean()) {
+ w.forceMerge(1);
+ }
+ IndexReader r = w.getReader();
+ w.close();
+
+ // We can't wrap with "exotic" readers because the BKD query must see the BKDDVFormat:
+ IndexSearcher s = newSearcher(r, false);
+
+ int iters = atLeast(75);
+ for (int iter=0;iter<iters;iter++) {
+ GeoRect rect = randomRect(small, small == false);
+
+ if (VERBOSE) {
+ System.out.println("\nTEST: iter=" + iter + " rect=" + rect);
+ }
+
+ Query query = newRectQuery(FIELD_NAME, rect);
+
+ final FixedBitSet hits = new FixedBitSet(r.maxDoc());
+ s.search(query, new SimpleCollector() {
+
+ private int docBase;
+
+ @Override
+ public boolean needsScores() {
+ return false;
+ }
+
+ @Override
+ protected void doSetNextReader(LeafReaderContext context) throws IOException {
+ docBase = context.docBase;
+ }
+
+ @Override
+ public void collect(int doc) {
+ hits.set(docBase+doc);
+ }
+ });
+
+ boolean fail = false;
+
+ for(int docID=0;docID<lats.length/2;docID++) {
+ double latDoc1 = lats[2*docID];
+ double lonDoc1 = lons[2*docID];
+ double latDoc2 = lats[2*docID+1];
+ double lonDoc2 = lons[2*docID+1];
+
+ Boolean result1 = rectContainsPoint(rect, latDoc1, lonDoc1);
+ if (result1 == null) {
+ // borderline case: cannot test
+ continue;
+ }
+
+ Boolean result2 = rectContainsPoint(rect, latDoc2, lonDoc2);
+ if (result2 == null) {
+ // borderline case: cannot test
+ continue;
+ }
+
+ boolean expected = result1 == Boolean.TRUE || result2 == Boolean.TRUE;
+
+ if (hits.get(docID) != expected) {
+ String id = s.doc(docID).get("id");
+ if (expected) {
+ System.out.println(Thread.currentThread().getName() + ": id=" + id + " docID=" + docID + " should match but did not");
+ } else {
+ System.out.println(Thread.currentThread().getName() + ": id=" + id + " docID=" + docID + " should not match but did");
+ }
+ System.out.println(" rect=" + rect);
+ System.out.println(" lat=" + latDoc1 + " lon=" + lonDoc1 + "\n lat=" + latDoc2 + " lon=" + lonDoc2);
+ System.out.println(" result1=" + result1 + " result2=" + result2);
+ fail = true;
+ }
+ }
+
+ if (fail) {
+ fail("some hits were wrong");
+ }
+ }
+ r.close();
+ dir.close();
+ }
+
+ public void testRandomTiny() throws Exception {
+ // Make sure single-leaf-node case is OK:
+ doTestRandom(10);
+ }
+
+ public void testRandomMedium() throws Exception {
+ doTestRandom(10000);
+ }
+
+ @Nightly
+ public void testRandomBig() throws Exception {
+ assumeFalse("Direct codec can OOME on this test", TestUtil.getDocValuesFormat(FIELD_NAME).equals("Direct"));
+ assumeFalse("Memory codec can OOME on this test", TestUtil.getDocValuesFormat(FIELD_NAME).equals("Memory"));
+ doTestRandom(200000);
+ }
+
+ private void doTestRandom(int count) throws Exception {
+
+ int numPoints = atLeast(count);
+
+ if (VERBOSE) {
+ System.out.println("TEST: numPoints=" + numPoints);
+ }
+
+ double[] lats = new double[numPoints];
+ double[] lons = new double[numPoints];
+
+ boolean small = random().nextBoolean();
+
+ boolean haveRealDoc = false;
+
+ for (int id=0;id<numPoints;id++) {
+ int x = random().nextInt(20);
+ if (x == 17) {
+ // Some docs don't have a point:
+ lats[id] = Double.NaN;
+ if (VERBOSE) {
+ System.out.println(" id=" + id + " is missing");
+ }
+ continue;
+ }
+
+ if (id > 0 && x < 3 && haveRealDoc) {
+ int oldID;
+ while (true) {
+ oldID = random().nextInt(id);
+ if (Double.isNaN(lats[oldID]) == false) {
+ break;
+ }
+ }
+
+ if (x == 0) {
+ // Identical lat to old point
+ lats[id] = lats[oldID];
+ lons[id] = randomLon(small);
+ if (VERBOSE) {
+ System.out.println(" id=" + id + " lat=" + lats[id] + " lon=" + lons[id] + " (same lat as doc=" + oldID + ")");
+ }
+ } else if (x == 1) {
+ // Identical lon to old point
+ lats[id] = randomLat(small);
+ lons[id] = lons[oldID];
+ if (VERBOSE) {
+ System.out.println(" id=" + id + " lat=" + lats[id] + " lon=" + lons[id] + " (same lon as doc=" + oldID + ")");
+ }
+ } else {
+ assert x == 2;
+ // Fully identical point:
+ lats[id] = lats[oldID];
+ lons[id] = lons[oldID];
+ if (VERBOSE) {
+ System.out.println(" id=" + id + " lat=" + lats[id] + " lon=" + lons[id] + " (same lat/lon as doc=" + oldID + ")");
+ }
+ }
+ } else {
+ lats[id] = randomLat(small);
+ lons[id] = randomLon(small);
+ haveRealDoc = true;
+ if (VERBOSE) {
+ System.out.println(" id=" + id + " lat=" + lats[id] + " lon=" + lons[id]);
+ }
+ }
+ }
+
+ verify(small, lats, lons);
+ }
+
+ public double randomLat(boolean small) {
+ double result;
+ if (small) {
+ result = GeoUtils.normalizeLat(originLat + latRange * (random().nextDouble() - 0.5));
+ } else {
+ result = -90 + 180.0 * random().nextDouble();
+ }
+ return result;
+ }
+
+ public double randomLon(boolean small) {
+ double result;
+ if (small) {
+ result = GeoUtils.normalizeLon(originLon + lonRange * (random().nextDouble() - 0.5));
+ } else {
+ result = -180 + 360.0 * random().nextDouble();
+ }
+ return result;
+ }
+
+ protected GeoRect randomRect(boolean small, boolean canCrossDateLine) {
+ double lat0 = randomLat(small);
+ double lat1 = randomLat(small);
+ double lon0 = randomLon(small);
+ double lon1 = randomLon(small);
+
+ if (lat1 < lat0) {
+ double x = lat0;
+ lat0 = lat1;
+ lat1 = x;
+ }
+
+ if (canCrossDateLine == false && lon1 < lon0) {
+ double x = lon0;
+ lon0 = lon1;
+ lon1 = x;
+ }
+
+ return new GeoRect(lon0, lon1, lat0, lat1);
+ }
+
+ protected void initIndexWriterConfig(String field, IndexWriterConfig iwc) {
+ }
+
+ protected abstract void addPointToDoc(String field, Document doc, double lat, double lon);
+
+ protected abstract Query newRectQuery(String field, GeoRect bbox);
+
+ protected abstract Query newDistanceQuery(String field, double centerLat, double centerLon, double radiusMeters);
+
+ protected abstract Query newDistanceRangeQuery(String field, double centerLat, double centerLon, double minRadiusMeters, double radiusMeters);
+
+ protected abstract Query newPolygonQuery(String field, double[] lats, double[] lons);
+
+ /** Returns null if it's borderline case */
+ protected abstract Boolean rectContainsPoint(GeoRect rect, double pointLat, double pointLon);
+
+ /** Returns null if it's borderline case */
+ protected abstract Boolean polyRectContainsPoint(GeoRect rect, double pointLat, double pointLon);
+
+ /** Returns null if it's borderline case */
+ protected abstract Boolean circleContainsPoint(double centerLat, double centerLon, double radiusMeters, double pointLat, double pointLon);
+
+ protected abstract Boolean distanceRangeContainsPoint(double centerLat, double centerLon, double minRadiusMeters, double radiusMeters, double pointLat, double pointLon);
+
+ private static abstract class VerifyHits {
+
+ public void test(AtomicBoolean failed, boolean small, IndexSearcher s, NumericDocValues docIDToID, Set<Integer> deleted, Query query, double[] lats, double[] lons) throws Exception {
+ int maxDoc = s.getIndexReader().maxDoc();
+ final FixedBitSet hits = new FixedBitSet(maxDoc);
+ s.search(query, new SimpleCollector() {
+
+ private int docBase;
+
+ @Override
+ public boolean needsScores() {
+ return false;
+ }
+
+ @Override
+ protected void doSetNextReader(LeafReaderContext context) throws IOException {
+ docBase = context.docBase;
+ }
+
+ @Override
+ public void collect(int doc) {
+ hits.set(docBase+doc);
+ }
+ });
+
+ boolean fail = false;
+
+ for(int docID=0;docID<maxDoc;docID++) {
+ int id = (int) docIDToID.get(docID);
+ Boolean expected;
+ if (deleted.contains(id)) {
+ expected = false;
+ } else if (Double.isNaN(lats[id])) {
+ expected = false;
+ } else {
+ expected = shouldMatch(lats[id], lons[id]);
+ }
+
+ // null means it's a borderline case which is allowed to be wrong:
+ if (expected != null && hits.get(docID) != expected) {
+ if (expected) {
+ System.out.println(Thread.currentThread().getName() + ": id=" + id + " should match but did not");
+ } else {
+ System.out.println(Thread.currentThread().getName() + ": id=" + id + " should not match but did");
+ }
+ System.out.println(" small=" + small + " query=" + query +
+ " docID=" + docID + "\n lat=" + lats[id] + " lon=" + lons[id] +
+ "\n deleted?=" + deleted.contains(id));
+ if (Double.isNaN(lats[id]) == false) {
+ describe(docID, lats[id], lons[id]);
+ }
+ fail = true;
+ }
+ }
+
+ if (fail) {
+ failed.set(true);
+ fail("some hits were wrong");
+ }
+ }
+
+ /** Return true if we definitely should match, false if we definitely
+ * should not match, and null if it's a borderline case which might
+ * go either way. */
+ protected abstract Boolean shouldMatch(double lat, double lon);
+
+ protected abstract void describe(int docID, double lat, double lon);
+ }
+
+ protected void verify(boolean small, double[] lats, double[] lons) throws Exception {
+ IndexWriterConfig iwc = newIndexWriterConfig();
+ // Else we can get O(N^2) merging:
+ int mbd = iwc.getMaxBufferedDocs();
+ if (mbd != -1 && mbd < lats.length/100) {
+ iwc.setMaxBufferedDocs(lats.length/100);
+ }
+ Directory dir;
+ if (lats.length > 100000) {
+ dir = newFSDirectory(createTempDir(getClass().getSimpleName()));
+ } else {
+ dir = newDirectory();
+ }
+ noVirusChecker(dir);
+
+ Set<Integer> deleted = new HashSet<>();
+ // RandomIndexWriter is too slow here:
+ IndexWriter w = new IndexWriter(dir, iwc);
+ for(int id=0;id<lats.length;id++) {
+ Document doc = new Document();
+ doc.add(newStringField("id", ""+id, Field.Store.NO));
+ doc.add(new NumericDocValuesField("id", id));
+ if (Double.isNaN(lats[id]) == false) {
+ addPointToDoc(FIELD_NAME, doc, lats[id], lons[id]);
+ }
+ w.addDocument(doc);
+ if (id > 0 && random().nextInt(100) == 42) {
+ int idToDelete = random().nextInt(id);
+ w.deleteDocuments(new Term("id", ""+idToDelete));
+ deleted.add(idToDelete);
+ if (VERBOSE) {
+ System.out.println(" delete id=" + idToDelete);
+ }
+ }
+ }
+
+ if (random().nextBoolean()) {
+ w.forceMerge(1);
+ }
+ final IndexReader r = DirectoryReader.open(w);
+ w.close();
+
+ // We can't wrap with "exotic" readers because the BKD query must see the BKDDVFormat:
+ IndexSearcher s = newSearcher(r, false);
+
+ // Make sure queries are thread safe:
+ int numThreads = TestUtil.nextInt(random(), 2, 5);
+
+ List<Thread> threads = new ArrayList<>();
+ final int iters = atLeast(75);
+
+ final CountDownLatch startingGun = new CountDownLatch(1);
+ final AtomicBoolean failed = new AtomicBoolean();
+
+ for(int i=0;i<numThreads;i++) {
+ Thread thread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ _run();
+ } catch (Exception e) {
+ failed.set(true);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void _run() throws Exception {
+ startingGun.await();
+
+ NumericDocValues docIDToID = MultiDocValues.getNumericValues(r, "id");
+
+ for (int iter=0;iter<iters && failed.get() == false;iter++) {
+
+ if (VERBOSE) {
+ System.out.println("\nTEST: iter=" + iter + " s=" + s);
+ }
+ Query query;
+ VerifyHits verifyHits;
+
+ if (random().nextBoolean()) {
+ // Rect: don't allow dateline crossing when testing small:
+ final GeoRect rect = randomRect(small, small == false);
+
+ query = newRectQuery(FIELD_NAME, rect);
+
+ verifyHits = new VerifyHits() {
+ @Override
+ protected Boolean shouldMatch(double pointLat, double pointLon) {
+ return rectContainsPoint(rect, pointLat, pointLon);
+ }
+ @Override
+ protected void describe(int docID, double lat, double lon) {
+ }
+ };
+
+ } else if (random().nextBoolean()) {
+ // Distance
+ final boolean rangeQuery = random().nextBoolean();
+ final double centerLat = randomLat(small);
+ final double centerLon = randomLon(small);
+
+ double radiusMeters;
+ double minRadiusMeters;
+
+ if (small) {
+ // Approx 3 degrees lon at the equator:
+ radiusMeters = random().nextDouble() * 333000 + 1.0;
+ } else {
+ // So the query can cover at most 50% of the earth's surface:
+ radiusMeters = random().nextDouble() * GeoProjectionUtils.SEMIMAJOR_AXIS * Math.PI / 2.0 + 1.0;
+ }
+
+ // generate a random minimum radius between 1% and 95% the max radius
+ minRadiusMeters = (0.01 + 0.94 * random().nextDouble()) * radiusMeters;
+
+ if (VERBOSE) {
+ final DecimalFormat df = new DecimalFormat("#,###.00", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
+ System.out.println(" radiusMeters = " + df.format(radiusMeters)
+ + ((rangeQuery == true) ? " minRadiusMeters = " + df.format(minRadiusMeters) : ""));
+ }
+
+ try {
+ if (rangeQuery == true) {
+ query = newDistanceRangeQuery(FIELD_NAME, centerLat, centerLon, minRadiusMeters, radiusMeters);
+ } else {
+ query = newDistanceQuery(FIELD_NAME, centerLat, centerLon, radiusMeters);
+ }
+ } catch (IllegalArgumentException e) {
+ if (e.getMessage().contains("exceeds maxRadius")) {
+ continue;
+ }
+ throw e;
+ }
+
+ verifyHits = new VerifyHits() {
+ @Override
+ protected Boolean shouldMatch(double pointLat, double pointLon) {
+ if (rangeQuery == false) {
+ return circleContainsPoint(centerLat, centerLon, radiusMeters, pointLat, pointLon);
+ } else {
+ return distanceRangeContainsPoint(centerLat, centerLon, minRadiusMeters, radiusMeters, pointLat, pointLon);
+ }
+ }
+
+ @Override
+ protected void describe(int docID, double pointLat, double pointLon) {
+ double distanceKM = SloppyMath.haversin(centerLat, centerLon, pointLat, pointLon);
+ System.out.println(" docID=" + docID + " centerLon=" + centerLon + " centerLat=" + centerLat
+ + " pointLon=" + pointLon + " pointLat=" + pointLat + " distanceMeters=" + (distanceKM * 1000)
+ + " vs" + ((rangeQuery == true) ? " minRadiusMeters=" + minRadiusMeters : "") + " radiusMeters=" + radiusMeters);
+ }
+ };
+
+ // TODO: get poly query working with dateline crossing too (how?)!
+ } else {
+
+ // TODO: poly query can't handle dateline crossing yet:
+ final GeoRect bbox = randomRect(small, false);
+
+ // Polygon
+ double[] lats = new double[5];
+ double[] lons = new double[5];
+ lats[0] = bbox.minLat;
+ lons[0] = bbox.minLon;
+ lats[1] = bbox.maxLat;
+ lons[1] = bbox.minLon;
+ lats[2] = bbox.maxLat;
+ lons[2] = bbox.maxLon;
+ lats[3] = bbox.minLat;
+ lons[3] = bbox.maxLon;
+ lats[4] = bbox.minLat;
+ lons[4] = bbox.minLon;
+ query = newPolygonQuery(FIELD_NAME, lats, lons);
+
+ verifyHits = new VerifyHits() {
+ @Override
+ protected Boolean shouldMatch(double pointLat, double pointLon) {
+ return polyRectContainsPoint(bbox, pointLat, pointLon);
+ }
+
+ @Override
+ protected void describe(int docID, double lat, double lon) {
+ }
+ };
+ }
+
+ if (query != null) {
+
+ if (VERBOSE) {
+ System.out.println(" query=" + query);
+ }
+
+ verifyHits.test(failed, small, s, docIDToID, deleted, query, lats, lons);
+ }
+ }
+ }
+ };
+ thread.setName("T" + i);
+ thread.start();
+ threads.add(thread);
+ }
+ startingGun.countDown();
+ for(Thread thread : threads) {
+ thread.join();
+ }
+ IOUtils.close(r, dir);
+ assertFalse(failed.get());
+ }
+
+ protected Directory noVirusChecker(Directory dir) {
+ if (dir instanceof MockDirectoryWrapper) {
+ ((MockDirectoryWrapper) dir).setEnableVirusScanner(false);
+ }
+ return dir;
+ }
+}
[6/6] lucene-solr git commit: LUCENE-6997: refactor sandboxed
GeoPointField and query classes to lucene-spatial module
Posted by nk...@apache.org.
LUCENE-6997: refactor sandboxed GeoPointField and query classes to lucene-spatial module
Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/665041c5
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/665041c5
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/665041c5
Branch: refs/heads/lucene-6997
Commit: 665041c52f088310c5bd3414607aa683a1d8813b
Parents: 4569fd7
Author: nknize <nk...@apache.org>
Authored: Fri Feb 5 10:58:39 2016 -0600
Committer: nknize <nk...@apache.org>
Committed: Fri Feb 5 10:58:39 2016 -0600
----------------------------------------------------------------------
dev-tools/idea/lucene/sandbox/sandbox.iml | 1 +
.../java/org/apache/lucene/util/SloppyMath.java | 6 +-
lucene/sandbox/build.xml | 25 +
.../apache/lucene/document/GeoPointField.java | 131 ----
.../org/apache/lucene/document/LatLonPoint.java | 2 +-
.../apache/lucene/search/GeoBoundingBox.java | 46 --
.../lucene/search/GeoPointDistanceQuery.java | 170 ----
.../search/GeoPointDistanceQueryImpl.java | 131 ----
.../search/GeoPointDistanceRangeQuery.java | 102 ---
.../lucene/search/GeoPointInBBoxQuery.java | 152 ----
.../lucene/search/GeoPointInBBoxQueryImpl.java | 160 ----
.../lucene/search/GeoPointInPolygonQuery.java | 194 -----
.../apache/lucene/search/GeoPointTermQuery.java | 70 --
.../GeoPointTermQueryConstantScoreWrapper.java | 129 ----
.../apache/lucene/search/GeoPointTermsEnum.java | 249 ------
.../lucene/search/PointInPolygonQuery.java | 4 +-
.../apache/lucene/search/PointInRectQuery.java | 2 +-
.../apache/lucene/util/GeoDistanceUtils.java | 217 ------
.../org/apache/lucene/util/GeoHashUtils.java | 273 -------
.../apache/lucene/util/GeoProjectionUtils.java | 437 -----------
.../java/org/apache/lucene/util/GeoRect.java | 66 --
.../apache/lucene/util/GeoRelationUtils.java | 497 ------------
.../java/org/apache/lucene/util/GeoUtils.java | 237 ------
.../java/org/apache/lucene/util/package.html | 28 -
.../apache/lucene/search/TestGeoPointQuery.java | 386 ----------
.../lucene/search/TestLatLonPointQueries.java | 6 +-
.../lucene/util/BaseGeoPointTestCase.java | 764 ------------------
.../org/apache/lucene/util/TestGeoUtils.java | 545 -------------
.../lucene/spatial/document/GeoPointField.java | 136 ++++
.../lucene/spatial/document/package-info.java | 21 +
.../lucene/spatial/search/GeoBoundingBox.java | 53 ++
.../spatial/search/GeoPointDistanceQuery.java | 182 +++++
.../search/GeoPointDistanceQueryImpl.java | 131 ++++
.../search/GeoPointDistanceRangeQuery.java | 110 +++
.../spatial/search/GeoPointInBBoxQuery.java | 166 ++++
.../spatial/search/GeoPointInBBoxQueryImpl.java | 161 ++++
.../spatial/search/GeoPointInPolygonQuery.java | 196 +++++
.../spatial/search/GeoPointTermQuery.java | 114 +++
.../GeoPointTermQueryConstantScoreWrapper.java | 138 ++++
.../spatial/search/GeoPointTermsEnum.java | 249 ++++++
.../lucene/spatial/search/package-info.java | 21 +
.../lucene/spatial/util/GeoDistanceUtils.java | 223 ++++++
.../lucene/spatial/util/GeoHashUtils.java | 283 +++++++
.../lucene/spatial/util/GeoProjectionUtils.java | 465 +++++++++++
.../org/apache/lucene/spatial/util/GeoRect.java | 66 ++
.../lucene/spatial/util/GeoRelationUtils.java | 520 +++++++++++++
.../apache/lucene/spatial/util/GeoUtils.java | 248 ++++++
.../spatial/search/TestGeoPointQuery.java | 389 ++++++++++
.../spatial/util/BaseGeoPointTestCase.java | 769 +++++++++++++++++++
.../lucene/spatial/util/TestGeoUtils.java | 546 +++++++++++++
50 files changed, 5223 insertions(+), 4994 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/dev-tools/idea/lucene/sandbox/sandbox.iml
----------------------------------------------------------------------
diff --git a/dev-tools/idea/lucene/sandbox/sandbox.iml b/dev-tools/idea/lucene/sandbox/sandbox.iml
index 05caa9f..47cf8da 100644
--- a/dev-tools/idea/lucene/sandbox/sandbox.iml
+++ b/dev-tools/idea/lucene/sandbox/sandbox.iml
@@ -23,6 +23,7 @@
<orderEntry type="library" scope="TEST" name="JUnit" level="project" />
<orderEntry type="module" scope="TEST" module-name="lucene-test-framework" />
<orderEntry type="module" scope="TEST" module-name="codecs" />
+ <orderEntry type="module" module-name="spatial" />
<orderEntry type="module" module-name="lucene-core" />
</component>
</module>
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/core/src/java/org/apache/lucene/util/SloppyMath.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/util/SloppyMath.java b/lucene/core/src/java/org/apache/lucene/util/SloppyMath.java
index 9a05704..8da8276 100644
--- a/lucene/core/src/java/org/apache/lucene/util/SloppyMath.java
+++ b/lucene/core/src/java/org/apache/lucene/util/SloppyMath.java
@@ -180,15 +180,15 @@ public class SloppyMath {
}
// haversin
- static final double TO_RADIANS = Math.PI / 180D;
- static final double TO_DEGREES = 180D / Math.PI;
+ public static final double TO_RADIANS = Math.PI / 180D;
+ public static final double TO_DEGREES = 180D / Math.PI;
// cos/asin
private static final double ONE_DIV_F2 = 1/2.0;
private static final double ONE_DIV_F3 = 1/6.0;
private static final double ONE_DIV_F4 = 1/24.0;
- static final double PIO2 = Math.PI / 2D;
+ public static final double PIO2 = Math.PI / 2D;
private static final double PIO2_HI = Double.longBitsToDouble(0x3FF921FB54400000L); // 1.57079632673412561417e+00 first 33 bits of pi/2
private static final double PIO2_LO = Double.longBitsToDouble(0x3DD0B4611A626331L); // 6.07710050650619224932e-11 pi/2 - PIO2_HI
private static final double TWOPI_HI = 4*PIO2_HI;
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/build.xml
----------------------------------------------------------------------
diff --git a/lucene/sandbox/build.xml b/lucene/sandbox/build.xml
index 93bc275..d53a6ac 100644
--- a/lucene/sandbox/build.xml
+++ b/lucene/sandbox/build.xml
@@ -23,4 +23,29 @@
<import file="../module-build.xml"/>
+ <target name="compile-test-spatial" depends="init" if="module.has.tests">
+ <ant dir="${common.dir}/spatial" target="compile-test" inheritAll="false"/>
+ </target>
+
+ <path id="classpath">
+ <path refid="base.classpath"/>
+ <pathelement path="${spatial.jar}"/>
+ </path>
+ <target name="compile-core" depends="jar-spatial,common.compile-core" />
+
+ <path id="test.classpath">
+ <pathelement location="${build.dir}/classes/java"/>
+ <pathelement location="${build.dir}/classes/test"/>
+ <pathelement location="${common.dir}/build/spatial/classes/test"/>
+ <path refid="test.base.classpath"/>
+ <pathelement path="${spatial.jar}"/>
+ <path refid="junit-path"/>
+ </path>
+
+ <path id="junit.classpath">
+ <path refid="test.classpath"/>
+ <pathelement path="${java.class.path}"/>
+ </path>
+ <target name="compile-test" depends="jar-spatial,compile-test-spatial,common.compile-test" />
+
</project>
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/document/GeoPointField.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/GeoPointField.java b/lucene/sandbox/src/java/org/apache/lucene/document/GeoPointField.java
deleted file mode 100644
index ad1483d..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/document/GeoPointField.java
+++ /dev/null
@@ -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.document;
-
-import org.apache.lucene.index.DocValuesType;
-import org.apache.lucene.index.IndexOptions;
-import org.apache.lucene.util.GeoUtils;
-
-/**
- * <p>
- * Field that indexes <code>latitude</code> <code>longitude</code> decimal-degree values
- * for efficient encoding, sorting, and querying. This Geo capability is intended
- * to provide a basic and efficient out of the box field type for indexing and
- * querying 2 dimensional points in WGS-84 decimal degrees. An example usage is as follows:
- *
- * <pre class="prettyprint">
- * document.add(new GeoPointField(name, -96.33, 32.66, Field.Store.NO));
- * </pre>
- *
- * <p>To perform simple geospatial queries against a <code>GeoPointField</code>,
- * see {@link org.apache.lucene.search.GeoPointInBBoxQuery}, {@link org.apache.lucene.search.GeoPointInPolygonQuery},
- * or {@link org.apache.lucene.search.GeoPointDistanceQuery}
- *
- * NOTE: This indexes only high precision encoded terms which may result in visiting a high number
- * of terms for large queries. See LUCENE-6481 for a future improvement.
- *
- * @lucene.experimental
- */
-public final class GeoPointField extends Field {
- public static final int PRECISION_STEP = 9;
-
- /**
- * Type for an GeoPointField that is not stored:
- * normalization factors, frequencies, and positions are omitted.
- */
- public static final FieldType TYPE_NOT_STORED = new FieldType();
- static {
- TYPE_NOT_STORED.setTokenized(false);
- TYPE_NOT_STORED.setOmitNorms(true);
- TYPE_NOT_STORED.setIndexOptions(IndexOptions.DOCS);
- TYPE_NOT_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
- TYPE_NOT_STORED.setNumericType(FieldType.LegacyNumericType.LONG);
- TYPE_NOT_STORED.setNumericPrecisionStep(PRECISION_STEP);
- TYPE_NOT_STORED.freeze();
- }
-
- /**
- * Type for a stored GeoPointField:
- * normalization factors, frequencies, and positions are omitted.
- */
- public static final FieldType TYPE_STORED = new FieldType();
- static {
- TYPE_STORED.setTokenized(false);
- TYPE_STORED.setOmitNorms(true);
- TYPE_STORED.setIndexOptions(IndexOptions.DOCS);
- TYPE_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
- TYPE_STORED.setNumericType(FieldType.LegacyNumericType.LONG);
- TYPE_STORED.setNumericPrecisionStep(PRECISION_STEP);
- TYPE_STORED.setStored(true);
- TYPE_STORED.freeze();
- }
-
- /** Creates a stored or un-stored GeoPointField with the provided value
- * and default <code>precisionStep</code> set to 64 to avoid wasteful
- * indexing of lower precision terms.
- * @param name field name
- * @param lon longitude double value [-180.0 : 180.0]
- * @param lat latitude double value [-90.0 : 90.0]
- * @param stored Store.YES if the content should also be stored
- * @throws IllegalArgumentException if the field name is null.
- */
- public GeoPointField(String name, double lon, double lat, Store stored) {
- super(name, stored == Store.YES ? TYPE_STORED : TYPE_NOT_STORED);
- fieldsData = GeoUtils.mortonHash(lon, lat);
- }
-
- /** Expert: allows you to customize the {@link
- * FieldType}.
- * @param name field name
- * @param lon longitude double value [-180.0 : 180.0]
- * @param lat latitude double value [-90.0 : 90.0]
- * @param type customized field type: must have {@link FieldType#numericType()}
- * of {@link org.apache.lucene.document.FieldType.LegacyNumericType#LONG}.
- * @throws IllegalArgumentException if the field name or type is null, or
- * if the field type does not have a LONG numericType()
- */
- public GeoPointField(String name, double lon, double lat, FieldType type) {
- super(name, type);
- if (type.numericType() != FieldType.LegacyNumericType.LONG) {
- throw new IllegalArgumentException("type.numericType() must be LONG but got " + type.numericType());
- }
- if (type.docValuesType() != DocValuesType.SORTED_NUMERIC) {
- throw new IllegalArgumentException("type.docValuesType() must be SORTED_NUMERIC but got " + type.docValuesType());
- }
- fieldsData = GeoUtils.mortonHash(lon, lat);
- }
-
- public double getLon() {
- return GeoUtils.mortonUnhashLon((long) fieldsData);
- }
-
- public double getLat() {
- return GeoUtils.mortonUnhashLat((long) fieldsData);
- }
-
- @Override
- public String toString() {
- if (fieldsData == null) {
- return null;
- }
- StringBuilder sb = new StringBuilder();
- sb.append(GeoUtils.mortonUnhashLon((long) fieldsData));
- sb.append(',');
- sb.append(GeoUtils.mortonUnhashLat((long) fieldsData));
- return sb.toString();
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java
index 71d9527..77f7d32 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java
@@ -17,8 +17,8 @@
package org.apache.lucene.document;
import org.apache.lucene.util.BytesRef;
-import org.apache.lucene.util.GeoUtils;
import org.apache.lucene.util.NumericUtils;
+import org.apache.lucene.spatial.util.GeoUtils;
/** Add this to a document to index lat/lon point dimensionally */
public class LatLonPoint extends Field {
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoBoundingBox.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoBoundingBox.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoBoundingBox.java
deleted file mode 100644
index c2c92ea..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoBoundingBox.java
+++ /dev/null
@@ -1,46 +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.search;
-
-import org.apache.lucene.util.GeoUtils;
-
-/** NOTE: package private; just used so {@link GeoPointInPolygonQuery} can communicate its bounding box to {@link GeoPointInBBoxQuery}. */
-class GeoBoundingBox {
- public final double minLon;
- public final double maxLon;
- public final double minLat;
- public final double maxLat;
-
- public GeoBoundingBox(double minLon, double maxLon, double minLat, double maxLat) {
- if (GeoUtils.isValidLon(minLon) == false) {
- throw new IllegalArgumentException("invalid minLon " + minLon);
- }
- if (GeoUtils.isValidLon(maxLon) == false) {
- throw new IllegalArgumentException("invalid maxLon " + minLon);
- }
- if (GeoUtils.isValidLat(minLat) == false) {
- throw new IllegalArgumentException("invalid minLat " + minLat);
- }
- if (GeoUtils.isValidLat(maxLat) == false) {
- throw new IllegalArgumentException("invalid maxLat " + minLat);
- }
- this.minLon = minLon;
- this.maxLon = maxLon;
- this.minLat = minLat;
- this.maxLat = maxLat;
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceQuery.java
deleted file mode 100644
index a77798b..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceQuery.java
+++ /dev/null
@@ -1,170 +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.search;
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.util.GeoDistanceUtils;
-import org.apache.lucene.util.GeoRect;
-import org.apache.lucene.util.GeoUtils;
-
-/** Implements a simple point distance query on a GeoPoint field. This is based on
- * {@link org.apache.lucene.search.GeoPointInBBoxQuery} and is implemented using a two phase approach. First,
- * like {@code GeoPointInBBoxQueryImpl} candidate terms are queried using the numeric ranges based on
- * the morton codes of the min and max lat/lon pairs that intersect the boundary of the point-radius
- * circle. Terms
- * passing this initial filter are then passed to a secondary {@code postFilter} method that verifies whether the
- * decoded lat/lon point fall within the specified query distance (see {@link org.apache.lucene.util.SloppyMath#haversin}.
- * All morton value comparisons are subject to the same precision tolerance defined in
- * {@value org.apache.lucene.util.GeoUtils#TOLERANCE} and distance comparisons are subject to the accuracy of the
- * haversine formula (from R.W. Sinnott, "Virtues of the Haversine", Sky and Telescope, vol. 68, no. 2, 1984, p. 159)
- *
- * <p>Note: This query currently uses haversine which is a sloppy distance calculation (see above reference). For large
- * queries one can expect upwards of 400m error. Vincenty shrinks this to ~40m error but pays a penalty for computing
- * using the spheroid
- *
- * @lucene.experimental */
-public class GeoPointDistanceQuery extends GeoPointInBBoxQuery {
- protected final double centerLon;
- protected final double centerLat;
- protected final double radiusMeters;
-
- /** NOTE: radius is in meters. */
- public GeoPointDistanceQuery(final String field, final double centerLon, final double centerLat, final double radiusMeters) {
- this(field, GeoUtils.circleToBBox(centerLon, centerLat, radiusMeters), centerLon, centerLat, radiusMeters);
- }
-
- private GeoPointDistanceQuery(final String field, GeoRect bbox, final double centerLon,
- final double centerLat, final double radiusMeters) {
- super(field, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
- {
- // check longitudinal overlap (limits radius)
- final double maxRadius = GeoDistanceUtils.maxRadialDistanceMeters(centerLon, centerLat);
- if (radiusMeters > maxRadius) {
- throw new IllegalArgumentException("radiusMeters " + radiusMeters + " exceeds maxRadius [" + maxRadius
- + "] at location [" + centerLon + " " + centerLat + "]");
- }
- }
-
- if (GeoUtils.isValidLon(centerLon) == false) {
- throw new IllegalArgumentException("invalid centerLon " + centerLon);
- }
-
- if (GeoUtils.isValidLat(centerLat) == false) {
- throw new IllegalArgumentException("invalid centerLat " + centerLat);
- }
-
- if (radiusMeters <= 0.0) {
- throw new IllegalArgumentException("invalid radiusMeters " + radiusMeters);
- }
-
- this.centerLon = centerLon;
- this.centerLat = centerLat;
- this.radiusMeters = radiusMeters;
- }
-
- @Override
- public Query rewrite(IndexReader reader) {
- // query crosses dateline; split into left and right queries
- if (maxLon < minLon) {
- BooleanQuery.Builder bqb = new BooleanQuery.Builder();
-
- // unwrap the longitude iff outside the specified min/max lon range
- double unwrappedLon = centerLon;
- if (unwrappedLon > maxLon) {
- // unwrap left
- unwrappedLon += -360.0D;
- }
- GeoPointDistanceQueryImpl left = new GeoPointDistanceQueryImpl(field, this, unwrappedLon,
- new GeoRect(GeoUtils.MIN_LON_INCL, maxLon, minLat, maxLat));
- bqb.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
-
- if (unwrappedLon < maxLon) {
- // unwrap right
- unwrappedLon += 360.0D;
- }
- GeoPointDistanceQueryImpl right = new GeoPointDistanceQueryImpl(field, this, unwrappedLon,
- new GeoRect(minLon, GeoUtils.MAX_LON_INCL, minLat, maxLat));
- bqb.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
-
- return bqb.build();
- }
- return new GeoPointDistanceQueryImpl(field, this, centerLon,
- new GeoRect(this.minLon, this.maxLon, this.minLat, this.maxLat));
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof GeoPointDistanceQuery)) return false;
- if (!super.equals(o)) return false;
-
- GeoPointDistanceQuery that = (GeoPointDistanceQuery) o;
-
- if (Double.compare(that.centerLat, centerLat) != 0) return false;
- if (Double.compare(that.centerLon, centerLon) != 0) return false;
- if (Double.compare(that.radiusMeters, radiusMeters) != 0) return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = super.hashCode();
- long temp;
- temp = Double.doubleToLongBits(centerLon);
- result = 31 * result + (int) (temp ^ (temp >>> 32));
- temp = Double.doubleToLongBits(centerLat);
- result = 31 * result + (int) (temp ^ (temp >>> 32));
- temp = Double.doubleToLongBits(radiusMeters);
- result = 31 * result + (int) (temp ^ (temp >>> 32));
- return result;
- }
-
- @Override
- public String toString(String field) {
- final StringBuilder sb = new StringBuilder();
- sb.append(getClass().getSimpleName());
- sb.append(':');
- if (!this.field.equals(field)) {
- sb.append(" field=");
- sb.append(this.field);
- sb.append(':');
- }
- return sb.append( " Center: [")
- .append(centerLon)
- .append(',')
- .append(centerLat)
- .append(']')
- .append(" Distance: ")
- .append(radiusMeters)
- .append(" meters")
- .append("]")
- .toString();
- }
-
- public double getCenterLon() {
- return this.centerLon;
- }
-
- public double getCenterLat() {
- return this.centerLat;
- }
-
- public double getRadiusMeters() {
- return this.radiusMeters;
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceQueryImpl.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceQueryImpl.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceQueryImpl.java
deleted file mode 100644
index ef2155b..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceQueryImpl.java
+++ /dev/null
@@ -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.search;
-
-import java.io.IOException;
-
-import org.apache.lucene.document.GeoPointField;
-import org.apache.lucene.index.Terms;
-import org.apache.lucene.index.TermsEnum;
-import org.apache.lucene.util.AttributeSource;
-import org.apache.lucene.util.GeoRect;
-import org.apache.lucene.util.GeoRelationUtils;
-import org.apache.lucene.util.GeoUtils;
-import org.apache.lucene.util.SloppyMath;
-
-/** Package private implementation for the public facing GeoPointDistanceQuery delegate class.
- *
- * @lucene.experimental
- */
-final class GeoPointDistanceQueryImpl extends GeoPointInBBoxQueryImpl {
- private final GeoPointDistanceQuery query;
- private final double centerLon;
-
- GeoPointDistanceQueryImpl(final String field, final GeoPointDistanceQuery q, final double centerLonUnwrapped,
- final GeoRect bbox) {
- super(field, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
- query = q;
- centerLon = centerLonUnwrapped;
- }
-
- @Override @SuppressWarnings("unchecked")
- protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
- return new GeoPointRadiusTermsEnum(terms.iterator(), this.minLon, this.minLat, this.maxLon, this.maxLat);
- }
-
- @Override
- public void setRewriteMethod(RewriteMethod method) {
- throw new UnsupportedOperationException("cannot change rewrite method");
- }
-
- private final class GeoPointRadiusTermsEnum extends GeoPointTermsEnum {
- GeoPointRadiusTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
- final double maxLon, final double maxLat) {
- super(tenum, minLon, minLat, maxLon, maxLat);
- }
-
- /**
- * Computes the maximum shift for the given pointDistanceQuery. This prevents unnecessary depth traversal
- * given the size of the distance query.
- */
- @Override
- protected short computeMaxShift() {
- final short shiftFactor;
-
- if (query.radiusMeters > 1000000) {
- shiftFactor = 5;
- } else {
- shiftFactor = 4;
- }
-
- return (short)(GeoPointField.PRECISION_STEP * shiftFactor);
- }
-
- @Override
- protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat) {
- return GeoRelationUtils.rectCrossesCircle(minLon, minLat, maxLon, maxLat,
- centerLon, query.centerLat, query.radiusMeters, true);
- }
-
- @Override
- protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat) {
- return GeoRelationUtils.rectWithinCircle(minLon, minLat, maxLon, maxLat,
- centerLon, query.centerLat, query.radiusMeters, true);
- }
-
- @Override
- protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat) {
- return (cellContains(minLon, minLat, maxLon, maxLat)
- || cellWithin(minLon, minLat, maxLon, maxLat) || cellCrosses(minLon, minLat, maxLon, maxLat));
- }
-
- /**
- * The two-phase query approach. The parent {@link org.apache.lucene.search.GeoPointTermsEnum} class matches
- * encoded terms that fall within the minimum bounding box of the point-radius circle. Those documents that pass
- * the initial bounding box filter are then post filter compared to the provided distance using the
- * {@link org.apache.lucene.util.SloppyMath#haversin} method.
- */
- @Override
- protected boolean postFilter(final double lon, final double lat) {
- return (SloppyMath.haversin(query.centerLat, centerLon, lat, lon) * 1000.0 <= query.radiusMeters);
- }
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof GeoPointDistanceQueryImpl)) return false;
- if (!super.equals(o)) return false;
-
- GeoPointDistanceQueryImpl that = (GeoPointDistanceQueryImpl) o;
-
- if (!query.equals(that.query)) return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = super.hashCode();
- result = 31 * result + query.hashCode();
- return result;
- }
-
- public double getRadiusMeters() {
- return query.getRadiusMeters();
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceRangeQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceRangeQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceRangeQuery.java
deleted file mode 100644
index 5324a6d..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointDistanceRangeQuery.java
+++ /dev/null
@@ -1,102 +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.search;
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.util.GeoProjectionUtils;
-
-/** Implements a point distance range query on a GeoPoint field. This is based on
- * {@code org.apache.lucene.search.GeoPointDistanceQuery} and is implemented using a
- * {@code org.apache.lucene.search.BooleanClause.MUST_NOT} clause to exclude any points that fall within
- * minRadiusMeters from the provided point.
- *
- * @lucene.experimental
- */
-public final class GeoPointDistanceRangeQuery extends GeoPointDistanceQuery {
- protected final double minRadiusMeters;
-
- public GeoPointDistanceRangeQuery(final String field, final double centerLon, final double centerLat,
- final double minRadiusMeters, final double maxRadius) {
- super(field, centerLon, centerLat, maxRadius);
- this.minRadiusMeters = minRadiusMeters;
- }
-
- @Override
- public Query rewrite(IndexReader reader) {
- Query q = super.rewrite(reader);
- if (minRadiusMeters == 0.0) {
- return q;
- }
-
- // add an exclusion query
- BooleanQuery.Builder bqb = new BooleanQuery.Builder();
-
- // create a new exclusion query
- GeoPointDistanceQuery exclude = new GeoPointDistanceQuery(field, centerLon, centerLat, minRadiusMeters);
- // full map search
-// if (radiusMeters >= GeoProjectionUtils.SEMIMINOR_AXIS) {
-// bqb.add(new BooleanClause(new GeoPointInBBoxQuery(this.field, -180.0, -90.0, 180.0, 90.0), BooleanClause.Occur.MUST));
-// } else {
- bqb.add(new BooleanClause(q, BooleanClause.Occur.MUST));
-// }
- bqb.add(new BooleanClause(exclude, BooleanClause.Occur.MUST_NOT));
-
- return bqb.build();
- }
-
- @Override
- public String toString(String field) {
- final StringBuilder sb = new StringBuilder();
- sb.append(getClass().getSimpleName());
- sb.append(':');
- if (!this.field.equals(field)) {
- sb.append(" field=");
- sb.append(this.field);
- sb.append(':');
- }
- return sb.append( " Center: [")
- .append(centerLon)
- .append(',')
- .append(centerLat)
- .append(']')
- .append(" From Distance: ")
- .append(minRadiusMeters)
- .append(" m")
- .append(" To Distance: ")
- .append(radiusMeters)
- .append(" m")
- .append(" Lower Left: [")
- .append(minLon)
- .append(',')
- .append(minLat)
- .append(']')
- .append(" Upper Right: [")
- .append(maxLon)
- .append(',')
- .append(maxLat)
- .append("]")
- .toString();
- }
-
- public double getMinRadiusMeters() {
- return this.minRadiusMeters;
- }
-
- public double getMaxRadiusMeters() {
- return this.radiusMeters;
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInBBoxQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInBBoxQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInBBoxQuery.java
deleted file mode 100644
index 0926a4f..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInBBoxQuery.java
+++ /dev/null
@@ -1,152 +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.search;
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.util.GeoUtils;
-
-/** Implements a simple bounding box query on a GeoPoint field. This is inspired by
- * {@link LegacyNumericRangeQuery} and is implemented using a
- * two phase approach. First, candidate terms are queried using a numeric
- * range based on the morton codes of the min and max lat/lon pairs. Terms
- * passing this initial filter are passed to a final check that verifies whether
- * the decoded lat/lon falls within (or on the boundary) of the query bounding box.
- * The value comparisons are subject to a precision tolerance defined in
- * {@value org.apache.lucene.util.GeoUtils#TOLERANCE}
- *
- * NOTES:
- * 1. All latitude/longitude values must be in decimal degrees.
- * 2. Complex computational geometry (e.g., dateline wrapping) is not supported
- * 3. For more advanced GeoSpatial indexing and query operations see spatial module
- * 4. This is well suited for small rectangles, large bounding boxes may result
- * in many terms, depending whether the bounding box falls on the boundary of
- * many cells (degenerate case)
- *
- * @lucene.experimental
- */
-public class GeoPointInBBoxQuery extends Query {
- protected final String field;
- protected final double minLon;
- protected final double minLat;
- protected final double maxLon;
- protected final double maxLat;
-
- public GeoPointInBBoxQuery(final String field, final double minLon, final double minLat, final double maxLon, final double maxLat) {
- this.field = field;
- this.minLon = minLon;
- this.minLat = minLat;
- this.maxLon = maxLon;
- this.maxLat = maxLat;
- }
-
- @Override
- public Query rewrite(IndexReader reader) {
- // short-circuit to match all if specifying the whole map
- if (minLon == GeoUtils.MIN_LON_INCL && maxLon == GeoUtils.MAX_LON_INCL
- && minLat == GeoUtils.MIN_LAT_INCL && maxLat == GeoUtils.MAX_LAT_INCL) {
- // FieldValueQuery is valid since DocValues are *required* for GeoPointField
- return new FieldValueQuery(field);
- }
-
- if (maxLon < minLon) {
- BooleanQuery.Builder bqb = new BooleanQuery.Builder();
-
- GeoPointInBBoxQueryImpl left = new GeoPointInBBoxQueryImpl(field, -180.0D, minLat, maxLon, maxLat);
- bqb.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
- GeoPointInBBoxQueryImpl right = new GeoPointInBBoxQueryImpl(field, minLon, minLat, 180.0D, maxLat);
- bqb.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
- return bqb.build();
- }
- return new GeoPointInBBoxQueryImpl(field, minLon, minLat, maxLon, maxLat);
- }
-
- @Override
- public String toString(String field) {
- final StringBuilder sb = new StringBuilder();
- sb.append(getClass().getSimpleName());
- sb.append(':');
- if (!this.field.equals(field)) {
- sb.append(" field=");
- sb.append(this.field);
- sb.append(':');
- }
- return sb.append(" Lower Left: [")
- .append(minLon)
- .append(',')
- .append(minLat)
- .append(']')
- .append(" Upper Right: [")
- .append(maxLon)
- .append(',')
- .append(maxLat)
- .append("]")
- .toString();
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof GeoPointInBBoxQuery)) return false;
- if (!super.equals(o)) return false;
-
- GeoPointInBBoxQuery that = (GeoPointInBBoxQuery) o;
-
- if (Double.compare(that.maxLat, maxLat) != 0) return false;
- if (Double.compare(that.maxLon, maxLon) != 0) return false;
- if (Double.compare(that.minLat, minLat) != 0) return false;
- if (Double.compare(that.minLon, minLon) != 0) return false;
- if (!field.equals(that.field)) return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = super.hashCode();
- long temp;
- result = 31 * result + field.hashCode();
- temp = Double.doubleToLongBits(minLon);
- result = 31 * result + (int) (temp ^ (temp >>> 32));
- temp = Double.doubleToLongBits(minLat);
- result = 31 * result + (int) (temp ^ (temp >>> 32));
- temp = Double.doubleToLongBits(maxLon);
- result = 31 * result + (int) (temp ^ (temp >>> 32));
- temp = Double.doubleToLongBits(maxLat);
- result = 31 * result + (int) (temp ^ (temp >>> 32));
- return result;
- }
-
- public final String getField() {
- return this.field;
- }
-
- public final double getMinLon() {
- return this.minLon;
- }
-
- public final double getMinLat() {
- return this.minLat;
- }
-
- public final double getMaxLon() {
- return this.maxLon;
- }
-
- public final double getMaxLat() {
- return this.maxLat;
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInBBoxQueryImpl.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInBBoxQueryImpl.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInBBoxQueryImpl.java
deleted file mode 100644
index b385a54..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInBBoxQueryImpl.java
+++ /dev/null
@@ -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.search;
-
-import java.io.IOException;
-
-import org.apache.lucene.document.GeoPointField;
-import org.apache.lucene.index.Terms;
-import org.apache.lucene.index.TermsEnum;
-import org.apache.lucene.util.AttributeSource;
-import org.apache.lucene.util.GeoRelationUtils;
-import org.apache.lucene.util.SloppyMath;
-
-/** Package private implementation for the public facing GeoPointInBBoxQuery delegate class.
- *
- * @lucene.experimental
- */
-class GeoPointInBBoxQueryImpl extends GeoPointTermQuery {
- /**
- * Constructs a new GeoBBoxQuery that will match encoded GeoPoint terms that fall within or on the boundary
- * of the bounding box defined by the input parameters
- * @param field the field name
- * @param minLon lower longitude (x) value of the bounding box
- * @param minLat lower latitude (y) value of the bounding box
- * @param maxLon upper longitude (x) value of the bounding box
- * @param maxLat upper latitude (y) value of the bounding box
- */
- GeoPointInBBoxQueryImpl(final String field, final double minLon, final double minLat, final double maxLon, final double maxLat) {
- super(field, minLon, minLat, maxLon, maxLat);
- }
-
- @Override @SuppressWarnings("unchecked")
- protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
- return new GeoPointInBBoxTermsEnum(terms.iterator(), minLon, minLat, maxLon, maxLat);
- }
-
- @Override
- public void setRewriteMethod(RewriteMethod method) {
- throw new UnsupportedOperationException("cannot change rewrite method");
- }
-
- protected class GeoPointInBBoxTermsEnum extends GeoPointTermsEnum {
- protected GeoPointInBBoxTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
- final double maxLon, final double maxLat) {
- super(tenum, minLon, minLat, maxLon, maxLat);
- }
-
- @Override
- protected short computeMaxShift() {
- final short shiftFactor;
-
- // compute diagonal radius
- double midLon = (minLon + maxLon) * 0.5;
- double midLat = (minLat + maxLat) * 0.5;
-
- if (SloppyMath.haversin(minLat, minLon, midLat, midLon)*1000 > 1000000) {
- shiftFactor = 5;
- } else {
- shiftFactor = 4;
- }
-
- return (short)(GeoPointField.PRECISION_STEP * shiftFactor);
- }
-
- /**
- * Determine whether the quad-cell crosses the shape
- */
- @Override
- protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat) {
- return GeoRelationUtils.rectCrosses(minLon, minLat, maxLon, maxLat, this.minLon, this.minLat, this.maxLon, this.maxLat);
- }
-
- /**
- * Determine whether quad-cell is within the shape
- */
- @Override
- protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat) {
- return GeoRelationUtils.rectWithin(minLon, minLat, maxLon, maxLat, this.minLon, this.minLat, this.maxLon, this.maxLat);
- }
-
- @Override
- protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat) {
- return cellIntersectsMBR(minLon, minLat, maxLon, maxLat);
- }
-
- @Override
- protected boolean postFilter(final double lon, final double lat) {
- return GeoRelationUtils.pointInRectPrecise(lon, lat, minLon, minLat, maxLon, maxLat);
- }
- }
-
- @Override
- @SuppressWarnings({"unchecked","rawtypes"})
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- if (!super.equals(o)) return false;
-
- GeoPointInBBoxQueryImpl that = (GeoPointInBBoxQueryImpl) o;
-
- if (Double.compare(that.maxLat, maxLat) != 0) return false;
- if (Double.compare(that.maxLon, maxLon) != 0) return false;
- if (Double.compare(that.minLat, minLat) != 0) return false;
- if (Double.compare(that.minLon, minLon) != 0) return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = super.hashCode();
- long temp;
- temp = Double.doubleToLongBits(minLon);
- result = 31 * result + (int) (temp ^ (temp >>> 32));
- temp = Double.doubleToLongBits(minLat);
- result = 31 * result + (int) (temp ^ (temp >>> 32));
- temp = Double.doubleToLongBits(maxLon);
- result = 31 * result + (int) (temp ^ (temp >>> 32));
- temp = Double.doubleToLongBits(maxLat);
- result = 31 * result + (int) (temp ^ (temp >>> 32));
- return result;
- }
-
- @Override
- public String toString(String field) {
- final StringBuilder sb = new StringBuilder();
- sb.append(getClass().getSimpleName());
- sb.append(':');
- if (!getField().equals(field)) {
- sb.append(" field=");
- sb.append(getField());
- sb.append(':');
- }
- return sb.append(" Lower Left: [")
- .append(minLon)
- .append(',')
- .append(minLat)
- .append(']')
- .append(" Upper Right: [")
- .append(maxLon)
- .append(',')
- .append(maxLat)
- .append("]")
- .toString();
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInPolygonQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInPolygonQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInPolygonQuery.java
deleted file mode 100644
index abdcf95..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInPolygonQuery.java
+++ /dev/null
@@ -1,194 +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.search;
-
-import java.io.IOException;
-import java.util.Arrays;
-
-import org.apache.lucene.index.Terms;
-import org.apache.lucene.index.TermsEnum;
-import org.apache.lucene.util.AttributeSource;
-import org.apache.lucene.util.GeoRect;
-import org.apache.lucene.util.GeoRelationUtils;
-import org.apache.lucene.util.GeoUtils;
-
-/** Implements a simple point in polygon query on a GeoPoint field. This is based on
- * {@code GeoPointInBBoxQueryImpl} and is implemented using a
- * three phase approach. First, like {@code GeoPointInBBoxQueryImpl}
- * candidate terms are queried using a numeric range based on the morton codes
- * of the min and max lat/lon pairs. Terms passing this initial filter are passed
- * to a secondary filter that verifies whether the decoded lat/lon point falls within
- * (or on the boundary) of the bounding box query. Finally, the remaining candidate
- * term is passed to the final point in polygon check. All value comparisons are subject
- * to the same precision tolerance defined in {@value org.apache.lucene.util.GeoUtils#TOLERANCE}
- *
- * <p>NOTES:
- * 1. The polygon coordinates need to be in either clockwise or counter-clockwise order.
- * 2. The polygon must not be self-crossing, otherwise the query may result in unexpected behavior
- * 3. All latitude/longitude values must be in decimal degrees.
- * 4. Complex computational geometry (e.g., dateline wrapping, polygon with holes) is not supported
- * 5. For more advanced GeoSpatial indexing and query operations see spatial module
- *
- * @lucene.experimental
- */
-public final class GeoPointInPolygonQuery extends GeoPointInBBoxQueryImpl {
- // polygon position arrays - this avoids the use of any objects or
- // or geo library dependencies
- private final double[] x;
- private final double[] y;
-
- /**
- * Constructs a new GeoPolygonQuery that will match encoded {@link org.apache.lucene.document.GeoPointField} terms
- * that fall within or on the boundary of the polygon defined by the input parameters.
- */
- public GeoPointInPolygonQuery(final String field, final double[] polyLons, final double[] polyLats) {
- this(field, GeoUtils.polyToBBox(polyLons, polyLats), polyLons, polyLats);
- }
-
- /** Common constructor, used only internally. */
- private GeoPointInPolygonQuery(final String field, GeoRect bbox, final double[] polyLons, final double[] polyLats) {
- super(field, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
- if (polyLats.length != polyLons.length) {
- throw new IllegalArgumentException("polyLats and polyLons must be equal length");
- }
- if (polyLats.length < 4) {
- throw new IllegalArgumentException("at least 4 polygon points required");
- }
- if (polyLats[0] != polyLats[polyLats.length-1]) {
- throw new IllegalArgumentException("first and last points of the polygon must be the same (it must close itself): polyLats[0]=" + polyLats[0] + " polyLats[" + (polyLats.length-1) + "]=" + polyLats[polyLats.length-1]);
- }
- if (polyLons[0] != polyLons[polyLons.length-1]) {
- throw new IllegalArgumentException("first and last points of the polygon must be the same (it must close itself): polyLons[0]=" + polyLons[0] + " polyLons[" + (polyLons.length-1) + "]=" + polyLons[polyLons.length-1]);
- }
-
- this.x = polyLons;
- this.y = polyLats;
- }
-
- @Override @SuppressWarnings("unchecked")
- protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
- return new GeoPolygonTermsEnum(terms.iterator(), this.minLon, this.minLat, this.maxLon, this.maxLat);
- }
-
- @Override
- public void setRewriteMethod(RewriteMethod method) {
- throw new UnsupportedOperationException("cannot change rewrite method");
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- if (!super.equals(o)) return false;
-
- GeoPointInPolygonQuery that = (GeoPointInPolygonQuery) o;
-
- if (!Arrays.equals(x, that.x)) return false;
- if (!Arrays.equals(y, that.y)) return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = super.hashCode();
- result = 31 * result + (x != null ? Arrays.hashCode(x) : 0);
- result = 31 * result + (y != null ? Arrays.hashCode(y) : 0);
- return result;
- }
-
- @Override
- public String toString(String field) {
- assert x.length == y.length;
-
- final StringBuilder sb = new StringBuilder();
- sb.append(getClass().getSimpleName());
- sb.append(':');
- if (!getField().equals(field)) {
- sb.append(" field=");
- sb.append(getField());
- sb.append(':');
- }
- sb.append(" Points: ");
- for (int i=0; i<x.length; ++i) {
- sb.append("[")
- .append(x[i])
- .append(", ")
- .append(y[i])
- .append("] ");
- }
-
- return sb.toString();
- }
-
- /**
- * Custom {@link org.apache.lucene.index.TermsEnum} that computes morton hash ranges based on the defined edges of
- * the provided polygon.
- */
- private final class GeoPolygonTermsEnum extends GeoPointTermsEnum {
- GeoPolygonTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
- final double maxLon, final double maxLat) {
- super(tenum, minLon, minLat, maxLon, maxLat);
- }
-
- @Override
- protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat) {
- return GeoRelationUtils.rectCrossesPolyApprox(minLon, minLat, maxLon, maxLat, x, y, GeoPointInPolygonQuery.this.minLon,
- GeoPointInPolygonQuery.this.minLat, GeoPointInPolygonQuery.this.maxLon, GeoPointInPolygonQuery.this.maxLat);
- }
-
- @Override
- protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat) {
- return GeoRelationUtils.rectWithinPolyApprox(minLon, minLat, maxLon, maxLat, x, y, GeoPointInPolygonQuery.this.minLon,
- GeoPointInPolygonQuery.this.minLat, GeoPointInPolygonQuery.this.maxLon, GeoPointInPolygonQuery.this.maxLat);
- }
-
- @Override
- protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat) {
- return cellContains(minLon, minLat, maxLon, maxLat) || cellWithin(minLon, minLat, maxLon, maxLat)
- || cellCrosses(minLon, minLat, maxLon, maxLat);
- }
-
- /**
- * The two-phase query approach. The parent
- * {@link org.apache.lucene.search.GeoPointTermsEnum#accept} method is called to match
- * encoded terms that fall within the bounding box of the polygon. Those documents that pass the initial
- * bounding box filter are then compared to the provided polygon using the
- * {@link org.apache.lucene.util.GeoRelationUtils#pointInPolygon} method.
- */
- @Override
- protected boolean postFilter(final double lon, final double lat) {
- return GeoRelationUtils.pointInPolygon(x, y, lat, lon);
- }
- }
-
- /**
- * API utility method for returning the array of longitudinal values for this GeoPolygon
- * The returned array is not a copy so do not change it!
- */
- public double[] getLons() {
- return this.x;
- }
-
- /**
- * API utility method for returning the array of latitudinal values for this GeoPolygon
- * The returned array is not a copy so do not change it!
- */
- public double[] getLats() {
- return this.y;
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermQuery.java
deleted file mode 100644
index 5ec0774..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermQuery.java
+++ /dev/null
@@ -1,70 +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.search;
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.util.GeoUtils;
-
-/**
- * TermQuery for GeoPointField for overriding {@link org.apache.lucene.search.MultiTermQuery} methods specific to
- * Geospatial operations
- *
- * @lucene.experimental
- */
-abstract class GeoPointTermQuery extends MultiTermQuery {
- // simple bounding box optimization - no objects used to avoid dependencies
- protected final double minLon;
- protected final double minLat;
- protected final double maxLon;
- protected final double maxLat;
-
- /**
- * Constructs a query matching terms that cannot be represented with a single
- * Term.
- */
- public GeoPointTermQuery(String field, final double minLon, final double minLat, final double maxLon, final double maxLat) {
- super(field);
-
- if (GeoUtils.isValidLon(minLon) == false) {
- throw new IllegalArgumentException("invalid minLon " + minLon);
- }
- if (GeoUtils.isValidLon(maxLon) == false) {
- throw new IllegalArgumentException("invalid maxLon " + maxLon);
- }
- if (GeoUtils.isValidLat(minLat) == false) {
- throw new IllegalArgumentException("invalid minLat " + minLat);
- }
- if (GeoUtils.isValidLat(maxLat) == false) {
- throw new IllegalArgumentException("invalid maxLat " + maxLat);
- }
- this.minLon = minLon;
- this.minLat = minLat;
- this.maxLon = maxLon;
- this.maxLat = maxLat;
-
- this.rewriteMethod = GEO_CONSTANT_SCORE_REWRITE;
- }
-
- public static final RewriteMethod GEO_CONSTANT_SCORE_REWRITE = new RewriteMethod() {
- @Override
- public Query rewrite(IndexReader reader, MultiTermQuery query) {
- return new GeoPointTermQueryConstantScoreWrapper<>((GeoPointTermQuery)query);
- }
- };
-
-
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermQueryConstantScoreWrapper.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermQueryConstantScoreWrapper.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermQueryConstantScoreWrapper.java
deleted file mode 100644
index e00bb72..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermQueryConstantScoreWrapper.java
+++ /dev/null
@@ -1,129 +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.search;
-
-import java.io.IOException;
-
-import org.apache.lucene.index.LeafReader;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.PostingsEnum;
-import org.apache.lucene.index.SortedNumericDocValues;
-import org.apache.lucene.index.Terms;
-import org.apache.lucene.util.DocIdSetBuilder;
-import org.apache.lucene.util.GeoUtils;
-
-/**
- * Custom ConstantScoreWrapper for {@code GeoPointTermQuery} that cuts over to DocValues
- * for post filtering boundary ranges. Multi-valued GeoPoint documents are supported.
- *
- * @lucene.experimental
- */
-final class GeoPointTermQueryConstantScoreWrapper <Q extends GeoPointTermQuery> extends Query {
- protected final Q query;
-
- protected GeoPointTermQueryConstantScoreWrapper(Q query) {
- this.query = query;
- }
-
- @Override
- public String toString(String field) {
- return query.toString();
- }
-
- @Override
- public final boolean equals(final Object o) {
- if (super.equals(o) == false) {
- return false;
- }
- final GeoPointTermQueryConstantScoreWrapper<?> that = (GeoPointTermQueryConstantScoreWrapper<?>) o;
- return this.query.equals(that.query);
- }
-
- @Override
- public final int hashCode() {
- return 31 * super.hashCode() + query.hashCode();
- }
-
- @Override
- public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
- return new ConstantScoreWeight(this) {
-
- private DocIdSet getDocIDs(LeafReaderContext context) throws IOException {
- final Terms terms = context.reader().terms(query.field);
- if (terms == null) {
- return DocIdSet.EMPTY;
- }
-
- final GeoPointTermsEnum termsEnum = (GeoPointTermsEnum)(query.getTermsEnum(terms));
- assert termsEnum != null;
-
- LeafReader reader = context.reader();
- DocIdSetBuilder builder = new DocIdSetBuilder(reader.maxDoc());
- PostingsEnum docs = null;
- SortedNumericDocValues sdv = reader.getSortedNumericDocValues(query.field);
-
- while (termsEnum.next() != null) {
- docs = termsEnum.postings(docs, PostingsEnum.NONE);
- // boundary terms need post filtering by
- if (termsEnum.boundaryTerm()) {
- int docId = docs.nextDoc();
- long hash;
- do {
- sdv.setDocument(docId);
- for (int i=0; i<sdv.count(); ++i) {
- hash = sdv.valueAt(i);
- if (termsEnum.postFilter(GeoUtils.mortonUnhashLon(hash), GeoUtils.mortonUnhashLat(hash))) {
- builder.add(docId);
- break;
- }
- }
- } while ((docId = docs.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS);
- } else {
- builder.add(docs);
- }
- }
-
- return builder.build();
- }
-
- private Scorer scorer(DocIdSet set) throws IOException {
- if (set == null) {
- return null;
- }
- final DocIdSetIterator disi = set.iterator();
- if (disi == null) {
- return null;
- }
- return new ConstantScoreScorer(this, score(), disi);
- }
-
- @Override
- public BulkScorer bulkScorer(LeafReaderContext context) throws IOException {
- final Scorer scorer = scorer(getDocIDs(context));
- if (scorer == null) {
- return null;
- }
- return new DefaultBulkScorer(scorer);
- }
-
- @Override
- public Scorer scorer(LeafReaderContext context) throws IOException {
- return scorer(getDocIDs(context));
- }
- };
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermsEnum.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermsEnum.java b/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermsEnum.java
deleted file mode 100644
index e2f96d4..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/search/GeoPointTermsEnum.java
+++ /dev/null
@@ -1,249 +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.search;
-
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.apache.lucene.document.GeoPointField;
-import org.apache.lucene.index.FilteredTermsEnum;
-import org.apache.lucene.index.TermsEnum;
-import org.apache.lucene.util.BytesRef;
-import org.apache.lucene.util.BytesRefBuilder;
-import org.apache.lucene.util.GeoRelationUtils;
-import org.apache.lucene.util.GeoUtils;
-import org.apache.lucene.util.LegacyNumericUtils;
-
-/**
- * computes all ranges along a space-filling curve that represents
- * the given bounding box and enumerates all terms contained within those ranges
- *
- * @lucene.experimental
- */
-abstract class GeoPointTermsEnum extends FilteredTermsEnum {
- protected final double minLon;
- protected final double minLat;
- protected final double maxLon;
- protected final double maxLat;
-
- protected Range currentRange;
- private final BytesRefBuilder currentCell = new BytesRefBuilder();
- private final BytesRefBuilder nextSubRange = new BytesRefBuilder();
-
- private final List<Range> rangeBounds = new LinkedList<>();
-
- // detail level should be a factor of PRECISION_STEP limiting the depth of recursion (and number of ranges)
- protected final short DETAIL_LEVEL;
-
- GeoPointTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
- final double maxLon, final double maxLat) {
- super(tenum);
- final long rectMinHash = GeoUtils.mortonHash(minLon, minLat);
- final long rectMaxHash = GeoUtils.mortonHash(maxLon, maxLat);
- this.minLon = GeoUtils.mortonUnhashLon(rectMinHash);
- this.minLat = GeoUtils.mortonUnhashLat(rectMinHash);
- this.maxLon = GeoUtils.mortonUnhashLon(rectMaxHash);
- this.maxLat = GeoUtils.mortonUnhashLat(rectMaxHash);
- DETAIL_LEVEL = (short)(((GeoUtils.BITS<<1)-computeMaxShift())/2);
-
- computeRange(0L, (short) ((GeoUtils.BITS << 1) - 1));
- assert rangeBounds.isEmpty() == false;
- Collections.sort(rangeBounds);
- }
-
- /**
- * entry point for recursively computing ranges
- */
- private final void computeRange(long term, final short shift) {
- final long split = term | (0x1L<<shift);
- assert shift < 64;
- final long upperMax;
- if (shift < 63) {
- upperMax = term | ((1L << (shift+1))-1);
- } else {
- upperMax = 0xffffffffffffffffL;
- }
- final long lowerMax = split-1;
-
- relateAndRecurse(term, lowerMax, shift);
- relateAndRecurse(split, upperMax, shift);
- }
-
- /**
- * recurse to higher level precision cells to find ranges along the space-filling curve that fall within the
- * query box
- *
- * @param start starting value on the space-filling curve for a cell at a given res
- * @param end ending value on the space-filling curve for a cell at a given res
- * @param res spatial res represented as a bit shift (MSB is lower res)
- */
- private void relateAndRecurse(final long start, final long end, final short res) {
- final double minLon = GeoUtils.mortonUnhashLon(start);
- final double minLat = GeoUtils.mortonUnhashLat(start);
- final double maxLon = GeoUtils.mortonUnhashLon(end);
- final double maxLat = GeoUtils.mortonUnhashLat(end);
-
- final short level = (short)((GeoUtils.BITS<<1)-res>>>1);
-
- // if cell is within and a factor of the precision step, or it crosses the edge of the shape add the range
- final boolean within = res % GeoPointField.PRECISION_STEP == 0 && cellWithin(minLon, minLat, maxLon, maxLat);
- if (within || (level == DETAIL_LEVEL && cellIntersectsShape(minLon, minLat, maxLon, maxLat))) {
- final short nextRes = (short)(res-1);
- if (nextRes % GeoPointField.PRECISION_STEP == 0) {
- rangeBounds.add(new Range(start, nextRes, !within));
- rangeBounds.add(new Range(start|(1L<<nextRes), nextRes, !within));
- } else {
- rangeBounds.add(new Range(start, res, !within));
- }
- } else if (level < DETAIL_LEVEL && cellIntersectsMBR(minLon, minLat, maxLon, maxLat)) {
- computeRange(start, (short) (res - 1));
- }
- }
-
- protected short computeMaxShift() {
- // in this case a factor of 4 brings the detail level to ~0.002/0.001 degrees lon/lat respectively (or ~222m/111m)
- return GeoPointField.PRECISION_STEP * 4;
- }
-
- /**
- * Determine whether the quad-cell crosses the shape
- */
- protected abstract boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat);
-
- /**
- * Determine whether quad-cell is within the shape
- */
- protected abstract boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat);
-
- /**
- * Default shape is a rectangle, so this returns the same as {@code cellIntersectsMBR}
- */
- protected abstract boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat);
-
- /**
- * Primary driver for cells intersecting shape boundaries
- */
- protected boolean cellIntersectsMBR(final double minLon, final double minLat, final double maxLon, final double maxLat) {
- return GeoRelationUtils.rectIntersects(minLon, minLat, maxLon, maxLat, this.minLon, this.minLat, this.maxLon, this.maxLat);
- }
-
- /**
- * Return whether quad-cell contains the bounding box of this shape
- */
- protected boolean cellContains(final double minLon, final double minLat, final double maxLon, final double maxLat) {
- return GeoRelationUtils.rectWithin(this.minLon, this.minLat, this.maxLon, this.maxLat, minLon, minLat, maxLon, maxLat);
- }
-
- public boolean boundaryTerm() {
- if (currentRange == null) {
- throw new IllegalStateException("GeoPointTermsEnum empty or not initialized");
- }
- return currentRange.boundary;
- }
-
- private void nextRange() {
- currentRange = rangeBounds.remove(0);
- currentRange.fillBytesRef(currentCell);
- }
-
- @Override
- protected final BytesRef nextSeekTerm(BytesRef term) {
- while (!rangeBounds.isEmpty()) {
- if (currentRange == null) {
- nextRange();
- }
-
- // if the new upper bound is before the term parameter, the sub-range is never a hit
- if (term != null && term.compareTo(currentCell.get()) > 0) {
- nextRange();
- if (!rangeBounds.isEmpty()) {
- continue;
- }
- }
- // never seek backwards, so use current term if lower bound is smaller
- return (term != null && term.compareTo(currentCell.get()) > 0) ?
- term : currentCell.get();
- }
-
- // no more sub-range enums available
- assert rangeBounds.isEmpty();
- return null;
- }
-
- /**
- * The two-phase query approach. {@link #nextSeekTerm} is called to obtain the next term that matches a numeric
- * range of the bounding box. Those terms that pass the initial range filter are then compared against the
- * decoded min/max latitude and longitude values of the bounding box only if the range is not a "boundary" range
- * (e.g., a range that straddles the boundary of the bbox).
- * @param term term for candidate document
- * @return match status
- */
- @Override
- protected AcceptStatus accept(BytesRef term) {
- // validate value is in range
- while (currentCell == null || term.compareTo(currentCell.get()) > 0) {
- if (rangeBounds.isEmpty()) {
- return AcceptStatus.END;
- }
- // peek next sub-range, only seek if the current term is smaller than next lower bound
- rangeBounds.get(0).fillBytesRef(this.nextSubRange);
- if (term.compareTo(this.nextSubRange.get()) < 0) {
- return AcceptStatus.NO_AND_SEEK;
- }
- // step forward to next range without seeking, as next range is less or equal current term
- nextRange();
- }
-
- return AcceptStatus.YES;
- }
-
- protected abstract boolean postFilter(final double lon, final double lat);
-
- /**
- * Internal class to represent a range along the space filling curve
- */
- protected final class Range implements Comparable<Range> {
- final short shift;
- final long start;
- final boolean boundary;
-
- Range(final long lower, final short shift, boolean boundary) {
- this.boundary = boundary;
- this.start = lower;
- this.shift = shift;
- }
-
- /**
- * Encode as a BytesRef using a reusable object. This allows us to lazily create the BytesRef (which is
- * quite expensive), only when we need it.
- */
- private void fillBytesRef(BytesRefBuilder result) {
- assert result != null;
- LegacyNumericUtils.longToPrefixCoded(start, shift, result);
- }
-
- @Override
- public int compareTo(Range other) {
- final int result = Short.compare(this.shift, other.shift);
- if (result == 0) {
- return Long.compare(this.start, other.start);
- }
- return result;
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/PointInPolygonQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/PointInPolygonQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/PointInPolygonQuery.java
index 861a109..8e099c4 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/search/PointInPolygonQuery.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/search/PointInPolygonQuery.java
@@ -26,9 +26,9 @@ import org.apache.lucene.index.PointValues;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.util.DocIdSetBuilder;
-import org.apache.lucene.util.GeoRelationUtils;
-import org.apache.lucene.util.GeoUtils;
import org.apache.lucene.util.NumericUtils;
+import org.apache.lucene.spatial.util.GeoRelationUtils;
+import org.apache.lucene.spatial.util.GeoUtils;
/** Finds all previously indexed points that fall within the specified polygon.
*
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/search/PointInRectQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/search/PointInRectQuery.java b/lucene/sandbox/src/java/org/apache/lucene/search/PointInRectQuery.java
index 6ef1703..1d95399 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/search/PointInRectQuery.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/search/PointInRectQuery.java
@@ -26,8 +26,8 @@ import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.util.DocIdSetBuilder;
-import org.apache.lucene.util.GeoUtils;
import org.apache.lucene.util.NumericUtils;
+import org.apache.lucene.spatial.util.GeoUtils;
/** Finds all previously indexed points that fall within the specified boundings box.
*
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/util/GeoDistanceUtils.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/util/GeoDistanceUtils.java b/lucene/sandbox/src/java/org/apache/lucene/util/GeoDistanceUtils.java
deleted file mode 100644
index 9134709..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/util/GeoDistanceUtils.java
+++ /dev/null
@@ -1,217 +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.util;
-
-import static org.apache.lucene.util.SloppyMath.TO_RADIANS;
-
-/**
- * Reusable geo-spatial distance utility methods.
- *
- * @lucene.experimental
- */
-public class GeoDistanceUtils {
- /** error threshold for point-distance queries (in percent) NOTE: Guideline from USGS is 0.005 **/
- public static final double DISTANCE_PCT_ERR = 0.005;
-
- /**
- * Compute the great-circle distance using original haversine implementation published by Sinnot in:
- * R.W. Sinnott, "Virtues of the Haversine", Sky and Telescope, vol. 68, no. 2, 1984, p. 159
- *
- * NOTE: this differs from {@link org.apache.lucene.util.SloppyMath#haversin} in that it uses the semi-major axis
- * of the earth instead of an approximation based on the average latitude of the two points (which can introduce an
- * additional error up to .337%, or ~67.6 km at the equator)
- */
- public static double haversin(double lat1, double lon1, double lat2, double lon2) {
- double dLat = TO_RADIANS * (lat2 - lat1);
- double dLon = TO_RADIANS * (lon2 - lon1);
- lat1 = TO_RADIANS * (lat1);
- lat2 = TO_RADIANS * (lat2);
-
- final double sinDLatO2 = SloppyMath.sin(dLat / 2);
- final double sinDLonO2 = SloppyMath.sin(dLon / 2);
-
- double a = sinDLatO2*sinDLatO2 + sinDLonO2 * sinDLonO2 * SloppyMath.cos(lat1) * SloppyMath.cos(lat2);
- double c = 2 * SloppyMath.asin(Math.sqrt(a));
- return (GeoProjectionUtils.SEMIMAJOR_AXIS * c);
- }
-
- /**
- * Compute the distance between two geo-points using vincenty distance formula
- * Vincenty uses the oblate spheroid whereas haversine uses unit sphere, this will give roughly
- * 22m better accuracy (in worst case) than haversine
- *
- * @param lonA longitudinal coordinate of point A (in degrees)
- * @param latA latitudinal coordinate of point A (in degrees)
- * @param lonB longitudinal coordinate of point B (in degrees)
- * @param latB latitudinal coordinate of point B (in degrees)
- * @return distance (in meters) between point A and point B
- */
- public static final double vincentyDistance(final double lonA, final double latA, final double lonB, final double latB) {
- final double L = StrictMath.toRadians(lonB - lonA);
- final double oF = 1 - GeoProjectionUtils.FLATTENING;
- final double U1 = StrictMath.atan(oF * StrictMath.tan(StrictMath.toRadians(latA)));
- final double U2 = StrictMath.atan(oF * StrictMath.tan(StrictMath.toRadians(latB)));
- final double sU1 = StrictMath.sin(U1);
- final double cU1 = StrictMath.cos(U1);
- final double sU2 = StrictMath.sin(U2);
- final double cU2 = StrictMath.cos(U2);
-
- double sigma, sinSigma, cosSigma;
- double sinAlpha, cos2Alpha, cos2SigmaM;
- double lambda = L;
- double lambdaP;
- double iters = 100;
- double sinLambda, cosLambda, c;
-
- do {
- sinLambda = StrictMath.sin(lambda);
- cosLambda = Math.cos(lambda);
- sinSigma = Math.sqrt((cU2 * sinLambda) * (cU2 * sinLambda) + (cU1 * sU2 - sU1 * cU2 * cosLambda)
- * (cU1 * sU2 - sU1 * cU2 * cosLambda));
- if (sinSigma == 0) {
- return 0;
- }
-
- cosSigma = sU1 * sU2 + cU1 * cU2 * cosLambda;
- sigma = Math.atan2(sinSigma, cosSigma);
- sinAlpha = cU1 * cU2 * sinLambda / sinSigma;
- cos2Alpha = 1 - sinAlpha * sinAlpha;
- cos2SigmaM = cosSigma - 2 * sU1 * sU2 / cos2Alpha;
-
- c = GeoProjectionUtils.FLATTENING/16 * cos2Alpha * (4 + GeoProjectionUtils.FLATTENING * (4 - 3 * cos2Alpha));
- lambdaP = lambda;
- lambda = L + (1 - c) * GeoProjectionUtils.FLATTENING * sinAlpha * (sigma + c * sinSigma * (cos2SigmaM + c * cosSigma *
- (-1 + 2 * cos2SigmaM * cos2SigmaM)));
- } while (StrictMath.abs(lambda - lambdaP) > 1E-12 && --iters > 0);
-
- if (iters == 0) {
- return 0;
- }
-
- final double uSq = cos2Alpha * (GeoProjectionUtils.SEMIMAJOR_AXIS2 - GeoProjectionUtils.SEMIMINOR_AXIS2) / (GeoProjectionUtils.SEMIMINOR_AXIS2);
- final double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
- final double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
- final double deltaSigma = B * sinSigma * (cos2SigmaM + B/4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B/6 * cos2SigmaM
- * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
-
- return (GeoProjectionUtils.SEMIMINOR_AXIS * A * (sigma - deltaSigma));
- }
-
- /**
- * Computes distance between two points in a cartesian (x, y, {z - optional}) coordinate system
- */
- public static double linearDistance(double[] pt1, double[] pt2) {
- assert pt1 != null && pt2 != null && pt1.length == pt2.length && pt1.length > 1;
- final double d0 = pt1[0] - pt2[0];
- final double d1 = pt1[1] - pt2[1];
- if (pt1.length == 3) {
- final double d2 = pt1[2] - pt2[2];
- return Math.sqrt(d0*d0 + d1*d1 + d2*d2);
- }
- return Math.sqrt(d0*d0 + d1*d1);
- }
-
- /**
- * Compute the inverse haversine to determine distance in degrees longitude for provided distance in meters
- * @param lat latitude to compute delta degrees lon
- * @param distance distance in meters to convert to degrees lon
- * @return Sloppy distance in degrees longitude for provided distance in meters
- */
- public static double distanceToDegreesLon(double lat, double distance) {
- distance /= 1000.0;
- // convert latitude to radians
- lat = StrictMath.toRadians(lat);
-
- // get the diameter at the latitude
- final double diameter = SloppyMath.earthDiameter(StrictMath.toRadians(lat));
-
- // compute inverse haversine
- double a = StrictMath.sin(distance/diameter);
- double h = StrictMath.min(1, a);
- h *= h;
- double cLat = StrictMath.cos(lat);
-
- return StrictMath.toDegrees(StrictMath.acos(1-((2d*h)/(cLat*cLat))));
- }
-
- /**
- * Finds the closest point within a rectangle (defined by rMinX, rMinY, rMaxX, rMaxY) to the given (lon, lat) point
- * the result is provided in closestPt. When the point is outside the rectangle, the closest point is on an edge
- * or corner of the rectangle; else, the closest point is the point itself.
- */
- public static void closestPointOnBBox(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
- final double lon, final double lat, double[] closestPt) {
- assert closestPt != null && closestPt.length == 2;
-
- closestPt[0] = 0;
- closestPt[1] = 0;
-
- boolean xSet = true;
- boolean ySet = true;
-
- if (lon > rMaxX) {
- closestPt[0] = rMaxX;
- } else if (lon < rMinX) {
- closestPt[0] = rMinX;
- } else {
- xSet = false;
- }
-
- if (lat > rMaxY) {
- closestPt[1] = rMaxY;
- } else if (lat < rMinY) {
- closestPt[1] = rMinY;
- } else {
- ySet = false;
- }
-
- if (closestPt[0] == 0 && xSet == false) {
- closestPt[0] = lon;
- }
-
- if (closestPt[1] == 0 && ySet == false) {
- closestPt[1] = lat;
- }
- }
-
- /** Returns the maximum distance/radius (in meters) from the point 'center' before overlapping */
- public static double maxRadialDistanceMeters(final double centerLon, final double centerLat) {
- if (Math.abs(centerLat) == GeoUtils.MAX_LAT_INCL) {
- return GeoDistanceUtils.haversin(centerLat, centerLon, 0, centerLon);
- }
- return GeoDistanceUtils.haversin(centerLat, centerLon, centerLat, (GeoUtils.MAX_LON_INCL + centerLon) % 360);
- }
-
- /**
- * Compute the inverse haversine to determine distance in degrees longitude for provided distance in meters
- * @param lat latitude to compute delta degrees lon
- * @param distance distance in meters to convert to degrees lon
- * @return Sloppy distance in degrees longitude for provided distance in meters
- */
- public static double distanceToDegreesLat(double lat, double distance) {
- // get the diameter at the latitude
- final double diameter = SloppyMath.earthDiameter(StrictMath.toRadians(lat));
- distance /= 1000.0;
-
- // compute inverse haversine
- double a = StrictMath.sin(distance/diameter);
- double h = StrictMath.min(1, a);
- h *= h;
-
- return StrictMath.toDegrees(StrictMath.acos(1-(2d*h)));
- }
-}
[3/6] lucene-solr git commit: LUCENE-6997: refactor sandboxed
GeoPointField and query classes to lucene-spatial module
Posted by nk...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQueryImpl.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQueryImpl.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQueryImpl.java
new file mode 100644
index 0000000..bd44b1e
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQueryImpl.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.spatial.search;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.search.MultiTermQuery;
+import org.apache.lucene.util.AttributeSource;
+import org.apache.lucene.util.SloppyMath;
+import org.apache.lucene.spatial.document.GeoPointField;
+import org.apache.lucene.spatial.util.GeoRelationUtils;
+
+/** Package private implementation for the public facing GeoPointInBBoxQuery delegate class.
+ *
+ * @lucene.experimental
+ */
+class GeoPointInBBoxQueryImpl extends GeoPointTermQuery {
+ /**
+ * Constructs a new GeoBBoxQuery that will match encoded GeoPoint terms that fall within or on the boundary
+ * of the bounding box defined by the input parameters
+ * @param field the field name
+ * @param minLon lower longitude (x) value of the bounding box
+ * @param minLat lower latitude (y) value of the bounding box
+ * @param maxLon upper longitude (x) value of the bounding box
+ * @param maxLat upper latitude (y) value of the bounding box
+ */
+ GeoPointInBBoxQueryImpl(final String field, final double minLon, final double minLat, final double maxLon, final double maxLat) {
+ super(field, minLon, minLat, maxLon, maxLat);
+ }
+
+ @Override @SuppressWarnings("unchecked")
+ protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
+ return new GeoPointInBBoxTermsEnum(terms.iterator(), minLon, minLat, maxLon, maxLat);
+ }
+
+ @Override
+ public void setRewriteMethod(MultiTermQuery.RewriteMethod method) {
+ throw new UnsupportedOperationException("cannot change rewrite method");
+ }
+
+ protected class GeoPointInBBoxTermsEnum extends GeoPointTermsEnum {
+ protected GeoPointInBBoxTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
+ final double maxLon, final double maxLat) {
+ super(tenum, minLon, minLat, maxLon, maxLat);
+ }
+
+ @Override
+ protected short computeMaxShift() {
+ final short shiftFactor;
+
+ // compute diagonal radius
+ double midLon = (minLon + maxLon) * 0.5;
+ double midLat = (minLat + maxLat) * 0.5;
+
+ if (SloppyMath.haversin(minLat, minLon, midLat, midLon)*1000 > 1000000) {
+ shiftFactor = 5;
+ } else {
+ shiftFactor = 4;
+ }
+
+ return (short)(GeoPointField.PRECISION_STEP * shiftFactor);
+ }
+
+ /**
+ * Determine whether the quad-cell crosses the shape
+ */
+ @Override
+ protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+ return GeoRelationUtils.rectCrosses(minLon, minLat, maxLon, maxLat, this.minLon, this.minLat, this.maxLon, this.maxLat);
+ }
+
+ /**
+ * Determine whether quad-cell is within the shape
+ */
+ @Override
+ protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+ return GeoRelationUtils.rectWithin(minLon, minLat, maxLon, maxLat, this.minLon, this.minLat, this.maxLon, this.maxLat);
+ }
+
+ @Override
+ protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+ return cellIntersectsMBR(minLon, minLat, maxLon, maxLat);
+ }
+
+ @Override
+ protected boolean postFilter(final double lon, final double lat) {
+ return GeoRelationUtils.pointInRectPrecise(lon, lat, minLon, minLat, maxLon, maxLat);
+ }
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked","rawtypes"})
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ GeoPointInBBoxQueryImpl that = (GeoPointInBBoxQueryImpl) o;
+
+ if (Double.compare(that.maxLat, maxLat) != 0) return false;
+ if (Double.compare(that.maxLon, maxLon) != 0) return false;
+ if (Double.compare(that.minLat, minLat) != 0) return false;
+ if (Double.compare(that.minLon, minLon) != 0) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ long temp;
+ temp = Double.doubleToLongBits(minLon);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(minLat);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(maxLon);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(maxLat);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ @Override
+ public String toString(String field) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(getClass().getSimpleName());
+ sb.append(':');
+ if (!getField().equals(field)) {
+ sb.append(" field=");
+ sb.append(getField());
+ sb.append(':');
+ }
+ return sb.append(" Lower Left: [")
+ .append(minLon)
+ .append(',')
+ .append(minLat)
+ .append(']')
+ .append(" Upper Right: [")
+ .append(maxLon)
+ .append(',')
+ .append(maxLat)
+ .append("]")
+ .toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInPolygonQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInPolygonQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInPolygonQuery.java
new file mode 100644
index 0000000..b1e864b
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInPolygonQuery.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.spatial.search;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.util.AttributeSource;
+import org.apache.lucene.spatial.util.GeoRect;
+import org.apache.lucene.spatial.util.GeoRelationUtils;
+import org.apache.lucene.spatial.util.GeoUtils;
+
+/** Implements a simple point in polygon query on a GeoPoint field. This is based on
+ * {@code GeoPointInBBoxQueryImpl} and is implemented using a
+ * three phase approach. First, like {@code GeoPointInBBoxQueryImpl}
+ * candidate terms are queried using a numeric range based on the morton codes
+ * of the min and max lat/lon pairs. Terms passing this initial filter are passed
+ * to a secondary filter that verifies whether the decoded lat/lon point falls within
+ * (or on the boundary) of the bounding box query. Finally, the remaining candidate
+ * term is passed to the final point in polygon check. All value comparisons are subject
+ * to the same precision tolerance defined in {@value org.apache.lucene.spatial.util.GeoUtils#TOLERANCE}
+ *
+ * <p>NOTES:
+ * 1. The polygon coordinates need to be in either clockwise or counter-clockwise order.
+ * 2. The polygon must not be self-crossing, otherwise the query may result in unexpected behavior
+ * 3. All latitude/longitude values must be in decimal degrees.
+ * 4. Complex computational geometry (e.g., dateline wrapping, polygon with holes) is not supported
+ * 5. For more advanced GeoSpatial indexing and query operations see spatial module
+ *
+ * @lucene.experimental
+ */
+public final class GeoPointInPolygonQuery extends GeoPointInBBoxQueryImpl {
+ // polygon position arrays - this avoids the use of any objects or
+ // or geo library dependencies
+ private final double[] x;
+ private final double[] y;
+
+ /**
+ * Constructs a new GeoPolygonQuery that will match encoded {@link org.apache.lucene.spatial.document.GeoPointField} terms
+ * that fall within or on the boundary of the polygon defined by the input parameters.
+ */
+ public GeoPointInPolygonQuery(final String field, final double[] polyLons, final double[] polyLats) {
+ this(field, GeoUtils.polyToBBox(polyLons, polyLats), polyLons, polyLats);
+ }
+
+ /** Common constructor, used only internally. */
+ private GeoPointInPolygonQuery(final String field, GeoRect bbox, final double[] polyLons, final double[] polyLats) {
+ super(field, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
+ if (polyLats.length != polyLons.length) {
+ throw new IllegalArgumentException("polyLats and polyLons must be equal length");
+ }
+ if (polyLats.length < 4) {
+ throw new IllegalArgumentException("at least 4 polygon points required");
+ }
+ if (polyLats[0] != polyLats[polyLats.length-1]) {
+ throw new IllegalArgumentException("first and last points of the polygon must be the same (it must close itself): polyLats[0]=" + polyLats[0] + " polyLats[" + (polyLats.length-1) + "]=" + polyLats[polyLats.length-1]);
+ }
+ if (polyLons[0] != polyLons[polyLons.length-1]) {
+ throw new IllegalArgumentException("first and last points of the polygon must be the same (it must close itself): polyLons[0]=" + polyLons[0] + " polyLons[" + (polyLons.length-1) + "]=" + polyLons[polyLons.length-1]);
+ }
+
+ this.x = polyLons;
+ this.y = polyLats;
+ }
+
+ @Override @SuppressWarnings("unchecked")
+ protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
+ return new GeoPolygonTermsEnum(terms.iterator(), this.minLon, this.minLat, this.maxLon, this.maxLat);
+ }
+
+ /** throw exception if trying to change rewrite method */
+ @Override
+ public void setRewriteMethod(RewriteMethod method) {
+ throw new UnsupportedOperationException("cannot change rewrite method");
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ GeoPointInPolygonQuery that = (GeoPointInPolygonQuery) o;
+
+ if (!Arrays.equals(x, that.x)) return false;
+ if (!Arrays.equals(y, that.y)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (x != null ? Arrays.hashCode(x) : 0);
+ result = 31 * result + (y != null ? Arrays.hashCode(y) : 0);
+ return result;
+ }
+
+ /** print out this polygon query */
+ @Override
+ public String toString(String field) {
+ assert x.length == y.length;
+
+ final StringBuilder sb = new StringBuilder();
+ sb.append(getClass().getSimpleName());
+ sb.append(':');
+ if (!getField().equals(field)) {
+ sb.append(" field=");
+ sb.append(getField());
+ sb.append(':');
+ }
+ sb.append(" Points: ");
+ for (int i=0; i<x.length; ++i) {
+ sb.append("[")
+ .append(x[i])
+ .append(", ")
+ .append(y[i])
+ .append("] ");
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Custom {@link org.apache.lucene.index.TermsEnum} that computes morton hash ranges based on the defined edges of
+ * the provided polygon.
+ */
+ private final class GeoPolygonTermsEnum extends GeoPointTermsEnum {
+ GeoPolygonTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
+ final double maxLon, final double maxLat) {
+ super(tenum, minLon, minLat, maxLon, maxLat);
+ }
+
+ @Override
+ protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+ return GeoRelationUtils.rectCrossesPolyApprox(minLon, minLat, maxLon, maxLat, x, y, GeoPointInPolygonQuery.this.minLon,
+ GeoPointInPolygonQuery.this.minLat, GeoPointInPolygonQuery.this.maxLon, GeoPointInPolygonQuery.this.maxLat);
+ }
+
+ @Override
+ protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+ return GeoRelationUtils.rectWithinPolyApprox(minLon, minLat, maxLon, maxLat, x, y, GeoPointInPolygonQuery.this.minLon,
+ GeoPointInPolygonQuery.this.minLat, GeoPointInPolygonQuery.this.maxLon, GeoPointInPolygonQuery.this.maxLat);
+ }
+
+ @Override
+ protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+ return cellContains(minLon, minLat, maxLon, maxLat) || cellWithin(minLon, minLat, maxLon, maxLat)
+ || cellCrosses(minLon, minLat, maxLon, maxLat);
+ }
+
+ /**
+ * The two-phase query approach. The parent
+ * {@link GeoPointTermsEnum#accept} method is called to match
+ * encoded terms that fall within the bounding box of the polygon. Those documents that pass the initial
+ * bounding box filter are then compared to the provided polygon using the
+ * {@link org.apache.lucene.spatial.util.GeoRelationUtils#pointInPolygon} method.
+ */
+ @Override
+ protected boolean postFilter(final double lon, final double lat) {
+ return GeoRelationUtils.pointInPolygon(x, y, lat, lon);
+ }
+ }
+
+ /**
+ * API utility method for returning the array of longitudinal values for this GeoPolygon
+ * The returned array is not a copy so do not change it!
+ */
+ public double[] getLons() {
+ return this.x;
+ }
+
+ /**
+ * API utility method for returning the array of latitudinal values for this GeoPolygon
+ * The returned array is not a copy so do not change it!
+ */
+ public double[] getLats() {
+ return this.y;
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQuery.java
new file mode 100644
index 0000000..894a1e9
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQuery.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.spatial.search;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.search.MultiTermQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.spatial.util.GeoUtils;
+import org.apache.lucene.util.AttributeSource;
+
+/**
+ * TermQuery for GeoPointField for overriding {@link org.apache.lucene.search.MultiTermQuery} methods specific to
+ * Geospatial operations
+ *
+ * @lucene.experimental
+ */
+abstract class GeoPointTermQuery extends MultiTermQuery {
+ // simple bounding box optimization - no objects used to avoid dependencies
+ /** minimum longitude value (in degrees) */
+ protected final double minLon;
+ /** minimum latitude value (in degrees) */
+ protected final double minLat;
+ /** maximum longitude value (in degrees) */
+ protected final double maxLon;
+ /** maximum latitude value (in degrees) */
+ protected final double maxLat;
+
+ /**
+ * Constructs a query matching terms that cannot be represented with a single
+ * Term.
+ */
+ public GeoPointTermQuery(String field, final double minLon, final double minLat, final double maxLon, final double maxLat) {
+ super(field);
+
+ if (GeoUtils.isValidLon(minLon) == false) {
+ throw new IllegalArgumentException("invalid minLon " + minLon);
+ }
+ if (GeoUtils.isValidLon(maxLon) == false) {
+ throw new IllegalArgumentException("invalid maxLon " + maxLon);
+ }
+ if (GeoUtils.isValidLat(minLat) == false) {
+ throw new IllegalArgumentException("invalid minLat " + minLat);
+ }
+ if (GeoUtils.isValidLat(maxLat) == false) {
+ throw new IllegalArgumentException("invalid maxLat " + maxLat);
+ }
+ this.minLon = minLon;
+ this.minLat = minLat;
+ this.maxLon = maxLon;
+ this.maxLat = maxLat;
+
+ this.rewriteMethod = GEO_CONSTANT_SCORE_REWRITE;
+ }
+
+ private static final RewriteMethod GEO_CONSTANT_SCORE_REWRITE = new RewriteMethod() {
+ @Override
+ public Query rewrite(IndexReader reader, MultiTermQuery query) {
+ return new GeoPointTermQueryConstantScoreWrapper<>((GeoPointTermQuery)query);
+ }
+ };
+
+ /** override package protected method */
+ @Override
+ protected abstract TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException;
+
+ /** check if this instance equals another instance */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ GeoPointTermQuery that = (GeoPointTermQuery) o;
+
+ if (Double.compare(that.minLon, minLon) != 0) return false;
+ if (Double.compare(that.minLat, minLat) != 0) return false;
+ if (Double.compare(that.maxLon, maxLon) != 0) return false;
+ return Double.compare(that.maxLat, maxLat) == 0;
+ }
+
+ /** compute hashcode */
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ long temp;
+ temp = Double.doubleToLongBits(minLon);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(minLat);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(maxLon);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(maxLat);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQueryConstantScoreWrapper.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQueryConstantScoreWrapper.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQueryConstantScoreWrapper.java
new file mode 100644
index 0000000..8176aec
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQueryConstantScoreWrapper.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.spatial.search;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.PostingsEnum;
+import org.apache.lucene.index.SortedNumericDocValues;
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.search.BulkScorer;
+import org.apache.lucene.search.ConstantScoreScorer;
+import org.apache.lucene.search.ConstantScoreWeight;
+import org.apache.lucene.search.DocIdSet;
+import org.apache.lucene.search.DocIdSetIterator;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.Weight;
+import org.apache.lucene.util.DocIdSetBuilder;
+import org.apache.lucene.spatial.util.GeoUtils;
+
+/**
+ * Custom ConstantScoreWrapper for {@code GeoPointTermQuery} that cuts over to DocValues
+ * for post filtering boundary ranges. Multi-valued GeoPoint documents are supported.
+ *
+ * @lucene.experimental
+ */
+final class GeoPointTermQueryConstantScoreWrapper <Q extends GeoPointTermQuery> extends Query {
+ protected final Q query;
+
+ protected GeoPointTermQueryConstantScoreWrapper(Q query) {
+ this.query = query;
+ }
+
+ @Override
+ public String toString(String field) {
+ return query.toString();
+ }
+
+ @Override
+ public final boolean equals(final Object o) {
+ if (super.equals(o) == false) {
+ return false;
+ }
+ final GeoPointTermQueryConstantScoreWrapper<?> that = (GeoPointTermQueryConstantScoreWrapper<?>) o;
+ return this.query.equals(that.query);
+ }
+
+ @Override
+ public final int hashCode() {
+ return 31 * super.hashCode() + query.hashCode();
+ }
+
+ @Override
+ public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
+ return new ConstantScoreWeight(this) {
+
+ private DocIdSet getDocIDs(LeafReaderContext context) throws IOException {
+ final Terms terms = context.reader().terms(query.getField());
+ if (terms == null) {
+ return DocIdSet.EMPTY;
+ }
+
+ final GeoPointTermsEnum termsEnum = (GeoPointTermsEnum)(query.getTermsEnum(terms, null));
+ assert termsEnum != null;
+
+ LeafReader reader = context.reader();
+ DocIdSetBuilder builder = new DocIdSetBuilder(reader.maxDoc());
+ PostingsEnum docs = null;
+ SortedNumericDocValues sdv = reader.getSortedNumericDocValues(query.getField());
+
+ while (termsEnum.next() != null) {
+ docs = termsEnum.postings(docs, PostingsEnum.NONE);
+ // boundary terms need post filtering by
+ if (termsEnum.boundaryTerm()) {
+ int docId = docs.nextDoc();
+ long hash;
+ do {
+ sdv.setDocument(docId);
+ for (int i=0; i<sdv.count(); ++i) {
+ hash = sdv.valueAt(i);
+ if (termsEnum.postFilter(GeoUtils.mortonUnhashLon(hash), GeoUtils.mortonUnhashLat(hash))) {
+ builder.add(docId);
+ break;
+ }
+ }
+ } while ((docId = docs.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS);
+ } else {
+ builder.add(docs);
+ }
+ }
+
+ return builder.build();
+ }
+
+ private Scorer scorer(DocIdSet set) throws IOException {
+ if (set == null) {
+ return null;
+ }
+ final DocIdSetIterator disi = set.iterator();
+ if (disi == null) {
+ return null;
+ }
+ return new ConstantScoreScorer(this, score(), disi);
+ }
+
+ @Override
+ public BulkScorer bulkScorer(LeafReaderContext context) throws IOException {
+ final Scorer scorer = scorer(getDocIDs(context));
+ if (scorer == null) {
+ return null;
+ }
+ return new DefaultBulkScorer(scorer);
+ }
+
+ @Override
+ public Scorer scorer(LeafReaderContext context) throws IOException {
+ return scorer(getDocIDs(context));
+ }
+ };
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermsEnum.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermsEnum.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermsEnum.java
new file mode 100644
index 0000000..71eb26e
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermsEnum.java
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.spatial.search;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.lucene.spatial.document.GeoPointField;
+import org.apache.lucene.index.FilteredTermsEnum;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefBuilder;
+import org.apache.lucene.spatial.util.GeoRelationUtils;
+import org.apache.lucene.spatial.util.GeoUtils;
+import org.apache.lucene.util.LegacyNumericUtils;
+
+/**
+ * computes all ranges along a space-filling curve that represents
+ * the given bounding box and enumerates all terms contained within those ranges
+ *
+ * @lucene.experimental
+ */
+abstract class GeoPointTermsEnum extends FilteredTermsEnum {
+ protected final double minLon;
+ protected final double minLat;
+ protected final double maxLon;
+ protected final double maxLat;
+
+ protected Range currentRange;
+ private final BytesRefBuilder currentCell = new BytesRefBuilder();
+ private final BytesRefBuilder nextSubRange = new BytesRefBuilder();
+
+ private final List<Range> rangeBounds = new LinkedList<>();
+
+ // detail level should be a factor of PRECISION_STEP limiting the depth of recursion (and number of ranges)
+ protected final short DETAIL_LEVEL;
+
+ GeoPointTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
+ final double maxLon, final double maxLat) {
+ super(tenum);
+ final long rectMinHash = GeoUtils.mortonHash(minLon, minLat);
+ final long rectMaxHash = GeoUtils.mortonHash(maxLon, maxLat);
+ this.minLon = GeoUtils.mortonUnhashLon(rectMinHash);
+ this.minLat = GeoUtils.mortonUnhashLat(rectMinHash);
+ this.maxLon = GeoUtils.mortonUnhashLon(rectMaxHash);
+ this.maxLat = GeoUtils.mortonUnhashLat(rectMaxHash);
+ DETAIL_LEVEL = (short)(((GeoUtils.BITS<<1)-computeMaxShift())/2);
+
+ computeRange(0L, (short) ((GeoUtils.BITS << 1) - 1));
+ assert rangeBounds.isEmpty() == false;
+ Collections.sort(rangeBounds);
+ }
+
+ /**
+ * entry point for recursively computing ranges
+ */
+ private final void computeRange(long term, final short shift) {
+ final long split = term | (0x1L<<shift);
+ assert shift < 64;
+ final long upperMax;
+ if (shift < 63) {
+ upperMax = term | ((1L << (shift+1))-1);
+ } else {
+ upperMax = 0xffffffffffffffffL;
+ }
+ final long lowerMax = split-1;
+
+ relateAndRecurse(term, lowerMax, shift);
+ relateAndRecurse(split, upperMax, shift);
+ }
+
+ /**
+ * recurse to higher level precision cells to find ranges along the space-filling curve that fall within the
+ * query box
+ *
+ * @param start starting value on the space-filling curve for a cell at a given res
+ * @param end ending value on the space-filling curve for a cell at a given res
+ * @param res spatial res represented as a bit shift (MSB is lower res)
+ */
+ private void relateAndRecurse(final long start, final long end, final short res) {
+ final double minLon = GeoUtils.mortonUnhashLon(start);
+ final double minLat = GeoUtils.mortonUnhashLat(start);
+ final double maxLon = GeoUtils.mortonUnhashLon(end);
+ final double maxLat = GeoUtils.mortonUnhashLat(end);
+
+ final short level = (short)((GeoUtils.BITS<<1)-res>>>1);
+
+ // if cell is within and a factor of the precision step, or it crosses the edge of the shape add the range
+ final boolean within = res % GeoPointField.PRECISION_STEP == 0 && cellWithin(minLon, minLat, maxLon, maxLat);
+ if (within || (level == DETAIL_LEVEL && cellIntersectsShape(minLon, minLat, maxLon, maxLat))) {
+ final short nextRes = (short)(res-1);
+ if (nextRes % GeoPointField.PRECISION_STEP == 0) {
+ rangeBounds.add(new Range(start, nextRes, !within));
+ rangeBounds.add(new Range(start|(1L<<nextRes), nextRes, !within));
+ } else {
+ rangeBounds.add(new Range(start, res, !within));
+ }
+ } else if (level < DETAIL_LEVEL && cellIntersectsMBR(minLon, minLat, maxLon, maxLat)) {
+ computeRange(start, (short) (res - 1));
+ }
+ }
+
+ protected short computeMaxShift() {
+ // in this case a factor of 4 brings the detail level to ~0.002/0.001 degrees lon/lat respectively (or ~222m/111m)
+ return GeoPointField.PRECISION_STEP * 4;
+ }
+
+ /**
+ * Determine whether the quad-cell crosses the shape
+ */
+ protected abstract boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat);
+
+ /**
+ * Determine whether quad-cell is within the shape
+ */
+ protected abstract boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat);
+
+ /**
+ * Default shape is a rectangle, so this returns the same as {@code cellIntersectsMBR}
+ */
+ protected abstract boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat);
+
+ /**
+ * Primary driver for cells intersecting shape boundaries
+ */
+ protected boolean cellIntersectsMBR(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+ return GeoRelationUtils.rectIntersects(minLon, minLat, maxLon, maxLat, this.minLon, this.minLat, this.maxLon, this.maxLat);
+ }
+
+ /**
+ * Return whether quad-cell contains the bounding box of this shape
+ */
+ protected boolean cellContains(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+ return GeoRelationUtils.rectWithin(this.minLon, this.minLat, this.maxLon, this.maxLat, minLon, minLat, maxLon, maxLat);
+ }
+
+ public boolean boundaryTerm() {
+ if (currentRange == null) {
+ throw new IllegalStateException("GeoPointTermsEnum empty or not initialized");
+ }
+ return currentRange.boundary;
+ }
+
+ private void nextRange() {
+ currentRange = rangeBounds.remove(0);
+ currentRange.fillBytesRef(currentCell);
+ }
+
+ @Override
+ protected final BytesRef nextSeekTerm(BytesRef term) {
+ while (!rangeBounds.isEmpty()) {
+ if (currentRange == null) {
+ nextRange();
+ }
+
+ // if the new upper bound is before the term parameter, the sub-range is never a hit
+ if (term != null && term.compareTo(currentCell.get()) > 0) {
+ nextRange();
+ if (!rangeBounds.isEmpty()) {
+ continue;
+ }
+ }
+ // never seek backwards, so use current term if lower bound is smaller
+ return (term != null && term.compareTo(currentCell.get()) > 0) ?
+ term : currentCell.get();
+ }
+
+ // no more sub-range enums available
+ assert rangeBounds.isEmpty();
+ return null;
+ }
+
+ /**
+ * The two-phase query approach. {@link #nextSeekTerm} is called to obtain the next term that matches a numeric
+ * range of the bounding box. Those terms that pass the initial range filter are then compared against the
+ * decoded min/max latitude and longitude values of the bounding box only if the range is not a "boundary" range
+ * (e.g., a range that straddles the boundary of the bbox).
+ * @param term term for candidate document
+ * @return match status
+ */
+ @Override
+ protected AcceptStatus accept(BytesRef term) {
+ // validate value is in range
+ while (currentCell == null || term.compareTo(currentCell.get()) > 0) {
+ if (rangeBounds.isEmpty()) {
+ return AcceptStatus.END;
+ }
+ // peek next sub-range, only seek if the current term is smaller than next lower bound
+ rangeBounds.get(0).fillBytesRef(this.nextSubRange);
+ if (term.compareTo(this.nextSubRange.get()) < 0) {
+ return AcceptStatus.NO_AND_SEEK;
+ }
+ // step forward to next range without seeking, as next range is less or equal current term
+ nextRange();
+ }
+
+ return AcceptStatus.YES;
+ }
+
+ protected abstract boolean postFilter(final double lon, final double lat);
+
+ /**
+ * Internal class to represent a range along the space filling curve
+ */
+ protected final class Range implements Comparable<Range> {
+ final short shift;
+ final long start;
+ final boolean boundary;
+
+ Range(final long lower, final short shift, boolean boundary) {
+ this.boundary = boundary;
+ this.start = lower;
+ this.shift = shift;
+ }
+
+ /**
+ * Encode as a BytesRef using a reusable object. This allows us to lazily create the BytesRef (which is
+ * quite expensive), only when we need it.
+ */
+ private void fillBytesRef(BytesRefBuilder result) {
+ assert result != null;
+ LegacyNumericUtils.longToPrefixCoded(start, shift, result);
+ }
+
+ @Override
+ public int compareTo(Range other) {
+ final int result = Short.compare(this.shift, other.shift);
+ if (result == 0) {
+ return Long.compare(this.start, other.start);
+ }
+ return result;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/package-info.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/package-info.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/package-info.java
new file mode 100644
index 0000000..8e8265c
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Geospatial Query Implementations for Core Lucene
+ */
+package org.apache.lucene.spatial.search;
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoDistanceUtils.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoDistanceUtils.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoDistanceUtils.java
new file mode 100644
index 0000000..e845c9e
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoDistanceUtils.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.util.SloppyMath;
+
+import static org.apache.lucene.util.SloppyMath.TO_RADIANS;
+
+/**
+ * Reusable geo-spatial distance utility methods.
+ *
+ * @lucene.experimental
+ */
+public class GeoDistanceUtils {
+ /** error threshold for point-distance queries (in percent) NOTE: Guideline from USGS is 0.005 **/
+ public static final double DISTANCE_PCT_ERR = 0.005;
+
+ // No instance:
+ private GeoDistanceUtils() {
+ }
+
+ /**
+ * Compute the great-circle distance using original haversine implementation published by Sinnot in:
+ * R.W. Sinnott, "Virtues of the Haversine", Sky and Telescope, vol. 68, no. 2, 1984, p. 159
+ *
+ * NOTE: this differs from {@link org.apache.lucene.util.SloppyMath#haversin} in that it uses the semi-major axis
+ * of the earth instead of an approximation based on the average latitude of the two points (which can introduce an
+ * additional error up to .337%, or ~67.6 km at the equator)
+ */
+ public static double haversin(double lat1, double lon1, double lat2, double lon2) {
+ double dLat = TO_RADIANS * (lat2 - lat1);
+ double dLon = TO_RADIANS * (lon2 - lon1);
+ lat1 = TO_RADIANS * (lat1);
+ lat2 = TO_RADIANS * (lat2);
+
+ final double sinDLatO2 = SloppyMath.sin(dLat / 2);
+ final double sinDLonO2 = SloppyMath.sin(dLon / 2);
+
+ double a = sinDLatO2*sinDLatO2 + sinDLonO2 * sinDLonO2 * SloppyMath.cos(lat1) * SloppyMath.cos(lat2);
+ double c = 2 * SloppyMath.asin(Math.sqrt(a));
+ return (GeoProjectionUtils.SEMIMAJOR_AXIS * c);
+ }
+
+ /**
+ * Compute the distance between two geo-points using vincenty distance formula
+ * Vincenty uses the oblate spheroid whereas haversine uses unit sphere, this will give roughly
+ * 22m better accuracy (in worst case) than haversine
+ *
+ * @param lonA longitudinal coordinate of point A (in degrees)
+ * @param latA latitudinal coordinate of point A (in degrees)
+ * @param lonB longitudinal coordinate of point B (in degrees)
+ * @param latB latitudinal coordinate of point B (in degrees)
+ * @return distance (in meters) between point A and point B
+ */
+ public static final double vincentyDistance(final double lonA, final double latA, final double lonB, final double latB) {
+ final double L = StrictMath.toRadians(lonB - lonA);
+ final double oF = 1 - GeoProjectionUtils.FLATTENING;
+ final double U1 = StrictMath.atan(oF * StrictMath.tan(StrictMath.toRadians(latA)));
+ final double U2 = StrictMath.atan(oF * StrictMath.tan(StrictMath.toRadians(latB)));
+ final double sU1 = StrictMath.sin(U1);
+ final double cU1 = StrictMath.cos(U1);
+ final double sU2 = StrictMath.sin(U2);
+ final double cU2 = StrictMath.cos(U2);
+
+ double sigma, sinSigma, cosSigma;
+ double sinAlpha, cos2Alpha, cos2SigmaM;
+ double lambda = L;
+ double lambdaP;
+ double iters = 100;
+ double sinLambda, cosLambda, c;
+
+ do {
+ sinLambda = StrictMath.sin(lambda);
+ cosLambda = Math.cos(lambda);
+ sinSigma = Math.sqrt((cU2 * sinLambda) * (cU2 * sinLambda) + (cU1 * sU2 - sU1 * cU2 * cosLambda)
+ * (cU1 * sU2 - sU1 * cU2 * cosLambda));
+ if (sinSigma == 0) {
+ return 0;
+ }
+
+ cosSigma = sU1 * sU2 + cU1 * cU2 * cosLambda;
+ sigma = Math.atan2(sinSigma, cosSigma);
+ sinAlpha = cU1 * cU2 * sinLambda / sinSigma;
+ cos2Alpha = 1 - sinAlpha * sinAlpha;
+ cos2SigmaM = cosSigma - 2 * sU1 * sU2 / cos2Alpha;
+
+ c = GeoProjectionUtils.FLATTENING/16 * cos2Alpha * (4 + GeoProjectionUtils.FLATTENING * (4 - 3 * cos2Alpha));
+ lambdaP = lambda;
+ lambda = L + (1 - c) * GeoProjectionUtils.FLATTENING * sinAlpha * (sigma + c * sinSigma * (cos2SigmaM + c * cosSigma *
+ (-1 + 2 * cos2SigmaM * cos2SigmaM)));
+ } while (StrictMath.abs(lambda - lambdaP) > 1E-12 && --iters > 0);
+
+ if (iters == 0) {
+ return 0;
+ }
+
+ final double uSq = cos2Alpha * (GeoProjectionUtils.SEMIMAJOR_AXIS2 - GeoProjectionUtils.SEMIMINOR_AXIS2) / (GeoProjectionUtils.SEMIMINOR_AXIS2);
+ final double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
+ final double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
+ final double deltaSigma = B * sinSigma * (cos2SigmaM + B/4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B/6 * cos2SigmaM
+ * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
+
+ return (GeoProjectionUtils.SEMIMINOR_AXIS * A * (sigma - deltaSigma));
+ }
+
+ /**
+ * Computes distance between two points in a cartesian (x, y, {z - optional}) coordinate system
+ */
+ public static double linearDistance(double[] pt1, double[] pt2) {
+ assert pt1 != null && pt2 != null && pt1.length == pt2.length && pt1.length > 1;
+ final double d0 = pt1[0] - pt2[0];
+ final double d1 = pt1[1] - pt2[1];
+ if (pt1.length == 3) {
+ final double d2 = pt1[2] - pt2[2];
+ return Math.sqrt(d0*d0 + d1*d1 + d2*d2);
+ }
+ return Math.sqrt(d0*d0 + d1*d1);
+ }
+
+ /**
+ * Compute the inverse haversine to determine distance in degrees longitude for provided distance in meters
+ * @param lat latitude to compute delta degrees lon
+ * @param distance distance in meters to convert to degrees lon
+ * @return Sloppy distance in degrees longitude for provided distance in meters
+ */
+ public static double distanceToDegreesLon(double lat, double distance) {
+ distance /= 1000.0;
+ // convert latitude to radians
+ lat = StrictMath.toRadians(lat);
+
+ // get the diameter at the latitude
+ final double diameter = SloppyMath.earthDiameter(StrictMath.toRadians(lat));
+
+ // compute inverse haversine
+ double a = StrictMath.sin(distance/diameter);
+ double h = StrictMath.min(1, a);
+ h *= h;
+ double cLat = StrictMath.cos(lat);
+
+ return StrictMath.toDegrees(StrictMath.acos(1-((2d*h)/(cLat*cLat))));
+ }
+
+ /**
+ * Finds the closest point within a rectangle (defined by rMinX, rMinY, rMaxX, rMaxY) to the given (lon, lat) point
+ * the result is provided in closestPt. When the point is outside the rectangle, the closest point is on an edge
+ * or corner of the rectangle; else, the closest point is the point itself.
+ */
+ public static void closestPointOnBBox(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
+ final double lon, final double lat, double[] closestPt) {
+ assert closestPt != null && closestPt.length == 2;
+
+ closestPt[0] = 0;
+ closestPt[1] = 0;
+
+ boolean xSet = true;
+ boolean ySet = true;
+
+ if (lon > rMaxX) {
+ closestPt[0] = rMaxX;
+ } else if (lon < rMinX) {
+ closestPt[0] = rMinX;
+ } else {
+ xSet = false;
+ }
+
+ if (lat > rMaxY) {
+ closestPt[1] = rMaxY;
+ } else if (lat < rMinY) {
+ closestPt[1] = rMinY;
+ } else {
+ ySet = false;
+ }
+
+ if (closestPt[0] == 0 && xSet == false) {
+ closestPt[0] = lon;
+ }
+
+ if (closestPt[1] == 0 && ySet == false) {
+ closestPt[1] = lat;
+ }
+ }
+
+ /** Returns the maximum distance/radius (in meters) from the point 'center' before overlapping */
+ public static double maxRadialDistanceMeters(final double centerLon, final double centerLat) {
+ if (Math.abs(centerLat) == GeoUtils.MAX_LAT_INCL) {
+ return GeoDistanceUtils.haversin(centerLat, centerLon, 0, centerLon);
+ }
+ return GeoDistanceUtils.haversin(centerLat, centerLon, centerLat, (GeoUtils.MAX_LON_INCL + centerLon) % 360);
+ }
+
+ /**
+ * Compute the inverse haversine to determine distance in degrees longitude for provided distance in meters
+ * @param lat latitude to compute delta degrees lon
+ * @param distance distance in meters to convert to degrees lon
+ * @return Sloppy distance in degrees longitude for provided distance in meters
+ */
+ public static double distanceToDegreesLat(double lat, double distance) {
+ // get the diameter at the latitude
+ final double diameter = SloppyMath.earthDiameter(StrictMath.toRadians(lat));
+ distance /= 1000.0;
+
+ // compute inverse haversine
+ double a = StrictMath.sin(distance/diameter);
+ double h = StrictMath.min(1, a);
+ h *= h;
+
+ return StrictMath.toDegrees(StrictMath.acos(1-(2d*h)));
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoHashUtils.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoHashUtils.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoHashUtils.java
new file mode 100644
index 0000000..9450c1e
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoHashUtils.java
@@ -0,0 +1,283 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.lucene.util.BitUtil;
+
+/**
+ * Utilities for converting to/from the GeoHash standard
+ *
+ * The geohash long format is represented as lon/lat (x/y) interleaved with the 4 least significant bits
+ * representing the level (1-12) [xyxy...xyxyllll]
+ *
+ * This differs from a morton encoded value which interleaves lat/lon (y/x).
+ *
+ * @lucene.experimental
+ */
+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 static final String BASE_32_STRING = new String(BASE_32);
+
+ /** maximum precision for geohash strings */
+ public static final int PRECISION = 12;
+ private static final short MORTON_OFFSET = (GeoUtils.BITS<<1) - (PRECISION*5);
+
+ // No instance:
+ private GeoHashUtils() {
+ }
+
+ /**
+ * Encode lon/lat to the geohash based long format (lon/lat interleaved, 4 least significant bits = level)
+ */
+ public static final long longEncode(final double lon, final double lat, final int level) {
+ // shift to appropriate level
+ final short msf = (short)(((12 - level) * 5) + MORTON_OFFSET);
+ return ((BitUtil.flipFlop(GeoUtils.mortonHash(lon, lat)) >>> msf) << 4) | level;
+ }
+
+ /**
+ * Encode from geohash string to the geohash based long format (lon/lat interleaved, 4 least significant bits = level)
+ */
+ public static final long longEncode(final String hash) {
+ int level = hash.length()-1;
+ long b;
+ long l = 0L;
+ for(char c : hash.toCharArray()) {
+ b = (long)(BASE_32_STRING.indexOf(c));
+ l |= (b<<(level--*5));
+ }
+ return (l<<4)|hash.length();
+ }
+
+ /**
+ * Encode an existing geohash long to the provided precision
+ */
+ public static long longEncode(long geohash, int level) {
+ final short precision = (short)(geohash & 15);
+ if (precision == level) {
+ return geohash;
+ } else if (precision > level) {
+ return ((geohash >>> (((precision - level) * 5) + 4)) << 4) | level;
+ }
+ return ((geohash >>> 4) << (((level - precision) * 5) + 4) | level);
+ }
+
+ /**
+ * Convert from a morton encoded long from a geohash encoded long
+ */
+ public static long fromMorton(long morton, int level) {
+ long mFlipped = BitUtil.flipFlop(morton);
+ mFlipped >>>= (((GeoHashUtils.PRECISION - level) * 5) + MORTON_OFFSET);
+ return (mFlipped << 4) | level;
+ }
+
+ /**
+ * Encode to a geohash string from the geohash based long format
+ */
+ public static final String stringEncode(long geoHashLong) {
+ int level = (int)geoHashLong&15;
+ geoHashLong >>>= 4;
+ char[] chars = new char[level];
+ do {
+ chars[--level] = BASE_32[(int) (geoHashLong&31L)];
+ geoHashLong>>>=5;
+ } while(level > 0);
+
+ return new String(chars);
+ }
+
+ /**
+ * Encode to a geohash string from full resolution longitude, latitude)
+ */
+ public static final String stringEncode(final double lon, final double lat) {
+ return stringEncode(lon, lat, 12);
+ }
+
+ /**
+ * Encode to a level specific geohash string from full resolution longitude, latitude
+ */
+ public static final String stringEncode(final double lon, final double lat, final int level) {
+ // convert to geohashlong
+ final long ghLong = fromMorton(GeoUtils.mortonHash(lon, lat), level);
+ return stringEncode(ghLong);
+
+ }
+
+ /**
+ * Encode to a full precision geohash string from a given morton encoded long value
+ */
+ public static final String stringEncodeFromMortonLong(final long hashedVal) throws Exception {
+ return stringEncode(hashedVal, PRECISION);
+ }
+
+ /**
+ * Encode to a geohash string at a given level from a morton long
+ */
+ public static final String stringEncodeFromMortonLong(long hashedVal, final int level) {
+ // bit twiddle to geohash (since geohash is a swapped (lon/lat) encoding)
+ hashedVal = BitUtil.flipFlop(hashedVal);
+
+ StringBuilder geoHash = new StringBuilder();
+ short precision = 0;
+ final short msf = (GeoUtils.BITS<<1)-5;
+ long mask = 31L<<msf;
+ do {
+ geoHash.append(BASE_32[(int)((mask & hashedVal)>>>(msf-(precision*5)))]);
+ // next 5 bits
+ mask >>>= 5;
+ } while (++precision < level);
+ return geoHash.toString();
+ }
+
+ /**
+ * Encode to a morton long value from a given geohash string
+ */
+ public static final long mortonEncode(final String hash) {
+ int level = 11;
+ long b;
+ long l = 0L;
+ for(char c : hash.toCharArray()) {
+ b = (long)(BASE_32_STRING.indexOf(c));
+ l |= (b<<((level--*5) + MORTON_OFFSET));
+ }
+ return BitUtil.flipFlop(l);
+ }
+
+ /**
+ * Encode to a morton long value from a given geohash long value
+ */
+ public static final long mortonEncode(final long geoHashLong) {
+ final int level = (int)(geoHashLong&15);
+ final short odd = (short)(level & 1);
+
+ return BitUtil.flipFlop(((geoHashLong >>> 4) << odd) << (((12 - level) * 5) + (MORTON_OFFSET - odd)));
+ }
+
+ private static final char encode(int x, int y) {
+ return BASE_32[((x & 1) + ((y & 1) * 2) + ((x & 2) * 2) + ((y & 2) * 4) + ((x & 4) * 4)) % 32];
+ }
+
+ /**
+ * Calculate all neighbors of a given geohash cell.
+ *
+ * @param geohash Geohash of the defined cell
+ * @return geohashes of all neighbor cells
+ */
+ public static Collection<? extends CharSequence> neighbors(String geohash) {
+ return addNeighbors(geohash, geohash.length(), new ArrayList<CharSequence>(8));
+ }
+
+ /**
+ * Calculate the geohash of a neighbor of a geohash
+ *
+ * @param geohash the geohash of a cell
+ * @param level level of the geohash
+ * @param dx delta of the first grid coordinate (must be -1, 0 or +1)
+ * @param dy delta of the second grid coordinate (must be -1, 0 or +1)
+ * @return geohash of the defined cell
+ */
+ public final static String neighbor(String geohash, int level, int dx, int dy) {
+ int cell = BASE_32_STRING.indexOf(geohash.charAt(level -1));
+
+ // Decoding the Geohash bit pattern to determine grid coordinates
+ int x0 = cell & 1; // first bit of x
+ int y0 = cell & 2; // first bit of y
+ int x1 = cell & 4; // second bit of x
+ int y1 = cell & 8; // second bit of y
+ int x2 = cell & 16; // third bit of x
+
+ // combine the bitpattern to grid coordinates.
+ // note that the semantics of x and y are swapping
+ // on each level
+ int x = x0 + (x1 / 2) + (x2 / 4);
+ int y = (y0 / 2) + (y1 / 4);
+
+ if (level == 1) {
+ // Root cells at north (namely "bcfguvyz") or at
+ // south (namely "0145hjnp") do not have neighbors
+ // in north/south direction
+ if ((dy < 0 && y == 0) || (dy > 0 && y == 3)) {
+ return null;
+ } else {
+ return Character.toString(encode(x + dx, y + dy));
+ }
+ } else {
+ // define grid coordinates for next level
+ final int nx = ((level % 2) == 1) ? (x + dx) : (x + dy);
+ final int ny = ((level % 2) == 1) ? (y + dy) : (y + dx);
+
+ // if the defined neighbor has the same parent a the current cell
+ // encode the cell directly. Otherwise find the cell next to this
+ // cell recursively. Since encoding wraps around within a cell
+ // it can be encoded here.
+ // xLimit and YLimit must always be respectively 7 and 3
+ // since x and y semantics are swapping on each level.
+ if (nx >= 0 && nx <= 7 && ny >= 0 && ny <= 3) {
+ return geohash.substring(0, level - 1) + encode(nx, ny);
+ } else {
+ String neighbor = neighbor(geohash, level - 1, dx, dy);
+ return (neighbor != null) ? neighbor + encode(nx, ny) : neighbor;
+ }
+ }
+ }
+
+ /**
+ * Add all geohashes of the cells next to a given geohash to a list.
+ *
+ * @param geohash Geohash of a specified cell
+ * @param neighbors list to add the neighbors to
+ * @return the given list
+ */
+ public static final <E extends Collection<? super String>> E addNeighbors(String geohash, E neighbors) {
+ return addNeighbors(geohash, geohash.length(), neighbors);
+ }
+
+ /**
+ * Add all geohashes of the cells next to a given geohash to a list.
+ *
+ * @param geohash Geohash of a specified cell
+ * @param length level of the given geohash
+ * @param neighbors list to add the neighbors to
+ * @return the given list
+ */
+ public static final <E extends Collection<? super String>> E addNeighbors(String geohash, int length, E neighbors) {
+ String south = neighbor(geohash, length, 0, -1);
+ String north = neighbor(geohash, length, 0, +1);
+ if (north != null) {
+ neighbors.add(neighbor(north, length, -1, 0));
+ neighbors.add(north);
+ neighbors.add(neighbor(north, length, +1, 0));
+ }
+
+ neighbors.add(neighbor(geohash, length, -1, 0));
+ neighbors.add(neighbor(geohash, length, +1, 0));
+
+ if (south != null) {
+ neighbors.add(neighbor(south, length, -1, 0));
+ neighbors.add(south);
+ neighbors.add(neighbor(south, length, +1, 0));
+ }
+
+ return neighbors;
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoProjectionUtils.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoProjectionUtils.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoProjectionUtils.java
new file mode 100644
index 0000000..5a81adc
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoProjectionUtils.java
@@ -0,0 +1,465 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static java.lang.StrictMath.sqrt;
+
+import static org.apache.lucene.util.SloppyMath.asin;
+import static org.apache.lucene.util.SloppyMath.cos;
+import static org.apache.lucene.util.SloppyMath.sin;
+import static org.apache.lucene.util.SloppyMath.tan;
+import static org.apache.lucene.util.SloppyMath.PIO2;
+import static org.apache.lucene.util.SloppyMath.TO_DEGREES;
+import static org.apache.lucene.util.SloppyMath.TO_RADIANS;
+
+import static org.apache.lucene.spatial.util.GeoUtils.MAX_LAT_INCL;
+import static org.apache.lucene.spatial.util.GeoUtils.MAX_LON_INCL;
+import static org.apache.lucene.spatial.util.GeoUtils.MIN_LAT_INCL;
+import static org.apache.lucene.spatial.util.GeoUtils.MIN_LON_INCL;
+import static org.apache.lucene.spatial.util.GeoUtils.normalizeLat;
+import static org.apache.lucene.spatial.util.GeoUtils.normalizeLon;
+
+/**
+ * Reusable geo-spatial projection utility methods.
+ *
+ * @lucene.experimental
+ */
+public class GeoProjectionUtils {
+ // WGS84 earth-ellipsoid parameters
+ /** major (a) axis in meters */
+ public static final double SEMIMAJOR_AXIS = 6_378_137; // [m]
+ /** earth flattening factor (f) */
+ public static final double FLATTENING = 1.0/298.257223563;
+ /** minor (b) axis in meters */
+ public static final double SEMIMINOR_AXIS = SEMIMAJOR_AXIS * (1.0 - FLATTENING); //6_356_752.31420; // [m]
+ /** first eccentricity (e) */
+ public static final double ECCENTRICITY = sqrt((2.0 - FLATTENING) * FLATTENING);
+ /** major axis squared (a2) */
+ public static final double SEMIMAJOR_AXIS2 = SEMIMAJOR_AXIS * SEMIMAJOR_AXIS;
+ /** minor axis squared (b2) */
+ public static final double SEMIMINOR_AXIS2 = SEMIMINOR_AXIS * SEMIMINOR_AXIS;
+ private static final double E2 = (SEMIMAJOR_AXIS2 - SEMIMINOR_AXIS2)/(SEMIMAJOR_AXIS2);
+ private static final double EP2 = (SEMIMAJOR_AXIS2 - SEMIMINOR_AXIS2)/(SEMIMINOR_AXIS2);
+
+ /** min longitude value in radians */
+ public static final double MIN_LON_RADIANS = TO_RADIANS * MIN_LON_INCL;
+ /** min latitude value in radians */
+ public static final double MIN_LAT_RADIANS = TO_RADIANS * MIN_LAT_INCL;
+ /** max longitude value in radians */
+ public static final double MAX_LON_RADIANS = TO_RADIANS * MAX_LON_INCL;
+ /** max latitude value in radians */
+ public static final double MAX_LAT_RADIANS = TO_RADIANS * MAX_LAT_INCL;
+
+ // No instance:
+ private GeoProjectionUtils() {
+ }
+
+ /**
+ * Converts from geocentric earth-centered earth-fixed to geodesic lat/lon/alt
+ * @param x Cartesian x coordinate
+ * @param y Cartesian y coordinate
+ * @param z Cartesian z coordinate
+ * @param lla 0: longitude 1: latitude: 2: altitude
+ * @return double array as 0: longitude 1: latitude 2: altitude
+ */
+ public static final double[] ecfToLLA(final double x, final double y, final double z, double[] lla) {
+ boolean atPole = false;
+ final double ad_c = 1.0026000D;
+ final double cos67P5 = 0.38268343236508977D;
+
+ if (lla == null) {
+ lla = new double[3];
+ }
+
+ if (x != 0.0) {
+ lla[0] = StrictMath.atan2(y,x);
+ } else {
+ if (y > 0) {
+ lla[0] = PIO2;
+ } else if (y < 0) {
+ lla[0] = -PIO2;
+ } else {
+ atPole = true;
+ lla[0] = 0.0D;
+ if (z > 0.0) {
+ lla[1] = PIO2;
+ } else if (z < 0.0) {
+ lla[1] = -PIO2;
+ } else {
+ lla[1] = PIO2;
+ lla[2] = -SEMIMINOR_AXIS;
+ return lla;
+ }
+ }
+ }
+
+ final double w2 = x*x + y*y;
+ final double w = StrictMath.sqrt(w2);
+ final double t0 = z * ad_c;
+ final double s0 = StrictMath.sqrt(t0 * t0 + w2);
+ final double sinB0 = t0 / s0;
+ final double cosB0 = w / s0;
+ final double sin3B0 = sinB0 * sinB0 * sinB0;
+ final double t1 = z + SEMIMINOR_AXIS * EP2 * sin3B0;
+ final double sum = w - SEMIMAJOR_AXIS * E2 * cosB0 * cosB0 * cosB0;
+ final double s1 = StrictMath.sqrt(t1 * t1 + sum * sum);
+ final double sinP1 = t1 / s1;
+ final double cosP1 = sum / s1;
+ final double rn = SEMIMAJOR_AXIS / StrictMath.sqrt(1.0D - E2 * sinP1 * sinP1);
+
+ if (cosP1 >= cos67P5) {
+ lla[2] = w / cosP1 - rn;
+ } else if (cosP1 <= -cos67P5) {
+ lla[2] = w / -cosP1 - rn;
+ } else {
+ lla[2] = z / sinP1 + rn * (E2 - 1.0);
+ }
+ if (!atPole) {
+ lla[1] = StrictMath.atan(sinP1/cosP1);
+ }
+ lla[0] = TO_DEGREES * lla[0];
+ lla[1] = TO_DEGREES * lla[1];
+
+ return lla;
+ }
+
+ /**
+ * Converts from geodesic lon lat alt to geocentric earth-centered earth-fixed
+ * @param lon geodesic longitude
+ * @param lat geodesic latitude
+ * @param alt geodesic altitude
+ * @param ecf reusable earth-centered earth-fixed result
+ * @return either a new ecef array or the reusable ecf parameter
+ */
+ public static final double[] llaToECF(double lon, double lat, double alt, double[] ecf) {
+ lon = TO_RADIANS * lon;
+ lat = TO_RADIANS * lat;
+
+ final double sl = sin(lat);
+ final double s2 = sl*sl;
+ final double cl = cos(lat);
+
+ if (ecf == null) {
+ ecf = new double[3];
+ }
+
+ if (lat < -PIO2 && lat > -1.001D * PIO2) {
+ lat = -PIO2;
+ } else if (lat > PIO2 && lat < 1.001D * PIO2) {
+ lat = PIO2;
+ }
+ assert (lat >= -PIO2) || (lat <= PIO2);
+
+ if (lon > StrictMath.PI) {
+ lon -= (2*StrictMath.PI);
+ }
+
+ final double rn = SEMIMAJOR_AXIS / StrictMath.sqrt(1.0D - E2 * s2);
+ ecf[0] = (rn+alt) * cl * cos(lon);
+ ecf[1] = (rn+alt) * cl * sin(lon);
+ ecf[2] = ((rn*(1.0-E2))+alt)*sl;
+
+ return ecf;
+ }
+
+ /**
+ * Converts from lat lon alt (in degrees) to East North Up right-hand coordinate system
+ * @param lon longitude in degrees
+ * @param lat latitude in degrees
+ * @param alt altitude in meters
+ * @param centerLon reference point longitude in degrees
+ * @param centerLat reference point latitude in degrees
+ * @param centerAlt reference point altitude in meters
+ * @param enu result east, north, up coordinate
+ * @return east, north, up coordinate
+ */
+ public static double[] llaToENU(final double lon, final double lat, final double alt, double centerLon,
+ double centerLat, final double centerAlt, double[] enu) {
+ if (enu == null) {
+ enu = new double[3];
+ }
+
+ // convert point to ecf coordinates
+ final double[] ecf = llaToECF(lon, lat, alt, null);
+
+ // convert from ecf to enu
+ return ecfToENU(ecf[0], ecf[1], ecf[2], centerLon, centerLat, centerAlt, enu);
+ }
+
+ /**
+ * Converts from East North Up right-hand rule to lat lon alt in degrees
+ * @param x easting (in meters)
+ * @param y northing (in meters)
+ * @param z up (in meters)
+ * @param centerLon reference point longitude (in degrees)
+ * @param centerLat reference point latitude (in degrees)
+ * @param centerAlt reference point altitude (in meters)
+ * @param lla resulting lat, lon, alt point (in degrees)
+ * @return lat, lon, alt point (in degrees)
+ */
+ public static double[] enuToLLA(final double x, final double y, final double z, final double centerLon,
+ final double centerLat, final double centerAlt, double[] lla) {
+ // convert enuToECF
+ if (lla == null) {
+ lla = new double[3];
+ }
+
+ // convert enuToECF, storing intermediate result in lla
+ lla = enuToECF(x, y, z, centerLon, centerLat, centerAlt, lla);
+
+ // convert ecf to LLA
+ return ecfToLLA(lla[0], lla[1], lla[2], lla);
+ }
+
+ /**
+ * Convert from Earth-Centered-Fixed to Easting, Northing, Up Right Hand System
+ * @param x ECF X coordinate (in meters)
+ * @param y ECF Y coordinate (in meters)
+ * @param z ECF Z coordinate (in meters)
+ * @param centerLon ENU origin longitude (in degrees)
+ * @param centerLat ENU origin latitude (in degrees)
+ * @param centerAlt ENU altitude (in meters)
+ * @param enu reusable enu result
+ * @return Easting, Northing, Up coordinate
+ */
+ public static double[] ecfToENU(double x, double y, double z, final double centerLon,
+ final double centerLat, final double centerAlt, double[] enu) {
+ if (enu == null) {
+ enu = new double[3];
+ }
+
+ // create rotation matrix and rotate to enu orientation
+ final double[][] phi = createPhiTransform(centerLon, centerLat, null);
+
+ // convert origin to ENU
+ final double[] originECF = llaToECF(centerLon, centerLat, centerAlt, null);
+ final double[] originENU = new double[3];
+ originENU[0] = ((phi[0][0] * originECF[0]) + (phi[0][1] * originECF[1]) + (phi[0][2] * originECF[2]));
+ originENU[1] = ((phi[1][0] * originECF[0]) + (phi[1][1] * originECF[1]) + (phi[1][2] * originECF[2]));
+ originENU[2] = ((phi[2][0] * originECF[0]) + (phi[2][1] * originECF[1]) + (phi[2][2] * originECF[2]));
+
+ // rotate then translate
+ enu[0] = ((phi[0][0] * x) + (phi[0][1] * y) + (phi[0][2] * z)) - originENU[0];
+ enu[1] = ((phi[1][0] * x) + (phi[1][1] * y) + (phi[1][2] * z)) - originENU[1];
+ enu[2] = ((phi[2][0] * x) + (phi[2][1] * y) + (phi[2][2] * z)) - originENU[2];
+
+ return enu;
+ }
+
+ /**
+ * Convert from Easting, Northing, Up Right-Handed system to Earth Centered Fixed system
+ * @param x ENU x coordinate (in meters)
+ * @param y ENU y coordinate (in meters)
+ * @param z ENU z coordinate (in meters)
+ * @param centerLon ENU origin longitude (in degrees)
+ * @param centerLat ENU origin latitude (in degrees)
+ * @param centerAlt ENU origin altitude (in meters)
+ * @param ecf reusable ecf result
+ * @return ecf result coordinate
+ */
+ public static double[] enuToECF(final double x, final double y, final double z, double centerLon,
+ double centerLat, final double centerAlt, double[] ecf) {
+ if (ecf == null) {
+ ecf = new double[3];
+ }
+
+ double[][] phi = createTransposedPhiTransform(centerLon, centerLat, null);
+ double[] ecfOrigin = llaToECF(centerLon, centerLat, centerAlt, null);
+
+ // rotate and translate
+ ecf[0] = (phi[0][0]*x + phi[0][1]*y + phi[0][2]*z) + ecfOrigin[0];
+ ecf[1] = (phi[1][0]*x + phi[1][1]*y + phi[1][2]*z) + ecfOrigin[1];
+ ecf[2] = (phi[2][0]*x + phi[2][1]*y + phi[2][2]*z) + ecfOrigin[2];
+
+ return ecf;
+ }
+
+ /**
+ * Create the rotation matrix for converting Earth Centered Fixed to Easting Northing Up
+ * @param originLon ENU origin longitude (in degrees)
+ * @param originLat ENU origin latitude (in degrees)
+ * @param phiMatrix reusable phi matrix result
+ * @return phi rotation matrix
+ */
+ private static double[][] createPhiTransform(double originLon, double originLat, double[][] phiMatrix) {
+
+ if (phiMatrix == null) {
+ phiMatrix = new double[3][3];
+ }
+
+ originLon = TO_RADIANS * originLon;
+ originLat = TO_RADIANS * originLat;
+
+ final double sLon = sin(originLon);
+ final double cLon = cos(originLon);
+ final double sLat = sin(originLat);
+ final double cLat = cos(originLat);
+
+ phiMatrix[0][0] = -sLon;
+ phiMatrix[0][1] = cLon;
+ phiMatrix[0][2] = 0.0D;
+ phiMatrix[1][0] = -sLat * cLon;
+ phiMatrix[1][1] = -sLat * sLon;
+ phiMatrix[1][2] = cLat;
+ phiMatrix[2][0] = cLat * cLon;
+ phiMatrix[2][1] = cLat * sLon;
+ phiMatrix[2][2] = sLat;
+
+ return phiMatrix;
+ }
+
+ /**
+ * Create the transposed rotation matrix for converting Easting Northing Up coordinates to Earth Centered Fixed
+ * @param originLon ENU origin longitude (in degrees)
+ * @param originLat ENU origin latitude (in degrees)
+ * @param phiMatrix reusable phi rotation matrix result
+ * @return transposed phi rotation matrix
+ */
+ private static double[][] createTransposedPhiTransform(double originLon, double originLat, double[][] phiMatrix) {
+
+ if (phiMatrix == null) {
+ phiMatrix = new double[3][3];
+ }
+
+ originLon = TO_RADIANS * originLon;
+ originLat = TO_RADIANS * originLat;
+
+ final double sLat = sin(originLat);
+ final double cLat = cos(originLat);
+ final double sLon = sin(originLon);
+ final double cLon = cos(originLon);
+
+ phiMatrix[0][0] = -sLon;
+ phiMatrix[1][0] = cLon;
+ phiMatrix[2][0] = 0.0D;
+ phiMatrix[0][1] = -sLat * cLon;
+ phiMatrix[1][1] = -sLat * sLon;
+ phiMatrix[2][1] = cLat;
+ phiMatrix[0][2] = cLat * cLon;
+ phiMatrix[1][2] = cLat * sLon;
+ phiMatrix[2][2] = sLat;
+
+ return phiMatrix;
+ }
+
+ /**
+ * Finds a point along a bearing from a given lon,lat geolocation using vincenty's distance formula
+ *
+ * @param lon origin longitude in degrees
+ * @param lat origin latitude in degrees
+ * @param bearing azimuthal bearing in degrees
+ * @param dist distance in meters
+ * @param pt resulting point
+ * @return the point along a bearing at a given distance in meters
+ */
+ public static final double[] pointFromLonLatBearingVincenty(double lon, double lat, double bearing, double dist, double[] pt) {
+
+ if (pt == null) {
+ pt = new double[2];
+ }
+
+ final double alpha1 = TO_RADIANS * bearing;
+ final double cosA1 = cos(alpha1);
+ final double sinA1 = sin(alpha1);
+ final double tanU1 = (1-FLATTENING) * tan(TO_RADIANS * lat);
+ final double cosU1 = 1 / StrictMath.sqrt((1+tanU1*tanU1));
+ final double sinU1 = tanU1*cosU1;
+ final double sig1 = StrictMath.atan2(tanU1, cosA1);
+ final double sinAlpha = cosU1 * sinA1;
+ final double cosSqAlpha = 1 - sinAlpha*sinAlpha;
+ final double uSq = cosSqAlpha * EP2;
+ final double A = 1 + uSq/16384D*(4096D + uSq * (-768D + uSq * (320D - 175D*uSq)));
+ final double B = uSq/1024D * (256D + uSq * (-128D + uSq * (74D - 47D * uSq)));
+
+ double sigma = dist / (SEMIMINOR_AXIS*A);
+ double sigmaP;
+ double sinSigma, cosSigma, cos2SigmaM, deltaSigma;
+
+ do {
+ cos2SigmaM = cos(2*sig1 + sigma);
+ sinSigma = sin(sigma);
+ cosSigma = cos(sigma);
+
+ deltaSigma = B * sinSigma * (cos2SigmaM + (B/4D) * (cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
+ (B/6) * cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
+ sigmaP = sigma;
+ sigma = dist / (SEMIMINOR_AXIS*A) + deltaSigma;
+ } while (StrictMath.abs(sigma-sigmaP) > 1E-12);
+
+ final double tmp = sinU1*sinSigma - cosU1*cosSigma*cosA1;
+ final double lat2 = StrictMath.atan2(sinU1*cosSigma + cosU1*sinSigma*cosA1,
+ (1-FLATTENING) * StrictMath.sqrt(sinAlpha*sinAlpha + tmp*tmp));
+ final double lambda = StrictMath.atan2(sinSigma*sinA1, cosU1*cosSigma - sinU1*sinSigma*cosA1);
+ final double c = FLATTENING/16 * cosSqAlpha * (4 + FLATTENING * (4 - 3 * cosSqAlpha));
+
+ final double lam = lambda - (1-c) * FLATTENING * sinAlpha *
+ (sigma + c * sinSigma * (cos2SigmaM + c * cosSigma * (-1 + 2* cos2SigmaM*cos2SigmaM)));
+ pt[0] = normalizeLon(lon + TO_DEGREES * lam);
+ pt[1] = normalizeLat(TO_DEGREES * lat2);
+
+ return pt;
+ }
+
+ /**
+ * Finds a point along a bearing from a given lon,lat geolocation using great circle arc
+ *
+ * @param lon origin longitude in degrees
+ * @param lat origin latitude in degrees
+ * @param bearing azimuthal bearing in degrees
+ * @param dist distance in meters
+ * @param pt resulting point
+ * @return the point along a bearing at a given distance in meters
+ */
+ public static final double[] pointFromLonLatBearingGreatCircle(double lon, double lat, double bearing, double dist, double[] pt) {
+
+ if (pt == null) {
+ pt = new double[2];
+ }
+
+ lon *= TO_RADIANS;
+ lat *= TO_RADIANS;
+ bearing *= TO_RADIANS;
+
+ final double cLat = cos(lat);
+ final double sLat = sin(lat);
+ final double sinDoR = sin(dist / GeoProjectionUtils.SEMIMAJOR_AXIS);
+ final double cosDoR = cos(dist / GeoProjectionUtils.SEMIMAJOR_AXIS);
+
+ pt[1] = asin(sLat*cosDoR + cLat * sinDoR * cos(bearing));
+ pt[0] = TO_DEGREES * (lon + Math.atan2(sin(bearing) * sinDoR * cLat, cosDoR - sLat * sin(pt[1])));
+ pt[1] *= TO_DEGREES;
+
+ return pt;
+ }
+
+ /**
+ * Finds the bearing (in degrees) between 2 geo points (lon, lat) using great circle arc
+ * @param lon1 first point longitude in degrees
+ * @param lat1 first point latitude in degrees
+ * @param lon2 second point longitude in degrees
+ * @param lat2 second point latitude in degrees
+ * @return the bearing (in degrees) between the two provided points
+ */
+ public static double bearingGreatCircle(double lon1, double lat1, double lon2, double lat2) {
+ double dLon = (lon2 - lon1) * TO_RADIANS;
+ lat2 *= TO_RADIANS;
+ lat1 *= TO_RADIANS;
+ double y = sin(dLon) * cos(lat2);
+ double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon);
+ return Math.atan2(y, x) * TO_DEGREES;
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java
new file mode 100644
index 0000000..fa93e61
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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;
+
+/** Represents a lat/lon rectangle. */
+public class GeoRect {
+ public final double minLon;
+ public final double maxLon;
+ public final double minLat;
+ public final double maxLat;
+
+ public GeoRect(double minLon, double maxLon, double minLat, double maxLat) {
+ if (GeoUtils.isValidLon(minLon) == false) {
+ throw new IllegalArgumentException("invalid minLon " + minLon);
+ }
+ if (GeoUtils.isValidLon(maxLon) == false) {
+ throw new IllegalArgumentException("invalid maxLon " + maxLon);
+ }
+ if (GeoUtils.isValidLat(minLat) == false) {
+ throw new IllegalArgumentException("invalid minLat " + minLat);
+ }
+ if (GeoUtils.isValidLat(maxLat) == false) {
+ throw new IllegalArgumentException("invalid maxLat " + maxLat);
+ }
+ this.minLon = minLon;
+ this.maxLon = maxLon;
+ this.minLat = minLat;
+ this.maxLat = maxLat;
+ assert maxLat >= minLat;
+
+ // NOTE: cannot assert maxLon >= minLon since this rect could cross the dateline
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("GeoRect(lon=");
+ b.append(minLon);
+ b.append(" TO ");
+ b.append(maxLon);
+ if (maxLon < minLon) {
+ b.append(" (crosses dateline!)");
+ }
+ b.append(" lat=");
+ b.append(minLat);
+ b.append(" TO ");
+ b.append(maxLat);
+ b.append(")");
+
+ return b.toString();
+ }
+}
[4/6] lucene-solr git commit: LUCENE-6997: refactor sandboxed
GeoPointField and query classes to lucene-spatial module
Posted by nk...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/test/org/apache/lucene/util/BaseGeoPointTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/util/BaseGeoPointTestCase.java b/lucene/sandbox/src/test/org/apache/lucene/util/BaseGeoPointTestCase.java
deleted file mode 100644
index 5a469db..0000000
--- a/lucene/sandbox/src/test/org/apache/lucene/util/BaseGeoPointTestCase.java
+++ /dev/null
@@ -1,764 +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.util;
-
-import java.io.IOException;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.Field;
-import org.apache.lucene.document.NumericDocValuesField;
-import org.apache.lucene.index.DirectoryReader;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.index.IndexWriterConfig;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.MultiDocValues;
-import org.apache.lucene.index.NumericDocValues;
-import org.apache.lucene.index.RandomIndexWriter;
-import org.apache.lucene.index.Term;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.SimpleCollector;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.store.MockDirectoryWrapper;
-import org.junit.BeforeClass;
-
-// TODO: cutover TestGeoUtils too?
-
-public abstract class BaseGeoPointTestCase extends LuceneTestCase {
-
- protected static final String FIELD_NAME = "point";
-
- private static final double LON_SCALE = (0x1L<<GeoUtils.BITS)/360.0D;
- private static final double LAT_SCALE = (0x1L<<GeoUtils.BITS)/180.0D;
-
- private static double originLat;
- private static double originLon;
- private static double lonRange;
- private static double latRange;
-
- @BeforeClass
- public static void beforeClassBase() throws Exception {
- // Between 1.0 and 3.0:
- lonRange = 2 * (random().nextDouble() + 0.5);
- latRange = 2 * (random().nextDouble() + 0.5);
-
- originLon = GeoUtils.normalizeLon(GeoUtils.MIN_LON_INCL + lonRange + (GeoUtils.MAX_LON_INCL - GeoUtils.MIN_LON_INCL - 2 * lonRange) * random().nextDouble());
- originLat = GeoUtils.normalizeLat(GeoUtils.MIN_LAT_INCL + latRange + (GeoUtils.MAX_LAT_INCL - GeoUtils.MIN_LAT_INCL - 2 * latRange) * random().nextDouble());
- }
-
- /** Return true when testing on a non-small region may be too slow (GeoPoint*Query) */
- protected boolean forceSmall() {
- return false;
- }
-
- // A particularly tricky adversary for BKD tree:
- public void testSamePointManyTimes() throws Exception {
-
- // For GeoPointQuery, only run this test nightly:
- assumeTrue("GeoPoint*Query is too slow otherwise", TEST_NIGHTLY || forceSmall() == false);
-
- int numPoints = atLeast(1000);
- boolean small = random().nextBoolean();
-
- // Every doc has 2 points:
- double theLat = randomLat(small);
- double theLon = randomLon(small);
-
- double[] lats = new double[numPoints];
- Arrays.fill(lats, theLat);
-
- double[] lons = new double[numPoints];
- Arrays.fill(lons, theLon);
-
- verify(small, lats, lons);
- }
-
- public void testAllLatEqual() throws Exception {
-
- // For GeoPointQuery, only run this test nightly:
- assumeTrue("GeoPoint*Query is too slow otherwise", TEST_NIGHTLY || forceSmall() == false);
-
- int numPoints = atLeast(10000);
- boolean small = forceSmall() || random().nextBoolean();
- double lat = randomLat(small);
- double[] lats = new double[numPoints];
- double[] lons = new double[numPoints];
-
- boolean haveRealDoc = false;
-
- for(int docID=0;docID<numPoints;docID++) {
- int x = random().nextInt(20);
- if (x == 17) {
- // Some docs don't have a point:
- lats[docID] = Double.NaN;
- if (VERBOSE) {
- System.out.println(" doc=" + docID + " is missing");
- }
- continue;
- }
-
- if (docID > 0 && x == 14 && haveRealDoc) {
- int oldDocID;
- while (true) {
- oldDocID = random().nextInt(docID);
- if (Double.isNaN(lats[oldDocID]) == false) {
- break;
- }
- }
-
- // Fully identical point:
- lons[docID] = lons[oldDocID];
- if (VERBOSE) {
- System.out.println(" doc=" + docID + " lat=" + lat + " lon=" + lons[docID] + " (same lat/lon as doc=" + oldDocID + ")");
- }
- } else {
- lons[docID] = randomLon(small);
- haveRealDoc = true;
- if (VERBOSE) {
- System.out.println(" doc=" + docID + " lat=" + lat + " lon=" + lons[docID]);
- }
- }
- lats[docID] = lat;
- }
-
- verify(small, lats, lons);
- }
-
- public void testAllLonEqual() throws Exception {
-
- // For GeoPointQuery, only run this test nightly:
- assumeTrue("GeoPoint*Query is too slow otherwise", TEST_NIGHTLY || forceSmall() == false);
-
- int numPoints = atLeast(10000);
- boolean small = forceSmall() || random().nextBoolean();
- double theLon = randomLon(small);
- double[] lats = new double[numPoints];
- double[] lons = new double[numPoints];
-
- boolean haveRealDoc = false;
-
- //System.out.println("theLon=" + theLon);
-
- for(int docID=0;docID<numPoints;docID++) {
- int x = random().nextInt(20);
- if (x == 17) {
- // Some docs don't have a point:
- lats[docID] = Double.NaN;
- if (VERBOSE) {
- System.out.println(" doc=" + docID + " is missing");
- }
- continue;
- }
-
- if (docID > 0 && x == 14 && haveRealDoc) {
- int oldDocID;
- while (true) {
- oldDocID = random().nextInt(docID);
- if (Double.isNaN(lats[oldDocID]) == false) {
- break;
- }
- }
-
- // Fully identical point:
- lats[docID] = lats[oldDocID];
- if (VERBOSE) {
- System.out.println(" doc=" + docID + " lat=" + lats[docID] + " lon=" + theLon + " (same lat/lon as doc=" + oldDocID + ")");
- }
- } else {
- lats[docID] = randomLat(small);
- haveRealDoc = true;
- if (VERBOSE) {
- System.out.println(" doc=" + docID + " lat=" + lats[docID] + " lon=" + theLon);
- }
- }
- lons[docID] = theLon;
- }
-
- verify(small, lats, lons);
- }
-
- public void testMultiValued() throws Exception {
-
- // For GeoPointQuery, only run this test nightly:
- assumeTrue("GeoPoint*Query is too slow otherwise", TEST_NIGHTLY || forceSmall() == false);
-
- int numPoints = atLeast(10000);
- // Every doc has 2 points:
- double[] lats = new double[2*numPoints];
- double[] lons = new double[2*numPoints];
- Directory dir = newDirectory();
- noVirusChecker(dir);
- IndexWriterConfig iwc = newIndexWriterConfig();
- initIndexWriterConfig(FIELD_NAME, iwc);
-
- // We rely on docID order:
- iwc.setMergePolicy(newLogMergePolicy());
- RandomIndexWriter w = new RandomIndexWriter(random(), dir, iwc);
-
- boolean small = random().nextBoolean();
-
- for (int id=0;id<numPoints;id++) {
- Document doc = new Document();
- lats[2*id] = randomLat(small);
- lons[2*id] = randomLon(small);
- doc.add(newStringField("id", ""+id, Field.Store.YES));
- addPointToDoc(FIELD_NAME, doc, lats[2*id], lons[2*id]);
- lats[2*id+1] = randomLat(small);
- lons[2*id+1] = randomLon(small);
- addPointToDoc(FIELD_NAME, doc, lats[2*id+1], lons[2*id+1]);
-
- if (VERBOSE) {
- System.out.println("id=" + id);
- System.out.println(" lat=" + lats[2*id] + " lon=" + lons[2*id]);
- System.out.println(" lat=" + lats[2*id+1] + " lon=" + lons[2*id+1]);
- }
- w.addDocument(doc);
- }
-
- // TODO: share w/ verify; just need parallel array of the expected ids
- if (random().nextBoolean()) {
- w.forceMerge(1);
- }
- IndexReader r = w.getReader();
- w.close();
-
- // We can't wrap with "exotic" readers because the BKD query must see the BKDDVFormat:
- IndexSearcher s = newSearcher(r, false);
-
- int iters = atLeast(75);
- for (int iter=0;iter<iters;iter++) {
- GeoRect rect = randomRect(small, small == false);
-
- if (VERBOSE) {
- System.out.println("\nTEST: iter=" + iter + " rect=" + rect);
- }
-
- Query query = newRectQuery(FIELD_NAME, rect);
-
- final FixedBitSet hits = new FixedBitSet(r.maxDoc());
- s.search(query, new SimpleCollector() {
-
- private int docBase;
-
- @Override
- public boolean needsScores() {
- return false;
- }
-
- @Override
- protected void doSetNextReader(LeafReaderContext context) throws IOException {
- docBase = context.docBase;
- }
-
- @Override
- public void collect(int doc) {
- hits.set(docBase+doc);
- }
- });
-
- boolean fail = false;
-
- for(int docID=0;docID<lats.length/2;docID++) {
- double latDoc1 = lats[2*docID];
- double lonDoc1 = lons[2*docID];
- double latDoc2 = lats[2*docID+1];
- double lonDoc2 = lons[2*docID+1];
-
- Boolean result1 = rectContainsPoint(rect, latDoc1, lonDoc1);
- if (result1 == null) {
- // borderline case: cannot test
- continue;
- }
-
- Boolean result2 = rectContainsPoint(rect, latDoc2, lonDoc2);
- if (result2 == null) {
- // borderline case: cannot test
- continue;
- }
-
- boolean expected = result1 == Boolean.TRUE || result2 == Boolean.TRUE;
-
- if (hits.get(docID) != expected) {
- String id = s.doc(docID).get("id");
- if (expected) {
- System.out.println(Thread.currentThread().getName() + ": id=" + id + " docID=" + docID + " should match but did not");
- } else {
- System.out.println(Thread.currentThread().getName() + ": id=" + id + " docID=" + docID + " should not match but did");
- }
- System.out.println(" rect=" + rect);
- System.out.println(" lat=" + latDoc1 + " lon=" + lonDoc1 + "\n lat=" + latDoc2 + " lon=" + lonDoc2);
- System.out.println(" result1=" + result1 + " result2=" + result2);
- fail = true;
- }
- }
-
- if (fail) {
- fail("some hits were wrong");
- }
- }
- r.close();
- dir.close();
- }
-
- public void testRandomTiny() throws Exception {
- // Make sure single-leaf-node case is OK:
- doTestRandom(10);
- }
-
- public void testRandomMedium() throws Exception {
- doTestRandom(10000);
- }
-
- @Nightly
- public void testRandomBig() throws Exception {
- assumeFalse("Direct codec can OOME on this test", TestUtil.getDocValuesFormat(FIELD_NAME).equals("Direct"));
- assumeFalse("Memory codec can OOME on this test", TestUtil.getDocValuesFormat(FIELD_NAME).equals("Memory"));
- doTestRandom(200000);
- }
-
- private void doTestRandom(int count) throws Exception {
-
- int numPoints = atLeast(count);
-
- if (VERBOSE) {
- System.out.println("TEST: numPoints=" + numPoints);
- }
-
- double[] lats = new double[numPoints];
- double[] lons = new double[numPoints];
-
- boolean small = random().nextBoolean();
-
- boolean haveRealDoc = false;
-
- for (int id=0;id<numPoints;id++) {
- int x = random().nextInt(20);
- if (x == 17) {
- // Some docs don't have a point:
- lats[id] = Double.NaN;
- if (VERBOSE) {
- System.out.println(" id=" + id + " is missing");
- }
- continue;
- }
-
- if (id > 0 && x < 3 && haveRealDoc) {
- int oldID;
- while (true) {
- oldID = random().nextInt(id);
- if (Double.isNaN(lats[oldID]) == false) {
- break;
- }
- }
-
- if (x == 0) {
- // Identical lat to old point
- lats[id] = lats[oldID];
- lons[id] = randomLon(small);
- if (VERBOSE) {
- System.out.println(" id=" + id + " lat=" + lats[id] + " lon=" + lons[id] + " (same lat as doc=" + oldID + ")");
- }
- } else if (x == 1) {
- // Identical lon to old point
- lats[id] = randomLat(small);
- lons[id] = lons[oldID];
- if (VERBOSE) {
- System.out.println(" id=" + id + " lat=" + lats[id] + " lon=" + lons[id] + " (same lon as doc=" + oldID + ")");
- }
- } else {
- assert x == 2;
- // Fully identical point:
- lats[id] = lats[oldID];
- lons[id] = lons[oldID];
- if (VERBOSE) {
- System.out.println(" id=" + id + " lat=" + lats[id] + " lon=" + lons[id] + " (same lat/lon as doc=" + oldID + ")");
- }
- }
- } else {
- lats[id] = randomLat(small);
- lons[id] = randomLon(small);
- haveRealDoc = true;
- if (VERBOSE) {
- System.out.println(" id=" + id + " lat=" + lats[id] + " lon=" + lons[id]);
- }
- }
- }
-
- verify(small, lats, lons);
- }
-
- public double randomLat(boolean small) {
- double result;
- if (small) {
- result = GeoUtils.normalizeLat(originLat + latRange * (random().nextDouble() - 0.5));
- } else {
- result = -90 + 180.0 * random().nextDouble();
- }
- return result;
- }
-
- public double randomLon(boolean small) {
- double result;
- if (small) {
- result = GeoUtils.normalizeLon(originLon + lonRange * (random().nextDouble() - 0.5));
- } else {
- result = -180 + 360.0 * random().nextDouble();
- }
- return result;
- }
-
- protected GeoRect randomRect(boolean small, boolean canCrossDateLine) {
- double lat0 = randomLat(small);
- double lat1 = randomLat(small);
- double lon0 = randomLon(small);
- double lon1 = randomLon(small);
-
- if (lat1 < lat0) {
- double x = lat0;
- lat0 = lat1;
- lat1 = x;
- }
-
- if (canCrossDateLine == false && lon1 < lon0) {
- double x = lon0;
- lon0 = lon1;
- lon1 = x;
- }
-
- return new GeoRect(lon0, lon1, lat0, lat1);
- }
-
- protected void initIndexWriterConfig(String field, IndexWriterConfig iwc) {
- }
-
- protected abstract void addPointToDoc(String field, Document doc, double lat, double lon);
-
- protected abstract Query newRectQuery(String field, GeoRect bbox);
-
- protected abstract Query newDistanceQuery(String field, double centerLat, double centerLon, double radiusMeters);
-
- protected abstract Query newDistanceRangeQuery(String field, double centerLat, double centerLon, double minRadiusMeters, double radiusMeters);
-
- protected abstract Query newPolygonQuery(String field, double[] lats, double[] lons);
-
- /** Returns null if it's borderline case */
- protected abstract Boolean rectContainsPoint(GeoRect rect, double pointLat, double pointLon);
-
- /** Returns null if it's borderline case */
- protected abstract Boolean polyRectContainsPoint(GeoRect rect, double pointLat, double pointLon);
-
- /** Returns null if it's borderline case */
- protected abstract Boolean circleContainsPoint(double centerLat, double centerLon, double radiusMeters, double pointLat, double pointLon);
-
- protected abstract Boolean distanceRangeContainsPoint(double centerLat, double centerLon, double minRadiusMeters, double radiusMeters, double pointLat, double pointLon);
-
- private static abstract class VerifyHits {
-
- public void test(AtomicBoolean failed, boolean small, IndexSearcher s, NumericDocValues docIDToID, Set<Integer> deleted, Query query, double[] lats, double[] lons) throws Exception {
- int maxDoc = s.getIndexReader().maxDoc();
- final FixedBitSet hits = new FixedBitSet(maxDoc);
- s.search(query, new SimpleCollector() {
-
- private int docBase;
-
- @Override
- public boolean needsScores() {
- return false;
- }
-
- @Override
- protected void doSetNextReader(LeafReaderContext context) throws IOException {
- docBase = context.docBase;
- }
-
- @Override
- public void collect(int doc) {
- hits.set(docBase+doc);
- }
- });
-
- boolean fail = false;
-
- for(int docID=0;docID<maxDoc;docID++) {
- int id = (int) docIDToID.get(docID);
- Boolean expected;
- if (deleted.contains(id)) {
- expected = false;
- } else if (Double.isNaN(lats[id])) {
- expected = false;
- } else {
- expected = shouldMatch(lats[id], lons[id]);
- }
-
- // null means it's a borderline case which is allowed to be wrong:
- if (expected != null && hits.get(docID) != expected) {
- if (expected) {
- System.out.println(Thread.currentThread().getName() + ": id=" + id + " should match but did not");
- } else {
- System.out.println(Thread.currentThread().getName() + ": id=" + id + " should not match but did");
- }
- System.out.println(" small=" + small + " query=" + query +
- " docID=" + docID + "\n lat=" + lats[id] + " lon=" + lons[id] +
- "\n deleted?=" + deleted.contains(id));
- if (Double.isNaN(lats[id]) == false) {
- describe(docID, lats[id], lons[id]);
- }
- fail = true;
- }
- }
-
- if (fail) {
- failed.set(true);
- fail("some hits were wrong");
- }
- }
-
- /** Return true if we definitely should match, false if we definitely
- * should not match, and null if it's a borderline case which might
- * go either way. */
- protected abstract Boolean shouldMatch(double lat, double lon);
-
- protected abstract void describe(int docID, double lat, double lon);
- }
-
- protected void verify(boolean small, double[] lats, double[] lons) throws Exception {
- IndexWriterConfig iwc = newIndexWriterConfig();
- // Else we can get O(N^2) merging:
- int mbd = iwc.getMaxBufferedDocs();
- if (mbd != -1 && mbd < lats.length/100) {
- iwc.setMaxBufferedDocs(lats.length/100);
- }
- Directory dir;
- if (lats.length > 100000) {
- dir = newFSDirectory(createTempDir(getClass().getSimpleName()));
- } else {
- dir = newDirectory();
- }
- noVirusChecker(dir);
-
- Set<Integer> deleted = new HashSet<>();
- // RandomIndexWriter is too slow here:
- IndexWriter w = new IndexWriter(dir, iwc);
- for(int id=0;id<lats.length;id++) {
- Document doc = new Document();
- doc.add(newStringField("id", ""+id, Field.Store.NO));
- doc.add(new NumericDocValuesField("id", id));
- if (Double.isNaN(lats[id]) == false) {
- addPointToDoc(FIELD_NAME, doc, lats[id], lons[id]);
- }
- w.addDocument(doc);
- if (id > 0 && random().nextInt(100) == 42) {
- int idToDelete = random().nextInt(id);
- w.deleteDocuments(new Term("id", ""+idToDelete));
- deleted.add(idToDelete);
- if (VERBOSE) {
- System.out.println(" delete id=" + idToDelete);
- }
- }
- }
-
- if (random().nextBoolean()) {
- w.forceMerge(1);
- }
- final IndexReader r = DirectoryReader.open(w);
- w.close();
-
- // We can't wrap with "exotic" readers because the BKD query must see the BKDDVFormat:
- IndexSearcher s = newSearcher(r, false);
-
- // Make sure queries are thread safe:
- int numThreads = TestUtil.nextInt(random(), 2, 5);
-
- List<Thread> threads = new ArrayList<>();
- final int iters = atLeast(75);
-
- final CountDownLatch startingGun = new CountDownLatch(1);
- final AtomicBoolean failed = new AtomicBoolean();
-
- for(int i=0;i<numThreads;i++) {
- Thread thread = new Thread() {
- @Override
- public void run() {
- try {
- _run();
- } catch (Exception e) {
- failed.set(true);
- throw new RuntimeException(e);
- }
- }
-
- private void _run() throws Exception {
- startingGun.await();
-
- NumericDocValues docIDToID = MultiDocValues.getNumericValues(r, "id");
-
- for (int iter=0;iter<iters && failed.get() == false;iter++) {
-
- if (VERBOSE) {
- System.out.println("\nTEST: iter=" + iter + " s=" + s);
- }
- Query query;
- VerifyHits verifyHits;
-
- if (random().nextBoolean()) {
- // Rect: don't allow dateline crossing when testing small:
- final GeoRect rect = randomRect(small, small == false);
-
- query = newRectQuery(FIELD_NAME, rect);
-
- verifyHits = new VerifyHits() {
- @Override
- protected Boolean shouldMatch(double pointLat, double pointLon) {
- return rectContainsPoint(rect, pointLat, pointLon);
- }
- @Override
- protected void describe(int docID, double lat, double lon) {
- }
- };
-
- } else if (random().nextBoolean()) {
- // Distance
- final boolean rangeQuery = random().nextBoolean();
- final double centerLat = randomLat(small);
- final double centerLon = randomLon(small);
-
- double radiusMeters;
- double minRadiusMeters;
-
- if (small) {
- // Approx 3 degrees lon at the equator:
- radiusMeters = random().nextDouble() * 333000 + 1.0;
- } else {
- // So the query can cover at most 50% of the earth's surface:
- radiusMeters = random().nextDouble() * GeoProjectionUtils.SEMIMAJOR_AXIS * Math.PI / 2.0 + 1.0;
- }
-
- // generate a random minimum radius between 1% and 95% the max radius
- minRadiusMeters = (0.01 + 0.94 * random().nextDouble()) * radiusMeters;
-
- if (VERBOSE) {
- final DecimalFormat df = new DecimalFormat("#,###.00", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
- System.out.println(" radiusMeters = " + df.format(radiusMeters)
- + ((rangeQuery == true) ? " minRadiusMeters = " + df.format(minRadiusMeters) : ""));
- }
-
- try {
- if (rangeQuery == true) {
- query = newDistanceRangeQuery(FIELD_NAME, centerLat, centerLon, minRadiusMeters, radiusMeters);
- } else {
- query = newDistanceQuery(FIELD_NAME, centerLat, centerLon, radiusMeters);
- }
- } catch (IllegalArgumentException e) {
- if (e.getMessage().contains("exceeds maxRadius")) {
- continue;
- }
- throw e;
- }
-
- verifyHits = new VerifyHits() {
- @Override
- protected Boolean shouldMatch(double pointLat, double pointLon) {
- if (rangeQuery == false) {
- return circleContainsPoint(centerLat, centerLon, radiusMeters, pointLat, pointLon);
- } else {
- return distanceRangeContainsPoint(centerLat, centerLon, minRadiusMeters, radiusMeters, pointLat, pointLon);
- }
- }
-
- @Override
- protected void describe(int docID, double pointLat, double pointLon) {
- double distanceKM = SloppyMath.haversin(centerLat, centerLon, pointLat, pointLon);
- System.out.println(" docID=" + docID + " centerLon=" + centerLon + " centerLat=" + centerLat
- + " pointLon=" + pointLon + " pointLat=" + pointLat + " distanceMeters=" + (distanceKM * 1000)
- + " vs" + ((rangeQuery == true) ? " minRadiusMeters=" + minRadiusMeters : "") + " radiusMeters=" + radiusMeters);
- }
- };
-
- // TODO: get poly query working with dateline crossing too (how?)!
- } else {
-
- // TODO: poly query can't handle dateline crossing yet:
- final GeoRect bbox = randomRect(small, false);
-
- // Polygon
- double[] lats = new double[5];
- double[] lons = new double[5];
- lats[0] = bbox.minLat;
- lons[0] = bbox.minLon;
- lats[1] = bbox.maxLat;
- lons[1] = bbox.minLon;
- lats[2] = bbox.maxLat;
- lons[2] = bbox.maxLon;
- lats[3] = bbox.minLat;
- lons[3] = bbox.maxLon;
- lats[4] = bbox.minLat;
- lons[4] = bbox.minLon;
- query = newPolygonQuery(FIELD_NAME, lats, lons);
-
- verifyHits = new VerifyHits() {
- @Override
- protected Boolean shouldMatch(double pointLat, double pointLon) {
- return polyRectContainsPoint(bbox, pointLat, pointLon);
- }
-
- @Override
- protected void describe(int docID, double lat, double lon) {
- }
- };
- }
-
- if (query != null) {
-
- if (VERBOSE) {
- System.out.println(" query=" + query);
- }
-
- verifyHits.test(failed, small, s, docIDToID, deleted, query, lats, lons);
- }
- }
- }
- };
- thread.setName("T" + i);
- thread.start();
- threads.add(thread);
- }
- startingGun.countDown();
- for(Thread thread : threads) {
- thread.join();
- }
- IOUtils.close(r, dir);
- assertFalse(failed.get());
- }
-
- protected Directory noVirusChecker(Directory dir) {
- if (dir instanceof MockDirectoryWrapper) {
- ((MockDirectoryWrapper) dir).setEnableVirusScanner(false);
- }
- return dir;
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/test/org/apache/lucene/util/TestGeoUtils.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/util/TestGeoUtils.java b/lucene/sandbox/src/test/org/apache/lucene/util/TestGeoUtils.java
deleted file mode 100644
index 5650614..0000000
--- a/lucene/sandbox/src/test/org/apache/lucene/util/TestGeoUtils.java
+++ /dev/null
@@ -1,545 +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.util;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.junit.BeforeClass;
-
-import com.carrotsearch.randomizedtesting.generators.RandomInts;
-
-import static org.apache.lucene.util.GeoDistanceUtils.DISTANCE_PCT_ERR;
-
-/**
- * Tests class for methods in GeoUtils
- *
- * @lucene.experimental
- */
-public class TestGeoUtils extends LuceneTestCase {
-
- private static final double LON_SCALE = (0x1L<<GeoUtils.BITS)/360.0D;
- private static final double LAT_SCALE = (0x1L<<GeoUtils.BITS)/180.0D;
-
- // Global bounding box we will "cover" in the random test; we have to make this "smallish" else the queries take very long:
- private static double originLat;
- private static double originLon;
- // private static double range;
- private static double lonRange;
- private static double latRange;
-
- @BeforeClass
- public static void beforeClass() throws Exception {
- // Between 1.0 and 3.0:
- lonRange = 2 * (random().nextDouble() + 0.5);
- latRange = 2 * (random().nextDouble() + 0.5);
-
- originLon = GeoUtils.MIN_LON_INCL + lonRange + (GeoUtils.MAX_LON_INCL - GeoUtils.MIN_LON_INCL - 2 * lonRange) * random().nextDouble();
- originLon = GeoUtils.normalizeLon(originLon);
- originLat = GeoUtils.MIN_LAT_INCL + latRange + (GeoUtils.MAX_LAT_INCL - GeoUtils.MIN_LAT_INCL - 2 * latRange) * random().nextDouble();
- originLat = GeoUtils.normalizeLat(originLat);
-
- if (VERBOSE) {
- System.out.println("TEST: originLon=" + originLon + " lonRange= " + lonRange + " originLat=" + originLat + " latRange=" + latRange);
- }
- }
-
- public void testGeoHash() {
- int numPoints = atLeast(100);
- String randomGeoHashString;
- String mortonGeoHash;
- long mortonLongFromGHLong, geoHashLong, mortonLongFromGHString;
- int randomLevel;
- for (int i = 0; i < numPoints; ++i) {
- // random point
- double lat = randomLat(false);
- double lon = randomLon(false);
-
- // compute geohash straight from lat/lon and from morton encoded value to ensure they're the same
- randomGeoHashString = GeoHashUtils.stringEncode(lon, lat, randomLevel = random().nextInt(12 - 1) + 1);
- mortonGeoHash = GeoHashUtils.stringEncodeFromMortonLong(GeoUtils.mortonHash(lon, lat), randomLevel);
- assertEquals(randomGeoHashString, mortonGeoHash);
-
- // v&v conversion from lat/lon or geohashstring to geohash long and back to geohash string
- geoHashLong = (random().nextBoolean()) ? GeoHashUtils.longEncode(lon, lat, randomLevel) : GeoHashUtils.longEncode(randomGeoHashString);
- assertEquals(randomGeoHashString, GeoHashUtils.stringEncode(geoHashLong));
-
- // v&v conversion from geohash long to morton long
- mortonLongFromGHString = GeoHashUtils.mortonEncode(randomGeoHashString);
- mortonLongFromGHLong = GeoHashUtils.mortonEncode(geoHashLong);
- assertEquals(mortonLongFromGHLong, mortonLongFromGHString);
-
- // v&v lat/lon from geohash string and geohash long
- assertEquals(GeoUtils.mortonUnhashLat(mortonLongFromGHString), GeoUtils.mortonUnhashLat(mortonLongFromGHLong), 0);
- assertEquals(GeoUtils.mortonUnhashLon(mortonLongFromGHString), GeoUtils.mortonUnhashLon(mortonLongFromGHLong), 0);
- }
- }
-
- /**
- * Pass condition: lat=42.6, lng=-5.6 should be encoded as "ezs42e44yx96",
- * lat=57.64911 lng=10.40744 should be encoded as "u4pruydqqvj8"
- */
- public void testEncode() {
- String hash = GeoHashUtils.stringEncode(-5.6, 42.6, 12);
- assertEquals("ezs42e44yx96", hash);
-
- hash = GeoHashUtils.stringEncode(10.40744, 57.64911, 12);
- assertEquals("u4pruydqqvj8", hash);
- }
-
- /**
- * Pass condition: lat=52.3738007, lng=4.8909347 should be encoded and then
- * decoded within 0.00001 of the original value
- */
- public void testDecodePreciseLongitudeLatitude() {
- final String geohash = GeoHashUtils.stringEncode(4.8909347, 52.3738007);
- final long hash = GeoHashUtils.mortonEncode(geohash);
-
- assertEquals(52.3738007, GeoUtils.mortonUnhashLat(hash), 0.00001D);
- assertEquals(4.8909347, GeoUtils.mortonUnhashLon(hash), 0.00001D);
- }
-
- /**
- * Pass condition: lat=84.6, lng=10.5 should be encoded and then decoded
- * within 0.00001 of the original value
- */
- public void testDecodeImpreciseLongitudeLatitude() {
- final String geohash = GeoHashUtils.stringEncode(10.5, 84.6);
-
- final long hash = GeoHashUtils.mortonEncode(geohash);
-
- assertEquals(84.6, GeoUtils.mortonUnhashLat(hash), 0.00001D);
- assertEquals(10.5, GeoUtils.mortonUnhashLon(hash), 0.00001D);
- }
-
- public void testDecodeEncode() {
- final String geoHash = "u173zq37x014";
- assertEquals(geoHash, GeoHashUtils.stringEncode(4.8909347, 52.3738007));
- final long mortonHash = GeoHashUtils.mortonEncode(geoHash);
- final double lon = GeoUtils.mortonUnhashLon(mortonHash);
- final double lat = GeoUtils.mortonUnhashLat(mortonHash);
- assertEquals(52.37380061d, GeoUtils.mortonUnhashLat(mortonHash), 0.000001d);
- assertEquals(4.8909343d, GeoUtils.mortonUnhashLon(mortonHash), 0.000001d);
-
- assertEquals(geoHash, GeoHashUtils.stringEncode(lon, lat));
- }
-
- public void testNeighbors() {
- String geohash = "gcpv";
- List<String> expectedNeighbors = new ArrayList<>();
- expectedNeighbors.add("gcpw");
- expectedNeighbors.add("gcpy");
- expectedNeighbors.add("u10n");
- expectedNeighbors.add("gcpt");
- expectedNeighbors.add("u10j");
- expectedNeighbors.add("gcps");
- expectedNeighbors.add("gcpu");
- expectedNeighbors.add("u10h");
- Collection<? super String> neighbors = new ArrayList<>();
- GeoHashUtils.addNeighbors(geohash, neighbors );
- assertEquals(expectedNeighbors, neighbors);
-
- // Border odd geohash
- geohash = "u09x";
- expectedNeighbors = new ArrayList<>();
- expectedNeighbors.add("u0c2");
- expectedNeighbors.add("u0c8");
- expectedNeighbors.add("u0cb");
- expectedNeighbors.add("u09r");
- expectedNeighbors.add("u09z");
- expectedNeighbors.add("u09q");
- expectedNeighbors.add("u09w");
- expectedNeighbors.add("u09y");
- neighbors = new ArrayList<>();
- GeoHashUtils.addNeighbors(geohash, neighbors);
- assertEquals(expectedNeighbors, neighbors);
-
- // Border even geohash
- geohash = "u09tv";
- expectedNeighbors = new ArrayList<>();
- expectedNeighbors.add("u09wh");
- expectedNeighbors.add("u09wj");
- expectedNeighbors.add("u09wn");
- expectedNeighbors.add("u09tu");
- expectedNeighbors.add("u09ty");
- expectedNeighbors.add("u09ts");
- expectedNeighbors.add("u09tt");
- expectedNeighbors.add("u09tw");
- neighbors = new ArrayList<>();
- GeoHashUtils.addNeighbors(geohash, neighbors );
- assertEquals(expectedNeighbors, neighbors);
-
- // Border even and odd geohash
- geohash = "ezzzz";
- expectedNeighbors = new ArrayList<>();
- expectedNeighbors.add("gbpbn");
- expectedNeighbors.add("gbpbp");
- expectedNeighbors.add("u0000");
- expectedNeighbors.add("ezzzy");
- expectedNeighbors.add("spbpb");
- expectedNeighbors.add("ezzzw");
- expectedNeighbors.add("ezzzx");
- expectedNeighbors.add("spbp8");
- neighbors = new ArrayList<>();
- GeoHashUtils.addNeighbors(geohash, neighbors );
- assertEquals(expectedNeighbors, neighbors);
- }
-
- public void testClosestPointOnBBox() {
- double[] result = new double[2];
- GeoDistanceUtils.closestPointOnBBox(20, 30, 40, 50, 70, 70, result);
- assertEquals(40.0, result[0], 0.0);
- assertEquals(50.0, result[1], 0.0);
-
- GeoDistanceUtils.closestPointOnBBox(-20, -20, 0, 0, 70, 70, result);
- assertEquals(0.0, result[0], 0.0);
- assertEquals(0.0, result[1], 0.0);
- }
-
- private static class Cell {
- static int nextCellID;
-
- final Cell parent;
- final int cellID;
- final double minLon, maxLon;
- final double minLat, maxLat;
- final int splitCount;
-
- public Cell(Cell parent,
- double minLon, double minLat,
- double maxLon, double maxLat,
- int splitCount) {
- assert maxLon >= minLon;
- assert maxLat >= minLat;
- this.parent = parent;
- this.minLon = minLon;
- this.minLat = minLat;
- this.maxLon = maxLon;
- this.maxLat = maxLat;
- this.cellID = nextCellID++;
- this.splitCount = splitCount;
- }
-
- /** Returns true if the quantized point lies within this cell, inclusive on all bounds. */
- public boolean contains(double lon, double lat) {
- return lon >= minLon && lon <= maxLon && lat >= minLat && lat <= maxLat;
- }
-
- @Override
- public String toString() {
- return "cell=" + cellID + (parent == null ? "" : " parentCellID=" + parent.cellID) + " lon: " + minLon + " TO " + maxLon + ", lat: " + minLat + " TO " + maxLat + ", splits: " + splitCount;
- }
- }
-
- public long scaleLon(final double val) {
- return (long) ((val-GeoUtils.MIN_LON_INCL) * LON_SCALE);
- }
-
- public long scaleLat(final double val) {
- return (long) ((val-GeoUtils.MIN_LAT_INCL) * LAT_SCALE);
- }
-
- public double unscaleLon(final long val) {
- return (val / LON_SCALE) + GeoUtils.MIN_LON_INCL;
- }
-
- public double unscaleLat(final long val) {
- return (val / LAT_SCALE) + GeoUtils.MIN_LAT_INCL;
- }
-
- public double randomLat(boolean small) {
- double result;
- if (small) {
- result = GeoUtils.normalizeLat(originLat + latRange * (random().nextDouble() - 0.5));
- } else {
- result = -90 + 180.0 * random().nextDouble();
- }
- return result;
- }
-
- public double randomLon(boolean small) {
- double result;
- if (small) {
- result = GeoUtils.normalizeLon(originLon + lonRange * (random().nextDouble() - 0.5));
- } else {
- result = -180 + 360.0 * random().nextDouble();
- }
- return result;
- }
-
- private void findMatches(Set<Integer> hits, PrintWriter log, Cell root,
- double centerLon, double centerLat, double radiusMeters,
- double[] docLons, double[] docLats) {
-
- if (VERBOSE) {
- log.println(" root cell: " + root);
- }
-
- List<Cell> queue = new ArrayList<>();
- queue.add(root);
-
- int recurseDepth = RandomInts.randomIntBetween(random(), 5, 15);
-
- while (queue.size() > 0) {
- Cell cell = queue.get(queue.size()-1);
- queue.remove(queue.size()-1);
- if (VERBOSE) {
- log.println(" cycle: " + cell + " queue.size()=" + queue.size());
- }
-
- if (random().nextInt(10) == 7 || cell.splitCount > recurseDepth) {
- if (VERBOSE) {
- log.println(" leaf");
- }
- // Leaf cell: brute force check all docs that fall within this cell:
- for(int docID=0;docID<docLons.length;docID++) {
- if (cell.contains(docLons[docID], docLats[docID])) {
- double distanceMeters = GeoDistanceUtils.haversin(centerLat, centerLon, docLats[docID], docLons[docID]);
- if (distanceMeters <= radiusMeters) {
- if (VERBOSE) {
- log.println(" check doc=" + docID + ": match!");
- }
- hits.add(docID);
- } else {
- if (VERBOSE) {
- log.println(" check doc=" + docID + ": no match");
- }
- }
- }
- }
- } else {
-
- if (GeoRelationUtils.rectWithinCircle(cell.minLon, cell.minLat, cell.maxLon, cell.maxLat, centerLon, centerLat, radiusMeters)) {
- // Query circle fully contains this cell, just addAll:
- if (VERBOSE) {
- log.println(" circle fully contains cell: now addAll");
- }
- for(int docID=0;docID<docLons.length;docID++) {
- if (cell.contains(docLons[docID], docLats[docID])) {
- if (VERBOSE) {
- log.println(" addAll doc=" + docID);
- }
- hits.add(docID);
- }
- }
- continue;
- } else if (GeoRelationUtils.rectWithin(root.minLon, root.minLat, root.maxLon, root.maxLat,
- cell.minLon, cell.minLat, cell.maxLon, cell.maxLat)) {
- // Fall through below to "recurse"
- if (VERBOSE) {
- log.println(" cell fully contains circle: keep splitting");
- }
- } else if (GeoRelationUtils.rectCrossesCircle(cell.minLon, cell.minLat, cell.maxLon, cell.maxLat,
- centerLon, centerLat, radiusMeters)) {
- // Fall through below to "recurse"
- if (VERBOSE) {
- log.println(" cell overlaps circle: keep splitting");
- }
- } else {
- if (VERBOSE) {
- log.println(" no overlap: drop this cell");
- for(int docID=0;docID<docLons.length;docID++) {
- if (cell.contains(docLons[docID], docLats[docID])) {
- if (VERBOSE) {
- log.println(" skip doc=" + docID);
- }
- }
- }
- }
- continue;
- }
-
- // Randomly split:
- if (random().nextBoolean()) {
-
- // Split on lon:
- double splitValue = cell.minLon + (cell.maxLon - cell.minLon) * random().nextDouble();
- if (VERBOSE) {
- log.println(" now split on lon=" + splitValue);
- }
- Cell cell1 = new Cell(cell,
- cell.minLon, cell.minLat,
- splitValue, cell.maxLat,
- cell.splitCount+1);
- Cell cell2 = new Cell(cell,
- splitValue, cell.minLat,
- cell.maxLon, cell.maxLat,
- cell.splitCount+1);
- if (VERBOSE) {
- log.println(" split cell1: " + cell1);
- log.println(" split cell2: " + cell2);
- }
- queue.add(cell1);
- queue.add(cell2);
- } else {
-
- // Split on lat:
- double splitValue = cell.minLat + (cell.maxLat - cell.minLat) * random().nextDouble();
- if (VERBOSE) {
- log.println(" now split on lat=" + splitValue);
- }
- Cell cell1 = new Cell(cell,
- cell.minLon, cell.minLat,
- cell.maxLon, splitValue,
- cell.splitCount+1);
- Cell cell2 = new Cell(cell,
- cell.minLon, splitValue,
- cell.maxLon, cell.maxLat,
- cell.splitCount+1);
- if (VERBOSE) {
- log.println(" split cells:\n " + cell1 + "\n " + cell2);
- }
- queue.add(cell1);
- queue.add(cell2);
- }
- }
- }
- }
-
- /** Tests consistency of GeoUtils.rectWithinCircle, .rectCrossesCircle, .rectWithin and SloppyMath.haversine distance check */
- public void testGeoRelations() throws Exception {
-
- int numDocs = atLeast(1000);
-
- boolean useSmallRanges = random().nextBoolean();
-
- if (VERBOSE) {
- System.out.println("TEST: " + numDocs + " docs useSmallRanges=" + useSmallRanges);
- }
-
- double[] docLons = new double[numDocs];
- double[] docLats = new double[numDocs];
- for(int docID=0;docID<numDocs;docID++) {
- docLons[docID] = randomLon(useSmallRanges);
- docLats[docID] = randomLat(useSmallRanges);
- if (VERBOSE) {
- System.out.println(" doc=" + docID + ": lon=" + docLons[docID] + " lat=" + docLats[docID]);
- }
- }
-
- int iters = atLeast(10);
-
- iters = atLeast(50);
-
- for(int iter=0;iter<iters;iter++) {
-
- Cell.nextCellID = 0;
-
- double centerLon = randomLon(useSmallRanges);
- double centerLat = randomLat(useSmallRanges);
-
- // So the circle covers at most 50% of the earth's surface:
-
- double radiusMeters;
-
- // TODO: large exotic rectangles created by BKD may be inaccurate up to 2 times DISTANCE_PCT_ERR.
- // restricting size until LUCENE-6994 can be addressed
- if (true || useSmallRanges) {
- // Approx 3 degrees lon at the equator:
- radiusMeters = random().nextDouble() * 333000;
- } else {
- radiusMeters = random().nextDouble() * GeoProjectionUtils.SEMIMAJOR_AXIS * Math.PI / 2.0;
- }
-
- StringWriter sw = new StringWriter();
- PrintWriter log = new PrintWriter(sw, true);
-
- if (VERBOSE) {
- log.println("\nTEST: iter=" + iter + " radiusMeters=" + radiusMeters + " centerLon=" + centerLon + " centerLat=" + centerLat);
- }
-
- GeoRect bbox = GeoUtils.circleToBBox(centerLon, centerLat, radiusMeters);
-
- Set<Integer> hits = new HashSet<>();
-
- if (bbox.maxLon < bbox.minLon) {
- // Crosses dateline
- log.println(" circle crosses dateline; first left query");
- double unwrappedLon = centerLon;
- if (unwrappedLon > bbox.maxLon) {
- // unwrap left
- unwrappedLon += -360.0D;
- }
- findMatches(hits, log,
- new Cell(null,
- -180, bbox.minLat,
- bbox.maxLon, bbox.maxLat,
- 0),
- unwrappedLon, centerLat, radiusMeters, docLons, docLats);
- log.println(" circle crosses dateline; now right query");
- if (unwrappedLon < bbox.maxLon) {
- // unwrap right
- unwrappedLon += 360.0D;
- }
- findMatches(hits, log,
- new Cell(null,
- bbox.minLon, bbox.minLat,
- 180, bbox.maxLat,
- 0),
- unwrappedLon, centerLat, radiusMeters, docLons, docLats);
- } else {
- // Start with the root cell that fully contains the shape:
- findMatches(hits, log,
- new Cell(null,
- bbox.minLon, bbox.minLat,
- bbox.maxLon, bbox.maxLat,
- 0),
- centerLon, centerLat, radiusMeters,
- docLons, docLats);
- }
-
- if (VERBOSE) {
- log.println(" " + hits.size() + " hits");
- }
-
- int failCount = 0;
-
- // Done matching, now verify:
- for(int docID=0;docID<numDocs;docID++) {
- double distanceMeters = GeoDistanceUtils.haversin(centerLat, centerLon, docLats[docID], docLons[docID]);
- final Boolean expected;
- final double percentError = Math.abs(distanceMeters - radiusMeters) / distanceMeters;
- if (percentError <= DISTANCE_PCT_ERR) {
- expected = null;
- } else {
- expected = distanceMeters <= radiusMeters;
- }
-
- boolean actual = hits.contains(docID);
- if (expected != null && actual != expected) {
- if (actual) {
- log.println("doc=" + docID + " matched but should not with distance error " + percentError + " on iteration " + iter);
- } else {
- log.println("doc=" + docID + " did not match but should with distance error " + percentError + " on iteration " + iter);
- }
- log.println(" lon=" + docLons[docID] + " lat=" + docLats[docID] + " distanceMeters=" + distanceMeters + " vs radiusMeters=" + radiusMeters);
- failCount++;
- }
- }
-
- if (failCount != 0) {
- System.out.print(sw.toString());
- fail(failCount + " incorrect hits (see above)");
- }
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointField.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointField.java b/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointField.java
new file mode 100644
index 0000000..6386ee6
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointField.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.spatial.document;
+
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FieldType;
+import org.apache.lucene.index.DocValuesType;
+import org.apache.lucene.index.IndexOptions;
+import org.apache.lucene.spatial.util.GeoUtils;
+
+/**
+ * <p>
+ * Field that indexes <code>latitude</code> <code>longitude</code> decimal-degree values
+ * for efficient encoding, sorting, and querying. This Geo capability is intended
+ * to provide a basic and efficient out of the box field type for indexing and
+ * querying 2 dimensional points in WGS-84 decimal degrees. An example usage is as follows:
+ *
+ * <pre class="prettyprint">
+ * document.add(new GeoPointField(name, -96.33, 32.66, Field.Store.NO));
+ * </pre>
+ *
+ * <p>To perform simple geospatial queries against a <code>GeoPointField</code>,
+ * see {@link org.apache.lucene.spatial.search.GeoPointInBBoxQuery}, {@link org.apache.lucene.spatial.search.GeoPointInPolygonQuery},
+ * or {@link org.apache.lucene.spatial.search.GeoPointDistanceQuery}
+ *
+ * NOTE: This indexes only high precision encoded terms which may result in visiting a high number
+ * of terms for large queries. See LUCENE-6481 for a future improvement.
+ *
+ * @lucene.experimental
+ */
+public final class GeoPointField extends Field {
+ /** encoding step value for GeoPoint prefix terms */
+ public static final int PRECISION_STEP = 9;
+
+ /**
+ * Type for an GeoPointField that is not stored:
+ * normalization factors, frequencies, and positions are omitted.
+ */
+ public static final FieldType TYPE_NOT_STORED = new FieldType();
+ static {
+ TYPE_NOT_STORED.setTokenized(false);
+ TYPE_NOT_STORED.setOmitNorms(true);
+ TYPE_NOT_STORED.setIndexOptions(IndexOptions.DOCS);
+ TYPE_NOT_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
+ TYPE_NOT_STORED.setNumericType(FieldType.LegacyNumericType.LONG);
+ TYPE_NOT_STORED.setNumericPrecisionStep(PRECISION_STEP);
+ TYPE_NOT_STORED.freeze();
+ }
+
+ /**
+ * Type for a stored GeoPointField:
+ * normalization factors, frequencies, and positions are omitted.
+ */
+ public static final FieldType TYPE_STORED = new FieldType();
+ static {
+ TYPE_STORED.setTokenized(false);
+ TYPE_STORED.setOmitNorms(true);
+ TYPE_STORED.setIndexOptions(IndexOptions.DOCS);
+ TYPE_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
+ TYPE_STORED.setNumericType(FieldType.LegacyNumericType.LONG);
+ TYPE_STORED.setNumericPrecisionStep(PRECISION_STEP);
+ TYPE_STORED.setStored(true);
+ TYPE_STORED.freeze();
+ }
+
+ /** Creates a stored or un-stored GeoPointField with the provided value
+ * and default <code>precisionStep</code> set to 64 to avoid wasteful
+ * indexing of lower precision terms.
+ * @param name field name
+ * @param lon longitude double value [-180.0 : 180.0]
+ * @param lat latitude double value [-90.0 : 90.0]
+ * @param stored Store.YES if the content should also be stored
+ * @throws IllegalArgumentException if the field name is null.
+ */
+ public GeoPointField(String name, double lon, double lat, Store stored) {
+ super(name, stored == Store.YES ? TYPE_STORED : TYPE_NOT_STORED);
+ fieldsData = GeoUtils.mortonHash(lon, lat);
+ }
+
+ /** Expert: allows you to customize the {@link
+ * FieldType}.
+ * @param name field name
+ * @param lon longitude double value [-180.0 : 180.0]
+ * @param lat latitude double value [-90.0 : 90.0]
+ * @param type customized field type: must have {@link FieldType#numericType()}
+ * of {@link org.apache.lucene.document.FieldType.LegacyNumericType#LONG}.
+ * @throws IllegalArgumentException if the field name or type is null, or
+ * if the field type does not have a LONG numericType()
+ */
+ public GeoPointField(String name, double lon, double lat, FieldType type) {
+ super(name, type);
+ if (type.numericType() != FieldType.LegacyNumericType.LONG) {
+ throw new IllegalArgumentException("type.numericType() must be LONG but got " + type.numericType());
+ }
+ if (type.docValuesType() != DocValuesType.SORTED_NUMERIC) {
+ throw new IllegalArgumentException("type.docValuesType() must be SORTED_NUMERIC but got " + type.docValuesType());
+ }
+ fieldsData = GeoUtils.mortonHash(lon, lat);
+ }
+
+ /** access longitude value */
+ public double getLon() {
+ return GeoUtils.mortonUnhashLon((long) fieldsData);
+ }
+
+ /** access latitude value */
+ public double getLat() {
+ return GeoUtils.mortonUnhashLat((long) fieldsData);
+ }
+
+ @Override
+ public String toString() {
+ if (fieldsData == null) {
+ return null;
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.append(GeoUtils.mortonUnhashLon((long) fieldsData));
+ sb.append(',');
+ sb.append(GeoUtils.mortonUnhashLat((long) fieldsData));
+ return sb.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/document/package-info.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/document/package-info.java b/lucene/spatial/src/java/org/apache/lucene/spatial/document/package-info.java
new file mode 100644
index 0000000..2550fa1
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/document/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Geospatial Field Implementations for Core Lucene
+ */
+package org.apache.lucene.spatial.document;
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoBoundingBox.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoBoundingBox.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoBoundingBox.java
new file mode 100644
index 0000000..f26f8b1
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoBoundingBox.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.spatial.search;
+
+import org.apache.lucene.spatial.util.GeoUtils;
+
+/** NOTE: package private; just used so {@link GeoPointInPolygonQuery} can communicate its bounding box to {@link GeoPointInBBoxQuery}. */
+class GeoBoundingBox {
+ /** minimum longitude value (in degrees) */
+ public final double minLon;
+ /** minimum latitude value (in degrees) */
+ public final double maxLon;
+ /** maximum longitude value (in degrees) */
+ public final double minLat;
+ /** maximum latitude value (in degrees) */
+ public final double maxLat;
+
+ /**
+ * Constructs a bounding box by first validating the provided latitude and longitude coordinates
+ */
+ public GeoBoundingBox(double minLon, double maxLon, double minLat, double maxLat) {
+ if (GeoUtils.isValidLon(minLon) == false) {
+ throw new IllegalArgumentException("invalid minLon " + minLon);
+ }
+ if (GeoUtils.isValidLon(maxLon) == false) {
+ throw new IllegalArgumentException("invalid maxLon " + minLon);
+ }
+ if (GeoUtils.isValidLat(minLat) == false) {
+ throw new IllegalArgumentException("invalid minLat " + minLat);
+ }
+ if (GeoUtils.isValidLat(maxLat) == false) {
+ throw new IllegalArgumentException("invalid maxLat " + minLat);
+ }
+ this.minLon = minLon;
+ this.maxLon = maxLon;
+ this.minLat = minLat;
+ this.maxLat = maxLat;
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQuery.java
new file mode 100644
index 0000000..27f78e8
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQuery.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.spatial.search;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.spatial.util.GeoDistanceUtils;
+import org.apache.lucene.spatial.util.GeoRect;
+import org.apache.lucene.spatial.util.GeoUtils;
+
+/** Implements a simple point distance query on a GeoPoint field. This is based on
+ * {@link GeoPointInBBoxQuery} and is implemented using a two phase approach. First,
+ * like {@code GeoPointInBBoxQueryImpl} candidate terms are queried using the numeric ranges based on
+ * the morton codes of the min and max lat/lon pairs that intersect the boundary of the point-radius
+ * circle. Terms
+ * passing this initial filter are then passed to a secondary {@code postFilter} method that verifies whether the
+ * decoded lat/lon point fall within the specified query distance (see {@link org.apache.lucene.util.SloppyMath#haversin}.
+ * All morton value comparisons are subject to the same precision tolerance defined in
+ * {@value org.apache.lucene.spatial.util.GeoUtils#TOLERANCE} and distance comparisons are subject to the accuracy of the
+ * haversine formula (from R.W. Sinnott, "Virtues of the Haversine", Sky and Telescope, vol. 68, no. 2, 1984, p. 159)
+ *
+ * <p>Note: This query currently uses haversine which is a sloppy distance calculation (see above reference). For large
+ * queries one can expect upwards of 400m error. Vincenty shrinks this to ~40m error but pays a penalty for computing
+ * using the spheroid
+ *
+ * @lucene.experimental */
+public class GeoPointDistanceQuery extends GeoPointInBBoxQuery {
+ /** longitude value (in degrees) for query location */
+ protected final double centerLon;
+ /** latitude value (in degrees) for query location */
+ protected final double centerLat;
+ /** distance (in meters) from lon, lat center location */
+ protected final double radiusMeters;
+
+ /**
+ * Constructs a Query for all {@link org.apache.lucene.spatial.document.GeoPointField} types within a
+ * distance (in meters) from a given point
+ **/
+ public GeoPointDistanceQuery(final String field, final double centerLon, final double centerLat, final double radiusMeters) {
+ this(field, GeoUtils.circleToBBox(centerLon, centerLat, radiusMeters), centerLon, centerLat, radiusMeters);
+ }
+
+ private GeoPointDistanceQuery(final String field, GeoRect bbox, final double centerLon,
+ final double centerLat, final double radiusMeters) {
+ super(field, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
+ {
+ // check longitudinal overlap (limits radius)
+ final double maxRadius = GeoDistanceUtils.maxRadialDistanceMeters(centerLon, centerLat);
+ if (radiusMeters > maxRadius) {
+ throw new IllegalArgumentException("radiusMeters " + radiusMeters + " exceeds maxRadius [" + maxRadius
+ + "] at location [" + centerLon + " " + centerLat + "]");
+ }
+ }
+
+ if (GeoUtils.isValidLon(centerLon) == false) {
+ throw new IllegalArgumentException("invalid centerLon " + centerLon);
+ }
+
+ if (GeoUtils.isValidLat(centerLat) == false) {
+ throw new IllegalArgumentException("invalid centerLat " + centerLat);
+ }
+
+ if (radiusMeters <= 0.0) {
+ throw new IllegalArgumentException("invalid radiusMeters " + radiusMeters);
+ }
+
+ this.centerLon = centerLon;
+ this.centerLat = centerLat;
+ this.radiusMeters = radiusMeters;
+ }
+
+ @Override
+ public Query rewrite(IndexReader reader) {
+ // query crosses dateline; split into left and right queries
+ if (maxLon < minLon) {
+ BooleanQuery.Builder bqb = new BooleanQuery.Builder();
+
+ // unwrap the longitude iff outside the specified min/max lon range
+ double unwrappedLon = centerLon;
+ if (unwrappedLon > maxLon) {
+ // unwrap left
+ unwrappedLon += -360.0D;
+ }
+ GeoPointDistanceQueryImpl left = new GeoPointDistanceQueryImpl(field, this, unwrappedLon,
+ new GeoRect(GeoUtils.MIN_LON_INCL, maxLon, minLat, maxLat));
+ bqb.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
+
+ if (unwrappedLon < maxLon) {
+ // unwrap right
+ unwrappedLon += 360.0D;
+ }
+ GeoPointDistanceQueryImpl right = new GeoPointDistanceQueryImpl(field, this, unwrappedLon,
+ new GeoRect(minLon, GeoUtils.MAX_LON_INCL, minLat, maxLat));
+ bqb.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
+
+ return bqb.build();
+ }
+ return new GeoPointDistanceQueryImpl(field, this, centerLon,
+ new GeoRect(this.minLon, this.maxLon, this.minLat, this.maxLat));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof GeoPointDistanceQuery)) return false;
+ if (!super.equals(o)) return false;
+
+ GeoPointDistanceQuery that = (GeoPointDistanceQuery) o;
+
+ if (Double.compare(that.centerLat, centerLat) != 0) return false;
+ if (Double.compare(that.centerLon, centerLon) != 0) return false;
+ if (Double.compare(that.radiusMeters, radiusMeters) != 0) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ long temp;
+ temp = Double.doubleToLongBits(centerLon);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(centerLat);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(radiusMeters);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ @Override
+ public String toString(String field) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(getClass().getSimpleName());
+ sb.append(':');
+ if (!this.field.equals(field)) {
+ sb.append(" field=");
+ sb.append(this.field);
+ sb.append(':');
+ }
+ return sb.append( " Center: [")
+ .append(centerLon)
+ .append(',')
+ .append(centerLat)
+ .append(']')
+ .append(" Distance: ")
+ .append(radiusMeters)
+ .append(" meters")
+ .append("]")
+ .toString();
+ }
+
+ /** getter method for center longitude value */
+ public double getCenterLon() {
+ return this.centerLon;
+ }
+
+ /** getter method for center latitude value */
+ public double getCenterLat() {
+ return this.centerLat;
+ }
+
+ /** getter method for distance value (in meters) */
+ public double getRadiusMeters() {
+ return this.radiusMeters;
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQueryImpl.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQueryImpl.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQueryImpl.java
new file mode 100644
index 0000000..0777799
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQueryImpl.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.spatial.search;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.search.MultiTermQuery;
+import org.apache.lucene.util.AttributeSource;
+import org.apache.lucene.spatial.document.GeoPointField;
+import org.apache.lucene.spatial.util.GeoRect;
+import org.apache.lucene.spatial.util.GeoRelationUtils;
+import org.apache.lucene.util.SloppyMath;
+
+/** Package private implementation for the public facing GeoPointDistanceQuery delegate class.
+ *
+ * @lucene.experimental
+ */
+final class GeoPointDistanceQueryImpl extends GeoPointInBBoxQueryImpl {
+ private final GeoPointDistanceQuery query;
+ private final double centerLon;
+
+ GeoPointDistanceQueryImpl(final String field, final GeoPointDistanceQuery q, final double centerLonUnwrapped,
+ final GeoRect bbox) {
+ super(field, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
+ query = q;
+ centerLon = centerLonUnwrapped;
+ }
+
+ @Override @SuppressWarnings("unchecked")
+ protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
+ return new GeoPointRadiusTermsEnum(terms.iterator(), this.minLon, this.minLat, this.maxLon, this.maxLat);
+ }
+
+ @Override
+ public void setRewriteMethod(MultiTermQuery.RewriteMethod method) {
+ throw new UnsupportedOperationException("cannot change rewrite method");
+ }
+
+ private final class GeoPointRadiusTermsEnum extends GeoPointTermsEnum {
+ GeoPointRadiusTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
+ final double maxLon, final double maxLat) {
+ super(tenum, minLon, minLat, maxLon, maxLat);
+ }
+
+ /**
+ * Computes the maximum shift for the given pointDistanceQuery. This prevents unnecessary depth traversal
+ * given the size of the distance query.
+ */
+ @Override
+ protected short computeMaxShift() {
+ final short shiftFactor;
+
+ if (query.radiusMeters > 1000000) {
+ shiftFactor = 5;
+ } else {
+ shiftFactor = 4;
+ }
+
+ return (short)(GeoPointField.PRECISION_STEP * shiftFactor);
+ }
+
+ @Override
+ protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+ return GeoRelationUtils.rectCrossesCircle(minLon, minLat, maxLon, maxLat,
+ centerLon, query.centerLat, query.radiusMeters, true);
+ }
+
+ @Override
+ protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+ return GeoRelationUtils.rectWithinCircle(minLon, minLat, maxLon, maxLat,
+ centerLon, query.centerLat, query.radiusMeters, true);
+ }
+
+ @Override
+ protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+ return (cellContains(minLon, minLat, maxLon, maxLat)
+ || cellWithin(minLon, minLat, maxLon, maxLat) || cellCrosses(minLon, minLat, maxLon, maxLat));
+ }
+
+ /**
+ * The two-phase query approach. The parent {@link GeoPointTermsEnum} class matches
+ * encoded terms that fall within the minimum bounding box of the point-radius circle. Those documents that pass
+ * the initial bounding box filter are then post filter compared to the provided distance using the
+ * {@link org.apache.lucene.util.SloppyMath#haversin} method.
+ */
+ @Override
+ protected boolean postFilter(final double lon, final double lat) {
+ return (SloppyMath.haversin(query.centerLat, centerLon, lat, lon) * 1000.0 <= query.radiusMeters);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof GeoPointDistanceQueryImpl)) return false;
+ if (!super.equals(o)) return false;
+
+ GeoPointDistanceQueryImpl that = (GeoPointDistanceQueryImpl) o;
+
+ if (!query.equals(that.query)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + query.hashCode();
+ return result;
+ }
+
+ public double getRadiusMeters() {
+ return query.getRadiusMeters();
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceRangeQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceRangeQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceRangeQuery.java
new file mode 100644
index 0000000..df76c0c
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceRangeQuery.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.spatial.search;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Query;
+
+/** Implements a point distance range query on a GeoPoint field. This is based on
+ * {@code org.apache.lucene.spatial.search.GeoPointDistanceQuery} and is implemented using a
+ * {@code org.apache.lucene.search.BooleanClause.MUST_NOT} clause to exclude any points that fall within
+ * minRadiusMeters from the provided point.
+ *
+ * @lucene.experimental
+ */
+public final class GeoPointDistanceRangeQuery extends GeoPointDistanceQuery {
+ protected final double minRadiusMeters;
+
+ /**
+ * Constructs a query for all {@link org.apache.lucene.spatial.document.GeoPointField} types within a minimum / maximum
+ * distance (in meters) range from a given point
+ */
+ public GeoPointDistanceRangeQuery(final String field, final double centerLon, final double centerLat,
+ final double minRadiusMeters, final double maxRadius) {
+ super(field, centerLon, centerLat, maxRadius);
+ this.minRadiusMeters = minRadiusMeters;
+ }
+
+ @Override
+ public Query rewrite(IndexReader reader) {
+ Query q = super.rewrite(reader);
+ if (minRadiusMeters == 0.0) {
+ return q;
+ }
+
+ // add an exclusion query
+ BooleanQuery.Builder bqb = new BooleanQuery.Builder();
+
+ // create a new exclusion query
+ GeoPointDistanceQuery exclude = new GeoPointDistanceQuery(field, centerLon, centerLat, minRadiusMeters);
+ // full map search
+// if (radiusMeters >= GeoProjectionUtils.SEMIMINOR_AXIS) {
+// bqb.add(new BooleanClause(new GeoPointInBBoxQuery(this.field, -180.0, -90.0, 180.0, 90.0), BooleanClause.Occur.MUST));
+// } else {
+ bqb.add(new BooleanClause(q, BooleanClause.Occur.MUST));
+// }
+ bqb.add(new BooleanClause(exclude, BooleanClause.Occur.MUST_NOT));
+
+ return bqb.build();
+ }
+
+ @Override
+ public String toString(String field) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(getClass().getSimpleName());
+ sb.append(':');
+ if (!this.field.equals(field)) {
+ sb.append(" field=");
+ sb.append(this.field);
+ sb.append(':');
+ }
+ return sb.append( " Center: [")
+ .append(centerLon)
+ .append(',')
+ .append(centerLat)
+ .append(']')
+ .append(" From Distance: ")
+ .append(minRadiusMeters)
+ .append(" m")
+ .append(" To Distance: ")
+ .append(radiusMeters)
+ .append(" m")
+ .append(" Lower Left: [")
+ .append(minLon)
+ .append(',')
+ .append(minLat)
+ .append(']')
+ .append(" Upper Right: [")
+ .append(maxLon)
+ .append(',')
+ .append(maxLat)
+ .append("]")
+ .toString();
+ }
+
+ /** getter method for minimum distance */
+ public double getMinRadiusMeters() {
+ return this.minRadiusMeters;
+ }
+
+ /** getter method for maximum distance */
+ public double getMaxRadiusMeters() {
+ return this.radiusMeters;
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQuery.java
new file mode 100644
index 0000000..75f38a9
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQuery.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.spatial.search;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.FieldValueQuery;
+import org.apache.lucene.search.LegacyNumericRangeQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.spatial.util.GeoUtils;
+
+/** Implements a simple bounding box query on a GeoPoint field. This is inspired by
+ * {@link LegacyNumericRangeQuery} and is implemented using a
+ * two phase approach. First, candidate terms are queried using a numeric
+ * range based on the morton codes of the min and max lat/lon pairs. Terms
+ * passing this initial filter are passed to a final check that verifies whether
+ * the decoded lat/lon falls within (or on the boundary) of the query bounding box.
+ * The value comparisons are subject to a precision tolerance defined in
+ * {@value org.apache.lucene.spatial.util.GeoUtils#TOLERANCE}
+ *
+ * NOTES:
+ * 1. All latitude/longitude values must be in decimal degrees.
+ * 2. Complex computational geometry (e.g., dateline wrapping) is not supported
+ * 3. For more advanced GeoSpatial indexing and query operations see spatial module
+ * 4. This is well suited for small rectangles, large bounding boxes may result
+ * in many terms, depending whether the bounding box falls on the boundary of
+ * many cells (degenerate case)
+ *
+ * @lucene.experimental
+ */
+public class GeoPointInBBoxQuery extends Query {
+ protected final String field;
+ protected final double minLon;
+ protected final double minLat;
+ protected final double maxLon;
+ protected final double maxLat;
+
+ /**
+ * Constructs a query for all {@link org.apache.lucene.spatial.document.GeoPointField} types that fall within a
+ * defined bounding box
+ */
+ public GeoPointInBBoxQuery(final String field, final double minLon, final double minLat, final double maxLon, final double maxLat) {
+ this.field = field;
+ this.minLon = minLon;
+ this.minLat = minLat;
+ this.maxLon = maxLon;
+ this.maxLat = maxLat;
+ }
+
+ @Override
+ public Query rewrite(IndexReader reader) {
+ // short-circuit to match all if specifying the whole map
+ if (minLon == GeoUtils.MIN_LON_INCL && maxLon == GeoUtils.MAX_LON_INCL
+ && minLat == GeoUtils.MIN_LAT_INCL && maxLat == GeoUtils.MAX_LAT_INCL) {
+ // FieldValueQuery is valid since DocValues are *required* for GeoPointField
+ return new FieldValueQuery(field);
+ }
+
+ if (maxLon < minLon) {
+ BooleanQuery.Builder bqb = new BooleanQuery.Builder();
+
+ GeoPointInBBoxQueryImpl left = new GeoPointInBBoxQueryImpl(field, -180.0D, minLat, maxLon, maxLat);
+ bqb.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
+ GeoPointInBBoxQueryImpl right = new GeoPointInBBoxQueryImpl(field, minLon, minLat, 180.0D, maxLat);
+ bqb.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
+ return bqb.build();
+ }
+ return new GeoPointInBBoxQueryImpl(field, minLon, minLat, maxLon, maxLat);
+ }
+
+ @Override
+ public String toString(String field) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(getClass().getSimpleName());
+ sb.append(':');
+ if (!this.field.equals(field)) {
+ sb.append(" field=");
+ sb.append(this.field);
+ sb.append(':');
+ }
+ return sb.append(" Lower Left: [")
+ .append(minLon)
+ .append(',')
+ .append(minLat)
+ .append(']')
+ .append(" Upper Right: [")
+ .append(maxLon)
+ .append(',')
+ .append(maxLat)
+ .append("]")
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof GeoPointInBBoxQuery)) return false;
+ if (!super.equals(o)) return false;
+
+ GeoPointInBBoxQuery that = (GeoPointInBBoxQuery) o;
+
+ if (Double.compare(that.maxLat, maxLat) != 0) return false;
+ if (Double.compare(that.maxLon, maxLon) != 0) return false;
+ if (Double.compare(that.minLat, minLat) != 0) return false;
+ if (Double.compare(that.minLon, minLon) != 0) return false;
+ if (!field.equals(that.field)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ long temp;
+ result = 31 * result + field.hashCode();
+ temp = Double.doubleToLongBits(minLon);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(minLat);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(maxLon);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(maxLat);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ /** getter method for retrieving the field name */
+ public final String getField() {
+ return this.field;
+ }
+
+ /** getter method for retrieving the minimum longitude (in degrees) */
+ public final double getMinLon() {
+ return this.minLon;
+ }
+
+ /** getter method for retrieving the minimum latitude (in degrees) */
+ public final double getMinLat() {
+ return this.minLat;
+ }
+
+ /** getter method for retrieving the maximum longitude (in degrees) */
+ public final double getMaxLon() {
+ return this.maxLon;
+ }
+
+ /** getter method for retrieving the maximum latitude (in degrees) */
+ public final double getMaxLat() {
+ return this.maxLat;
+ }
+}
[5/6] lucene-solr git commit: LUCENE-6997: refactor sandboxed
GeoPointField and query classes to lucene-spatial module
Posted by nk...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/util/GeoHashUtils.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/util/GeoHashUtils.java b/lucene/sandbox/src/java/org/apache/lucene/util/GeoHashUtils.java
deleted file mode 100644
index d421cc9..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/util/GeoHashUtils.java
+++ /dev/null
@@ -1,273 +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.util;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-/**
- * Utilities for converting to/from the GeoHash standard
- *
- * The geohash long format is represented as lon/lat (x/y) interleaved with the 4 least significant bits
- * representing the level (1-12) [xyxy...xyxyllll]
- *
- * This differs from a morton encoded value which interleaves lat/lon (y/x).
- *
- * @lucene.experimental
- */
-public class GeoHashUtils {
- public 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'};
-
- public static final String BASE_32_STRING = new String(BASE_32);
-
- public static final int PRECISION = 12;
- private static final short MORTON_OFFSET = (GeoUtils.BITS<<1) - (PRECISION*5);
-
- /**
- * Encode lon/lat to the geohash based long format (lon/lat interleaved, 4 least significant bits = level)
- */
- public static final long longEncode(final double lon, final double lat, final int level) {
- // shift to appropriate level
- final short msf = (short)(((12 - level) * 5) + MORTON_OFFSET);
- return ((BitUtil.flipFlop(GeoUtils.mortonHash(lon, lat)) >>> msf) << 4) | level;
- }
-
- /**
- * Encode from geohash string to the geohash based long format (lon/lat interleaved, 4 least significant bits = level)
- */
- public static final long longEncode(final String hash) {
- int level = hash.length()-1;
- long b;
- long l = 0L;
- for(char c : hash.toCharArray()) {
- b = (long)(BASE_32_STRING.indexOf(c));
- l |= (b<<(level--*5));
- }
- return (l<<4)|hash.length();
- }
-
- /**
- * Encode an existing geohash long to the provided precision
- */
- public static long longEncode(long geohash, int level) {
- final short precision = (short)(geohash & 15);
- if (precision == level) {
- return geohash;
- } else if (precision > level) {
- return ((geohash >>> (((precision - level) * 5) + 4)) << 4) | level;
- }
- return ((geohash >>> 4) << (((level - precision) * 5) + 4) | level);
- }
-
- public static long fromMorton(long morton, int level) {
- long mFlipped = BitUtil.flipFlop(morton);
- mFlipped >>>= (((GeoHashUtils.PRECISION - level) * 5) + MORTON_OFFSET);
- return (mFlipped << 4) | level;
- }
-
- /**
- * Encode to a geohash string from the geohash based long format
- */
- public static final String stringEncode(long geoHashLong) {
- int level = (int)geoHashLong&15;
- geoHashLong >>>= 4;
- char[] chars = new char[level];
- do {
- chars[--level] = BASE_32[(int) (geoHashLong&31L)];
- geoHashLong>>>=5;
- } while(level > 0);
-
- return new String(chars);
- }
-
- /**
- * Encode to a geohash string from full resolution longitude, latitude)
- */
- public static final String stringEncode(final double lon, final double lat) {
- return stringEncode(lon, lat, 12);
- }
-
- /**
- * Encode to a level specific geohash string from full resolution longitude, latitude
- */
- public static final String stringEncode(final double lon, final double lat, final int level) {
- // convert to geohashlong
- final long ghLong = fromMorton(GeoUtils.mortonHash(lon, lat), level);
- return stringEncode(ghLong);
-
- }
-
- /**
- * Encode to a full precision geohash string from a given morton encoded long value
- */
- public static final String stringEncodeFromMortonLong(final long hashedVal) throws Exception {
- return stringEncode(hashedVal, PRECISION);
- }
-
- /**
- * Encode to a geohash string at a given level from a morton long
- */
- public static final String stringEncodeFromMortonLong(long hashedVal, final int level) {
- // bit twiddle to geohash (since geohash is a swapped (lon/lat) encoding)
- hashedVal = BitUtil.flipFlop(hashedVal);
-
- StringBuilder geoHash = new StringBuilder();
- short precision = 0;
- final short msf = (GeoUtils.BITS<<1)-5;
- long mask = 31L<<msf;
- do {
- geoHash.append(BASE_32[(int)((mask & hashedVal)>>>(msf-(precision*5)))]);
- // next 5 bits
- mask >>>= 5;
- } while (++precision < level);
- return geoHash.toString();
- }
-
- /**
- * Encode to a morton long value from a given geohash string
- */
- public static final long mortonEncode(final String hash) {
- int level = 11;
- long b;
- long l = 0L;
- for(char c : hash.toCharArray()) {
- b = (long)(BASE_32_STRING.indexOf(c));
- l |= (b<<((level--*5) + MORTON_OFFSET));
- }
- return BitUtil.flipFlop(l);
- }
-
- /**
- * Encode to a morton long value from a given geohash long value
- */
- public static final long mortonEncode(final long geoHashLong) {
- final int level = (int)(geoHashLong&15);
- final short odd = (short)(level & 1);
-
- return BitUtil.flipFlop(((geoHashLong >>> 4) << odd) << (((12 - level) * 5) + (MORTON_OFFSET - odd)));
- }
-
- private static final char encode(int x, int y) {
- return BASE_32[((x & 1) + ((y & 1) * 2) + ((x & 2) * 2) + ((y & 2) * 4) + ((x & 4) * 4)) % 32];
- }
-
- /**
- * Calculate all neighbors of a given geohash cell.
- *
- * @param geohash Geohash of the defined cell
- * @return geohashes of all neighbor cells
- */
- public static Collection<? extends CharSequence> neighbors(String geohash) {
- return addNeighbors(geohash, geohash.length(), new ArrayList<CharSequence>(8));
- }
-
- /**
- * Calculate the geohash of a neighbor of a geohash
- *
- * @param geohash the geohash of a cell
- * @param level level of the geohash
- * @param dx delta of the first grid coordinate (must be -1, 0 or +1)
- * @param dy delta of the second grid coordinate (must be -1, 0 or +1)
- * @return geohash of the defined cell
- */
- public final static String neighbor(String geohash, int level, int dx, int dy) {
- int cell = BASE_32_STRING.indexOf(geohash.charAt(level -1));
-
- // Decoding the Geohash bit pattern to determine grid coordinates
- int x0 = cell & 1; // first bit of x
- int y0 = cell & 2; // first bit of y
- int x1 = cell & 4; // second bit of x
- int y1 = cell & 8; // second bit of y
- int x2 = cell & 16; // third bit of x
-
- // combine the bitpattern to grid coordinates.
- // note that the semantics of x and y are swapping
- // on each level
- int x = x0 + (x1 / 2) + (x2 / 4);
- int y = (y0 / 2) + (y1 / 4);
-
- if (level == 1) {
- // Root cells at north (namely "bcfguvyz") or at
- // south (namely "0145hjnp") do not have neighbors
- // in north/south direction
- if ((dy < 0 && y == 0) || (dy > 0 && y == 3)) {
- return null;
- } else {
- return Character.toString(encode(x + dx, y + dy));
- }
- } else {
- // define grid coordinates for next level
- final int nx = ((level % 2) == 1) ? (x + dx) : (x + dy);
- final int ny = ((level % 2) == 1) ? (y + dy) : (y + dx);
-
- // if the defined neighbor has the same parent a the current cell
- // encode the cell directly. Otherwise find the cell next to this
- // cell recursively. Since encoding wraps around within a cell
- // it can be encoded here.
- // xLimit and YLimit must always be respectively 7 and 3
- // since x and y semantics are swapping on each level.
- if (nx >= 0 && nx <= 7 && ny >= 0 && ny <= 3) {
- return geohash.substring(0, level - 1) + encode(nx, ny);
- } else {
- String neighbor = neighbor(geohash, level - 1, dx, dy);
- return (neighbor != null) ? neighbor + encode(nx, ny) : neighbor;
- }
- }
- }
-
- /**
- * Add all geohashes of the cells next to a given geohash to a list.
- *
- * @param geohash Geohash of a specified cell
- * @param neighbors list to add the neighbors to
- * @return the given list
- */
- public static final <E extends Collection<? super String>> E addNeighbors(String geohash, E neighbors) {
- return addNeighbors(geohash, geohash.length(), neighbors);
- }
-
- /**
- * Add all geohashes of the cells next to a given geohash to a list.
- *
- * @param geohash Geohash of a specified cell
- * @param length level of the given geohash
- * @param neighbors list to add the neighbors to
- * @return the given list
- */
- public static final <E extends Collection<? super String>> E addNeighbors(String geohash, int length, E neighbors) {
- String south = neighbor(geohash, length, 0, -1);
- String north = neighbor(geohash, length, 0, +1);
- if (north != null) {
- neighbors.add(neighbor(north, length, -1, 0));
- neighbors.add(north);
- neighbors.add(neighbor(north, length, +1, 0));
- }
-
- neighbors.add(neighbor(geohash, length, -1, 0));
- neighbors.add(neighbor(geohash, length, +1, 0));
-
- if (south != null) {
- neighbors.add(neighbor(south, length, -1, 0));
- neighbors.add(south);
- neighbors.add(neighbor(south, length, +1, 0));
- }
-
- return neighbors;
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/util/GeoProjectionUtils.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/util/GeoProjectionUtils.java b/lucene/sandbox/src/java/org/apache/lucene/util/GeoProjectionUtils.java
deleted file mode 100644
index 7e285da..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/util/GeoProjectionUtils.java
+++ /dev/null
@@ -1,437 +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.util;
-
-import static org.apache.lucene.util.SloppyMath.PIO2;
-import static org.apache.lucene.util.SloppyMath.TO_DEGREES;
-import static org.apache.lucene.util.SloppyMath.TO_RADIANS;
-
-/**
- * Reusable geo-spatial projection utility methods.
- *
- * @lucene.experimental
- */
-public class GeoProjectionUtils {
- // WGS84 earth-ellipsoid major (a) minor (b) radius, (f) flattening and eccentricity (e)
- public static final double SEMIMAJOR_AXIS = 6_378_137; // [m]
- public static final double FLATTENING = 1.0/298.257223563;
- public static final double SEMIMINOR_AXIS = SEMIMAJOR_AXIS * (1.0 - FLATTENING); //6_356_752.31420; // [m]
- public static final double ECCENTRICITY = StrictMath.sqrt((2.0 - FLATTENING) * FLATTENING);
- static final double SEMIMAJOR_AXIS2 = SEMIMAJOR_AXIS * SEMIMAJOR_AXIS;
- static final double SEMIMINOR_AXIS2 = SEMIMINOR_AXIS * SEMIMINOR_AXIS;
- public static final double MIN_LON_RADIANS = TO_RADIANS * GeoUtils.MIN_LON_INCL;
- public static final double MIN_LAT_RADIANS = TO_RADIANS * GeoUtils.MIN_LAT_INCL;
- public static final double MAX_LON_RADIANS = TO_RADIANS * GeoUtils.MAX_LON_INCL;
- public static final double MAX_LAT_RADIANS = TO_RADIANS * GeoUtils.MAX_LAT_INCL;
- private static final double E2 = (SEMIMAJOR_AXIS2 - SEMIMINOR_AXIS2)/(SEMIMAJOR_AXIS2);
- private static final double EP2 = (SEMIMAJOR_AXIS2 - SEMIMINOR_AXIS2)/(SEMIMINOR_AXIS2);
-
- /**
- * Converts from geocentric earth-centered earth-fixed to geodesic lat/lon/alt
- * @param x Cartesian x coordinate
- * @param y Cartesian y coordinate
- * @param z Cartesian z coordinate
- * @param lla 0: longitude 1: latitude: 2: altitude
- * @return double array as 0: longitude 1: latitude 2: altitude
- */
- public static final double[] ecfToLLA(final double x, final double y, final double z, double[] lla) {
- boolean atPole = false;
- final double ad_c = 1.0026000D;
- final double cos67P5 = 0.38268343236508977D;
-
- if (lla == null) {
- lla = new double[3];
- }
-
- if (x != 0.0) {
- lla[0] = StrictMath.atan2(y,x);
- } else {
- if (y > 0) {
- lla[0] = PIO2;
- } else if (y < 0) {
- lla[0] = -PIO2;
- } else {
- atPole = true;
- lla[0] = 0.0D;
- if (z > 0.0) {
- lla[1] = PIO2;
- } else if (z < 0.0) {
- lla[1] = -PIO2;
- } else {
- lla[1] = PIO2;
- lla[2] = -SEMIMINOR_AXIS;
- return lla;
- }
- }
- }
-
- final double w2 = x*x + y*y;
- final double w = StrictMath.sqrt(w2);
- final double t0 = z * ad_c;
- final double s0 = StrictMath.sqrt(t0 * t0 + w2);
- final double sinB0 = t0 / s0;
- final double cosB0 = w / s0;
- final double sin3B0 = sinB0 * sinB0 * sinB0;
- final double t1 = z + SEMIMINOR_AXIS * EP2 * sin3B0;
- final double sum = w - SEMIMAJOR_AXIS * E2 * cosB0 * cosB0 * cosB0;
- final double s1 = StrictMath.sqrt(t1 * t1 + sum * sum);
- final double sinP1 = t1 / s1;
- final double cosP1 = sum / s1;
- final double rn = SEMIMAJOR_AXIS / StrictMath.sqrt(1.0D - E2 * sinP1 * sinP1);
-
- if (cosP1 >= cos67P5) {
- lla[2] = w / cosP1 - rn;
- } else if (cosP1 <= -cos67P5) {
- lla[2] = w / -cosP1 - rn;
- } else {
- lla[2] = z / sinP1 + rn * (E2 - 1.0);
- }
- if (!atPole) {
- lla[1] = StrictMath.atan(sinP1/cosP1);
- }
- lla[0] = TO_DEGREES * lla[0];
- lla[1] = TO_DEGREES * lla[1];
-
- return lla;
- }
-
- /**
- * Converts from geodesic lon lat alt to geocentric earth-centered earth-fixed
- * @param lon geodesic longitude
- * @param lat geodesic latitude
- * @param alt geodesic altitude
- * @param ecf reusable earth-centered earth-fixed result
- * @return either a new ecef array or the reusable ecf parameter
- */
- public static final double[] llaToECF(double lon, double lat, double alt, double[] ecf) {
- lon = TO_RADIANS * lon;
- lat = TO_RADIANS * lat;
-
- final double sl = SloppyMath.sin(lat);
- final double s2 = sl*sl;
- final double cl = SloppyMath.cos(lat);
-
- if (ecf == null) {
- ecf = new double[3];
- }
-
- if (lat < -PIO2 && lat > -1.001D * PIO2) {
- lat = -PIO2;
- } else if (lat > PIO2 && lat < 1.001D * PIO2) {
- lat = PIO2;
- }
- assert (lat >= -PIO2) || (lat <= PIO2);
-
- if (lon > StrictMath.PI) {
- lon -= (2*StrictMath.PI);
- }
-
- final double rn = SEMIMAJOR_AXIS / StrictMath.sqrt(1.0D - E2 * s2);
- ecf[0] = (rn+alt) * cl * SloppyMath.cos(lon);
- ecf[1] = (rn+alt) * cl * SloppyMath.sin(lon);
- ecf[2] = ((rn*(1.0-E2))+alt)*sl;
-
- return ecf;
- }
-
- /**
- * Converts from lat lon alt (in degrees) to East North Up right-hand coordinate system
- * @param lon longitude in degrees
- * @param lat latitude in degrees
- * @param alt altitude in meters
- * @param centerLon reference point longitude in degrees
- * @param centerLat reference point latitude in degrees
- * @param centerAlt reference point altitude in meters
- * @param enu result east, north, up coordinate
- * @return east, north, up coordinate
- */
- public static double[] llaToENU(final double lon, final double lat, final double alt, double centerLon,
- double centerLat, final double centerAlt, double[] enu) {
- if (enu == null) {
- enu = new double[3];
- }
-
- // convert point to ecf coordinates
- final double[] ecf = llaToECF(lon, lat, alt, null);
-
- // convert from ecf to enu
- return ecfToENU(ecf[0], ecf[1], ecf[2], centerLon, centerLat, centerAlt, enu);
- }
-
- /**
- * Converts from East North Up right-hand rule to lat lon alt in degrees
- * @param x easting (in meters)
- * @param y northing (in meters)
- * @param z up (in meters)
- * @param centerLon reference point longitude (in degrees)
- * @param centerLat reference point latitude (in degrees)
- * @param centerAlt reference point altitude (in meters)
- * @param lla resulting lat, lon, alt point (in degrees)
- * @return lat, lon, alt point (in degrees)
- */
- public static double[] enuToLLA(final double x, final double y, final double z, final double centerLon,
- final double centerLat, final double centerAlt, double[] lla) {
- // convert enuToECF
- if (lla == null) {
- lla = new double[3];
- }
-
- // convert enuToECF, storing intermediate result in lla
- lla = enuToECF(x, y, z, centerLon, centerLat, centerAlt, lla);
-
- // convert ecf to LLA
- return ecfToLLA(lla[0], lla[1], lla[2], lla);
- }
-
- /**
- * Convert from Earth-Centered-Fixed to Easting, Northing, Up Right Hand System
- * @param x ECF X coordinate (in meters)
- * @param y ECF Y coordinate (in meters)
- * @param z ECF Z coordinate (in meters)
- * @param centerLon ENU origin longitude (in degrees)
- * @param centerLat ENU origin latitude (in degrees)
- * @param centerAlt ENU altitude (in meters)
- * @param enu reusable enu result
- * @return Easting, Northing, Up coordinate
- */
- public static double[] ecfToENU(double x, double y, double z, final double centerLon,
- final double centerLat, final double centerAlt, double[] enu) {
- if (enu == null) {
- enu = new double[3];
- }
-
- // create rotation matrix and rotate to enu orientation
- final double[][] phi = createPhiTransform(centerLon, centerLat, null);
-
- // convert origin to ENU
- final double[] originECF = llaToECF(centerLon, centerLat, centerAlt, null);
- final double[] originENU = new double[3];
- originENU[0] = ((phi[0][0] * originECF[0]) + (phi[0][1] * originECF[1]) + (phi[0][2] * originECF[2]));
- originENU[1] = ((phi[1][0] * originECF[0]) + (phi[1][1] * originECF[1]) + (phi[1][2] * originECF[2]));
- originENU[2] = ((phi[2][0] * originECF[0]) + (phi[2][1] * originECF[1]) + (phi[2][2] * originECF[2]));
-
- // rotate then translate
- enu[0] = ((phi[0][0] * x) + (phi[0][1] * y) + (phi[0][2] * z)) - originENU[0];
- enu[1] = ((phi[1][0] * x) + (phi[1][1] * y) + (phi[1][2] * z)) - originENU[1];
- enu[2] = ((phi[2][0] * x) + (phi[2][1] * y) + (phi[2][2] * z)) - originENU[2];
-
- return enu;
- }
-
- /**
- * Convert from Easting, Northing, Up Right-Handed system to Earth Centered Fixed system
- * @param x ENU x coordinate (in meters)
- * @param y ENU y coordinate (in meters)
- * @param z ENU z coordinate (in meters)
- * @param centerLon ENU origin longitude (in degrees)
- * @param centerLat ENU origin latitude (in degrees)
- * @param centerAlt ENU origin altitude (in meters)
- * @param ecf reusable ecf result
- * @return ecf result coordinate
- */
- public static double[] enuToECF(final double x, final double y, final double z, double centerLon,
- double centerLat, final double centerAlt, double[] ecf) {
- if (ecf == null) {
- ecf = new double[3];
- }
-
- double[][] phi = createTransposedPhiTransform(centerLon, centerLat, null);
- double[] ecfOrigin = llaToECF(centerLon, centerLat, centerAlt, null);
-
- // rotate and translate
- ecf[0] = (phi[0][0]*x + phi[0][1]*y + phi[0][2]*z) + ecfOrigin[0];
- ecf[1] = (phi[1][0]*x + phi[1][1]*y + phi[1][2]*z) + ecfOrigin[1];
- ecf[2] = (phi[2][0]*x + phi[2][1]*y + phi[2][2]*z) + ecfOrigin[2];
-
- return ecf;
- }
-
- /**
- * Create the rotation matrix for converting Earth Centered Fixed to Easting Northing Up
- * @param originLon ENU origin longitude (in degrees)
- * @param originLat ENU origin latitude (in degrees)
- * @param phiMatrix reusable phi matrix result
- * @return phi rotation matrix
- */
- private static double[][] createPhiTransform(double originLon, double originLat, double[][] phiMatrix) {
-
- if (phiMatrix == null) {
- phiMatrix = new double[3][3];
- }
-
- originLon = TO_RADIANS * originLon;
- originLat = TO_RADIANS * originLat;
-
- final double sLon = SloppyMath.sin(originLon);
- final double cLon = SloppyMath.cos(originLon);
- final double sLat = SloppyMath.sin(originLat);
- final double cLat = SloppyMath.cos(originLat);
-
- phiMatrix[0][0] = -sLon;
- phiMatrix[0][1] = cLon;
- phiMatrix[0][2] = 0.0D;
- phiMatrix[1][0] = -sLat * cLon;
- phiMatrix[1][1] = -sLat * sLon;
- phiMatrix[1][2] = cLat;
- phiMatrix[2][0] = cLat * cLon;
- phiMatrix[2][1] = cLat * sLon;
- phiMatrix[2][2] = sLat;
-
- return phiMatrix;
- }
-
- /**
- * Create the transposed rotation matrix for converting Easting Northing Up coordinates to Earth Centered Fixed
- * @param originLon ENU origin longitude (in degrees)
- * @param originLat ENU origin latitude (in degrees)
- * @param phiMatrix reusable phi rotation matrix result
- * @return transposed phi rotation matrix
- */
- private static double[][] createTransposedPhiTransform(double originLon, double originLat, double[][] phiMatrix) {
-
- if (phiMatrix == null) {
- phiMatrix = new double[3][3];
- }
-
- originLon = TO_RADIANS * originLon;
- originLat = TO_RADIANS * originLat;
-
- final double sLat = SloppyMath.sin(originLat);
- final double cLat = SloppyMath.cos(originLat);
- final double sLon = SloppyMath.sin(originLon);
- final double cLon = SloppyMath.cos(originLon);
-
- phiMatrix[0][0] = -sLon;
- phiMatrix[1][0] = cLon;
- phiMatrix[2][0] = 0.0D;
- phiMatrix[0][1] = -sLat * cLon;
- phiMatrix[1][1] = -sLat * sLon;
- phiMatrix[2][1] = cLat;
- phiMatrix[0][2] = cLat * cLon;
- phiMatrix[1][2] = cLat * sLon;
- phiMatrix[2][2] = sLat;
-
- return phiMatrix;
- }
-
- /**
- * Finds a point along a bearing from a given lon,lat geolocation using vincenty's distance formula
- *
- * @param lon origin longitude in degrees
- * @param lat origin latitude in degrees
- * @param bearing azimuthal bearing in degrees
- * @param dist distance in meters
- * @param pt resulting point
- * @return the point along a bearing at a given distance in meters
- */
- public static final double[] pointFromLonLatBearingVincenty(double lon, double lat, double bearing, double dist, double[] pt) {
-
- if (pt == null) {
- pt = new double[2];
- }
-
- final double alpha1 = TO_RADIANS * bearing;
- final double cosA1 = SloppyMath.cos(alpha1);
- final double sinA1 = SloppyMath.sin(alpha1);
- final double tanU1 = (1-FLATTENING) * SloppyMath.tan(TO_RADIANS * lat);
- final double cosU1 = 1 / StrictMath.sqrt((1+tanU1*tanU1));
- final double sinU1 = tanU1*cosU1;
- final double sig1 = StrictMath.atan2(tanU1, cosA1);
- final double sinAlpha = cosU1 * sinA1;
- final double cosSqAlpha = 1 - sinAlpha*sinAlpha;
- final double uSq = cosSqAlpha * EP2;
- final double A = 1 + uSq/16384D*(4096D + uSq * (-768D + uSq * (320D - 175D*uSq)));
- final double B = uSq/1024D * (256D + uSq * (-128D + uSq * (74D - 47D * uSq)));
-
- double sigma = dist / (SEMIMINOR_AXIS*A);
- double sigmaP;
- double sinSigma, cosSigma, cos2SigmaM, deltaSigma;
-
- do {
- cos2SigmaM = SloppyMath.cos(2*sig1 + sigma);
- sinSigma = SloppyMath.sin(sigma);
- cosSigma = SloppyMath.cos(sigma);
-
- deltaSigma = B * sinSigma * (cos2SigmaM + (B/4D) * (cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
- (B/6) * cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
- sigmaP = sigma;
- sigma = dist / (SEMIMINOR_AXIS*A) + deltaSigma;
- } while (StrictMath.abs(sigma-sigmaP) > 1E-12);
-
- final double tmp = sinU1*sinSigma - cosU1*cosSigma*cosA1;
- final double lat2 = StrictMath.atan2(sinU1*cosSigma + cosU1*sinSigma*cosA1,
- (1-FLATTENING) * StrictMath.sqrt(sinAlpha*sinAlpha + tmp*tmp));
- final double lambda = StrictMath.atan2(sinSigma*sinA1, cosU1*cosSigma - sinU1*sinSigma*cosA1);
- final double c = FLATTENING/16 * cosSqAlpha * (4 + FLATTENING * (4 - 3 * cosSqAlpha));
-
- final double lam = lambda - (1-c) * FLATTENING * sinAlpha *
- (sigma + c * sinSigma * (cos2SigmaM + c * cosSigma * (-1 + 2* cos2SigmaM*cos2SigmaM)));
- pt[0] = GeoUtils.normalizeLon(lon + TO_DEGREES * lam);
- pt[1] = GeoUtils.normalizeLat(TO_DEGREES * lat2);
-
- return pt;
- }
-
- /**
- * Finds a point along a bearing from a given lon,lat geolocation using great circle arc
- *
- * @param lon origin longitude in degrees
- * @param lat origin latitude in degrees
- * @param bearing azimuthal bearing in degrees
- * @param dist distance in meters
- * @param pt resulting point
- * @return the point along a bearing at a given distance in meters
- */
- public static final double[] pointFromLonLatBearingGreatCircle(double lon, double lat, double bearing, double dist, double[] pt) {
-
- if (pt == null) {
- pt = new double[2];
- }
-
- lon *= TO_RADIANS;
- lat *= TO_RADIANS;
- bearing *= TO_RADIANS;
-
- final double cLat = SloppyMath.cos(lat);
- final double sLat = SloppyMath.sin(lat);
- final double sinDoR = SloppyMath.sin(dist / GeoProjectionUtils.SEMIMAJOR_AXIS);
- final double cosDoR = SloppyMath.cos(dist / GeoProjectionUtils.SEMIMAJOR_AXIS);
-
- pt[1] = SloppyMath.asin(sLat*cosDoR + cLat * sinDoR * SloppyMath.cos(bearing));
- pt[0] = TO_DEGREES * (lon + Math.atan2(SloppyMath.sin(bearing) * sinDoR * cLat, cosDoR - sLat * SloppyMath.sin(pt[1])));
- pt[1] *= TO_DEGREES;
-
- return pt;
- }
-
- /**
- * Finds the bearing (in degrees) between 2 geo points (lon, lat) using great circle arc
- * @param lon1 first point longitude in degrees
- * @param lat1 first point latitude in degrees
- * @param lon2 second point longitude in degrees
- * @param lat2 second point latitude in degrees
- * @return the bearing (in degrees) between the two provided points
- */
- public static double bearingGreatCircle(double lon1, double lat1, double lon2, double lat2) {
- double dLon = (lon2 - lon1) * TO_RADIANS;
- lat2 *= TO_RADIANS;
- lat1 *= TO_RADIANS;
- double y = SloppyMath.sin(dLon) * SloppyMath.cos(lat2);
- double x = SloppyMath.cos(lat1) * SloppyMath.sin(lat2) - SloppyMath.sin(lat1) * SloppyMath.cos(lat2) * SloppyMath.cos(dLon);
- return Math.atan2(y, x) * TO_DEGREES;
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/util/GeoRect.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/util/GeoRect.java b/lucene/sandbox/src/java/org/apache/lucene/util/GeoRect.java
deleted file mode 100644
index f8cf1da..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/util/GeoRect.java
+++ /dev/null
@@ -1,66 +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.util;
-
-/** Represents a lat/lon rectangle. */
-public class GeoRect {
- public final double minLon;
- public final double maxLon;
- public final double minLat;
- public final double maxLat;
-
- public GeoRect(double minLon, double maxLon, double minLat, double maxLat) {
- if (GeoUtils.isValidLon(minLon) == false) {
- throw new IllegalArgumentException("invalid minLon " + minLon);
- }
- if (GeoUtils.isValidLon(maxLon) == false) {
- throw new IllegalArgumentException("invalid maxLon " + maxLon);
- }
- if (GeoUtils.isValidLat(minLat) == false) {
- throw new IllegalArgumentException("invalid minLat " + minLat);
- }
- if (GeoUtils.isValidLat(maxLat) == false) {
- throw new IllegalArgumentException("invalid maxLat " + maxLat);
- }
- this.minLon = minLon;
- this.maxLon = maxLon;
- this.minLat = minLat;
- this.maxLat = maxLat;
- assert maxLat >= minLat;
-
- // NOTE: cannot assert maxLon >= minLon since this rect could cross the dateline
- }
-
- @Override
- public String toString() {
- StringBuilder b = new StringBuilder();
- b.append("GeoRect(lon=");
- b.append(minLon);
- b.append(" TO ");
- b.append(maxLon);
- if (maxLon < minLon) {
- b.append(" (crosses dateline!)");
- }
- b.append(" lat=");
- b.append(minLat);
- b.append(" TO ");
- b.append(maxLat);
- b.append(")");
-
- return b.toString();
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/util/GeoRelationUtils.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/util/GeoRelationUtils.java b/lucene/sandbox/src/java/org/apache/lucene/util/GeoRelationUtils.java
deleted file mode 100644
index 092b949..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/util/GeoRelationUtils.java
+++ /dev/null
@@ -1,497 +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.util;
-
-/**
- * Reusable geo-relation utility methods
- */
-public class GeoRelationUtils {
-
- /**
- * Determine if a bbox (defined by minLon, minLat, maxLon, maxLat) contains the provided point (defined by lon, lat)
- * NOTE: this is a basic method that does not handle dateline or pole crossing. Unwrapping must be done before
- * calling this method.
- */
- public static boolean pointInRectPrecise(final double lon, final double lat, final double minLon,
- final double minLat, final double maxLon, final double maxLat) {
- return lon >= minLon && lon <= maxLon && lat >= minLat && lat <= maxLat;
- }
-
- /**
- * simple even-odd point in polygon computation
- * 1. Determine if point is contained in the longitudinal range
- * 2. Determine whether point crosses the edge by computing the latitudinal delta
- * between the end-point of a parallel vector (originating at the point) and the
- * y-component of the edge sink
- *
- * NOTE: Requires polygon point (x,y) order either clockwise or counter-clockwise
- */
- public static boolean pointInPolygon(double[] x, double[] y, double lat, double lon) {
- assert x.length == y.length;
- boolean inPoly = false;
- /**
- * Note: This is using a euclidean coordinate system which could result in
- * upwards of 110KM error at the equator.
- * TODO convert coordinates to cylindrical projection (e.g. mercator)
- */
- for (int i = 1; i < x.length; i++) {
- if (x[i] <= lon && x[i-1] >= lon || x[i-1] <= lon && x[i] >= lon) {
- if (y[i] + (lon - x[i]) / (x[i-1] - x[i]) * (y[i-1] - y[i]) <= lat) {
- inPoly = !inPoly;
- }
- }
- }
- return inPoly;
- }
-
- /////////////////////////
- // Rectangle relations
- /////////////////////////
-
- public static boolean rectDisjoint(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
- final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
- return (aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY);
- }
-
- /**
- * Computes whether the first (a) rectangle is wholly within another (b) rectangle (shared boundaries allowed)
- */
- public static boolean rectWithin(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
- final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
- return !(aMinX < bMinX || aMinY < bMinY || aMaxX > bMaxX || aMaxY > bMaxY);
- }
-
- public static boolean rectCrosses(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
- final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
- return !(rectDisjoint(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY) ||
- rectWithin(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY));
- }
-
- /**
- * Computes whether rectangle a contains rectangle b (touching allowed)
- */
- public static boolean rectContains(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
- final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
- return !(bMinX < aMinX || bMinY < aMinY || bMaxX > aMaxX || bMaxY > aMaxY);
- }
-
- /**
- * Computes whether a rectangle intersects another rectangle (crosses, within, touching, etc)
- */
- public static boolean rectIntersects(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
- final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
- return !((aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY) );
- }
-
- /////////////////////////
- // Polygon relations
- /////////////////////////
-
- /**
- * Convenience method for accurately computing whether a rectangle crosses a poly
- */
- public static boolean rectCrossesPolyPrecise(final double rMinX, final double rMinY, final double rMaxX,
- final double rMaxY, final double[] shapeX, final double[] shapeY,
- final double sMinX, final double sMinY, final double sMaxX,
- final double sMaxY) {
- // short-circuit: if the bounding boxes are disjoint then the shape does not cross
- if (rectDisjoint(rMinX, rMinY, rMaxX, rMaxY, sMinX, sMinY, sMaxX, sMaxY)) {
- return false;
- }
- return rectCrossesPoly(rMinX, rMinY, rMaxX, rMaxY, shapeX, shapeY);
- }
-
- /**
- * Compute whether a rectangle crosses a shape. (touching not allowed) Includes a flag for approximating the
- * relation.
- */
- public static boolean rectCrossesPolyApprox(final double rMinX, final double rMinY, final double rMaxX,
- final double rMaxY, final double[] shapeX, final double[] shapeY,
- final double sMinX, final double sMinY, final double sMaxX,
- final double sMaxY) {
- // short-circuit: if the bounding boxes are disjoint then the shape does not cross
- if (rectDisjoint(rMinX, rMinY, rMaxX, rMaxY, sMinX, sMinY, sMaxX, sMaxY)) {
- return false;
- }
-
- final int polyLength = shapeX.length-1;
- for (short p=0; p<polyLength; ++p) {
- if (lineCrossesRect(shapeX[p], shapeY[p], shapeX[p+1], shapeY[p+1], rMinX, rMinY, rMaxX, rMaxY) == true) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Accurately compute (within restrictions of cartesian decimal degrees) whether a rectangle crosses a polygon
- */
- private static boolean rectCrossesPoly(final double rMinX, final double rMinY, final double rMaxX,
- final double rMaxY, final double[] shapeX, final double[] shapeY) {
- final double[][] bbox = new double[][] { {rMinX, rMinY}, {rMaxX, rMinY}, {rMaxX, rMaxY}, {rMinX, rMaxY}, {rMinX, rMinY} };
- final int polyLength = shapeX.length-1;
- double d, s, t, a1, b1, c1, a2, b2, c2;
- double x00, y00, x01, y01, x10, y10, x11, y11;
-
- // computes the intersection point between each bbox edge and the polygon edge
- for (short b=0; b<4; ++b) {
- a1 = bbox[b+1][1]-bbox[b][1];
- b1 = bbox[b][0]-bbox[b+1][0];
- c1 = a1*bbox[b+1][0] + b1*bbox[b+1][1];
- for (int p=0; p<polyLength; ++p) {
- a2 = shapeY[p+1]-shapeY[p];
- b2 = shapeX[p]-shapeX[p+1];
- // compute determinant
- d = a1*b2 - a2*b1;
- if (d != 0) {
- // lines are not parallel, check intersecting points
- c2 = a2*shapeX[p+1] + b2*shapeY[p+1];
- s = (1/d)*(b2*c1 - b1*c2);
- t = (1/d)*(a1*c2 - a2*c1);
- x00 = StrictMath.min(bbox[b][0], bbox[b+1][0]) - GeoUtils.TOLERANCE;
- x01 = StrictMath.max(bbox[b][0], bbox[b+1][0]) + GeoUtils.TOLERANCE;
- y00 = StrictMath.min(bbox[b][1], bbox[b+1][1]) - GeoUtils.TOLERANCE;
- y01 = StrictMath.max(bbox[b][1], bbox[b+1][1]) + GeoUtils.TOLERANCE;
- x10 = StrictMath.min(shapeX[p], shapeX[p+1]) - GeoUtils.TOLERANCE;
- x11 = StrictMath.max(shapeX[p], shapeX[p+1]) + GeoUtils.TOLERANCE;
- y10 = StrictMath.min(shapeY[p], shapeY[p+1]) - GeoUtils.TOLERANCE;
- y11 = StrictMath.max(shapeY[p], shapeY[p+1]) + GeoUtils.TOLERANCE;
- // check whether the intersection point is touching one of the line segments
- boolean touching = ((x00 == s && y00 == t) || (x01 == s && y01 == t))
- || ((x10 == s && y10 == t) || (x11 == s && y11 == t));
- // if line segments are not touching and the intersection point is within the range of either segment
- if (!(touching || x00 > s || x01 < s || y00 > t || y01 < t || x10 > s || x11 < s || y10 > t || y11 < t)) {
- return true;
- }
- }
- } // for each poly edge
- } // for each bbox edge
- return false;
- }
-
- private static boolean lineCrossesRect(double aX1, double aY1, double aX2, double aY2,
- final double rMinX, final double rMinY, final double rMaxX, final double rMaxY) {
- // short-circuit: if one point inside rect, other outside
- if (pointInRectPrecise(aX1, aY1, rMinX, rMinY, rMaxX, rMaxY) ?
- !pointInRectPrecise(aX2, aY2, rMinX, rMinY, rMaxX, rMaxY) : pointInRectPrecise(aX2, aY2, rMinX, rMinY, rMaxX, rMaxY)) {
- return true;
- }
-
- return lineCrossesLine(aX1, aY1, aX2, aY2, rMinX, rMinY, rMaxX, rMaxY)
- || lineCrossesLine(aX1, aY1, aX2, aY2, rMaxX, rMinY, rMinX, rMaxY);
- }
-
- private static boolean lineCrossesLine(final double aX1, final double aY1, final double aX2, final double aY2,
- final double bX1, final double bY1, final double bX2, final double bY2) {
- // determine if three points are ccw (right-hand rule) by computing the determinate
- final double aX2X1d = aX2 - aX1;
- final double aY2Y1d = aY2 - aY1;
- final double bX2X1d = bX2 - bX1;
- final double bY2Y1d = bY2 - bY1;
-
- final double t1B = aX2X1d * (bY2 - aY1) - aY2Y1d * (bX2 - aX1);
- final double test1 = (aX2X1d * (bY1 - aY1) - aY2Y1d * (bX1 - aX1)) * t1B;
- final double t2B = bX2X1d * (aY2 - bY1) - bY2Y1d * (aX2 - bX1);
- final double test2 = (bX2X1d * (aY1 - bY1) - bY2Y1d * (aX1 - bX1)) * t2B;
-
- if (test1 < 0 && test2 < 0) {
- return true;
- }
-
- if (test1 == 0 || test2 == 0) {
- // vertically collinear
- if (aX1 == aX2 || bX1 == bX2) {
- final double minAy = Math.min(aY1, aY2);
- final double maxAy = Math.max(aY1, aY2);
- final double minBy = Math.min(bY1, bY2);
- final double maxBy = Math.max(bY1, bY2);
-
- return !(minBy >= maxAy || maxBy <= minAy);
- }
- // horizontally collinear
- final double minAx = Math.min(aX1, aX2);
- final double maxAx = Math.max(aX1, aX2);
- final double minBx = Math.min(bX1, bX2);
- final double maxBx = Math.max(bX1, bX2);
-
- return !(minBx >= maxAx || maxBx <= minAx);
- }
- return false;
- }
-
- public static boolean rectWithinPolyPrecise(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
- final double[] shapeX, final double[] shapeY, final double sMinX,
- final double sMinY, final double sMaxX, final double sMaxY) {
- // check if rectangle crosses poly (to handle concave/pacman polys), then check that all 4 corners
- // are contained
- return !(rectCrossesPolyPrecise(rMinX, rMinY, rMaxX, rMaxY, shapeX, shapeY, sMinX, sMinY, sMaxX, sMaxY) ||
- !pointInPolygon(shapeX, shapeY, rMinY, rMinX) || !pointInPolygon(shapeX, shapeY, rMinY, rMaxX) ||
- !pointInPolygon(shapeX, shapeY, rMaxY, rMaxX) || !pointInPolygon(shapeX, shapeY, rMaxY, rMinX));
- }
-
- /**
- * Computes whether a rectangle is within a given polygon (shared boundaries allowed)
- */
- public static boolean rectWithinPolyApprox(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
- final double[] shapeX, final double[] shapeY, final double sMinX,
- final double sMinY, final double sMaxX, final double sMaxY) {
- // approximation: check if rectangle crosses poly (to handle concave/pacman polys), then check one of the corners
- // are contained
-
- // short-cut: if bounding boxes cross, rect is not within
- if (rectCrosses(rMinX, rMinY, rMaxX, rMaxY, sMinX, sMinY, sMaxX, sMaxY) == true) {
- return false;
- }
-
- return !(rectCrossesPolyApprox(rMinX, rMinY, rMaxX, rMaxY, shapeX, shapeY, sMinX, sMinY, sMaxX, sMaxY)
- || !pointInPolygon(shapeX, shapeY, rMinY, rMinX));
- }
-
- /////////////////////////
- // Circle relations
- /////////////////////////
-
- private static boolean rectAnyCornersInCircle(final double rMinX, final double rMinY, final double rMaxX,
- final double rMaxY, final double centerLon, final double centerLat,
- final double radiusMeters, final boolean approx) {
- if (approx == true) {
- return rectAnyCornersInCircleSloppy(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters);
- }
- double w = Math.abs(rMaxX - rMinX);
- if (w <= 90.0) {
- return GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMinX) <= radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMinX) <= radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMaxX) <= radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMaxX) <= radiusMeters;
- }
- // partition
- w /= 4;
- final double p1 = rMinX + w;
- final double p2 = p1 + w;
- final double p3 = p2 + w;
-
- return GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMinX) <= radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMinX) <= radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, p1) <= radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, p1) <= radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, p2) <= radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, p2) <= radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, p3) <= radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, p3) <= radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMaxX) <= radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMaxX) <= radiusMeters;
- }
-
- private static boolean rectAnyCornersInCircleSloppy(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
- final double centerLon, final double centerLat, final double radiusMeters) {
- return SloppyMath.haversin(centerLat, centerLon, rMinY, rMinX)*1000.0 <= radiusMeters
- || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMinX)*1000.0 <= radiusMeters
- || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMaxX)*1000.0 <= radiusMeters
- || SloppyMath.haversin(centerLat, centerLon, rMinY, rMaxX)*1000.0 <= radiusMeters;
- }
-
- /**
- * Compute whether any of the 4 corners of the rectangle (defined by min/max X/Y) are outside the circle (defined
- * by centerLon, centerLat, radiusMeters)
- *
- * Note: exotic rectangles at the poles (e.g., those whose lon/lat distance ratios greatly deviate from 1) can not
- * be determined by using distance alone. For this reason the approx flag may be set to false, in which case the
- * space will be further divided to more accurately compute whether the rectangle crosses the circle
- */
- private static boolean rectAnyCornersOutsideCircle(final double rMinX, final double rMinY, final double rMaxX,
- final double rMaxY, final double centerLon, final double centerLat,
- final double radiusMeters, final boolean approx) {
- if (approx == true) {
- return rectAnyCornersOutsideCircleSloppy(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters);
- }
- // if span is less than 70 degrees we can approximate using distance alone
- if (Math.abs(rMaxX - rMinX) <= 70.0) {
- return GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMinX) > radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMinX) > radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxY, rMaxX) > radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMinY, rMaxX) > radiusMeters;
- }
- return rectCrossesOblateCircle(centerLon, centerLat, radiusMeters, rMinX, rMinY, rMaxX, rMaxY);
- }
-
- /**
- * Compute whether the rectangle (defined by min/max Lon/Lat) crosses a potentially oblate circle
- *
- * TODO benchmark for replacing existing rectCrossesCircle.
- */
- public static boolean rectCrossesOblateCircle(double centerLon, double centerLat, double radiusMeters, double rMinLon, double rMinLat, double rMaxLon, double rMaxLat) {
- double w = Math.abs(rMaxLon - rMinLon);
- final int segs = (int)Math.ceil(w / 45.0);
- w /= segs;
- short i = 1;
- double p1 = rMinLon;
- double maxLon, midLon;
- double[] pt = new double[2];
-
- do {
- maxLon = (i == segs) ? rMaxLon : p1 + w;
-
- final double d1, d2;
- // short-circuit if we find a corner outside the circle
- if ( (d1 = GeoDistanceUtils.haversin(centerLat, centerLon, rMinLat, p1)) > radiusMeters
- || (d2 = GeoDistanceUtils.haversin(centerLat, centerLon, rMinLat, maxLon)) > radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxLat, p1) > radiusMeters
- || GeoDistanceUtils.haversin(centerLat, centerLon, rMaxLat, maxLon) > radiusMeters) {
- return true;
- }
-
- // else we treat as an oblate circle by slicing the longitude space and checking the azimuthal range
- // OPTIMIZATION: this is only executed for latitude values "closeTo" the poles (e.g., 88.0 > lat < -88.0)
- if ( (rMaxLat > 88.0 || rMinLat < -88.0)
- && (pt = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(p1, rMinLat,
- GeoProjectionUtils.bearingGreatCircle(p1, rMinLat, p1, rMaxLat), radiusMeters - d1, pt))[1] < rMinLat || pt[1] < rMaxLat
- || (pt = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(maxLon, rMinLat,
- GeoProjectionUtils.bearingGreatCircle(maxLon, rMinLat, maxLon, rMaxLat), radiusMeters - d2, pt))[1] < rMinLat || pt[1] < rMaxLat
- || (pt = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(maxLon, rMinLat,
- GeoProjectionUtils.bearingGreatCircle(maxLon, rMinLat, (midLon = p1 + 0.5*(maxLon - p1)), rMaxLat),
- radiusMeters - GeoDistanceUtils.haversin(centerLat, centerLon, rMinLat, midLon), pt))[1] < rMinLat
- || pt[1] < rMaxLat == false ) {
- return true;
- }
- p1 += w;
- } while (++i <= segs);
- return false;
- }
-
- private static boolean rectAnyCornersOutsideCircleSloppy(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
- final double centerLon, final double centerLat, final double radiusMeters) {
- return SloppyMath.haversin(centerLat, centerLon, rMinY, rMinX)*1000.0 > radiusMeters
- || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMinX)*1000.0 > radiusMeters
- || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMaxX)*1000.0 > radiusMeters
- || SloppyMath.haversin(centerLat, centerLon, rMinY, rMaxX)*1000.0 > radiusMeters;
- }
-
- public static boolean rectWithinCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
- final double centerLon, final double centerLat, final double radiusMeters) {
- return rectWithinCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, false);
- }
-
- public static boolean rectWithinCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
- final double centerLon, final double centerLat, final double radiusMeters,
- final boolean approx) {
- return rectAnyCornersOutsideCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx) == false;
- }
-
- /**
- * Determine if a bbox (defined by minLon, minLat, maxLon, maxLat) contains the provided point (defined by lon, lat)
- * NOTE: this is basic method that does not handle dateline or pole crossing. Unwrapping must be done before
- * calling this method.
- */
- public static boolean rectCrossesCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
- final double centerLon, final double centerLat, final double radiusMeters) {
- return rectCrossesCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, false);
- }
-
- public static boolean rectCrossesCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
- final double centerLon, final double centerLat, final double radiusMeters,
- final boolean approx) {
- if (approx == true) {
- return rectAnyCornersInCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx)
- || isClosestPointOnRectWithinRange(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx);
- }
-
- return (rectAnyCornersInCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx) &&
- rectAnyCornersOutsideCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx))
- || isClosestPointOnRectWithinRange(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters, approx);
- }
-
- private static boolean isClosestPointOnRectWithinRange(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
- final double centerLon, final double centerLat, final double radiusMeters,
- final boolean approx) {
- double[] closestPt = {0, 0};
- GeoDistanceUtils.closestPointOnBBox(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, closestPt);
- boolean haverShortCut = GeoDistanceUtils.haversin(centerLat, centerLon, closestPt[1], closestPt[0]) <= radiusMeters;
- if (approx == true || haverShortCut == true) {
- return haverShortCut;
- }
- double lon1 = rMinX;
- double lon2 = rMaxX;
- double lat1 = rMinY;
- double lat2 = rMaxY;
- if (closestPt[0] == rMinX || closestPt[0] == rMaxX) {
- lon1 = closestPt[0];
- lon2 = lon1;
- } else if (closestPt[1] == rMinY || closestPt[1] == rMaxY) {
- lat1 = closestPt[1];
- lat2 = lat1;
- }
-
- return lineCrossesSphere(lon1, lat1, 0, lon2, lat2, 0, centerLon, centerLat, 0, radiusMeters);
- }
-
- /**
- * Computes whether or a 3dimensional line segment intersects or crosses a sphere
- *
- * @param lon1 longitudinal location of the line segment start point (in degrees)
- * @param lat1 latitudinal location of the line segment start point (in degrees)
- * @param alt1 altitude of the line segment start point (in degrees)
- * @param lon2 longitudinal location of the line segment end point (in degrees)
- * @param lat2 latitudinal location of the line segment end point (in degrees)
- * @param alt2 altitude of the line segment end point (in degrees)
- * @param centerLon longitudinal location of center search point (in degrees)
- * @param centerLat latitudinal location of center search point (in degrees)
- * @param centerAlt altitude of the center point (in meters)
- * @param radiusMeters search sphere radius (in meters)
- * @return whether the provided line segment is a secant of the
- */
- private static boolean lineCrossesSphere(double lon1, double lat1, double alt1, double lon2,
- double lat2, double alt2, double centerLon, double centerLat,
- double centerAlt, double radiusMeters) {
- // convert to cartesian 3d (in meters)
- double[] ecf1 = GeoProjectionUtils.llaToECF(lon1, lat1, alt1, null);
- double[] ecf2 = GeoProjectionUtils.llaToECF(lon2, lat2, alt2, null);
- double[] cntr = GeoProjectionUtils.llaToECF(centerLon, centerLat, centerAlt, null);
-
- // convert radius from arc radius to cartesian radius
- double[] oneEighty = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(centerLon, centerLat, 180.0d, radiusMeters, new double[3]);
- GeoProjectionUtils.llaToECF(oneEighty[0], oneEighty[1], 0, oneEighty);
-
- radiusMeters = GeoDistanceUtils.linearDistance(oneEighty, cntr);// Math.sqrt(oneEighty[0]*cntr[0] + oneEighty[1]*cntr[1] + oneEighty[2]*cntr[2]);
-
- final double dX = ecf2[0] - ecf1[0];
- final double dY = ecf2[1] - ecf1[1];
- final double dZ = ecf2[2] - ecf1[2];
- final double fX = ecf1[0] - cntr[0];
- final double fY = ecf1[1] - cntr[1];
- final double fZ = ecf1[2] - cntr[2];
-
- final double a = dX*dX + dY*dY + dZ*dZ;
- final double b = 2 * (fX*dX + fY*dY + fZ*dZ);
- final double c = (fX*fX + fY*fY + fZ*fZ) - (radiusMeters*radiusMeters);
-
- double discrim = (b*b)-(4*a*c);
- if (discrim < 0) {
- return false;
- }
-
- discrim = StrictMath.sqrt(discrim);
- final double a2 = 2*a;
- final double t1 = (-b - discrim)/a2;
- final double t2 = (-b + discrim)/a2;
-
- if ( (t1 < 0 || t1 > 1) ) {
- return !(t2 < 0 || t2 > 1);
- }
-
- return true;
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/util/GeoUtils.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/util/GeoUtils.java b/lucene/sandbox/src/java/org/apache/lucene/util/GeoUtils.java
deleted file mode 100644
index a2d0fd1..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/util/GeoUtils.java
+++ /dev/null
@@ -1,237 +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.util;
-
-import java.util.ArrayList;
-
-import static org.apache.lucene.util.SloppyMath.TO_DEGREES;
-import static org.apache.lucene.util.SloppyMath.TO_RADIANS;
-
-/**
- * Basic reusable geo-spatial utility methods
- *
- * @lucene.experimental
- */
-public final class GeoUtils {
- public static final short BITS = 31;
- private static final double LON_SCALE = (0x1L<<BITS)/360.0D;
- private static final double LAT_SCALE = (0x1L<<BITS)/180.0D;
- public static final double TOLERANCE = 1E-6;
-
- /** Minimum longitude value. */
- public static final double MIN_LON_INCL = -180.0D;
-
- /** Maximum longitude value. */
- public static final double MAX_LON_INCL = 180.0D;
-
- /** Minimum latitude value. */
- public static final double MIN_LAT_INCL = -90.0D;
-
- /** Maximum latitude value. */
- public static final double MAX_LAT_INCL = 90.0D;
-
- // No instance:
- private GeoUtils() {
- }
-
- public static final Long mortonHash(final double lon, final double lat) {
- return BitUtil.interleave(scaleLon(lon), scaleLat(lat));
- }
-
- public static final double mortonUnhashLon(final long hash) {
- return unscaleLon(BitUtil.deinterleave(hash));
- }
-
- public static final double mortonUnhashLat(final long hash) {
- return unscaleLat(BitUtil.deinterleave(hash >>> 1));
- }
-
- private static final long scaleLon(final double val) {
- return (long) ((val-MIN_LON_INCL) * LON_SCALE);
- }
-
- private static final long scaleLat(final double val) {
- return (long) ((val-MIN_LAT_INCL) * LAT_SCALE);
- }
-
- private static final double unscaleLon(final long val) {
- return (val / LON_SCALE) + MIN_LON_INCL;
- }
-
- private static final double unscaleLat(final long val) {
- return (val / LAT_SCALE) + MIN_LAT_INCL;
- }
-
- /**
- * Compare two position values within a {@link org.apache.lucene.util.GeoUtils#TOLERANCE} factor
- */
- public static double compare(final double v1, final double v2) {
- final double delta = v1-v2;
- return Math.abs(delta) <= TOLERANCE ? 0 : delta;
- }
-
- /**
- * Puts longitude in range of -180 to +180.
- */
- public static double normalizeLon(double lon_deg) {
- if (lon_deg >= -180 && lon_deg <= 180) {
- return lon_deg; //common case, and avoids slight double precision shifting
- }
- double off = (lon_deg + 180) % 360;
- if (off < 0) {
- return 180 + off;
- } else if (off == 0 && lon_deg > 0) {
- return 180;
- } else {
- return -180 + off;
- }
- }
-
- /**
- * Puts latitude in range of -90 to 90.
- */
- public static double normalizeLat(double lat_deg) {
- if (lat_deg >= -90 && lat_deg <= 90) {
- return lat_deg; //common case, and avoids slight double precision shifting
- }
- double off = Math.abs((lat_deg + 90) % 360);
- return (off <= 180 ? off : 360-off) - 90;
- }
-
- public static String geoTermToString(long term) {
- StringBuilder s = new StringBuilder(64);
- final int numberOfLeadingZeros = Long.numberOfLeadingZeros(term);
- for (int i = 0; i < numberOfLeadingZeros; i++) {
- s.append('0');
- }
- if (term != 0) {
- s.append(Long.toBinaryString(term));
- }
- return s.toString();
- }
-
- /**
- * Converts a given circle (defined as a point/radius) to an approximated line-segment polygon
- *
- * @param lon longitudinal center of circle (in degrees)
- * @param lat latitudinal center of circle (in degrees)
- * @param radiusMeters distance radius of circle (in meters)
- * @return a list of lon/lat points representing the circle
- */
- @SuppressWarnings({"unchecked","rawtypes"})
- public static ArrayList<double[]> circleToPoly(final double lon, final double lat, final double radiusMeters) {
- double angle;
- // a little under-sampling (to limit the number of polygonal points): using archimedes estimation of pi
- final int sides = 25;
- ArrayList<double[]> geometry = new ArrayList();
- double[] lons = new double[sides];
- double[] lats = new double[sides];
-
- double[] pt = new double[2];
- final int sidesLen = sides-1;
- for (int i=0; i<sidesLen; ++i) {
- angle = (i*360/sides);
- pt = GeoProjectionUtils.pointFromLonLatBearingGreatCircle(lon, lat, angle, radiusMeters, pt);
- lons[i] = pt[0];
- lats[i] = pt[1];
- }
- // close the poly
- lons[sidesLen] = lons[0];
- lats[sidesLen] = lats[0];
- geometry.add(lons);
- geometry.add(lats);
-
- return geometry;
- }
-
- /**
- * Compute Bounding Box for a circle using WGS-84 parameters
- */
- public static GeoRect circleToBBox(final double centerLon, final double centerLat, final double radiusMeters) {
- final double radLat = TO_RADIANS * centerLat;
- final double radLon = TO_RADIANS * centerLon;
- double radDistance = radiusMeters / GeoProjectionUtils.SEMIMAJOR_AXIS;
- double minLat = radLat - radDistance;
- double maxLat = radLat + radDistance;
- double minLon;
- double maxLon;
-
- if (minLat > GeoProjectionUtils.MIN_LAT_RADIANS && maxLat < GeoProjectionUtils.MAX_LAT_RADIANS) {
- double deltaLon = SloppyMath.asin(SloppyMath.sin(radDistance) / SloppyMath.cos(radLat));
- minLon = radLon - deltaLon;
- if (minLon < GeoProjectionUtils.MIN_LON_RADIANS) {
- minLon += 2d * StrictMath.PI;
- }
- maxLon = radLon + deltaLon;
- if (maxLon > GeoProjectionUtils.MAX_LON_RADIANS) {
- maxLon -= 2d * StrictMath.PI;
- }
- } else {
- // a pole is within the distance
- minLat = StrictMath.max(minLat, GeoProjectionUtils.MIN_LAT_RADIANS);
- maxLat = StrictMath.min(maxLat, GeoProjectionUtils.MAX_LAT_RADIANS);
- minLon = GeoProjectionUtils.MIN_LON_RADIANS;
- maxLon = GeoProjectionUtils.MAX_LON_RADIANS;
- }
-
- return new GeoRect(TO_DEGREES * minLon, TO_DEGREES * maxLon, TO_DEGREES * minLat, TO_DEGREES * maxLat);
- }
-
- /**
- * Compute Bounding Box for a polygon using WGS-84 parameters
- */
- public static GeoRect polyToBBox(double[] polyLons, double[] polyLats) {
- if (polyLons.length != polyLats.length) {
- throw new IllegalArgumentException("polyLons and polyLats must be equal length");
- }
-
- double minLon = Double.POSITIVE_INFINITY;
- double maxLon = Double.NEGATIVE_INFINITY;
- double minLat = Double.POSITIVE_INFINITY;
- double maxLat = Double.NEGATIVE_INFINITY;
-
- for (int i=0;i<polyLats.length;i++) {
- if (GeoUtils.isValidLon(polyLons[i]) == false) {
- throw new IllegalArgumentException("invalid polyLons[" + i + "]=" + polyLons[i]);
- }
- if (GeoUtils.isValidLat(polyLats[i]) == false) {
- throw new IllegalArgumentException("invalid polyLats[" + i + "]=" + polyLats[i]);
- }
- minLon = Math.min(polyLons[i], minLon);
- maxLon = Math.max(polyLons[i], maxLon);
- minLat = Math.min(polyLats[i], minLat);
- maxLat = Math.max(polyLats[i], maxLat);
- }
- // expand bounding box by TOLERANCE factor to handle round-off error
- return new GeoRect(Math.max(minLon - TOLERANCE, MIN_LON_INCL), Math.min(maxLon + TOLERANCE, MAX_LON_INCL),
- Math.max(minLat - TOLERANCE, MIN_LAT_INCL), Math.min(maxLat + TOLERANCE, MAX_LAT_INCL));
- }
-
- /**
- * validates latitude value is within standard +/-90 coordinate bounds
- */
- public static boolean isValidLat(double lat) {
- return Double.isNaN(lat) == false && lat >= MIN_LAT_INCL && lat <= MAX_LAT_INCL;
- }
-
- /**
- * validates longitude value is within standard +/-180 coordinate bounds
- */
- public static boolean isValidLon(double lon) {
- return Double.isNaN(lon) == false && lon >= MIN_LON_INCL && lon <= MAX_LON_INCL;
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/java/org/apache/lucene/util/package.html
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/util/package.html b/lucene/sandbox/src/java/org/apache/lucene/util/package.html
deleted file mode 100644
index 9a8856c..0000000
--- a/lucene/sandbox/src/java/org/apache/lucene/util/package.html
+++ /dev/null
@@ -1,28 +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.
--->
-
-<!-- not a package-info.java, because we already defined this package in core/ -->
-
-<html>
-<head>
- <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
-</head>
-<body>
-This package contains utility APIs, currently for geospatial searching.
-</body>
-</html>
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/test/org/apache/lucene/search/TestGeoPointQuery.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/search/TestGeoPointQuery.java b/lucene/sandbox/src/test/org/apache/lucene/search/TestGeoPointQuery.java
deleted file mode 100644
index a91cbd6..0000000
--- a/lucene/sandbox/src/test/org/apache/lucene/search/TestGeoPointQuery.java
+++ /dev/null
@@ -1,386 +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.search;
-
-import org.apache.lucene.analysis.MockAnalyzer;
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.Field;
-import org.apache.lucene.document.FieldType;
-import org.apache.lucene.document.GeoPointField;
-import org.apache.lucene.document.StringField;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.RandomIndexWriter;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.util.BaseGeoPointTestCase;
-import org.apache.lucene.util.GeoRect;
-import org.apache.lucene.util.GeoRelationUtils;
-import org.apache.lucene.util.GeoUtils;
-import org.apache.lucene.util.SloppyMath;
-import org.apache.lucene.util.TestUtil;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-import static org.apache.lucene.util.GeoDistanceUtils.DISTANCE_PCT_ERR;
-
-/**
- * Unit testing for basic GeoPoint query logic
- *
- * @lucene.experimental
- */
-
-public class TestGeoPointQuery extends BaseGeoPointTestCase {
-
- private static Directory directory = null;
- private static IndexReader reader = null;
- private static IndexSearcher searcher = null;
-
- @Override
- protected boolean forceSmall() {
- return false;
- }
-
- @Override
- protected void addPointToDoc(String field, Document doc, double lat, double lon) {
- doc.add(new GeoPointField(field, lon, lat, Field.Store.NO));
- }
-
- @Override
- protected Query newRectQuery(String field, GeoRect rect) {
- return new GeoPointInBBoxQuery(field, rect.minLon, rect.minLat, rect.maxLon, rect.maxLat);
- }
-
- @Override
- protected Query newDistanceQuery(String field, double centerLat, double centerLon, double radiusMeters) {
- return new GeoPointDistanceQuery(field, centerLon, centerLat, radiusMeters);
- }
-
- @Override
- protected Query newDistanceRangeQuery(String field, double centerLat, double centerLon, double minRadiusMeters, double radiusMeters) {
- return new GeoPointDistanceRangeQuery(field, centerLon, centerLat, minRadiusMeters, radiusMeters);
- }
-
- @Override
- protected Query newPolygonQuery(String field, double[] lats, double[] lons) {
- return new GeoPointInPolygonQuery(field, lons, lats);
- }
-
- @BeforeClass
- public static void beforeClass() throws Exception {
- directory = newDirectory();
-
- RandomIndexWriter writer = new RandomIndexWriter(random(), directory,
- newIndexWriterConfig(new MockAnalyzer(random()))
- .setMaxBufferedDocs(TestUtil.nextInt(random(), 100, 1000))
- .setMergePolicy(newLogMergePolicy()));
-
- // create some simple geo points
- final FieldType storedPoint = new FieldType(GeoPointField.TYPE_STORED);
- // this is a simple systematic test
- GeoPointField[] pts = new GeoPointField[] {
- new GeoPointField(FIELD_NAME, -96.774, 32.763420, storedPoint),
- new GeoPointField(FIELD_NAME, -96.7759895324707, 32.7559529921407, storedPoint),
- new GeoPointField(FIELD_NAME, -96.77701950073242, 32.77866942010977, storedPoint),
- new GeoPointField(FIELD_NAME, -96.7706036567688, 32.7756745755423, storedPoint),
- new GeoPointField(FIELD_NAME, -139.73458170890808, 27.703618681345585, storedPoint),
- new GeoPointField(FIELD_NAME, -96.4538113027811, 32.94823588839368, storedPoint),
- new GeoPointField(FIELD_NAME, -96.65084838867188, 33.06047141970814, storedPoint),
- new GeoPointField(FIELD_NAME, -96.7772, 32.778650, storedPoint),
- new GeoPointField(FIELD_NAME, -177.23537676036358, -88.56029371730983, storedPoint),
- new GeoPointField(FIELD_NAME, -26.779373834241003, 33.541429799076354, storedPoint),
- new GeoPointField(FIELD_NAME, -77.35379276106497, 26.774024500421728, storedPoint),
- new GeoPointField(FIELD_NAME, -14.796283808944777, -90.0, storedPoint),
- new GeoPointField(FIELD_NAME, -178.8538113027811, 32.94823588839368, storedPoint),
- new GeoPointField(FIELD_NAME, 178.8538113027811, 32.94823588839368, storedPoint),
- new GeoPointField(FIELD_NAME, -73.998776, 40.720611, storedPoint),
- new GeoPointField(FIELD_NAME, -179.5, -44.5, storedPoint)};
-
- for (GeoPointField p : pts) {
- Document doc = new Document();
- doc.add(p);
- writer.addDocument(doc);
- }
-
- // add explicit multi-valued docs
- for (int i=0; i<pts.length; i+=2) {
- Document doc = new Document();
- doc.add(pts[i]);
- doc.add(pts[i+1]);
- writer.addDocument(doc);
- }
-
- // index random string documents
- for (int i=0; i<random().nextInt(10); ++i) {
- Document doc = new Document();
- doc.add(new StringField("string", Integer.toString(i), Field.Store.NO));
- writer.addDocument(doc);
- }
-
- reader = writer.getReader();
- searcher = newSearcher(reader);
- writer.close();
- }
-
- @AfterClass
- public static void afterClass() throws Exception {
- searcher = null;
- reader.close();
- reader = null;
- directory.close();
- directory = null;
- }
-
- private TopDocs bboxQuery(double minLon, double minLat, double maxLon, double maxLat, int limit) throws Exception {
- GeoPointInBBoxQuery q = new GeoPointInBBoxQuery(FIELD_NAME, minLon, minLat, maxLon, maxLat);
- return searcher.search(q, limit);
- }
-
- private TopDocs polygonQuery(double[] lon, double[] lat, int limit) throws Exception {
- GeoPointInPolygonQuery q = new GeoPointInPolygonQuery(FIELD_NAME, lon, lat);
- return searcher.search(q, limit);
- }
-
- private TopDocs geoDistanceQuery(double lon, double lat, double radius, int limit) throws Exception {
- GeoPointDistanceQuery q = new GeoPointDistanceQuery(FIELD_NAME, lon, lat, radius);
- return searcher.search(q, limit);
- }
-
- @Override
- protected Boolean rectContainsPoint(GeoRect rect, double pointLat, double pointLon) {
- if (GeoUtils.compare(pointLon, rect.minLon) == 0.0 ||
- GeoUtils.compare(pointLon, rect.maxLon) == 0.0 ||
- GeoUtils.compare(pointLat, rect.minLat) == 0.0 ||
- GeoUtils.compare(pointLat, rect.maxLat) == 0.0) {
- // Point is very close to rect boundary
- return null;
- }
-
- if (rect.minLon < rect.maxLon) {
- return GeoRelationUtils.pointInRectPrecise(pointLon, pointLat, rect.minLon, rect.minLat, rect.maxLon, rect.maxLat);
- } else {
- // Rect crosses dateline:
- return GeoRelationUtils.pointInRectPrecise(pointLon, pointLat, -180.0, rect.minLat, rect.maxLon, rect.maxLat)
- || GeoRelationUtils.pointInRectPrecise(pointLon, pointLat, rect.minLon, rect.minLat, 180.0, rect.maxLat);
- }
- }
-
- @Override
- protected Boolean polyRectContainsPoint(GeoRect rect, double pointLat, double pointLon) {
- return rectContainsPoint(rect, pointLat, pointLon);
- }
-
- @Override
- protected Boolean circleContainsPoint(double centerLat, double centerLon, double radiusMeters, double pointLat, double pointLon) {
- if (radiusQueryCanBeWrong(centerLat, centerLon, pointLon, pointLat, radiusMeters)) {
- return null;
- } else {
- return SloppyMath.haversin(centerLat, centerLon, pointLat, pointLon)*1000.0 <= radiusMeters;
- }
- }
-
- @Override
- protected Boolean distanceRangeContainsPoint(double centerLat, double centerLon, double minRadiusMeters, double radiusMeters, double pointLat, double pointLon) {
- if (radiusQueryCanBeWrong(centerLat, centerLon, pointLon, pointLat, minRadiusMeters)
- || radiusQueryCanBeWrong(centerLat, centerLon, pointLon, pointLat, radiusMeters)) {
- return null;
- } else {
- final double d = SloppyMath.haversin(centerLat, centerLon, pointLat, pointLon)*1000.0;
- return d >= minRadiusMeters && d <= radiusMeters;
- }
- }
-
- private static boolean radiusQueryCanBeWrong(double centerLat, double centerLon, double ptLon, double ptLat,
- final double radius) {
- final long hashedCntr = GeoUtils.mortonHash(centerLon, centerLat);
- centerLon = GeoUtils.mortonUnhashLon(hashedCntr);
- centerLat = GeoUtils.mortonUnhashLat(hashedCntr);
- final long hashedPt = GeoUtils.mortonHash(ptLon, ptLat);
- ptLon = GeoUtils.mortonUnhashLon(hashedPt);
- ptLat = GeoUtils.mortonUnhashLat(hashedPt);
-
- double ptDistance = SloppyMath.haversin(centerLat, centerLon, ptLat, ptLon)*1000.0;
- double delta = StrictMath.abs(ptDistance - radius);
-
- // if its within the distance error then it can be wrong
- return delta < (ptDistance*DISTANCE_PCT_ERR);
- }
-
- public void testRectCrossesCircle() throws Exception {
- assertTrue(GeoRelationUtils.rectCrossesCircle(-180, -90, 180, 0.0, 0.667, 0.0, 88000.0));
- }
-
- private TopDocs geoDistanceRangeQuery(double lon, double lat, double minRadius, double maxRadius, int limit)
- throws Exception {
- GeoPointDistanceRangeQuery q = new GeoPointDistanceRangeQuery(FIELD_NAME, lon, lat, minRadius, maxRadius);
- return searcher.search(q, limit);
- }
-
- public void testBBoxQuery() throws Exception {
- TopDocs td = bboxQuery(-96.7772, 32.778650, -96.77690000, 32.778950, 5);
- assertEquals("GeoBoundingBoxQuery failed", 4, td.totalHits);
- }
-
- public void testPolyQuery() throws Exception {
- TopDocs td = polygonQuery(new double[]{-96.7682647, -96.8280029, -96.6288757, -96.4929199,
- -96.6041564, -96.7449188, -96.76826477, -96.7682647},
- new double[]{33.073130, 32.9942669, 32.938386, 33.0374494,
- 33.1369762, 33.1162747, 33.073130, 33.073130}, 5);
- assertEquals("GeoPolygonQuery failed", 2, td.totalHits);
- }
-
- public void testPacManPolyQuery() throws Exception {
- // pacman
- double[] px = {0, 10, 10, 0, -8, -10, -8, 0, 10, 10, 0};
- double[] py = {0, 5, 9, 10, 9, 0, -9, -10, -9, -5, 0};
-
- // shape bbox
- double xMinA = -10;
- double xMaxA = 10;
- double yMinA = -10;
- double yMaxA = 10;
-
- // candidate crosses cell
- double xMin = 2;//-5;
- double xMax = 11;//0.000001;
- double yMin = -1;//0;
- double yMax = 1;//5;
-
- // test cell crossing poly
- assertTrue(GeoRelationUtils.rectCrossesPolyApprox(xMin, yMin, xMax, yMax, px, py, xMinA, yMinA, xMaxA, yMaxA));
- assertFalse(GeoRelationUtils.rectCrossesPolyApprox(-5, 0, 0.000001, 5, px, py, xMin, yMin, xMax, yMax));
- assertTrue(GeoRelationUtils.rectWithinPolyApprox(-5, 0, -2, 5, px, py, xMin, yMin, xMax, yMax));
- }
-
- public void testBBoxCrossDateline() throws Exception {
- TopDocs td = bboxQuery(179.0, -45.0, -179.0, -44.0, 20);
- assertEquals("BBoxCrossDateline query failed", 2, td.totalHits);
- }
-
- public void testWholeMap() throws Exception {
- TopDocs td = bboxQuery(GeoUtils.MIN_LON_INCL, GeoUtils.MIN_LAT_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MAX_LAT_INCL, 20);
- assertEquals("testWholeMap failed", 24, td.totalHits);
- td = polygonQuery(new double[] {GeoUtils.MIN_LON_INCL, GeoUtils.MIN_LON_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MAX_LON_INCL, GeoUtils.MIN_LON_INCL},
- new double[] {GeoUtils.MIN_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MAX_LAT_INCL, GeoUtils.MIN_LAT_INCL, GeoUtils.MIN_LAT_INCL}, 20);
- assertEquals("testWholeMap failed", 24, td.totalHits);
- }
-
- public void smallTest() throws Exception {
- TopDocs td = geoDistanceQuery(-73.998776, 40.720611, 1, 20);
- assertEquals("smallTest failed", 2, td.totalHits);
- }
-
- public void testInvalidBBox() throws Exception {
- try {
- bboxQuery(179.0, -92.0, 181.0, -91.0, 20);
- } catch(Exception e) {
- return;
- }
- throw new Exception("GeoBoundingBox should not accept invalid lat/lon");
- }
-
- public void testGeoDistanceQuery() throws Exception {
- TopDocs td = geoDistanceQuery(-96.4538113027811, 32.94823588839368, 6000, 20);
- assertEquals("GeoDistanceQuery failed", 2, td.totalHits);
- }
-
- /** see https://issues.apache.org/jira/browse/LUCENE-6905 */
- public void testNonEmptyTermsEnum() throws Exception {
- TopDocs td = geoDistanceQuery(-177.23537676036358, -88.56029371730983, 7757.999232959935, 20);
- assertEquals("GeoDistanceQuery failed", 2, td.totalHits);
- }
-
- public void testMultiValuedQuery() throws Exception {
- TopDocs td = bboxQuery(-96.4538113027811, 32.7559529921407, -96.7706036567688, 32.7756745755423, 20);
- // 3 single valued docs + 2 multi-valued docs
- assertEquals("testMultiValuedQuery failed", 5, td.totalHits);
- }
-
- public void testTooBigRadius() throws Exception {
- try {
- geoDistanceQuery(0.0, 85.0, 4000000, 20);
- } catch (IllegalArgumentException e) {
- e.getMessage().contains("exceeds maxRadius");
- }
- }
-
- /**
- * Explicitly large
- */
- public void testGeoDistanceQueryHuge() throws Exception {
- TopDocs td = geoDistanceQuery(-96.4538113027811, 32.94823588839368, 6000000, 20);
- assertEquals("GeoDistanceQuery failed", 16, td.totalHits);
- }
-
- public void testGeoDistanceQueryCrossDateline() throws Exception {
- TopDocs td = geoDistanceQuery(-179.9538113027811, 32.94823588839368, 120000, 20);
- assertEquals("GeoDistanceQuery failed", 3, td.totalHits);
- }
-
- public void testInvalidGeoDistanceQuery() throws Exception {
- try {
- geoDistanceQuery(181.0, 92.0, 120000, 20);
- } catch (Exception e) {
- return;
- }
- throw new Exception("GeoDistanceQuery should not accept invalid lat/lon as origin");
- }
-
- public void testMaxDistanceRangeQuery() throws Exception {
- TopDocs td = geoDistanceRangeQuery(0.0, 0.0, 10, 20000000, 20);
- assertEquals("GeoDistanceRangeQuery failed", 24, td.totalHits);
- }
-
- public void testMortonEncoding() throws Exception {
- long hash = GeoUtils.mortonHash(180, 90);
- assertEquals(180.0, GeoUtils.mortonUnhashLon(hash), 0);
- assertEquals(90.0, GeoUtils.mortonUnhashLat(hash), 0);
- }
-
- public void testEncodeDecode() throws Exception {
- int iters = atLeast(10000);
- boolean small = random().nextBoolean();
- for(int iter=0;iter<iters;iter++) {
- double lat = randomLat(small);
- double lon = randomLon(small);
-
- long enc = GeoUtils.mortonHash(lon, lat);
- double latEnc = GeoUtils.mortonUnhashLat(enc);
- double lonEnc = GeoUtils.mortonUnhashLon(enc);
-
- assertEquals("lat=" + lat + " latEnc=" + latEnc + " diff=" + (lat - latEnc), lat, latEnc, GeoUtils.TOLERANCE);
- assertEquals("lon=" + lon + " lonEnc=" + lonEnc + " diff=" + (lon - lonEnc), lon, lonEnc, GeoUtils.TOLERANCE);
- }
- }
-
- public void testScaleUnscaleIsStable() throws Exception {
- int iters = atLeast(1000);
- boolean small = random().nextBoolean();
- for(int iter=0;iter<iters;iter++) {
- double lat = randomLat(small);
- double lon = randomLon(small);
-
- long enc = GeoUtils.mortonHash(lon, lat);
- double latEnc = GeoUtils.mortonUnhashLat(enc);
- double lonEnc = GeoUtils.mortonUnhashLon(enc);
-
- long enc2 = GeoUtils.mortonHash(lon, lat);
- double latEnc2 = GeoUtils.mortonUnhashLat(enc2);
- double lonEnc2 = GeoUtils.mortonUnhashLon(enc2);
- assertEquals(latEnc, latEnc2, 0.0);
- assertEquals(lonEnc, lonEnc2, 0.0);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/665041c5/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java b/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
index e898f1c..5ae9503 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java
@@ -18,9 +18,9 @@ package org.apache.lucene.search;
import org.apache.lucene.document.LatLonPoint;
import org.apache.lucene.document.Document;
-import org.apache.lucene.util.BaseGeoPointTestCase;
-import org.apache.lucene.util.GeoDistanceUtils;
-import org.apache.lucene.util.GeoRect;
+import org.apache.lucene.spatial.util.BaseGeoPointTestCase;
+import org.apache.lucene.spatial.util.GeoDistanceUtils;
+import org.apache.lucene.spatial.util.GeoRect;
public class TestLatLonPointQueries extends BaseGeoPointTestCase {
// todo deconflict GeoPoint and BKD encoding methods and error tolerance