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 import java.util.Locale;
9
10 import org.djutils.draw.DrawRuntimeException;
11 import org.djutils.draw.Drawable2d;
12 import org.djutils.draw.bounds.Bounds2d;
13 import org.djutils.draw.line.LineSegment2d;
14 import org.djutils.exceptions.Throw;
15
16
17
18
19
20
21
22
23
24
25
26 public class Point2d implements Drawable2d, Point<Point2d>
27 {
28
29 private static final long serialVersionUID = 20201201L;
30
31
32 @SuppressWarnings("checkstyle:visibilitymodifier")
33 public final double x;
34
35
36 @SuppressWarnings("checkstyle:visibilitymodifier")
37 public final double y;
38
39
40
41
42
43
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
54
55
56
57
58 public Point2d(final double[] xy) throws NullPointerException, IllegalArgumentException
59 {
60 this(checkLengthIsTwo(Throw.whenNull(xy, "xy"))[0], xy[1]);
61 }
62
63
64
65
66
67
68
69 public Point2d(final Point2D point) throws NullPointerException, IllegalArgumentException
70 {
71 Throw.whenNull(point, "point");
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
80
81
82
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 @Override
91 public final double getX()
92 {
93 return this.x;
94 }
95
96 @Override
97 public final double getY()
98 {
99 return this.y;
100 }
101
102 @Override
103 public double distance(final Point2d otherPoint)
104 {
105 Throw.whenNull(otherPoint, "otherPoint");
106 return Math.hypot(otherPoint.x - this.x, otherPoint.y - this.y);
107 }
108
109 @Override
110 public double distanceSquared(final Point2d otherPoint) throws NullPointerException
111 {
112 Throw.whenNull(otherPoint, "otherPoint");
113 double dx = this.x - otherPoint.x;
114 double dy = this.y - otherPoint.y;
115 return dx * dx + dy * dy;
116 }
117
118 @Override
119 public int size()
120 {
121 return 1;
122 }
123
124 @Override
125 public Iterator<? extends Point2d> getPoints()
126 {
127 return Arrays.stream(new Point2d[] {this}).iterator();
128 }
129
130
131
132
133
134
135
136
137 public Point2d translate(final double dx, final double dy)
138 {
139 Throw.when(Double.isNaN(dx) || Double.isNaN(dy), IllegalArgumentException.class, "translation may not be NaN");
140 return new Point2d(this.x + dx, this.y + dy);
141 }
142
143
144
145
146
147
148
149
150
151
152 public Point3d translate(final double dx, final double dy, final double dz)
153 {
154 Throw.when(Double.isNaN(dx) || Double.isNaN(dy) || Double.isNaN(dz), IllegalArgumentException.class,
155 "translation may not be NaN");
156 return new Point3d(this.x + dx, this.y + dy, dz);
157 }
158
159 @Override
160 public Point2d scale(final double factor)
161 {
162 Throw.when(Double.isNaN(factor), IllegalArgumentException.class, "factor must be a number (not NaN)");
163 return new Point2d(this.x * factor, this.y * factor);
164 }
165
166 @Override
167 public Point2d neg()
168 {
169 return scale(-1.0);
170 }
171
172 @Override
173 public Point2d abs()
174 {
175 return new Point2d(Math.abs(this.x), Math.abs(this.y));
176 }
177
178 @Override
179 public Point2d normalize() throws DrawRuntimeException
180 {
181 double length = Math.sqrt(this.x * this.x + this.y * this.y);
182 Throw.when(length == 0.0, DrawRuntimeException.class, "cannot normalize (0.0, 0.0)");
183 return this.scale(1.0 / length);
184 }
185
186 @Override
187 public Point2d interpolate(final Point2d otherPoint, final double fraction)
188 {
189 Throw.whenNull(otherPoint, "otherPoint");
190 Throw.when(Double.isNaN(fraction), IllegalArgumentException.class, "fraction must be a number (not NaN)");
191 return new Point2d((1.0 - fraction) * this.x + fraction * otherPoint.x,
192 (1.0 - fraction) * this.y + fraction * otherPoint.y);
193 }
194
195 @Override
196 public boolean epsilonEquals(final Point2d otherPoint, final double epsilon)
197 {
198 Throw.whenNull(otherPoint, "otherPoint");
199 if (Math.abs(this.x - otherPoint.x) > epsilon)
200 {
201 return false;
202 }
203 if (Math.abs(this.y - otherPoint.y) > epsilon)
204 {
205 return false;
206 }
207 return true;
208 }
209
210 @Override
211 public Bounds2d getBounds()
212 {
213 return new Bounds2d(this);
214 }
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234 @SuppressWarnings("checkstyle:parameternumber")
235 public static Point2d intersectionOfLines(final double line1P1X, final double line1P1Y, final double line1P2X,
236 final double line1P2Y, final boolean lowLimitLine1, final boolean highLimitLine1, final double line2P1X,
237 final double line2P1Y, final double line2P2X, final double line2P2Y, final boolean lowLimitLine2,
238 final boolean highLimitLine2) throws DrawRuntimeException
239 {
240 double line1DX = line1P2X - line1P1X;
241 double line1DY = line1P2Y - line1P1Y;
242 double l2p1x = line2P1X - line1P1X;
243 double l2p1y = line2P1Y - line1P1Y;
244 double l2p2x = line2P2X - line1P1X;
245 double l2p2y = line2P2Y - line1P1Y;
246 double denominator = (l2p2y - l2p1y) * line1DX - (l2p2x - l2p1x) * line1DY;
247 Throw.when(Double.isNaN(denominator), DrawRuntimeException.class, "NaN value not permitted");
248 if (denominator == 0.0)
249 {
250 return null;
251 }
252 double uA = ((l2p2x - l2p1x) * (-l2p1y) - (l2p2y - l2p1y) * (-l2p1x)) / denominator;
253
254 if (uA < 0.0 && lowLimitLine1 || uA > 1.0 && highLimitLine1)
255 {
256 return null;
257 }
258 double uB = (line1DY * l2p1x - line1DX * l2p1y) / denominator;
259
260 if (uB < 0.0 && lowLimitLine2 || uB > 1.0 && highLimitLine2)
261 {
262 return null;
263 }
264 if (uA == 1.0)
265 {
266 return new Point2d(line1P2X, line1P2Y);
267 }
268 if (uB == 0.0)
269 {
270 return new Point2d(line2P1X, line2P1Y);
271 }
272 if (uB == 1.0)
273 {
274 return new Point2d(line2P2X, line2P2Y);
275 }
276 return new Point2d(line1P1X + uA * line1DX, line1P1Y + uA * line1DY);
277 }
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293 @SuppressWarnings("checkstyle:parameternumber")
294 public static Point2d intersectionOfLines(final double l1P1X, final double l1P1Y, final double l1P2X, final double l1P2Y,
295 final double l2P1X, final double l2P1Y, final double l2P2X, final double l2P2Y) throws DrawRuntimeException
296 {
297 return intersectionOfLines(l1P1X, l1P1Y, l1P2X, l1P2Y, false, false, l2P1X, l2P1Y, l2P2X, l2P2Y, false, false);
298 }
299
300
301
302
303
304
305
306
307
308
309
310 public static Point2d intersectionOfLines(final Point2d line1P1, final Point2d line1P2, final Point2d line2P1,
311 final Point2d line2P2) throws NullPointerException
312 {
313 Throw.when(line1P1 == null || line1P2 == null || line2P1 == null || line2P2 == null, NullPointerException.class,
314 "Points may not be null");
315 return intersectionOfLines(line1P1.x, line1P1.y, line1P2.x, line1P2.y, false, false, line2P1.x, line2P1.y, line2P2.x,
316 line2P2.y, false, false);
317 }
318
319
320
321
322
323
324
325
326
327
328
329
330
331 public static Point2d intersectionOfLineSegments(final Point2d line1P1, final Point2d line1P2, final Point2d line2P1,
332 final Point2d line2P2) throws NullPointerException, DrawRuntimeException
333 {
334 Throw.when(line1P1 == null || line1P2 == null || line2P1 == null || line2P2 == null, NullPointerException.class,
335 "Points may not be null");
336 return intersectionOfLines(line1P1.x, line1P1.y, line1P2.x, line1P2.y, true, true, line2P1.x, line2P1.y, line2P2.x,
337 line2P2.y, true, true);
338 }
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354 @SuppressWarnings("checkstyle:parameternumber")
355 public static Point2d intersectionOfLineSegments(final double line1P1X, final double line1P1Y, final double line1P2X,
356 final double line1P2Y, final double line2P1X, final double line2P1Y, final double line2P2X, final double line2P2Y)
357 throws DrawRuntimeException
358 {
359 return intersectionOfLines(line1P1X, line1P1Y, line1P2X, line1P2Y, true, true, line2P1X, line2P1Y, line2P2X, line2P2Y,
360 true, true);
361 }
362
363
364
365
366
367
368
369 public static Point2d intersectionOfLineSegments(final LineSegment2d segment1, final LineSegment2d segment2)
370 {
371 return intersectionOfLineSegments(segment1.startX, segment1.startY, segment1.endX, segment1.endY, segment2.startX,
372 segment2.startY, segment2.endX, segment2.endY);
373 }
374
375 @Override
376 public Point2d closestPointOnSegment(final Point2d segmentPoint1, final Point2d segmentPoint2)
377 {
378 Throw.whenNull(segmentPoint1, "segmentPoint1");
379 Throw.whenNull(segmentPoint2, "segmentPoint2");
380 return closestPointOnSegment(segmentPoint1.x, segmentPoint1.y, segmentPoint2.x, segmentPoint2.y);
381 }
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398 public Point2d closestPointOnLine(final double p1X, final double p1Y, final double p2X, final double p2Y,
399 final Boolean lowLimitHandling, final Boolean highLimitHandling) throws DrawRuntimeException
400 {
401 double fraction = fractionalPositionOnLine(p1X, p1Y, p2X, p2Y, lowLimitHandling, highLimitHandling);
402 if (Double.isNaN(fraction))
403 {
404 return null;
405 }
406 if (fraction == 1.0)
407 {
408 return new Point2d(p2X, p2Y);
409 }
410 return new Point2d(p1X + fraction * (p2X - p1X), p1Y + fraction * (p2Y - p1Y));
411 }
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431 public double fractionalPositionOnLine(final double p1X, final double p1Y, final double p2X, final double p2Y,
432 final Boolean lowLimitHandling, final Boolean highLimitHandling) throws DrawRuntimeException
433 {
434 double dX = p2X - p1X;
435 double dY = p2Y - p1Y;
436 Throw.when(Double.isNaN(dX) || Double.isNaN(dY), DrawRuntimeException.class, "NaN values not permitted");
437 if (0 == dX && 0 == dY)
438 {
439 return 0.0;
440 }
441 double fraction = ((this.x - p1X) * dX + (this.y - p1Y) * dY) / (dX * dX + dY * dY);
442 if (fraction < 0.0)
443 {
444 if (lowLimitHandling == null)
445 {
446 return Double.NaN;
447 }
448 if (lowLimitHandling)
449 {
450 fraction = 0.0;
451 }
452 }
453 else if (fraction > 1.0)
454 {
455 if (highLimitHandling == null)
456 {
457 return Double.NaN;
458 }
459 if (highLimitHandling)
460 {
461 fraction = 1.0;
462 }
463 }
464 return fraction;
465 }
466
467
468
469
470
471
472
473
474
475
476
477
478
479 public final Point2d closestPointOnSegment(final double p1X, final double p1Y, final double p2X, final double p2Y)
480 {
481 return closestPointOnLine(p1X, p1Y, p2X, p2Y, true, true);
482 }
483
484 @Override
485 public Point2d closestPointOnLine(final Point2d linePoint1, final Point2d linePoint2)
486 throws NullPointerException, DrawRuntimeException
487 {
488 Throw.whenNull(linePoint1, "linePoint1");
489 Throw.whenNull(linePoint2, "linePoint2");
490 return closestPointOnLine(linePoint1.x, linePoint1.y, linePoint2.x, linePoint2.y);
491 }
492
493
494
495
496
497
498
499
500
501
502
503
504 public final Point2d closestPointOnLine(final double p1X, final double p1Y, final double p2X, final double p2Y)
505 throws DrawRuntimeException
506 {
507 Throw.when(p1X == p2X && p1Y == p2Y, DrawRuntimeException.class, "degenerate line not allowed");
508 return closestPointOnLine(p1X, p1Y, p2X, p2Y, false, false);
509 }
510
511
512
513
514
515
516
517
518
519
520
521
522
523 public static final List<Point2d> circleIntersections(final Point2d center1, final double radius1, final Point2d center2,
524 final double radius2) throws NullPointerException, DrawRuntimeException
525 {
526 Throw.whenNull(center1, "center1");
527 Throw.whenNull(center2, "center2");
528 Throw.when(radius1 < 0 || radius2 < 0, DrawRuntimeException.class, "radius may not be less than 0");
529 Throw.when(center1.equals(center2) && radius1 == radius2, DrawRuntimeException.class, "Circles must be different");
530 List<Point2d> result = new ArrayList<>();
531
532 double dX = center2.x - center1.x;
533 double dY = center2.y - center1.y;
534 double distance = Math.hypot(dX, dY);
535 if (distance > radius1 + radius2 || distance < Math.abs(radius1 - radius2))
536 {
537 return result;
538 }
539 double a = (radius1 * radius1 - radius2 * radius2 + distance * distance) / (2 * distance);
540
541 double x2 = center1.x + (dX * a / distance);
542 double y2 = center1.y + (dY * a / distance);
543
544 double h = Math.sqrt(radius1 * radius1 - a * a);
545
546 double rX = -dY * (h / distance);
547 double rY = dX * (h / distance);
548 result.add(new Point2d(x2 + rX, y2 + rY));
549 if (h > 0)
550 {
551
552 result.add(new Point2d(x2 - rX, y2 - rY));
553 }
554 return result;
555 }
556
557
558
559
560
561
562
563 public double directionTo(final Point2d otherPoint)
564 {
565 return Math.atan2(otherPoint.y - this.y, otherPoint.x - this.x);
566 }
567
568
569
570
571
572 public Point2D toPoint2D()
573 {
574 return new Point2D.Double(this.x, this.y);
575 }
576
577 @Override
578 public String toString()
579 {
580 return toString("%f");
581 }
582
583 @Override
584 public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
585 {
586 String format = String.format("%1$s[x=%2$s, y=%2$s]", doNotIncludeClassName ? "" : "Point2d ", doubleFormat);
587 return String.format(Locale.US, format, this.x, this.y);
588 }
589
590 @Override
591 public int hashCode()
592 {
593 final int prime = 31;
594 int result = 1;
595 long temp;
596 temp = Double.doubleToLongBits(this.x);
597 result = prime * result + (int) (temp ^ (temp >>> 32));
598 temp = Double.doubleToLongBits(this.y);
599 result = prime * result + (int) (temp ^ (temp >>> 32));
600 return result;
601 }
602
603 @SuppressWarnings("checkstyle:needbraces")
604 @Override
605 public boolean equals(final Object obj)
606 {
607 if (this == obj)
608 return true;
609 if (obj == null)
610 return false;
611 if (getClass() != obj.getClass())
612 return false;
613 Point2d other = (Point2d) obj;
614 if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x))
615 return false;
616 if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y))
617 return false;
618 return true;
619 }
620
621 }