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