View Javadoc
1   package org.opentrafficsim.core.geometry;
2   
3   import java.awt.geom.Line2D;
4   import java.util.ArrayList;
5   import java.util.List;
6   
7   import org.djutils.logger.CategoryLogger;
8   import org.locationtech.jts.geom.Coordinate;
9   import org.locationtech.jts.geom.Geometry;
10  import org.locationtech.jts.linearref.LengthIndexedLine;
11  import org.locationtech.jts.operation.buffer.BufferParameters;
12  import org.opentrafficsim.base.logger.Cat;
13  import org.opentrafficsim.core.network.NetworkException;
14  
15  /**
16   * <p>
17   * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
18   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
19   * <p>
20   * $LastChangedDate: 2015-07-16 10:20:53 +0200 (Thu, 16 Jul 2015) $, @version $Revision: 1124 $, by $Author: pknoppers $,
21   * initial version Jul 22, 2015 <br>
22   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
23   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
24   */
25  public final class OTSBufferingJTS
26  {
27      /** Precision of buffer operations. */
28      private static final int QUADRANTSEGMENTS = 16;
29  
30      /**
31       * 
32       */
33      private OTSBufferingJTS()
34      {
35          // cannot be instantiated.
36      }
37  
38      /**
39       * normalize an angle between 0 and 2 * PI.
40       * @param angle double; original angle.
41       * @return angle between 0 and 2 * PI.
42       */
43      private static double norm(final double angle)
44      {
45          double normalized = angle % (2 * Math.PI);
46          if (normalized < 0.0)
47          {
48              normalized += 2 * Math.PI;
49          }
50          return normalized;
51      }
52  
53      /**
54       * @param c1 Coordinate; first coordinate
55       * @param c2 Coordinate; second coordinate
56       * @return the normalized angle of the line between c1 and c2
57       */
58      private static double angle(final Coordinate c1, final Coordinate c2)
59      {
60          return norm(Math.atan2(c2.y - c1.y, c2.x - c1.x));
61      }
62  
63      /**
64       * Compute the distance of a line segment to a point. If the the projected points lies outside the line segment, the nearest
65       * end point of the line segment is returned. Otherwise the point return lies between the end points of the line segment.
66       * <br>
67       * Adapted from <a href="http://paulbourke.net/geometry/pointlineplane/DistancePoint.java"> example code provided by Paul
68       * Bourke</a>.
69       * @param lineP1 OTSPoint3D; start of line segment
70       * @param lineP2 OTSPoint3D; end of line segment
71       * @param point OTSPoint3D; Point to project onto the line segment
72       * @return double; the distance of the projected point or one of the end points of the line segment to the point
73       */
74      public static double distanceLineSegmentToPoint(final OTSPoint3DOTSPoint3D.html#OTSPoint3D">OTSPoint3DOTSPoint3D.html#OTSPoint3D">OTSPoint3D lineP1, final OTSPoint3DOTSPoint3D.html#OTSPoint3D">OTSPoint3D lineP2, final OTSPoint3D point)
75      {
76          return closestPointOnSegmentToPoint(lineP1, lineP2, point).distanceSI(point);
77      }
78  
79      /**
80       * Project a point on a line (2D). If the the projected points lies outside the line segment, the nearest end point of the
81       * line segment is returned. Otherwise the point return lies between the end points of the line segment. <br>
82       * Adapted from <a href="http://paulbourke.net/geometry/pointlineplane/DistancePoint.java"> example code provided by Paul
83       * Bourke</a>.
84       * @param lineP1 OTSPoint3D; start of line segment
85       * @param lineP2 OTSPoint3D; end of line segment
86       * @param point OTSPoint3D; Point to project onto the line segment
87       * @return Point2D.Double; either <cite>lineP1</cite>, or <cite>lineP2</cite> or a new OTSPoint3D that lies somewhere in
88       *         between those two
89       */
90      public static OTSPoint3DOTSPoint3D.html#OTSPoint3D">OTSPoint3Dint3D">OTSPoint3D closestPointOnSegmentToPoint(final OTSPoint3DOTSPoint3D.html#OTSPoint3D">OTSPoint3D lineP1, final OTSPoint3D lineP2,
91              final OTSPoint3D point)
92      {
93          double dX = lineP2.x - lineP1.x;
94          double dY = lineP2.y - lineP1.y;
95          if ((0 == dX) && (0 == dY))
96          {
97              return lineP1;
98          }
99          final double u = ((point.x - lineP1.x) * dX + (point.y - lineP1.y) * dY) / (dX * dX + dY * dY);
100         if (u < 0)
101         {
102             return lineP1;
103         }
104         else if (u > 1)
105         {
106             return lineP2;
107         }
108         else
109         {
110             return new OTSPoint3D(lineP1.x + u * dX, lineP1.y + u * dY); // could use interpolate in stead
111         }
112     }
113 
114     /**
115      * Construct parallel line without.
116      * @param referenceLine OTSLine3D; the reference line
117      * @param offset double; offset distance from the reference line; positive is LEFT, negative is RIGHT
118      * @return OTSLine3D; the line that has the specified offset from the reference line
119      */
120     public static OTSLine3DSLine3D.html#OTSLine3D">OTSLine3D offsetLine(final OTSLine3D referenceLine, final double offset)
121     {
122         try
123         {
124             double bufferOffset = Math.abs(offset);
125             final double precision = 0.00001;
126             if (bufferOffset < precision)
127             {
128                 return referenceLine; // It is immutable; so we can safely return the original
129             }
130             final double circlePrecision = 0.001;
131             List<OTSPoint3D> points = new ArrayList<>();
132             // Make good use of the fact that an OTSLine3D cannot have consecutive duplicate points and has > 1 points
133             OTSPoint3D prevPoint = referenceLine.get(0);
134             Double prevAngle = null;
135             for (int index = 0; index < referenceLine.size() - 1; index++)
136             {
137                 OTSPoint3D nextPoint = referenceLine.get(index + 1);
138                 double angle = Math.atan2(nextPoint.y - prevPoint.y, nextPoint.x - prevPoint.x);
139                 OTSPoint3D segmentFrom =
140                         new OTSPoint3D(prevPoint.x - Math.sin(angle) * offset, prevPoint.y + Math.cos(angle) * offset);
141                 OTSPoint3D segmentTo =
142                         new OTSPoint3D(nextPoint.x - Math.sin(angle) * offset, nextPoint.y + Math.cos(angle) * offset);
143                 if (index > 0)
144                 {
145                     double deltaAngle = angle - prevAngle;
146                     if (Math.abs(deltaAngle) > Math.PI)
147                     {
148                         deltaAngle -= Math.signum(deltaAngle) * 2 * Math.PI;
149                     }
150                     if (deltaAngle * offset > 0)
151                     {
152                         // Inside of curve of reference line.
153                         // Add the intersection point of each previous segment and the next segment
154                         OTSPoint3D pPoint = null;
155                         for (int i = 0; i < points.size(); i++)
156                         {
157                             OTSPoint3D p = points.get(i);
158                             if (Double.isNaN(p.z))
159                             {
160                                 continue; // skip this one
161                             }
162                             if (null != pPoint)
163                             {
164                                 double pAngle = Math.atan2(p.y - pPoint.y, p.x - pPoint.x);
165                                 double totalAngle = angle - pAngle;
166                                 if (Math.abs(totalAngle) > Math.PI)
167                                 {
168                                     totalAngle += Math.signum(totalAngle) * 2 * Math.PI;
169                                 }
170                                 if (Math.abs(totalAngle) > 0.01)
171                                 {
172                                     // CategoryLogger.trace(Cat.CORE, "preceding segment " + pPoint + " to " + p + ", this
173                                     // segment "
174                                     // + segmentFrom + " to " + segmentTo + " totalAngle " + totalAngle);
175                                     OTSPoint3D intermediatePoint =
176                                             intersectionOfLineSegments(pPoint, p, segmentFrom, segmentTo);
177                                     if (null != intermediatePoint)
178                                     {
179                                         // mark it as added point at inside corner
180                                         intermediatePoint =
181                                                 new OTSPoint3D(intermediatePoint.x, intermediatePoint.y, Double.NaN);
182                                         // CategoryLogger.trace(Cat.CORE, "Inserting intersection of preceding segment and this
183                                         // "
184                                         // + "segment " + intermediatePoint);
185                                         points.add(intermediatePoint);
186                                     }
187                                 }
188                             }
189                             pPoint = p;
190                         }
191                     }
192                     else
193                     {
194                         // Outside of curve of reference line
195                         // Approximate an arc using straight segments.
196                         // Determine how many segments are needed.
197                         int numSegments = 1;
198                         if (Math.abs(deltaAngle) > Math.PI / 2)
199                         {
200                             numSegments = 2;
201                         }
202                         for (; numSegments < 1000; numSegments *= 2)
203                         {
204                             double maxError = bufferOffset * (1 - Math.abs(Math.cos(deltaAngle / numSegments / 2)));
205                             if (maxError < circlePrecision)
206                             {
207                                 break; // required precision reached
208                             }
209                         }
210                         // Generate the intermediate points
211                         for (int additionalPoint = 1; additionalPoint < numSegments; additionalPoint++)
212                         {
213                             double intermediateAngle =
214                                     (additionalPoint * angle + (numSegments - additionalPoint) * prevAngle) / numSegments;
215                             if (prevAngle * angle < 0 && Math.abs(prevAngle) > Math.PI / 2 && Math.abs(angle) > Math.PI / 2)
216                             {
217                                 intermediateAngle += Math.PI;
218                             }
219                             OTSPoint3D.html#OTSPoint3D">OTSPoint3D intermediatePoint = new OTSPoint3D(prevPoint.x - Math.sin(intermediateAngle) * offset,
220                                     prevPoint.y + Math.cos(intermediateAngle) * offset);
221                             // CategoryLogger.trace(Cat.CORE, "inserting intermediate point " + intermediatePoint + " for angle
222                             // "
223                             // + Math.toDegrees(intermediateAngle));
224                             points.add(intermediatePoint);
225                         }
226                     }
227                 }
228                 points.add(segmentFrom);
229                 points.add(segmentTo);
230                 prevPoint = nextPoint;
231                 prevAngle = angle;
232             }
233             // CategoryLogger.trace(Cat.CORE, OTSGeometry.printCoordinates("#before cleanup: \nc0,0,0\n#", new
234             // OTSLine3D(points), "\n
235             // "));
236             // Remove points that are closer than the specified offset
237             for (int index = 1; index < points.size() - 1; index++)
238             {
239                 OTSPoint3D checkPoint = points.get(index);
240                 prevPoint = null;
241                 boolean tooClose = false;
242                 boolean somewhereAtCorrectDistance = false;
243                 for (int i = 0; i < referenceLine.size(); i++)
244                 {
245                     OTSPoint3D p = referenceLine.get(i);
246                     if (null != prevPoint)
247                     {
248                         OTSPoint3D closestPoint = closestPointOnSegmentToPoint(prevPoint, p, checkPoint);
249                         if (closestPoint != referenceLine.get(0) && closestPoint != referenceLine.get(referenceLine.size() - 1))
250                         {
251                             double distance = closestPoint.horizontalDistanceSI(checkPoint);
252                             if (distance < bufferOffset - circlePrecision)
253                             {
254                                 // CategoryLogger.trace(Cat.CORE, "point " + checkPoint + " inside buffer (distance is " +
255                                 // distance +
256                                 // ")");
257                                 tooClose = true;
258                                 break;
259                             }
260                             else if (distance < bufferOffset + precision)
261                             {
262                                 somewhereAtCorrectDistance = true;
263                             }
264                         }
265                     }
266                     prevPoint = p;
267                 }
268                 if (tooClose || !somewhereAtCorrectDistance)
269                 {
270                     // CategoryLogger.trace(Cat.CORE, "Removing " + checkPoint);
271                     points.remove(index);
272                     index--;
273                 }
274             }
275             // Fix the z-coordinate of all points that were added as intersections of segments.
276             for (int index = 0; index < points.size(); index++)
277             {
278                 OTSPoint3D p = points.get(index);
279                 if (Double.isNaN(p.z))
280                 {
281                     points.set(index, new OTSPoint3D(p.x, p.y, 0));
282                 }
283             }
284             return OTSLine3D.createAndCleanOTSLine3D(points);
285         }
286         catch (OTSGeometryException exception)
287         {
288             CategoryLogger.always().error(exception, "Exception in offsetLine - should never happen");
289             return null;
290         }
291     }
292 
293     /**
294      * Compute the 2D intersection of two line segments. Both line segments are defined by two points (that should be distinct).
295      * @param line1P1 OTSPoint3D; first point of line 1
296      * @param line1P2 OTSPoint3D; second point of line 1
297      * @param line2P1 OTSPoint3D; first point of line 2
298      * @param line2P2 OTSPoint3D; second point of line 2
299      * @return OTSPoint3D; the intersection of the two lines, or null if the lines are (almost) parallel, or do not intersect
300      */
301     private static OTSPoint3DTSPoint3D.html#OTSPoint3D">OTSPoint3DPoint3D">OTSPoint3D intersectionOfLineSegments(final OTSPoint3DTSPoint3D.html#OTSPoint3D">OTSPoint3D line1P1, final OTSPoint3D line1P2,
302             final OTSPoint3DTSPoint3D.html#OTSPoint3D">OTSPoint3D line2P1, final OTSPoint3D line2P2)
303     {
304         double denominator =
305                 (line2P2.y - line2P1.y) * (line1P2.x - line1P1.x) - (line2P2.x - line2P1.x) * (line1P2.y - line1P1.y);
306         if (denominator == 0f)
307         {
308             return null; // lines are parallel (they might even be on top of each other, but we don't check that)
309         }
310         double uA = ((line2P2.x - line2P1.x) * (line1P1.y - line2P1.y) - (line2P2.y - line2P1.y) * (line1P1.x - line2P1.x))
311                 / denominator;
312         if ((uA < 0f) || (uA > 1f))
313         {
314             return null; // intersection outside line 1
315         }
316         double uB = ((line1P2.x - line1P1.x) * (line1P1.y - line2P1.y) - (line1P2.y - line1P1.y) * (line1P1.x - line2P1.x))
317                 / denominator;
318         if (uB < 0 || uB > 1)
319         {
320             return null; // intersection outside line 2
321         }
322         return new OTSPoint3D(line1P1.x + uA * (line1P2.x - line1P1.x), line1P1.y + uA * (line1P2.y - line1P1.y), 0);
323     }
324 
325     /**
326      * Generate a Geometry that has a fixed offset from a reference Geometry.
327      * @param referenceLine OTSLine3D; the reference line
328      * @param offset double; offset distance from the reference line; positive is LEFT, negative is RIGHT
329      * @return OTSLine3D; the line that has the specified offset from the reference line
330      * @throws OTSGeometryException on failure
331      */
332     @SuppressWarnings("checkstyle:methodlength")
333     public static OTSLine3D.html#OTSLine3D">OTSLine3D offsetGeometryOLD(final OTSLine3D referenceLine, final double offset) throws OTSGeometryException
334     {
335         Coordinate[] referenceCoordinates = referenceLine.getCoordinates();
336         // printCoordinates("reference", referenceCoordinates);
337         double bufferOffset = Math.abs(offset);
338         final double precision = 0.000001;
339         if (bufferOffset < precision) // if this is not added, and offset = 1E-16: CRASH
340         {
341             // return a copy of the reference line
342             return new OTSLine3D(referenceCoordinates);
343         }
344         Geometry geometryLine = referenceLine.getLineString();
345         Coordinate[] bufferCoordinates =
346                 geometryLine.buffer(bufferOffset, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();
347 
348         // Z coordinates may be NaN at this point
349 
350         // find the coordinate indices closest to the start point and end point,
351         // at a distance of approximately the offset
352         Coordinate sC0 = referenceCoordinates[0];
353         Coordinate sC1 = referenceCoordinates[1];
354         Coordinate eCm1 = referenceCoordinates[referenceCoordinates.length - 1];
355         Coordinate eCm2 = referenceCoordinates[referenceCoordinates.length - 2];
356 
357         double expectedStartAngle = norm(angle(sC0, sC1) + Math.signum(offset) * Math.PI / 2.0);
358         double expectedEndAngle = norm(angle(eCm2, eCm1) + Math.signum(offset) * Math.PI / 2.0);
359         Coordinate sExpected = new Coordinate(sC0.x + bufferOffset * Math.cos(expectedStartAngle),
360                 sC0.y + bufferOffset * Math.sin(expectedStartAngle));
361         Coordinate eExpected = new Coordinate(eCm1.x + bufferOffset * Math.cos(expectedEndAngle),
362                 eCm1.y + bufferOffset * Math.sin(expectedEndAngle));
363 
364         // which coordinates are closest to sExpected and eExpected?
365         double dS = Double.MAX_VALUE;
366         double dE = Double.MAX_VALUE;
367         int sIndex = -1;
368         int eIndex = -1;
369         for (int i = 0; i < bufferCoordinates.length; i++)
370         {
371             Coordinate c = bufferCoordinates[i];
372             double dsc = c.distance(sExpected);
373             double dec = c.distance(eExpected);
374             if (dsc < dS)
375             {
376                 dS = dsc;
377                 sIndex = i;
378             }
379             if (dec < dE)
380             {
381                 dE = dec;
382                 eIndex = i;
383             }
384         }
385 
386         if (sIndex == -1)
387         {
388             throw new OTSGeometryException("offsetGeometry: startIndex not found for line " + referenceLine);
389         }
390         if (eIndex == -1)
391         {
392             throw new OTSGeometryException("offsetGeometry: endIndex not found for line " + referenceLine);
393         }
394         if (dS > 0.01)
395         {
396             CategoryLogger.filter(Cat.CORE).trace(referenceLine.toExcel() + "\n\n\n\n"
397                     + new OTSLine3D(bufferCoordinates).toExcel() + "\n\n\n\n" + sExpected + "\n" + eExpected);
398             throw new OTSGeometryException("offsetGeometry: startDistance too big (" + dS + ") for line " + referenceLine);
399         }
400         if (dE > 0.01)
401         {
402             throw new OTSGeometryException("offsetGeometry: endDistance too big (" + dE + ") for line " + referenceLine);
403         }
404 
405         // try positive direction
406         boolean ok = true;
407         int i = sIndex;
408         Coordinate lastC = null;
409         List<OTSPoint3D> result = new ArrayList<>();
410         while (ok)
411         {
412             Coordinate c = bufferCoordinates[i];
413             if (lastC != null && close(c, lastC, sC0, eCm1))
414             {
415                 ok = false;
416                 break;
417             }
418             result.add(new OTSPoint3D(c));
419             if (i == eIndex)
420             {
421                 return OTSLine3D.createAndCleanOTSLine3D(result);
422             }
423             i = (i == bufferCoordinates.length - 1) ? 0 : i + 1;
424             lastC = c;
425         }
426 
427         // try negative direction
428         ok = true;
429         i = sIndex;
430         lastC = null;
431         result = new ArrayList<>();
432         while (ok)
433         {
434             Coordinate c = bufferCoordinates[i];
435             if (lastC != null && close(c, lastC, sC0, eCm1))
436             {
437                 ok = false;
438                 break;
439             }
440             result.add(new OTSPoint3D(c));
441             if (i == eIndex)
442             {
443                 return OTSLine3D.createAndCleanOTSLine3D(result);
444             }
445             i = (i == 0) ? bufferCoordinates.length - 1 : i - 1;
446             lastC = c;
447         }
448 
449         /*- CategoryLogger.trace(Cat.CORE, referenceLine.toExcel() + "\n\n\n\n" + new OTSLine3D(bufferCoordinates).toExcel()
450             + "\n\n\n\n" + sExpected + "\n" + eExpected); */
451         throw new OTSGeometryException("offsetGeometry: could not find offset in either direction for line " + referenceLine);
452     }
453 
454     /**
455      * Check if the points check[] are close to the line [lineC1..LineC2].
456      * @param lineC1 Coordinate; first point of the line
457      * @param lineC2 Coordinate; second point of the line
458      * @param check Coordinate...; the coordinates to check
459      * @return whether one of the points to check is close to the line.
460      */
461     private static boolean close(final Coordinate lineC1, final Coordinate lineC2, final Coordinate... check)
462     {
463         Line2D.Double line = new Line2D.Double(lineC1.x, lineC1.y, lineC2.x, lineC2.y);
464         for (Coordinate c : check)
465         {
466             if (line.ptSegDist(c.x, c.y) < 0.01)
467             {
468                 return true;
469             }
470         }
471         return false;
472     }
473 
474     /**
475      * Create a line at linearly varying offset from a reference line. The offset may change linearly from its initial value at
476      * the start of the reference line to its final offset value at the end of the reference line.
477      * @param referenceLine OTSLine3D; the Geometry of the reference line
478      * @param offsetAtStart double; offset at the start of the reference line (positive value is Left, negative value is Right)
479      * @param offsetAtEnd double; offset at the end of the reference line (positive value is Left, negative value is Right)
480      * @return Geometry; the Geometry of the line at linearly changing offset of the reference line
481      * @throws OTSGeometryException when this method fails to create the offset line
482      */
483     public static OTSLine3DSLine3D.html#OTSLine3D">OTSLine3D offsetLine(final OTSLine3D referenceLine, final double offsetAtStart, final double offsetAtEnd)
484             throws OTSGeometryException
485     {
486         // CategoryLogger.trace(Cat.CORE, OTSGeometry.printCoordinates("#referenceLine: \nc1,0,0\n# offset at start is " +
487         // offsetAtStart + " at end is " + offsetAtEnd + "\n#", referenceLine, "\n "));
488 
489         OTSLine3D offsetLineAtStart = offsetLine(referenceLine, offsetAtStart);
490         if (offsetAtStart == offsetAtEnd)
491         {
492             return offsetLineAtStart; // offset does not change
493         }
494         // CategoryLogger.trace(Cat.CORE, OTSGeometry.printCoordinates("#offset line at start: \nc0,0,0\n#", offsetLineAtStart,
495         // "\n"));
496         OTSLine3D offsetLineAtEnd = offsetLine(referenceLine, offsetAtEnd);
497         // CategoryLogger.trace(Cat.CORE, OTSGeometry.printCoordinates("#offset line at end: \nc0.7,0.7,0.7\n#",
498         // offsetLineAtEnd,
499         // "\n"));
500         Geometry startGeometry = offsetLineAtStart.getLineString();
501         Geometry endGeometry = offsetLineAtEnd.getLineString();
502         LengthIndexedLine first = new LengthIndexedLine(startGeometry);
503         double firstLength = startGeometry.getLength();
504         LengthIndexedLine second = new LengthIndexedLine(endGeometry);
505         double secondLength = endGeometry.getLength();
506         ArrayList<Coordinate> out = new ArrayList<Coordinate>();
507         Coordinate[] firstCoordinates = startGeometry.getCoordinates();
508         Coordinate[] secondCoordinates = endGeometry.getCoordinates();
509         int firstIndex = 0;
510         int secondIndex = 0;
511         Coordinate prevCoordinate = null;
512         final double tooClose = 0.05; // 5 cm
513         while (firstIndex < firstCoordinates.length && secondIndex < secondCoordinates.length)
514         {
515             double firstRatio = firstIndex < firstCoordinates.length ? first.indexOf(firstCoordinates[firstIndex]) / firstLength
516                     : Double.MAX_VALUE;
517             double secondRatio = secondIndex < secondCoordinates.length
518                     ? second.indexOf(secondCoordinates[secondIndex]) / secondLength : Double.MAX_VALUE;
519             double ratio;
520             if (firstRatio < secondRatio)
521             {
522                 ratio = firstRatio;
523                 firstIndex++;
524             }
525             else
526             {
527                 ratio = secondRatio;
528                 secondIndex++;
529             }
530             Coordinate firstCoordinate = first.extractPoint(ratio * firstLength);
531             Coordinate secondCoordinate = second.extractPoint(ratio * secondLength);
532             Coordinate resultCoordinate = new Coordinate((1 - ratio) * firstCoordinate.x + ratio * secondCoordinate.x,
533                     (1 - ratio) * firstCoordinate.y + ratio * secondCoordinate.y);
534             if (null == prevCoordinate || resultCoordinate.distance(prevCoordinate) > tooClose)
535             {
536                 out.add(resultCoordinate);
537                 prevCoordinate = resultCoordinate;
538             }
539         }
540         Coordinate[] resultCoordinates = new Coordinate[out.size()];
541         for (int index = 0; index < out.size(); index++)
542         {
543             resultCoordinates[index] = out.get(index);
544         }
545         return new OTSLine3D(resultCoordinates);
546     }
547 
548     /**
549      * @param args String[]; args
550      * @throws NetworkException on error
551      * @throws OTSGeometryException on error
552      */
553     public static void main(final String[] args) throws NetworkException, OTSGeometryException
554     {
555         // OTSLine3D line =
556         // new OTSLine3D(new OTSPoint3D[]{new OTSPoint3D(-579.253, 60.157, 1.568),
557         // new OTSPoint3D(-579.253, 60.177, 1.568)});
558         // double offset = 4.83899987;
559         // System.out.println(OTSBufferingOLD.offsetGeometryOLD(line, offset));
560         OTSLine3Dtry/OTSLine3D.html#OTSLine3D">OTSLine3D line = new OTSLine3D(new OTSPoint3Dometry/OTSPoint3D.html#OTSPoint3D">OTSPoint3D[] {new OTSPoint3D(-579.253, 60.157, 4.710),
561                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-579.253, 60.144, 4.712), new OTSPoint3D(-579.253, 60.144, 0.000),
562                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-579.251, 60.044, 0.000), new OTSPoint3D(-579.246, 59.944, 0.000),
563                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-579.236, 59.845, 0.000), new OTSPoint3D(-579.223, 59.746, 0.000),
564                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-579.206, 59.647, 0.000), new OTSPoint3D(-579.185, 59.549, 0.000),
565                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-579.161, 59.452, 0.000), new OTSPoint3D(-579.133, 59.356, 0.000),
566                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-579.101, 59.261, 0.000), new OTSPoint3D(-579.066, 59.168, 0.000),
567                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-579.028, 59.075, 0.000), new OTSPoint3D(-578.986, 58.985, 0.000),
568                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-578.940, 58.896, 0.000), new OTSPoint3D(-578.891, 58.809, 0.000),
569                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-578.839, 58.723, 0.000), new OTSPoint3D(-578.784, 58.640, 0.000),
570                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-578.725, 58.559, 0.000), new OTSPoint3D(-578.664, 58.480, 0.000),
571                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-578.599, 58.403, 0.000), new OTSPoint3D(-578.532, 58.329, 0.000),
572                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-578.462, 58.258, 0.000), new OTSPoint3D(-578.390, 58.189, 0.000),
573                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-578.314, 58.123, 0.000), new OTSPoint3D(-578.237, 58.060, 0.000),
574                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-578.157, 58.000, 0.000), new OTSPoint3D(-578.075, 57.943, 0.000),
575                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-577.990, 57.889, 0.000), new OTSPoint3D(-577.904, 57.839, 0.000),
576                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-577.816, 57.791, 0.000), new OTSPoint3D(-577.726, 57.747, 0.000),
577                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-577.635, 57.707, 0.000), new OTSPoint3D(-577.542, 57.670, 0.000),
578                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-577.448, 57.636, 0.000), new OTSPoint3D(-577.352, 57.606, 0.000),
579                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-577.256, 57.580, 0.000), new OTSPoint3D(-577.159, 57.557, 0.000),
580                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-577.060, 57.538, 0.000), new OTSPoint3D(-576.962, 57.523, 0.000),
581                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-576.862, 57.512, 0.000), new OTSPoint3D(-576.763, 57.504, 0.000),
582                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-576.663, 57.500, 0.000), new OTSPoint3D(-576.623, 57.500, 6.278),
583                 new OTSPoint3DOTSPoint3D">OTSPoint3D(-576.610, 57.500, 6.280), new OTSPoint3D(-567.499, 57.473, 6.280)});
584         System.out.println(line.toExcel());
585         System.out.println(OTSBufferingJTS.offsetGeometryOLD(line, -1.831));
586     }
587 }