Skip to content

Commit 0faba17

Browse files
committed
haversine distance, geodesic midpoint, geodesic paths, geodesic bounds
1 parent 5ac4416 commit 0faba17

File tree

5 files changed

+911
-1
lines changed

5 files changed

+911
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ Adheres to [Semantic Versioning](http://semver.org/).
66

77
## 2.2.2 (TBD)
88

9-
* TBD
9+
* Geometry Utils for Haversine distance, geodesic midpoints, geodesic paths, and geodesic envelopes
10+
* Envelope left mid, bottom mid, right mid, and top mid methods
1011

1112
## [2.2.1](https://github.com/ngageoint/simple-features-java/releases/tag/2.2.1) (01-19-2023)
1213

src/main/java/mil/nga/sf/GeometryEnvelope.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,46 @@ public Point getTopRight() {
504504
return new Point(maxX, maxY);
505505
}
506506

507+
/**
508+
* Get the left mid point
509+
*
510+
* @return left mid point
511+
* @since 2.2.2
512+
*/
513+
public Point getLeftMid() {
514+
return new Point(minX, getMidY());
515+
}
516+
517+
/**
518+
* Get the bottom mid point
519+
*
520+
* @return bottom mid point
521+
* @since 2.2.2
522+
*/
523+
public Point getBottomMid() {
524+
return new Point(getMidX(), minY);
525+
}
526+
527+
/**
528+
* Get the right mid point
529+
*
530+
* @return right mid point
531+
* @since 2.2.2
532+
*/
533+
public Point getRightMid() {
534+
return new Point(maxX, getMidY());
535+
}
536+
537+
/**
538+
* Get the top mid point
539+
*
540+
* @return top mid point
541+
* @since 2.2.2
542+
*/
543+
public Point getTopMid() {
544+
return new Point(getMidX(), maxY);
545+
}
546+
507547
/**
508548
* Get the left line
509549
*

src/main/java/mil/nga/sf/util/GeometryConstants.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,11 @@ public class GeometryConstants {
7878
*/
7979
public static final double DEGREES_TO_RADIANS = Math.PI / 180.0;
8080

81+
/**
82+
* Earth radius in meters (WGS84)
83+
*
84+
* @since 2.2.2
85+
*/
86+
public static final double EARTH_RADIUS = 6378137.0;
87+
8188
}

src/main/java/mil/nga/sf/util/GeometryUtils.java

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,32 @@ public static double distance(Line line) {
127127
return distance(line.startPoint(), line.endPoint());
128128
}
129129

