View Javadoc
1   package org.opentrafficsim.core.math;
2   
3   import java.io.Serializable;
4   import java.util.Locale;
5   
6   import org.djunits.unit.DirectionUnit;
7   import org.djunits.unit.SpeedUnit;
8   import org.djunits.value.ValueRuntimeException;
9   import org.djunits.value.vdouble.scalar.Direction;
10  import org.djunits.value.vdouble.scalar.Speed;
11  import org.djunits.value.vdouble.vector.SpeedVector;
12  
13  /**
14   * A 3D speed vector, decomposed in X, Y, and Z-speed with easy conversion from and to a spherical coordinate system. <br>
15   * <a href="https://en.wikipedia.org/wiki/Spherical_coordinate_system">Physicists and mathematicians <strong>do not</strong>
16   * agree on the meaning of theta and phi.</a> In this class the convention in the physics domain is used:
17   * <ul>
18   * <li>theta is the angle from the z direction.</li>
19   * <li>phi is the projected angle in the xy-plane from the x direction.</li>
20   * </ul>
21   * N.B. In the geography domain yet another convention is used. <br>
22   * <p>
23   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
24   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
25   * </p>
26   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
27   * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
28   */
29  public class Speed3d implements Serializable
30  {
31      /** */
32      private static final long serialVersionUID = 20160000L;
33  
34      /** The speed in 3D (XYZ coded). */
35      private final SpeedVector speed;
36  
37      /**
38       * Construct a new Speed3d from vector of strongly typed Cartesian coordinates.
39       * @param speed SpeedVector; the speeds in 3D (YPR coded)
40       * @throws ValueRuntimeException in case the vector does not have exactly three elements
41       */
42      public Speed3d(final SpeedVector speed) throws ValueRuntimeException
43      {
44          if (speed.size() != 3)
45          {
46              throw new ValueRuntimeException("Size of an RPY-speed vector should be exactly 3. Got: " + speed);
47          }
48          this.speed = speed;
49      }
50  
51      /**
52       * Construct a new Speed3d from three strongly typed Cartesian coordinates.
53       * @param x Speed; the speed in the x-direction
54       * @param y Speed; the speed in the y-direction
55       * @param z Speed; the speed in the z-direction
56       * @throws ValueRuntimeException in case the units are incorrect
57       */
58      public Speed3d(final Speed x, final Speed y, final Speed z) throws ValueRuntimeException
59      {
60          this.speed = new SpeedVector(new Speed[] {x, y, z}, x.getDisplayUnit());
61      }
62  
63      /**
64       * Construct a new Speed3d from three double Cartesian coordinates and a speed unit.
65       * @param x double; the speed in the x-direction
66       * @param y double; the speed in the y-direction
67       * @param z double; the speed in the z-direction
68       * @param unit SpeedUnit; the unit of the xyz parameters
69       * @throws ValueRuntimeException in case the units are incorrect
70       */
71      public Speed3d(final double x, final double y, final double z, final SpeedUnit unit) throws ValueRuntimeException
72      {
73          this.speed = new SpeedVector(new double[] {x, y, z}, unit);
74      }
75  
76      /**
77       * Construct a new Speed3d from a strongly typed speed and polar coordinates.
78       * @param speed Speed; the speed in the direction of the angle along the vector
79       * @param theta Direction; the angle from the z direction
80       * @param phi Direction; the projected angle in the xy-plane from the x direction
81       * @throws ValueRuntimeException in case the vector does not have exactly three elements
82       */
83      public Speed3d(final Speed speed, final Direction theta, final Direction phi) throws ValueRuntimeException
84      {
85          double[] xyz = Scalar3d.polarToCartesian(speed.getInUnit(), theta.si, phi.si);
86          this.speed = new SpeedVector(xyz, speed.getDisplayUnit());
87      }
88  
89      /**
90       * Retrieve the x-component of this Speed3d.
91       * @return the speed in the x-direction.
92       */
93      public final Speed getX()
94      {
95          try
96          {
97              return this.speed.get(0);
98          }
99          catch (ValueRuntimeException exception)
100         {
101             // should be impossible as we constructed the vector always with three elements
102             throw new RuntimeException(
103                     "getX() gave an exception; apparently vector " + this.speed + " was not constructed right", exception);
104         }
105     }
106 
107     /**
108      * Retrieve the y-component of this Speed3d.
109      * @return the speed in the y-direction.
110      */
111     public final Speed getY()
112     {
113         try
114         {
115             return this.speed.get(1);
116         }
117         catch (ValueRuntimeException exception)
118         {
119             // should be impossible as we constructed the vector always with three elements
120             throw new RuntimeException(
121                     "getY() gave an exception; apparently vector " + this.speed + " was not constructed right", exception);
122         }
123     }
124 
125     /**
126      * Retrieve the z-component of this Speed3d.
127      * @return the speed in the z-direction.
128      */
129     public final Speed getZ()
130     {
131         try
132         {
133             return this.speed.get(2);
134         }
135         catch (ValueRuntimeException exception)
136         {
137             // should be impossible as we constructed the vector always with three elements
138             throw new RuntimeException(
139                     "getZ() gave an exception; apparently vector " + this.speed + " was not constructed right", exception);
140         }
141     }
142 
143     /**
144      * Retrieve the theta of this Speed3d.
145      * @return the angle of direction perpendicular to the xy-plane
146      */
147     public final Direction getTheta()
148     {
149         return Scalar3d.cartesianToTheta(getX().si, getY().si, getZ().si);
150     }
151 
152     /**
153      * Retrieve the phi of this Speed3d.
154      * @return the projected angle of direction in the xy-plane
155      */
156     public final Direction getPhi()
157     {
158         return Scalar3d.cartesianToPhi(getX().si, getY().si);
159     }
160 
161     /**
162      * Retrieve the norm of this Speed3d.
163      * @return the combined speed in the direction of the angle
164      */
165     public final Speed getSpeed()
166     {
167         return new Speed(Scalar3d.cartesianToRadius(getX().si, getY().si, getZ().si), SpeedUnit.SI);
168     }
169 
170     /** {@inheritDoc} */
171     @Override
172     public final String toString()
173     {
174         return String.format(Locale.US, "Speed3d %s (%s, theta %s, phi %s)", this.speed, getSpeed(),
175                 new Direction(getTheta().getInUnit(DirectionUnit.EAST_DEGREE), DirectionUnit.EAST_DEGREE),
176                 new Direction(getPhi().getInUnit(DirectionUnit.EAST_DEGREE), DirectionUnit.EAST_DEGREE));
177     }
178 
179 }