So looking around and even asking on probably a more appropriate site (here), I found a solution based on something by Chris Veness (here) to find the intersection point given two points and their bearings. So to get the corners of the bounding box I just take each combination of top/bottom and left/right (1 & 3, 1 & 4, 2 & 3, 2 & 4) and find the intersections using the known bearing and adjusting accordingly. For example to find the bottom right of the image I'd calculate the intersection of points 1 & 3 using the bearing + 90 for the direction of point 1 and the bearing - 180 for the direction of point 3.
I can take no credit for the algorithm or even really explain it in terms of how it works geometrically, but its worked in my testing. Below is my java translation from the javascript version provided by Chris
public static CoordD getIntersection(CoordD point1, double bearing1, CoordD point2, double bearning2) {
double lat1 = rad(point1.latitude); double lon1 = rad(point1.longitude);
double lat2 = rad(point2.latitude); double lon2 = rad(point2.longitude);
double bearing13 = rad(bearing1); double bearing 23 = rad(bearing2);
double dLat = lat2 - lat1; double dLon = lon2 - lon1;
double dist12 = 2 * Math.asin( Math.sqrt( Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) * Math.sin(dLon / 2) ) );
if (dist12 == 0) return null;
double bearingA = Math.acos( ( Math.sin(lat2) - Math.sin(lat1) * Math.cos(dist12) ) /
( Math.sin(dist12) * Math.cos(lat1) ) );
double bearingB = Math.acos( ( Math.sin(lat1) - Math.sin(lat2) * Math.cos(dist12) ) /
( Math.sin(dist12) * Math.cos(lat2) ) );
if (Double.isNaN(bearingA)) bearingA = 0;
if (Double.isNaN(bearingB)) bearingB = 0;
double bearing12, bearing21;
if (Math.sin(dLon) > 0) {
bearing12 = bearingA;
bearing21 = 2 * Math.PI - bearingB;
} else {
bearing12 = 2 * Math.PI - bearingA;
bearing21 = bearingB;
}
double alpha1 = (bearing13 - bearing12 + Math.PI) % (2 * Math.PI) - Math.PI; // Angle 2-1-3
double alpha2 = (bearing21 - bearing23 + Math.PI) % (2 * Math.PI) - Math.PI; // Angle 1-2-3
if (Math.sin(alpha1) == 0 && Math.sin(alpha2) == 0) return null; // Infinite intersections
if (Math.sin(alpha1) * Math.sin(alpha2) < 0) return null; // Ambiguous intersection
// needed?
// alpha1 = Math.abs(alpha1);
// alpha2 = Math.abs(alpha2);
double alpha3 = Math.acos( -Math.cos(alpha1) * Math.cos(alpha2) +
Math.sin(alpha1) * Math.sin(alpha2) * Math.cos(dist12) );
double dist13 = Math.atan2( Math.sin(dist12) * Math.sin(alpha1) * Math.sin(alpha2),
Math.cos(alpha2) + Math.cos(alpha1) * Math.cos(alpha3) );
double lat3 = Math.asin( Math.sin(lat1) * Math.cos(dist13) +
Math.cos(lat1) * Math.sin(dist13) * Math.cos(bearing13) );
double dLon13 = Math.atan2( Math.sin(bearing13) * Math.sin(dist13) * Math.cos(lat1),
Math.cos(dist13) - Math.sin(lat1) * Math.sin(lat3) );
double lon3 = lon1 + dLon3;
lon3 = (lon3 + 3 * Math.PI) % ( 2* Math.PI) - Math.PI // normalize to +/-180
return new CoordD(deg(lat3), deg(lon3));
}
rad()
and deg()
are just helper functions that translate between radians and degrees. CoordD
is a helper class that just contains two double to store a lat/long point.