1 package org.djunits.quantity;
2
3 import org.djunits.quantity.def.Quantity;
4 import org.djunits.unit.AbstractUnit;
5 import org.djunits.unit.UnitInterface;
6 import org.djunits.unit.UnitRuntimeException;
7 import org.djunits.unit.Unitless;
8 import org.djunits.unit.scale.GradeScale;
9 import org.djunits.unit.scale.IdentityScale;
10 import org.djunits.unit.scale.LinearScale;
11 import org.djunits.unit.scale.Scale;
12 import org.djunits.unit.si.SIPrefix;
13 import org.djunits.unit.si.SIUnit;
14 import org.djunits.unit.system.UnitSystem;
15
16 /**
17 * Angle is the measure of rotation between two intersecting lines, expressed in radians (rad) or degrees.
18 * <p>
19 * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
20 * for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
21 * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
22 * @author Alexander Verbraeck
23 */
24 public class Angle extends Quantity<Angle>
25 {
26 /** Constant with value zero radians. */
27 public static final Angle ZERO = ofSi(0.0);
28
29 /** Constant with value pi radians. */
30 public static final Angle PI = ofSi(Math.PI);
31
32 /** Constant with value pi/2 radians. */
33 public static final Angle HALF_PI = ofSi(Math.PI / 2.0);
34
35 /** Constant with value 2 pi radians. */
36 public static final Angle TWO_PI = ofSi(Math.PI * 2.0);
37
38 /** Constant with value tau radians. */
39 public static final Angle TAU = ofSi(Math.PI * 2.0);
40
41 /** Constant with value one radian. */
42 public static final Angle ONE = ofSi(1.0);
43
44 /** Constant with value NaN. */
45 @SuppressWarnings("checkstyle:constantname")
46 public static final Angle NaN = ofSi(Double.NaN);
47
48 /** Constant with value POSITIVE_INFINITY. */
49 public static final Angle POSITIVE_INFINITY = ofSi(Double.POSITIVE_INFINITY);
50
51 /** Constant with value NEGATIVE_INFINITY. */
52 public static final Angle NEGATIVE_INFINITY = ofSi(Double.NEGATIVE_INFINITY);
53
54 /** Constant with value MAX_VALUE. */
55 public static final Angle POS_MAXVALUE = ofSi(Double.MAX_VALUE);
56
57 /** Constant with value -MAX_VALUE. */
58 public static final Angle NEG_MAXVALUE = ofSi(-Double.MAX_VALUE);
59
60 /** */
61 private static final long serialVersionUID = 600L;
62
63 /**
64 * Instantiate a Angle quantity with an SI or base value and a display unit.
65 * @param value the quantity value expressed in the SI or base unit
66 * @param displayUnit the display unit to use
67 * @param useSi use SI value when true, use value in unit when false
68 */
69 public Angle(final double value, final Angle.Unit displayUnit, final boolean useSi)
70 {
71 super(value, displayUnit, useSi);
72 }
73
74 /**
75 * Instantiate a Angle quantity expressed in the given unit.
76 * @param valueInUnit the quantity value expressed in the given unit
77 * @param unit the unit of the value, also acts as the display unit
78 */
79 public Angle(final double valueInUnit, final Angle.Unit unit)
80 {
81 this(valueInUnit, unit, false);
82 }
83
84 /**
85 * Return a Angle instance based on an SI value.
86 * @param si the si value
87 * @return the Angle instance based on an SI value
88 */
89 public static Angle ofSi(final double si)
90 {
91 return new Angle(si, Angle.Unit.SI, true);
92 }
93
94 /**
95 * Instantiate a Angle quantity with an SI or base value and a display unit.
96 * @param siValue the quantity value expressed in the SI or base unit
97 * @param displayUnit the display unit to use
98 * @return the Angle instance based on an SI value with the given display unit
99 */
100 public static Angle ofSi(final double siValue, final Angle.Unit displayUnit)
101 {
102 return new Angle(siValue, displayUnit, true);
103 }
104
105 @Override
106 public Angle instantiateSi(final double siValue, final UnitInterface<Angle> displayUnit)
107 {
108 return new Angle(siValue, (Angle.Unit) displayUnit, true);
109 }
110
111 /**
112 * Returns a Angle representation of a textual representation of a value with a unit. The String representation that can be
113 * parsed is the double value in the unit, followed by a localized or English abbreviation of the unit. Spaces are allowed,
114 * but not required, between the value and the unit.
115 * @param text the textual representation to parse into a Angle
116 * @return the Scalar representation of the value in its unit
117 * @throws IllegalArgumentException when the text cannot be parsed
118 * @throws NullPointerException when the text argument is null
119 */
120 public static Angle valueOf(final String text)
121 {
122 return Quantity.valueOf(text, ZERO);
123 }
124
125 /**
126 * Returns a Angle based on a value expressed in the unit.
127 * @param valueInUnit the value, expressed in the given unit
128 * @param unit the unit of the value, also acts as the display unit
129 * @return ab Angle representation of the value in its unit
130 */
131 public static Angle of(final double valueInUnit, final Angle.Unit unit)
132 {
133 return new Angle(valueInUnit, unit);
134 }
135
136 /**
137 * Returns a Angle based on a value and the textual representation of the unit, which can be localized.
138 * @param valueInUnit the value, expressed in the unit as given by unitString
139 * @param unitString the textual representation of the unit
140 * @return the Scalar representation of the value in its unit
141 * @throws IllegalArgumentException when the unit cannot be parsed or is incorrect
142 * @throws NullPointerException when the unitString argument is null
143 */
144 public static Angle of(final double valueInUnit, final String unitString)
145 {
146 return Quantity.of(valueInUnit, unitString, ZERO);
147 }
148
149 @Override
150 public Angle.Unit getDisplayUnit()
151 {
152 return (Angle.Unit) super.getDisplayUnit();
153 }
154
155 /**
156 * Add an (absolute) direction to this angle, and return a direction. The unit of the return value will be the unit of this
157 * angle, and the reference of the return value will be the reference belonging to the given direction.
158 * <code>R.add(A)</code> = unit of R and reference value of A.
159 * @param direction the absolute direction to add
160 * @return the absolute direction plus this angle
161 */
162 public Direction add(final Direction direction)
163 {
164 return new Direction(new Angle(direction.si() + si(), getDisplayUnit(), true), direction.getReference());
165 }
166
167 /**
168 * Calculate the division of Angle and Angle, which results in a Dimensionless scalar.
169 * @param v scalar
170 * @return scalar as a division of Angle and Angle
171 */
172 public Dimensionless divide(final Angle v)
173 {
174 return new Dimensionless(this.si() / v.si(), Unitless.BASE);
175 }
176
177 /**
178 * Calculate the multiplication of Angle and Frequency, which results in a AngularVelocity scalar.
179 * @param v scalar
180 * @return scalar as a multiplication of Angle and Frequency
181 */
182 public AngularVelocity multiply(final Frequency v)
183 {
184 return new AngularVelocity(this.si() * v.si(), AngularVelocity.Unit.SI);
185 }
186
187 /**
188 * Calculate the division of Angle and Duration, which results in a AngularVelocity scalar.
189 * @param v scalar
190 * @return scalar as a division of Angle and Duration
191 */
192 public AngularVelocity divide(final Duration v)
193 {
194 return new AngularVelocity(this.si() / v.si(), AngularVelocity.Unit.SI);
195 }
196
197 /**
198 * Calculate the division of Angle and AngularVelocity, which results in a Duration scalar.
199 * @param v scalar
200 * @return scalar as a division of Angle and AngularVelocity
201 */
202 public Duration divide(final AngularVelocity v)
203 {
204 return new Duration(this.si() / v.si(), Duration.Unit.SI);
205 }
206
207 /**
208 * Normalize an angle between 0 and 2 * PI.
209 * @param angle original angle.
210 * @return angle between 0 and 2 * PI.
211 */
212 public static double normalize(final double angle)
213 {
214 double normalized = angle % (2 * Math.PI);
215 if (normalized < 0.0)
216 {
217 normalized += 2 * Math.PI;
218 }
219 return normalized;
220 }
221
222 /**
223 * Normalize an angle between 0 and 2 * PI.
224 * @param angle original angle.
225 * @return a new Angle object with angle between 0 and 2 * PI.
226 */
227 public static Angle normalize(final Angle angle)
228 {
229 return new Angle(normalize(angle.si()), Angle.Unit.rad, true);
230 }
231
232 /******************************************************************************************************/
233 /********************************************** UNIT CLASS ********************************************/
234 /******************************************************************************************************/
235
236 /**
237 * Angle.Unit encodes the units of angle (radians, degrees).
238 * <p>
239 * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved.
240 * See for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
241 * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
242 * @author Alexander Verbraeck
243 */
244 @SuppressWarnings("checkstyle:constantname")
245 public static class Unit extends AbstractUnit<Angle>
246 {
247 /** The dimensions of Angle: rad. */
248 public static final SIUnit SI_UNIT = SIUnit.of("rad");
249
250 /** radian. */
251 public static final Angle.Unit rad =
252 new Angle.Unit("rad", "rad", "radian", IdentityScale.SCALE, UnitSystem.SI_DERIVED, null);
253
254 /** The SI or BASE unit. */
255 public static final Angle.Unit SI = rad;
256
257 /** percent (non-linear, 100% is 45 degrees; 90 degrees is infinite). */
258 public static final Angle.Unit percent =
259 new Angle.Unit("%", "%", "percent", new GradeScale(0.01), UnitSystem.OTHER, null);
260
261 /** degree. */
262 public static final Angle.Unit deg =
263 rad.deriveUnit("deg", "\u00b0", "degree", Math.PI / 180.0, UnitSystem.SI_ACCEPTED, null);
264
265 /** arcminute. */
266 public static final Angle.Unit arcmin = deg.deriveUnit("arcmin", "'", "arcminute", 1.0 / 60.0, UnitSystem.OTHER, null);
267
268 /** arcsecond. */
269 public static final Angle.Unit arcsec =
270 deg.deriveUnit("arcsec", "\"", "arcsecond", 1.0 / 3600.0, UnitSystem.OTHER, null);
271
272 /** grad. */
273 public static final Angle.Unit grad =
274 rad.deriveUnit("grad", "grad", "gradian", 2.0 * Math.PI / 400.0, UnitSystem.OTHER, null);
275
276 /** centesimal arcminute. */
277 public static final Angle.Unit cdm =
278 grad.deriveUnit("cdm", "c'", "centesimal arcminute", 1.0 / 100.0, UnitSystem.OTHER, null);
279
280 /** centesimal arcsecond. */
281 public static final Angle.Unit cds =
282 grad.deriveUnit("cds", "c\"", "centesimal arcsecond", 1.0 / 10000.0, UnitSystem.OTHER, null);
283
284 /**
285 * Create a new Angle unit.
286 * @param id the id or main abbreviation of the unit
287 * @param name the full name of the unit
288 * @param scaleFactorToBaseUnit the scale factor of the unit to convert it TO the base (SI) unit
289 * @param unitSystem the unit system such as SI or IMPERIAL
290 */
291 public Unit(final String id, final String name, final double scaleFactorToBaseUnit, final UnitSystem unitSystem)
292 {
293 super(id, name, scaleFactorToBaseUnit, unitSystem);
294 }
295
296 /**
297 * Return a derived unit for this unit, with textual abbreviation(s) and a display abbreviation.
298 * @param textualAbbreviation the textual abbreviation of the unit, which doubles as the id
299 * @param displayAbbreviation the display abbreviation of the unit
300 * @param name the full name of the unit
301 * @param scale the scale to use to convert from this unit to the standard (e.g., SI, BASE) unit
302 * @param unitSystem unit system, e.g. SI or Imperial
303 * @param siPrefix the SI Prefix of this unit
304 */
305 public Unit(final String textualAbbreviation, final String displayAbbreviation, final String name, final Scale scale,
306 final UnitSystem unitSystem, final SIPrefix siPrefix)
307 {
308 super(textualAbbreviation, displayAbbreviation, name, scale, unitSystem, siPrefix);
309 }
310
311 @Override
312 public SIUnit siUnit()
313 {
314 return SI_UNIT;
315 }
316
317 @Override
318 public Unit getBaseUnit()
319 {
320 return SI;
321 }
322
323 @Override
324 public Angle ofSi(final double si, final UnitInterface<Angle> displayUnit)
325 {
326 return new Angle(si, (Unit) displayUnit, true);
327 }
328
329 @Override
330 public Angle.Unit deriveUnit(final String textualAbbreviation, final String displayAbbreviation, final String name,
331 final double scaleFactor, final UnitSystem unitSystem, final SIPrefix siPrefix)
332 {
333 if (getScale() instanceof LinearScale ls)
334 {
335 return new Angle.Unit(textualAbbreviation, displayAbbreviation, name,
336 new LinearScale(ls.getScaleFactorToBaseUnit() * scaleFactor), unitSystem, siPrefix);
337 }
338 throw new UnitRuntimeException("Only possible to derive a unit from a unit with a linear scale");
339 }
340
341 }
342
343 }