Is a point between two angles from another point? - math

I am trying to determine if a point is between two angles coming from an original point (in order to determine whether or not to draw it with OpenGL, although that is irrelevant). What is the easiest way of doing this?

If absolute value of angles CAB + BAD = 45, then point is inside. If CAB + BAD > 45, then point is outside.

The 2-dimensional cross-product of 2 vectors u = (ux, uy), v = (vx, vy), is
u x v = ux * vy - uy * vx = |u| * |v| * sin(phi)
where phi is the angle between u to v (measured from u to v). The cross-product is positive if the angle is between 0 and 180 degrees.
Therefore
(B - A) x (D - A) > 0
if B lies in the half plane "left of" the vector from A to D, and thus
(B - A) x (D - A) > 0 and (B - A) x (C - A) < 0
exactly if B lies in the sector. If you want also to catch the case that B lies on the boundary of the sector, use >= resp. <=.
(Note: This works as long as the angle of the sector at A is less than 180 degrees, and can probably generalized for greater angles. Since your angle is 45 degrees, these formulas can be used.)

If you have point coordinates and no angles then use polar coordinates to convert [X,Y] -> [R,Theta] (Radius and Angle) relative to center (A in your figure) and then compare angles (thetas).
This code converts Point to PolarPoint relative to center point:
/// <summary>
/// Converts Point to polar coordinate point
/// </summary>
public static PolarPoint PointToPolarPoint(Point center, Point point)
{
double dist = Distance(center, point);
double theta = Math.Atan2(point.Y - center.Y, point.X - center.X);
if (theta < 0) // provide 0 - 2Pi "experience"
theta = 2 * Math.PI + theta;
return new PolarPoint(dist, theta);
}
/// <summary>
/// Calculates distance between two points
/// </summary>
public static int Distance(Point p1, Point p2)
{
return (int) Math.Sqrt
(
Math.Pow(p1.X - p2.X, 2) +
Math.Pow(p1.Y - p2.Y, 2)
);
}
The Polar Point Class in C# (That includes conversion back to Point):
/* NFX by ITAdapter
* Originated: 2006.01
* Revision: NFX 0.2 2009.02.10
*/
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
namespace NFX.Geometry
{
/// <summary>
/// Represents a point with polar coordinates
/// </summary>
public struct PolarPoint
{
#region .ctor
/// <summary>
/// Initializes polar coordinates
/// </summary>
public PolarPoint(double r, double theta)
{
m_R = r;
m_Theta = 0;
Theta = theta;
}
/// <summary>
/// Initializes polar coordinates from 2-d cartesian coordinates
/// </summary>
public PolarPoint(Point center, Point point)
{
this = CartesianUtils.PointToPolarPoint(center, point);
}
#endregion
#region Private Fields
private double m_R;
private double m_Theta;
#endregion
#region Properties
/// <summary>
/// R coordinate component which is coordinate distance from point of coordinates origin
/// </summary>
public double R
{
get { return m_R; }
set { m_R = value; }
}
/// <summary>
/// Angular azimuth coordinate component. An angle must be between 0 and 2Pi.
/// Note: Due to screen Y coordinate going from top to bottom (in usual orientation)
/// Theta angle may be reversed, that is - be positive in the lower half coordinate plane.
/// Please refer to:
/// http://en.wikipedia.org/wiki/Polar_coordinates
/// </summary>
public double Theta
{
get { return m_Theta; }
set
{
if ((value < 0) || (value > Math.PI * 2))
throw new NFXException("Invalid polar coordinates angle");
m_Theta = value;
}
}
/// <summary>
/// Returns polar coordinate converted to 2-d cartesian coordinates.
/// Coordinates are relative to 0,0 of the angle base vertex
/// </summary>
public Point Point
{
get
{
int x = (int)(m_R * Math.Cos(m_Theta));
int y = (int)(m_R * Math.Sin(m_Theta));
return new Point(x, y);
}
}
#endregion
#region Operators
public static bool operator ==(PolarPoint left, PolarPoint right)
{
return (left.m_R == right.m_R) && (left.m_Theta == right.m_Theta);
}
public static bool operator !=(PolarPoint left, PolarPoint right)
{
return (left.m_R != right.m_R) || (left.m_Theta != right.m_Theta);
}
#endregion
#region Object overrides
public override bool Equals(object obj)
{
if (obj is PolarPoint)
return this==((PolarPoint)obj);
else
return false;
}
public override int GetHashCode()
{
return m_R.GetHashCode() + m_Theta.GetHashCode();
}
public override string ToString()
{
return string.Format("Distance: {0}; Angle: {1} rad.", m_R, m_Theta);
}
#endregion
}
}

I eventually got it down to this function (where cameraYR is the angle that the point A is rotated at, cameraX is A.x, cameraY is A.y, x is B.x and y is B.y):
float cameraAngle = PI + cameraYR;
float angle = PI / 2 + atan2f(cameraY - y, cameraX - x);
float anglediff = fmodf(angle - cameraAngle + PI, PI * 2) - PI;
return (anglediff <= visibleAngle && anglediff >= -visibleAngle) || (anglediff <= -PI * 2 + visibleAngle && angleDiff >= -PI * 2 - visibleAngle);

I just answered a similar question. Find if an angle is between 2 angles (which also solves your problem). Check below
Is angle in between two angles
Hope it helps

Related

find tangent line of two adjacent circle

