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