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.Drawable2d;
8   import org.djutils.draw.bounds.Bounds2d;
9   import org.djutils.draw.point.DirectedPoint2d;
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-2025 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://github.com/peter-knoppers">Peter Knoppers</a>
22   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
23   */
24  public class LineSegment2d implements Drawable2d, LineSegment<Point2d, DirectedPoint2d>
25  {
26      /** */
27      private static final long serialVersionUID = 20210121L;
28  
29      /** The start x-coordinate. */
30      @SuppressWarnings("checkstyle:visibilitymodifier")
31      public final double startX;
32  
33      /** The start y-coordinate. */
34      @SuppressWarnings("checkstyle:visibilitymodifier")
35      public final double startY;
36  
37      /** The end x-coordinate. */
38      @SuppressWarnings("checkstyle:visibilitymodifier")
39      public final double endX;
40  
41      /** The end y-coordinate. */
42      @SuppressWarnings("checkstyle:visibilitymodifier")
43      public final double endY;
44  
45      /**
46       * Construct a new LineSegment2d from four coordinates.
47       * @param startX the x-coordinate of the start point
48       * @param startY the y-coordinate of the start point
49       * @param endX the x-coordinate of the end point
50       * @param endY the y-coordinate of the end point
51       * @throws IllegalArgumentException when <code>(startX,startY)</code> is the same as<code>(endX,endY)</code>
52       */
53      public LineSegment2d(final double startX, final double startY, final double endX, final double endY)
54      {
55          Throw.when(startX == endX && startY == endY, IllegalArgumentException.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 from a Point2d and two coordinates.
64       * @param start the start point
65       * @param endX the x-coordinate of the end point
66       * @param endY the y-coordinate of the end point
67       * @throws NullPointerException when <code>start</code> is <code>null</code>
68       * @throws IllegalArgumentException when <code>start</code> has the exact coordinates <code>(endX,endY)</code>
69       */
70      public LineSegment2d(final Point2d start, final double endX, final double endY)
71      {
72          this(Throw.whenNull(start, "start").x, start.y, endX, endY);
73      }
74  
75      /**
76       * Construct a new LineSegment2d from two coordinates and a Point2d.
77       * @param startX the x-coordinate of the start point
78       * @param startY the y-coordinate of the start point
79       * @param end the end point
80       * @throws NullPointerException when <code>end</code> is <code>null</code>
81       * @throws IllegalArgumentException when <code>end</code> has the exact coordinates <code>(startX,startY)</code>
82       */
83      public LineSegment2d(final double startX, final double startY, final Point2d end)
84              throws NullPointerException, IllegalArgumentException
85      {
86          this(startX, startY, Throw.whenNull(end, "end").x, end.y);
87      }
88  
89      /**
90       * Construct a new LineSegment2d from two Point2d objects.
91       * @param start the start point
92       * @param end the end point
93       * @throws NullPointerException when <code>start</code>, or <code>end</code> is <code>null</code>
94       * @throws IllegalArgumentException when <code>start</code> has the same coordinates as <code>end</code>
95       */
96      public LineSegment2d(final Point2d start, final Point2d end)
97      {
98          this(Throw.whenNull(start, "start").x, start.y, Throw.whenNull(end, "end").x, end.y);
99      }
100 
101     @Override
102     public Point2d getStartPoint()
103     {
104         return new Point2d(this.startX, this.startY);
105     }
106 
107     @Override
108     public Point2d getEndPoint()
109     {
110         return new Point2d(this.endX, this.endY);
111     }
112 
113     @Override
114     public double getLength()
115     {
116         return Math.hypot(this.endX - this.startX, this.endY - this.startY);
117     }
118 
119     @Override
120     public Iterator<Point2d> iterator()
121     {
122         return Arrays.stream(new Point2d[] {getStartPoint(), getEndPoint()}).iterator();
123     }
124 
125     @Override
126     public int size()
127     {
128         return 2;
129     }
130 
131     @Override
132     public Bounds2d getBounds()
133     {
134         return new Bounds2d(Math.min(this.startX, this.endX), Math.max(this.startX, this.endX),
135                 Math.min(this.startY, this.endY), Math.max(this.startY, this.endY));
136     }
137 
138     @Override
139     public DirectedPoint2d getLocationExtended(final double position)
140     {
141         Throw.whenNaN(position, "position");
142         Throw.when(Double.isInfinite(position), IllegalArgumentException.class, "position must be finite");
143         double dX = this.endX - this.startX;
144         double dY = this.endY - this.startY;
145         double length = Math.hypot(dX, dY);
146         return new DirectedPoint2d(this.startX + position * dX / length, this.startY + position * dY / length,
147                 Math.atan2(dY, dX));
148     }
149 
150     @Override
151     public Point2d closestPointOnSegment(final Point2d point)
152     {
153         Throw.whenNull(point, "point");
154         return point.closestPointOnLine(this.startX, this.startY, this.endX, this.endY, true, true);
155     }
156 
157     @Override
158     public LineSegment2d reverse()
159     {
160         return new LineSegment2d(this.endX, this.endY, this.startX, this.startY);
161     }
162 
163     @Override
164     public Point2d projectOrthogonal(final Point2d point)
165     {
166         Throw.whenNull(point, "point");
167         return point.closestPointOnLine(this.startX, this.startY, this.endX, this.endY, null, null);
168     }
169 
170     @Override
171     public Point2d projectOrthogonalExtended(final Point2d point)
172     {
173         Throw.whenNull(point, "point");
174         return point.closestPointOnLine(this.startX, this.startY, this.endX, this.endY, false, false);
175     }
176 
177     @Override
178     public double projectOrthogonalFractional(final Point2d point)
179     {
180         Throw.whenNull(point, "point");
181         return point.fractionalPositionOnLine(this.startX, this.startY, this.endX, this.endY, null, null);
182     }
183 
184     @Override
185     public double projectOrthogonalFractionalExtended(final Point2d point)
186     {
187         Throw.whenNull(point, "point");
188         return point.fractionalPositionOnLine(this.startX, this.startY, this.endX, this.endY, false, false);
189     }
190 
191     @Override
192     public String toString()
193     {
194         return toString("%f", false);
195     }
196 
197     @Override
198     public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
199     {
200         String format = String.format("%1$s[startX=%2$s, startY=%2$s - endX=%2%s, endY=%2$s]",
201                 doNotIncludeClassName ? "" : "LineSegment2d ", doubleFormat);
202         return String.format(Locale.US, format, this.startX, this.startY, this.endX, this.endY);
203     }
204 
205     @Override
206     public int hashCode()
207     {
208         final int prime = 31;
209         int result = 1;
210         long temp;
211         temp = Double.doubleToLongBits(this.endX);
212         result = prime * result + (int) (temp ^ (temp >>> 32));
213         temp = Double.doubleToLongBits(this.endY);
214         result = prime * result + (int) (temp ^ (temp >>> 32));
215         temp = Double.doubleToLongBits(this.startX);
216         result = prime * result + (int) (temp ^ (temp >>> 32));
217         temp = Double.doubleToLongBits(this.startY);
218         result = prime * result + (int) (temp ^ (temp >>> 32));
219         return result;
220     }
221 
222     @Override
223     @SuppressWarnings("checkstyle:needbraces")
224     public boolean equals(final Object obj)
225     {
226         if (this == obj)
227             return true;
228         if (obj == null)
229             return false;
230         if (getClass() != obj.getClass())
231             return false;
232         LineSegment2d other = (LineSegment2d) obj;
233         if (Double.doubleToLongBits(this.endX) != Double.doubleToLongBits(other.endX))
234             return false;
235         if (Double.doubleToLongBits(this.endY) != Double.doubleToLongBits(other.endY))
236             return false;
237         if (Double.doubleToLongBits(this.startX) != Double.doubleToLongBits(other.startX))
238             return false;
239         if (Double.doubleToLongBits(this.startY) != Double.doubleToLongBits(other.startY))
240             return false;
241         return true;
242     }
243 
244 }