1 package org.opentrafficsim.core.geometry;
2
3 import java.io.Serializable;
4 import java.util.Arrays;
5 import java.util.List;
6
7 import javax.media.j3d.Bounds;
8
9 import nl.tudelft.simulation.dsol.animation.LocatableInterface;
10 import nl.tudelft.simulation.language.d3.BoundingBox;
11 import nl.tudelft.simulation.language.d3.DirectedPoint;
12
13 import org.djunits.unit.LengthUnit;
14 import org.opentrafficsim.core.OTS_SCALAR;
15 import org.opentrafficsim.core.network.NetworkException;
16
17 import com.vividsolutions.jts.geom.Coordinate;
18 import com.vividsolutions.jts.geom.CoordinateSequence;
19 import com.vividsolutions.jts.geom.Geometry;
20 import com.vividsolutions.jts.geom.GeometryFactory;
21 import com.vividsolutions.jts.geom.LineString;
22
23
24
25
26
27
28
29
30
31
32
33
34 public class OTSLine3D implements LocatableInterface, Serializable, OTS_SCALAR
35 {
36
37 private static final long serialVersionUID = 20150722L;
38
39
40 private final OTSPoint3D[] points;
41
42
43 private double[] lengthIndexedLine = null;
44
45
46 private double length = Double.NaN;
47
48
49 private OTSPoint3D centroid = null;
50
51
52 private Bounds bounds = null;
53
54
55
56
57 public OTSLine3D(final OTSPoint3D[] points)
58 {
59 this.points = points;
60 }
61
62
63
64
65 public OTSLine3D(final Coordinate[] coordinates)
66 {
67 this.points = new OTSPoint3D[coordinates.length];
68 int i = 0;
69 for (Coordinate c : coordinates)
70 {
71 this.points[i++] = new OTSPoint3D(c);
72 }
73 }
74
75
76
77
78 public OTSLine3D(final LineString lineString)
79 {
80 this(lineString.getCoordinates());
81 }
82
83
84
85
86 public OTSLine3D(final Geometry geometry)
87 {
88 this(geometry.getCoordinates());
89 }
90
91
92
93
94 public OTSLine3D(final List<OTSPoint3D> pointList)
95 {
96 this(pointList.toArray(new OTSPoint3D[pointList.size()]));
97 }
98
99
100
101
102 public final Coordinate[] getCoordinates()
103 {
104 Coordinate[] result = new Coordinate[size()];
105 for (int i = 0; i < size(); i++)
106 {
107 result[i] = this.points[i].getCoordinate();
108 }
109 return result;
110 }
111
112
113
114
115 public final LineString getLineString()
116 {
117 GeometryFactory factory = new GeometryFactory();
118 Coordinate[] coordinates = getCoordinates();
119 CoordinateSequence cs = factory.getCoordinateSequenceFactory().create(coordinates);
120 return new LineString(cs, factory);
121 }
122
123
124
125
126 public final int size()
127 {
128 return this.points.length;
129 }
130
131
132
133
134
135
136 public final OTSPoint3D get(final int i) throws OTSGeometryException
137 {
138 if (i < 0 || i > size() - 1)
139 {
140 throw new OTSGeometryException("OTSLine3D.get(i=" + i + "); i<0 or i>=size(), which is " + size());
141 }
142 return this.points[i];
143 }
144
145
146
147
148 public final synchronized double getLengthSI()
149 {
150 if (Double.isNaN(this.length))
151 {
152 this.length = 0.0;
153 for (int i = 0; i < size() - 1; i++)
154 {
155 this.length += this.points[i].distanceSI(this.points[i + 1]);
156 }
157 }
158 return this.length;
159 }
160
161
162
163
164 public final Length.Rel getLength()
165 {
166 return new Length.Rel(getLengthSI(), LengthUnit.SI);
167 }
168
169
170
171
172 public final OTSPoint3D[] getPoints()
173 {
174 return this.points;
175 }
176
177
178
179
180 private void makeLengthIndexedLine()
181 {
182 if (this.lengthIndexedLine == null)
183 {
184 this.lengthIndexedLine = new double[this.points.length];
185 this.lengthIndexedLine[0] = 0.0;
186 for (int i = 1; i < this.points.length; i++)
187 {
188 this.lengthIndexedLine[i] = this.lengthIndexedLine[i - 1] + this.points[i - 1].distanceSI(this.points[i]);
189 }
190 }
191 }
192
193
194
195
196
197
198
199
200 public final DirectedPoint getLocationExtended(final Length.Rel position) throws NetworkException
201 {
202 return getLocationExtendedSI(position.getSI());
203 }
204
205
206
207
208
209
210
211
212 public final DirectedPoint getLocationExtendedSI(final double positionSI) throws NetworkException
213 {
214 makeLengthIndexedLine();
215 if (positionSI >= 0.0 && positionSI <= getLengthSI())
216 {
217 return getLocationSI(positionSI);
218 }
219
220
221 if (positionSI < 0.0)
222 {
223 double len = positionSI;
224 double fraction = len / (this.lengthIndexedLine[1] - this.lengthIndexedLine[0]);
225 OTSPoint3D p1 = this.points[0];
226 OTSPoint3D p2 = this.points[1];
227 return new DirectedPoint(p1.x + fraction * (p2.x - p1.x), p1.y + fraction * (p2.y - p1.y), p1.z + fraction
228 * (p2.z - p1.z), 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
229 }
230
231
232 int n1 = this.lengthIndexedLine.length - 1;
233 int n2 = this.lengthIndexedLine.length - 2;
234 double len = positionSI - getLengthSI();
235 double fraction = len / (this.lengthIndexedLine[n1] - this.lengthIndexedLine[n2]);
236 OTSPoint3D p1 = this.points[n2];
237 OTSPoint3D p2 = this.points[n1];
238 return new DirectedPoint(p2.x + fraction * (p2.x - p1.x), p2.y + fraction * (p2.y - p1.y), p2.z + fraction
239 * (p2.z - p1.z), 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
240 }
241
242
243
244
245
246
247
248 public final DirectedPoint getLocationFraction(final double fraction) throws NetworkException
249 {
250 if (fraction < 0.0 || fraction > 1.0)
251 {
252 throw new NetworkException("getLocationFraction for line: fraction < 0.0 or > 1.0. fraction = " + fraction);
253 }
254 return getLocationSI(fraction * getLengthSI());
255 }
256
257
258
259
260
261
262
263 public final DirectedPoint getLocation(final Length.Rel position) throws NetworkException
264 {
265 return getLocationSI(position.getSI());
266 }
267
268
269
270
271
272
273
274 private int find(final double pos) throws NetworkException
275 {
276 if (pos == 0)
277 {
278 return 0;
279 }
280
281 for (int i = 0; i < this.lengthIndexedLine.length - 2; i++)
282 {
283 if (pos > this.lengthIndexedLine[i] && pos <= this.lengthIndexedLine[i + 1])
284 {
285 return i;
286 }
287 }
288
289 return this.lengthIndexedLine.length - 2;
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313 }
314
315
316
317
318
319
320
321 public final DirectedPoint getLocationSI(final double positionSI) throws NetworkException
322 {
323 makeLengthIndexedLine();
324 if (positionSI < 0.0 || positionSI > getLengthSI())
325 {
326 throw new NetworkException("getLocationSI for line: position < 0.0 or > line length. Position = " + positionSI
327 + " m. Length = " + getLengthSI() + " m.");
328 }
329
330
331 if (positionSI == 0.0)
332 {
333 OTSPoint3D p1 = this.points[0];
334 OTSPoint3D p2 = this.points[1];
335 return new DirectedPoint(p1.x, p1.y, p1.z, 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
336 }
337 if (positionSI == getLengthSI())
338 {
339 OTSPoint3D p1 = this.points[this.points.length - 2];
340 OTSPoint3D p2 = this.points[this.points.length - 1];
341 return new DirectedPoint(p2.x, p2.y, p2.z, 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
342 }
343
344
345 int index = find(positionSI);
346 double remainder = positionSI - this.lengthIndexedLine[index];
347 double fraction = remainder / (this.lengthIndexedLine[index + 1] - this.lengthIndexedLine[index]);
348 OTSPoint3D p1 = this.points[index];
349 OTSPoint3D p2 = this.points[index + 1];
350 return new DirectedPoint(p1.x + fraction * (p2.x - p1.x), p1.y + fraction * (p2.y - p1.y), p1.z + fraction
351 * (p2.z - p1.z), 0.0, 0.0, Math.atan2(p2.y - p1.y, p2.x - p1.x));
352 }
353
354
355
356
357
358 private void calcCentroidBounds()
359 {
360 double minX = Double.POSITIVE_INFINITY;
361 double minY = Double.POSITIVE_INFINITY;
362 double minZ = Double.POSITIVE_INFINITY;
363 double maxX = Double.NEGATIVE_INFINITY;
364 double maxY = Double.NEGATIVE_INFINITY;
365 double maxZ = Double.NEGATIVE_INFINITY;
366 for (OTSPoint3D p : this.points)
367 {
368 minX = Math.min(minX, p.x);
369 minY = Math.min(minY, p.y);
370 minZ = Math.min(minZ, p.z);
371 maxX = Math.max(maxX, p.x);
372 maxY = Math.max(maxY, p.y);
373 maxZ = Math.max(maxZ, p.z);
374 }
375 this.centroid = new OTSPoint3D((maxX + minX) / 2, (maxY + minY) / 2, (maxZ + minZ) / 2);
376 double deltaX = Math.max(maxX - minX, 0.5);
377 double deltaY = Math.max(maxY - minY, 0.5);
378 double deltaZ = Math.max(maxZ - minZ, 0.5);
379 this.bounds = new BoundingBox(deltaX, deltaY, deltaZ);
380 }
381
382
383 @Override
384 @SuppressWarnings("checkstyle:designforextension")
385 public DirectedPoint getLocation()
386 {
387 if (this.centroid == null)
388 {
389 calcCentroidBounds();
390 }
391 return this.centroid.getDirectedPoint();
392 }
393
394
395 @Override
396 @SuppressWarnings("checkstyle:designforextension")
397 public Bounds getBounds()
398 {
399 if (this.bounds == null)
400 {
401 calcCentroidBounds();
402 }
403 return this.bounds;
404 }
405
406
407 @Override
408 @SuppressWarnings("checkstyle:designforextension")
409 public String toString()
410 {
411 return Arrays.toString(this.points);
412 }
413
414
415 @Override
416 @SuppressWarnings("checkstyle:designforextension")
417 public int hashCode()
418 {
419 final int prime = 31;
420 int result = 1;
421 result = prime * result + ((this.bounds == null) ? 0 : this.bounds.hashCode());
422 result = prime * result + ((this.centroid == null) ? 0 : this.centroid.hashCode());
423 long temp;
424 temp = Double.doubleToLongBits(this.length);
425 result = prime * result + (int) (temp ^ (temp >>> 32));
426 result = prime * result + Arrays.hashCode(this.points);
427 return result;
428 }
429
430
431 @Override
432 @SuppressWarnings({"checkstyle:designforextension", "checkstyle:needbraces"})
433 public boolean equals(final Object obj)
434 {
435 if (this == obj)
436 return true;
437 if (obj == null)
438 return false;
439 if (getClass() != obj.getClass())
440 return false;
441 OTSLine3D other = (OTSLine3D) obj;
442 if (this.bounds == null)
443 {
444 if (other.bounds != null)
445 return false;
446 }
447 else if (!this.bounds.equals(other.bounds))
448 return false;
449 if (this.centroid == null)
450 {
451 if (other.centroid != null)
452 return false;
453 }
454 else if (!this.centroid.equals(other.centroid))
455 return false;
456 if (Double.doubleToLongBits(this.length) != Double.doubleToLongBits(other.length))
457 return false;
458 if (!Arrays.equals(this.points, other.points))
459 return false;
460 return true;
461 }
462
463 }