You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by no...@apache.org on 2016/04/07 22:29:59 UTC
[27/50] [abbrv] lucene-solr:apiv2: LUCENE-7163: refactor GeoRect,
Polygon, and GeoUtils tests to geo package in core.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/6c219e99/lucene/spatial/src/test/org/apache/lucene/spatial/util/GeoTestUtil.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/util/GeoTestUtil.java b/lucene/spatial/src/test/org/apache/lucene/spatial/util/GeoTestUtil.java
deleted file mode 100644
index 1e4fcf3..0000000
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/util/GeoTestUtil.java
+++ /dev/null
@@ -1,500 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.lucene.spatial.util;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-import org.apache.lucene.geo.GeoUtils;
-import org.apache.lucene.util.SloppyMath;
-
-import com.carrotsearch.randomizedtesting.RandomizedContext;
-
-/** static methods for testing geo */
-public class GeoTestUtil {
-
- /** returns next pseudorandom latitude (anywhere) */
- public static double nextLatitude() {
- return -90 + 180.0 * random().nextDouble();
- }
-
- /** returns next pseudorandom longitude (anywhere) */
- public static double nextLongitude() {
- return -180 + 360.0 * random().nextDouble();
- }
-
- /** returns next pseudorandom latitude, kinda close to {@code otherLatitude} */
- public static double nextLatitudeNear(double otherLatitude) {
- GeoUtils.checkLatitude(otherLatitude);
- return normalizeLatitude(otherLatitude + random().nextDouble() - 0.5);
- }
-
- /** returns next pseudorandom longitude, kinda close to {@code otherLongitude} */
- public static double nextLongitudeNear(double otherLongitude) {
- GeoUtils.checkLongitude(otherLongitude);
- return normalizeLongitude(otherLongitude + random().nextDouble() - 0.5);
- }
-
- /**
- * returns next pseudorandom latitude, kinda close to {@code minLatitude/maxLatitude}
- * <b>NOTE:</b>minLatitude/maxLatitude are merely guidelines. the returned value is sometimes
- * outside of that range! this is to facilitate edge testing.
- */
- public static double nextLatitudeAround(double minLatitude, double maxLatitude) {
- GeoUtils.checkLatitude(minLatitude);
- GeoUtils.checkLatitude(maxLatitude);
- return normalizeLatitude(randomRangeMaybeSlightlyOutside(minLatitude, maxLatitude));
- }
-
- /**
- * returns next pseudorandom longitude, kinda close to {@code minLongitude/maxLongitude}
- * <b>NOTE:</b>minLongitude/maxLongitude are merely guidelines. the returned value is sometimes
- * outside of that range! this is to facilitate edge testing.
- */
- public static double nextLongitudeAround(double minLongitude, double maxLongitude) {
- GeoUtils.checkLongitude(minLongitude);
- GeoUtils.checkLongitude(maxLongitude);
- return normalizeLongitude(randomRangeMaybeSlightlyOutside(minLongitude, maxLongitude));
- }
-
- /** returns next pseudorandom box: can cross the 180th meridian */
- public static GeoRect nextBox() {
- return nextBoxInternal(nextLatitude(), nextLatitude(), nextLongitude(), nextLongitude(), true);
- }
-
- /** returns next pseudorandom box, can cross the 180th meridian, kinda close to {@code otherLatitude} and {@code otherLongitude} */
- public static GeoRect nextBoxNear(double otherLatitude, double otherLongitude) {
- GeoUtils.checkLongitude(otherLongitude);
- GeoUtils.checkLongitude(otherLongitude);
- return nextBoxInternal(nextLatitudeNear(otherLatitude), nextLatitudeNear(otherLatitude),
- nextLongitudeNear(otherLongitude), nextLongitudeNear(otherLongitude), true);
- }
-
- /** returns next pseudorandom polygon */
- public static Polygon nextPolygon() {
- if (random().nextBoolean()) {
- return surpriseMePolygon(null, null);
- }
-
- GeoRect box = nextBoxInternal(nextLatitude(), nextLatitude(), nextLongitude(), nextLongitude(), false);
- if (random().nextBoolean()) {
- // box
- return boxPolygon(box);
- } else {
- // triangle
- return trianglePolygon(box);
- }
- }
-
- /** returns next pseudorandom polygon, kinda close to {@code otherLatitude} and {@code otherLongitude} */
- public static Polygon nextPolygonNear(double otherLatitude, double otherLongitude) {
- if (random().nextBoolean()) {
- return surpriseMePolygon(otherLatitude, otherLongitude);
- }
-
- GeoRect box = nextBoxInternal(nextLatitudeNear(otherLatitude), nextLatitudeNear(otherLatitude),
- nextLongitudeNear(otherLongitude), nextLongitudeNear(otherLongitude), false);
- if (random().nextBoolean()) {
- // box
- return boxPolygon(box);
- } else {
- // triangle
- return trianglePolygon(box);
- }
- }
-
- private static GeoRect nextBoxInternal(double lat0, double lat1, double lon0, double lon1, boolean canCrossDateLine) {
- 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(lat0, lat1, lon0, lon1);
- }
-
- private static Polygon boxPolygon(GeoRect box) {
- assert box.crossesDateline() == false;
- final double[] polyLats = new double[5];
- final double[] polyLons = new double[5];
- polyLats[0] = box.minLat;
- polyLons[0] = box.minLon;
- polyLats[1] = box.maxLat;
- polyLons[1] = box.minLon;
- polyLats[2] = box.maxLat;
- polyLons[2] = box.maxLon;
- polyLats[3] = box.minLat;
- polyLons[3] = box.maxLon;
- polyLats[4] = box.minLat;
- polyLons[4] = box.minLon;
- return new Polygon(polyLats, polyLons);
- }
-
- private static Polygon trianglePolygon(GeoRect box) {
- assert box.crossesDateline() == false;
- final double[] polyLats = new double[4];
- final double[] polyLons = new double[4];
- polyLats[0] = box.minLat;
- polyLons[0] = box.minLon;
- polyLats[1] = box.maxLat;
- polyLons[1] = box.minLon;
- polyLats[2] = box.maxLat;
- polyLons[2] = box.maxLon;
- polyLats[3] = box.minLat;
- polyLons[3] = box.minLon;
- return new Polygon(polyLats, polyLons);
- }
-
- private static Polygon surpriseMePolygon(Double otherLatitude, Double otherLongitude) {
- // repeat until we get a poly that doesn't cross dateline:
- newPoly:
- while (true) {
- //System.out.println("\nPOLY ITER");
- final double centerLat;
- final double centerLon;
- if (otherLatitude == null) {
- centerLat = nextLatitude();
- centerLon = nextLongitude();
- } else {
- GeoUtils.checkLatitude(otherLatitude);
- GeoUtils.checkLongitude(otherLongitude);
- centerLat = nextLatitudeNear(otherLatitude);
- centerLon = nextLongitudeNear(otherLongitude);
- }
-
- double radius = 0.1 + 20 * random().nextDouble();
- double radiusDelta = random().nextDouble();
-
- ArrayList<Double> lats = new ArrayList<>();
- ArrayList<Double> lons = new ArrayList<>();
- double angle = 0.0;
- while (true) {
- angle += random().nextDouble()*40.0;
- //System.out.println(" angle " + angle);
- if (angle > 360) {
- break;
- }
- double len = radius * (1.0 - radiusDelta + radiusDelta * random().nextDouble());
- //System.out.println(" len=" + len);
- double lat = centerLat + len * Math.cos(Math.toRadians(angle));
- double lon = centerLon + len * Math.sin(Math.toRadians(angle));
- if (lon <= GeoUtils.MIN_LON_INCL || lon >= GeoUtils.MAX_LON_INCL) {
- // cannot cross dateline: try again!
- continue newPoly;
- }
- if (lat > 90) {
- // cross the north pole
- lat = 180 - lat;
- lon = 180 - lon;
- } else if (lat < -90) {
- // cross the south pole
- lat = -180 - lat;
- lon = 180 - lon;
- }
- if (lon <= GeoUtils.MIN_LON_INCL || lon >= GeoUtils.MAX_LON_INCL) {
- // cannot cross dateline: try again!
- continue newPoly;
- }
- lats.add(lat);
- lons.add(lon);
-
- //System.out.println(" lat=" + lats.get(lats.size()-1) + " lon=" + lons.get(lons.size()-1));
- }
-
- // close it
- lats.add(lats.get(0));
- lons.add(lons.get(0));
-
- double[] latsArray = new double[lats.size()];
- double[] lonsArray = new double[lons.size()];
- for(int i=0;i<lats.size();i++) {
- latsArray[i] = lats.get(i);
- lonsArray[i] = lons.get(i);
- }
- return new Polygon(latsArray, lonsArray);
- }
- }
-
- /** Returns random double min to max or up to 1% outside of that range */
- private static double randomRangeMaybeSlightlyOutside(double min, double max) {
- return min + (random().nextDouble() + (0.5 - random().nextDouble()) * .02) * (max - min);
- }
-
- /** Puts latitude in range of -90 to 90. */
- private static double normalizeLatitude(double latitude) {
- if (latitude >= -90 && latitude <= 90) {
- return latitude; //common case, and avoids slight double precision shifting
- }
- double off = Math.abs((latitude + 90) % 360);
- return (off <= 180 ? off : 360-off) - 90;
- }
-
- /** Puts longitude in range of -180 to +180. */
- private static double normalizeLongitude(double longitude) {
- if (longitude >= -180 && longitude <= 180) {
- return longitude; //common case, and avoids slight double precision shifting
- }
- double off = (longitude + 180) % 360;
- if (off < 0) {
- return 180 + off;
- } else if (off == 0 && longitude > 0) {
- return 180;
- } else {
- return -180 + off;
- }
- }
-
- /** Keep it simple, we don't need to take arbitrary Random for geo tests */
- private static Random random() {
- return RandomizedContext.current().getRandom();
- }
-
- // craziness for plotting stuff :)
-
- private static double wrapLat(double lat) {
- //System.out.println("wrapLat " + lat);
- if (lat > 90) {
- //System.out.println(" " + (180 - lat));
- return 180 - lat;
- } else if (lat < -90) {
- //System.out.println(" " + (-180 - lat));
- return -180 - lat;
- } else {
- //System.out.println(" " + lat);
- return lat;
- }
- }
-
- private static double wrapLon(double lon) {
- //System.out.println("wrapLon " + lon);
- if (lon > 180) {
- //System.out.println(" " + (lon - 360));
- return lon - 360;
- } else if (lon < -180) {
- //System.out.println(" " + (lon + 360));
- return lon + 360;
- } else {
- //System.out.println(" " + lon);
- return lon;
- }
- }
-
- private static void drawRectApproximatelyOnEarthSurface(String name, String color, double minLat, double maxLat, double minLon, double maxLon) {
- int steps = 20;
- System.out.println(" var " + name + " = WE.polygon([");
- System.out.println(" // min -> max lat, min lon");
- for(int i=0;i<steps;i++) {
- System.out.println(" [" + (minLat + (maxLat - minLat) * i / steps) + ", " + minLon + "],");
- }
- System.out.println(" // max lat, min -> max lon");
- for(int i=0;i<steps;i++) {
- System.out.println(" [" + (maxLat + ", " + (minLon + (maxLon - minLon) * i / steps)) + "],");
- }
- System.out.println(" // max -> min lat, max lon");
- for(int i=0;i<steps;i++) {
- System.out.println(" [" + (minLat + (maxLat - minLat) * (steps-i) / steps) + ", " + maxLon + "],");
- }
- System.out.println(" // min lat, max -> min lon");
- for(int i=0;i<steps;i++) {
- System.out.println(" [" + minLat + ", " + (minLon + (maxLon - minLon) * (steps-i) / steps) + "],");
- }
- System.out.println(" // min lat, min lon");
- System.out.println(" [" + minLat + ", " + minLon + "]");
- System.out.println(" ], {color: \"" + color + "\", fillColor: \"" + color + "\"});");
- System.out.println(" " + name + ".addTo(earth);");
- }
-
- private static void plotLatApproximatelyOnEarthSurface(String name, String color, double lat, double minLon, double maxLon) {
- System.out.println(" var " + name + " = WE.polygon([");
- double lon;
- for(lon = minLon;lon<=maxLon;lon += (maxLon-minLon)/36) {
- System.out.println(" [" + lat + ", " + lon + "],");
- }
- System.out.println(" [" + lat + ", " + maxLon + "],");
- lon -= (maxLon-minLon)/36;
- for(;lon>=minLon;lon -= (maxLon-minLon)/36) {
- System.out.println(" [" + lat + ", " + lon + "],");
- }
- System.out.println(" ], {color: \"" + color + "\", fillColor: \"#ffffff\", opacity: " + (color.equals("#ffffff") ? "0.3" : "1") + ", fillOpacity: 0.0001});");
- System.out.println(" " + name + ".addTo(earth);");
- }
-
- private static void plotLonApproximatelyOnEarthSurface(String name, String color, double lon, double minLat, double maxLat) {
- System.out.println(" var " + name + " = WE.polygon([");
- double lat;
- for(lat = minLat;lat<=maxLat;lat += (maxLat-minLat)/36) {
- System.out.println(" [" + lat + ", " + lon + "],");
- }
- System.out.println(" [" + maxLat + ", " + lon + "],");
- lat -= (maxLat-minLat)/36;
- for(;lat>=minLat;lat -= (maxLat-minLat)/36) {
- System.out.println(" [" + lat + ", " + lon + "],");
- }
- System.out.println(" ], {color: \"" + color + "\", fillColor: \"#ffffff\", opacity: " + (color.equals("#ffffff") ? "0.3" : "1") + ", fillOpacity: 0.0001});");
- System.out.println(" " + name + ".addTo(earth);");
- }
-
- // http://www.webglearth.org has API details:
- public static void polysToWebGLEarth(List<double[][]> polys) {
- System.out.println("<!DOCTYPE HTML>");
- System.out.println("<html>");
- System.out.println(" <head>");
- System.out.println(" <script src=\"http://www.webglearth.com/v2/api.js\"></script>");
- System.out.println(" <script>");
- System.out.println(" function initialize() {");
- System.out.println(" var earth = new WE.map('earth_div');");
-
- int count = 0;
- for (double[][] poly : polys) {
- System.out.println(" var poly" + count + " = WE.polygon([");
- for(int i=0;i<poly[0].length;i++) {
- double lat = poly[0][i];
- double lon = poly[1][i];
- System.out.println(" [" + lat + ", " + lon + "],");
- }
- System.out.println(" ], {color: '#00ff00'});");
- System.out.println(" poly" + count + ".addTo(earth);");
- }
-
- System.out.println(" WE.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{");
- System.out.println(" attribution: '© OpenStreetMap contributors'");
- System.out.println(" }).addTo(earth);");
- System.out.println(" }");
- System.out.println(" </script>");
- System.out.println(" <style>");
- System.out.println(" html, body{padding: 0; margin: 0;}");
- System.out.println(" #earth_div{top: 0; right: 0; bottom: 0; left: 0; position: absolute !important;}");
- System.out.println(" </style>");
- System.out.println(" <title>WebGL Earth API: Hello World</title>");
- System.out.println(" </head>");
- System.out.println(" <body onload=\"initialize()\">");
- System.out.println(" <div id=\"earth_div\"></div>");
- System.out.println(" </body>");
- System.out.println("</html>");
- }
-
- // http://www.webglearth.org has API details:
- public static void toWebGLEarth(double rectMinLatitude, double rectMaxLatitude,
- double rectMinLongitude, double rectMaxLongitude,
- double centerLatitude, double centerLongitude,
- double radiusMeters) {
- GeoRect box = GeoRect.fromPointDistance(centerLatitude, centerLongitude, radiusMeters);
- System.out.println("<!DOCTYPE HTML>");
- System.out.println("<html>");
- System.out.println(" <head>");
- System.out.println(" <script src=\"http://www.webglearth.com/v2/api.js\"></script>");
- System.out.println(" <script>");
- System.out.println(" function initialize() {");
- System.out.println(" var earth = new WE.map('earth_div', {center: [" + centerLatitude + ", " + centerLongitude + "]});");
- System.out.println(" var marker = WE.marker([" + centerLatitude + ", " + centerLongitude + "]).addTo(earth);");
- drawRectApproximatelyOnEarthSurface("cell", "#ff0000", rectMinLatitude, rectMaxLatitude, rectMinLongitude, rectMaxLongitude);
- System.out.println(" var polygonB = WE.polygon([");
- StringBuilder b = new StringBuilder();
- inverseHaversin(b, centerLatitude, centerLongitude, radiusMeters);
- System.out.println(b);
- System.out.println(" ], {color: '#00ff00'});");
- System.out.println(" polygonB.addTo(earth);");
- drawRectApproximatelyOnEarthSurface("bbox", "#00ff00", box.minLat, box.maxLat, box.minLon, box.maxLon);
- System.out.println(" WE.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{");
- System.out.println(" attribution: '© OpenStreetMap contributors'");
- System.out.println(" }).addTo(earth);");
- plotLatApproximatelyOnEarthSurface("lat0", "#ffffff", 4.68, 0.0, 360.0);
- plotLatApproximatelyOnEarthSurface("lat1", "#ffffff", 180-93.09, 0.0, 360.0);
- plotLatApproximatelyOnEarthSurface("axisLat", "#00ff00", GeoRect.axisLat(centerLatitude, radiusMeters), box.minLon, box.maxLon);
- plotLonApproximatelyOnEarthSurface("axisLon", "#00ff00", centerLongitude, box.minLat, box.maxLat);
- System.out.println(" }");
- System.out.println(" </script>");
- System.out.println(" <style>");
- System.out.println(" html, body{padding: 0; margin: 0;}");
- System.out.println(" #earth_div{top: 0; right: 0; bottom: 0; left: 0; position: absolute !important;}");
- System.out.println(" </style>");
- System.out.println(" <title>WebGL Earth API: Hello World</title>");
- System.out.println(" </head>");
- System.out.println(" <body onload=\"initialize()\">");
- System.out.println(" <div id=\"earth_div\"></div>");
- System.out.println(" </body>");
- System.out.println("</html>");
- }
-
- private static void inverseHaversin(StringBuilder b, double centerLat, double centerLon, double radiusMeters) {
- double angle = 0;
- int steps = 100;
-
- newAngle:
- while (angle < 360) {
- double x = Math.cos(Math.toRadians(angle));
- double y = Math.sin(Math.toRadians(angle));
- double factor = 2.0;
- double step = 1.0;
- int last = 0;
- double lastDistanceMeters = 0.0;
- //System.out.println("angle " + angle + " slope=" + slope);
- while (true) {
- double lat = wrapLat(centerLat + y * factor);
- double lon = wrapLon(centerLon + x * factor);
- double distanceMeters = SloppyMath.haversinMeters(centerLat, centerLon, lat, lon);
-
- if (last == 1 && distanceMeters < lastDistanceMeters) {
- // For large enough circles, some angles are not possible:
- //System.out.println(" done: give up on angle " + angle);
- angle += 360./steps;
- continue newAngle;
- }
- if (last == -1 && distanceMeters > lastDistanceMeters) {
- // For large enough circles, some angles are not possible:
- //System.out.println(" done: give up on angle " + angle);
- angle += 360./steps;
- continue newAngle;
- }
- lastDistanceMeters = distanceMeters;
-
- //System.out.println(" iter lat=" + lat + " lon=" + lon + " distance=" + distanceMeters + " vs " + radiusMeters);
- if (Math.abs(distanceMeters - radiusMeters) < 0.1) {
- b.append(" [" + lat + ", " + lon + "],\n");
- break;
- }
- if (distanceMeters > radiusMeters) {
- // too big
- //System.out.println(" smaller");
- factor -= step;
- if (last == 1) {
- //System.out.println(" half-step");
- step /= 2.0;
- }
- last = -1;
- } else if (distanceMeters < radiusMeters) {
- // too small
- //System.out.println(" bigger");
- factor += step;
- if (last == -1) {
- //System.out.println(" half-step");
- step /= 2.0;
- }
- last = 1;
- }
- }
- angle += 360./steps;
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/6c219e99/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoEncodingUtils.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoEncodingUtils.java b/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoEncodingUtils.java
new file mode 100644
index 0000000..1a1b2cc
--- /dev/null
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoEncodingUtils.java
@@ -0,0 +1,101 @@
+package org.apache.lucene.spatial.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.util.BytesRefBuilder;
+import org.apache.lucene.util.LuceneTestCase;
+
+import static org.apache.lucene.geo.GeoTestUtil.nextLatitude;
+import static org.apache.lucene.geo.GeoTestUtil.nextLongitude;
+
+/**
+ * Tests methods in {@link GeoEncodingUtils}
+ */
+public class TestGeoEncodingUtils extends LuceneTestCase {
+ /**
+ * Tests stability of {@link GeoEncodingUtils#geoCodedToPrefixCoded}
+ */
+ public void testGeoPrefixCoding() throws Exception {
+ int numIters = atLeast(1000);
+ long hash;
+ long decodedHash;
+ BytesRefBuilder brb = new BytesRefBuilder();
+ while (numIters-- >= 0) {
+ hash = GeoEncodingUtils.mortonHash(nextLatitude(), nextLongitude());
+ for (int i=32; i<64; ++i) {
+ GeoEncodingUtils.geoCodedToPrefixCoded(hash, i, brb);
+ decodedHash = GeoEncodingUtils.prefixCodedToGeoCoded(brb.get());
+ assertEquals((hash >>> i) << i, decodedHash);
+ }
+ }
+ }
+
+ public void testMortonEncoding() throws Exception {
+ long hash = GeoEncodingUtils.mortonHash(90, 180);
+ assertEquals(180.0, GeoEncodingUtils.mortonUnhashLon(hash), 0);
+ assertEquals(90.0, GeoEncodingUtils.mortonUnhashLat(hash), 0);
+ }
+
+ public void testEncodeDecode() throws Exception {
+ int iters = atLeast(10000);
+ for(int iter=0;iter<iters;iter++) {
+ double lat = nextLatitude();
+ double lon = nextLongitude();
+
+ long enc = GeoEncodingUtils.mortonHash(lat, lon);
+ double latEnc = GeoEncodingUtils.mortonUnhashLat(enc);
+ double lonEnc = GeoEncodingUtils.mortonUnhashLon(enc);
+
+ assertEquals("lat=" + lat + " latEnc=" + latEnc + " diff=" + (lat - latEnc), lat, latEnc, GeoEncodingUtils.TOLERANCE);
+ assertEquals("lon=" + lon + " lonEnc=" + lonEnc + " diff=" + (lon - lonEnc), lon, lonEnc, GeoEncodingUtils.TOLERANCE);
+ }
+ }
+
+ /** make sure values always go down: this is important for edge case consistency */
+ public void testEncodeDecodeRoundsDown() throws Exception {
+ int iters = atLeast(1000);
+ for(int iter=0;iter<iters;iter++) {
+ double lat = -90 + 180.0 * random().nextDouble();
+ double lon = -180 + 360.0 * random().nextDouble();
+
+ long enc = GeoEncodingUtils.mortonHash(lat, lon);
+ double latEnc = GeoEncodingUtils.mortonUnhashLat(enc);
+ double lonEnc = GeoEncodingUtils.mortonUnhashLon(enc);
+ assertTrue(latEnc <= lat);
+ assertTrue(lonEnc <= lon);
+ }
+ }
+
+ public void testScaleUnscaleIsStable() throws Exception {
+ int iters = atLeast(1000);
+ for(int iter=0;iter<iters;iter++) {
+ double lat = nextLatitude();
+ double lon = nextLongitude();
+
+ long enc = GeoEncodingUtils.mortonHash(lat, lon);
+ double latEnc = GeoEncodingUtils.mortonUnhashLat(enc);
+ double lonEnc = GeoEncodingUtils.mortonUnhashLon(enc);
+
+ long enc2 = GeoEncodingUtils.mortonHash(lat, lon);
+ double latEnc2 = GeoEncodingUtils.mortonUnhashLat(enc2);
+ double lonEnc2 = GeoEncodingUtils.mortonUnhashLon(enc2);
+ assertEquals(latEnc, latEnc2, 0.0);
+ assertEquals(lonEnc, lonEnc2, 0.0);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/6c219e99/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
deleted file mode 100644
index 28b885a..0000000
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.lucene.spatial.util;
-
-import java.util.Locale;
-
-import org.apache.lucene.geo.GeoUtils;
-import org.apache.lucene.util.BytesRefBuilder;
-import org.apache.lucene.util.LuceneTestCase;
-import org.apache.lucene.util.SloppyMath;
-import org.junit.BeforeClass;
-
-/**
- * Tests class for methods in GeoUtils
- *
- * @lucene.experimental
- */
-public class TestGeoUtils extends LuceneTestCase {
-
- // 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;
-
- @BeforeClass
- public static void beforeClass() throws Exception {
- originLon = GeoTestUtil.nextLongitude();
- originLat = GeoTestUtil.nextLatitude();
- }
-
- public double randomLat(boolean small) {
- double result;
- if (small) {
- result = GeoTestUtil.nextLatitudeNear(originLat);
- } else {
- result = GeoTestUtil.nextLatitude();
- }
- return result;
- }
-
- public double randomLon(boolean small) {
- double result;
- if (small) {
- result = GeoTestUtil.nextLongitudeNear(originLon);
- } else {
- result = GeoTestUtil.nextLongitude();
- }
- return result;
- }
-
- /**
- * Tests stability of {@link GeoEncodingUtils#geoCodedToPrefixCoded}
- */
- public void testGeoPrefixCoding() throws Exception {
- int numIters = atLeast(1000);
- long hash;
- long decodedHash;
- BytesRefBuilder brb = new BytesRefBuilder();
- while (numIters-- >= 0) {
- hash = GeoEncodingUtils.mortonHash(randomLat(false), randomLon(false));
- for (int i=32; i<64; ++i) {
- GeoEncodingUtils.geoCodedToPrefixCoded(hash, i, brb);
- decodedHash = GeoEncodingUtils.prefixCodedToGeoCoded(brb.get());
- assertEquals((hash >>> i) << i, decodedHash);
- }
- }
- }
-
- public void testMortonEncoding() throws Exception {
- long hash = GeoEncodingUtils.mortonHash(90, 180);
- assertEquals(180.0, GeoEncodingUtils.mortonUnhashLon(hash), 0);
- assertEquals(90.0, GeoEncodingUtils.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 = GeoEncodingUtils.mortonHash(lat, lon);
- double latEnc = GeoEncodingUtils.mortonUnhashLat(enc);
- double lonEnc = GeoEncodingUtils.mortonUnhashLon(enc);
-
- assertEquals("lat=" + lat + " latEnc=" + latEnc + " diff=" + (lat - latEnc), lat, latEnc, GeoEncodingUtils.TOLERANCE);
- assertEquals("lon=" + lon + " lonEnc=" + lonEnc + " diff=" + (lon - lonEnc), lon, lonEnc, GeoEncodingUtils.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 = GeoEncodingUtils.mortonHash(lat, lon);
- double latEnc = GeoEncodingUtils.mortonUnhashLat(enc);
- double lonEnc = GeoEncodingUtils.mortonUnhashLon(enc);
-
- long enc2 = GeoEncodingUtils.mortonHash(lat, lon);
- double latEnc2 = GeoEncodingUtils.mortonUnhashLat(enc2);
- double lonEnc2 = GeoEncodingUtils.mortonUnhashLon(enc2);
- assertEquals(latEnc, latEnc2, 0.0);
- assertEquals(lonEnc, lonEnc2, 0.0);
- }
- }
-
- // We rely heavily on GeoUtils.circleToBBox so we test it here:
- public void testRandomCircleToBBox() throws Exception {
- int iters = atLeast(1000);
- for(int iter=0;iter<iters;iter++) {
-
- boolean useSmallRanges = random().nextBoolean();
-
- double radiusMeters;
-
- double centerLat = randomLat(useSmallRanges);
- double centerLon = randomLon(useSmallRanges);
-
- if (useSmallRanges) {
- // Approx 4 degrees lon at the equator:
- radiusMeters = random().nextDouble() * 444000;
- } else {
- radiusMeters = random().nextDouble() * 50000000;
- }
-
- // TODO: randomly quantize radius too, to provoke exact math errors?
-
- GeoRect bbox = GeoRect.fromPointDistance(centerLat, centerLon, radiusMeters);
-
- int numPointsToTry = 1000;
- for(int i=0;i<numPointsToTry;i++) {
-
- double lat;
- double lon;
-
- if (random().nextBoolean()) {
- lat = randomLat(useSmallRanges);
- lon = randomLon(useSmallRanges);
- } else {
- // pick a lat/lon within the bbox or "slightly" outside it to try to improve test efficiency
- lat = GeoTestUtil.nextLatitudeAround(bbox.minLat, bbox.maxLat);
- if (bbox.crossesDateline()) {
- if (random().nextBoolean()) {
- lon = GeoTestUtil.nextLongitudeAround(bbox.maxLon, -180);
- } else {
- lon = GeoTestUtil.nextLongitudeAround(0, bbox.minLon);
- }
- } else {
- lon = GeoTestUtil.nextLongitudeAround(bbox.minLon, bbox.maxLon);
- }
- }
-
- double distanceMeters = SloppyMath.haversinMeters(centerLat, centerLon, lat, lon);
-
- // Haversin says it's within the circle:
- boolean haversinSays = distanceMeters <= radiusMeters;
-
- // BBox says its within the box:
- boolean bboxSays;
- if (bbox.crossesDateline()) {
- if (lat >= bbox.minLat && lat <= bbox.maxLat) {
- bboxSays = lon <= bbox.maxLon || lon >= bbox.minLon;
- } else {
- bboxSays = false;
- }
- } else {
- bboxSays = lat >= bbox.minLat && lat <= bbox.maxLat && lon >= bbox.minLon && lon <= bbox.maxLon;
- }
-
- if (haversinSays) {
- if (bboxSays == false) {
- System.out.println("small=" + useSmallRanges + " centerLat=" + centerLat + " cetnerLon=" + centerLon + " radiusMeters=" + radiusMeters);
- System.out.println(" bbox: lat=" + bbox.minLat + " to " + bbox.maxLat + " lon=" + bbox.minLon + " to " + bbox.maxLon);
- System.out.println(" point: lat=" + lat + " lon=" + lon);
- System.out.println(" haversin: " + distanceMeters);
- fail("point was within the distance according to haversin, but the bbox doesn't contain it");
- }
- } else {
- // it's fine if haversin said it was outside the radius and bbox said it was inside the box
- }
- }
- }
- }
-
- // similar to testRandomCircleToBBox, but different, less evil, maybe simpler
- public void testBoundingBoxOpto() {
- for (int i = 0; i < 1000; i++) {
- double lat = GeoTestUtil.nextLatitude();
- double lon = GeoTestUtil.nextLongitude();
- double radius = 50000000 * random().nextDouble();
- GeoRect box = GeoRect.fromPointDistance(lat, lon, radius);
- final GeoRect box1;
- final GeoRect box2;
- if (box.crossesDateline()) {
- box1 = new GeoRect(box.minLat, box.maxLat, -180, box.maxLon);
- box2 = new GeoRect(box.minLat, box.maxLat, box.minLon, 180);
- } else {
- box1 = box;
- box2 = null;
- }
-
- for (int j = 0; j < 10000; j++) {
- double lat2 = GeoTestUtil.nextLatitude();
- double lon2 = GeoTestUtil.nextLongitude();
- // if the point is within radius, then it should be in our bounding box
- if (SloppyMath.haversinMeters(lat, lon, lat2, lon2) <= radius) {
- assertTrue(lat >= box.minLat && lat <= box.maxLat);
- assertTrue(lon >= box1.minLon && lon <= box1.maxLon || (box2 != null && lon >= box2.minLon && lon <= box2.maxLon));
- }
- }
- }
- }
-
- // test we can use haversinSortKey() for distance queries.
- public void testHaversinOpto() {
- for (int i = 0; i < 1000; i++) {
- double lat = GeoTestUtil.nextLatitude();
- double lon = GeoTestUtil.nextLongitude();
- double radius = 50000000 * random().nextDouble();
- GeoRect box = GeoRect.fromPointDistance(lat, lon, radius);
-
- if (box.maxLon - lon < 90 && lon - box.minLon < 90) {
- double minPartialDistance = Math.max(SloppyMath.haversinSortKey(lat, lon, lat, box.maxLon),
- SloppyMath.haversinSortKey(lat, lon, box.maxLat, lon));
-
- for (int j = 0; j < 10000; j++) {
- double lat2 = GeoTestUtil.nextLatitude();
- double lon2 = GeoTestUtil.nextLongitude();
- // if the point is within radius, then it should be <= our sort key
- if (SloppyMath.haversinMeters(lat, lon, lat2, lon2) <= radius) {
- assertTrue(SloppyMath.haversinSortKey(lat, lon, lat2, lon2) <= minPartialDistance);
- }
- }
- }
- }
- }
-
- /** Test infinite radius covers whole earth */
- public void testInfiniteRect() {
- for (int i = 0; i < 1000; i++) {
- double centerLat = GeoTestUtil.nextLatitude();
- double centerLon = GeoTestUtil.nextLongitude();
- GeoRect rect = GeoRect.fromPointDistance(centerLat, centerLon, Double.POSITIVE_INFINITY);
- assertEquals(-180.0, rect.minLon, 0.0D);
- assertEquals(180.0, rect.maxLon, 0.0D);
- assertEquals(-90.0, rect.minLat, 0.0D);
- assertEquals(90.0, rect.maxLat, 0.0D);
- assertFalse(rect.crossesDateline());
- }
- }
-
- public void testAxisLat() {
- double earthCircumference = 2D * Math.PI * GeoUtils.EARTH_MEAN_RADIUS_METERS;
- assertEquals(90, GeoRect.axisLat(0, earthCircumference / 4), 0.0D);
-
- for (int i = 0; i < 100; ++i) {
- boolean reallyBig = random().nextInt(10) == 0;
- final double maxRadius = reallyBig ? 1.1 * earthCircumference : earthCircumference / 8;
- final double radius = maxRadius * random().nextDouble();
- double prevAxisLat = GeoRect.axisLat(0.0D, radius);
- for (double lat = 0.1D; lat < 90D; lat += 0.1D) {
- double nextAxisLat = GeoRect.axisLat(lat, radius);
- GeoRect bbox = GeoRect.fromPointDistance(lat, 180D, radius);
- double dist = SloppyMath.haversinMeters(lat, 180D, nextAxisLat, bbox.maxLon);
- if (nextAxisLat < GeoUtils.MAX_LAT_INCL) {
- assertEquals("lat = " + lat, dist, radius, 0.1D);
- }
- assertTrue("lat = " + lat, prevAxisLat <= nextAxisLat);
- prevAxisLat = nextAxisLat;
- }
-
- prevAxisLat = GeoRect.axisLat(-0.0D, radius);
- for (double lat = -0.1D; lat > -90D; lat -= 0.1D) {
- double nextAxisLat = GeoRect.axisLat(lat, radius);
- GeoRect bbox = GeoRect.fromPointDistance(lat, 180D, radius);
- double dist = SloppyMath.haversinMeters(lat, 180D, nextAxisLat, bbox.maxLon);
- if (nextAxisLat > GeoUtils.MIN_LAT_INCL) {
- assertEquals("lat = " + lat, dist, radius, 0.1D);
- }
- assertTrue("lat = " + lat, prevAxisLat >= nextAxisLat);
- prevAxisLat = nextAxisLat;
- }
- }
- }
-
- // TODO: does not really belong here, but we test it like this for now
- // we can make a fake IndexReader to send boxes directly to Point visitors instead?
- public void testCircleOpto() throws Exception {
- for (int i = 0; i < 50; i++) {
- // circle
- final double centerLat = -90 + 180.0 * random().nextDouble();
- final double centerLon = -180 + 360.0 * random().nextDouble();
- final double radius = 50_000_000D * random().nextDouble();
- final GeoRect box = GeoRect.fromPointDistance(centerLat, centerLon, radius);
- // TODO: remove this leniency!
- if (box.crossesDateline()) {
- --i; // try again...
- continue;
- }
- final double axisLat = GeoRect.axisLat(centerLat, radius);
-
- for (int k = 0; k < 1000; ++k) {
-
- double[] latBounds = {-90, box.minLat, axisLat, box.maxLat, 90};
- double[] lonBounds = {-180, box.minLon, centerLon, box.maxLon, 180};
- // first choose an upper left corner
- int maxLatRow = random().nextInt(4);
- double latMax = randomInRange(latBounds[maxLatRow], latBounds[maxLatRow + 1]);
- int minLonCol = random().nextInt(4);
- double lonMin = randomInRange(lonBounds[minLonCol], lonBounds[minLonCol + 1]);
- // now choose a lower right corner
- int minLatMaxRow = maxLatRow == 3 ? 3 : maxLatRow + 1; // make sure it will at least cross into the bbox
- int minLatRow = random().nextInt(minLatMaxRow);
- double latMin = randomInRange(latBounds[minLatRow], Math.min(latBounds[minLatRow + 1], latMax));
- int maxLonMinCol = Math.max(minLonCol, 1); // make sure it will at least cross into the bbox
- int maxLonCol = maxLonMinCol + random().nextInt(4 - maxLonMinCol);
- double lonMax = randomInRange(Math.max(lonBounds[maxLonCol], lonMin), lonBounds[maxLonCol + 1]);
-
- assert latMax >= latMin;
- assert lonMax >= lonMin;
-
- if (isDisjoint(centerLat, centerLon, radius, axisLat, latMin, latMax, lonMin, lonMax)) {
- // intersects says false: test a ton of points
- for (int j = 0; j < 200; j++) {
- double lat = latMin + (latMax - latMin) * random().nextDouble();
- double lon = lonMin + (lonMax - lonMin) * random().nextDouble();
-
- if (random().nextBoolean()) {
- // explicitly test an edge
- int edge = random().nextInt(4);
- if (edge == 0) {
- lat = latMin;
- } else if (edge == 1) {
- lat = latMax;
- } else if (edge == 2) {
- lon = lonMin;
- } else if (edge == 3) {
- lon = lonMax;
- }
- }
- double distance = SloppyMath.haversinMeters(centerLat, centerLon, lat, lon);
- try {
- assertTrue(String.format(Locale.ROOT, "\nisDisjoint(\n" +
- "centerLat=%s\n" +
- "centerLon=%s\n" +
- "radius=%s\n" +
- "latMin=%s\n" +
- "latMax=%s\n" +
- "lonMin=%s\n" +
- "lonMax=%s) == false BUT\n" +
- "haversin(%s, %s, %s, %s) = %s\nbbox=%s",
- centerLat, centerLon, radius, latMin, latMax, lonMin, lonMax,
- centerLat, centerLon, lat, lon, distance, GeoRect.fromPointDistance(centerLat, centerLon, radius)),
- distance > radius);
- } catch (AssertionError e) {
- GeoTestUtil.toWebGLEarth(latMin, latMax, lonMin, lonMax, centerLat, centerLon, radius);
- throw e;
- }
- }
- }
- }
- }
- }
-
- static double randomInRange(double min, double max) {
- return min + (max - min) * random().nextDouble();
- }
-
- static boolean isDisjoint(double centerLat, double centerLon, double radius, double axisLat, double latMin, double latMax, double lonMin, double lonMax) {
- if ((centerLon < lonMin || centerLon > lonMax) && (axisLat+GeoRect.AXISLAT_ERROR < latMin || axisLat-GeoRect.AXISLAT_ERROR > latMax)) {
- // circle not fully inside / crossing axis
- if (SloppyMath.haversinMeters(centerLat, centerLon, latMin, lonMin) > radius &&
- SloppyMath.haversinMeters(centerLat, centerLon, latMin, lonMax) > radius &&
- SloppyMath.haversinMeters(centerLat, centerLon, latMax, lonMin) > radius &&
- SloppyMath.haversinMeters(centerLat, centerLon, latMax, lonMax) > radius) {
- // no points inside
- return true;
- }
- }
-
- return false;
- }
-}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/6c219e99/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestPolygon.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestPolygon.java b/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestPolygon.java
index 4ccd8d4..259aada 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestPolygon.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestPolygon.java
@@ -16,8 +16,15 @@
*/
package org.apache.lucene.spatial.util;
+import org.apache.lucene.geo.Polygon;
import org.apache.lucene.util.LuceneTestCase;
+import static org.apache.lucene.geo.GeoTestUtil.nextLatitude;
+import static org.apache.lucene.geo.GeoTestUtil.nextLatitudeAround;
+import static org.apache.lucene.geo.GeoTestUtil.nextLongitude;
+import static org.apache.lucene.geo.GeoTestUtil.nextLongitudeAround;
+import static org.apache.lucene.geo.GeoTestUtil.nextPolygon;
+
public class TestPolygon extends LuceneTestCase {
/** null polyLats not allowed */
@@ -109,11 +116,11 @@ public class TestPolygon extends LuceneTestCase {
public void testBoundingBox() throws Exception {
for (int i = 0; i < 100; i++) {
- Polygon polygon = GeoTestUtil.nextPolygon();
+ Polygon polygon = nextPolygon();
for (int j = 0; j < 100; j++) {
- double latitude = GeoTestUtil.nextLatitude();
- double longitude = GeoTestUtil.nextLongitude();
+ double latitude = nextLatitude();
+ double longitude = nextLongitude();
// if the point is within poly, then it should be in our bounding box
if (polygon.contains(latitude, longitude)) {
assertTrue(latitude >= polygon.minLat && latitude <= polygon.maxLat);
@@ -125,11 +132,11 @@ public class TestPolygon extends LuceneTestCase {
public void testBoundingBoxEdgeCases() throws Exception {
for (int i = 0; i < 100; i++) {
- Polygon polygon = GeoTestUtil.nextPolygon();
+ Polygon polygon = nextPolygon();
for (int j = 0; j < 100; j++) {
- double latitude = GeoTestUtil.nextLatitudeAround(polygon.minLat, polygon.maxLat);
- double longitude = GeoTestUtil.nextLongitudeAround(polygon.minLon, polygon.maxLon);
+ double latitude = nextLatitudeAround(polygon.minLat, polygon.maxLat);
+ double longitude = nextLongitudeAround(polygon.minLon, polygon.maxLon);
// if the point is within poly, then it should be in our bounding box
if (polygon.contains(latitude, longitude)) {
assertTrue(latitude >= polygon.minLat && latitude <= polygon.maxLat);
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/6c219e99/lucene/test-framework/src/java/org/apache/lucene/geo/GeoTestUtil.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/geo/GeoTestUtil.java b/lucene/test-framework/src/java/org/apache/lucene/geo/GeoTestUtil.java
new file mode 100644
index 0000000..bcb473e
--- /dev/null
+++ b/lucene/test-framework/src/java/org/apache/lucene/geo/GeoTestUtil.java
@@ -0,0 +1,499 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.lucene.geo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import org.apache.lucene.util.SloppyMath;
+
+import com.carrotsearch.randomizedtesting.RandomizedContext;
+
+/** static methods for testing geo */
+public class GeoTestUtil {
+
+ /** returns next pseudorandom latitude (anywhere) */
+ public static double nextLatitude() {
+ return -90 + 180.0 * random().nextDouble();
+ }
+
+ /** returns next pseudorandom longitude (anywhere) */
+ public static double nextLongitude() {
+ return -180 + 360.0 * random().nextDouble();
+ }
+
+ /** returns next pseudorandom latitude, kinda close to {@code otherLatitude} */
+ public static double nextLatitudeNear(double otherLatitude) {
+ GeoUtils.checkLatitude(otherLatitude);
+ return normalizeLatitude(otherLatitude + random().nextDouble() - 0.5);
+ }
+
+ /** returns next pseudorandom longitude, kinda close to {@code otherLongitude} */
+ public static double nextLongitudeNear(double otherLongitude) {
+ GeoUtils.checkLongitude(otherLongitude);
+ return normalizeLongitude(otherLongitude + random().nextDouble() - 0.5);
+ }
+
+ /**
+ * returns next pseudorandom latitude, kinda close to {@code minLatitude/maxLatitude}
+ * <b>NOTE:</b>minLatitude/maxLatitude are merely guidelines. the returned value is sometimes
+ * outside of that range! this is to facilitate edge testing.
+ */
+ public static double nextLatitudeAround(double minLatitude, double maxLatitude) {
+ GeoUtils.checkLatitude(minLatitude);
+ GeoUtils.checkLatitude(maxLatitude);
+ return normalizeLatitude(randomRangeMaybeSlightlyOutside(minLatitude, maxLatitude));
+ }
+
+ /**
+ * returns next pseudorandom longitude, kinda close to {@code minLongitude/maxLongitude}
+ * <b>NOTE:</b>minLongitude/maxLongitude are merely guidelines. the returned value is sometimes
+ * outside of that range! this is to facilitate edge testing.
+ */
+ public static double nextLongitudeAround(double minLongitude, double maxLongitude) {
+ GeoUtils.checkLongitude(minLongitude);
+ GeoUtils.checkLongitude(maxLongitude);
+ return normalizeLongitude(randomRangeMaybeSlightlyOutside(minLongitude, maxLongitude));
+ }
+
+ /** returns next pseudorandom box: can cross the 180th meridian */
+ public static Rectangle nextBox() {
+ return nextBoxInternal(nextLatitude(), nextLatitude(), nextLongitude(), nextLongitude(), true);
+ }
+
+ /** returns next pseudorandom box, can cross the 180th meridian, kinda close to {@code otherLatitude} and {@code otherLongitude} */
+ public static Rectangle nextBoxNear(double otherLatitude, double otherLongitude) {
+ GeoUtils.checkLongitude(otherLongitude);
+ GeoUtils.checkLongitude(otherLongitude);
+ return nextBoxInternal(nextLatitudeNear(otherLatitude), nextLatitudeNear(otherLatitude),
+ nextLongitudeNear(otherLongitude), nextLongitudeNear(otherLongitude), true);
+ }
+
+ /** returns next pseudorandom polygon */
+ public static Polygon nextPolygon() {
+ if (random().nextBoolean()) {
+ return surpriseMePolygon(null, null);
+ }
+
+ Rectangle box = nextBoxInternal(nextLatitude(), nextLatitude(), nextLongitude(), nextLongitude(), false);
+ if (random().nextBoolean()) {
+ // box
+ return boxPolygon(box);
+ } else {
+ // triangle
+ return trianglePolygon(box);
+ }
+ }
+
+ /** returns next pseudorandom polygon, kinda close to {@code otherLatitude} and {@code otherLongitude} */
+ public static Polygon nextPolygonNear(double otherLatitude, double otherLongitude) {
+ if (random().nextBoolean()) {
+ return surpriseMePolygon(otherLatitude, otherLongitude);
+ }
+
+ Rectangle box = nextBoxInternal(nextLatitudeNear(otherLatitude), nextLatitudeNear(otherLatitude),
+ nextLongitudeNear(otherLongitude), nextLongitudeNear(otherLongitude), false);
+ if (random().nextBoolean()) {
+ // box
+ return boxPolygon(box);
+ } else {
+ // triangle
+ return trianglePolygon(box);
+ }
+ }
+
+ private static Rectangle nextBoxInternal(double lat0, double lat1, double lon0, double lon1, boolean canCrossDateLine) {
+ if (lat1 < lat0) {
+ double x = lat0;
+ lat0 = lat1;
+ lat1 = x;
+ }
+
+ if (canCrossDateLine == false && lon1 < lon0) {
+ double x = lon0;
+ lon0 = lon1;
+ lon1 = x;
+ }
+
+ return new Rectangle(lat0, lat1, lon0, lon1);
+ }
+
+ private static Polygon boxPolygon(Rectangle box) {
+ assert box.crossesDateline() == false;
+ final double[] polyLats = new double[5];
+ final double[] polyLons = new double[5];
+ polyLats[0] = box.minLat;
+ polyLons[0] = box.minLon;
+ polyLats[1] = box.maxLat;
+ polyLons[1] = box.minLon;
+ polyLats[2] = box.maxLat;
+ polyLons[2] = box.maxLon;
+ polyLats[3] = box.minLat;
+ polyLons[3] = box.maxLon;
+ polyLats[4] = box.minLat;
+ polyLons[4] = box.minLon;
+ return new Polygon(polyLats, polyLons);
+ }
+
+ private static Polygon trianglePolygon(Rectangle box) {
+ assert box.crossesDateline() == false;
+ final double[] polyLats = new double[4];
+ final double[] polyLons = new double[4];
+ polyLats[0] = box.minLat;
+ polyLons[0] = box.minLon;
+ polyLats[1] = box.maxLat;
+ polyLons[1] = box.minLon;
+ polyLats[2] = box.maxLat;
+ polyLons[2] = box.maxLon;
+ polyLats[3] = box.minLat;
+ polyLons[3] = box.minLon;
+ return new Polygon(polyLats, polyLons);
+ }
+
+ private static Polygon surpriseMePolygon(Double otherLatitude, Double otherLongitude) {
+ // repeat until we get a poly that doesn't cross dateline:
+ newPoly:
+ while (true) {
+ //System.out.println("\nPOLY ITER");
+ final double centerLat;
+ final double centerLon;
+ if (otherLatitude == null) {
+ centerLat = nextLatitude();
+ centerLon = nextLongitude();
+ } else {
+ GeoUtils.checkLatitude(otherLatitude);
+ GeoUtils.checkLongitude(otherLongitude);
+ centerLat = nextLatitudeNear(otherLatitude);
+ centerLon = nextLongitudeNear(otherLongitude);
+ }
+
+ double radius = 0.1 + 20 * random().nextDouble();
+ double radiusDelta = random().nextDouble();
+
+ ArrayList<Double> lats = new ArrayList<>();
+ ArrayList<Double> lons = new ArrayList<>();
+ double angle = 0.0;
+ while (true) {
+ angle += random().nextDouble()*40.0;
+ //System.out.println(" angle " + angle);
+ if (angle > 360) {
+ break;
+ }
+ double len = radius * (1.0 - radiusDelta + radiusDelta * random().nextDouble());
+ //System.out.println(" len=" + len);
+ double lat = centerLat + len * Math.cos(Math.toRadians(angle));
+ double lon = centerLon + len * Math.sin(Math.toRadians(angle));
+ if (lon <= GeoUtils.MIN_LON_INCL || lon >= GeoUtils.MAX_LON_INCL) {
+ // cannot cross dateline: try again!
+ continue newPoly;
+ }
+ if (lat > 90) {
+ // cross the north pole
+ lat = 180 - lat;
+ lon = 180 - lon;
+ } else if (lat < -90) {
+ // cross the south pole
+ lat = -180 - lat;
+ lon = 180 - lon;
+ }
+ if (lon <= GeoUtils.MIN_LON_INCL || lon >= GeoUtils.MAX_LON_INCL) {
+ // cannot cross dateline: try again!
+ continue newPoly;
+ }
+ lats.add(lat);
+ lons.add(lon);
+
+ //System.out.println(" lat=" + lats.get(lats.size()-1) + " lon=" + lons.get(lons.size()-1));
+ }
+
+ // close it
+ lats.add(lats.get(0));
+ lons.add(lons.get(0));
+
+ double[] latsArray = new double[lats.size()];
+ double[] lonsArray = new double[lons.size()];
+ for(int i=0;i<lats.size();i++) {
+ latsArray[i] = lats.get(i);
+ lonsArray[i] = lons.get(i);
+ }
+ return new Polygon(latsArray, lonsArray);
+ }
+ }
+
+ /** Returns random double min to max or up to 1% outside of that range */
+ private static double randomRangeMaybeSlightlyOutside(double min, double max) {
+ return min + (random().nextDouble() + (0.5 - random().nextDouble()) * .02) * (max - min);
+ }
+
+ /** Puts latitude in range of -90 to 90. */
+ private static double normalizeLatitude(double latitude) {
+ if (latitude >= -90 && latitude <= 90) {
+ return latitude; //common case, and avoids slight double precision shifting
+ }
+ double off = Math.abs((latitude + 90) % 360);
+ return (off <= 180 ? off : 360-off) - 90;
+ }
+
+ /** Puts longitude in range of -180 to +180. */
+ private static double normalizeLongitude(double longitude) {
+ if (longitude >= -180 && longitude <= 180) {
+ return longitude; //common case, and avoids slight double precision shifting
+ }
+ double off = (longitude + 180) % 360;
+ if (off < 0) {
+ return 180 + off;
+ } else if (off == 0 && longitude > 0) {
+ return 180;
+ } else {
+ return -180 + off;
+ }
+ }
+
+ /** Keep it simple, we don't need to take arbitrary Random for geo tests */
+ private static Random random() {
+ return RandomizedContext.current().getRandom();
+ }
+
+ // craziness for plotting stuff :)
+
+ private static double wrapLat(double lat) {
+ //System.out.println("wrapLat " + lat);
+ if (lat > 90) {
+ //System.out.println(" " + (180 - lat));
+ return 180 - lat;
+ } else if (lat < -90) {
+ //System.out.println(" " + (-180 - lat));
+ return -180 - lat;
+ } else {
+ //System.out.println(" " + lat);
+ return lat;
+ }
+ }
+
+ private static double wrapLon(double lon) {
+ //System.out.println("wrapLon " + lon);
+ if (lon > 180) {
+ //System.out.println(" " + (lon - 360));
+ return lon - 360;
+ } else if (lon < -180) {
+ //System.out.println(" " + (lon + 360));
+ return lon + 360;
+ } else {
+ //System.out.println(" " + lon);
+ return lon;
+ }
+ }
+
+ private static void drawRectApproximatelyOnEarthSurface(String name, String color, double minLat, double maxLat, double minLon, double maxLon) {
+ int steps = 20;
+ System.out.println(" var " + name + " = WE.polygon([");
+ System.out.println(" // min -> max lat, min lon");
+ for(int i=0;i<steps;i++) {
+ System.out.println(" [" + (minLat + (maxLat - minLat) * i / steps) + ", " + minLon + "],");
+ }
+ System.out.println(" // max lat, min -> max lon");
+ for(int i=0;i<steps;i++) {
+ System.out.println(" [" + (maxLat + ", " + (minLon + (maxLon - minLon) * i / steps)) + "],");
+ }
+ System.out.println(" // max -> min lat, max lon");
+ for(int i=0;i<steps;i++) {
+ System.out.println(" [" + (minLat + (maxLat - minLat) * (steps-i) / steps) + ", " + maxLon + "],");
+ }
+ System.out.println(" // min lat, max -> min lon");
+ for(int i=0;i<steps;i++) {
+ System.out.println(" [" + minLat + ", " + (minLon + (maxLon - minLon) * (steps-i) / steps) + "],");
+ }
+ System.out.println(" // min lat, min lon");
+ System.out.println(" [" + minLat + ", " + minLon + "]");
+ System.out.println(" ], {color: \"" + color + "\", fillColor: \"" + color + "\"});");
+ System.out.println(" " + name + ".addTo(earth);");
+ }
+
+ private static void plotLatApproximatelyOnEarthSurface(String name, String color, double lat, double minLon, double maxLon) {
+ System.out.println(" var " + name + " = WE.polygon([");
+ double lon;
+ for(lon = minLon;lon<=maxLon;lon += (maxLon-minLon)/36) {
+ System.out.println(" [" + lat + ", " + lon + "],");
+ }
+ System.out.println(" [" + lat + ", " + maxLon + "],");
+ lon -= (maxLon-minLon)/36;
+ for(;lon>=minLon;lon -= (maxLon-minLon)/36) {
+ System.out.println(" [" + lat + ", " + lon + "],");
+ }
+ System.out.println(" ], {color: \"" + color + "\", fillColor: \"#ffffff\", opacity: " + (color.equals("#ffffff") ? "0.3" : "1") + ", fillOpacity: 0.0001});");
+ System.out.println(" " + name + ".addTo(earth);");
+ }
+
+ private static void plotLonApproximatelyOnEarthSurface(String name, String color, double lon, double minLat, double maxLat) {
+ System.out.println(" var " + name + " = WE.polygon([");
+ double lat;
+ for(lat = minLat;lat<=maxLat;lat += (maxLat-minLat)/36) {
+ System.out.println(" [" + lat + ", " + lon + "],");
+ }
+ System.out.println(" [" + maxLat + ", " + lon + "],");
+ lat -= (maxLat-minLat)/36;
+ for(;lat>=minLat;lat -= (maxLat-minLat)/36) {
+ System.out.println(" [" + lat + ", " + lon + "],");
+ }
+ System.out.println(" ], {color: \"" + color + "\", fillColor: \"#ffffff\", opacity: " + (color.equals("#ffffff") ? "0.3" : "1") + ", fillOpacity: 0.0001});");
+ System.out.println(" " + name + ".addTo(earth);");
+ }
+
+ // http://www.webglearth.org has API details:
+ public static void polysToWebGLEarth(List<double[][]> polys) {
+ System.out.println("<!DOCTYPE HTML>");
+ System.out.println("<html>");
+ System.out.println(" <head>");
+ System.out.println(" <script src=\"http://www.webglearth.com/v2/api.js\"></script>");
+ System.out.println(" <script>");
+ System.out.println(" function initialize() {");
+ System.out.println(" var earth = new WE.map('earth_div');");
+
+ int count = 0;
+ for (double[][] poly : polys) {
+ System.out.println(" var poly" + count + " = WE.polygon([");
+ for(int i=0;i<poly[0].length;i++) {
+ double lat = poly[0][i];
+ double lon = poly[1][i];
+ System.out.println(" [" + lat + ", " + lon + "],");
+ }
+ System.out.println(" ], {color: '#00ff00'});");
+ System.out.println(" poly" + count + ".addTo(earth);");
+ }
+
+ System.out.println(" WE.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{");
+ System.out.println(" attribution: '© OpenStreetMap contributors'");
+ System.out.println(" }).addTo(earth);");
+ System.out.println(" }");
+ System.out.println(" </script>");
+ System.out.println(" <style>");
+ System.out.println(" html, body{padding: 0; margin: 0;}");
+ System.out.println(" #earth_div{top: 0; right: 0; bottom: 0; left: 0; position: absolute !important;}");
+ System.out.println(" </style>");
+ System.out.println(" <title>WebGL Earth API: Hello World</title>");
+ System.out.println(" </head>");
+ System.out.println(" <body onload=\"initialize()\">");
+ System.out.println(" <div id=\"earth_div\"></div>");
+ System.out.println(" </body>");
+ System.out.println("</html>");
+ }
+
+ // http://www.webglearth.org has API details:
+ public static void toWebGLEarth(double rectMinLatitude, double rectMaxLatitude,
+ double rectMinLongitude, double rectMaxLongitude,
+ double centerLatitude, double centerLongitude,
+ double radiusMeters) {
+ Rectangle box = Rectangle.fromPointDistance(centerLatitude, centerLongitude, radiusMeters);
+ System.out.println("<!DOCTYPE HTML>");
+ System.out.println("<html>");
+ System.out.println(" <head>");
+ System.out.println(" <script src=\"http://www.webglearth.com/v2/api.js\"></script>");
+ System.out.println(" <script>");
+ System.out.println(" function initialize() {");
+ System.out.println(" var earth = new WE.map('earth_div', {center: [" + centerLatitude + ", " + centerLongitude + "]});");
+ System.out.println(" var marker = WE.marker([" + centerLatitude + ", " + centerLongitude + "]).addTo(earth);");
+ drawRectApproximatelyOnEarthSurface("cell", "#ff0000", rectMinLatitude, rectMaxLatitude, rectMinLongitude, rectMaxLongitude);
+ System.out.println(" var polygonB = WE.polygon([");
+ StringBuilder b = new StringBuilder();
+ inverseHaversin(b, centerLatitude, centerLongitude, radiusMeters);
+ System.out.println(b);
+ System.out.println(" ], {color: '#00ff00'});");
+ System.out.println(" polygonB.addTo(earth);");
+ drawRectApproximatelyOnEarthSurface("bbox", "#00ff00", box.minLat, box.maxLat, box.minLon, box.maxLon);
+ System.out.println(" WE.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{");
+ System.out.println(" attribution: '© OpenStreetMap contributors'");
+ System.out.println(" }).addTo(earth);");
+ plotLatApproximatelyOnEarthSurface("lat0", "#ffffff", 4.68, 0.0, 360.0);
+ plotLatApproximatelyOnEarthSurface("lat1", "#ffffff", 180-93.09, 0.0, 360.0);
+ plotLatApproximatelyOnEarthSurface("axisLat", "#00ff00", Rectangle.axisLat(centerLatitude, radiusMeters), box.minLon, box.maxLon);
+ plotLonApproximatelyOnEarthSurface("axisLon", "#00ff00", centerLongitude, box.minLat, box.maxLat);
+ System.out.println(" }");
+ System.out.println(" </script>");
+ System.out.println(" <style>");
+ System.out.println(" html, body{padding: 0; margin: 0;}");
+ System.out.println(" #earth_div{top: 0; right: 0; bottom: 0; left: 0; position: absolute !important;}");
+ System.out.println(" </style>");
+ System.out.println(" <title>WebGL Earth API: Hello World</title>");
+ System.out.println(" </head>");
+ System.out.println(" <body onload=\"initialize()\">");
+ System.out.println(" <div id=\"earth_div\"></div>");
+ System.out.println(" </body>");
+ System.out.println("</html>");
+ }
+
+ private static void inverseHaversin(StringBuilder b, double centerLat, double centerLon, double radiusMeters) {
+ double angle = 0;
+ int steps = 100;
+
+ newAngle:
+ while (angle < 360) {
+ double x = Math.cos(Math.toRadians(angle));
+ double y = Math.sin(Math.toRadians(angle));
+ double factor = 2.0;
+ double step = 1.0;
+ int last = 0;
+ double lastDistanceMeters = 0.0;
+ //System.out.println("angle " + angle + " slope=" + slope);
+ while (true) {
+ double lat = wrapLat(centerLat + y * factor);
+ double lon = wrapLon(centerLon + x * factor);
+ double distanceMeters = SloppyMath.haversinMeters(centerLat, centerLon, lat, lon);
+
+ if (last == 1 && distanceMeters < lastDistanceMeters) {
+ // For large enough circles, some angles are not possible:
+ //System.out.println(" done: give up on angle " + angle);
+ angle += 360./steps;
+ continue newAngle;
+ }
+ if (last == -1 && distanceMeters > lastDistanceMeters) {
+ // For large enough circles, some angles are not possible:
+ //System.out.println(" done: give up on angle " + angle);
+ angle += 360./steps;
+ continue newAngle;
+ }
+ lastDistanceMeters = distanceMeters;
+
+ //System.out.println(" iter lat=" + lat + " lon=" + lon + " distance=" + distanceMeters + " vs " + radiusMeters);
+ if (Math.abs(distanceMeters - radiusMeters) < 0.1) {
+ b.append(" [" + lat + ", " + lon + "],\n");
+ break;
+ }
+ if (distanceMeters > radiusMeters) {
+ // too big
+ //System.out.println(" smaller");
+ factor -= step;
+ if (last == 1) {
+ //System.out.println(" half-step");
+ step /= 2.0;
+ }
+ last = -1;
+ } else if (distanceMeters < radiusMeters) {
+ // too small
+ //System.out.println(" bigger");
+ factor += step;
+ if (last == -1) {
+ //System.out.println(" half-step");
+ step /= 2.0;
+ }
+ last = 1;
+ }
+ }
+ angle += 360./steps;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/6c219e99/lucene/test-framework/src/java/org/apache/lucene/geo/package-info.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/geo/package-info.java b/lucene/test-framework/src/java/org/apache/lucene/geo/package-info.java
new file mode 100644
index 0000000..9f0340b
--- /dev/null
+++ b/lucene/test-framework/src/java/org/apache/lucene/geo/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.
+ */
+
+/**
+ * Reusable Geo test utilities.
+ */
+package org.apache.lucene.geo;
\ No newline at end of file