From 3ebab20014ca6e80bce138f200e559368cc59923 Mon Sep 17 00:00:00 2001 From: Silvio Heuberger Date: Fri, 14 Jul 2017 17:58:49 +0200 Subject: [PATCH] Add sample/test of circle query with a radius of 100km --- src/main/java/ch/hsr/geohash/GeoHash.java | 6 +- .../geohash/queries/GeoHashCircleQuery.java | 26 ++++----- .../hsr/geohash/GeoHashCircleQueryTest.java | 58 +++++++++++++++++++ .../hsr/geohash/util/RandomWGS84Points.java | 29 ++++++++++ 4 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 src/test/java/ch/hsr/geohash/util/RandomWGS84Points.java diff --git a/src/main/java/ch/hsr/geohash/GeoHash.java b/src/main/java/ch/hsr/geohash/GeoHash.java index e692a1c1..8d3da8c9 100644 --- a/src/main/java/ch/hsr/geohash/GeoHash.java +++ b/src/main/java/ch/hsr/geohash/GeoHash.java @@ -14,13 +14,13 @@ @SuppressWarnings("javadoc") public final class GeoHash implements Comparable, Serializable { - private static final int MAX_BIT_PRECISION = 64; - private static final int MAX_CHARACTER_PRECISION = 12; + public static final int MAX_BIT_PRECISION = 64; + public static final int MAX_CHARACTER_PRECISION = 12; private static final long serialVersionUID = -8553214249630252175L; private static final int[] BITS = { 16, 8, 4, 2, 1 }; private static final int BASE32_BITS = 5; - public static final long FIRST_BIT_FLAGGED = 0x8000000000000000l; + private static final long FIRST_BIT_FLAGGED = 0x8000000000000000l; private static final char[] base32 = { '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' }; diff --git a/src/main/java/ch/hsr/geohash/queries/GeoHashCircleQuery.java b/src/main/java/ch/hsr/geohash/queries/GeoHashCircleQuery.java index 32db87ac..4705a586 100644 --- a/src/main/java/ch/hsr/geohash/queries/GeoHashCircleQuery.java +++ b/src/main/java/ch/hsr/geohash/queries/GeoHashCircleQuery.java @@ -17,26 +17,26 @@ import ch.hsr.geohash.util.VincentyGeodesy; /** - * represents a radius search around a specific point via geohashes. + * represents a radiusInMetres search around a specific point via geohashes. * Approximates the circle with a square! */ public class GeoHashCircleQuery implements GeoHashQuery, Serializable { private static final long serialVersionUID = 1263295371663796291L; - private double radius; + private double radiusInMetres; private GeoHashBoundingBoxQuery query; private WGS84Point center; /** * create a {@link GeoHashCircleQuery} with the given center point and a - * radius in meters. + * radiusInMetres in meters. */ - public GeoHashCircleQuery(WGS84Point center, double radius) { - this.radius = radius; + public GeoHashCircleQuery(WGS84Point center, double radiusInMetres) { + this.radiusInMetres = radiusInMetres; this.center = center; - WGS84Point northEast = VincentyGeodesy.moveInDirection(VincentyGeodesy.moveInDirection(center, 0, radius), 90, - radius); - WGS84Point southWest = VincentyGeodesy.moveInDirection(VincentyGeodesy.moveInDirection(center, 180, radius), - 270, radius); + WGS84Point northEast = VincentyGeodesy.moveInDirection(VincentyGeodesy.moveInDirection(center, 0, radiusInMetres), 90, + radiusInMetres); + WGS84Point southWest = VincentyGeodesy.moveInDirection(VincentyGeodesy.moveInDirection(center, 180, radiusInMetres), + 270, radiusInMetres); BoundingBox bbox = new BoundingBox(northEast, southWest); query = new GeoHashBoundingBoxQuery(bbox); } @@ -58,14 +58,14 @@ public List getSearchHashes() { @Override public String toString() { - return "Cicle Query [center=" + center + ", radius=" + getRadiusString() + "]"; + return "Cicle Query [center=" + center + ", radiusInMetres=" + getRadiusString() + "]"; } private String getRadiusString() { - if (radius > 1000) { - return radius / 1000 + "km"; + if (radiusInMetres > 1000) { + return radiusInMetres / 1000 + "km"; } else { - return radius + "m"; + return radiusInMetres + "m"; } } diff --git a/src/test/java/ch/hsr/geohash/GeoHashCircleQueryTest.java b/src/test/java/ch/hsr/geohash/GeoHashCircleQueryTest.java index 6e976685..3ea3e35d 100644 --- a/src/test/java/ch/hsr/geohash/GeoHashCircleQueryTest.java +++ b/src/test/java/ch/hsr/geohash/GeoHashCircleQueryTest.java @@ -1,12 +1,26 @@ package ch.hsr.geohash; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import ch.hsr.geohash.util.RandomWGS84Points; +import ch.hsr.geohash.util.VincentyGeodesy; +import org.hamcrest.CoreMatchers; import org.junit.Test; import ch.hsr.geohash.queries.GeoHashCircleQuery; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + public class GeoHashCircleQueryTest { + + private static final int NUMBER_OF_RANDOM_POINTS = 1000000; + @Test public void testIssue3WithCircleQuery() throws Exception { WGS84Point center = new WGS84Point(39.86391280373075, 116.37356590048701); @@ -20,4 +34,48 @@ public void testIssue3WithCircleQuery() throws Exception { assertTrue(query.contains(test1)); assertTrue(query.contains(test2)); } + + @Test + public void testStoringManyPointsAndPerformingCircleQuery() throws Exception { + List hashes = createRandomHashes(); + WGS84Point queryPoint = RandomWGS84Points.get(); + + GeoHashCircleQuery oneHundredKilometresQuery = new GeoHashCircleQuery(queryPoint, 100 * 1000); + assertThat(oneHundredKilometresQuery.toString(), containsString("100.0km")); + + // filter points/hashes based on prefix, no expensive vincenty math here. + long t1 = System.currentTimeMillis(); + List contained = new ArrayList<>(); + for(GeoHash hash: hashes) { + if(oneHundredKilometresQuery.contains(hash)) { + contained.add(hash); + } + } + long t2 = System.currentTimeMillis(); + + System.out.println(String.format("Checking %d hashes took %dms", NUMBER_OF_RANDOM_POINTS, t2-t1)); + + // let's argue at least one point should have been contained + assertThat(contained.size(), is(not(0))); + System.out.println(String.format("Number of points matched by query: %d", contained.size())); + + for(GeoHash hash: contained) { + double actualDistanceInMeters = VincentyGeodesy.distanceInMeters(queryPoint, hash.getPoint()); + System.out.println(String.format("Actual distance: %.2fkm", actualDistanceInMeters / 1000)); + } + } + + private List createRandomHashes() { + List points = RandomWGS84Points.get(NUMBER_OF_RANDOM_POINTS); + List result = new ArrayList<>(NUMBER_OF_RANDOM_POINTS); + + for(WGS84Point point : points) { + result.add( + GeoHash.withBitPrecision(point.getLatitude(), point.getLongitude(), GeoHash.MAX_BIT_PRECISION) + ); + } + return result; + } + + } diff --git a/src/test/java/ch/hsr/geohash/util/RandomWGS84Points.java b/src/test/java/ch/hsr/geohash/util/RandomWGS84Points.java new file mode 100644 index 00000000..dd7696cb --- /dev/null +++ b/src/test/java/ch/hsr/geohash/util/RandomWGS84Points.java @@ -0,0 +1,29 @@ +package ch.hsr.geohash.util; + +import ch.hsr.geohash.WGS84Point; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class RandomWGS84Points { + private static final Random RAND = new Random(System.currentTimeMillis()); + + public static WGS84Point get() { + return createRandomPoint(); + } + + public static List get(int n) { + List result = new ArrayList<>(n); + for (int i = 0; i < n; i++) { + result.add(createRandomPoint()); + } + return result; + } + + private static WGS84Point createRandomPoint() { + double latitude = (RAND.nextDouble() - 0.5) * 180; + double longitude = (RAND.nextDouble() - 0.5) * 360; + return new WGS84Point(latitude, longitude); + } +}