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-point cannot be null"))[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 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
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
91 @Override
92 public final double getX()
93 {
94 return this.x;
95 }
96
97
98 @Override
99 public final double getY()
100 {
101 return this.y;
102 }
103
104
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
113 @Override
114 public double distanceSquared(final Point2d otherPoint) throws NullPointerException
115 {
116 Throw.whenNull(otherPoint, "point cannot be null");
117 double dx = this.x - otherPoint.x;
118 double dy = this.y - otherPoint.y;
119 return dx * dx + dy * dy;
120 }
121
122
123 @Override
124 public int size()
125 {
126 return 1;
127 }
128
129
130 @Override
131 public Iterator<? extends Point2d> getPoints()
132 {
133 return Arrays.stream(new Point2d[] {this}).iterator();
134 }
135
136
137
138
139
140
141
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(this.x + dx, this.y + dy);
147 }
148
149
150
151
152
153
154
155
156
157
158 public Point3d translate(final double dx, final double dy, final double dz)
159 {
160 Throw.when(Double.isNaN(dx) || Double.isNaN(dy) || Double.isNaN(dz), IllegalArgumentException.class,
161 "translation may not be NaN");
162 return new Point3d(this.x + dx, this.y + dy, dz);
163 }
164
165
166 @Override
167 public Point2d scale(final double factor)
168 {
169 Throw.when(Double.isNaN(factor), IllegalArgumentException.class, "factor must be a number (not NaN)");
170 return new Point2d(this.x * factor, this.y * factor);
171 }
172
173
174 @Override
175 public Point2d neg()
176 {
177 return scale(-1.0);
178 }
179
180
181 @Override
182 public Point2d abs()
183 {
184 return new Point2d(Math.abs(this.x), Math.abs(this.y));
185 }
186
187
188 @Override
189 public Point2d normalize() throws DrawRuntimeException
190 {
191 double length = Math.sqrt(this.x * this.x + this.y * this.y);
192 Throw.when(length == 0.0, DrawRuntimeException.class, "cannot normalize (0.0, 0.0)");
193 return this.scale(1.0 / length);
194 }
195
196
197 @Override
198 public Point2d interpolate(final Point2d point, final double fraction)
199 {
200 Throw.whenNull(point, "point cannot be null");
201 Throw.when(Double.isNaN(fraction), IllegalArgumentException.class, "fraction must be a number (not NaN)");
202 return new Point2d((1.0 - fraction) * this.x + fraction * point.x, (1.0 - fraction) * this.y + fraction * point.y);
203 }
204
205
206 @Override
207 public boolean epsilonEquals(final Point2d other, final double epsilon)
208 {
209 Throw.whenNull(other, "other point cannot be null");
210 if (Math.abs(this.x - other.x) > epsilon)
211 {
212 return false;
213 }
214 if (Math.abs(this.y - other.y) > epsilon)
215 {
216 return false;
217 }
218 return true;
219 }
220
221
222 @Override
223 public Bounds2d getBounds()
224 {
225 return new Bounds2d(this);
226 }
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246 @SuppressWarnings("checkstyle:parameternumber")
247 public static Point2d intersectionOfLines(final double line1P1X, final double line1P1Y, final double line1P2X,
248 final double line1P2Y, final boolean lowLimitLine1, final boolean highLimitLine1, final double line2P1X,
249 final double line2P1Y, final double line2P2X, final double line2P2Y, final boolean lowLimitLine2,
250 final boolean highLimitLine2) throws DrawRuntimeException
251 {
252 double line1DX = line1P2X - line1P1X;
253 double line1DY = line1P2Y - line1P1Y;
254 double l2p1x = line2P1X - line1P1X;
255 double l2p1y = line2P1Y - line1P1Y;
256 double l2p2x = line2P2X - line1P1X;
257 double l2p2y = line2P2Y - line1P1Y;
258 double denominator = (l2p2y - l2p1y) * line1DX - (l2p2x - l2p1x) * line1DY;
259 Throw.when(Double.isNaN(denominator), DrawRuntimeException.class, "NaN value not permitted");
260 if (denominator == 0.0)
261 {
262 return null;
263 }
264 double uA = ((l2p2x - l2p1x) * (-l2p1y) - (l2p2y - l2p1y) * (-l2p1x)) / denominator;
265
266 if (uA < 0.0 && lowLimitLine1 || uA > 1.0 && highLimitLine1)
267 {
268 return null;
269 }
270 double uB = (line1DY * l2p1x - line1DX * l2p1y) / denominator;
271
272 if (uB < 0.0 && lowLimitLine2 || uB > 1.0 && highLimitLine2)
273 {
274 return null;
275 }
276 if (uA == 1.0)
277 {
278 return new Point2d(line1P2X, line1P2Y);
279 }
280 if (uB == 0.0)
281 {
282 return new Point2d(line2P1X, line2P1Y);
283 }
284 if (uB == 1.0)
285 {
286 return new Point2d(line2P2X, line2P2Y);
287 }
288 return new Point2d(line1P1X + uA * line1DX, line1P1Y + uA * line1DY);
289 }
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305 @SuppressWarnings("checkstyle:parameternumber")
306 public static Point2d intersectionOfLines(final double l1P1X, final double l1P1Y, final double l1P2X, final double l1P2Y,
307 final double l2P1X, final double l2P1Y, final double l2P2X, final double l2P2Y) throws DrawRuntimeException
308 {
309 return intersectionOfLines(l1P1X, l1P1Y, l1P2X, l1P2Y, false, false, l2P1X, l2P1Y, l2P2X, l2P2Y, false, false);
310 }
311
312
313
314
315
316
317
318
319
320
321
322 public static Point2d intersectionOfLines(final Point2d line1P1, final Point2d line1P2, final Point2d line2P1,
323 final Point2d line2P2) throws NullPointerException
324 {
325 Throw.when(line1P1 == null || line1P2 == null || line2P1 == null || line2P2 == null, NullPointerException.class,
326 "Points may not be null");
327 return intersectionOfLines(line1P1.x, line1P1.y, line1P2.x, line1P2.y, false, false, line2P1.x, line2P1.y, line2P2.x,
328 line2P2.y, false, false);
329 }
330
331
332
333
334
335
336
337
338
339
340
341
342
343 public static Point2d intersectionOfLineSegments(final Point2d line1P1, final Point2d line1P2, final Point2d line2P1,
344 final Point2d line2P2) throws NullPointerException, DrawRuntimeException
345 {
346 Throw.when(line1P1 == null || line1P2 == null || line2P1 == null || line2P2 == null, NullPointerException.class,
347 "Points may not be null");
348 return intersectionOfLines(line1P1.x, line1P1.y, line1P2.x, line1P2.y, true, true, line2P1.x, line2P1.y, line2P2.x,
349 line2P2.y, true, true);
350 }
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366 @SuppressWarnings("checkstyle:parameternumber")
367 public static Point2d intersectionOfLineSegments(final double line1P1X, final double line1P1Y, final double line1P2X,
368 final double line1P2Y, final double line2P1X, final double line2P1Y, final double line2P2X, final double line2P2Y)
369 throws DrawRuntimeException
370 {
371 return intersectionOfLines(line1P1X, line1P1Y, line1P2X, line1P2Y, true, true, line2P1X, line2P1Y, line2P2X, line2P2Y,
372 true, true);
373 }
374
375
376
377
378
379
380
381 public static Point2d intersectionOfLineSegments(final LineSegment2d segment1, final LineSegment2d segment2)
382 {
383 return intersectionOfLineSegments(segment1.startX, segment1.startY, segment1.endX, segment1.endY,
384 segment2.startX, segment2.startY, segment2.endX, segment2.endY);
385 }
386
387
388 @Override
389 public Point2d closestPointOnSegment(final Point2d segmentPoint1, final Point2d segmentPoint2)
390 {
391 Throw.whenNull(segmentPoint1, "linePoint1 may not be null");
392 Throw.whenNull(segmentPoint2, "linePoint2 may not be null");
393 return closestPointOnSegment(segmentPoint1.x, segmentPoint1.y, segmentPoint2.x, segmentPoint2.y);
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) throws DrawRuntimeException
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 public double fractionalPositionOnLine(final double p1X, final double p1Y, final double p2X, final double p2Y,
445 final Boolean lowLimitHandling, final Boolean highLimitHandling) throws DrawRuntimeException
446 {
447 double dX = p2X - p1X;
448 double dY = p2Y - p1Y;
449 Throw.when(Double.isNaN(dX) || Double.isNaN(dY), DrawRuntimeException.class, "NaN values not permitted");
450 if (0 == dX && 0 == dY)
451 {
452 return 0.0;
453 }
454 double fraction = ((this.x - p1X) * dX + (this.y - p1Y) * dY) / (dX * dX + dY * dY);
455 if (fraction < 0.0)
456 {
457 if (lowLimitHandling == null)
458 {
459 return Double.NaN;
460 }
461 if (lowLimitHandling)
462 {
463 fraction = 0.0;
464 }
465 }
466 else if (fraction > 1.0)
467 {
468 if (highLimitHandling == null)
469 {
470 return Double.NaN;
471 }
472 if (highLimitHandling)
473 {
474 fraction = 1.0;
475 }
476 }
477 return fraction;
478 }
479
480
481
482
483
484
485
486
487
488
489
490
491
492 public final Point2d closestPointOnSegment(final double p1X, final double p1Y, final double p2X, final double p2Y)
493 {
494 return closestPointOnLine(p1X, p1Y, p2X, p2Y, true, true);
495 }
496
497
498 @Override
499 public Point2d closestPointOnLine(final Point2d linePoint1, final Point2d linePoint2)
500 throws NullPointerException, DrawRuntimeException
501 {
502 Throw.whenNull(linePoint1, "linePoint1 may not be null");
503 Throw.whenNull(linePoint2, "linePoint2 may not be null");
504 return closestPointOnLine(linePoint1.x, linePoint1.y, linePoint2.x, linePoint2.y);
505 }
506
507
508
509
510
511
512
513
514
515
516
517
518 public final Point2d closestPointOnLine(final double p1X, final double p1Y, final double p2X, final double p2Y)
519 throws DrawRuntimeException
520 {
521 Throw.when(p1X == p2X && p1Y == p2Y, DrawRuntimeException.class, "degenerate line not allowed");
522 return closestPointOnLine(p1X, p1Y, p2X, p2Y, false, false);
523 }
524
525
526
527
528
529
530
531
532
533
534
535
536
537 public static final List<Point2d> circleIntersections(final Point2d center1, final double radius1, final Point2d center2,
538 final double radius2) throws NullPointerException, DrawRuntimeException
539 {
540 Throw.whenNull(center1, "center1 may not be null");
541 Throw.whenNull(center2, "center2 may not be null");
542 Throw.when(radius1 < 0 || radius2 < 0, DrawRuntimeException.class, "radius may not be less than 0");
543 Throw.when(center1.equals(center2) && radius1 == radius2, DrawRuntimeException.class, "Circles must be different");
544 List<Point2d> result = new ArrayList<>();
545
546 double dX = center2.x - center1.x;
547 double dY = center2.y - center1.y;
548 double distance = Math.hypot(dX, dY);
549 if (distance > radius1 + radius2 || distance < Math.abs(radius1 - radius2))
550 {
551 return result;
552 }
553 double a = (radius1 * radius1 - radius2 * radius2 + distance * distance) / (2 * distance);
554
555 double x2 = center1.x + (dX * a / distance);
556 double y2 = center1.y + (dY * a / distance);
557
558 double h = Math.sqrt(radius1 * radius1 - a * a);
559
560 double rX = -dY * (h / distance);
561 double rY = dX * (h / distance);
562 result.add(new Point2d(x2 + rX, y2 + rY));
563 if (h > 0)
564 {
565
566 result.add(new Point2d(x2 - rX, y2 - rY));
567 }
568 return result;
569 }
570
571
572
573
574
575
576
577 public double directionTo(final Point2d otherPoint)
578 {
579 return Math.atan2(otherPoint.y - this.y, otherPoint.x - this.x);
580 }
581
582
583
584
585
586 public Point2D toPoint2D()
587 {
588 return new Point2D.Double(this.x, this.y);
589 }
590
591
592 @Override
593 public String toString()
594 {
595 return toString("%f");
596 }
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
607 @Override
608 public int hashCode()
609 {
610 final int prime = 31;
611 int result = 1;
612 long temp;
613 temp = Double.doubleToLongBits(this.x);
614 result = prime * result + (int) (temp ^ (temp >>> 32));
615 temp = Double.doubleToLongBits(this.y);
616 result = prime * result + (int) (temp ^ (temp >>> 32));
617 return result;
618 }
619
620
621 @SuppressWarnings("checkstyle:needbraces")
622 @Override
623 public boolean equals(final Object obj)
624 {
625 if (this == obj)
626 return true;
627 if (obj == null)
628 return false;
629 if (getClass() != obj.getClass())
630 return false;
631 Point2d other = (Point2d) obj;
632 if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x))
633 return false;
634 if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y))
635 return false;
636 return true;
637 }
638
639 }