1 package org.opentrafficsim.core.network.lane;
2
3 import java.rmi.RemoteException;
4 import java.util.ArrayList;
5 import java.util.HashSet;
6 import java.util.List;
7 import java.util.Locale;
8 import java.util.Set;
9
10 import javax.media.j3d.Bounds;
11 import javax.vecmath.Point3d;
12
13 import nl.tudelft.simulation.dsol.animation.LocatableInterface;
14 import nl.tudelft.simulation.language.d3.BoundingBox;
15 import nl.tudelft.simulation.language.d3.DirectedPoint;
16
17 import org.opentrafficsim.core.network.LateralDirectionality;
18 import org.opentrafficsim.core.network.NetworkException;
19 import org.opentrafficsim.core.network.geotools.LinearGeometry;
20 import org.opentrafficsim.core.unit.LengthUnit;
21 import org.opentrafficsim.core.value.vdouble.scalar.DoubleScalar;
22
23 import com.vividsolutions.jts.geom.Coordinate;
24 import com.vividsolutions.jts.geom.Envelope;
25 import com.vividsolutions.jts.geom.Geometry;
26 import com.vividsolutions.jts.geom.GeometryFactory;
27 import com.vividsolutions.jts.geom.LineString;
28 import com.vividsolutions.jts.linearref.LengthIndexedLine;
29 import com.vividsolutions.jts.operation.buffer.BufferParameters;
30
31
32
33
34
35
36
37
38
39
40
41
42 public abstract class CrossSectionElement implements LocatableInterface
43 {
44
45 private final CrossSectionLink<?, ?> parentLink;
46
47
48 private final DoubleScalar.Rel<LengthUnit> designLineOffsetAtBegin;
49
50
51 private final DoubleScalar.Rel<LengthUnit> designLineOffsetAtEnd;
52
53
54 private final DoubleScalar.Rel<LengthUnit> beginWidth;
55
56
57 private final DoubleScalar.Rel<LengthUnit> endWidth;
58
59
60 private final Geometry contour;
61
62
63 private LineString crossSectionDesignLine;
64
65
66 private final DoubleScalar.Rel<LengthUnit> length;
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 public CrossSectionElement(final CrossSectionLink<?, ?> parentLink,
83 final DoubleScalar.Rel<LengthUnit> lateralOffsetAtBegin,
84 final DoubleScalar.Rel<LengthUnit> lateralOffsetAtEnd, final DoubleScalar.Rel<LengthUnit> beginWidth,
85 final DoubleScalar.Rel<LengthUnit> endWidth) throws NetworkException
86 {
87 super();
88 this.parentLink = parentLink;
89 this.designLineOffsetAtBegin = lateralOffsetAtBegin;
90 this.designLineOffsetAtEnd = lateralOffsetAtEnd;
91 this.beginWidth = beginWidth;
92 this.endWidth = endWidth;
93 this.contour = constructGeometry();
94
95 this.length = new DoubleScalar.Rel<LengthUnit>(this.crossSectionDesignLine.getLength(), LengthUnit.METER);
96 this.parentLink.addCrossSectionElement(this);
97 }
98
99
100 private final static int QUADRANTSEGMENTS = 8;
101
102
103
104
105
106
107
108 private double norm(final double angle)
109 {
110 double normalized = angle % (2 * Math.PI);
111 if (normalized < 0.0)
112 {
113 normalized += 2 * Math.PI;
114 }
115 return normalized;
116 }
117
118
119
120
121
122
123
124
125 @SuppressWarnings("checkstyle:methodlength")
126 private Geometry offsetGeometry(final Geometry referenceLine, final double offset) throws NetworkException
127 {
128 Coordinate[] referenceCoordinates = referenceLine.getCoordinates();
129
130 double bufferOffset = Math.abs(offset);
131 if (bufferOffset == 0)
132 {
133
134 GeometryFactory factory = new GeometryFactory();
135 Geometry result = factory.createLineString(referenceCoordinates);
136 return result;
137 }
138 Coordinate[] bufferCoordinates =
139 referenceLine.buffer(bufferOffset, CrossSectionElement.QUADRANTSEGMENTS, BufferParameters.CAP_FLAT)
140 .getCoordinates();
141
142
143 Coordinate sC = referenceCoordinates[0];
144 Coordinate sC1 = referenceCoordinates[1];
145 Coordinate eC = referenceCoordinates[referenceCoordinates.length - 1];
146 Coordinate eC1 = referenceCoordinates[referenceCoordinates.length - 2];
147 Set<Integer> startIndexSet = new HashSet<>();
148 Set<Coordinate> startSet = new HashSet<Coordinate>();
149 Set<Integer> endIndexSet = new HashSet<>();
150 Set<Coordinate> endSet = new HashSet<Coordinate>();
151 final double precision = 0.000001;
152 for (int i = 0; i < bufferCoordinates.length; i++)
153 {
154 Coordinate c = bufferCoordinates[i];
155 if (Math.abs(c.distance(sC) - bufferOffset) < bufferOffset * precision && !startSet.contains(c))
156 {
157 startIndexSet.add(i);
158 startSet.add(c);
159 }
160 if (Math.abs(c.distance(eC) - bufferOffset) < bufferOffset * precision && !endSet.contains(c))
161 {
162 endIndexSet.add(i);
163 endSet.add(c);
164 }
165 }
166 if (startIndexSet.size() != 2)
167 {
168 throw new NetworkException("offsetGeometry: startIndexSet.size() = " + startIndexSet.size());
169 }
170 if (endIndexSet.size() != 2)
171 {
172 throw new NetworkException("offsetGeometry: endIndexSet.size() = " + endIndexSet.size());
173 }
174
175
176 int startIndex = -1;
177 int endIndex = -1;
178 double expectedStartAngle = norm(Math.atan2(sC1.y - sC.y, sC1.x - sC.x) + Math.signum(offset) * Math.PI / 2.0);
179 double expectedEndAngle = norm(Math.atan2(eC.y - eC1.y, eC.x - eC1.x) + Math.signum(offset) * Math.PI / 2.0);
180 for (int ic : startIndexSet)
181 {
182 if (Math.abs(norm(Math.atan2(bufferCoordinates[ic].y - sC.y, bufferCoordinates[ic].x - sC.x)
183 - expectedStartAngle)) < Math.PI / 4.0
184 || Math.abs(norm(Math.atan2(bufferCoordinates[ic].y - sC.y, bufferCoordinates[ic].x - sC.x)
185 - expectedStartAngle)
186 - 2.0 * Math.PI) < Math.PI / 4.0)
187 {
188 startIndex = ic;
189 }
190 }
191 for (int ic : endIndexSet)
192 {
193 if (Math.abs(norm(Math.atan2(bufferCoordinates[ic].y - eC.y, bufferCoordinates[ic].x - eC.x)
194 - expectedEndAngle)) < Math.PI / 4.0
195 || Math.abs(norm(Math.atan2(bufferCoordinates[ic].y - eC.y, bufferCoordinates[ic].x - eC.x)
196 - expectedEndAngle)
197 - 2.0 * Math.PI) < Math.PI / 4.0)
198 {
199 endIndex = ic;
200 }
201 }
202 if (startIndex == -1 || endIndex == -1)
203 {
204 throw new NetworkException("offsetGeometry: could not find startIndex or endIndex");
205 }
206 startIndexSet.remove(startIndex);
207 endIndexSet.remove(endIndex);
208
209
210 List<Coordinate> coordinateList1 = new ArrayList<>();
211 List<Coordinate> coordinateList2 = new ArrayList<>();
212 boolean use1 = true;
213 boolean use2 = true;
214
215 int i = startIndex;
216 while (i != endIndex)
217 {
218 if (!coordinateList1.contains(bufferCoordinates[i]))
219 {
220 coordinateList1.add(bufferCoordinates[i]);
221 }
222 i = (i + 1) % bufferCoordinates.length;
223 if (startIndexSet.contains(i) || endIndexSet.contains(i))
224 {
225 use1 = false;
226 }
227 }
228 if (!coordinateList1.contains(bufferCoordinates[endIndex]))
229 {
230 coordinateList1.add(bufferCoordinates[endIndex]);
231 }
232
233 i = startIndex;
234 while (i != endIndex)
235 {
236 if (!coordinateList2.contains(bufferCoordinates[i]))
237 {
238 coordinateList2.add(bufferCoordinates[i]);
239 }
240 i = (i == 0) ? bufferCoordinates.length - 1 : i - 1;
241 if (startIndexSet.contains(i) || endIndexSet.contains(i))
242 {
243 use2 = false;
244 }
245 }
246 if (!coordinateList2.contains(bufferCoordinates[endIndex]))
247 {
248 coordinateList2.add(bufferCoordinates[endIndex]);
249 }
250
251 if (!use1 && !use2)
252 {
253 throw new NetworkException("offsetGeometry: could not find path from start to end for offset");
254 }
255 if (use1 && use2)
256 {
257 throw new NetworkException("offsetGeometry: Both paths from start to end for offset were found to be ok");
258 }
259 Coordinate[] coordinates;
260 if (use1)
261 {
262 coordinates = new Coordinate[coordinateList1.size()];
263 coordinateList1.toArray(coordinates);
264 }
265 else
266 {
267 coordinates = new Coordinate[coordinateList2.size()];
268 coordinateList2.toArray(coordinates);
269 }
270 GeometryFactory factory = new GeometryFactory();
271 Geometry result = factory.createLineString(coordinates);
272 return result;
273 }
274
275
276
277
278
279
280
281
282
283
284
285
286 private Geometry offsetLine(final Geometry referenceLine, final double offsetAtStart, final double offsetAtEnd)
287 throws NetworkException
288 {
289
290 Geometry offsetLineAtStart = offsetGeometry(referenceLine, offsetAtStart);
291
292
293 if (offsetAtStart == offsetAtEnd)
294 {
295 return offsetLineAtStart;
296 }
297 Geometry offsetLineAtEnd = offsetGeometry(referenceLine, offsetAtEnd);
298
299
300 LengthIndexedLine first = new LengthIndexedLine(offsetLineAtStart);
301 double firstLength = offsetLineAtStart.getLength();
302 LengthIndexedLine second = new LengthIndexedLine(offsetLineAtEnd);
303 double secondLength = offsetLineAtEnd.getLength();
304 ArrayList<Coordinate> out = new ArrayList<Coordinate>();
305 Coordinate[] firstCoordinates = offsetLineAtStart.getCoordinates();
306 Coordinate[] secondCoordinates = offsetLineAtEnd.getCoordinates();
307 int firstIndex = 0;
308 int secondIndex = 0;
309 Coordinate prevCoordinate = null;
310 final double tooClose = 0.05;
311 while (firstIndex < firstCoordinates.length && secondIndex < secondCoordinates.length)
312 {
313 double firstRatio =
314 firstIndex < firstCoordinates.length ? first.indexOf(firstCoordinates[firstIndex]) / firstLength
315 : Double.MAX_VALUE;
316 double secondRatio =
317 secondIndex < secondCoordinates.length ? second.indexOf(secondCoordinates[secondIndex])
318 / secondLength : Double.MAX_VALUE;
319 double ratio;
320 if (firstRatio < secondRatio)
321 {
322 ratio = firstRatio;
323 firstIndex++;
324 }
325 else
326 {
327 ratio = secondRatio;
328 secondIndex++;
329 }
330 Coordinate firstCoordinate = first.extractPoint(ratio * firstLength);
331 Coordinate secondCoordinate = second.extractPoint(ratio * secondLength);
332 Coordinate resultCoordinate =
333 new Coordinate((1 - ratio) * firstCoordinate.x + ratio * secondCoordinate.x, (1 - ratio)
334 * firstCoordinate.y + ratio * secondCoordinate.y);
335
336
337
338 if (null == prevCoordinate || resultCoordinate.distance(prevCoordinate) > tooClose)
339 {
340 out.add(resultCoordinate);
341 prevCoordinate = resultCoordinate;
342 }
343 }
344 Coordinate[] resultCoordinates = new Coordinate[out.size()];
345 for (int index = 0; index < out.size(); index++)
346 {
347 resultCoordinates[index] = out.get(index);
348 }
349
350 GeometryFactory factory = new GeometryFactory();
351 return factory.createLineString(resultCoordinates);
352 }
353
354
355
356
357
358
359
360 private Geometry constructGeometry() throws NetworkException
361 {
362 GeometryFactory factory = new GeometryFactory();
363 LinearGeometry parentGeometry = this.parentLink.getGeometry();
364 if (null == parentGeometry)
365 {
366 return null;
367 }
368 Coordinate[] referenceCoordinates = parentGeometry.getLineString().getCoordinates();
369 if (referenceCoordinates.length < 2)
370 {
371 throw new NetworkException("Parent Link has bad Geometry");
372 }
373
374 Geometry referenceGeometry = factory.createLineString(referenceCoordinates);
375 Geometry resultLine =
376 offsetLine(referenceGeometry, this.designLineOffsetAtBegin.getSI(), this.designLineOffsetAtEnd.getSI());
377
378 this.crossSectionDesignLine = factory.createLineString(resultLine.getCoordinates());
379 Coordinate[] rightBoundary =
380 offsetLine(this.crossSectionDesignLine, -this.beginWidth.getSI() / 2, -this.endWidth.getSI() / 2)
381 .getCoordinates();
382
383 Coordinate[] leftBoundary =
384 offsetLine(this.crossSectionDesignLine, this.beginWidth.getSI() / 2, this.endWidth.getSI() / 2)
385 .getCoordinates();
386
387 Coordinate[] result = new Coordinate[rightBoundary.length + leftBoundary.length + 1];
388 int resultIndex = 0;
389 for (int index = 0; index < rightBoundary.length; index++)
390 {
391 result[resultIndex++] = rightBoundary[index];
392 }
393 for (int index = leftBoundary.length; --index >= 0;)
394 {
395 result[resultIndex++] = leftBoundary[index];
396 }
397 result[resultIndex] = rightBoundary[0];
398
399 return factory.createLineString(result);
400 }
401
402
403
404
405 public final CrossSectionLink<?, ?> getParentLink()
406 {
407 return this.parentLink;
408 }
409
410
411
412
413
414
415 public final DoubleScalar.Rel<LengthUnit> getLateralCenterPosition(final double fractionalPosition)
416 {
417 return DoubleScalar.interpolate(this.designLineOffsetAtBegin, this.designLineOffsetAtEnd, fractionalPosition)
418 .immutable();
419 }
420
421
422
423
424
425
426 public final DoubleScalar<LengthUnit> getLateralCenterPosition(
427 final DoubleScalar.Rel<LengthUnit> longitudinalPosition)
428 {
429 return getLateralCenterPosition(longitudinalPosition.getSI() / getLength().getSI());
430 }
431
432
433
434
435
436
437
438 public final DoubleScalar.Rel<LengthUnit> getWidth(final DoubleScalar.Rel<LengthUnit> longitudinalPosition)
439 {
440 return getWidth(longitudinalPosition.getSI() / getLength().getSI());
441 }
442
443
444
445
446
447
448
449 public final DoubleScalar.Rel<LengthUnit> getWidth(final double fractionalPosition)
450 {
451 return DoubleScalar.interpolate(this.beginWidth, this.endWidth, fractionalPosition).immutable();
452 }
453
454
455 protected abstract double getZ();
456
457
458 @Override
459 public final DirectedPoint getLocation() throws RemoteException
460 {
461 Envelope e = this.contour.getEnvelopeInternal();
462 return new DirectedPoint(0.5 * (e.getMaxX() - e.getMinX()), 0.5 * (e.getMaxY() - e.getMinY()), getZ());
463 }
464
465
466 @Override
467 public final Bounds getBounds() throws RemoteException
468 {
469 Envelope e = this.contour.getEnvelopeInternal();
470 double dx = 0.5 * (e.getMaxX() - e.getMinX());
471 double dy = 0.5 * (e.getMaxY() - e.getMinY());
472 return new BoundingBox(new Point3d(e.getMinX() - dx, e.getMinY() - dy, 0.0), new Point3d(e.getMinX() + dx,
473 e.getMinY() + dy, getZ()));
474 }
475
476
477
478
479
480 public final Geometry getContour()
481 {
482 return this.contour;
483 }
484
485
486
487
488
489
490
491 public final LineString getCenterLine()
492 {
493 return this.crossSectionDesignLine;
494 }
495
496
497
498
499
500
501 public static void printCoordinate(final String prefix, final Coordinate coordinate)
502 {
503 System.out.print(String.format(Locale.US, "%s %8.3f,%8.3f ", prefix, coordinate.x, coordinate.y));
504 }
505
506
507
508
509
510
511
512
513 public static void printCoordinates(final String prefix, final Geometry geometry, final int fromIndex,
514 final int toIndex)
515 {
516 printCoordinates(prefix, geometry.getCoordinates(), fromIndex, toIndex);
517 }
518
519
520
521
522
523
524 public static void printCoordinates(final String prefix, final Geometry geometry)
525 {
526 printCoordinates(prefix, geometry.getCoordinates());
527 }
528
529
530
531
532
533
534 public static void printCoordinates(final String prefix, final Coordinate[] coordinates)
535 {
536 printCoordinates(prefix + "(" + coordinates.length + " pts)", coordinates, 0, coordinates.length);
537 }
538
539
540
541
542
543
544
545
546 public static void printCoordinates(final String prefix, final Coordinate[] coordinates, final int fromIndex,
547 final int toIndex)
548 {
549 System.out.print(prefix);
550 String operator = "M";
551 for (int i = fromIndex; i < toIndex; i++)
552 {
553 printCoordinate(operator, coordinates[i]);
554 operator = "L";
555 }
556 System.out.println("");
557 }
558
559
560
561
562
563 public final DoubleScalar.Rel<LengthUnit> getLength()
564 {
565 return this.length;
566 }
567
568
569 @Override
570 @SuppressWarnings("checkstyle:designforextension")
571 public String toString()
572 {
573 return String.format("offset %.2fm", this.designLineOffsetAtBegin.getSI());
574 }
575
576
577
578
579
580
581
582
583 public final DoubleScalar.Rel<LengthUnit> getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
584 final double fractionalLongitudinalPosition)
585 {
586 DoubleScalar.Rel<LengthUnit> designLineOffset =
587 DoubleScalar.interpolate(this.designLineOffsetAtBegin, this.designLineOffsetAtEnd,
588 fractionalLongitudinalPosition).immutable();
589 DoubleScalar.Rel<LengthUnit> halfWidth =
590 (DoubleScalar.Rel<LengthUnit>) DoubleScalar
591 .interpolate(this.beginWidth, this.endWidth, fractionalLongitudinalPosition).multiply(0.5)
592 .immutable();
593 switch (lateralDirection)
594 {
595 case LEFT:
596 return DoubleScalar.minus(designLineOffset, halfWidth).immutable();
597 case RIGHT:
598 return DoubleScalar.plus(designLineOffset, halfWidth).immutable();
599 default:
600 throw new Error("Bad switch on LateralDirectionality " + lateralDirection);
601 }
602 }
603
604
605
606
607
608
609
610
611
612 public final DoubleScalar.Rel<LengthUnit> getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
613 final DoubleScalar.Rel<LengthUnit> longitudinalPosition)
614 {
615 return getLateralBoundaryPosition(lateralDirection, longitudinalPosition.getSI() / getLength().getSI());
616 }
617
618 }