Class Angle

  1 /*
  2  * Copyright (c) 2016 Vivid Solutions.
  3  *
  4  * All rights reserved. This program and the accompanying materials
  5  * are made available under the terms of the Eclipse Public License 2.0
  6  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
  7  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
  8  * and the Eclipse Distribution License is available at
  9  *
 10  * http://www.eclipse.org/org/documents/edl-v10.php.
 11  */
 12 package org.locationtech.jts.algorithm;
 13  
 14 import org.locationtech.jts.geom.Coordinate;
 15  
 16 /**
 17  * Utility functions for working with angles.
 18  * Unless otherwise noted, methods in this class express angles in radians.
 19  */
 20 public class Angle
 21 {
 22   /**
 23    * The value of 2*Pi
 24    */
 25   public static final double PI_TIMES_2 = 2.0 * Math.PI;
 26   /**
 27    * The value of Pi/2
 28    */
 29   public static final double PI_OVER_2 = Math.PI / 2.0;
 30   /**
 31    * The value of Pi/4
 32    */
 33   public static final double PI_OVER_4 = Math.PI / 4.0;
 34  
 35   /** Constant representing counterclockwise orientation */
 36   public static final int COUNTERCLOCKWISE = Orientation.COUNTERCLOCKWISE;
 37  
 38   /** Constant representing clockwise orientation */
 39   public static final int CLOCKWISE = Orientation.CLOCKWISE;
 40  
 41   /** Constant representing no orientation */
 42   public static final int NONE = Orientation.COLLINEAR;
 43  
 44   private Angle() {}
 45  
 46   /**
 47    * Converts from radians to degrees.
 48    * @param radians an angle in radians
 49    * @return the angle in degrees
 50    */
 51   public static double toDegrees(double radians) {
 52       return (radians * 180) / (Math.PI);
 53   }
 54  
 55   /**
 56    * Converts from degrees to radians.
 57    *
 58    * @param angleDegrees an angle in degrees
 59    * @return the angle in radians
 60    */
 61   public static double toRadians(double angleDegrees) {
 62       return (angleDegrees * Math.PI) / 180.0;
 63   }
 64  
 65  
 66   /**
 67    * Returns the angle of the vector from p0 to p1,
 68    * relative to the positive X-axis.
 69    * The angle is normalized to be in the range [ -Pi, Pi ].
 70    *
 71    * @param p0 the initial point of the vector
 72    * @param p1 the terminal point of the vector
 73    * @return the normalized angle (in radians) that p0-p1 makes with the positive x-axis.
 74    */
 75   public static double angle(Coordinate p0, Coordinate p1) {
 76       double dx = p1.x - p0.x;
 77       double dy = p1.y - p0.y;
 78       return Math.atan2(dy, dx);
 79   }
 80  
 81   /**
 82    * Returns the angle of the vector from (0,0) to p,
 83    * relative to the positive X-axis.
 84    * The angle is normalized to be in the range ( -Pi, Pi ].
 85    *
 86    * @param p the terminal point of the vector
 87    * @return the normalized angle (in radians) that p makes with the positive x-axis.
 88    */
 89   public static double angle(Coordinate p) {
 90       return Math.atan2(p.y, p.x);
 91   }
 92  
 93  
 94   /**
 95    * Tests whether the angle between p0-p1-p2 is acute.
 96    * An angle is acute if it is less than 90 degrees.
 97    * <p>
 98    * Note: this implementation is not precise (deterministic) for angles very close to 90 degrees.
 99    *
100    * @param p0 an endpoint of the angle
101    * @param p1 the base of the angle
102    * @param p2 the other endpoint of the angle
103    * @return true if the angle is acute
104    */
105   public static boolean isAcute(Coordinate p0, Coordinate p1, Coordinate p2)
106   {
107     // relies on fact that A dot B is positive iff A ang B is acute
108     double dx0 = p0.x - p1.x;
109     double dy0 = p0.y - p1.y;
110     double dx1 = p2.x - p1.x;
111     double dy1 = p2.y - p1.y;
112     double dotprod = dx0 * dx1 + dy0 * dy1;
113     return dotprod > 0;
114   }
115  
116   /**
117    * Tests whether the angle between p0-p1-p2 is obtuse.
118    * An angle is obtuse if it is greater than 90 degrees.
119    * <p>
120    * Note: this implementation is not precise (deterministic) for angles very close to 90 degrees.
121    *
122    * @param p0 an endpoint of the angle
123    * @param p1 the base of the angle
124    * @param p2 the other endpoint of the angle
125    * @return true if the angle is obtuse
126    */
127   public static boolean isObtuse(Coordinate p0, Coordinate p1, Coordinate p2)
128   {
129     // relies on fact that A dot B is negative iff A ang B is obtuse
130     double dx0 = p0.x - p1.x;
131     double dy0 = p0.y - p1.y;
132     double dx1 = p2.x - p1.x;
133     double dy1 = p2.y - p1.y;
134     double dotprod = dx0 * dx1 + dy0 * dy1;
135     return dotprod < 0;
136   }
137  
138   /**
139    * Returns the unoriented smallest angle between two vectors.
140    * The computed angle will be in the range [0, Pi).
141    *
142    * @param tip1 the tip of one vector
143    * @param tail the tail of each vector
144    * @param tip2 the tip of the other vector
145    * @return the angle between tail-tip1 and tail-tip2
146    */
147   public static double angleBetween(Coordinate tip1, Coordinate tail,
148             Coordinate tip2) {
149         double a1 = angle(tail, tip1);
150         double a2 = angle(tail, tip2);
151  
152         return diff(a1, a2);
153   }
154  
155   /**
156    * Returns the oriented smallest angle between two vectors.
157    * The computed angle will be in the range (-Pi, Pi].
158    * A positive result corresponds to a counterclockwise
159    * (CCW) rotation
160    * from v1 to v2;
161    * a negative result corresponds to a clockwise (CW) rotation;
162    * a zero result corresponds to no rotation.
163    *
164    * @param tip1 the tip of v1
165    * @param tail the tail of each vector
166    * @param tip2 the tip of v2
167    * @return the angle between v1 and v2, relative to v1
168    */
169   public static double angleBetweenOriented(Coordinate tip1, Coordinate tail,
170             Coordinate tip2) 
171   {
172         double a1 = angle(tail, tip1);
173         double a2 = angle(tail, tip2);
174         double angDel = a2 - a1;
175         
176         // normalize, maintaining orientation
177         if (angDel <= -Math.PI)
178             return angDel + PI_TIMES_2;
179         if (angDel > Math.PI)
180             return angDel - PI_TIMES_2;
181         return angDel;
182   }
183  
184   /**
185      * Computes the interior angle between two segments of a ring. The ring is
186      * assumed to be oriented in a clockwise direction. The computed angle will be
187      * in the range [0, 2Pi]
188      * 
189      * @param p0
190      *          a point of the ring
191      * @param p1
192      *          the next point of the ring
193      * @param p2
194      *          the next point of the ring
195      * @return the interior angle based at <code>p1</code>
196      */
197   public static double interiorAngle(Coordinate p0, Coordinate p1, Coordinate p2)
198   {
199     double anglePrev = Angle.angle(p1, p0);
200     double angleNext = Angle.angle(p1, p2);
201     return Math.abs(angleNext - anglePrev);
202   }
203  
204   /**
205    * Returns whether an angle must turn clockwise or counterclockwise
206    * to overlap another angle.
207    *
208    * @param ang1 an angle (in radians)
209    * @param ang2 an angle (in radians)
210    * @return whether a1 must turn CLOCKWISE, COUNTERCLOCKWISE or NONE to
211    * overlap a2.
212    */
213   public static int getTurn(double ang1, double ang2) {
214       double crossproduct = Math.sin(ang2 - ang1);
215  
216       if (crossproduct > 0) {
217           return COUNTERCLOCKWISE;
218       }
219       if (crossproduct < 0) {
220           return CLOCKWISE;
221       }
222       return NONE;
223   }
224  
225   /**
226    * Computes the normalized value of an angle, which is the
227    * equivalent angle in the range ( -Pi, Pi ].
228    *
229    * @param angle the angle to normalize
230    * @return an equivalent angle in the range (-Pi, Pi]
231    */
232   public static double normalize(double angle)
233   {
234     while (angle > Math.PI)
235       angle -= PI_TIMES_2;
236     while (angle <= -Math.PI)
237       angle += PI_TIMES_2;
238     return angle;
239   }
240  
241   /**
242    * Computes the normalized positive value of an angle, which is the
243    * equivalent angle in the range [ 0, 2*Pi ).
244    * E.g.:
245    * <ul>
246    * <li>normalizePositive(0.0) = 0.0
247    * <li>normalizePositive(-PI) = PI
248    * <li>normalizePositive(-2PI) = 0.0
249    * <li>normalizePositive(-3PI) = PI
250    * <li>normalizePositive(-4PI) = 0
251    * <li>normalizePositive(PI) = PI
252    * <li>normalizePositive(2PI) = 0.0
253    * <li>normalizePositive(3PI) = PI
254    * <li>normalizePositive(4PI) = 0.0
255    * </ul>
256    *
257    * @param angle the angle to normalize, in radians
258    * @return an equivalent positive angle
259    */
260   public static double normalizePositive(double angle)
261   {
262       if (angle < 0.0) {
263           while (angle < 0.0)
264               angle += PI_TIMES_2;
265           // in case round-off error bumps the value over 
266           if (angle >= PI_TIMES_2)
267               angle = 0.0;
268       }
269       else {
270           while (angle >= PI_TIMES_2)
271               angle -= PI_TIMES_2;
272           // in case round-off error bumps the value under 
273           if (angle < 0.0)
274               angle = 0.0;
275       }
276     return angle;
277   }
278  
279   /**
280    * Computes the unoriented smallest difference between two angles.
281    * The angles are assumed to be normalized to the range [-Pi, Pi].
282    * The result will be in the range [0, Pi].
283    *
284    * @param ang1 the angle of one vector (in [-Pi, Pi] )
285    * @param ang2 the angle of the other vector (in range [-Pi, Pi] )
286    * @return the angle (in radians) between the two vectors (in range [0, Pi] )
287    */
288   public static double diff(double ang1, double ang2) {
289     double delAngle;
290  
291     if (ang1 < ang2) {
292       delAngle = ang2 - ang1;
293     } else {
294       delAngle = ang1 - ang2;
295     }
296  
297     if (delAngle > Math.PI) {
298       delAngle = (2 * Math.PI) - delAngle;
299     }
300  
301     return delAngle;
302   }
303 }
304