those are 2 example cases of what I need to solve, it is just finding the coordinate of D, given position of A, and the direction vector of red and green line
red/green line vector (or direction) is known
point A is an intersection between the red line and red circle tangent point
point B is the center of the red circle with radius = R (known)
point C is an intersection between the green line and the green circle tangent point
point D is unknown and this one that needs to be calculated
point D will always located in green circle (radius of 2R from point B)
both red and green line has the same radius of R
V is the angle of the red line relative to north up
W is the angle of the green line relative to north up
the distance between point B and D is always 2R since the circle adjacent (touching each other)
much help and hint appreciated, preferred in some code instead of math equation
Having coordinates A,B,C, we can write two vector equations using scalar (dot) product:
AC.dot.DC = 0
DB.dot.DB = 4*R^2
The first one refers to perpendicularity between tangent to circle and radius to tangency point, the second one - just squared distance between circle centers.
In coordinates:
(cx-ax)*(cx-dx) + (cy-ay)*(cy-dy) = 0
(bx-dx)*(bx-dx) + (by-dy)*(by-dy) = 4*R^2
Solve this system for unknown dx, dy - two solutions in general case.
If A and C are not known, as #Mike 'Pomax' Kamermans noticed:
Let
cr = sin(v) sr = cos(v)
cg = sin(w) sg = cos(w)
So
ax = bx + R * cr
ay = by + R * sr
and
dx = cx - R * cg
dy = cy + R * sg
Substituting expressions into the system above we have:
(dx+R*cg-bx-R*cr)*cg - (dy-R*sg-by-R*sr)*sg = 0
(bx-dx)*(bx-dx) + (by-dy)*(by-dy) = 4*R^2
Again - solve system for unknowns dx, dy
As a hint: draw it out some more:
We can construct D by constructing the line segment AG, for which we know both the angle and length, because AC⟂AG, and the segment has length R.
We can then construct a line perpendicular to AG, through G, which gives us a chord on the blue circle, one endpoint of which is D. We know the distance from B to GD (because we know trigonometry) and we know that the distance BD is 2R (because that's a given). Pythagoras then trivially gives us D.
You know where A is and the angle θ it makes from vertical.
So specify the line though C called line(C) above and the offset the line by R in order to get line(D) above that goes through point D.
In C# code this is
Line line_C = Line.ThroughPointAtAngle(A, theta);
Line line_D = line_C.Offset(radius);
Now find the intersection of this line to the greater circle
Circle circle = new Circle(B, 2 * radius);
if (circle.Intersect(line_D, out Point D, alternate: false))
{
Console.WriteLine(D);
float d_BD = B.DistanceTo(D);
Console.WriteLine(d_BD);
}
else
{
Console.WriteLine("Does not intersect.");
}
This produces point D either above line(C) or below line(C) depending on the bool argument alternate.
The code example below produces the following output:
D=Point(-0.4846499,-1.94039)
|BD|=2
The source code is
Program.cs
using static Float;
static class Program
{
static void Main(string[] args)
{
float radius = 1;
Point A = new Point(-radius, 0);
Point B = new Point(0, 0);
float theta = deg(15);
Line line_C = Line.ThroughPointAtAngle(A, theta);
Line line_D = line_C.Offset(radius);
Circle circle = new Circle(B, 2 * radius);
if (circle.Intersect(line_D, out Point D, alternate: false))
{
Console.WriteLine($"D={D}");
float d_BD = B.DistanceTo(D);
Console.WriteLine($"|BD|={d_BD}");
}
else
{
Console.WriteLine("Does not intersect.");
}
}
}
Point.cs
Describes a point in cartesian space using two coordinates (x,y)
using static Float;
public readonly struct Point
{
readonly (float x, float y) data;
public Point(float x, float y)
{
this.data = (x, y);
}
public static Point Origin { get; } = new Point(0, 0);
public static Point FromTwoLines(Line line1, Line line2)
{
float x = line1.B * line2.C - line1.C * line2.B;
float y = line1.C * line2.A - line1.A * line2.C;
float w = line1.A * line2.B - line1.B * line2.A;
return new Point(x / w, y / w);
}
public float X => data.x;
public float Y => data.y;
public float SumSquares => data.x * data.x + data.y * data.y;
#region Algebra
public static Point Negate(Point a)
=> new Point(
-a.data.x,
-a.data.y);
public static Point Scale(float factor, Point a)
=> new Point(
factor * a.data.x,
factor * a.data.y);
public static Point Add(Point a, Point b)
=> new Point(
a.data.x + b.data.x,
a.data.y + b.data.y);
public static Point Subtract(Point a, Point b)
=> new Point(
a.data.x - b.data.x,
a.data.y - b.data.y);
public static float Dot(Point point, Line line)
=> line.A * point.data.x + line.B * point.data.y + line.C;
public static Point operator +(Point a, Point b) => Add(a, b);
public static Point operator -(Point a) => Negate(a);
public static Point operator -(Point a, Point b) => Subtract(a, b);
public static Point operator *(float f, Point a) => Scale(f, a);
public static Point operator *(Point a, float f) => Scale(f, a);
public static Point operator /(Point a, float d) => Scale(1 / d, a);
#endregion
#region Geometry
public Point Offset(float dx, float dy)
=> new Point(data.x + dx, data.y + dy);
public Point Offset(Vector2 delta) => Offset(delta.X, delta.Y);
public float DistanceTo(Point point)
=> sqrt(sqr(data.x - point.data.x) + sqr(data.y - point.data.y));
#endregion
#region Formatting
public string ToString(string formatting, IFormatProvider provider)
{
return $"Point({data.x.ToString(formatting, provider)},{data.y.ToString(formatting, provider)})";
}
public string ToString(string formatting)
=> ToString(formatting, null);
public override string ToString()
=> ToString("g");
#endregion
}
Line.cs
Describes a line in cartesian space using the coefficients (a,b,c) such that the equation of the line is a x + b y + c = 0
using static Float;
public readonly struct Line
{
readonly (float a, float b, float c) data;
public Line(float a, float b, float c) : this()
{
data = (a, b, c);
}
public static Line AlongX { get; } = new Line(0, 1, 0);
public static Line AlongY { get; } = new Line(-1, 0, 0);
public static Line ThroughPointAtAngle(Point point, float angle)
{
return new Line(cos(angle), -sin(angle), point.Y * sin(angle) - point.X * cos(angle));
}
public static Line ThroughTwoPoints(Point point1, Point point2)
=> new Line(
point1.Y - point2.Y,
point2.X - point1.X,
point1.X * point2.Y - point1.Y * point2.X);
public float A => data.a;
public float B => data.b;
public float C => data.c;
#region Algebra
public static float Dot(Line line, Point point)
=> line.data.a * point.X + line.data.b * point.Y + line.data.c;
#endregion
#region Geometry
public Line ParallelThrough(Point point)
{
return new Line(data.a, data.b, -data.a * point.X - data.b * point.Y);
}
public Line PerpendicularThrough(Point point)
{
return new Line(data.b, -data.a, -data.b * point.X + data.a * point.Y);
}
public Line Offset(float amount)
=> new Line(data.a, data.b, data.c - amount * sqrt(sqr(data.a) + sqr(data.b)));
public Line Offset(float dx, float dy)
=> new Line(data.a, data.b, data.c + data.a * dx + data.b * dy);
public Line Offset(Vector2 delta) => Offset(delta.X, delta.Y);
public float DistanceTo(Point point)
=> Dot(this, point) / (data.a * data.a + data.b * data.b);
#endregion
#region Formatting
public string ToString(string formatting, IFormatProvider provider)
{
return $"Line({data.a.ToString(formatting, provider)}x+{data.b.ToString(formatting, provider)}y+{data.c.ToString(formatting, provider)}=0)";
}
public string ToString(string formatting)
=> ToString(formatting, null);
public override string ToString()
=> ToString("g");
#endregion
}
Circle.cs
Describes a circle using the center and radius.
using static Float;
public readonly struct Circle
{
readonly (Point center, float radius) data;
public Circle(Point center, float radius)
{
this.data = (center, radius);
}
public static Circle FromTwoPoints(Point point1, Point point2)
{
float radius = point1.DistanceTo(point2) / 2;
Point center = (point1 + point2) / 2;
return new Circle(center, radius);
}
public static Circle FromThreePoints(Point point1, Point point2, Point point3)
{
float k_1 = point1.SumSquares / 2;
float k_2 = point2.SumSquares / 2;
float k_3 = point3.SumSquares / 2;
float dx_12 = point2.X - point1.X;
float dy_12 = point2.Y - point1.Y;
float dx_23 = point3.X - point2.X;
float dy_23 = point3.Y - point2.Y;
float det = dx_12 * dy_23 - dx_23 * dy_12;
Point center = new Point(
(dy_12 * (k_2 - k_3) + dy_23 * (k_2 - k_1)) / det,
(dx_12 * (k_3 - k_2) + dx_23 * (k_1 - k_2)) / det);
float radius = center.DistanceTo(point1);
return new Circle(center, radius);
}
public Point Center => data.center;
public float Radius => data.radius;
#region Geometry
public float DistanceTo(Point point)
=> data.center.DistanceTo(point) - data.radius;
public float DistanceTo(Line line)
{
float d = line.DistanceTo(Center);
if (d > 0)
{
return d - data.radius;
}
else
{
return d + data.radius;
}
}
public bool Intersect(Line line, out Point point, bool alternate = false)
{
line = line.Offset(-Center.X, -Center.Y);
int sign = alternate ? -1 : 1;
float discr = sqr(line.A * data.radius) + sqr(line.B * data.radius) - sqr(line.C);
if (discr >= 0)
{
float d = sign * sqrt(discr);
float ab = line.A * line.A + line.B * line.B;
point = new Point((line.B * d - line.A * line.C) / ab, -(line.A * d + line.B * line.C) / ab);
point += Center;
return true;
}
else
{
float ab = line.A * line.A + line.B * line.B;
point = new Point((-line.A * line.C) / ab, -(+line.B * line.C) / ab);
point += Center;
return false;
}
}
#endregion
#region Formatting
public string ToString(string formatting, IFormatProvider provider)
{
return $"Circle({data.center.ToString(formatting, provider)},{data.radius.ToString(formatting, provider)})";
}
public string ToString(string formatting)
=> ToString(formatting, null);
public override string ToString()
=> ToString("g");
#endregion
}
Float.cs
Helper functions dealing with float math which is lacking from System.Math.
public static class Float
{
/// <summary>
/// A factor of π.
/// </summary>
/// <param name="x">The factor.</param>
public static float pi(float x) => (float)(Math.PI * x);
/// <summary>
/// Degree to Radian conversion
/// </summary>
/// <param name="x">The angle in degrees.</param>
/// <returns>Angle in radians</returns>
public static float deg(float x) => pi(x) / 180;
/// <summary>
/// Radian to Degree conversion
/// </summary>
/// <param name="x">The angle in radians.</param>
/// <returns>Angle in degrees</returns>
public static float rad(float x) => x * 180 / pi(1);
public static float sqr(float x) => x * x;
public static float sqrt(float x) => (float)Math.Sqrt(x);
public static float sin(float x) => (float)Math.Sin(x);
public static float cos(float x) => (float)Math.Cos(x);
}
thx for all the answer (will upvote them), I did the work on it myself, I will share my result :
and here is length of d, to solve the equation :

Calculate sound value with distance

I have a more mathematical than programming question, sorry if I'm not in the right section. In my 2D game, we can move the camera on a map where there are objects that can emit sound, and this sound volume (defined by a float from 0 to 1) must increase when the screen center is near this object. For example, when the object is at the screen center, the sound volume is 1, and when we move away, the volume must decrease. Each object has its own scope value. (for example 1000 pixels).
I don't know how to write a method that can calculate it.
Here is some of my code (which is not the right calculation) :
private function setVolumeWithDistance():Void
{
sound.volume = getDistanceFromScreenCenter() / range;
// So the volume is a 0 to 1 float, the range is the scope in pixels and
// and the getDistanceFromScreenCenter() is the distance in pixels
}
I already have the method which calculates the distance of the object from the center screen :
public function getDistanceFromScreenCenter():Float
{
return Math.sqrt(Math.pow((Cameraman.getInstance().getFocusPosition().x - position.x), 2) +
Math.pow((Cameraman.getInstance().getFocusPosition().y - position.y), 2));
Simple acoustics can help.
Here is the formula for sound intensity from a point source. It follows an inverse square of distance rule. Build that into your code.
You need to consider the mapping between global and screen coordinates. You have to map pixel location on the screen to physical coordinates and back.
Your distance code is flawed. No one should use pow() to square numbers. Yours is susceptible to round off errors.
This code combines the distance calculation, done properly, and attempts to solve the inverse square intensity calculation. Note: Inverse square is singular for zero distance.
package physics;
/**
* Simple model for an acoustic point source
* Created by Michael
* Creation date 1/16/2016.
* #link https://stackoverflow.com/questions/34827629/calculate-sound-value-with-distance/34828300?noredirect=1#comment57399595_34828300
*/
public class AcousticPointSource {
// Units matter here....
private static final double DEFAULT_REFERENCE_INTENSITY = 0.01;
private static final double DEFAULT_REFERENCE_DISTANCE = 1.0;
// Units matter here...
private double referenceDistance;
private double referenceIntensity;
public static void main(String[] args) {
int numPoints = 20;
double x = 0.0;
double dx = 0.05;
AcousticPointSource source = new AcousticPointSource();
for (int i = 0; i < numPoints; ++i) {
x += dx;
Point p = new Point(x);
System.out.println(String.format("point %s intensity %-10.6f", p, source.intensity(p)));
}
}
public AcousticPointSource() {
this(DEFAULT_REFERENCE_DISTANCE, DEFAULT_REFERENCE_INTENSITY);
}
public AcousticPointSource(double referenceDistance, double referenceIntensity) {
if (referenceDistance <= 0.0) throw new IllegalArgumentException("distance must be positive");
if (referenceIntensity <= 0.0) throw new IllegalArgumentException("intensity must be positive");
this.referenceDistance = referenceDistance;
this.referenceIntensity = referenceIntensity;
}
public double distance2D(Point p1) {
return distance2D(p1, Point.ZERO);
}
public double distance2D(Point p1, Point p2) {
double distance = 0.0;
if ((p1 != null) && (p2 != null)) {
double dx = Math.abs(p1.x - p2.x);
double dy = Math.abs(p1.y - p2.y);
double ratio;
if (dx > dy) {
ratio = dy/dx;
distance = dx;
} else {
ratio = dx/dy;
distance = dy;
}
distance *= Math.sqrt(1.0 + ratio*ratio);
if (Double.isNaN(distance)) {
distance = 0.0;
}
}
return distance;
}
public double intensity(Point p) {
double intensity = 0.0;
if (p != null) {
double distance = distance2D(p);
if (distance != 0.0) {
double ratio = this.referenceDistance/distance;
intensity = this.referenceIntensity*ratio*ratio;
}
}
return intensity;
}
}
class Point {
public static final Point ZERO = new Point(0.0, 0.0, 0.0);
public final double x;
public final double y;
public final double z;
public Point(double x) {
this(x, 0.0, 0.0);
}
public Point(double x, double y) {
this(x, y, 0.0);
}
public Point(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
#Override
public String toString() {
return String.format("(%-10.4f,%-10.4f,%-10.4f)", x, y, z);
}
}

Telerik Map + WMS

I am integrating a WMS with Telerik map. In the Telerik forums, I found some sample code that works correctly until the zoom is great and performing the calculation is incorrect because MaxX and MinX returns the same value and Miny and Maxy returns the same value.
I do not quite understand the functioning of QuadKey, BBox, Tilex, Tiley ... and so as not correct the code. Here I put the sample code provided in your forum telerik.
See if someone sees where this error.
public class WMSCustomSource : TiledMapSource
{
private const string TileUrlFormat = #"http://www1.sedecatastro.gob.es/Cartografia/WMS/ServidorWMS.aspx?SERVICE=WMS&REQUEST=GetMap&SRS=EPSG:4326&BBOX={0},{1},{2},{3}&WIDTH={4}&HEIGHT={4}&Layers=Catastro&TRANSPARENT=TRUE&STYLES=PositionStyle&FORMAT=image/png";
private const int TileSize = 256;
/// <summary>
/// Earth Circumference.
/// </summary>
private double earthCircumference;
private double halfEarthCircumference;
private double earthRadius;
/// <summary>
/// Initializes a new instance of the OSMCustomSource class.
/// </summary>
public WMSCustomSource(ISpatialReference spatialReference)
: base(1, 20, TileSize, TileSize)
{
this.earthRadius = spatialReference.SpheroidRadius;
this.earthCircumference = this.earthRadius * 2 * Math.PI;
this.halfEarthCircumference = this.earthCircumference / 2d;
}
/// <summary>
/// Initialize provider.
/// </summary>
public override void Initialize()
{
// Raise provider intialized event.
this.RaiseIntializeCompleted();
}
/// <summary>
/// Returns the bounding BBox for a grid square represented by the given quad key
/// </summary>
/// <param name="quadKey"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="zoomLevel"></param>
/// <returns></returns>
private BBOX ConvertQuadKeyToBBox(string quadKey, int x, int y, int zoomLevel)
{
char c = quadKey[0];
int tileSize = 2 << (18 - zoomLevel - 1);
if (c == '0')
{
y = y - tileSize;
}
else if (c == '1')
{
y = y - tileSize;
x = x + tileSize;
}
else if (c == '3')
{
x = x + tileSize;
}
if (quadKey.Length > 1)
{
return ConvertQuadKeyToBBox(quadKey.Substring(1), x, y, zoomLevel + 1);
}
return new BBOX(x, y, tileSize, tileSize);
}
private BBOX ConvertQuadKeyToBBox(string quadKey)
{
const int x = 0;
const int y = 262144;
return ConvertQuadKeyToBBox(quadKey, x, y, 1);
}
/// <summary>
/// Converts radians to degrees
/// </summary>
/// <param name="d"></param>
/// <returns></returns>
private double RadiansToDegrees(double radians)
{
return radians / Math.PI * 180d;
}
/// <summary>
/// Converts a grid row to Latitude
/// </summary>
/// <param name="y"></param>
/// <param name="zoom"></param>
/// <returns></returns>
private double ConvertYToLatitude(int y, int zoom)
{
double arc = this.earthCircumference / ((double)(1 << zoom) * (double)TileSize);
double metersY = this.halfEarthCircumference - ((double)y * (double)TileSize * arc);
double a = Math.Exp(metersY * 2d / this.earthRadius);
double result = RadiansToDegrees(Math.Asin((a - 1d) / (a + 1d)));
return result;
}
/// <summary>
/// Converts a grid column to Longitude
/// </summary>
/// <param name="x"></param>
/// <param name="zoom"></param>
/// <returns></returns>
private double ConvertXToLongitude(int x, int zoom)
{
double arc = this.earthCircumference / ((double)(1 << zoom) * (double)TileSize);
double metersX = ((double)x * (double)TileSize * arc) - this.halfEarthCircumference;
double result = RadiansToDegrees(metersX / this.earthRadius);
return result;
}
private static string GetQuadKey(int tileX, int tileY, int levelOfDetail)
{
var quadKey = new StringBuilder();
for (int i = levelOfDetail; i > 0; i--)
{
char digit = '0';
int mask = 1 << (i - 1);
if ((tileX & mask) != 0)
{
digit++;
}
if ((tileY & mask) != 0)
{
digit++;
digit++;
}
quadKey.Append(digit);
}
return quadKey.ToString();
}
/// <summary>
/// Gets the image URI.
/// </summary>
/// <param name="tileLevel">Tile level.</param>
/// <param name="tilePositionX">Tile X.</param>
/// <param name="tilePositionY">Tile Y.</param>
/// <returns>URI of image.</returns>
protected override Uri GetTile(int tileLevel, int tilePositionX, int tilePositionY)
{
int zoomLevel = ConvertTileToZoomLevel(tileLevel);
string quadKey = GetQuadKey(tilePositionX, tilePositionY, zoomLevel);
BBOX boundingBox = ConvertQuadKeyToBBox(quadKey);
**double longitude = ConvertXToLongitude(boundingBox.x, 18);
double latitude = ConvertYToLatitude(boundingBox.y, 18);
double longitude2 = ConvertXToLongitude(boundingBox.x + boundingBox.width, 18);
double latitude2 = ConvertYToLatitude(boundingBox.y - boundingBox.height, 18);**
string url = string.Format(CultureInfo.InvariantCulture, TileUrlFormat,
longitude, latitude, longitude2, latitude2, TileSize);
return new Uri(url);
}
/// <summary>
/// The box of the bounds of a tile
/// </summary>
private class BBOX
{
public int x;
public int y;
public int width;
public int height;
public BBOX(int x, int y, int width, int height)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
}
}
The functions that I return the same values ​​when the zoom is high are:
ConvertYToLatitude and ConvertXToLongitude. Although the value I provide to X1 and X2 are different. And the value of Y1 and Y2 are also different.
I do not know if it can be a problem with decimal and double.
Thanks and sorry for my English
I found the solution!
I use another function found in http://alastaira.wordpress.com/2011/01/07/accessing-a-wms-tile-server-from-bing-maps-v7/ to convert the QuadKey in BBox
The final class will read:
public class WMSCustomSource : TiledMapSource
{
private const string TileUrlFormat = #"http://www1.sedecatastro.gob.es/Cartografia/WMS/ServidorWMS.aspx?SERVICE=WMS&REQUEST=GetMap&SRS=EPSG:4326&BBOX={0}&WIDTH={1}&HEIGHT={1}&Layers=Catastro&TRANSPARENT=TRUE&STYLES=PositionStyle&FORMAT=image/png";
private const int TileSize = 256;
/// <summary>
/// Earth Circumference.
/// </summary>
private double earthCircumference;
private double halfEarthCircumference;
private double earthRadius;
/// <summary>
/// Initializes a new instance of the OSMCustomSource class.
/// </summary>
public WMSCustomSource(ISpatialReference spatialReference)
: base(1, 20, TileSize, TileSize)
{
this.earthRadius = spatialReference.SpheroidRadius;
this.earthCircumference = this.earthRadius * 2 * Math.PI;
this.halfEarthCircumference = this.earthCircumference / 2d;
}
/// <summary>
/// Initialize provider.
/// </summary>
public override void Initialize()
{
// Raise provider intialized event.
this.RaiseIntializeCompleted();
}
public string QuadKeyToBBox(string quadKey)
{
int zoom = quadKey.Length;
int x = 0, y = 0;
// Work out the x and y position of this tile
for (int i = zoom; i > 0; i--)
{
int mask = 1 << (i - 1);
switch (quadKey[zoom - i])
{
case '0':
break;
case '1':
x |= mask;
break;
case '2':
y |= mask;
break;
case '3':
x |= mask;
y |= mask;
break;
default:
throw new ArgumentException("Invalid QuadKey digit sequence.");
}
}
// From the grid position and zoom, work out the min and max Latitude / Longitude values of this tile
double W = (float)(x * TileSize) * 360 / (float)(TileSize * Math.Pow(2, zoom)) - 180;
double N = (float)Math.Asin((Math.Exp((0.5 - (y * TileSize) / (TileSize) / Math.Pow(2, zoom)) * 4 * Math.PI) - 1) / (Math.Exp((0.5 - (y * TileSize) / 256 / Math.Pow(2, zoom)) * 4 * Math.PI) + 1)) * 180 / (float)Math.PI;
double E = (float)((x + 1) * TileSize) * 360 / (float)(TileSize * Math.Pow(2, zoom)) - 180;
double S = (float)Math.Asin((Math.Exp((0.5 - ((y + 1) * TileSize) / (TileSize) / Math.Pow(2, zoom)) * 4 * Math.PI) - 1) / (Math.Exp((0.5 - ((y + 1) * TileSize) / 256 / Math.Pow(2, zoom)) * 4 * Math.PI) + 1)) * 180 / (float)Math.PI;
double[] bounds = new double[] { W, S, E, N };
// Return a comma-separated string of the bounding coordinates
return string.Join(",", Array.ConvertAll(bounds, s => s.ToString().Replace(',','.')));
}
/// <summary>
/// Converts radians to degrees
/// </summary>
/// <param name="d"></param>
/// <returns></returns>
private double RadiansToDegrees(double radians)
{
return radians / Math.PI * 180d;
}
/// <summary>
/// Converts a grid row to Latitude
/// </summary>
/// <param name="y"></param>
/// <param name="zoom"></param>
/// <returns></returns>
private double ConvertYToLatitude(int y, int zoom)
{
double arc = this.earthCircumference / ((double)(1 << zoom) * (double)TileSize);
double metersY = this.halfEarthCircumference - ((double)y * (double)TileSize * arc);
double a = Math.Exp(metersY * 2d / this.earthRadius);
double result = RadiansToDegrees(Math.Asin((a - 1d) / (a + 1d)));
return result;
}
/// <summary>
/// Converts a grid column to Longitude
/// </summary>
/// <param name="x"></param>
/// <param name="zoom"></param>
/// <returns></returns>
private double ConvertXToLongitude(int x, int zoom)
{
double arc = this.earthCircumference / ((double)(1 << zoom) * (double)TileSize);
double metersX = ((double)x * (double)TileSize * arc) - this.halfEarthCircumference;
double result = RadiansToDegrees(metersX / this.earthRadius);
return result;
}
private static string GetQuadKey(int tileX, int tileY, int levelOfDetail)
{
var quadKey = new StringBuilder();
for (int i = levelOfDetail; i > 0; i--)
{
char digit = '0';
int mask = 1 << (i - 1);
if ((tileX & mask) != 0)
{
digit++;
}
if ((tileY & mask) != 0)
{
digit++;
digit++;
}
quadKey.Append(digit);
}
return quadKey.ToString();
}
/// <summary>
/// Gets the image URI.
/// </summary>
/// <param name="tileLevel">Tile level.</param>
/// <param name="tilePositionX">Tile X.</param>
/// <param name="tilePositionY">Tile Y.</param>
/// <returns>URI of image.</returns>
protected override Uri GetTile(int tileLevel, int tilePositionX, int tilePositionY)
{
int zoomLevel = ConvertTileToZoomLevel(tileLevel);
string quadKey = GetQuadKey(tilePositionX, tilePositionY, zoomLevel);
**string bbox = QuadKeyToBBox(quadKey);**
string url = string.Format(CultureInfo.InvariantCulture, TileUrlFormat, bbox, TileSize);
return new Uri(url);
}
}

Interpolating values between interval, interpolation as per Bezier curve

To implement a 2D animation I am looking for interpolating values between two key frames with the velocity of change defined by a Bezier curve. The problem is Bezier curve is represented in parametric form whereas requirement is to be able to evaluate the value for a particular time.
To elaborate, lets say the value of 10 and 40 is to be interpolated across 4 seconds with the value changing not constantly but as defined by a bezier curve represented as 0,0 0.2,0.3 0.5,0.5 1,1.
Now if I am drawing at 24 frames per second, I need to evaluate the value for every frame. How can I do this ? I looked at De Casteljau algorithm and thought that dividing the curve into 24*4 pieces for 4 seconds would solve my problem but that sounds erroneous as time is along the "x" axis and not along the curve.
To further simplify
If I draw the curve in a plane, the x axis represents the time and the y axis the value I am looking for. What I actually require is to to be able to find out "y" corresponding to "x". Then I can divide x in 24 divisions and know the value for each frame
I was facing the same problem: Every animation package out there seems to use Bézier curves to control values over time, but there is no information out there on how to implement a Bézier curve as a y(x) function. So here is what I came up with.
A standard cubic Bézier curve in 2D space can be defined by the four points P0=(x0, y0) .. P3=(x3, y3).
P0 and P3 are the end points of the curve, while P1 and P2 are the handles affecting its shape. Using a parameter t ϵ [0, 1], the x and y coordinates for any given point along the curve can then be determined using the equations
A) x = (1-t)3x0 + 3t(1-t)2x1 + 3t2(1-t)x2 + t3x3 and
B) y = (1-t)3y0 + 3t(1-t)2y1 + 3t2(1-t)y2 + t3y3.
What we want is a function y(x) that, given an x coordinate, will return the corresponding y coordinate of the curve. For this to work, the curve must move monotonically from left to right, so that it doesn't occupy the same x coordinate more than once on different y positions. The easiest way to ensure this is to restrict the input points so that x0 < x3 and x1, x2 ϵ [x0, x3]. In other words, P0 must be to the left of P3 with the two handles between them.
In order to calculate y for a given x, we must first determine t from x. Getting y from t is then a simple matter of applying t to equation B.
I see two ways of determining t for a given y.
First, you might try a binary search for t. Start with a lower bound of 0 and an upper bound of 1 and calculate x for these values for t via equation A. Keep bisecting the interval until you get a reasonably close approximation. While this should work fine, it will neither be particularly fast nor very precise (at least not both at once).
The second approach is to actually solve equation A for t. That's a bit tough to implement because the equation is cubic. On the other hand, calculation becomes really fast and yields precise results.
Equation A can be rewritten as
(-x0+3x1-3x2+x3)t3 + (3x0-6x1+3x2)t2 + (-3x0+3x1)t + (x0-x) = 0.
Inserting your actual values for x0..x3, we get a cubic equation of the form at3 + bt2 + c*t + d = 0 for which we know there is only one solution within [0, 1]. We can now solve this equation using an algorithm like the one posted in this Stack Overflow answer.
The following is a little C# class demonstrating this approach. It should be simple enough to convert it to a language of your choice.
using System;
public class Point {
public Point(double x, double y) {
X = x;
Y = y;
}
public double X { get; private set; }
public double Y { get; private set; }
}
public class BezierCurve {
public BezierCurve(Point p0, Point p1, Point p2, Point p3) {
P0 = p0;
P1 = p1;
P2 = p2;
P3 = p3;
}
public Point P0 { get; private set; }
public Point P1 { get; private set; }
public Point P2 { get; private set; }
public Point P3 { get; private set; }
public double? GetY(double x) {
// Determine t
double t;
if (x == P0.X) {
// Handle corner cases explicitly to prevent rounding errors
t = 0;
} else if (x == P3.X) {
t = 1;
} else {
// Calculate t
double a = -P0.X + 3 * P1.X - 3 * P2.X + P3.X;
double b = 3 * P0.X - 6 * P1.X + 3 * P2.X;
double c = -3 * P0.X + 3 * P1.X;
double d = P0.X - x;
double? tTemp = SolveCubic(a, b, c, d);
if (tTemp == null) return null;
t = tTemp.Value;
}
// Calculate y from t
return Cubed(1 - t) * P0.Y
+ 3 * t * Squared(1 - t) * P1.Y
+ 3 * Squared(t) * (1 - t) * P2.Y
+ Cubed(t) * P3.Y;
}
// Solves the equation ax³+bx²+cx+d = 0 for x ϵ ℝ
// and returns the first result in [0, 1] or null.
private static double? SolveCubic(double a, double b, double c, double d) {
if (a == 0) return SolveQuadratic(b, c, d);
if (d == 0) return 0;
b /= a;
c /= a;
d /= a;
double q = (3.0 * c - Squared(b)) / 9.0;
double r = (-27.0 * d + b * (9.0 * c - 2.0 * Squared(b))) / 54.0;
double disc = Cubed(q) + Squared(r);
double term1 = b / 3.0;
if (disc > 0) {
double s = r + Math.Sqrt(disc);
s = (s < 0) ? -CubicRoot(-s) : CubicRoot(s);
double t = r - Math.Sqrt(disc);
t = (t < 0) ? -CubicRoot(-t) : CubicRoot(t);
double result = -term1 + s + t;
if (result >= 0 && result <= 1) return result;
} else if (disc == 0) {
double r13 = (r < 0) ? -CubicRoot(-r) : CubicRoot(r);
double result = -term1 + 2.0 * r13;
if (result >= 0 && result <= 1) return result;
result = -(r13 + term1);
if (result >= 0 && result <= 1) return result;
} else {
q = -q;
double dum1 = q * q * q;
dum1 = Math.Acos(r / Math.Sqrt(dum1));
double r13 = 2.0 * Math.Sqrt(q);
double result = -term1 + r13 * Math.Cos(dum1 / 3.0);
if (result >= 0 && result <= 1) return result;
result = -term1 + r13 * Math.Cos((dum1 + 2.0 * Math.PI) / 3.0);
if (result >= 0 && result <= 1) return result;
result = -term1 + r13 * Math.Cos((dum1 + 4.0 * Math.PI) / 3.0);
if (result >= 0 && result <= 1) return result;
}
return null;
}
// Solves the equation ax² + bx + c = 0 for x ϵ ℝ
// and returns the first result in [0, 1] or null.
private static double? SolveQuadratic(double a, double b, double c) {
double result = (-b + Math.Sqrt(Squared(b) - 4 * a * c)) / (2 * a);
if (result >= 0 && result <= 1) return result;
result = (-b - Math.Sqrt(Squared(b) - 4 * a * c)) / (2 * a);
if (result >= 0 && result <= 1) return result;
return null;
}
private static double Squared(double f) { return f * f; }
private static double Cubed(double f) { return f * f * f; }
private static double CubicRoot(double f) { return Math.Pow(f, 1.0 / 3.0); }
}
You have a few options:
Let's say your curve function F(t) takes a parameter t that ranges from 0 to 1 where F(0) is the beginning of the curve and F(1) is the end of the curve.
You could animate motion along the curve by incrementing t at a constant change per unit of time.
So t is defined by function T(time) = Constant*time
For example, if your frame is 1/24th of a second, and you want to move along the curve at a rate of 0.1 units of t per second, then each frame you increment t by 0.1 (t/s) * 1/24 (sec/frame).
A drawback here is that your actual speed or distance traveled per unit time will not be constant. It will depends on the positions of your control points.
If you want to scale speed along the curve uniformly you can modify the constant change in t per unit time. However, if you want speeds to vary dramatically you will find it difficult to control the shape of the curve. If you want the velocity at one endpoint to be much larger, you must move the control point further away, which in turn pulls the shape of the curve towards that point. If this is a problem, you may consider using a non constant function for t. There are a variety of approaches with different trade-offs, and we need to know more details about your problem to suggest a solution. For example, in the past I have allowed users to define the speed at each keyframe and used a lookup table to translate from time to parameter t such that there is a linear change in speed between keyframe speeds (it's complicated).
Another common hangup: If you are animating by connecting several Bezier curves, and you want the velocity to be continuous when moving between curves, then you will need to constrain your control points so they are symmetrical with the adjacent curve. Catmull-Rom splines are a common approach.
I've answered a similar question here. Basically if you know the control points before hand then you can transform the f(t) function into a y(x) function. To not have to do it all by hand you can use services like Wolfram Alpha to help you with the math.

Detecting whether a GPS coordinate falls within a polygon on a map

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 or concave. It's defined as a set of edge vectors and a known point within that polygon. Each edge vector is further defined by four coordinates which are the latitudes and longitudes of respective tip points and a bearing relative to the starting point.
There are a couple of questions similar to this one here on StackOverflow but they describe the solution only in general terms and for a 2D plane, whereas I am looking for an existing implementation that supports polygons defined by latitude/longitude pairs in WGS 84.
What API-s or services are out there for doing such collision tests?
Here is a java program which uses a function that will return true if a latitude/longitude is found inside of a polygon defined by a list of lat/longs, with demonstration for the state of florida.
I'm not sure if it deals with the fact that the lat/long GPS system is not an x/y coordinate plane. For my uses I have demonstrated that it works (I think if you specify enough points in the bounding box, it washes away the effect that the earth is a sphere, and that straight lines between two points on the earth is not an arrow straight line.
First specify the points that make up the corner points of the polygon, it can have concave and convex corners. The coordinates I use below traces the perimeter of the state of Florida.
method coordinate_is_inside_polygon utilizes an algorithm I don't quite understand. Here is an official explanation from the source where I got it:
"... solution forwarded by Philippe Reverdy is to compute the sum of the angles made between the test point and each pair of points making up the polygon. If this sum is 2pi then the point is an interior point, if 0 then the point is an exterior point. This also works for polygons with holes given the polygon is defined with a path made up of coincident edges into and out of the hole as is common practice in many CAD packages. "
My unit tests show it does work reliably, even when the bounding box is a 'C' shape or even shaped like a Torus. (My unit tests test many points inside Florida and make sure the function returns true. And I pick a number of coordinates everywhere else in the world and make sure it returns false. I pick places all over the world which might confuse it.
I'm not sure this will work if the polygon bounding box crosses the equator, prime meridian, or any area where the coordinates change from -180 -> 180, -90 -> 90. Or your polygon wraps around the earth around the north/south poles. For me, I only need it to work for the perimeter of Florida. If you have to define a polygon that spans the earth or crosses these lines, you could work around it by making two polygons, one representing the area on one side of the meridian and one representing the area on the other side and testing if your point is in either of those points.
Here is where I found this algorithm: Determining if a point lies on the interior of a polygon - Solution 2
Run it for yourself to double check it.
Put this in a file called Runner.java
import java.util.ArrayList;
public class Runner
{
public static double PI = 3.14159265;
public static double TWOPI = 2*PI;
public static void main(String[] args) {
ArrayList<Double> lat_array = new ArrayList<Double>();
ArrayList<Double> long_array = new ArrayList<Double>();
//This is the polygon bounding box, if you plot it,
//you'll notice it is a rough tracing of the parameter of
//the state of Florida starting at the upper left, moving
//clockwise, and finishing at the upper left corner of florida.
ArrayList<String> polygon_lat_long_pairs = new ArrayList<String>();
polygon_lat_long_pairs.add("31.000213,-87.584839");
//lat/long of upper left tip of florida.
polygon_lat_long_pairs.add("31.009629,-85.003052");
polygon_lat_long_pairs.add("30.726726,-84.838257");
polygon_lat_long_pairs.add("30.584962,-82.168579");
polygon_lat_long_pairs.add("30.73617,-81.476441");
//lat/long of upper right tip of florida.
polygon_lat_long_pairs.add("29.002375,-80.795288");
polygon_lat_long_pairs.add("26.896598,-79.938355");
polygon_lat_long_pairs.add("25.813738,-80.059204");
polygon_lat_long_pairs.add("24.93028,-80.454712");
polygon_lat_long_pairs.add("24.401135,-81.817017");
polygon_lat_long_pairs.add("24.700927,-81.959839");
polygon_lat_long_pairs.add("24.950203,-81.124878");
polygon_lat_long_pairs.add("26.0015,-82.014771");
polygon_lat_long_pairs.add("27.833247,-83.014527");
polygon_lat_long_pairs.add("28.8389,-82.871704");
polygon_lat_long_pairs.add("29.987293,-84.091187");
polygon_lat_long_pairs.add("29.539053,-85.134888");
polygon_lat_long_pairs.add("30.272352,-86.47522");
polygon_lat_long_pairs.add("30.281839,-87.628784");
//Convert the strings to doubles.
for(String s : polygon_lat_long_pairs){
lat_array.add(Double.parseDouble(s.split(",")[0]));
long_array.add(Double.parseDouble(s.split(",")[1]));
}
//prints TRUE true because the lat/long passed in is
//inside the bounding box.
System.out.println(coordinate_is_inside_polygon(
25.7814014D,-80.186969D,
lat_array, long_array));
//prints FALSE because the lat/long passed in
//is Not inside the bounding box.
System.out.println(coordinate_is_inside_polygon(
25.831538D,-1.069338D,
lat_array, long_array));
}
public static boolean coordinate_is_inside_polygon(
double latitude, double longitude,
ArrayList<Double> lat_array, ArrayList<Double> long_array)
{
int i;
double angle=0;
double point1_lat;
double point1_long;
double point2_lat;
double point2_long;
int n = lat_array.size();
for (i=0;i<n;i++) {
point1_lat = lat_array.get(i) - latitude;
point1_long = long_array.get(i) - longitude;
point2_lat = lat_array.get((i+1)%n) - latitude;
//you should have paid more attention in high school geometry.
point2_long = long_array.get((i+1)%n) - longitude;
angle += Angle2D(point1_lat,point1_long,point2_lat,point2_long);
}
if (Math.abs(angle) < PI)
return false;
else
return true;
}
public static double Angle2D(double y1, double x1, double y2, double x2)
{
double 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);
}
public static boolean is_valid_gps_coordinate(double latitude,
double longitude)
{
//This is a bonus function, it's unused, to reject invalid lat/longs.
if (latitude > -90 && latitude < 90 &&
longitude > -180 && longitude < 180)
{
return true;
}
return false;
}
}
Demon magic needs to be unit-tested. Put this in a file called MainTest.java to verify it works for you
import java.util.ArrayList;
import org.junit.Test;
import static org.junit.Assert.*;
public class MainTest {
#Test
public void test_lat_long_in_bounds(){
Runner r = new Runner();
//These make sure the lat/long passed in is a valid gps
//lat/long coordinate. These should be valid.
assertTrue(r.is_valid_gps_coordinate(25, -82));
assertTrue(r.is_valid_gps_coordinate(-25, -82));
assertTrue(r.is_valid_gps_coordinate(25, 82));
assertTrue(r.is_valid_gps_coordinate(-25, 82));
assertTrue(r.is_valid_gps_coordinate(0, 0));
assertTrue(r.is_valid_gps_coordinate(89, 179));
assertTrue(r.is_valid_gps_coordinate(-89, -179));
assertTrue(r.is_valid_gps_coordinate(89.999, 179));
//If your bounding box crosses the equator or prime meridian,
then you have to test for those situations still work.
}
#Test
public void realTest_for_points_inside()
{
ArrayList<Double> lat_array = new ArrayList<Double>();
ArrayList<Double> long_array = new ArrayList<Double>();
ArrayList<String> polygon_lat_long_pairs = new ArrayList<String>();
//upper left tip of florida.
polygon_lat_long_pairs.add("31.000213,-87.584839");
polygon_lat_long_pairs.add("31.009629,-85.003052");
polygon_lat_long_pairs.add("30.726726,-84.838257");
polygon_lat_long_pairs.add("30.584962,-82.168579");
polygon_lat_long_pairs.add("30.73617,-81.476441");
//upper right tip of florida.
polygon_lat_long_pairs.add("29.002375,-80.795288");
polygon_lat_long_pairs.add("26.896598,-79.938355");
polygon_lat_long_pairs.add("25.813738,-80.059204");
polygon_lat_long_pairs.add("24.93028,-80.454712");
polygon_lat_long_pairs.add("24.401135,-81.817017");
polygon_lat_long_pairs.add("24.700927,-81.959839");
polygon_lat_long_pairs.add("24.950203,-81.124878");
polygon_lat_long_pairs.add("26.0015,-82.014771");
polygon_lat_long_pairs.add("27.833247,-83.014527");
polygon_lat_long_pairs.add("28.8389,-82.871704");
polygon_lat_long_pairs.add("29.987293,-84.091187");
polygon_lat_long_pairs.add("29.539053,-85.134888");
polygon_lat_long_pairs.add("30.272352,-86.47522");
polygon_lat_long_pairs.add("30.281839,-87.628784");
for(String s : polygon_lat_long_pairs){
lat_array.add(Double.parseDouble(s.split(",")[0]));
long_array.add(Double.parseDouble(s.split(",")[1]));
}
Runner r = new Runner();
ArrayList<String> pointsInside = new ArrayList<String>();
pointsInside.add("30.82112,-87.255249");
pointsInside.add("30.499804,-86.8927");
pointsInside.add("29.96826,-85.036011");
pointsInside.add("30.490338,-83.981323");
pointsInside.add("29.825395,-83.344116");
pointsInside.add("30.215406,-81.828003");
pointsInside.add("29.299813,-82.728882");
pointsInside.add("28.540135,-81.212769");
pointsInside.add("27.92065,-82.619019");
pointsInside.add("28.143691,-81.740113");
pointsInside.add("27.473186,-80.718384");
pointsInside.add("26.769154,-81.729126");
pointsInside.add("25.853292,-80.223999");
pointsInside.add("25.278477,-80.707398");
pointsInside.add("24.571105,-81.762085"); //bottom tip of keywest
pointsInside.add("24.900388,-80.663452");
pointsInside.add("24.680963,-81.366577");
for(String s : pointsInside)
{
assertTrue(r.coordinate_is_inside_polygon(
Double.parseDouble(s.split(",")[0]),
Double.parseDouble(s.split(",")[1]),
lat_array, long_array));
}
}
#Test
public void realTest_for_points_outside()
{
ArrayList<Double> lat_array = new ArrayList<Double>();
ArrayList<Double> long_array = new ArrayList<Double>();
ArrayList<String> polygon_lat_long_pairs = new ArrayList<String>();
//upper left tip, florida.
polygon_lat_long_pairs.add("31.000213,-87.584839");
polygon_lat_long_pairs.add("31.009629,-85.003052");
polygon_lat_long_pairs.add("30.726726,-84.838257");
polygon_lat_long_pairs.add("30.584962,-82.168579");
polygon_lat_long_pairs.add("30.73617,-81.476441");
//upper right tip, florida.
polygon_lat_long_pairs.add("29.002375,-80.795288");
polygon_lat_long_pairs.add("26.896598,-79.938355");
polygon_lat_long_pairs.add("25.813738,-80.059204");
polygon_lat_long_pairs.add("24.93028,-80.454712");
polygon_lat_long_pairs.add("24.401135,-81.817017");
polygon_lat_long_pairs.add("24.700927,-81.959839");
polygon_lat_long_pairs.add("24.950203,-81.124878");
polygon_lat_long_pairs.add("26.0015,-82.014771");
polygon_lat_long_pairs.add("27.833247,-83.014527");
polygon_lat_long_pairs.add("28.8389,-82.871704");
polygon_lat_long_pairs.add("29.987293,-84.091187");
polygon_lat_long_pairs.add("29.539053,-85.134888");
polygon_lat_long_pairs.add("30.272352,-86.47522");
polygon_lat_long_pairs.add("30.281839,-87.628784");
for(String s : polygon_lat_long_pairs)
{
lat_array.add(Double.parseDouble(s.split(",")[0]));
long_array.add(Double.parseDouble(s.split(",")[1]));
}
Runner r = new Runner();
ArrayList<String> pointsOutside = new ArrayList<String>();
pointsOutside.add("31.451159,-87.958374");
pointsOutside.add("31.319856,-84.607544");
pointsOutside.add("30.868282,-84.717407");
pointsOutside.add("31.338624,-81.685181");
pointsOutside.add("29.452991,-80.498657");
pointsOutside.add("26.935783,-79.487915");
pointsOutside.add("25.159207,-79.916382");
pointsOutside.add("24.311058,-81.17981");
pointsOutside.add("25.149263,-81.838989");
pointsOutside.add("27.726326,-83.695679");
pointsOutside.add("29.787263,-87.024536");
pointsOutside.add("29.205877,-62.102052");
pointsOutside.add("14.025751,-80.690919");
pointsOutside.add("29.029276,-90.805666");
pointsOutside.add("-12.606032,-70.151369");
pointsOutside.add("-56.520716,-172.822269");
pointsOutside.add("-75.89666,9.082024");
pointsOutside.add("-24.078567,142.675774");
pointsOutside.add("84.940737,177.480462");
pointsOutside.add("47.374545,9.082024");
pointsOutside.add("25.831538,-1.069338");
pointsOutside.add("0,0");
for(String s : pointsOutside){
assertFalse(r.coordinate_is_inside_polygon(
Double.parseDouble(s.split(",")[0]),
Double.parseDouble(s.split(",")[1]), lat_array, long_array));
}
}
}
//The list of lat/long inside florida bounding box all return true.
//The list of lat/long outside florida bounding box all return false.
I used eclipse IDE to get this to run java using java 1.6.0. For me all the unit tests pass. You need to include the junit 4 jar file in your classpath or import it into Eclipse.
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;
}
If you have WGS84 coordinates on the sphere, then your polygon divides the sphere into two areas - how do we know which area is 'inside' and which is 'outside' the polygon? The question is essentially meaningless!
For example, suppose the polygon formed the line of the equator - is the Northern hemisphere 'in' or 'out'?
From memory, the way to determine whether a point lies within a polygon is to imagine drawing a line from the position to some far away point. You then count the number of intersections between the line and the line segments of the polygon. If it count is even, then it does not lie within the polygon. If it is false, then it does lie within the polygon.
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)
}
Assuming you handle the case of wrapping around the meridian and crossing the equator (by adding offsets) - can't you just treat this as a simple 2d point in polygon ?
Here is the algorithm written in Go:
It takes point coordinates in [lat,long] format and polygon in format [[lat,long],[lat,long]...]. Algorithm will join the first and last point in the polygon slice
import "math"
// ContainsLocation determines whether the point is inside the polygon
func ContainsLocation(point []float64, polygon [][]float64, geodesic
bool) bool {
size := len(polygon)
if size == 0 {
return false
}
var (
lat2, lng2, dLng3 float64
)
lat3 := toRadians(point[0])
lng3 := toRadians(point[1])
prev := polygon[size-1]
lat1 := toRadians(prev[0])
lng1 := toRadians(prev[1])
nIntersect := 0
for _, v := range polygon {
dLng3 = wrap(lng3-lng1, -math.Pi, math.Pi)
// Special case: point equal to vertex is inside.
if lat3 == lat1 && dLng3 == 0 {
return true
}
lat2 = toRadians(v[0])
lng2 = toRadians(v[1])
// Offset longitudes by -lng1.
if intersects(lat1, lat2, wrap(lng2-lng1, -math.Pi, math.Pi), lat3, dLng3, geodesic) {
nIntersect++
}
lat1 = lat2
lng1 = lng2
}
return (nIntersect & 1) != 0
}
func toRadians(p float64) float64 {
return p * (math.Pi / 180.0)
}
func wrap(n, min, max float64) float64 {
if n >= min && n < max {
return n
}
return mod(n-min, max-min) + min
}
func mod(x, m float64) float64 {
return math.Remainder(math.Remainder(x, m)+m, m)
}
func intersects(lat1, lat2, lng2, lat3, lng3 float64, geodesic bool) bool {
// Both ends on the same side of lng3.
if (lng3 >= 0 && lng3 >= lng2) || (lng3 < 0 && lng3 < lng2) {
return false
}
// Point is South Pole.
if lat3 <= -math.Pi/2 {
return false
}
// Any segment end is a pole.
if lat1 <= -math.Pi/2 || lat2 <= -math.Pi/2 || lat1 >= math.Pi/2 || lat2 >= math.Pi/2 {
return false
}
if lng2 <= -math.Pi {
return false
}
linearLat := (lat1*(lng2-lng3) + lat2*lng3) / lng2
// Northern hemisphere and point under lat-lng line.
if lat1 >= 0 && lat2 >= 0 && lat3 < linearLat {
return false
}
// Southern hemisphere and point above lat-lng line.
if lat1 <= 0 && lat2 <= 0 && lat3 >= linearLat {
return true
}
// North Pole.
if lat3 >= math.Pi/2 {
return true
}
// Compare lat3 with latitude on the GC/Rhumb segment corresponding to lng3.
// Compare through a strictly-increasing function (tan() or mercator()) as convenient.
if geodesic {
return math.Tan(lat3) >= tanLatGC(lat1, lat2, lng2, lng3)
}
return mercator(lat3) >= mercatorLatRhumb(lat1, lat2, lng2, lng3)
}
func tanLatGC(lat1, lat2, lng2, lng3 float64) float64 {
return (math.Tan(lat1)*math.Sin(lng2-lng3) + math.Tan(lat2)*math.Sin(lng3)) / math.Sin(lng2)
}
func mercator(lat float64) float64 {
return math.Log(math.Tan(lat*0.5 + math.Pi/4))
}
func mercatorLatRhumb(lat1, lat2, lng2, lng3 float64) float64 {
return (mercator(lat1)*(lng2-lng3) + mercator(lat2)*lng3) / lng2
}
Runner.Java code in VB.NET
For the benefit of .NET folks the same code is put in VB.NET. Have tried it and is quite fast. Tried with 350000 records, it finishes in just few minutes.
But as said by author, i'm yet to test scenarios intersecting equator, multizones etc.
'Usage
If coordinate_is_inside_polygon(CurLat, CurLong, Lat_Array, Long_Array) Then
MsgBox("Location " & CurLat & "," & CurLong & " is within polygon boundary")
Else
MsgBox("Location " & CurLat & "," & CurLong & " is NOT within polygon boundary")
End If
'Functions
Public Function coordinate_is_inside_polygon(ByVal latitude As Double, ByVal longitude As Double, ByVal lat_array() As Double, ByVal long_array() As Double) As Boolean
Dim i As Integer
Dim angle As Double = 0
Dim point1_lat As Double
Dim point1_long As Double
Dim point2_lat As Double
Dim point2_long As Double
Dim n As Integer = lat_array.Length()
For i = 0 To n - 1
point1_lat = lat_array(i) - latitude
point1_long = long_array(i) - longitude
point2_lat = lat_array((i + 1) Mod n) - latitude
point2_long = long_array((i + 1) Mod n) - longitude
angle += Angle2D(point1_lat, point1_long, point2_lat, point2_long)
Next
If Math.Abs(angle) < PI Then Return False Else Return True
End Function
Public Function Angle2D(ByVal y1 As Double, ByVal x1 As Double, ByVal y2 As Double, ByVal x2 As Double) As Double
Dim dtheta, theta1, theta2 As Double
theta1 = Math.Atan2(y1, x1)
theta2 = Math.Atan2(y2, x2)
dtheta = theta2 - theta1
While dtheta > PI
dtheta -= TWOPI
End While
While dtheta < -PI
dtheta += TWOPI
End While
Return (dtheta)
End Function
Public Function is_valid_gps_coordinate(ByVal latitude As Double, ByVal longitude As Double) As Boolean
If latitude > -90 AndAlso latitude < 90 AndAlso longitude > -180 AndAlso longitude < 180 Then
Return True
End If
Return False
End Function

Resources