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