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  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  public class LineSegment2d implements Drawable2d, LineSegment<Point2d, DirectedPoint2d>
25  {
26      
27      @SuppressWarnings("checkstyle:visibilitymodifier")
28      public final double startX;
29  
30      
31      @SuppressWarnings("checkstyle:visibilitymodifier")
32      public final double startY;
33  
34      
35      @SuppressWarnings("checkstyle:visibilitymodifier")
36      public final double endX;
37  
38      
39      @SuppressWarnings("checkstyle:visibilitymodifier")
40      public final double endY;
41  
42      
43  
44  
45  
46  
47  
48  
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  
61  
62  
63  
64  
65  
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  
74  
75  
76  
77  
78  
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  
88  
89  
90  
91  
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 }