130+
/**
131+
* Get the distance in meters between two points in degrees using the
132+
* Haversine formula
133+
*
134+
* @param point1
135+
* point 1
136+
* @param point2
137+
* point 2
138+
* @return distance in meters
139+
* @since 2.2.2
140+
*/
141+
public static double distanceHaversine(Point point1, Point point2) {
142+
double lat1 = point1.getY();
143+
double lon1 = point1.getX();
144+
double lat2 = point2.getY();
145+
double lon2 = point2.getX();
146+
double diffLat = degreesToRadians(lat2 - lat1);
147+
double diffLon = degreesToRadians(lon2 - lon1);
148+
double a = Math.sin(diffLat / 2) * Math.sin(diffLat / 2)
149+
+ Math.cos(degreesToRadians(lat1))
150+
* Math.cos(degreesToRadians(lat2))
151+
* Math.sin(diffLon / 2) * Math.sin(diffLon / 2);
152+
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
153+
return GeometryConstants.EARTH_RADIUS * c;
154+
}
155+
130156
/**
131157
* Get the bearing heading in degrees between two points in degrees
132158
*
@@ -147,6 +173,52 @@ public static double bearing(Point point1, Point point2) {
147173
return (radiansToDegrees(Math.atan2(y, x)) + 360) % 360;
148174
}
149175

176+
/**
177+
* Get the geodesic midpoint in degrees between two points in degrees
178+
*
179+
* @param point1
180+
* point 1
181+
* @param point2
182+
* point 2
183+
* @return geodesic midpoint in degrees
184+
* @since 2.2.2
185+
*/
186+
public static Point geodesicMidpoint(Point point1, Point point2) {
187+
Point point1Radians = degreesToRadians(point1);
188+
Point point2Radians = degreesToRadians(point2);
189+
Point midpointRadians = geodesicMidpointRadians(point1Radians,
190+
point2Radians);
191+
return radiansToDegrees(midpointRadians);
192+
}
193+
194+
/**
195+
* Get the geodesic midpoint in radians between two points in radians
196+
*
197+
* @param point1
198+
* point 1
199+
* @param point2
200+
* point 2
201+
* @return geodesic midpoint in radians
202+
* @since 2.2.2
203+
*/
204+
public static Point geodesicMidpointRadians(Point point1, Point point2) {
205+
206+
double xDiff = point2.getX() - point1.getX();
207+
double y1 = point1.getY();
208+
double y2 = point2.getY();
209+
double x1 = point1.getX();
210+
211+
double bx = Math.cos(y2) * Math.cos(xDiff);
212+
double by = Math.cos(y2) * Math.sin(xDiff);
213+
214+
double y = Math.atan2(Math.sin(y1) + Math.sin(y2),
215+
Math.sqrt((Math.cos(y1) + bx) * (Math.cos(y1) + bx) + by * by));
216+
double x = x1 + Math.atan2(by, Math.cos(y1) + bx);
217+
Point midpoint = new Point(x, y);
218+
219+
return midpoint;
220+
}
221+
150222
/**
151223
* Get the bearing heading in degrees between line end points in degrees
152224
*
@@ -238,6 +310,34 @@ public static double radiansToDegrees(double radians) {
238310
return radians * GeometryConstants.RADIANS_TO_DEGREES;
239311
}
240312

313+
/**
314+
* Convert point in degrees to radians
315+
*
316+
* @param point
317+
* point in degrees
318+
* @return point in radians
319+
* @since 2.2.2
320+
*/
321+
public static Point degreesToRadians(Point point) {
322+
double x = degreesToRadians(point.getX());
323+
double y = degreesToRadians(point.getY());
324+
return new Point(x, y);
325+
}
326+
327+
/**
328+
* Convert point in radians to degrees
329+
*
330+
* @param point
331+
* point in radians
332+
* @return point in degrees
333+
* @since 2.2.2
334+
*/
335+
public static Point radiansToDegrees(Point point) {
336+
double x = radiansToDegrees(point.getX());
337+
double y = radiansToDegrees(point.getY());
338+
return new Point(x, y);
339+
}
340+
241341
/**
242342
* Get the centroid point of a 2 dimensional representation of the Geometry
243343
* (balancing point of a 2d cutout of the geometry). Only the x and y
@@ -883,6 +983,127 @@ private static List<Point> simplifyPoints(List<Point> points,
883983
return result;
884984
}
885985

986+
/**
987+
* Create a geodesic path of a line string in degrees with a max distance
988+
* between any two path points
989+
*
990+
* @param lineString
991+
* line string in degrees
992+
* @param maxDistance
993+
* max distance allowed between path points
994+
* @return geodesic path of points
995+
* @since 2.2.2
996+
*/
997+
public static List<Point> geodesicPath(LineString lineString,
998+
double maxDistance) {
999+
return geodesicPath(lineString.getPoints(), maxDistance);
1000+
}
1001+
1002+
/**
1003+
* Create a geodesic path of a points in degrees with a max distance between
1004+
* any two path points
1005+
*
1006+
* @param points
1007+
* points in degrees
1008+
* @param maxDistance
1009+
* max distance allowed between path points
1010+
* @return geodesic path of points
1011+
* @since 2.2.2
1012+
*/
1013+
public static List<Point> geodesicPath(List<Point> points,
1014+
double maxDistance) {
1015+
List<Point> path = new ArrayList<>();
1016+
if (!points.isEmpty()) {
1017+
path.add(points.get(0));
1018+
for (int i = 0; i < points.size() - 1; i++) {
1019+
List<Point> subPath = geodesicPath(points.get(i),
1020+
points.get(i + 1), maxDistance);
1021+
path.addAll(subPath.subList(1, subPath.size()));
1022+
}
1023+
}
1024+
return path;
1025+
}
1026+
1027+
/**
1028+
* Create a geodesic path between the two points in degrees with a max
1029+
* distance between any two path points
1030+
*
1031+
* @param point1
1032+
* point 1
1033+
* @param point2
1034+
* point 2
1035+
* @param maxDistance
1036+
* max distance allowed between path points
1037+
* @return geodesic path of points
1038+
* @since 2.2.2
1039+
*/
1040+
public static List<Point> geodesicPath(Point point1, Point point2,
1041+
double maxDistance) {
1042+
List<Point> path = new ArrayList<>();
1043+
path.add(point1);
1044+
geodesicPath(point1, point2, maxDistance, path);
1045+
path.add(point2);
1046+
return path;
1047+
}
1048+
1049+
/**
1050+
* Populate a geodesic path between the two points in degrees with a max
1051+
* distance between any two path points
1052+
*
1053+
* @param point1
1054+
* point 1
1055+
* @param point2
1056+
* point 2
1057+
* @param maxDistance
1058+
* max distance allowed between path points
1059+
* @param path
1060+
* geodesic path of points
1061+
*/
1062+
private static void geodesicPath(Point point1, Point point2,
1063+
double maxDistance, List<Point> path) {
1064+
double distance = distanceHaversine(point1, point2);
1065+
if (distance > maxDistance) {
1066+
Point midpoint = geodesicMidpoint(point1, point2);
1067+
geodesicPath(point1, midpoint, maxDistance, path);
1068+
path.add(midpoint);
1069+
geodesicPath(midpoint, point2, maxDistance, path);
1070+
}
1071+
}
1072+
1073+
/**
1074+
* Expand the vertical bounds of a geometry envelope in degrees by including
1075+
* geodesic bounds
1076+
*
1077+
* @param envelope
1078+
* geometry envelope in degrees
1079+
* @return geodesic expanded geometry envelope in degrees
1080+
* @since 2.2.2
1081+
*/
1082+
public static GeometryEnvelope geodesicEnvelope(GeometryEnvelope envelope) {
1083+
GeometryEnvelope geodesic = envelope.copy();
1084+
if (envelope.getMinY() < 0) {
1085+
Point left = envelope.getBottomLeft();
1086+
Point right = envelope.getBottomRight();
1087+
Point midpoint = geodesicMidpoint(left, right);
1088+
double y = midpoint.getY();
1089+
if (y < geodesic.getMinY()) {
1090+
geodesic.setMinY(y);
1091+
}
1092+
1093+
}
1094+
if (envelope.getMaxY() > 0) {
1095+
Point left = envelope.getTopLeft();
1096+
Point right = envelope.getTopRight();
1097+
Point midpoint = geodesicMidpoint(left, right);
1098+
double y = midpoint.getY();
1099+
if (y > geodesic.getMaxY()) {
1100+
geodesic.setMaxY(y);
1101+
}
1102+
1103+
}
1104+
return geodesic;
1105+
}
1106+
8861107
/**
8871108
* Calculate the perpendicular distance between the point and the line
8881109
* represented by the start and end points. Points should be in a meters

0 commit comments

Comments
 (0)