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]
This commit is contained in:
Haoming Yin
2017-08-06 12:36:17 +12:00
parent e90a0ce435
commit 43788bd153
2 changed files with 179 additions and 118 deletions
+154 -118
View File
@@ -5,146 +5,182 @@ import seng302.model.GeoPoint;
public class GeoUtility { 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 * Calculates the euclidean distance between two markers on the canvas using xy coordinates
* *
* @param p1 first geographical position * @param p1 first geographical position
* @param p2 second geographical position * @param p2 second geographical position
* @return the distance in meter between two points in meters * @return the distance in meter between two points in meters
*/ */
public static Double getDistance(GeoPoint p1, GeoPoint p2) { public static Double getDistance(GeoPoint p1, GeoPoint p2) {
double dLat = Math.toRadians(p2.getLat() - p1.getLat()); double dLat = Math.toRadians(p2.getLat() - p1.getLat());
double dLon = Math.toRadians(p2.getLng() - p1.getLng()); double dLon = Math.toRadians(p2.getLng() - p1.getLng());
double a = Math.pow(Math.sin(dLat / 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.cos(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat()))
* Math.pow(Math.sin(dLon / 2), 2.0); * Math.pow(Math.sin(dLon / 2), 2.0);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
double d = EARTH_RADIUS * c; 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. * Calculates the angle between to angular co-ordinates on a sphere.
* *
* @param p1 the first geographical position, start point * @param p1 the first geographical position, start point
* @param p2 the second geographical position, end point * @param p2 the second geographical position, end point
* @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). * @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). vertical up
* vertical up is 0 deg. horizontal right is 90 deg. * is 0 deg. horizontal right is 90 deg.
* *
* NOTE: * NOTE: The final bearing will differ from the initial bearing by varying degrees according to
* The final bearing will differ from the initial bearing by varying degrees * distance and latitude (if you were to go from say 35°N,45°E (≈ Baghdad) to 35°N,135°E (≈
* according to distance and latitude (if you were to go from say 35°N,45°E * Osaka), you would start on a heading of 60° and end up on a heading of 120°
* (≈ 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;
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. * Calculates the angle between to angular co-ordinates on a sphere in radians.
* *
* @param p1 the first geographical position, start point * @param p1 the first geographical position, start point
* @param p2 the second geographical position, end point * @param p2 the second geographical position, end point
* @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). * @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). vertical up
* vertical up is 0 deg. horizontal right is 90 deg. * is 0 deg. horizontal right is 90 deg.
* *
* NOTE: * NOTE: The final bearing will differ from the initial bearing by varying degrees according to
* The final bearing will differ from the initial bearing by varying degrees * distance and latitude (if you were to go from say 35°N,45°E (≈ Baghdad) to 35°N,135°E (≈
* according to distance and latitude (if you were to go from say 35°N,45°E * Osaka), you would start on a heading of 60° and end up on a heading of 120°
* (≈ 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());
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 y = Math.sin(dLon) * Math.cos(Math.toRadians(p2.getLat()));
double x = Math.cos(Math.toRadians(p1.getLat())) * Math.sin(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); - 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 * Given an existing point in lat/lng, distance in (in meter) and bearing (in degrees),
* (in degrees), calculates the new lat/lng. * calculates the new lat/lng.
* *
* @param origin the original position within lat / lng * @param origin the original position within lat / lng
* @param bearing the bearing in degree, from original position to the new position * @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 * @param distance the distance in meter, from original position to the new position
* @return the new position * @return the new position
*/ */
public static GeoPoint getGeoCoordinate(GeoPoint origin, Double bearing, Double distance) { public static GeoPoint getGeoCoordinate(GeoPoint origin, Double bearing, Double distance) {
double b = Math.toRadians(bearing); // bearing to radians double b = Math.toRadians(bearing); // bearing to radians
double d = distance / 1000.0; // distance to km double d = distance / 1000.0; // distance to km
double originLat = Math.toRadians(origin.getLat()); double originLat = Math.toRadians(origin.getLat());
double originLng = Math.toRadians(origin.getLng()); double originLng = Math.toRadians(origin.getLng());
double endLat = Math.asin(Math.sin(originLat) * Math.cos(d / EARTH_RADIUS) double endLat = Math.asin(Math.sin(originLat) * Math.cos(d / EARTH_RADIUS)
+ Math.cos(originLat) * Math.sin(d / EARTH_RADIUS) * Math.cos(b)); + Math.cos(originLat) * Math.sin(d / EARTH_RADIUS) * Math.cos(b));
double endLng = originLng double endLng = originLng
+ Math.atan2(Math.sin(b) * Math.sin(d / EARTH_RADIUS) * Math.cos(originLat), + Math.atan2(Math.sin(b) * Math.sin(d / EARTH_RADIUS) * Math.cos(originLat),
Math.cos(d / EARTH_RADIUS) - Math.sin(originLat) * Math.sin(endLat)); 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 * Performs the line function on two points of a line and a test point to test which side of the
* on. If the return value is * line that point is on. If the return value is return 1, then the point is on one side of the
* return 1, then the point is on one side of the line, * line, return -1 then the point is on the other side of the line return 0 then the point is
* return -1 then the point is on the other side of the line * exactly on the line.
* return 0 then the point is exactly on the line. *
* @param linePoint1 One point of the line * @param linePoint1 One point of the line
* @param linePoint2 Second point of the line * @param linePoint2 Second point of the line
* @param testPoint The point to test with this line * @param testPoint The point to test with this line
* @return A return value indicating which side of the line the point is on * @return A return value indicating which side of the line the point is on
*/ */
public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) { public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) {
Double x = testPoint.getX(); Double x = testPoint.getX();
Double y = testPoint.getY(); Double y = testPoint.getY();
Double x1 = linePoint1.getX(); Double x1 = linePoint1.getX();
Double y1 = linePoint1.getY(); Double y1 = linePoint1.getY();
Double x2 = linePoint2.getX(); Double x2 = linePoint2.getX();
Double y2 = linePoint2.getY(); 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) { if (result > 0) {
return 1; return 1;
} } else if (result < 0) {
else if (result < 0) { return -1;
return -1; } else {
} return 0;
else { }
return 0; }
}
}
/** /**
* Given a point and a vector (angle and vector length) Will create a new point, that vector away from the origin * Given a point and a vector (angle and vector length) Will create a new point, that vector
* point * 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 originPoint The point with which to use as the base for our vector addition
* @param vectorLength The length out on this angle from the origin point to create the new point * @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!)
* @return a Point2D * @param vectorLength The length out on this angle from the origin point to create the new
*/ * point
public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, Double vectorLength) { * @return a Point2D
*/
public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg,
Double vectorLength) {
Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg)); Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg));
Double endPointY = originPoint.getY() + vectorLength * Math.sin(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;
}
} }
@@ -1,7 +1,9 @@
package seng302.utilities; package seng302.utilities;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import org.junit.Before; import org.junit.Before;
@@ -30,6 +32,7 @@ public class GeoUtilityTest {
private GeoPoint p2 = new GeoPoint(57.671524, 11.844495); private GeoPoint p2 = new GeoPoint(57.671524, 11.844495);
private GeoPoint p3 = new GeoPoint(57.670822, 11.843392); private GeoPoint p3 = new GeoPoint(57.670822, 11.843392);
private GeoPoint p4 = new GeoPoint(25.694829, 98.392049); private GeoPoint p4 = new GeoPoint(25.694829, 98.392049);
private GeoPoint p5 = new GeoPoint(57.671829, 11.842049);
private double toleranceRate = 0.01; private double toleranceRate = 0.01;
@@ -125,4 +128,26 @@ public class GeoUtilityTest {
assertEquals(expected.getY(), newPoint.getY(), 1E-6); 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));
}
} }