OTSBufferingJTS.java

  1. package org.opentrafficsim.core.geometry;

  2. import java.awt.geom.Line2D;
  3. import java.util.ArrayList;
  4. import java.util.List;

  5. import org.opentrafficsim.core.network.NetworkException;

  6. import com.vividsolutions.jts.geom.Coordinate;
  7. import com.vividsolutions.jts.geom.Geometry;
  8. import com.vividsolutions.jts.linearref.LengthIndexedLine;
  9. import com.vividsolutions.jts.operation.buffer.BufferParameters;

  10. /**
  11.  * <p>
  12.  * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  13.  * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  14.  * <p>
  15.  * $LastChangedDate: 2015-07-16 10:20:53 +0200 (Thu, 16 Jul 2015) $, @version $Revision: 1124 $, by $Author: pknoppers $,
  16.  * initial version Jul 22, 2015 <br>
  17.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  18.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  19.  */
  20. public final class OTSBufferingJTS
  21. {
  22.     /** Precision of buffer operations. */
  23.     private static final int QUADRANTSEGMENTS = 16;

  24.     /**
  25.      *
  26.      */
  27.     private OTSBufferingJTS()
  28.     {
  29.         // cannot be instantiated.
  30.     }

  31.     /**
  32.      * normalize an angle between 0 and 2 * PI.
  33.      * @param angle original angle.
  34.      * @return angle between 0 and 2 * PI.
  35.      */
  36.     private static double norm(final double angle)
  37.     {
  38.         double normalized = angle % (2 * Math.PI);
  39.         if (normalized < 0.0)
  40.         {
  41.             normalized += 2 * Math.PI;
  42.         }
  43.         return normalized;
  44.     }

  45.     /**
  46.      * @param c1 first coordinate
  47.      * @param c2 second coordinate
  48.      * @return the normalized angle of the line between c1 and c2
  49.      */
  50.     private static double angle(final Coordinate c1, final Coordinate c2)
  51.     {
  52.         return norm(Math.atan2(c2.y - c1.y, c2.x - c1.x));
  53.     }

  54.     /**
  55.      * Compute the distance of a line segment to a point. If the the projected points lies outside the line segment, the nearest
  56.      * end point of the line segment is returned. Otherwise the point return lies between the end points of the line segment.
  57.      * <br>
  58.      * Adapted from <a href="http://paulbourke.net/geometry/pointlineplane/DistancePoint.java"> example code provided by Paul
  59.      * Bourke</a>.
  60.      * @param lineP1 OTSPoint3D; start of line segment
  61.      * @param lineP2 OTSPoint3D; end of line segment
  62.      * @param point Point to project onto the line segment
  63.      * @return double; the distance of the projected point or one of the end points of the line segment to the point
  64.      */
  65.     public static double distanceLineSegmentToPoint(final OTSPoint3D lineP1, final OTSPoint3D lineP2, final OTSPoint3D point)
  66.     {
  67.         return closestPointOnSegmentToPoint(lineP1, lineP2, point).distanceSI(point);
  68.     }

  69.     /**
  70.      * Project a point on a line (2D). If the the projected points lies outside the line segment, the nearest end point of the
  71.      * line segment is returned. Otherwise the point return lies between the end points of the line segment. <br>
  72.      * Adapted from <a href="http://paulbourke.net/geometry/pointlineplane/DistancePoint.java"> example code provided by Paul
  73.      * Bourke</a>.
  74.      * @param lineP1 OTSPoint3D; start of line segment
  75.      * @param lineP2 OTSPoint3D; end of line segment
  76.      * @param point Point to project onto the line segment
  77.      * @return Point2D.Double; either <cite>lineP1</cite>, or <cite>lineP2</cite> or a new OTSPoint3D that lies somewhere in
  78.      *         between those two
  79.      */
  80.     public static OTSPoint3D closestPointOnSegmentToPoint(final OTSPoint3D lineP1, final OTSPoint3D lineP2,
  81.             final OTSPoint3D point)
  82.     {
  83.         double dX = lineP2.x - lineP1.x;
  84.         double dY = lineP2.y - lineP1.y;
  85.         if ((0 == dX) && (0 == dY))
  86.         {
  87.             return lineP1;
  88.         }
  89.         final double u = ((point.x - lineP1.x) * dX + (point.y - lineP1.y) * dY) / (dX * dX + dY * dY);
  90.         if (u < 0)
  91.         {
  92.             return lineP1;
  93.         }
  94.         else if (u > 1)
  95.         {
  96.             return lineP2;
  97.         }
  98.         else
  99.         {
  100.             return new OTSPoint3D(lineP1.x + u * dX, lineP1.y + u * dY); // could use interpolate in stead
  101.         }
  102.     }

  103.     /**
  104.      * Construct parallel line without.
  105.      * @param referenceLine OTSLine3D; the reference line
  106.      * @param offset double; offset distance from the reference line; positive is LEFT, negative is RIGHT
  107.      * @return OTSLine3D; the line that has the specified offset from the reference line
  108.      */
  109.     public static OTSLine3D offsetLine(final OTSLine3D referenceLine, final double offset)
  110.     {
  111.         try
  112.         {
  113.             double bufferOffset = Math.abs(offset);
  114.             final double precision = 0.00001;
  115.             if (bufferOffset < precision)
  116.             {
  117.                 return referenceLine; // It is immutable; so we can safely return the original
  118.             }
  119.             final double circlePrecision = 0.001;
  120.             List<OTSPoint3D> points = new ArrayList<>();
  121.             // Make good use of the fact that an OTSLine3D cannot have consecutive duplicate points and has > 1 points
  122.             OTSPoint3D prevPoint = referenceLine.get(0);
  123.             Double prevAngle = null;
  124.             for (int index = 0; index < referenceLine.size() - 1; index++)
  125.             {
  126.                 OTSPoint3D nextPoint = referenceLine.get(index + 1);
  127.                 double angle = Math.atan2(nextPoint.y - prevPoint.y, nextPoint.x - prevPoint.x);
  128.                 OTSPoint3D segmentFrom =
  129.                         new OTSPoint3D(prevPoint.x - Math.sin(angle) * offset, prevPoint.y + Math.cos(angle) * offset);
  130.                 OTSPoint3D segmentTo =
  131.                         new OTSPoint3D(nextPoint.x - Math.sin(angle) * offset, nextPoint.y + Math.cos(angle) * offset);
  132.                 if (index > 0)
  133.                 {
  134.                     double deltaAngle = angle - prevAngle;
  135.                     if (Math.abs(deltaAngle) > Math.PI)
  136.                     {
  137.                         deltaAngle -= Math.signum(deltaAngle) * 2 * Math.PI;
  138.                     }
  139.                     if (deltaAngle * offset > 0)
  140.                     {
  141.                         // Inside of curve of reference line.
  142.                         // Add the intersection point of each previous segment and the next segment
  143.                         OTSPoint3D pPoint = null;
  144.                         for (int i = 0; i < points.size(); i++)
  145.                         {
  146.                             OTSPoint3D p = points.get(i);
  147.                             if (Double.isNaN(p.z))
  148.                             {
  149.                                 continue; // skip this one
  150.                             }
  151.                             if (null != pPoint)
  152.                             {
  153.                                 double pAngle = Math.atan2(p.y - pPoint.y, p.x - pPoint.x);
  154.                                 double totalAngle = angle - pAngle;
  155.                                 if (Math.abs(totalAngle) > Math.PI)
  156.                                 {
  157.                                     totalAngle += Math.signum(totalAngle) * 2 * Math.PI;
  158.                                 }
  159.                                 if (Math.abs(totalAngle) > 0.01)
  160.                                 {
  161.                                     // System.out.println("preceding segment " + pPoint + " to " + p + ", this segment "
  162.                                     // + segmentFrom + " to " + segmentTo + " totalAngle " + totalAngle);
  163.                                     OTSPoint3D intermediatePoint =
  164.                                             intersectionOfLineSegments(pPoint, p, segmentFrom, segmentTo);
  165.                                     if (null != intermediatePoint)
  166.                                     {
  167.                                         // mark it as added point at inside corner
  168.                                         intermediatePoint =
  169.                                                 new OTSPoint3D(intermediatePoint.x, intermediatePoint.y, Double.NaN);
  170.                                         // System.out.println("Inserting intersection of preceding segment and this segment "
  171.                                         // + intermediatePoint);
  172.                                         points.add(intermediatePoint);
  173.                                     }
  174.                                 }
  175.                             }
  176.                             pPoint = p;
  177.                         }
  178.                     }
  179.                     else
  180.                     {
  181.                         // Outside of curve of reference line
  182.                         // Approximate an arc using straight segments.
  183.                         // Determine how many segments are needed.
  184.                         int numSegments = 1;
  185.                         if (Math.abs(deltaAngle) > Math.PI / 2)
  186.                         {
  187.                             numSegments = 2;
  188.                         }
  189.                         for (; numSegments < 1000; numSegments *= 2)
  190.                         {
  191.                             double maxError = bufferOffset * (1 - Math.abs(Math.cos(deltaAngle / numSegments / 2)));
  192.                             if (maxError < circlePrecision)
  193.                             {
  194.                                 break; // required precision reached
  195.                             }
  196.                         }
  197.                         // Generate the intermediate points
  198.                         for (int additionalPoint = 1; additionalPoint < numSegments; additionalPoint++)
  199.                         {
  200.                             double intermediateAngle =
  201.                                     (additionalPoint * angle + (numSegments - additionalPoint) * prevAngle) / numSegments;
  202.                             if (prevAngle * angle < 0 && Math.abs(prevAngle) > Math.PI / 2 && Math.abs(angle) > Math.PI / 2)
  203.                             {
  204.                                 intermediateAngle += Math.PI;
  205.                             }
  206.                             OTSPoint3D intermediatePoint = new OTSPoint3D(prevPoint.x - Math.sin(intermediateAngle) * offset,
  207.                                     prevPoint.y + Math.cos(intermediateAngle) * offset);
  208.                             // System.out.println("inserting intermediate point " + intermediatePoint + " for angle "
  209.                             // + Math.toDegrees(intermediateAngle));
  210.                             points.add(intermediatePoint);
  211.                         }
  212.                     }
  213.                 }
  214.                 points.add(segmentFrom);
  215.                 points.add(segmentTo);
  216.                 prevPoint = nextPoint;
  217.                 prevAngle = angle;
  218.             }
  219.             // System.out.println(OTSGeometry.printCoordinates("#before cleanup: \nc0,0,0\n#", new OTSLine3D(points), "\n "));
  220.             // Remove points that are closer than the specified offset
  221.             for (int index = 1; index < points.size() - 1; index++)
  222.             {
  223.                 OTSPoint3D checkPoint = points.get(index);
  224.                 prevPoint = null;
  225.                 boolean tooClose = false;
  226.                 boolean somewhereAtCorrectDistance = false;
  227.                 for (int i = 0; i < referenceLine.size(); i++)
  228.                 {
  229.                     OTSPoint3D p = referenceLine.get(i);
  230.                     if (null != prevPoint)
  231.                     {
  232.                         OTSPoint3D closestPoint = closestPointOnSegmentToPoint(prevPoint, p, checkPoint);
  233.                         if (closestPoint != referenceLine.get(0) && closestPoint != referenceLine.get(referenceLine.size() - 1))
  234.                         {
  235.                             double distance = closestPoint.horizontalDistanceSI(checkPoint);
  236.                             if (distance < bufferOffset - circlePrecision)
  237.                             {
  238.                                 // System.out.print("point " + checkPoint + " inside buffer (distance is " + distance + ")");
  239.                                 tooClose = true;
  240.                                 break;
  241.                             }
  242.                             else if (distance < bufferOffset + precision)
  243.                             {
  244.                                 somewhereAtCorrectDistance = true;
  245.                             }
  246.                         }
  247.                     }
  248.                     prevPoint = p;
  249.                 }
  250.                 if (tooClose || !somewhereAtCorrectDistance)
  251.                 {
  252.                     // System.out.println("Removing " + checkPoint);
  253.                     points.remove(index);
  254.                     index--;
  255.                 }
  256.             }
  257.             // Fix the z-coordinate of all points that were added as intersections of segments.
  258.             for (int index = 0; index < points.size(); index++)
  259.             {
  260.                 OTSPoint3D p = points.get(index);
  261.                 if (Double.isNaN(p.z))
  262.                 {
  263.                     points.set(index, new OTSPoint3D(p.x, p.y, 0));
  264.                 }
  265.             }
  266.             return OTSLine3D.createAndCleanOTSLine3D(points);
  267.         }
  268.         catch (OTSGeometryException exception)
  269.         {
  270.             System.err.println("Cannot happen");
  271.             exception.printStackTrace();
  272.             return null;
  273.         }
  274.     }

  275.     /**
  276.      * Compute the 2D intersection of two line segments. Both line segments are defined by two points (that should be distinct).
  277.      * @param line1P1 OTSPoint3D; first point of line 1
  278.      * @param line1P2 OTSPoint3D; second point of line 1
  279.      * @param line2P1 OTSPoint3D; first point of line 2
  280.      * @param line2P2 OTSPoint3D; second point of line 2
  281.      * @return OTSPoint3D; the intersection of the two lines, or null if the lines are (almost) parallel, or do not intersect
  282.      */
  283.     private static OTSPoint3D intersectionOfLineSegments(final OTSPoint3D line1P1, final OTSPoint3D line1P2,
  284.             final OTSPoint3D line2P1, final OTSPoint3D line2P2)
  285.     {
  286.         double denominator =
  287.                 (line2P2.y - line2P1.y) * (line1P2.x - line1P1.x) - (line2P2.x - line2P1.x) * (line1P2.y - line1P1.y);
  288.         if (denominator == 0f)
  289.         {
  290.             return null; // lines are parallel (they might even be on top of each other, but we don't check that)
  291.         }
  292.         double uA = ((line2P2.x - line2P1.x) * (line1P1.y - line2P1.y) - (line2P2.y - line2P1.y) * (line1P1.x - line2P1.x))
  293.                 / denominator;
  294.         if ((uA < 0f) || (uA > 1f))
  295.         {
  296.             return null; // intersection outside line 1
  297.         }
  298.         double uB = ((line1P2.x - line1P1.x) * (line1P1.y - line2P1.y) - (line1P2.y - line1P1.y) * (line1P1.x - line2P1.x))
  299.                 / denominator;
  300.         if (uB < 0 || uB > 1)
  301.         {
  302.             return null; // intersection outside line 2
  303.         }
  304.         return new OTSPoint3D(line1P1.x + uA * (line1P2.x - line1P1.x), line1P1.y + uA * (line1P2.y - line1P1.y), 0);
  305.     }

  306.     /**
  307.      * Generate a Geometry that has a fixed offset from a reference Geometry.
  308.      * @param referenceLine Geometry; the reference line
  309.      * @param offset double; offset distance from the reference line; positive is LEFT, negative is RIGHT
  310.      * @return OTSLine3D; the line that has the specified offset from the reference line
  311.      * @throws OTSGeometryException on failure
  312.      */
  313.     @SuppressWarnings("checkstyle:methodlength")
  314.     public static OTSLine3D offsetGeometryOLD(final OTSLine3D referenceLine, final double offset) throws OTSGeometryException
  315.     {
  316.         Coordinate[] referenceCoordinates = referenceLine.getCoordinates();
  317.         // printCoordinates("reference", referenceCoordinates);
  318.         double bufferOffset = Math.abs(offset);
  319.         final double precision = 0.000001;
  320.         if (bufferOffset < precision) // if this is not added, and offset = 1E-16: CRASH
  321.         {
  322.             // return a copy of the reference line
  323.             return new OTSLine3D(referenceCoordinates);
  324.         }
  325.         Geometry geometryLine = referenceLine.getLineString();
  326.         Coordinate[] bufferCoordinates =
  327.                 geometryLine.buffer(bufferOffset, QUADRANTSEGMENTS, BufferParameters.CAP_FLAT).getCoordinates();

  328.         // Z coordinates may be NaN at this point

  329.         // find the coordinate indices closest to the start point and end point,
  330.         // at a distance of approximately the offset
  331.         Coordinate sC0 = referenceCoordinates[0];
  332.         Coordinate sC1 = referenceCoordinates[1];
  333.         Coordinate eCm1 = referenceCoordinates[referenceCoordinates.length - 1];
  334.         Coordinate eCm2 = referenceCoordinates[referenceCoordinates.length - 2];

  335.         double expectedStartAngle = norm(angle(sC0, sC1) + Math.signum(offset) * Math.PI / 2.0);
  336.         double expectedEndAngle = norm(angle(eCm2, eCm1) + Math.signum(offset) * Math.PI / 2.0);
  337.         Coordinate sExpected = new Coordinate(sC0.x + bufferOffset * Math.cos(expectedStartAngle),
  338.                 sC0.y + bufferOffset * Math.sin(expectedStartAngle));
  339.         Coordinate eExpected = new Coordinate(eCm1.x + bufferOffset * Math.cos(expectedEndAngle),
  340.                 eCm1.y + bufferOffset * Math.sin(expectedEndAngle));

  341.         // which coordinates are closest to sExpected and eExpected?
  342.         double dS = Double.MAX_VALUE;
  343.         double dE = Double.MAX_VALUE;
  344.         int sIndex = -1;
  345.         int eIndex = -1;
  346.         for (int i = 0; i < bufferCoordinates.length; i++)
  347.         {
  348.             Coordinate c = bufferCoordinates[i];
  349.             double dsc = c.distance(sExpected);
  350.             double dec = c.distance(eExpected);
  351.             if (dsc < dS)
  352.             {
  353.                 dS = dsc;
  354.                 sIndex = i;
  355.             }
  356.             if (dec < dE)
  357.             {
  358.                 dE = dec;
  359.                 eIndex = i;
  360.             }
  361.         }

  362.         if (sIndex == -1)
  363.         {
  364.             throw new OTSGeometryException("offsetGeometry: startIndex not found for line " + referenceLine);
  365.         }
  366.         if (eIndex == -1)
  367.         {
  368.             throw new OTSGeometryException("offsetGeometry: endIndex not found for line " + referenceLine);
  369.         }
  370.         if (dS > 0.01)
  371.         {
  372.             System.err.println(referenceLine.toExcel() + "\n\n\n\n" + new OTSLine3D(bufferCoordinates).toExcel() + "\n\n\n\n"
  373.                     + sExpected + "\n" + eExpected);
  374.             throw new OTSGeometryException("offsetGeometry: startDistance too big (" + dS + ") for line " + referenceLine);
  375.         }
  376.         if (dE > 0.01)
  377.         {
  378.             throw new OTSGeometryException("offsetGeometry: endDistance too big (" + dE + ") for line " + referenceLine);
  379.         }

  380.         // try positive direction
  381.         boolean ok = true;
  382.         int i = sIndex;
  383.         Coordinate lastC = null;
  384.         List<OTSPoint3D> result = new ArrayList<>();
  385.         while (ok)
  386.         {
  387.             Coordinate c = bufferCoordinates[i];
  388.             if (lastC != null && close(c, lastC, sC0, eCm1))
  389.             {
  390.                 ok = false;
  391.                 break;
  392.             }
  393.             result.add(new OTSPoint3D(c));
  394.             if (i == eIndex)
  395.             {
  396.                 return OTSLine3D.createAndCleanOTSLine3D(result);
  397.             }
  398.             i = (i == bufferCoordinates.length - 1) ? 0 : i + 1;
  399.             lastC = c;
  400.         }

  401.         // try negative direction
  402.         ok = true;
  403.         i = sIndex;
  404.         lastC = null;
  405.         result = new ArrayList<>();
  406.         while (ok)
  407.         {
  408.             Coordinate c = bufferCoordinates[i];
  409.             if (lastC != null && close(c, lastC, sC0, eCm1))
  410.             {
  411.                 ok = false;
  412.                 break;
  413.             }
  414.             result.add(new OTSPoint3D(c));
  415.             if (i == eIndex)
  416.             {
  417.                 return OTSLine3D.createAndCleanOTSLine3D(result);
  418.             }
  419.             i = (i == 0) ? bufferCoordinates.length - 1 : i - 1;
  420.             lastC = c;
  421.         }

  422.         /*- System.err.println(referenceLine.toExcel() + "\n\n\n\n" + new OTSLine3D(bufferCoordinates).toExcel()
  423.             + "\n\n\n\n" + sExpected + "\n" + eExpected); */
  424.         throw new OTSGeometryException("offsetGeometry: could not find offset in either direction for line " + referenceLine);
  425.     }

  426.     /**
  427.      * Check if the points check[] are close to the line [lineC1..LineC2].
  428.      * @param lineC1 first point of the line
  429.      * @param lineC2 second point of the line
  430.      * @param check the coordinates to check
  431.      * @return whether one of the points to check is close to the line.
  432.      */
  433.     private static boolean close(final Coordinate lineC1, final Coordinate lineC2, final Coordinate... check)
  434.     {
  435.         Line2D.Double line = new Line2D.Double(lineC1.x, lineC1.y, lineC2.x, lineC2.y);
  436.         for (Coordinate c : check)
  437.         {
  438.             if (line.ptSegDist(c.x, c.y) < 0.01)
  439.             {
  440.                 return true;
  441.             }
  442.         }
  443.         return false;
  444.     }

  445.     /**
  446.      * Create a line at linearly varying offset from a reference line. The offset may change linearly from its initial value at
  447.      * the start of the reference line to its final offset value at the end of the reference line.
  448.      * @param referenceLine Geometry; the Geometry of the reference line
  449.      * @param offsetAtStart double; offset at the start of the reference line (positive value is Left, negative value is Right)
  450.      * @param offsetAtEnd double; offset at the end of the reference line (positive value is Left, negative value is Right)
  451.      * @return Geometry; the Geometry of the line at linearly changing offset of the reference line
  452.      * @throws OTSGeometryException when this method fails to create the offset line
  453.      */
  454.     public static OTSLine3D offsetLine(final OTSLine3D referenceLine, final double offsetAtStart, final double offsetAtEnd)
  455.             throws OTSGeometryException
  456.     {
  457.         // System.out.println(OTSGeometry.printCoordinates("#referenceLine: \nc1,0,0\n# offset at start is " + offsetAtStart
  458.         // + " at end is " + offsetAtEnd + "\n#", referenceLine, "\n "));

  459.         OTSLine3D offsetLineAtStart = offsetLine(referenceLine, offsetAtStart);
  460.         if (offsetAtStart == offsetAtEnd)
  461.         {
  462.             return offsetLineAtStart; // offset does not change
  463.         }
  464.         // System.out.println(OTSGeometry.printCoordinates("#offset line at start: \nc0,0,0\n#", offsetLineAtStart, "\n "));
  465.         OTSLine3D offsetLineAtEnd = offsetLine(referenceLine, offsetAtEnd);
  466.         // System.out.println(OTSGeometry.printCoordinates("#offset line at end: \nc0.7,0.7,0.7\n#", offsetLineAtEnd, "\n "));
  467.         Geometry startGeometry = offsetLineAtStart.getLineString();
  468.         Geometry endGeometry = offsetLineAtEnd.getLineString();
  469.         LengthIndexedLine first = new LengthIndexedLine(startGeometry);
  470.         double firstLength = startGeometry.getLength();
  471.         LengthIndexedLine second = new LengthIndexedLine(endGeometry);
  472.         double secondLength = endGeometry.getLength();
  473.         ArrayList<Coordinate> out = new ArrayList<Coordinate>();
  474.         Coordinate[] firstCoordinates = startGeometry.getCoordinates();
  475.         Coordinate[] secondCoordinates = endGeometry.getCoordinates();
  476.         int firstIndex = 0;
  477.         int secondIndex = 0;
  478.         Coordinate prevCoordinate = null;
  479.         final double tooClose = 0.05; // 5 cm
  480.         while (firstIndex < firstCoordinates.length && secondIndex < secondCoordinates.length)
  481.         {
  482.             double firstRatio = firstIndex < firstCoordinates.length ? first.indexOf(firstCoordinates[firstIndex]) / firstLength
  483.                     : Double.MAX_VALUE;
  484.             double secondRatio = secondIndex < secondCoordinates.length
  485.                     ? second.indexOf(secondCoordinates[secondIndex]) / secondLength : Double.MAX_VALUE;
  486.             double ratio;
  487.             if (firstRatio < secondRatio)
  488.             {
  489.                 ratio = firstRatio;
  490.                 firstIndex++;
  491.             }
  492.             else
  493.             {
  494.                 ratio = secondRatio;
  495.                 secondIndex++;
  496.             }
  497.             Coordinate firstCoordinate = first.extractPoint(ratio * firstLength);
  498.             Coordinate secondCoordinate = second.extractPoint(ratio * secondLength);
  499.             Coordinate resultCoordinate = new Coordinate((1 - ratio) * firstCoordinate.x + ratio * secondCoordinate.x,
  500.                     (1 - ratio) * firstCoordinate.y + ratio * secondCoordinate.y);
  501.             if (null == prevCoordinate || resultCoordinate.distance(prevCoordinate) > tooClose)
  502.             {
  503.                 out.add(resultCoordinate);
  504.                 prevCoordinate = resultCoordinate;
  505.             }
  506.         }
  507.         Coordinate[] resultCoordinates = new Coordinate[out.size()];
  508.         for (int index = 0; index < out.size(); index++)
  509.         {
  510.             resultCoordinates[index] = out.get(index);
  511.         }
  512.         return new OTSLine3D(resultCoordinates);
  513.     }

  514.     /**
  515.      * @param args args
  516.      * @throws NetworkException on error
  517.      * @throws OTSGeometryException on error
  518.      */
  519.     public static void main(final String[] args) throws NetworkException, OTSGeometryException
  520.     {
  521.         // OTSLine3D line =
  522.         // new OTSLine3D(new OTSPoint3D[]{new OTSPoint3D(-579.253, 60.157, 1.568),
  523.         // new OTSPoint3D(-579.253, 60.177, 1.568)});
  524.         // double offset = 4.83899987;
  525.         // System.out.println(OTSBufferingOLD.offsetGeometryOLD(line, offset));
  526.         OTSLine3D line = new OTSLine3D(new OTSPoint3D[] { new OTSPoint3D(-579.253, 60.157, 4.710),
  527.                 new OTSPoint3D(-579.253, 60.144, 4.712), new OTSPoint3D(-579.253, 60.144, 0.000),
  528.                 new OTSPoint3D(-579.251, 60.044, 0.000), new OTSPoint3D(-579.246, 59.944, 0.000),
  529.                 new OTSPoint3D(-579.236, 59.845, 0.000), new OTSPoint3D(-579.223, 59.746, 0.000),
  530.                 new OTSPoint3D(-579.206, 59.647, 0.000), new OTSPoint3D(-579.185, 59.549, 0.000),
  531.                 new OTSPoint3D(-579.161, 59.452, 0.000), new OTSPoint3D(-579.133, 59.356, 0.000),
  532.                 new OTSPoint3D(-579.101, 59.261, 0.000), new OTSPoint3D(-579.066, 59.168, 0.000),
  533.                 new OTSPoint3D(-579.028, 59.075, 0.000), new OTSPoint3D(-578.986, 58.985, 0.000),
  534.                 new OTSPoint3D(-578.940, 58.896, 0.000), new OTSPoint3D(-578.891, 58.809, 0.000),
  535.                 new OTSPoint3D(-578.839, 58.723, 0.000), new OTSPoint3D(-578.784, 58.640, 0.000),
  536.                 new OTSPoint3D(-578.725, 58.559, 0.000), new OTSPoint3D(-578.664, 58.480, 0.000),
  537.                 new OTSPoint3D(-578.599, 58.403, 0.000), new OTSPoint3D(-578.532, 58.329, 0.000),
  538.                 new OTSPoint3D(-578.462, 58.258, 0.000), new OTSPoint3D(-578.390, 58.189, 0.000),
  539.                 new OTSPoint3D(-578.314, 58.123, 0.000), new OTSPoint3D(-578.237, 58.060, 0.000),
  540.                 new OTSPoint3D(-578.157, 58.000, 0.000), new OTSPoint3D(-578.075, 57.943, 0.000),
  541.                 new OTSPoint3D(-577.990, 57.889, 0.000), new OTSPoint3D(-577.904, 57.839, 0.000),
  542.                 new OTSPoint3D(-577.816, 57.791, 0.000), new OTSPoint3D(-577.726, 57.747, 0.000),
  543.                 new OTSPoint3D(-577.635, 57.707, 0.000), new OTSPoint3D(-577.542, 57.670, 0.000),
  544.                 new OTSPoint3D(-577.448, 57.636, 0.000), new OTSPoint3D(-577.352, 57.606, 0.000),
  545.                 new OTSPoint3D(-577.256, 57.580, 0.000), new OTSPoint3D(-577.159, 57.557, 0.000),
  546.                 new OTSPoint3D(-577.060, 57.538, 0.000), new OTSPoint3D(-576.962, 57.523, 0.000),
  547.                 new OTSPoint3D(-576.862, 57.512, 0.000), new OTSPoint3D(-576.763, 57.504, 0.000),
  548.                 new OTSPoint3D(-576.663, 57.500, 0.000), new OTSPoint3D(-576.623, 57.500, 6.278),
  549.                 new OTSPoint3D(-576.610, 57.500, 6.280), new OTSPoint3D(-567.499, 57.473, 6.280) });
  550.         System.out.println(line.toExcel());
  551.         System.out.println(OTSBufferingJTS.offsetGeometryOLD(line, -1.831));
  552.     }
  553. }