Related
Assume that we have a rectangle or a square and we know the x,y coordinates of its corners (4 corners).
Also assume that we have a point inside that square for which we know its coordinates (x,y), its speed (km/h), its heading (heading is measured in directional degrees, 0 for north, 180 for south and so on) and the time point it has these attributes (epoch time in seconds).
How can we calculate the time point (epoch time in seconds) in which the point will exit the rectangle as well as the coordinates (x,y) of the exit ?
You need to find what edge is intersected first. Make equations for moving along both coordinates and calculate the first time of intersection.
Note that for geographic coordinates you might need more complex calculations because "rectangle" defined by Lat/Lon coordinates is really curvy trapezoid on the Earth surface. Look at "Intersection of two paths given start points and bearings" chapter on this page to get travel time.
vx = V * Cos(heading + Pi/2) //for y-axis north=0
vy = V * Sin(heading + Pi/2)
x = x0 + vx * t
y = y0 + vy * t
//potential border positions
if vx > 0 then
ex = x2
else
ex = x1
if vy > 0 then
ey = y2
else
ey = y1
//check for horizontal/vertical directions
if vx = 0 then
return cx = x0, cy = ey, ct = (ey - y0) / vy
if vy = 0 then
return cx = ex, cy = y0, ct = (ex - x0) / vx
//in general case find times of intersections with horizontal and vertical edge line
tx = (ex - x0) / vx
ty = (ey - y0) / vy
//and get intersection for smaller parameter value
if tx <= ty then
return cx = ex, cy = y0 + tx * vy, ct = tx
else
return cx = x0 + ty * vx, cy = ey, ct = ty
I'm working on some 3d fractals... If I take any arbitrary point (x,y,z), start from there, and then draw a line of given length "d", in a direction defined by Euler angles... (by rotation A about the x-axis, B about the y-axis, and C about the z-axis) -- and then calculate the resulting endpoint of the line.
This would be simple in 2 dimensions, as I could find the endpoint like:
endX = beginX + d * cos(angle)
endY = beginY + d * sin(angle)
Basically, I need to fill in the blanks here:
endX = beginX + d * (??)
endY = beginY + d * (??)
endZ = beginZ + d * (??)
Where I only know angles defined by 3 rotations, 1 about each axis
I want to create 2 new longitude and 2 new latitudes based on a coordinate and a distance in meters, I want to create a nice bounding box around a certain point. It is for a part of a city and max ±1500 meters. I therefore don't think the curvature of earth has to be taken into account.
So I have 50.0452345 (x) and 4.3242234 (y) and I want to know x + 500 meters, x - 500 meters, y - 500 meters, y + 500 meters
I found many algorithms but almost all seem to deal with the distance between points.
The number of kilometers per degree of longitude is approximately
(pi/180) * r_earth * cos(theta*pi/180)
where theta is the latitude in degrees and r_earth is approximately 6378 km.
The number of kilometers per degree of latitude is approximately the same at all locations, approx
(pi/180) * r_earth = 111 km / degree
So you can do:
new_latitude = latitude + (dy / r_earth) * (180 / pi);
new_longitude = longitude + (dx / r_earth) * (180 / pi) / cos(latitude * pi/180);
As long as dx and dy are small compared to the radius of the earth and you don't get too close to the poles.
The accepted answer is perfectly right and works. I made some tweaks and turned into this:
double meters = 50;
// number of km per degree = ~111km (111.32 in google maps, but range varies
// between 110.567km at the equator and 111.699km at the poles)
//
// 111.32km = 111320.0m (".0" is used to make sure the result of division is
// double even if the "meters" variable can't be explicitly declared as double)
double coef = meters / 111320.0;
double new_lat = my_lat + coef;
// pi / 180 ~= 0.01745
double new_long = my_long + coef / Math.cos(my_lat * 0.01745);
Hope this helps too.
For latitude do:
var earth = 6378.137, //radius of the earth in kilometer
pi = Math.PI,
m = (1 / ((2 * pi / 360) * earth)) / 1000; //1 meter in degree
var new_latitude = latitude + (your_meters * m);
For longitude do:
var earth = 6378.137, //radius of the earth in kilometer
pi = Math.PI,
cos = Math.cos,
m = (1 / ((2 * pi / 360) * earth)) / 1000; //1 meter in degree
var new_longitude = longitude + (your_meters * m) / cos(latitude * (pi / 180));
The variable your_meters can contain a positive or a negative value.
I had to spend about two hours to work out the solution by #nibot , I simply needed a method to create a boundary box given its center point and width/height (or radius) in kilometers:
I don't fully understand the solution mathematically/ geographically.
I tweaked the solution (by trial and error) to get the four coordinates. Distances in km, given the current position and distance we shift to the new position in the four coordinates:
North:
private static Position ToNorthPosition(Position center, double northDistance)
{
double r_earth = 6378;
var pi = Math.PI;
var new_latitude = center.Lat + (northDistance / r_earth) * (180 / pi);
return new Position(new_latitude, center.Long);
}
East:
private static Position ToEastPosition(Position center, double eastDistance)
{
double r_earth = 6378;
var pi = Math.PI;
var new_longitude = center.Long + (eastDistance / r_earth) * (180 / pi) / Math.Cos(center.Lat * pi / 180);
return new Position(center.Lat, new_longitude);
}
South:
private static Position ToSouthPosition(Position center, double southDistance)
{
double r_earth = 6378;
var pi = Math.PI;
var new_latitude = center.Lat - (southDistance / r_earth) * (180 / pi);
return new Position(new_latitude, center.Long);
}
West:
private static Position ToWestPosition(Position center, double westDistance)
{
double r_earth = 6378;
var pi = Math.PI;
var new_longitude = center.Long - (westDistance / r_earth) * (180 / pi) / Math.Cos(center.Lat * pi / 180);
return new Position(center.Lat, new_longitude);
}
Have you checked out: How do I find the lat/long that is x km north of a given lat/long ?
These calculations are annoying at best, I've done many of them. The haversine formula will be your friend.
Some reference: http://www.movable-type.co.uk/scripts/latlong.html
Posting this method for sake of completeness.
Use this method "as it is" to:
Move any (lat,long) point by given meters in either axis.
Python method to move any point by defined meters.
def translate_latlong(lat,long,lat_translation_meters,long_translation_meters):
''' method to move any lat,long point by provided meters in lat and long direction.
params :
lat,long: lattitude and longitude in degrees as decimal values, e.g. 37.43609517497065, -122.17226450150885
lat_translation_meters: movement of point in meters in lattitude direction.
positive value: up move, negative value: down move
long_translation_meters: movement of point in meters in longitude direction.
positive value: left move, negative value: right move
'''
earth_radius = 6378.137
#Calculate top, which is lat_translation_meters above
m_lat = (1 / ((2 * math.pi / 360) * earth_radius)) / 1000;
lat_new = lat + (lat_translation_meters * m_lat)
#Calculate right, which is long_translation_meters right
m_long = (1 / ((2 * math.pi / 360) * earth_radius)) / 1000; # 1 meter in degree
long_new = long + (long_translation_meters * m_long) / math.cos(lat * (math.pi / 180));
return lat_new,long_new
Working Python code to offset coordinates by 10 metres.
def add_blur(lat, long):
meters = 10
blur_factor = meters * 0.000006279
new_lat = lat + blur_factor
new_long = long + blur_factor / math.cos(lat * 0.018)
return new_lat, new_long
if you don't have to be very exact then: each 10000 meters is about 0.1 for latitude and longitude.
for example I want to load locations 3000 meters around point_A from my database:
double newMeter = 3000 * 0.1 / 10000;
double lat1 = point_A.latitude - newMeter;
double lat2 = point_A.latitude + newMeter;
double lon1 = point_A.longitude - newMeter;
double lon1 = point_A.longitude + newMeter;
Cursor c = mDb.rawQuery("select * from TABLE1 where lat >= " + lat1 + " and lat <= " + lat2 + " and lon >= " + lon1 + " and lon <= " + lon2 + " order by id", null);
public double MeterToDegree(double meters, double latitude)
{
return meters / (111.32 * 1000 * Math.Cos(latitude * (Math.PI / 180)));
}
var meters = 50;
var coef = meters * 0.0000089;
var new_lat = map.getCenter().lat.apply() + coef;
var new_long = map.getCenter().lng.apply() + coef / Math.cos(new_lat * 0.018);
map.setCenter({lat:new_lat, lng:new_long});
See from Official Google Maps Documentation (link below) as they solve on easy/simple maps the problems with distance by countries :)
I recommended this solution to easy/simply solve issue with boundaries that you can know which area you're solving the problem with boundaries (not recommended globally)
Note:
Latitude lines run west-east and mark the position south-north of a point. Lines of latitude are called parallels and in total there are 180 degrees of latitude. The distance between each degree of latitude is about 69 miles (110 kilometers).
The distance between longitudes narrows the further away from the equator. The distance between longitudes at the equator is the same as latitude, roughly 69 miles (110 kilometers) . At 45 degrees north or south, the distance between is about 49 miles (79 kilometers). The distance between longitudes reaches zero at the poles as the lines of meridian converge at that point.
Original source 1
Original source 2
Official Google Maps Documentation: Code Example: Autocomplete Restricted to Multiple Countries
See the part of their code how they solve problem with distance center + 10 kilometers by +/- 0.1 degree
function initMap(): void {
const map = new google.maps.Map(
document.getElementById("map") as HTMLElement,
{
center: { lat: 50.064192, lng: -130.605469 },
zoom: 3,
}
);
const card = document.getElementById("pac-card") as HTMLElement;
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(card);
const center = { lat: 50.064192, lng: -130.605469 };
// Create a bounding box with sides ~10km away from the center point
const defaultBounds = {
north: center.lat + 0.1,
south: center.lat - 0.1,
east: center.lng + 0.1,
west: center.lng - 0.1,
};
const input = document.getElementById("pac-input") as HTMLInputElement;
const options = {
bounds: defaultBounds,
componentRestrictions: { country: "us" },
fields: ["address_components", "geometry", "icon", "name"],
origin: center,
strictBounds: false,
types: ["establishment"],
};
This is what I did in VBA that seems to be working for me. Calculation is in feet not meters though
Public Function CalcLong(OrigLong As Double, OrigLat As Double, DirLong As String, DirLat As String, DistLong As Double, DistLat As Double)
Dim FT As Double
Dim NewLong, NewLat As Double
FT = 1 / ((2 * WorksheetFunction.Pi / 360) * 20902230.971129)
If DirLong = "W" Then
NewLat = CalcLat(OrigLong, OrigLat, DirLong, DirLat, DistLong, DistLat)
NewLong = OrigLong - ((FT * DistLong) / Cos(NewLat * (WorksheetFunction.Pi / 180)))
CalcLong = NewLong
Else
NewLong = OrigLong + ((FT * DistLong) / Math.Cos(CalcLat(OrigLong, OrigLat, DirLong, DirLat, DistLong, DistLat) * (WorksheetFunction.Pi / 180)))
CalcLong = NewLong
End If
End Function
Public Function CalcLat(OrigLong As Double, OrigLat As Double, DirLong As String, DirLat As String, DistLong As Double, DistLat As Double) As Double
Dim FT As Double
Dim NewLat As Double
FT = 1 / ((2 * WorksheetFunction.Pi / 360) * 20902230.971129)
If DirLat = "S" Then
NewLat = (OrigLat - (FT * DistLat))
CalcLat = NewLat
Else
NewLat = (OrigLat + (FT * DistLat))
CalcLat = NewLat
End If
End Function
Original poster said:
"So I have 50.0452345 (x) and 4.3242234 (y) and I want to know x + 500 meters..."
I will assume the units of the x and y values he gave there were in meters (and not degrees Longitude, Latitude). If so then he is stating measurements to 0.1 micrometer, so I will assume he needs similar accuracy for the translated output. I also will assume by "+500 meters" etc. he meant
the direction to be due North-South and due East-West.
He refers to a reference point:
"2 new latitudes based on a coordinate";
but he did not give the Longitude and Latitude,
so to explain the procedure concretely I will give
the Latitudes and Longitudes for the corners of the
500 meter box he requested around the point
[30 degrees Longitude,30 degrees Latitude].
The exact solution on the surface of the GRS80 Ellipsoid is
given with the following set of functions
(I wrote these for the free-open-source-mac-pc math program called "PARI"
which allows any number of digits precision to be setup):
\\=======Arc lengths along Latitude and Longitude and the respective scales:
dms(u)=[truncate(u),truncate((u-truncate(u))*60),((u-truncate(u))*60-truncate((u-truncate(u))*60))*60];
SpinEarthRadiansPerSec=7.292115e-5;\
GMearth=3986005e8;\
J2earth=108263e-8;\
re=6378137;\
ecc=solve(ecc=.0001,.9999,eccp=ecc/sqrt(1-ecc^2);qecc=(1+3/eccp^2)*atan(eccp)-3/eccp;ecc^2-(3*J2earth+4/15*SpinEarthRadiansPerSec^2*re^3/GMearth*ecc^3/qecc));\
e2=ecc^2;\
b2=1-e2;\
b=sqrt(b2);\
fl=1-b;\
rfl=1/fl;\
U0=GMearth/ecc/re*atan(eccp)+1/3*SpinEarthRadiansPerSec^2*re^2;\
HeightAboveEllipsoid=0;\
reh=re+HeightAboveEllipsoid;\
longscale(lat)=reh*Pi/648000/sqrt(1+b2*(tan(lat))^2);
latscale(lat)=reh*b*Pi/648000/(1-e2*(sin(lat))^2)^(3/2);
longarc(lat,long1,long2)=longscale(lat)*648000/Pi*(long2-long1);
latarc(lat1,lat2)=(intnum(th=lat1,lat2,sqrt(1-e2*(sin(th))^2))+e2/2*sin(2*lat1)/sqrt(1-e2*(sin(lat1))^2)-e2/2*sin(2*lat2)/sqrt(1-e2*(sin(lat2))^2))*reh;
\\=======
I then plugged the reference point [30,30]
into those functions at the PARI command prompt
and had PARI solve for the point +/- 500 meters away
from it, giving the two new Longitudes and
two new Latitudes that the original poster asked for.
Here is the input and output showing that:
? dms(solve(x=29,31,longarc(30*Pi/180,30*Pi/180,x*Pi/180)+500))
cpu time = 1 ms, real time = 1 ms.
%1172 = [29, 59, 41.3444979398934670450280297216509190843055]
? dms(solve(x=29,31,longarc(30*Pi/180,30*Pi/180,x*Pi/180)-500))
cpu time = 1 ms, real time = 1 ms.
%1173 = [30, 0, 18.6555020601065329549719702783490809156945]
? dms(solve(x=29,31,latarc(30*Pi/180,x*Pi/180)+500))
cpu time = 1,357 ms, real time = 1,358 ms.
%1174 = [29, 59, 43.7621925447500548285775757329518579545513]
? dms(solve(x=29,31,latarc(30*Pi/180,x*Pi/180)-500))
cpu time = 1,365 ms, real time = 1,368 ms.
%1175 = [30, 0, 16.2377963202802863245716034907838199823349]
?
Given a rectangle (w, h) and a pie slice with a radius less or equal to the smaller of both sides (w, h), a start angle and an end angle, how can I place the slice optimally in the rectangle so that it fills the room best (from an optical point of view, not mathematically speaking)?
I'm currently placing the pie slice's center in the center of the rectangle and use the half of the smaller of both rectangle sides as the radius. This leaves plenty of room for certain configurations.
Examples to make clear what I'm after, based on the precondition that the slice is drawn like a unit circle (i.e. 0 degrees on positive X axis, then running clock-wise):
A start angle of 0 and an end angle of PI would lead to a filled lower half of the rectangle and an empty upper half. A good solution here would be to move the center up by 1/4*h.
A start angle of 0 and an end angle of PI/2 would lead to a filled bottom right quarter of the rectangle. A good solution here would be to move the center point to the top left of the rectangle and to set the radius to the smaller of both rectangle sides.
This is fairly easy for the cases I've sketched but it becomes complicated when the start and end angles are arbitrary. I am searching for an algorithm which determines center of the slice and radius in a way that fills the rectangle best. Pseudo code would be great since I'm not a big mathematician.
The extrema of the bounding box of your arc are in the following format:
x + x0 * r = 0
x + x1 * r = w
y + y0 * r = 0
y + y1 * r = h
The values x0, x1, y0 and y1 are found by taking the minimum and maximum values of up to 7 points: any tangential points that are spanned (i.e. 0, 90, 180 and 270 degrees) and the end points of the two line segments.
Given the extrema of the axis-aligned bounding box of the arc (x0, y0), (x1, y1) the radius and center point can be calculated as follows:
r = min(w/(x1-x0), h/(y1-y0)
x = -x0 * r
y = -y0 * r
Here is an implementation written in Lua:
-- ensures the angle is in the range [0, 360)
function wrap(angle)
local x = math.fmod(angle, 2 * math.pi)
if x < 0 then
x = x + 2 * math.pi
end
return x
end
function place_arc(t0, t1, w, h)
-- find the x-axis extrema
local x0 = 1
local x1 = -1
local xlist = {}
table.insert(xlist, 0)
table.insert(xlist, math.cos(t0))
table.insert(xlist, math.cos(t1))
if wrap(t0) > wrap(t1) then
table.insert(xlist, 1)
end
if wrap(t0-math.pi) > wrap(t1-math.pi) then
table.insert(xlist, -1)
end
for _, x in ipairs(xlist) do
if x < x0 then x0 = x end
if x > x1 then x1 = x end
end
-- find the y-axis extrema
local ylist = {}
local y0 = 1
local y1 = -1
table.insert(ylist, 0)
table.insert(ylist, math.sin(t0))
table.insert(ylist, math.sin(t1))
if wrap(t0-0.5*math.pi) > wrap(t1-0.5*math.pi) then
table.insert(ylist, 1)
end
if wrap(t0-1.5*math.pi) > wrap(t1-1.5*math.pi) then
table.insert(ylist, -1)
end
for _, y in ipairs(ylist) do
if y < y0 then y0 = y end
if y > y1 then y1 = y end
end
-- calculate the maximum radius the fits in the bounding box
local r = math.min(w / (x1 - x0), h / (y1 - y0))
-- find x & y from the radius and minimum extrema
local x = -x0 * r
local y = -y0 * r
-- calculate the final axis-aligned bounding-box (AABB)
local aabb = {
x0 = x + x0 * r,
y0 = y + y0 * r,
x1 = x + x1 * r,
y1 = y + y1 * r
}
return x, y, r, aabb
end
function center_arc(x, y, aabb, w, h)
dx = (w - aabb.x1) / 2
dy = (h - aabb.y1) / 2
return x + dx, y + dy
end
t0 = math.rad(60)
t1 = math.rad(300)
w = 320
h = 240
x, y, r, aabb = place_arc(t0, t1, w, h)
x, y = center_arc(x, y, aabb, w, h)
print(x, y, r)
Example output:
Instead of pseudo code, I used python, but it should be usable. For this algorithm, I assume that startAngle < endAngle and that both are within [-2 * PI, 2 * PI]. If you want to use both within [0, 2 * PI] and let startAngle > endAngle, I would do:
if (startAngle > endAngle):
startAngle = startAngle - 2 * PI
So, the algorithm that comes to mind is to calculate the bounds of the unit arc and then scale to fit your rectangle.
The first is the harder part. You need to calculate 4 numbers:
Left: MIN(cos(angle), 0)
Right: MAX(cos(angle), 0)
Top: MIN(sin(angle),0)
Bottom: MAX(sin(angle),0)
Of course, angle is a range, so it's not as simple as this. However, you really only have to include up to 11 points in this calculation. The start angle, the end angle, and potentially, the cardinal directions (there are 9 of these going from -2 * PI to 2 * PI.) I'm going to define boundingBoxes as lists of 4 elements, ordered [left, right, top, bottom]
def IncludeAngle(boundingBox, angle)
x = cos(angle)
y = sin(angle)
if (x < boundingBox[0]):
boundingBox[0] = x
if (x > boundingBox[1]):
boundingBox[1] = x
if (y < boundingBox[2]):
boundingBox[2] = y
if (y > boundingBox[3]):
boundingBox[3] = y
def CheckAngle(boundingBox, startAngle, endAngle, angle):
if (startAngle <= angle and endAngle >= angle):
IncludeAngle(boundingBox, angle)
boundingBox = [0, 0, 0, 0]
IncludeAngle(boundingBox, startAngle)
IncludeAngle(boundingBox, endAngle)
CheckAngle(boundingBox, startAngle, endAngle, -2 * PI)
CheckAngle(boundingBox, startAngle, endAngle, -3 * PI / 2)
CheckAngle(boundingBox, startAngle, endAngle, -PI)
CheckAngle(boundingBox, startAngle, endAngle, -PI / 2)
CheckAngle(boundingBox, startAngle, endAngle, 0)
CheckAngle(boundingBox, startAngle, endAngle, PI / 2)
CheckAngle(boundingBox, startAngle, endAngle, PI)
CheckAngle(boundingBox, startAngle, endAngle, 3 * PI / 2)
CheckAngle(boundingBox, startAngle, endAngle, 2 * PI)
Now you've computed the bounding box of an arc with center of 0,0 and radius of 1. To fill the box, we're going to have to solve a linear equation:
boundingBox[0] * xRadius + xOffset = 0
boundingBox[1] * xRadius + xOffset = w
boundingBox[2] * yRadius + yOffset = 0
boundingBox[3] * yRadius + yOffset = h
And we have to solve for xRadius and yRadius. You'll note there are two radiuses here. The reason for that is that in order to fill the rectangle, we have to multiple by different amounts in the two directions. Since your algorithm asks for only one radius, we will just pick the lower of the two values.
Solving the equation gives:
xRadius = w / (boundingBox[1] - boundingBox[0])
yRadius = h / (boundingBox[2] - boundingBox[3])
radius = MIN(xRadius, yRadius)
Here, you have to check for boundingBox[1] - boundingBox[0] being 0 and set xRadius to infinity in that case. This will give the correct result as yRadius will be smaller. If you don't have an infinity available, you can just set it to 0 and in the MIN function, check for 0 and use the other value in that case. xRadius and yRadius can't both be 0 because both sin and cos would have to be 0 for all angles included above for that to be the case.
Now we have to place the center of the arc. We want it centered in both directions. Now we'll create another linear equation:
(boundingBox[0] + boundingBox[1]) / 2 * radius + x = xCenter = w/2
(boundingBox[2] + boundingBox[3]) / 2 * radius + y = yCenter = h/2
Solving for x and y, the center of the arc, gives
x = w/2 - (boundingBox[0] + boundingBox[1]) * radius / 2
y = h/2 - (boundingBox[3] + boundingBox[2]) * radius / 2
This should give you the center of the arc and the radius needed to put the largest circle in the given rectangle.
I haven't tested any of this code, so this algorithm may have huge holes, or perhaps tiny ones caused by typos. I'd love to know if this algoritm works.
edit:
Putting all of the code together gives:
def IncludeAngle(boundingBox, angle)
x = cos(angle)
y = sin(angle)
if (x < boundingBox[0]):
boundingBox[0] = x
if (x > boundingBox[1]):
boundingBox[1] = x
if (y < boundingBox[2]):
boundingBox[2] = y
if (y > boundingBox[3]):
boundingBox[3] = y
def CheckAngle(boundingBox, startAngle, endAngle, angle):
if (startAngle <= angle and endAngle >= angle):
IncludeAngle(boundingBox, angle)
boundingBox = [0, 0, 0, 0]
IncludeAngle(boundingBox, startAngle)
IncludeAngle(boundingBox, endAngle)
CheckAngle(boundingBox, startAngle, endAngle, -2 * PI)
CheckAngle(boundingBox, startAngle, endAngle, -3 * PI / 2)
CheckAngle(boundingBox, startAngle, endAngle, -PI)
CheckAngle(boundingBox, startAngle, endAngle, -PI / 2)
CheckAngle(boundingBox, startAngle, endAngle, 0)
CheckAngle(boundingBox, startAngle, endAngle, PI / 2)
CheckAngle(boundingBox, startAngle, endAngle, PI)
CheckAngle(boundingBox, startAngle, endAngle, 3 * PI / 2)
CheckAngle(boundingBox, startAngle, endAngle, 2 * PI)
if (boundingBox[1] == boundingBox[0]):
xRadius = 0
else:
xRadius = w / (boundingBox[1] - boundingBox[0])
if (boundingBox[3] == boundingBox[2]):
yRadius = 0
else:
yRadius = h / (boundingBox[3] - boundingBox[2])
if xRadius == 0:
radius = yRadius
elif yRadius == 0:
radius = xRadius
else:
radius = MIN(xRadius, yRadius)
x = w/2 - (boundingBox[0] + boundingBox[1]) * radius / 2
y = h/2 - (boundingBox[3] + boundingBox[2]) * radius / 2
edit:
One issue here is that sin[2 * PI] is not going to be exactly 0 because of rounding errors. I think the solution is to get rid of the CheckAngle calls and replace them with something like:
def CheckCardinal(boundingBox, startAngle, endAngle, cardinal):
if startAngle < cardinal * PI / 2 and endAngle > cardinal * PI / 2:
cardinal = cardinal % 4
if cardinal == 0:
boundingBox[1] = 1
if cardinal == 1:
boundingBox[3] = 1
if cardinal == 2:
boundingBox[0] = -1
if cardinal == 3:
boundingBox[2] = -1
CheckCardinal(boundingBox, startAngle, endAngle, -4)
CheckCardinal(boundingBox, startAngle, endAngle, -3)
CheckCardinal(boundingBox, startAngle, endAngle, -2)
CheckCardinal(boundingBox, startAngle, endAngle, -1)
CheckCardinal(boundingBox, startAngle, endAngle, 0)
CheckCardinal(boundingBox, startAngle, endAngle, 1)
CheckCardinal(boundingBox, startAngle, endAngle, 2)
CheckCardinal(boundingBox, startAngle, endAngle, 3)
CheckCardinal(boundingBox, startAngle, endAngle, 4)
You still need IncludeAngle(startAngle) and IncludeAngle(endAngle)
Just consider a circle and forget the filling. The bounds will either be the center of the circle, the endpoints, or the points at 0, 90, 180, or 270 degrees (if they exist in this slice). The maxima and minima of these seven points will determine your bounding rectangle.
As far as placing it in the center, calculate the average of the max and min for both the rectangle and the pie slice, and add or subtract the difference of these to whichever one you want to move.
I would divide the problem into three steps:
Find the bounding box of a unit pie slice (or if a radius is given the actual pie slice centered at (0, 0)).
Fit the bounding box in your rectangle.
Use the information about fitting the bounding box to adjust the center and radius of the pie slice.
When I have time, I may flush this out with more details.
I need an algorithm to figure out if one angle is within a certain amount of degrees from another angle.
My first thought was (a-x < b) && (a+x > b), but it fails when it has to work with angles that wrap around from -179 to 180.
In the diagram above, the region (green) that the angle must be between wraps between the negative and positive sides. How can I determine whether the angle (the red line) falls inside this region?
try this formula:
360-(|a-b|)%360<x || (|a-b|)%360<x
Or, in PHP:
<?php
$b = 10;
$angle1 = -179;
$angle2 = 180;
$diff = $angle1 - $angle2;
if(abs($diff % 360) <= $b || (360-abs($diff % 360))<=$b) {
echo "yes";
} else {
echo "no";
}
?>
As Marcel rightly points out, modulo on negative numbers is potentially problematic. Also, what is the difference between 355 and 5 degrees? It might be worked out to be 350 degrees but 10 degrees is probably what people are expecting. We make the following assumptions:
we want the smallest positive angle between two other angles so 0 <= diff <= 180;
we are working in degrees. If radians, substitute 360 for 2*PI;
angles can be positive or negative can be outside the range -360 < x < 360 where x is an input angle and
order of input angles or the direction of the difference is irrelevant.
Inputs: angles a and b. So the algorithm is simply:
Normalize a and b to 0 <= x < 360;
Compute the shortest angle between the two normal angles.
For the first step, to convert the angle to the desired range, there are two possibilities:
x >= 0: normal = x % 360
x < 0: normal = (-x / 360 + 1) * 360 + x
The second is designed to remove any ambiguity on the difference in interpretation of negative modulus operations. So to give a worked example for x = -400:
-x / 360 + 1
= -(-400) / 360 + 1
= 400 / 360 + 1
= 1 + 1
= 2
then
normal = 2 * 360 + (-400)
= 320
so for inputs 10 and -400 the normal angles are 10 and 320.
Now we calculate the shortest angle between them. As a sanity check, the sum of those two angles must be 360. In this case the possibilities are 50 and 310 (draw it and you'll see this). To work these out:
normal1 = min(normal(a), normal(b))
normal2 = max(normal(a), normal(b))
angle1 = normal2 - normal1
angle2 = 360 + normal1 - normal2
So for our example:
normal1 = min(320, 10) = 10
normal2 = max(320, 10) = 320
angle1 = normal2 - normal1 = 320 - 10 = 310
angle2 = 360 + normal1 - normal2 = 360 + 10 - 320 = 50
You'll note normal1 + normal2 = 360 (and you can even prove this will be the case if you like).
Lastly:
diff = min(normal1, normal2)
or 50 in our case.
You can also use a dot product:
cos(a)*cos(b) + sin(a)*sin(b) >= cos(x)
For a radius of 1, the distance between the line endpoints is 2sin((a-b/2). So throw away the 2 since you are only interested in a comparison, and compare sin(x/2) with sin((a-b)/2). The trig functions take care of all the wrapping.
c++ implementation:
float diff = fabsf(angle1 - angle2);
bool isInRange = fmodf(diff, 360.0f) <= ANGLE_RANGE ||
360.0f - fmodf(diff, 360.0f) <= ANGLE_RANGE;