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.draw.DrawRuntimeException;
8   import org.djutils.draw.Drawable2d;
9   import org.djutils.draw.bounds.Bounds2d;
10  import org.djutils.draw.point.Point2d;
11  import org.djutils.exceptions.Throw;
12  
13  /**
14   * LineSegment2d is a line segment bound by 2 end points in 2D-space. A line segment stores the order in which it has been
15   * created, so the end points are known as 'start' and 'end'.
16   * <p>
17   * Copyright (c) 2020-2024 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 LineSegment2d implements Drawable2d, LineSegment<Point2d, Ray2d>
24  {
25      /** */
26      private static final long serialVersionUID = 20210121L;
27  
28      /** The start x-coordinate. */
29      @SuppressWarnings("checkstyle:visibilitymodifier")
30      public final double startX;
31  
32      /** The start y-coordinate. */
33      @SuppressWarnings("checkstyle:visibilitymodifier")
34      public final double startY;
35  
36      /** The end x-coordinate. */
37      @SuppressWarnings("checkstyle:visibilitymodifier")
38      public final double endX;
39  
40      /** The end y-coordinate. */
41      @SuppressWarnings("checkstyle:visibilitymodifier")
42      public final double endY;
43  
44      /**
45       * Construct a new LineSegment2d start four coordinates.
46       * @param startX double; the x-coordinate of the start point
47       * @param startY double; the y-coordinate of the start point
48       * @param endX double; the x-coordinate of the end point
49       * @param endY double; the y-coordinate of the end point
50       * @throws DrawRuntimeException when (startX,startY) is equals end (endX,endY)
51       */
52      public LineSegment2d(final double startX, final double startY, final double endX, final double endY)
53              throws DrawRuntimeException
54      {
55          Throw.when(startX == endX && startY == endY, DrawRuntimeException.class, "Start and end may not be equal");
56          this.startX = startX;
57          this.startY = startY;
58          this.endX = endX;
59          this.endY = endY;
60      }
61  
62      /**
63       * Construct a new LineSegment2d start a Point2d and two coordinates.
64       * @param start Point2d; the start point
65       * @param endX double; the x-coordinate of the end point
66       * @param endY double; the y-coordinate of the end point
67       * @throws NullPointerException when start is null
68       * @throws DrawRuntimeException when start has the exact coordinates endX, endY
69       */
70      public LineSegment2d(final Point2d start, final double endX, final double endY)
71              throws NullPointerException, DrawRuntimeException
72      {
73          this(Throw.whenNull(start, "start point may not be null").x, start.y, endX, endY);
74      }
75  
76      /**
77       * Construct a new LineSegment2d start two coordinates and a Point2d.
78       * @param startX double; the x-coordinate of the start point
79       * @param startY double; the y-coordinate of the start point
80       * @param end Point2d; the end point
81       * @throws NullPointerException when end is null
82       * @throws DrawRuntimeException when end has the exact coordinates startX, startY
83       */
84      public LineSegment2d(final double startX, final double startY, final Point2d end)
85              throws NullPointerException, DrawRuntimeException
86      {
87          this(startX, startY, Throw.whenNull(end, "end point may not be null").x, end.y);
88      }
89  
90      /**
91       * Construct a new LineSegment2d start two Point2d objects.
92       * @param start Point2d; the start point
93       * @param end Point2d; the end point
94       * @throws NullPointerException when start is null
95       * @throws DrawRuntimeException when start has the exact coordinates endX, endY
96       */
97      public LineSegment2d(final Point2d start, final Point2d end) throws NullPointerException, DrawRuntimeException
98      {
99          this(Throw.whenNull(start, "start point may not be null").x, start.y,
100                 Throw.whenNull(end, "end point may not be null").x, end.y);
101     }
102 
103     /** {@inheritDoc} */
104     @Override
105     public Point2d getStartPoint()
106     {
107         return new Point2d(this.startX, this.startY);
108     }
109 
110     /** {@inheritDoc} */
111     @Override
112     public Point2d getEndPoint()
113     {
114         return new Point2d(this.endX, this.endY);
115     }
116 
117     /** {@inheritDoc} */
118     @Override
119     public double getLength()
120     {
121         return Math.hypot(this.endX - this.startX, this.endY - this.startY);
122     }
123 
124     /** {@inheritDoc} */
125     @Override
126     public Iterator<? extends Point2d> getPoints()
127     {
128         return Arrays.stream(new Point2d[] { getStartPoint(), getEndPoint() }).iterator();
129     }
130 
131     /** {@inheritDoc} */
132     @Override
133     public int size()
134     {
135         return 2;
136     }
137 
138     /** {@inheritDoc} */
139     @Override
140     public Bounds2d getBounds()
141     {
142         return new Bounds2d(Math.min(this.startX, this.endX), Math.max(this.startX, this.endX),
143                 Math.min(this.startY, this.endY), Math.max(this.startY, this.endY));
144     }
145 
146     /** {@inheritDoc} */
147     @Override
148     public Ray2d getLocationExtended(final double position) throws DrawRuntimeException
149     {
150         Throw.when(Double.isNaN(position) || Double.isInfinite(position), DrawRuntimeException.class,
151                 "position must be finite");
152         double dX = this.endX - this.startX;
153         double dY = this.endY - this.startY;
154         double length = Math.hypot(dX, dY);
155         return new Ray2d(this.startX + position * dX / length, this.startY + position * dY / length, Math.atan2(dY, dX));
156     }
157 
158     /** {@inheritDoc} */
159     @Override
160     public Point2d closestPointOnSegment(final Point2d point) throws NullPointerException
161     {
162         Throw.whenNull(point, "point may not be null");
163         return point.closestPointOnLine(this.startX, this.startY, this.endX, this.endY, true, true);
164     }
165     
166     /** {@inheritDoc} */
167     @Override
168     public LineSegment2d reverse()
169     {
170         return new LineSegment2d(this.endX, this.endY, this.startX, this.startY);
171     }
172 
173     /** {@inheritDoc} */
174     @Override
175     public Point2d projectOrthogonal(final Point2d point) throws NullPointerException
176     {
177         Throw.whenNull(point, "point may not be null");
178         return point.closestPointOnLine(this.startX, this.startY, this.endX, this.endY, null, null);
179     }
180 
181     /** {@inheritDoc} */
182     @Override
183     public Point2d projectOrthogonalExtended(final Point2d point)
184     {
185         Throw.whenNull(point, "point may not be null");
186         return point.closestPointOnLine(this.startX, this.startY, this.endX, this.endY, false, false);
187     }
188 
189     /** {@inheritDoc} */
190     @Override
191     public double projectOrthogonalFractional(final Point2d point) throws NullPointerException
192     {
193         Throw.whenNull(point, "point may not be null");
194         return point.fractionalPositionOnLine(this.startX, this.startY, this.endX, this.endY, null, null);
195     }
196 
197     /** {@inheritDoc} */
198     @Override
199     public double projectOrthogonalFractionalExtended(final Point2d point) throws NullPointerException
200     {
201         Throw.whenNull(point, "point may not be null");
202         return point.fractionalPositionOnLine(this.startX, this.startY, this.endX, this.endY, false, false);
203     }
204 
205     /** {@inheritDoc} */
206     @Override
207     public String toString()
208     {
209         return toString("%f", false);
210     }
211 
212     /** {@inheritDoc} */
213     @Override
214     public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
215     {
216         String format = String.format("%1$s[startX=%2$s, startY=%2$s - endX=%2%s, endY=%2$s]",
217                 doNotIncludeClassName ? "" : "LineSegment2d ", doubleFormat);
218         return String.format(Locale.US, format, this.startX, this.startY, this.endX, this.endY);
219     }
220 
221     /** {@inheritDoc} */
222     @Override
223     public String toExcel()
224     {
225         return this.startX + "\t" + this.startY + "\n" + this.endX + "\t" + this.endY + "\n";
226     }
227 
228     /**
229      * Convert this PolyLine2D to Peter's plot format.
230      * @return Peter's format plot output
231      */
232     public String toPlot()
233     {
234         return String.format(Locale.US, "M%.3f,%.3fL%.3f,%.3f\n", this.startX, this.startY, this.endX, this.endY);
235     }
236 
237     /** {@inheritDoc} */
238     @Override
239     public int hashCode()
240     {
241         final int prime = 31;
242         int result = 1;
243         long temp;
244         temp = Double.doubleToLongBits(this.endX);
245         result = prime * result + (int) (temp ^ (temp >>> 32));
246         temp = Double.doubleToLongBits(this.endY);
247         result = prime * result + (int) (temp ^ (temp >>> 32));
248         temp = Double.doubleToLongBits(this.startX);
249         result = prime * result + (int) (temp ^ (temp >>> 32));
250         temp = Double.doubleToLongBits(this.startY);
251         result = prime * result + (int) (temp ^ (temp >>> 32));
252         return result;
253     }
254 
255     /** {@inheritDoc} */
256     @Override
257     @SuppressWarnings("checkstyle:needbraces")
258     public boolean equals(final Object obj)
259     {
260         if (this == obj)
261             return true;
262         if (obj == null)
263             return false;
264         if (getClass() != obj.getClass())
265             return false;
266         LineSegment2d other = (LineSegment2d) obj;
267         if (Double.doubleToLongBits(this.endX) != Double.doubleToLongBits(other.endX))
268             return false;
269         if (Double.doubleToLongBits(this.endY) != Double.doubleToLongBits(other.endY))
270             return false;
271         if (Double.doubleToLongBits(this.startX) != Double.doubleToLongBits(other.startX))
272             return false;
273         if (Double.doubleToLongBits(this.startY) != Double.doubleToLongBits(other.startY))
274             return false;
275         return true;
276     }
277 
278 }