View Javadoc
1   package org.djutils.draw.line;
2   
3   import java.util.Arrays;
4   import java.util.Iterator;
5   import java.util.Locale;
6   
7   import org.djutils.base.AngleUtil;
8   import org.djutils.draw.DrawRuntimeException;
9   import org.djutils.draw.Drawable2d;
10  import org.djutils.draw.Space2d;
11  import org.djutils.draw.bounds.Bounds2d;
12  import org.djutils.draw.point.Point2d;
13  import org.djutils.exceptions.Throw;
14  
15  /**
16   * Ray2d is a half-line; it has one end point with non-infinite coordinates; the other end point is infinitely far away.
17   * <p>
18   * Copyright (c) 2020-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
19   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
20   * </p>
21   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
22   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
23   */
24  public class Ray2d extends Point2d implements Drawable2d, Ray<Ray2d, Point2d, Space2d>
25  {
26      /** */
27      private static final long serialVersionUID = 20210119L;
28  
29      /** Phi; the angle from the positive X axis direction in radians. */
30      @SuppressWarnings("checkstyle:visibilitymodifier")
31      public final double phi;
32  
33      /**
34       * Construct a new Ray2d.
35       * @param x double; the x coordinate of the finite end point of the ray
36       * @param y double; the y coordinate of the finite end point of the ray
37       * @param phi double; the angle from the positive X axis direction in radians.
38       * @throws DrawRuntimeException when phi is NaN
39       */
40      public Ray2d(final double x, final double y, final double phi) throws DrawRuntimeException
41      {
42          super(x, y);
43          Throw.when(Double.isNaN(phi), DrawRuntimeException.class, "phi may not be NaN");
44          this.phi = phi;
45      }
46  
47      /**
48       * Construct a new Ray2d.
49       * @param point Point2d; the finite end point of the ray
50       * @param phi double; the angle from the positive X axis direction in radians.
51       * @throws NullPointerException when point is null
52       * @throws DrawRuntimeException when phi is NaN
53       */
54      public Ray2d(final Point2d point, final double phi) throws NullPointerException, DrawRuntimeException
55      {
56          this(Throw.whenNull(point, "point may not be null").x, point.y, phi);
57      }
58  
59      /**
60       * Construct a new Ray2d.
61       * @param x double; the x coordinate of the finite end point of the ray
62       * @param y double; the y coordinate of the finite end point of the ray
63       * @param throughX double; the x coordinate of another point on the ray
64       * @param throughY double; the y coordinate of another point on the ray
65       * @throws DrawRuntimeException when throughX == x and throughY == y
66       */
67      public Ray2d(final double x, final double y, final double throughX, final double throughY) throws DrawRuntimeException
68      {
69          super(x, y);
70          Throw.when(throughX == x && throughY == y, DrawRuntimeException.class,
71                  "the coordinates of the through point must differ from (x, y)");
72          this.phi = Math.atan2(throughY - y, throughX - x);
73      }
74  
75      /**
76       * Construct a new Ray2d.
77       * @param point Point2d; the finite end point of the ray
78       * @param throughX double; the x coordinate of another point on the ray
79       * @param throughY double; the y coordinate of another point on the ray
80       * @throws NullPointerException when point is null
81       * @throws DrawRuntimeException when throughX == point.x and throughY == point.y
82       */
83      public Ray2d(final Point2d point, final double throughX, final double throughY)
84              throws NullPointerException, DrawRuntimeException
85      {
86          this(Throw.whenNull(point, "point may not be null").x, point.y, throughX, throughY);
87      }
88  
89      /**
90       * Construct a new Ray2d.
91       * @param x double; the x coordinate of the finite end point of the ray
92       * @param y double; the y coordinate of the finite end point of the ray
93       * @param throughPoint Point2d; another point on the ray
94       * @throws NullPointerException when throughPoint is null
95       * @throws DrawRuntimeException when throughPoint is exactly at (x, y)
96       */
97      public Ray2d(final double x, final double y, final Point2d throughPoint) throws NullPointerException, DrawRuntimeException
98      {
99          this(x, y, Throw.whenNull(throughPoint, "througPoint may not be null").x, throughPoint.y);
100     }
101 
102     /**
103      * Construct a new Ray2d.
104      * @param point Point2d; the finite end point of the ray
105      * @param throughPoint Point2d; another point on the ray
106      * @throws NullPointerException when point is null or throughPoint is null
107      * @throws DrawRuntimeException when throughPoint is exactly at point
108      */
109     public Ray2d(final Point2dd.html#Point2d">Point2d point, final Point2d throughPoint) throws NullPointerException, DrawRuntimeException
110     {
111         this(Throw.whenNull(point, "point may not be null").x, point.y,
112                 Throw.whenNull(throughPoint, "throughPoint may not be null").x, throughPoint.y);
113     }
114 
115     /** {@inheritDoc} */
116     @Override
117     public final double getPhi()
118     {
119         return this.phi;
120     }
121 
122     /** {@inheritDoc} */
123     @Override
124     public Point2d getEndPoint()
125     {
126         return new Point2d(this.x, this.y);
127     }
128 
129     /** {@inheritDoc} */
130     @Override
131     public int size()
132     {
133         return 2;
134     }
135 
136     /** {@inheritDoc} */
137     @Override
138     public Iterator<Point2d> getPoints()
139     {
140         double cosPhi = Math.cos(this.phi);
141         double sinPhi = Math.sin(this.phi);
142         Point2doint2d.html#Point2d">Point2d.html#Point2d">Point2d[] array = new Point2doint2d.html#Point2d">Point2d[] { new Point2d(this.x, this.y),
143                 new Point2d(cosPhi == 0 ? this.x : cosPhi * Double.POSITIVE_INFINITY,
144                         sinPhi == 0 ? this.y : sinPhi * Double.POSITIVE_INFINITY) };
145         return Arrays.stream(array).iterator();
146     }
147 
148     /** {@inheritDoc} */
149     @Override
150     public Bounds2d getBounds()
151     {
152         double cosPhi = Math.cos(this.phi);
153         double sinPhi = Math.sin(this.phi);
154         return new Bounds2d(cosPhi >= 0 ? this.x : Double.NEGATIVE_INFINITY, cosPhi <= 0 ? this.x : Double.POSITIVE_INFINITY,
155                 sinPhi >= 0 ? this.y : Double.NEGATIVE_INFINITY, sinPhi <= 0 ? this.y : Double.POSITIVE_INFINITY);
156     }
157 
158     /** {@inheritDoc} */
159     @Override
160     public Ray2d neg()
161     {
162         return new Ray2d(-this.x, -this.y, this.phi + Math.PI);
163     }
164 
165     /** {@inheritDoc} */
166     @Override
167     public Ray2d flip()
168     {
169         return new Ray2d(this.x, this.y, this.phi + Math.PI);
170     }
171 
172     /** {@inheritDoc} */
173     @Override
174     public Ray2d getLocationExtended(final double position) throws DrawRuntimeException
175     {
176         Throw.when(Double.isNaN(position) || Double.isInfinite(position), DrawRuntimeException.class,
177                 "position must be finite");
178         return new Ray2d(this.x + Math.cos(this.phi) * position, this.y + Math.sin(this.phi) * position, this.phi);
179     }
180 
181     /** {@inheritDoc} */
182     @Override
183     public Point2dt2d">Point2d closestPointOnRay(final Point2d point) throws NullPointerException
184     {
185         Throw.whenNull(point, "point may not be null");
186         double dX = Math.cos(this.phi);
187         double dY = Math.sin(this.phi);
188         return point.closestPointOnLine(this.x, this.y, this.x + dX, this.y + dY, true, false);
189     }
190 
191     /** {@inheritDoc} */
192     @Override
193     public Point2dt2d">Point2d projectOrthogonal(final Point2d point) throws NullPointerException
194     {
195         Throw.whenNull(point, "point may not be null");
196         return point.closestPointOnLine(this.x, this.y, this.x + Math.cos(this.phi), this.y + Math.sin(this.phi), null, false);
197     }
198 
199     /** {@inheritDoc} */
200     @Override
201     public Point2dnt2d projectOrthogonalExtended(final Point2d point)
202     {
203         Throw.whenNull(point, "point may not be null");
204         return point.closestPointOnLine(getX(), getY(), getX() + Math.cos(this.phi), getY() + Math.sin(this.phi), false, false);
205     }
206 
207     /** {@inheritDoc} */
208     @Override
209     public double projectOrthogonalFractional(final Point2d point) throws NullPointerException
210     {
211         Throw.whenNull(point, "point may not be null");
212         return point.fractionalPositionOnLine(this.x, this.y, this.x + Math.cos(this.phi), this.y + Math.sin(this.phi), null,
213                 false);
214     }
215 
216     /** {@inheritDoc} */
217     @Override
218     public double projectOrthogonalFractionalExtended(final Point2d point) throws NullPointerException
219     {
220         Throw.whenNull(point, "point may not be null");
221         return point.fractionalPositionOnLine(this.x, this.y, this.x + Math.cos(this.phi), this.y + Math.sin(this.phi), false,
222                 false);
223     }
224 
225     /** {@inheritDoc} */
226     @Override
227     public boolean epsilonEquals(final Ray2d other, final double epsilonCoordinate, final double epsilonDirection)
228             throws NullPointerException, IllegalArgumentException
229     {
230         Throw.whenNull(other, "other point may not be null");
231         Throw.when(
232                 Double.isNaN(epsilonCoordinate) || epsilonCoordinate < 0 || Double.isNaN(epsilonDirection)
233                         || epsilonDirection < 0,
234                 IllegalArgumentException.class, "epsilon values may not be negative and may not be NaN");
235         if (Math.abs(this.x - other.x) > epsilonCoordinate)
236         {
237             return false;
238         }
239         if (Math.abs(this.y - other.y) > epsilonCoordinate)
240         {
241             return false;
242         }
243         if (Math.abs(AngleUtil.normalizeAroundZero(this.phi - other.phi)) > epsilonDirection)
244         {
245             return false;
246         }
247         return true;
248     }
249 
250     /** {@inheritDoc} */
251     @Override
252     public String toString()
253     {
254         return toString("%f", false);
255     }
256 
257     /** {@inheritDoc} */
258     @Override
259     public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
260     {
261         String format = String.format("%1$s[x=%2$s, y=%2$s, phi=%2%s]", doNotIncludeClassName ? "" : "Ray2d ", doubleFormat);
262         return String.format(Locale.US, format, this.x, this.y, this.phi);
263     }
264 
265     /** {@inheritDoc} */
266     @Override
267     public int hashCode()
268     {
269         final int prime = 31;
270         int result = super.hashCode();
271         long temp;
272         temp = Double.doubleToLongBits(this.phi);
273         result = prime * result + (int) (temp ^ (temp >>> 32));
274         return result;
275     }
276 
277     /** {@inheritDoc} */
278     @Override
279     @SuppressWarnings("checkstyle:needbraces")
280     public boolean equals(final Object obj)
281     {
282         if (this == obj)
283             return true;
284         if (!super.equals(obj))
285             return false;
286         if (getClass() != obj.getClass())
287             return false;
288         Ray2d="../../../../org/djutils/draw/line/Ray2d.html#Ray2d">Ray2d other = (Ray2d) obj;
289         if (Double.doubleToLongBits(this.phi) != Double.doubleToLongBits(other.phi))
290             return false;
291         return true;
292     }
293 
294 }