As stated in the title, the goal is to have a way for detecting whether a given GPS coordinate falls inside a polygon or not.
The polygon itself can be either convex
I thought similarly as shab first (his proposal is called Ray-Casting Algorithm), but had second thoughts like Spacedman:
...but all the geometry will have to be redone in spherical coordinates...
I implemented and tested the mathematically correct way of doing that, e.i. intersecting great circles and determining whether one of the two intersecting points is on both arcs. (Note: I followed the steps described here, but I found several errors: The sign
function is missing at the end of step 6 (just before arcsin
), and the final test is numerical garbage (as subtraction is badly conditioned); use rather L_1T >= max(L_1a, L_1b)
to test whether S1 is on the first arc etc.)
That also is extremely slow and a numerical nightmare (evaluates ~100 trigonometric functions, among other things); it proved not to be usable in our embedded systems.
There's a trick, though: If the area you are considering is small enough, just do a standard cartographic projection, e.g. spherical Mercator projection, of each point:
// latitude, longitude in radians
x = longitude;
y = log(tan(pi/4 + latitude/2));
Then, you can apply ray-casting, where the intersection of arcs is checked by this function:
public bool ArcsIntersecting(double x1, double y1, double x2, double y2,
double x3, double y3, double x4, double y4)
{
double vx1 = x2 - x1;
double vy1 = y2 - y1;
double vx2 = x4 - x3;
double vy2 = y4 - y3;
double denom = vx1 * vy2 - vx2 * vy1;
if (denom == 0) { return false; } // edges are parallel
double t1 = (vx2 * (y1 - y3) - vy2 * (x1 - x3)) / denom;
double t2;
if (vx2 != 0) { t2 = (x1 - x3 + t1 * vx1) / vx2; }
else if (vy2 != 0) { t2 = (y1 - y3 + t1 * vy1) / vy2; }
else { return false; } // edges are matching
return min(t1, t2) >= 0 && max(t1, t2) <= 1;
}
JavaScript Version -
{
const PI = 3.14159265;
const TWOPI = 2*PI;
function isCoordinateInsidePitch(latitude, longitude, latArray, longArray)
{
let angle=0;
let p1Lat;
let p1Long;
let p2Lat;
let p2Long;
let n = latArray.length;
for (let i = 0; i < n; i++) {
p1Lat = latArray[i] - latitude;
p1Long = longArray[i] - longitude;
p2Lat = latArray[(i+1)%n] - latitude;
p2Long = longArray[(i+1)%n] - longitude;
angle += angle2D(p1Lat,p1Long,p2Lat,p2Long);
}
return !(Math.abs(angle) < PI);
}
function angle2D(y1, x1, y2, x2)
{
let dtheta,theta1,theta2;
theta1 = Math.atan2(y1,x1);
theta2 = Math.atan2(y2,x2);
dtheta = theta2 - theta1;
while (dtheta > PI)
dtheta -= TWOPI;
while (dtheta < -PI)
dtheta += TWOPI;
return dtheta;
}
function isValidCoordinate(latitude,longitude)
{
return (
latitude !== '' && longitude !== '' && !isNaN(latitude)
&& !isNaN(longitude) && latitude > -90 &&
latitude < 90 && longitude > -180 && longitude < 180
)
}
let latArray = [32.10458, 32.10479, 32.1038, 32.10361];
let longArray = [34.86448, 34.86529, 34.86563, 34.86486];
// true
console.log(isCoordinateInsidePitch(32.104447, 34.865108,latArray, longArray));
// false
// isCoordinateInsidePitch(32.104974, 34.864576,latArray, longArray);
// true
// isValidCoordinate(0, 0)
// true
// isValidCoordinate(32.104974, 34.864576)
}