View Javadoc
1   package org.opentrafficsim.core.geometry;
2   
3   import java.awt.geom.Point2D;
4   import java.io.Serializable;
5   
6   import javax.media.j3d.BoundingSphere;
7   import javax.media.j3d.Bounds;
8   import javax.vecmath.Point3d;
9   
10  import org.djunits.unit.LengthUnit;
11  import org.djunits.value.vdouble.scalar.Length;
12  
13  import com.vividsolutions.jts.geom.Coordinate;
14  import com.vividsolutions.jts.geom.Point;
15  
16  import nl.tudelft.simulation.dsol.animation.Locatable;
17  import nl.tudelft.simulation.language.d3.CartesianPoint;
18  import nl.tudelft.simulation.language.d3.DirectedPoint;
19  
20  /**
21   * An OTSPoint3D implements a 3D-coordinate for OTS. X, y and z are stored as doubles, but it is assumed that the scale is in SI
22   * units, i.e. in meters. A distance between two points is therefore also in meters.
23   * <p>
24   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
25   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
26   * <p>
27   * $LastChangedDate: 2015-07-16 10:20:53 +0200 (Thu, 16 Jul 2015) $, @version $Revision: 1124 $, by $Author: pknoppers $,
28   * initial version Jul 22, 2015 <br>
29   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
30   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
31   * @author <a href="http://www.citg.tudelft.nl">Guus Tamminga</a>
32   */
33  public class OTSPoint3D implements Locatable, Serializable
34  {
35      /** */
36      private static final long serialVersionUID = 20150722L;
37  
38      /** The internal representation of the point; x-coordinate. */
39      @SuppressWarnings("checkstyle:visibilitymodifier")
40      public final double x;
41  
42      /** The internal representation of the point; y-coordinate. */
43      @SuppressWarnings("checkstyle:visibilitymodifier")
44      public final double y;
45  
46      /** The internal representation of the point; z-coordinate. */
47      @SuppressWarnings("checkstyle:visibilitymodifier")
48      public final double z;
49  
50      /**
51       * The x, y and z in the point are assumed to be in meters relative to an origin.
52       * @param x x-coordinate
53       * @param y y-coordinate
54       * @param z z-coordinate
55       */
56      public OTSPoint3D(final double x, final double y, final double z)
57      {
58          this.x = x;
59          this.y = y;
60          this.z = z;
61      }
62  
63      /**
64       * @param xyz array with three elements; x, y and z are assumed to be in meters relative to an origin.
65       */
66      public OTSPoint3D(final double[] xyz)
67      {
68          this(xyz[0], xyz[1], (xyz.length > 2) ? xyz[2] : 0.0);
69      }
70  
71      /**
72       * @param point a point to "clone".
73       */
74      public OTSPoint3D(final OTSPoint3D point)
75      {
76          this(point.x, point.y, point.z);
77      }
78  
79      /**
80       * @param point javax.vecmath 3D double point; the x, y and z in the point are assumed to be in meters relative to an
81       *            origin.
82       */
83      public OTSPoint3D(final Point3d point)
84      {
85          this(point.x, point.y, point.z);
86      }
87  
88      /**
89       * @param point javax.vecmath 3D double point; the x, y and z in the point are assumed to be in meters relative to an
90       *            origin.
91       */
92      public OTSPoint3D(final CartesianPoint point)
93      {
94          this(point.x, point.y, point.z);
95      }
96  
97      /**
98       * @param point javax.vecmath 3D double point; the x, y and z in the point are assumed to be in meters relative to an
99       *            origin.
100      */
101     public OTSPoint3D(final DirectedPoint point)
102     {
103         this(point.x, point.y, point.z);
104     }
105 
106     /**
107      * @param point2d java.awt 2D point, z-coordinate will be zero; the x and y in the point are assumed to be in meters
108      *            relative to an origin.
109      */
110     public OTSPoint3D(final Point2D point2d)
111     {
112         this(point2d.getX(), point2d.getY(), 0.0);
113     }
114 
115     /**
116      * @param coordinate geotools coordinate; the x, y and z in the coordinate are assumed to be in meters relative to an
117      *            origin.
118      */
119     public OTSPoint3D(final Coordinate coordinate)
120     {
121         this(coordinate.x, coordinate.y, Double.isNaN(coordinate.z) ? 0.0 : coordinate.z);
122     }
123 
124     /**
125      * @param point geotools point; z-coordinate will be zero; the x and y in the point are assumed to be in meters relative to
126      *            an origin.
127      */
128     public OTSPoint3D(final Point point)
129     {
130         this(point.getX(), point.getY(), 0.0);
131     }
132 
133     /**
134      * The x and y in the point are assumed to be in meters relative to an origin. z will be set to 0.
135      * @param x x-coordinate
136      * @param y y-coordinate
137      */
138     public OTSPoint3D(final double x, final double y)
139     {
140         this(x, y, 0.0);
141     }
142 
143     /**
144      * Interpolate (or extrapolate) between (outside) two given points.
145      * @param ratio double; 0 selects the zeroValue point, 1 selects the oneValue point, 0.5 selects a point halfway, etc.
146      * @param zeroValue OTSPoint3D; the point that is returned when ratio equals 0
147      * @param oneValue OTSPoint3D; the point that is returned when ratio equals 1
148      * @return OTSPoint3D
149      */
150     public static OTSPoint3D interpolate(final double ratio, final OTSPoint3D zeroValue, final OTSPoint3D oneValue)
151     {
152         double complement = 1 - ratio;
153         return new OTSPoint3D(complement * zeroValue.x + ratio * oneValue.x, complement * zeroValue.y + ratio * oneValue.y,
154                 complement * zeroValue.z + ratio * oneValue.z);
155     }
156 
157     /**
158      * Compute the 2D intersection of two line segments. The Z-component of the lines is ignored. Both line segments are defined
159      * by two points (that should be distinct).
160      * @param line1P1 OTSPoint3D; first point of line segment 1
161      * @param line1P2 OTSPoint3D; second point of line segment 1
162      * @param line2P1 OTSPoint3D; first point of line segment 2
163      * @param line2P2 OTSPoint3D; second point of line segment 2
164      * @return OTSPoint3D; the intersection of the two lines, or null if the lines are (almost) parallel, or do not intersect
165      */
166     public static OTSPoint3D intersectionOfLineSegments(final OTSPoint3D line1P1, final OTSPoint3D line1P2,
167             final OTSPoint3D line2P1, final OTSPoint3D line2P2)
168     {
169         double denominator =
170                 (line2P2.y - line2P1.y) * (line1P2.x - line1P1.x) - (line2P2.x - line2P1.x) * (line1P2.y - line1P1.y);
171         if (denominator == 0f)
172         {
173             return null; // lines are parallel (they might even be on top of each other, but we don't check that)
174         }
175         double uA =
176                 ((line2P2.x - line2P1.x) * (line1P1.y - line2P1.y) - (line2P2.y - line2P1.y) * (line1P1.x - line2P1.x))
177                         / denominator;
178         if ((uA < 0f) || (uA > 1f))
179         {
180             return null; // intersection outside line 1
181         }
182         double uB =
183                 ((line1P2.x - line1P1.x) * (line1P1.y - line2P1.y) - (line1P2.y - line1P1.y) * (line1P1.x - line2P1.x))
184                         / denominator;
185         if (uB < 0 || uB > 1)
186         {
187             return null; // intersection outside line 2
188         }
189         return new OTSPoint3D(line1P1.x + uA * (line1P2.x - line1P1.x), line1P1.y + uA * (line1P2.y - line1P1.y), 0);
190     }
191 
192     /**
193      * Compute the 2D intersection of two infinite lines. The Z-component of the lines is ignored. Both lines are defined by two
194      * points (that should be distinct).
195      * @param line1P1 OTSPoint3D; first point of line 1
196      * @param line1P2 OTSPoint3D; second point of line 1
197      * @param line2P1 OTSPoint3D; first point of line 2
198      * @param line2P2 OTSPoint3D; second point of line 2
199      * @return OTSPoint3D; the intersection of the two lines, or null if the lines are (almost) parallel
200      */
201     public static OTSPoint3D intersectionOfLines(final OTSPoint3D line1P1, final OTSPoint3D line1P2, final OTSPoint3D line2P1,
202             final OTSPoint3D line2P2)
203     {
204         double determinant =
205                 (line1P1.x - line1P2.x) * (line2P1.y - line2P2.y) - (line1P1.y - line1P2.y) * (line2P1.x - line2P2.x);
206         if (Math.abs(determinant) < 0.0000001)
207         {
208             return null;
209         }
210         return new OTSPoint3D(
211                 ((line1P1.x * line1P2.y - line1P1.y * line1P2.x) * (line2P1.x - line2P2.x) - (line1P1.x - line1P2.x)
212                         * (line2P1.x * line2P2.y - line2P1.y * line2P2.x))
213                         / determinant,
214                 ((line1P1.x * line1P2.y - line1P1.y * line1P2.x) * (line2P1.y - line2P2.y) - (line1P1.y - line1P2.y)
215                         * (line2P1.x * line2P2.y - line2P1.y * line2P2.x))
216                         / determinant);
217     }
218 
219     /**
220      * Compute the distance to a line segment (2D; Z-component is ignored). If the projection of this point onto the line lies
221      * outside the line segment; the distance to nearest end point of the line segment is returned. Otherwise the distance to
222      * the line segment is returned. <br>
223      * Adapted from <a href="http://paulbourke.net/geometry/pointlineplane/DistancePoint.java"> example code provided by Paul
224      * Bourke</a>.
225      * @param segmentPoint1 OTSPoint3D; start of line segment
226      * @param segmentPoint2 OTSPoint3D; end of line segment
227      * @return double; the distance of this point to (one of the end points of the line segment)
228      */
229     // public final double horizontalDistanceToLineSegment(final OTSPoint3D segmentPoint1, final OTSPoint3D segmentPoint2)
230     // {
231     // return closestPointOnSegment(segmentPoint1, segmentPoint2).horizontalDistanceSI(this);
232     // }
233 
234     /**
235      * Project a point on a line segment (2D - Z-component is ignored). If the the projected points lies outside the line
236      * segment, the nearest end point of the line segment is returned. Otherwise the returned point lies between the end points
237      * of the line segment. <br>
238      * Adapted from <a href="http://paulbourke.net/geometry/pointlineplane/DistancePoint.java">example code provided by Paul
239      * Bourke</a>.
240      * @param segmentPoint1 OTSPoint3D; start of line segment
241      * @param segmentPoint2 OTSPoint3D; end of line segment
242      * @return Point2D.Double; either <cite>lineP1</cite>, or <cite>lineP2</cite> or a new OTSPoint3D that lies somewhere in
243      *         between those two. The Z-component of the result matches the Z-component of the line segment at that point
244      */
245     public final OTSPoint3D closestPointOnSegment(final OTSPoint3D segmentPoint1, final OTSPoint3D segmentPoint2)
246     {
247         double dX = segmentPoint2.x - segmentPoint1.x;
248         double dY = segmentPoint2.y - segmentPoint1.y;
249         if ((0 == dX) && (0 == dY))
250         {
251             return segmentPoint1;
252         }
253         final double u = ((this.x - segmentPoint1.x) * dX + (this.y - segmentPoint1.y) * dY) / (dX * dX + dY * dY);
254         if (u < 0)
255         {
256             return segmentPoint1;
257         }
258         else if (u > 1)
259         {
260             return segmentPoint2;
261         }
262         else
263         {
264             return interpolate(u, segmentPoint1, segmentPoint2);
265             // WAS new OTSPoint3D(segmentPoint1.x + u * dX, segmentPoint1.y + u * dY); // could use interpolate in stead
266         }
267     }
268 
269     /**
270      * Return the closest point on an OTSLine3D.
271      * @param line OTSLine3D; the line
272      * @param useHorizontalDistance boolean; if true; the horizontal distance is used to determine the closest point; if false;
273      *            the 3D distance is used to determine the closest point
274      * @return OTSPoint3D; the Z component of the returned point matches the Z-component of hte line at that point
275      */
276     private OTSPoint3D internalClosestPointOnLine(final OTSLine3D line, final boolean useHorizontalDistance)
277     {
278         OTSPoint3D prevPoint = null;
279         double distance = Double.MAX_VALUE;
280         OTSPoint3D result = null;
281         for (OTSPoint3D nextPoint : line.getPoints())
282         {
283             if (null != prevPoint)
284             {
285                 OTSPoint3D closest = closestPointOnSegment(prevPoint, nextPoint);
286                 double thisDistance = useHorizontalDistance ? horizontalDistanceSI(closest) : distanceSI(closest);
287                 if (thisDistance < distance)
288                 {
289                     result = closest;
290                     distance = thisDistance;
291                 }
292             }
293             prevPoint = nextPoint;
294         }
295         return result;
296     }
297 
298     /**
299      * Return the closest point on an OTSLine3D. This method takes the Z-component of this point and the line into account.
300      * @param line OTSLine3D; the line
301      * @return OTSPoint3D; the Z-component of the returned point matches the Z-component of the line at that point
302      */
303     public final OTSPoint3D closestPointOnLine(final OTSLine3D line)
304     {
305         return internalClosestPointOnLine(line, false);
306     }
307 
308     /**
309      * Return the closest point on an OTSLine3D. This method ignores the Z-component of this point and the line when computing
310      * the distance.
311      * @param line OTSLine3D; the line
312      * @return OTSPoint3D; the Z-component of the returned point matches the Z-component of the line at that point
313      */
314     public final OTSPoint3D closestPointOnLine2D(final OTSLine3D line)
315     {
316         return internalClosestPointOnLine(line, true);
317     }
318 
319     /**
320      * @param point the point to which the distance has to be calculated.
321      * @return the distance in 3D according to Pythagoras, expressed in the SI unit for length (meter)
322      */
323     public final double distanceSI(final OTSPoint3D point)
324     {
325         double dx = point.x - this.x;
326         double dy = point.y - this.y;
327         double dz = point.z - this.z;
328 
329         return Math.sqrt(dx * dx + dy * dy + dz * dz);
330     }
331 
332     /**
333      * @param point the point to which the distance has to be calculated.
334      * @return the distance in 3D according to Pythagoras, expressed in the SI unit for length (meter)
335      */
336     public final double horizontalDistanceSI(final OTSPoint3D point)
337     {
338         double dx = point.x - this.x;
339         double dy = point.y - this.y;
340 
341         return Math.sqrt(dx * dx + dy * dy);
342     }
343 
344     /**
345      * @param point the point to which the distance has to be calculated.
346      * @return the distance in 3D according to Pythagoras
347      */
348     public final Length horizontalDistance(final OTSPoint3D point)
349     {
350         return new Length(horizontalDistanceSI(point), LengthUnit.SI);
351     }
352 
353     /**
354      * @param point the point to which the distance has to be calculated.
355      * @return the distance in 3D according to Pythagoras
356      */
357     public final Length distance(final OTSPoint3D point)
358     {
359         return new Length(distanceSI(point), LengthUnit.SI);
360     }
361 
362     /**
363      * @return the equivalent geotools Coordinate of this point.
364      */
365     public final Coordinate getCoordinate()
366     {
367         return new Coordinate(this.x, this.y, this.z);
368     }
369 
370     /**
371      * @return the equivalent DSOL DirectedPoint of this point. Should the result be cached?
372      */
373     public final DirectedPoint getDirectedPoint()
374     {
375         return new DirectedPoint(this.x, this.y, this.z);
376     }
377 
378     /**
379      * @return a Point2D with the x and y structure.
380      */
381     public final Point2D getPoint2D()
382     {
383         return new Point2D.Double(this.x, this.y);
384     }
385 
386     /** {@inheritDoc} */
387     @Override
388     public final DirectedPoint getLocation()
389     {
390         return getDirectedPoint();
391     }
392 
393     /**
394      * This method returns a sphere with a diameter of half a meter as the default bounds for a point. {@inheritDoc}
395      */
396     @Override
397     public final Bounds getBounds()
398     {
399         return new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 0.5);
400     }
401 
402     /** {@inheritDoc} */
403     @Override
404     @SuppressWarnings("checkstyle:designforextension")
405     public String toString()
406     {
407         return String.format("(%.3f,%.3f,%.3f)", this.x, this.y, this.z);
408     }
409 
410     /** {@inheritDoc} */
411     @Override
412     @SuppressWarnings("checkstyle:designforextension")
413     public int hashCode()
414     {
415         final int prime = 31;
416         int result = 1;
417         long temp;
418         temp = Double.doubleToLongBits(this.x);
419         result = prime * result + (int) (temp ^ (temp >>> 32));
420         temp = Double.doubleToLongBits(this.y);
421         result = prime * result + (int) (temp ^ (temp >>> 32));
422         temp = Double.doubleToLongBits(this.z);
423         result = prime * result + (int) (temp ^ (temp >>> 32));
424         return result;
425     }
426 
427     /** {@inheritDoc} */
428     @Override
429     @SuppressWarnings({ "checkstyle:designforextension", "checkstyle:needbraces" })
430     public boolean equals(final Object obj)
431     {
432         if (this == obj)
433             return true;
434         if (obj == null)
435             return false;
436         if (getClass() != obj.getClass())
437             return false;
438         OTSPoint3D other = (OTSPoint3D) obj;
439         if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x))
440             return false;
441         if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y))
442             return false;
443         if (Double.doubleToLongBits(this.z) != Double.doubleToLongBits(other.z))
444             return false;
445         return true;
446     }
447 
448 }