2

I have two points, (x1,y1) and (x2,y2), that I'd like to draw a line between. I know I can figure out the angle (in degrees) of that line using arctangent and the slope:

atan((y2-y1)/(x2-x1))*180/pi

However, how would I convert this angle to a [0,360] scale? Basically, I want my angle to be on a compass scale in which "North" is 0 deg, "East" is 90 deg, "South" is 180 deg, and "West" is 270 deg.

Thanks!

theforestecologist
  • 4,667
  • 5
  • 54
  • 91

4 Answers4

4
(450-atan2(y2-y1,x2-x1)*180/pi)%%360​​​​​​​​​​​​​​​

segmentToAngle <- function(x1,y1,x2,y2) atan2(y2-y1,x2-x1)*180/pi;
segmentToAngle(0,0,1,0); ## east
## [1] 0
segmentToAngle(0,0,0,1); ## north
## [1] 90
segmentToAngle(0,0,-1,0); ## west
## [1] 180
segmentToAngle(0,0,0,-1); ## south
## [1] -90
segmentToCompassAngle <- function(x1,y1,x2,y2) (450-segmentToAngle(x1,y1,x2,y2))%%360;
segmentToCompassAngle(0,0,1,0); ## east
## [1] 90
segmentToCompassAngle(0,0,0,1); ## north
## [1] 0
segmentToCompassAngle(0,0,-1,0); ## west
## [1] 270
segmentToCompassAngle(0,0,0,-1); ## south
## [1] 180
bgoldst
  • 34,190
  • 6
  • 38
  • 64
  • I don't understand your notation. Could you explain what it means? Also, why 450?? – theforestecologist Aug 05 '15 at 18:20
  • (1) What notation? It's all R code, you can run it yourself by copying and pasting into an R session. I also include the output of each line of code as a comment. (2) It doesn't have to be 450, since we mod 360 immediately after the subtraction; it just has to be 90 plus a multiple of 360. That's just the arithmetic you have to do to carry out this conversion. The subtraction reverses the direction of increasing angle and sets north at 0°, and the mod 360 forces the result to be in [0,360). – bgoldst Aug 05 '15 at 18:25
  • You call %% "mod"? I don't know what %% does. – theforestecologist Aug 05 '15 at 18:28
  • 2
    [`%%`](https://stat.ethz.ch/R-manual/R-devel/library/base/html/Arithmetic.html) is the R token for the [modulus operation](https://en.wikipedia.org/wiki/Modular_arithmetic). – bgoldst Aug 05 '15 at 18:30
  • Thanks!. I will just note that `(270-atan2(y2-y1,x2-x1)*180/pi)%%360` put the angle in navigational-bearing scale (with "North" = 0 deg) for me. Not sure hwy we are seeing a difference. – theforestecologist Aug 05 '15 at 18:48
  • Using a minuend of 270 causes north to be 180°. – bgoldst Aug 05 '15 at 18:55
  • Nevermind, mine was a data error. But `(90-atan2(y2-y1,x2-x1)*180/pi)%%360` is what actually put it into scale with North = 0deg. This may arise because I'm in strange ordination space?? – theforestecologist Aug 05 '15 at 18:57
  • 1
    The minuend must be 90+360 *n* for any integer *n*. I used 450 = 90+360(1) for no particular reason, and you've discovered that 90 = 90+360(0) also works. But the result will always be identical regardless what *n* you use. The general principle at work here is that *a* mod *m* = (*a* + *nm*) mod *m* for any integer *n*. – bgoldst Aug 05 '15 at 19:08
  • Informal proof: `set.seed(1); N <- 1000; x <- rnorm(N,0,1000); res1 <- x%%360; res2 <- (x+sample(-1000:1000,N,replace=T)*360)%%360; all.equal(res1,res2);` outputs `[1] TRUE`. – bgoldst Aug 05 '15 at 19:15
  • Thanks! I've now answered generalizing your response as best as I could! – theforestecologist Aug 05 '15 at 19:19
2

Just to generalize @bgoldst's answer:

(A1 - atan2(y2-y1,x2-x1) * 180/pi ) %%360

Here is the explanation of the various parts of this equation:

  1. You have to use atan2() instead of atan().

    atan2() is the arctangent function with two arguments. The purpose of using two arguments is to gather information on the signs of the inputs in order to return the appropriate quadrant of the computed angle. This is not possible for the single-argument arctangent (atan) function.

  2. The modulus operator %% is used in this case to give the remainder of dividing the angle by 360. In this way, we force the angles to "wrap around" at 360.

  3. The angle calculated using atan2 is multiplied by 180/pi in order to convert the answer from radians (atan2's default output) into degrees.

  4. If we stopped there, the resulting angle would be based on standard trigonometric form in which "East" = 0 degrees. All angles would be relative to "East" = 0.

    By subtracting our calculated angle in degrees from some angle (A1), we can offset the calculated angle by A1 degrees. In the case of the navigational-bearing scale (with "North" = 0 degrees) we would set A1 = 90.

     (90 - atan2(y2-y1,x2-x1) * 180/pi ) %%360
    
Community
  • 1
  • 1
theforestecologist
  • 4,667
  • 5
  • 54
  • 91
  • as @bgoldst pointed out, `A1 = 90 + 360 * any_integer` will have the same affect as setting `A1=90`. This is a result of the `%%360` operation. – theforestecologist Aug 05 '15 at 19:24
0

For "North" 0 deg. "East" 90 deg. "South" 180 deg. and "West" 270 deg.

you can use the formula:

f(E,N)=180-90*(1+sign(N))* (1-sign(E)^2)-45*(2+sign(N))*sign(E)

     -180/pi()*sign(E*N)*atan((abs(N)-abs(E))/(abs(N)+abs(E)))

      E=E2-E1 and  N=N2-N1
0

I do not want to discredit the math-focussed discussion here. However, you are speaking about a navigational bearing. From that I assume, your focus is on navigating := applying math to get a navigational task done (my personal definition).

The 'geosphere package' will help you a lot in this area. It offers a set of geo / nav functions (for spherical calculations). To move from one point p1 to another p2, the functions 'geosphere::bearing()' or 'geosphere::bearingRhumb()' will give you the azimuth direction expressed in degrees, i.e. -180 ... +180. You can find a bit of more explanation in the answer to this related question.

As also flagged by @theforestecologist, the following will do the trick using the modulo approach to determine the rest of any angle math beyond a full circle, i.e. 360 degrees. The modulo operator gives you the rest on top of the modulo basis (in our case 360). This solves the negative sign issue for the azimuth. For example

  • -90 + 360 = 270 ==> 270 %% 360 := (0 * 360) + 270 = 270
  • 30 + 360 = 30 ==> 30 %% 360 := (0 * 360) + 30 = 30
  • for math-purists re the modulo, any number of full circles can be added, e.g. 2* 360 degrees = 720. This will yield: -30 + 720 = 690 ==> 690 %% 360 := (1 * 360) + 330 = 330

Use the following in R/RStudio with the geosherepackage loaded. Please note that the geosphere package uses x-y position concept, i.e. longitude before latitude, rather than the navigational practice of latitude-before-longitude (e.g. 43N 3E)!

brg <- geosphere::bearing(c(p1_lon, p1_lat), c(p2_lon, p2_lat))
crs <- (brg + 360) %% 360  # to generate 0...360 courses

Note 2: if you work with multiple point-to-point relationships. Recode them as matrices using cbind():

brg <- geosphere::bearing(cbind(p1_lon, p1_lat), cbind(p2_lon, p2_lat))
Ray
  • 2,008
  • 14
  • 21