/** Returns the maximum distance/radius (in meters) from the point 'center' before overlapping */ public static double maxRadialDistanceMeters(final double centerLat, final double centerLon) { if (Math.abs(centerLat) == MAX_LAT) { return SloppyMath.haversinMeters(centerLat, centerLon, 0, centerLon); } return SloppyMath.haversinMeters(centerLat, centerLon, centerLat, (MAX_LON + centerLon) % 360); }
/** Return the distance (in meters) between 2 lat,lon geo points using the haversine method implemented by lucene */ public static double arcDistance(double lat1, double lon1, double lat2, double lon2) { return SloppyMath.haversinMeters(lat1, lon1, lat2, lon2); }
/** * Return the distance (in meters) between 2 lat,lon geo points using a simple tangential plane * this provides a faster alternative to {@link GeoUtils#arcDistance} but is inaccurate for distances greater than * 4 decimal degrees */ public static double planeDistance(double lat1, double lon1, double lat2, double lon2) { double x = (lon2 - lon1) * SloppyMath.TO_RADIANS * Math.cos((lat2 + lat1) / 2.0 * SloppyMath.TO_RADIANS); double y = (lat2 - lat1) * SloppyMath.TO_RADIANS; return Math.sqrt(x * x + y * y) * EARTH_MEAN_RADIUS; }
/** * Return an approximate value of the diameter of the earth (in meters) at the given latitude (in radians). */ public static double earthDiameter(double latitude) { // SloppyMath impl returns a result in kilometers return SloppyMath.earthDiameter(latitude) * 1000; }
/** Given a latitude and longitude (in degrees) and the * maximum great circle (surface of the earth) distance, * returns a simple Filter bounding box to "fast match" * candidates. */ public static Filter getBoundingBoxFilter(double originLat, double originLng, double maxDistanceKM) { // Basic bounding box geo math from // http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates, // licensed under creative commons 3.0: // http://creativecommons.org/licenses/by/3.0 // TODO: maybe switch to recursive prefix tree instead // (in lucene/spatial)? It should be more efficient // since it's a 2D trie... // Degrees -> Radians: double originLatRadians = Math.toRadians(originLat); double originLngRadians = Math.toRadians(originLng); double angle = maxDistanceKM / (SloppyMath.earthDiameter(originLat) / 2.0); double minLat = originLatRadians - angle; double maxLat = originLatRadians + angle; double minLng; double maxLng; if (minLat > Math.toRadians(-90) && maxLat < Math.toRadians(90)) { double delta = Math.asin(Math.sin(angle)/Math.cos(originLatRadians)); minLng = originLngRadians - delta; if (minLng < Math.toRadians(-180)) { minLng += 2 * Math.PI; } maxLng = originLngRadians + delta; if (maxLng > Math.toRadians(180)) { maxLng -= 2 * Math.PI; } } else { // The query includes a pole! minLat = Math.max(minLat, Math.toRadians(-90)); maxLat = Math.min(maxLat, Math.toRadians(90)); minLng = Math.toRadians(-180); maxLng = Math.toRadians(180); } BooleanFilter f = new BooleanFilter(); // Add latitude range filter: f.add(NumericRangeFilter.newDoubleRange("latitude", Math.toDegrees(minLat), Math.toDegrees(maxLat), true, true), BooleanClause.Occur.MUST); // Add longitude range filter: if (minLng > maxLng) { // The bounding box crosses the international date // line: BooleanFilter lonF = new BooleanFilter(); lonF.add(NumericRangeFilter.newDoubleRange("longitude", Math.toDegrees(minLng), null, true, true), BooleanClause.Occur.SHOULD); lonF.add(NumericRangeFilter.newDoubleRange("longitude", null, Math.toDegrees(maxLng), true, true), BooleanClause.Occur.SHOULD); f.add(lonF, BooleanClause.Occur.MUST); } else { f.add(NumericRangeFilter.newDoubleRange("longitude", Math.toDegrees(minLng), Math.toDegrees(maxLng), true, true), BooleanClause.Occur.MUST); } return f; }