From c78b56645c4e079716a6eeaade6e8848665d79a7 Mon Sep 17 00:00:00 2001 From: "Phyks (Lucas Verney)" Date: Wed, 21 Nov 2018 15:29:57 +0100 Subject: [PATCH] Implement a cache for the cosine computation in CheapRuler. --- .../java/btools/router/OsmPathElement.java | 5 +- .../main/java/btools/mapaccess/OsmNode.java | 5 +- .../main/java/btools/memrouter/OsmNodeP.java | 5 +- .../src/main/java/btools/util/CheapRuler.java | 42 -------- .../java/btools/util/CheapRulerSingleton.java | 98 +++++++++++++++++++ 5 files changed, 107 insertions(+), 48 deletions(-) delete mode 100644 brouter-util/src/main/java/btools/util/CheapRuler.java create mode 100644 brouter-util/src/main/java/btools/util/CheapRulerSingleton.java diff --git a/brouter-core/src/main/java/btools/router/OsmPathElement.java b/brouter-core/src/main/java/btools/router/OsmPathElement.java index e4ec377..8e54a4c 100644 --- a/brouter-core/src/main/java/btools/router/OsmPathElement.java +++ b/brouter-core/src/main/java/btools/router/OsmPathElement.java @@ -2,7 +2,7 @@ package btools.router; import btools.mapaccess.OsmNode; import btools.mapaccess.OsmPos; -import btools.util.CheapRuler; +import btools.util.CheapRulerSingleton; import java.io.DataInput; import java.io.DataOutput; @@ -78,7 +78,8 @@ public class OsmPathElement implements OsmPos public final int calcDistance( OsmPos p ) { - return (int)(CheapRuler.distance(ilon, ilat, p.getILon(), p.getILat()) + 1.0 ); + CheapRulerSingleton cr = CheapRulerSingleton.getInstance(); + return (int)(cr.distance(ilon, ilat, p.getILon(), p.getILat()) + 1.0 ); } public OsmPathElement origin; diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNode.java b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNode.java index c001714..9825a96 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNode.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNode.java @@ -9,7 +9,7 @@ import btools.codec.MicroCache; import btools.codec.MicroCache2; import btools.expressions.BExpressionContextWay; import btools.util.ByteArrayUnifier; -import btools.util.CheapRuler; +import btools.util.CheapRulerSingleton; import btools.util.IByteArrayUnifier; public class OsmNode extends OsmLink implements OsmPos @@ -103,7 +103,8 @@ public class OsmNode extends OsmLink implements OsmPos public final int calcDistance( OsmPos p ) { - return (int) (CheapRuler.distance(ilon, ilat, p.getILon(), p.getILat()) + 1.0); + CheapRulerSingleton cr = CheapRulerSingleton.getInstance(); + return (int) (cr.distance(ilon, ilat, p.getILon(), p.getILat()) + 1.0); } public String toString() diff --git a/brouter-mem-router/src/main/java/btools/memrouter/OsmNodeP.java b/brouter-mem-router/src/main/java/btools/memrouter/OsmNodeP.java index 90f8e11..3b8241c 100644 --- a/brouter-mem-router/src/main/java/btools/memrouter/OsmNodeP.java +++ b/brouter-mem-router/src/main/java/btools/memrouter/OsmNodeP.java @@ -6,7 +6,7 @@ package btools.memrouter; import btools.mapaccess.OsmPos; -import btools.util.CheapRuler; +import btools.util.CheapRulerSingleton; public class OsmNodeP extends OsmLinkP implements Comparable, OsmPos { @@ -103,7 +103,8 @@ public class OsmNodeP extends OsmLinkP implements Comparable, OsmPos @Override public int calcDistance( OsmPos p ) { - return (int)(CheapRuler.distance(ilon, ilat, p.getILon(), p.getILat()) + 1.0 ); + CheapRulerSingleton cr = CheapRulerSingleton.getInstance(); + return (int)(cr.distance(ilon, ilat, p.getILon(), p.getILat()) + 1.0 ); } @Override diff --git a/brouter-util/src/main/java/btools/util/CheapRuler.java b/brouter-util/src/main/java/btools/util/CheapRuler.java deleted file mode 100644 index da090b2..0000000 --- a/brouter-util/src/main/java/btools/util/CheapRuler.java +++ /dev/null @@ -1,42 +0,0 @@ -package btools.util; - -public final class CheapRuler { - /** - * Cheap-Ruler Java implementation - * See - * https://blog.mapbox.com/fast-geodesic-approximations-with-cheap-ruler-106f229ad016 - * for more details. - * - * Original code is at https://github.com/mapbox/cheap-ruler under ISC license. - */ - static int KILOMETERS_TO_METERS = 1000; - static double ILATLNG_TO_LATLNG = 1e-6; - static double DEG_TO_RAD = Math.PI / 180.; - - /* - * @param ilon1 Integer longitude for the start point. this is (longitude in degrees + 180) * 1e6. - * @param ilat1 Integer latitude for the start point, this is (latitude + 90) * 1e6. - * @param ilon2 Integer longitude for the end point, this is (longitude + 180) * 1e6. - * @param ilat2 Integer latitude for the end point, this is (latitude + 90) * 1e6. - * - * @note Integer longitude is ((longitude in degrees) + 180) * 1e6. - * Integer latitude is ((latitude in degrees) + 90) * 1e6. - */ - static public double distance(int ilon1, int ilat1, int ilon2, int ilat2) { - double lat1 = ilat1 * ILATLNG_TO_LATLNG - 90.; // Real latitude, in degrees - double cos = Math.cos(lat1 * DEG_TO_RAD); - double cos2 = 2 * cos * cos - 1; - double cos3 = 2 * cos * cos2 - cos; - double cos4 = 2 * cos * cos3 - cos2; - double cos5 = 2 * cos * cos4 - cos3; - - // Multipliers for converting integer longitude and latitude into distance - // (http://1.usa.gov/1Wb1bv7) - double kx = (111.41513 * cos - 0.09455 * cos3 + 0.00012 * cos5) * ILATLNG_TO_LATLNG * KILOMETERS_TO_METERS; - double ky = (111.13209 - 0.56605 * cos2 + 0.0012 * cos4) * ILATLNG_TO_LATLNG * KILOMETERS_TO_METERS; - - double dlat = (ilat1 - ilat2) * ky; - double dlon = (ilon1 - ilon2) * kx; - return Math.sqrt(dlat * dlat + dlon * dlon); // in m - } -} diff --git a/brouter-util/src/main/java/btools/util/CheapRulerSingleton.java b/brouter-util/src/main/java/btools/util/CheapRulerSingleton.java new file mode 100644 index 0000000..7ff6c54 --- /dev/null +++ b/brouter-util/src/main/java/btools/util/CheapRulerSingleton.java @@ -0,0 +1,98 @@ +package btools.util; + +public final class CheapRulerSingleton { + /** + * Cheap-Ruler Java implementation + * See + * https://blog.mapbox.com/fast-geodesic-approximations-with-cheap-ruler-106f229ad016 + * for more details. + * + * Original code is at https://github.com/mapbox/cheap-ruler under ISC license. + * + * This is implemented as a Singleton to have a unique cache for the cosine + * values across all the code. + */ + private static volatile CheapRulerSingleton instance = null; + + // Conversion constants + private final static double ILATLNG_TO_LATLNG = 1e-6; // From integer to degrees + private final static int KILOMETERS_TO_METERS = 1000; + private final static double DEG_TO_RAD = Math.PI / 180.; + + // Cosine cache constants + private final static int COS_CACHE_LENGTH = 8192; + private final static double COS_CACHE_MAX_DEGREES = 90.0; + // COS_CACHE_LENGTH cached values between 0 and COS_CACHE_MAX_DEGREES degrees. + double[] COS_CACHE = new double[COS_CACHE_LENGTH]; + + /** + * Helper to build the cache of cosine values. + */ + private void buildCosCache() { + double increment = DEG_TO_RAD * COS_CACHE_MAX_DEGREES / COS_CACHE_LENGTH; + for (int i = 0; i < COS_CACHE_LENGTH; i++) { + COS_CACHE[i] = Math.cos(i * increment); + } + } + + private CheapRulerSingleton() { + super(); + // Build the cache of cosine values. + buildCosCache(); + } + + /** + * Get an instance of this singleton class. + */ + public final static CheapRulerSingleton getInstance() { + if (CheapRulerSingleton.instance == null) { + synchronized(CheapRulerSingleton.class) { + if (CheapRulerSingleton.instance == null) { + CheapRulerSingleton.instance = new CheapRulerSingleton(); + } + } + } + return CheapRulerSingleton.instance; + } + + /** + * Helper to compute the cosine of an integer latitude. + */ + private double cosLat(int ilat) { + double latDegrees = ilat * ILATLNG_TO_LATLNG; + if (ilat > 90000000) { + // Use the symmetry of the cosine. + latDegrees -= 90; + } + return COS_CACHE[(int) (latDegrees * COS_CACHE_LENGTH / COS_CACHE_MAX_DEGREES)]; + } + + /** + * Compute the distance (in meters) between two points represented by their + * (integer) latitude and longitude. + * + * @param ilon1 Integer longitude for the start point. this is (longitude in degrees + 180) * 1e6. + * @param ilat1 Integer latitude for the start point, this is (latitude + 90) * 1e6. + * @param ilon2 Integer longitude for the end point, this is (longitude + 180) * 1e6. + * @param ilat2 Integer latitude for the end point, this is (latitude + 90) * 1e6. + * + * @note Integer longitude is ((longitude in degrees) + 180) * 1e6. + * Integer latitude is ((latitude in degrees) + 90) * 1e6. + */ + public double distance(int ilon1, int ilat1, int ilon2, int ilat2) { + double cos = cosLat(ilat1); + double cos2 = 2 * cos * cos - 1; + double cos3 = 2 * cos * cos2 - cos; + double cos4 = 2 * cos * cos3 - cos2; + double cos5 = 2 * cos * cos4 - cos3; + + // Multipliers for converting integer longitude and latitude into distance + // (http://1.usa.gov/1Wb1bv7) + double kx = (111.41513 * cos - 0.09455 * cos3 + 0.00012 * cos5) * ILATLNG_TO_LATLNG * KILOMETERS_TO_METERS; + double ky = (111.13209 - 0.56605 * cos2 + 0.0012 * cos4) * ILATLNG_TO_LATLNG * KILOMETERS_TO_METERS; + + double dlat = (ilat1 - ilat2) * ky; + double dlon = (ilon1 - ilon2) * kx; + return Math.sqrt(dlat * dlat + dlon * dlon); // in m + } +}