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
18
19
20
21
22
23
24
25
26 public class Point2d implements Drawable2d, Point<Point2d, Space2d>
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 = getX() - otherPoint.x;
118 double dy = getY() - 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(getX() + dx, getY() + dy);
147 }
148
149
150
151
152
153
154
155
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
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
173 @Override
174 public Point2d neg()
175 {
176 return scale(-1.0);
177 }
178
179
180 @Override
181 public Point2d abs()
182 {
183 return new Point2d(Math.abs(getX()), Math.abs(getY()));
184 }
185
186
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
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
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
221 @Override
222 public Bounds2d getBounds()
223 {
224 return new Bounds2d(this);
225 }
226
227
228
229
230
231
232
233
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;
250 }
251 double u = ((l2p2x - l2p1x) * (-l2p1y) - (l2p2y - l2p1y) * (-l2p1x)) / denominator;
252 return line1P1.interpolate(line1P2, u);
253 }
254
255
256
257
258
259
260
261
262
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;
279 }
280 double uA = ((l2p2x - l2p1x) * (-l2p1y) - (l2p2y - l2p1y) * (-l2p1x)) / denominator;
281
282 if ((uA < 0.0) || (uA > 1.0))
283 {
284 return null;
285 }
286 double uB = (l1p2y * l2p1x - l1p2x * l2p1y) / denominator;
287
288 if (uB < 0.0 || uB > 1.0)
289 {
290 return null;
291 }
292 return line1P1.interpolate(line1P2, uA);
293
294 }
295
296
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)
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
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
334
335
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
347
348
349
350
351
352
353
354
355
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
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
375 double x2 = center1.x + (dX * a / distance);
376 double y2 = center1.y + (dY * a / distance);
377
378 double h = Math.sqrt(radius1 * radius1 - a * a);
379
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
386 result.add(new Point2d(x2 - rX, y2 - rY));
387 }
388 return result;
389 }
390
391
392
393
394
395
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
404
405
406 public Point2D toPoint2D()
407 {
408 return new Point2D.Double(getX(), getY());
409 }
410
411
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
421 @Override
422 public String toString()
423 {
424 return String.format("(%f,%f)", getX(), getY());
425 }
426
427
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
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 }