View Javadoc
1   package org.djutils.draw.point;
2   
3   import java.awt.geom.Point2D;
4   import java.util.ArrayList;
5   import java.util.Arrays;
6   import java.util.Iterator;
7   import java.util.List;
8   
9   import org.djutils.draw.DrawRuntimeException;
10  import org.djutils.draw.Drawable2d;
11  import org.djutils.draw.Space2d;
12  import org.djutils.draw.bounds.Bounds2d;
13  import org.djutils.draw.line.Ray2d;
14  import org.djutils.exceptions.Throw;
15  
16  /**
17   * A Point2d is an immutable Point with an x and y coordinate, stored with double precision. It differs from many Point
18   * implementations by being immutable.
19   * <p>
20   * Copyright (c) 2020-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
21   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
22   * </p>
23   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
24   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
25   */
26  public class Point2d implements Drawable2d, Point<Point2d, Space2d>
27  {
28      /** */
29      private static final long serialVersionUID = 20201201L;
30  
31      /** The x-coordinate. */
32      @SuppressWarnings("checkstyle:visibilitymodifier")
33      public final double x;
34  
35      /** The y-coordinate. */
36      @SuppressWarnings("checkstyle:visibilitymodifier")
37      public final double y;
38  
39      /**
40       * Create a new Point with just an x and y coordinate, stored with double precision.
41       * @param x double; the x coordinate
42       * @param y double; the y coordinate
43       * @throws IllegalArgumentException when x or y is NaN
44       */
45      public Point2d(final double x, final double y) throws IllegalArgumentException
46      {
47          Throw.when(Double.isNaN(x) || Double.isNaN(y), IllegalArgumentException.class, "Coordinate must be a number (not NaN)");
48          this.x = x;
49          this.y = y;
50      }
51  
52      /**
53       * Create a new Point with just an x and y coordinate, stored with double precision.
54       * @param xy double[]; the x and y coordinate
55       * @throws NullPointerException when xy is null
56       * @throws IllegalArgumentException when the dimension of xy is not 2, or a coordinate is NaN
57       */
58      public Point2d(final double[] xy) throws NullPointerException, IllegalArgumentException
59      {
60          this(checkLengthIsTwo(Throw.whenNull(xy, "xy-point cannot be null"))[0], xy[1]);
61      }
62  
63      /**
64       * Create an immutable point with just two values, x and y, stored with double precision from an AWT Point2D.
65       * @param point Point2D; an AWT Point2D
66       * @throws NullPointerException when point is null
67       * @throws IllegalArgumentException when point has a NaN coordinate
68       */
69      public Point2d(final Point2D point) throws NullPointerException, IllegalArgumentException
70      {
71          Throw.whenNull(point, "point cannot be null");
72          Throw.when(Double.isNaN(point.getX()) || Double.isNaN(point.getY()), IllegalArgumentException.class,
73                  "Coordinate must be a number (not NaN)");
74          this.x = point.getX();
75          this.y = point.getY();
76      }
77  
78      /**
79       * Throw an IllegalArgumentException if the length of the provided array is not two.
80       * @param xy double[]; the provided array
81       * @return double[]; the provided array
82       * @throws IllegalArgumentException when length of xy is not two
83       */
84      private static double[] checkLengthIsTwo(final double[] xy) throws IllegalArgumentException
85      {
86          Throw.when(xy.length != 2, IllegalArgumentException.class, "Length of xy-array must be 2");
87          return xy;
88      }
89  
90      /** {@inheritDoc} */
91      @Override
92      public final double getX()
93      {
94          return this.x;
95      }
96  
97      /** {@inheritDoc} */
98      @Override
99      public final double getY()
100     {
101         return this.y;
102     }
103 
104     /** {@inheritDoc} */
105     @Override
106     public double distance(final Point2d otherPoint)
107     {
108         Throw.whenNull(otherPoint, "point cannot be null");
109         return Math.hypot(otherPoint.x - this.x, otherPoint.y - this.y);
110     }
111 
112     /** {@inheritDoc} */
113     @Override
114     public double distanceSquared(final Point2d otherPoint) throws NullPointerException
115     {
116         Throw.whenNull(otherPoint, "point cannot be null");
117         double dx = getX() - otherPoint.x;
118         double dy = getY() - otherPoint.y;
119         return dx * dx + dy * dy;
120     }
121 
122     /** {@inheritDoc} */
123     @Override
124     public int size()
125     {
126         return 1;
127     }
128 
129     /** {@inheritDoc} */
130     @Override
131     public Iterator<? extends Point2d> getPoints()
132     {
133         return Arrays.stream(new Point2d[] {this}).iterator();
134     }
135 
136     /**
137      * Return a new Point with a translation by the provided dx and dy.
138      * @param dx double; the horizontal translation
139      * @param dy double; the vertical translation
140      * @return P; a new point with the translated coordinates
141      * @throws IllegalArgumentException when dx, or dy is NaN
142      */
143     public Point2d translate(final double dx, final double dy)
144     {
145         Throw.when(Double.isNaN(dx) || Double.isNaN(dy), IllegalArgumentException.class, "translation may not be NaN");
146         return new Point2d(getX() + dx, getY() + dy);
147     }
148 
149     /**
150      * Return a new Point3d with a translation by the provided delta-x, delta-y and deltaZ.
151      * @param dx double; the x translation
152      * @param dy double; the y translation
153      * @param dz double; the z translation
154      * @return Point2d; a new point with the translated coordinates
155      * @throws IllegalArgumentException when dx, dy, or dz is NaN
156      */
157     public Point3d translate(final double dx, final double dy, final double dz)
158     {
159         Throw.when(Double.isNaN(dx) || Double.isNaN(dy) || Double.isNaN(dz), IllegalArgumentException.class,
160                 "translation may not be NaN");
161         return new Point3d(getX() + dx, getY() + dy, dz);
162     }
163 
164     /** {@inheritDoc} */
165     @Override
166     public Point2d scale(final double factor)
167     {
168         Throw.when(Double.isNaN(factor), IllegalArgumentException.class, "factor must be a number (not NaN)");
169         return new Point2d(getX() * factor, getY() * factor);
170     }
171 
172     /** {@inheritDoc} */
173     @Override
174     public Point2d neg()
175     {
176         return scale(-1.0);
177     }
178 
179     /** {@inheritDoc} */
180     @Override
181     public Point2d abs()
182     {
183         return new Point2d(Math.abs(getX()), Math.abs(getY()));
184     }
185 
186     /** {@inheritDoc} */
187     @Override
188     public Point2d normalize() throws DrawRuntimeException
189     {
190         double length = Math.sqrt(getX() * getX() + getY() * getY());
191         Throw.when(length == 0.0, DrawRuntimeException.class, "cannot normalize (0.0, 0.0)");
192         return this.scale(1.0 / length);
193     }
194 
195     /** {@inheritDoc} */
196     @Override
197     public Point2dl#Point2d">Point2d interpolate(final Point2d point, final double fraction)
198     {
199         Throw.whenNull(point, "point cannot be null");
200         Throw.when(Double.isNaN(fraction), IllegalArgumentException.class, "fraction must be a number (not NaN)");
201         return new Point2d((1.0 - fraction) * getX() + fraction * point.x, (1.0 - fraction) * getY() + fraction * point.y);
202     }
203 
204     /** {@inheritDoc} */
205     @Override
206     public boolean epsilonEquals(final Point2d other, final double epsilon)
207     {
208         Throw.whenNull(other, "other point cannot be null");
209         if (Math.abs(getX() - other.x) > epsilon)
210         {
211             return false;
212         }
213         if (Math.abs(getY() - other.y) > epsilon)
214         {
215             return false;
216         }
217         return true;
218     }
219 
220     /** {@inheritDoc} */
221     @Override
222     public Bounds2d getBounds()
223     {
224         return new Bounds2d(this);
225     }
226 
227     /**
228      * Compute the 2D intersection of two lines. Both lines are defined by two points (that should be distinct).
229      * @param line1P1 Point2d; first point of line 1
230      * @param line1P2 Point2d; second point of line 1
231      * @param line2P1 Point2d; first point of line 2
232      * @param line2P2 Point2d; second point of line 2
233      * @return Point2d; the intersection of the two lines, or null if the lines are (almost) parallel
234      */
235     public static Point2dhtml#Point2d">Point2dhtml#Point2d">Point2dd">Point2d intersectionOfLines(final Point2dhtml#Point2d">Point2dhtml#Point2d">Point2d line1P1, final Point2dhtml#Point2d">Point2d line1P2, final Point2d line2P1,
236             final Point2d line2P2)
237     {
238         double l1p1x = line1P1.x;
239         double l1p1y = line1P1.y;
240         double l1p2x = line1P2.x - l1p1x;
241         double l1p2y = line1P2.y - l1p1y;
242         double l2p1x = line2P1.x - l1p1x;
243         double l2p1y = line2P1.y - l1p1y;
244         double l2p2x = line2P2.x - l1p1x;
245         double l2p2y = line2P2.y - l1p1y;
246         double denominator = (l2p2y - l2p1y) * l1p2x - (l2p2x - l2p1x) * l1p2y;
247         if (denominator == 0.0)
248         {
249             return null; // lines are parallel (they might even be on top of each other, but we don't check that)
250         }
251         double u = ((l2p2x - l2p1x) * (-l2p1y) - (l2p2y - l2p1y) * (-l2p1x)) / denominator;
252         return line1P1.interpolate(line1P2, u);
253     }
254 
255     /**
256      * Compute the 2D intersection of two line segments. Both line segments are defined by two points (that should be distinct).
257      * @param line1P1 Point2d; first point of line segment 1
258      * @param line1P2 Point2d; second point of line segment 1
259      * @param line2P1 Point2d; first point of line segment 2
260      * @param line2P2 Point2d; second point of line segment 2
261      * @return Point2d; the intersection of the two line segments, or null if the lines are (almost) parallel, or do not
262      *         intersect
263      */
264     public static Point2dhtml#Point2d">Point2dhtml#Point2d">Point2dt2d intersectionOfLineSegments(final Point2dhtml#Point2d">Point2dhtml#Point2d">Point2d line1P1, final Point2dhtml#Point2d">Point2d line1P2, final Point2d line2P1,
265             final Point2d line2P2)
266     {
267         double l1p1x = line1P1.x;
268         double l1p1y = line1P1.y;
269         double l1p2x = line1P2.x - l1p1x;
270         double l1p2y = line1P2.y - l1p1y;
271         double l2p1x = line2P1.x - l1p1x;
272         double l2p1y = line2P1.y - l1p1y;
273         double l2p2x = line2P2.x - l1p1x;
274         double l2p2y = line2P2.y - l1p1y;
275         double denominator = (l2p2y - l2p1y) * l1p2x - (l2p2x - l2p1x) * l1p2y;
276         if (denominator == 0.0)
277         {
278             return null; // lines are parallel (they might even be on top of each other, but we don't check that)
279         }
280         double uA = ((l2p2x - l2p1x) * (-l2p1y) - (l2p2y - l2p1y) * (-l2p1x)) / denominator;
281         // System.out.println("uA is " + uA);
282         if ((uA < 0.0) || (uA > 1.0))
283         {
284             return null; // intersection outside line 1
285         }
286         double uB = (l1p2y * l2p1x - l1p2x * l2p1y) / denominator;
287         // System.out.println("uB is " + uB);
288         if (uB < 0.0 || uB > 1.0)
289         {
290             return null; // intersection outside line 2
291         }
292         return line1P1.interpolate(line1P2, uA);
293         // return new Point2d(line1P1.x + uA * l1p2x, line1P1.y + uA * l1p2y);
294     }
295 
296     /** {@inheritDoc} */
297     @Override
298     public final Point2doint2d">Point2d>Point2d closestPointOnSegment(final Point2doint2d">Point2d segmentPoint1, final Point2d segmentPoint2)
299     {
300         double dX = segmentPoint2.x - segmentPoint1.x;
301         double dY = segmentPoint2.y - segmentPoint1.y;
302         if (0 == dX && 0 == dY) // The points may be equal (unlike in Segment2d)
303         {
304             return segmentPoint1;
305         }
306         final double u = ((this.x - segmentPoint1.x) * dX + (this.y - segmentPoint1.y) * dY) / (dX * dX + dY * dY);
307         if (u < 0)
308         {
309             return segmentPoint1;
310         }
311         else if (u > 1)
312         {
313             return segmentPoint2;
314         }
315         else
316         {
317             return segmentPoint1.interpolate(segmentPoint2, u);
318         }
319     }
320 
321     /** {@inheritDoc} */
322     @Override
323     public final Point2dl#Point2d">Point2d2d">Point2d closestPointOnLine(final Point2dl#Point2d">Point2d linePoint1, final Point2d linePoint2) throws DrawRuntimeException
324     {
325         double dX = linePoint2.x - linePoint1.x;
326         double dY = linePoint2.y - linePoint1.y;
327         Throw.when(dX == 0 && dY == 0, DrawRuntimeException.class, "line points are at same location");
328         final double u = ((this.x - linePoint1.x) * dX + (this.y - linePoint1.y) * dY) / (dX * dX + dY * dY);
329         return linePoint1.interpolate(linePoint2, u);
330     }
331 
332     /**
333      * Closest point on a ray.
334      * @param ray Ray2d; the ray
335      * @return Point2d; the point on the ray that is closest to this
336      */
337     public final Point2d closestPointOnLine(final Ray2d ray)
338     {
339         double dX = Math.cos(ray.phi);
340         double dY = Math.sin(ray.phi);
341         final double u = ((this.x - ray.x) * dX + (this.y - ray.y) * dY) / (dX * dX + dY * dY);
342         return ray.interpolate(new Point2d(ray.x + dX, ray.y + dY), Math.max(0, u));
343     }
344 
345     /**
346      * Return the zero, one or two intersections between two circles. The circles must be different. Derived from pseudo code by
347      * <a href="http://paulbourke.net/geometry/circlesphere/">Paul Bourke</a> and C implementation by
348      * <a href="http://paulbourke.net/geometry/circlesphere/tvoght.c">Tim Voght </a>.
349      * @param center1 Point2d; the center of circle 1
350      * @param radius1 double; the radius of circle 1
351      * @param center2 Point2d; the center of circle 2
352      * @param radius2 double; the radius of circle 2
353      * @return List&lt;Point2d&gt; a list of zero, one or two points
354      * @throws NullPointerException when center1 or center2 is null
355      * @throws DrawRuntimeException when the two circles are identical, or radius1 &lt; 0 or radius2 &lt; 0
356      */
357     public static final List<Point2d> circleIntersections(final Point2d center1, final Point2d_keyword">double radius1, final Point2d center2,
358             final double radius2) throws NullPointerException, DrawRuntimeException
359     {
360         Throw.whenNull(center1, "center1 may not be null");
361         Throw.whenNull(center2, "center2 may not be null");
362         Throw.when(radius1 < 0 || radius2 < 0, DrawRuntimeException.class, "radius may not be less than 0");
363         Throw.when(center1.equals(center2) && radius1 == radius2, DrawRuntimeException.class, "Circles must be different");
364         List<Point2d> result = new ArrayList<>();
365         // dX,dY is the vector from center1 to center2
366         double dX = center2.x - center1.x;
367         double dY = center2.y - center1.y;
368         double distance = Math.hypot(dX, dY);
369         if (distance > radius1 + radius2 || distance < Math.abs(radius1 - radius2))
370         {
371             return result;
372         }
373         double a = (radius1 * radius1 - radius2 * radius2 + distance * distance) / (2 * distance);
374         // x2,y2 is the point where the line through the circle intersections crosses the line through the circle centers
375         double x2 = center1.x + (dX * a / distance);
376         double y2 = center1.y + (dY * a / distance);
377         // h is distance from x2,y2 to each of the solutions
378         double h = Math.sqrt(radius1 * radius1 - a * a);
379         // rX, rY is vector from x2,y2 to the first solution
380         double rX = -dY * (h / distance);
381         double rY = dX * (h / distance);
382         result.add(new Point2d(x2 + rX, y2 + rY));
383         if (h > 0)
384         {
385             // Two distinct solutions; add the second one
386             result.add(new Point2d(x2 - rX, y2 - rY));
387         }
388         return result;
389     }
390 
391     /**
392      * Return the direction to another Point2d.
393      * @param otherPoint Point2d; the other point
394      * @return double; the direction to the other point in Radians (towards infinite X is 0; towards infinite Y is &pi; / 2;
395      *         etc.). If the points are identical; this method returns NaN.
396      */
397     public double directionTo(final Point2d otherPoint)
398     {
399         return Math.atan2(otherPoint.y - this.y, otherPoint.x - this.x);
400     }
401 
402     /**
403      * Return the coordinates as an AWT Point2D.Double object.
404      * @return Point2D; the coordinates as an AWT Point2D.Double object
405      */
406     public Point2D toPoint2D()
407     {
408         return new Point2D.Double(getX(), getY());
409     }
410 
411     /** {@inheritDoc} */
412     @Override
413     public String toString(final int fractionDigits)
414     {
415         int digits = fractionDigits < 0 ? 0 : fractionDigits;
416         String format = String.format("(%%.%1$df,%%.%1$df)", digits);
417         return String.format(format, getX(), getY());
418     }
419 
420     /** {@inheritDoc} */
421     @Override
422     public String toString()
423     {
424         return String.format("(%f,%f)", getX(), getY());
425     }
426 
427     /** {@inheritDoc} */
428     @Override
429     public int hashCode()
430     {
431         final int prime = 31;
432         int result = 1;
433         long temp;
434         temp = Double.doubleToLongBits(this.x);
435         result = prime * result + (int) (temp ^ (temp >>> 32));
436         temp = Double.doubleToLongBits(this.y);
437         result = prime * result + (int) (temp ^ (temp >>> 32));
438         return result;
439     }
440 
441     /** {@inheritDoc} */
442     @SuppressWarnings("checkstyle:needbraces")
443     @Override
444     public boolean equals(final Object obj)
445     {
446         if (this == obj)
447             return true;
448         if (obj == null)
449             return false;
450         if (getClass() != obj.getClass())
451             return false;
452         Point2d../../../../org/djutils/draw/point/Point2d.html#Point2d">Point2d other = (Point2d) obj;
453         if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x))
454             return false;
455         if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y))
456             return false;
457         return true;
458     }
459 
460 }