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