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