package com.github.davidmoten.rtree; import static org.junit.Assert.assertEquals; import java.util.List; import org.junit.Test; import com.github.davidmoten.grumpy.core.Position; import com.github.davidmoten.rtree.geometry.Geometries; import com.github.davidmoten.rtree.geometry.Point; import com.github.davidmoten.rtree.geometry.Rectangle; import rx.Observable; import rx.functions.Func1; public class LatLongExampleTest { private static final Point sydney = Geometries.point(151.2094, -33.86); private static final Point canberra = Geometries.point(149.1244, -35.3075); private static final Point brisbane = Geometries.point(153.0278, -27.4679); private static final Point bungendore = Geometries.point(149.4500, -35.2500); @Test public void testLatLongExample() { // This is to demonstrate how to use rtree to to do distance searches // with Lat Long points // Let's find all cities within 300km of Canberra RTree<String, Point> tree = RTree.star().create(); tree = tree.add("Sydney", sydney); tree = tree.add("Brisbane", brisbane); // Now search for all locations within 300km of Canberra final double distanceKm = 300; List<Entry<String, Point>> list = search(tree, canberra, distanceKm) // get the result .toList().toBlocking().single(); // should have returned Sydney only assertEquals(1, list.size()); assertEquals("Sydney", list.get(0).value()); } public static <T> Observable<Entry<T, Point>> search(RTree<T, Point> tree, Point lonLat, final double distanceKm) { // First we need to calculate an enclosing lat long rectangle for this // distance then we refine on the exact distance final Position from = Position.create(lonLat.y(), lonLat.x()); Rectangle bounds = createBounds(from, distanceKm); return tree // do the first search using the bounds .search(bounds) // refine using the exact distance .filter(new Func1<Entry<T, Point>, Boolean>() { @Override public Boolean call(Entry<T, Point> entry) { Point p = entry.geometry(); Position position = Position.create(p.y(), p.x()); return from.getDistanceToKm(position) < distanceKm; } }); } @Test public void testSearchLatLongCircles() { RTree<GeoCircleValue<String>, Rectangle> tree = RTree.star().create(); // create circles around these major towns GeoCircleValue<String> sydneyCircle = createGeoCircleValue(sydney, 100, "Sydney"); GeoCircleValue<String> canberraCircle = createGeoCircleValue(canberra, 50, "Canberra"); GeoCircleValue<String> brisbaneCircle = createGeoCircleValue(brisbane, 200, "Brisbane"); // add the circles to the RTree using the bounding box of the circle as // the geometry tree = add(tree, sydneyCircle); tree = add(tree, canberraCircle); tree = add(tree, brisbaneCircle); // now find the circles that contain bungendore (which is 30km from // Canberra) final Point location = bungendore; String result = tree.search(location) // filter on the exact distance from the centre of the GeoCircle .filter(new Func1<Entry<GeoCircleValue<String>, Rectangle>, Boolean>() { Position from = Position.create(location.y(), location.x()); @Override public Boolean call(Entry<GeoCircleValue<String>, Rectangle> entry) { Position centre = Position.create(entry.value().lat, entry.value().lon); return from.getDistanceToKm(centre) < entry.value().radiusKm; } }) // do the search (only expect one value) .toBlocking().single() // get the name of the GoeCircleValue returned .value().value; assertEquals("Canberra", result); } private static Rectangle createBounds(final Position from, final double distanceKm) { // this calculates a pretty accurate bounding box. Depending on the // performance you require you wouldn't have to be this accurate because // accuracy is enforced later Position north = from.predict(distanceKm, 0); Position south = from.predict(distanceKm, 180); Position east = from.predict(distanceKm, 90); Position west = from.predict(distanceKm, 270); return Geometries.rectangle(west.getLon(), south.getLat(), east.getLon(), north.getLat()); } private static <T> GeoCircleValue<T> createGeoCircleValue(Point point, double radiusKm, T value) { return new GeoCircleValue<T>(point.y(), point.x(), radiusKm, value); } private static <T> RTree<GeoCircleValue<T>, Rectangle> add( RTree<GeoCircleValue<T>, Rectangle> tree, GeoCircleValue<T> c) { return tree.add(c, createBounds(Position.create(c.lat, c.lon), c.radiusKm)); } private static class GeoCircleValue<T> { GeoCircleValue(float lat, float lon, double radiusKm, T value) { this.lat = lat; this.lon = lon; this.radiusKm = radiusKm; this.value = value; } float lat; float lon; double radiusKm; T value; } }