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