From 43788bd15396c08d2fe8dcac2de978036ab9d67c Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Sun, 6 Aug 2017 12:36:17 +1200 Subject: [PATCH] Added a method to test if a geo point is located in a triangle which is formed by other three geo points. The method helps to check the mark rounding. Also unit tests have been done for this method. tags: #story[1124] --- .../java/seng302/utilities/GeoUtility.java | 272 ++++++++++-------- .../seng302/utilities/GeoUtilityTest.java | 25 ++ 2 files changed, 179 insertions(+), 118 deletions(-) diff --git a/src/main/java/seng302/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index 0a49c776..9aa61b9d 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -5,146 +5,182 @@ import seng302.model.GeoPoint; public class GeoUtility { - private static double EARTH_RADIUS = 6378.137; + private static double EARTH_RADIUS = 6378.137; - /** - * Calculates the euclidean distance between two markers on the canvas using xy coordinates - * - * @param p1 first geographical position - * @param p2 second geographical position - * @return the distance in meter between two points in meters - */ - public static Double getDistance(GeoPoint p1, GeoPoint p2) { + /** + * Calculates the euclidean distance between two markers on the canvas using xy coordinates + * + * @param p1 first geographical position + * @param p2 second geographical position + * @return the distance in meter between two points in meters + */ + public static Double getDistance(GeoPoint p1, GeoPoint p2) { - double dLat = Math.toRadians(p2.getLat() - p1.getLat()); - double dLon = Math.toRadians(p2.getLng() - p1.getLng()); + double dLat = Math.toRadians(p2.getLat() - p1.getLat()); + double dLon = Math.toRadians(p2.getLng() - p1.getLng()); - double a = Math.pow(Math.sin(dLat / 2), 2.0) - + Math.cos(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) - * Math.pow(Math.sin(dLon / 2), 2.0); + double a = Math.pow(Math.sin(dLat / 2), 2.0) + + Math.cos(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) + * Math.pow(Math.sin(dLon / 2), 2.0); - double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - double d = EARTH_RADIUS * c; + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + double d = EARTH_RADIUS * c; - return d * 1000; // distance from km to meter - } + return d * 1000; // distance from km to meter + } - /** - * Calculates the angle between to angular co-ordinates on a sphere. - * - * @param p1 the first geographical position, start point - * @param p2 the second geographical position, end point - * @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). - * vertical up is 0 deg. horizontal right is 90 deg. - * - * NOTE: - * The final bearing will differ from the initial bearing by varying degrees - * according to distance and latitude (if you were to go from say 35°N,45°E - * (≈ Baghdad) to 35°N,135°E (≈ Osaka), you would start on a heading of 60° - * and end up on a heading of 120° - */ - public static Double getBearing(GeoPoint p1, GeoPoint p2) { - return (Math.toDegrees(getBearingRad(p1, p2)) + 360.0) % 360.0; - } + /** + * Calculates the angle between to angular co-ordinates on a sphere. + * + * @param p1 the first geographical position, start point + * @param p2 the second geographical position, end point + * @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). vertical up + * is 0 deg. horizontal right is 90 deg. + * + * NOTE: The final bearing will differ from the initial bearing by varying degrees according to + * distance and latitude (if you were to go from say 35°N,45°E (≈ Baghdad) to 35°N,135°E (≈ + * Osaka), you would start on a heading of 60° and end up on a heading of 120° + */ + public static Double getBearing(GeoPoint p1, GeoPoint p2) { + return (Math.toDegrees(getBearingRad(p1, p2)) + 360.0) % 360.0; + } - /** - * Calculates the angle between to angular co-ordinates on a sphere in radians. - * - * @param p1 the first geographical position, start point - * @param p2 the second geographical position, end point - * @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). - * vertical up is 0 deg. horizontal right is 90 deg. - * - * NOTE: - * The final bearing will differ from the initial bearing by varying degrees - * according to distance and latitude (if you were to go from say 35°N,45°E - * (≈ Baghdad) to 35°N,135°E (≈ Osaka), you would start on a heading of 60° - * and end up on a heading of 120° - */ - public static Double getBearingRad(GeoPoint p1, GeoPoint p2) { - double dLon = Math.toRadians(p2.getLng() - p1.getLng()); + /** + * Calculates the angle between to angular co-ordinates on a sphere in radians. + * + * @param p1 the first geographical position, start point + * @param p2 the second geographical position, end point + * @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). vertical up + * is 0 deg. horizontal right is 90 deg. + * + * NOTE: The final bearing will differ from the initial bearing by varying degrees according to + * distance and latitude (if you were to go from say 35°N,45°E (≈ Baghdad) to 35°N,135°E (≈ + * Osaka), you would start on a heading of 60° and end up on a heading of 120° + */ + public static Double getBearingRad(GeoPoint p1, GeoPoint p2) { + double dLon = Math.toRadians(p2.getLng() - p1.getLng()); - double y = Math.sin(dLon) * Math.cos(Math.toRadians(p2.getLat())); - double x = Math.cos(Math.toRadians(p1.getLat())) * Math.sin(Math.toRadians(p2.getLat())) - - Math.sin(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) * Math.cos(dLon); + double y = Math.sin(dLon) * Math.cos(Math.toRadians(p2.getLat())); + double x = Math.cos(Math.toRadians(p1.getLat())) * Math.sin(Math.toRadians(p2.getLat())) + - Math.sin(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) * Math + .cos(dLon); - return Math.atan2(y, x); - } + return Math.atan2(y, x); + } - /** - * Given an existing point in lat/lng, distance in (in meter) and bearing - * (in degrees), calculates the new lat/lng. - * - * @param origin the original position within lat / lng - * @param bearing the bearing in degree, from original position to the new position - * @param distance the distance in meter, from original position to the new position - * @return the new position - */ - public static GeoPoint getGeoCoordinate(GeoPoint origin, Double bearing, Double distance) { - double b = Math.toRadians(bearing); // bearing to radians - double d = distance / 1000.0; // distance to km + /** + * Given an existing point in lat/lng, distance in (in meter) and bearing (in degrees), + * calculates the new lat/lng. + * + * @param origin the original position within lat / lng + * @param bearing the bearing in degree, from original position to the new position + * @param distance the distance in meter, from original position to the new position + * @return the new position + */ + public static GeoPoint getGeoCoordinate(GeoPoint origin, Double bearing, Double distance) { + double b = Math.toRadians(bearing); // bearing to radians + double d = distance / 1000.0; // distance to km - double originLat = Math.toRadians(origin.getLat()); - double originLng = Math.toRadians(origin.getLng()); + double originLat = Math.toRadians(origin.getLat()); + double originLng = Math.toRadians(origin.getLng()); - double endLat = Math.asin(Math.sin(originLat) * Math.cos(d / EARTH_RADIUS) - + Math.cos(originLat) * Math.sin(d / EARTH_RADIUS) * Math.cos(b)); - double endLng = originLng - + Math.atan2(Math.sin(b) * Math.sin(d / EARTH_RADIUS) * Math.cos(originLat), - Math.cos(d / EARTH_RADIUS) - Math.sin(originLat) * Math.sin(endLat)); + double endLat = Math.asin(Math.sin(originLat) * Math.cos(d / EARTH_RADIUS) + + Math.cos(originLat) * Math.sin(d / EARTH_RADIUS) * Math.cos(b)); + double endLng = originLng + + Math.atan2(Math.sin(b) * Math.sin(d / EARTH_RADIUS) * Math.cos(originLat), + Math.cos(d / EARTH_RADIUS) - Math.sin(originLat) * Math.sin(endLat)); - return new GeoPoint(Math.toDegrees(endLat), Math.toDegrees(endLng)); - } + return new GeoPoint(Math.toDegrees(endLat), Math.toDegrees(endLng)); + } /** - * Performs the line function on two points of a line and a test point to test which side of the line that point is - * on. If the return value is - * return 1, then the point is on one side of the line, - * return -1 then the point is on the other side of the line - * return 0 then the point is exactly on the line. - * @param linePoint1 One point of the line - * @param linePoint2 Second point of the line - * @param testPoint The point to test with this line - * @return A return value indicating which side of the line the point is on - */ - public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) { + * Performs the line function on two points of a line and a test point to test which side of the + * line that point is on. If the return value is return 1, then the point is on one side of the + * line, return -1 then the point is on the other side of the line return 0 then the point is + * exactly on the line. + * + * @param linePoint1 One point of the line + * @param linePoint2 Second point of the line + * @param testPoint The point to test with this line + * @return A return value indicating which side of the line the point is on + */ + public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) { - Double x = testPoint.getX(); - Double y = testPoint.getY(); - Double x1 = linePoint1.getX(); - Double y1 = linePoint1.getY(); - Double x2 = linePoint2.getX(); - Double y2 = linePoint2.getY(); + Double x = testPoint.getX(); + Double y = testPoint.getY(); + Double x1 = linePoint1.getX(); + Double y1 = linePoint1.getY(); + Double x2 = linePoint2.getX(); + Double y2 = linePoint2.getY(); - Double result = (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1); //Line function + Double result = (x - x1) * (y2 - y1) - (y - y1) * (x2 - x1); //Line function - if (result > 0) { - return 1; - } - else if (result < 0) { - return -1; - } - else { - return 0; - } - } + if (result > 0) { + return 1; + } else if (result < 0) { + return -1; + } else { + return 0; + } + } - /** - * Given a point and a vector (angle and vector length) Will create a new point, that vector away from the origin - * point - * @param originPoint The point with which to use as the base for our vector addition - * @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!) - * @param vectorLength The length out on this angle from the origin point to create the new point - * @return a Point2D - */ - public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, Double vectorLength) { + /** + * Given a point and a vector (angle and vector length) Will create a new point, that vector + * away from the origin point + * + * @param originPoint The point with which to use as the base for our vector addition + * @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!) + * @param vectorLength The length out on this angle from the origin point to create the new + * point + * @return a Point2D + */ + public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, + Double vectorLength) { - Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg)); - Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg)); + Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg)); + Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg)); - return new Point2D(endPointX, endPointY); + return new Point2D(endPointX, endPointY); - } + } + + /** + * Define vector v1 = p1 - p0 to v2 = p2- p0. This function returns the difference of bearing + * from v1 to v2. For example, if bearing of v1 is 30 deg and bearing of v2 is 90 deg, then the + * difference is 60 deg. + * + * @param bearing1 the bearing of v1 + * @param bearing2 the bearing of v2 + * @return the difference of bearing from v1 to v2 + */ + private static double getBearingDiff(double bearing1, double bearing2) { + return ((360 - bearing1) + bearing2) % 360; + } + + /** + * Given three geo points to form a triangle, the method returns true if the fourth point is + * inside the triangle + * + * @param v1 the vertex of the triangle + * @param v2 the vertex of the triangle + * @param v3 the vertex of the triangle + * @param point the point to be tested + * @return true if the fourth point is inside the triangle + */ + public static boolean isPointInTriangle(GeoPoint v1, GeoPoint v2, GeoPoint v3, GeoPoint point) { + // true, if diff of bearing from (v1->v2) to (v1->p) is less than 180 deg + boolean sideFlag = getBearingDiff(getBearing(v1, v2), getBearing(v1, point)) < 180; + + if ((getBearingDiff(getBearing(v2, v3), getBearing(v2, point)) < 180) != sideFlag) { + return false; + } + + if ((getBearingDiff(getBearing(v3, v1), getBearing(v3, point)) < 180) != sideFlag) { + return false; + } + + return true; + } } diff --git a/src/test/java/seng302/utilities/GeoUtilityTest.java b/src/test/java/seng302/utilities/GeoUtilityTest.java index b7ad8f85..8382bb8c 100644 --- a/src/test/java/seng302/utilities/GeoUtilityTest.java +++ b/src/test/java/seng302/utilities/GeoUtilityTest.java @@ -1,7 +1,9 @@ package seng302.utilities; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; import javafx.geometry.Point2D; import org.junit.Before; @@ -30,6 +32,7 @@ public class GeoUtilityTest { private GeoPoint p2 = new GeoPoint(57.671524, 11.844495); private GeoPoint p3 = new GeoPoint(57.670822, 11.843392); private GeoPoint p4 = new GeoPoint(25.694829, 98.392049); + private GeoPoint p5 = new GeoPoint(57.671829, 11.842049); private double toleranceRate = 0.01; @@ -125,4 +128,26 @@ public class GeoUtilityTest { assertEquals(expected.getY(), newPoint.getY(), 1E-6); } + @Test + public void testIsPointInTriangle() { + GeoPoint v1 = new GeoPoint(57.670333, 11.842833); + GeoPoint v2 = new GeoPoint(57.671524, 11.844495); + GeoPoint v3 = new GeoPoint(57.671829, 11.842049); + GeoPoint p1 = new GeoPoint(57.670822, 11.843192); // inside triangle + GeoPoint p2 = new GeoPoint(57.670892, 11.843642); // outside triangle + + // benchmark test. 100,000 calculations for 0.336 seconds +// long startTime = System.nanoTime(); +// for (int i = 0; i < 100000; i++) { +// assertTrue(GeoUtility.isPointInTriangle(v1, v2, v3, p1)); +// } +// System.out.println((System.nanoTime() - startTime) / 1000000000.0); + + // test for different orders of vertices, which should not affect the result + assertTrue(GeoUtility.isPointInTriangle(v2, v1, v3, p1)); + assertTrue(GeoUtility.isPointInTriangle(v3, v1, v2, p1)); + + assertFalse(GeoUtility.isPointInTriangle(v1, v2, v3, p2)); + + } } \ No newline at end of file