OTSOffsetLinePK.java

  1. package org.opentrafficsim.core.geometry;

  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.Locale;

  5. /**
  6.  * Peter Knoppers' attempt to implement offsetLine.
  7.  * <p>
  8.  * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  9.  * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
  10.  * <p>
  11.  * @version $Revision$, $LastChangedDate$, by $Author$, initial version Dec 1, 2015 <br>
  12.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  13.  */
  14. public final class OTSOffsetLinePK
  15. {
  16.     /** This class should never be instantiated. */
  17.     private OTSOffsetLinePK()
  18.     {
  19.         // Cannot be instantiated.
  20.     }
  21.    
  22.     /** Debugging flag. */
  23.     static boolean debugOffsetLine = false;

  24.     /** Precision of approximation of arcs in the offsetLine method. */
  25.     static double circlePrecision = 0.001;

  26.     /** Noise in the reference line less than this value is always filtered. */
  27.     static double offsetMinimumFilterValue = 0.001;

  28.     /** Noise in the reference line greater than this value is never filtered. */
  29.     static double offsetMaximumFilterValue = 0.1;

  30.     /**
  31.      * Noise in the reference line less than <cite>offset / offsetFilterRatio</cite> is filtered except when the resulting value
  32.      * exceeds <cite>offsetMaximumFilterValue</cite>.
  33.      */
  34.     static double offsetFilterRatio = 10;

  35.     /**
  36.      * Construct an offset line.
  37.      * @param referenceLine OTSLine3D; the reference line
  38.      * @param offset double; the offset; positive values indicate left of the reference line, negative values indicate right of
  39.      *            the reference line
  40.      * @return OTSLine3D; a line at the specified offset from the reference line
  41.      * @throws OTSGeometryException when this method runs into major trouble and cannot produce a decent result
  42.      */
  43.     public static OTSLine3D offsetLine(final OTSLine3D referenceLine, final double offset) throws OTSGeometryException
  44.     {
  45.         // if (referenceLine.size() > 1 && referenceLine.getFirst().horizontalDistanceSI(new OTSPoint3D(-200.376, -111.999)) <
  46.         // 0.1
  47.         // && referenceLine.get(1).horizontalDistanceSI(new OTSPoint3D(-204.098, -100.180)) < 0.1 && Math.abs(offset) > 1)

  48.         // if (referenceLine.size() > 1 && referenceLine.getFirst().horizontalDistanceSI(new OTSPoint3D(-177.580, -169.726)) <
  49.         // 0.1
  50.         // && referenceLine.get(1).horizontalDistanceSI(new OTSPoint3D(-179.028, -166.084)) < 0.1 && Math.abs(offset) > 1
  51.         // && referenceLine.size() == 0)
  52.         // {
  53.         // debugOffsetLine = true;
  54.         // for (int i = 0; i < referenceLine.size(); i++)
  55.         // {
  56.         // System.out.println(String.format(
  57.         // Locale.US,
  58.         // "point %2d: %20s,%20s%s",
  59.         // i,
  60.         // referenceLine.get(i).x,
  61.         // referenceLine.get(i).y,
  62.         // (i == 0 ? "" : " ("
  63.         // + Math.toDegrees(Math.atan2(referenceLine.get(i).y - referenceLine.get(i - 1).y,
  64.         // referenceLine.get(i).x - referenceLine.get(i - 1).x)) + ")")));
  65.         // }
  66.         // System.out.println("# offset is " + offset);
  67.         // System.out.println(OTSGeometry.printCoordinates("#reference:\nc0,0,0\n#", referenceLine, "\n    "));
  68.         // }
  69.         // else
  70.         // {
  71.         // debugOffsetLine = false;
  72.         // }
  73.         double bufferOffset = Math.abs(offset);
  74.         final double precision = 0.00001;
  75.         if (bufferOffset < precision)
  76.         {
  77.             // squash the Z-coordinate
  78.             List<OTSPoint3D> coordinates = new ArrayList<>(referenceLine.size());
  79.             for (int i = 0; i < referenceLine.size(); i++)
  80.             {
  81.                 OTSPoint3D coordinate = referenceLine.get(i);
  82.                 coordinates.add(new OTSPoint3D(coordinate.x, coordinate.y));
  83.             }
  84.             return OTSLine3D.createAndCleanOTSLine3D(coordinates);
  85.         }

  86.         OTSLine3D filteredReferenceLine =
  87.                 referenceLine.noiseFilteredLine(Math.max(offsetMinimumFilterValue,
  88.                         Math.min(bufferOffset / offsetFilterRatio, offsetMaximumFilterValue)));
  89.         List<OTSPoint3D> tempPoints = new ArrayList<>();
  90.         // Make good use of the fact that an OTSLine3D cannot have consecutive duplicate points and has > 1 points
  91.         OTSPoint3D prevPoint = filteredReferenceLine.get(0);
  92.         Double prevAngle = null;
  93.         for (int index = 0; index < filteredReferenceLine.size() - 1; index++)
  94.         {
  95.             OTSPoint3D nextPoint = filteredReferenceLine.get(index + 1);
  96.             double angle = Math.atan2(nextPoint.y - prevPoint.y, nextPoint.x - prevPoint.x);
  97.             if (debugOffsetLine)
  98.             {
  99.                 System.out.println("#reference segment " + prevPoint + " to " + nextPoint + " angle " + Math.toDegrees(angle));
  100.             }
  101.             OTSPoint3D segmentFrom =
  102.                     new OTSPoint3D(prevPoint.x - Math.sin(angle) * offset, prevPoint.y + Math.cos(angle) * offset);
  103.             OTSPoint3D segmentTo =
  104.                     new OTSPoint3D(nextPoint.x - Math.sin(angle) * offset, nextPoint.y + Math.cos(angle) * offset);
  105.             boolean addSegment = true;
  106.             if (index > 0)
  107.             {
  108.                 double deltaAngle = angle - prevAngle;
  109.                 if (Math.abs(deltaAngle) > Math.PI)
  110.                 {
  111.                     deltaAngle -= Math.signum(deltaAngle) * 2 * Math.PI;
  112.                 }
  113.                 if (deltaAngle * offset <= 0)
  114.                 {
  115.                     // Outside of curve of reference line
  116.                     // Approximate an arc using straight segments.
  117.                     // Determine how many segments are needed.
  118.                     int numSegments = 1;
  119.                     if (Math.abs(deltaAngle) > Math.PI / 2)
  120.                     {
  121.                         numSegments = 2;
  122.                     }
  123.                     while (true)
  124.                     {
  125.                         double maxError = bufferOffset * (1 - Math.abs(Math.cos(deltaAngle / numSegments / 2)));
  126.                         if (maxError < circlePrecision)
  127.                         {
  128.                             break; // required precision reached
  129.                         }
  130.                         numSegments *= 2;
  131.                     }
  132.                     OTSPoint3D prevArcPoint = tempPoints.get(tempPoints.size() - 1);
  133.                     // Generate the intermediate points
  134.                     for (int additionalPoint = 1; additionalPoint < numSegments; additionalPoint++)
  135.                     {
  136.                         double intermediateAngle =
  137.                                 (additionalPoint * angle + (numSegments - additionalPoint) * prevAngle) / numSegments;
  138.                         if (prevAngle * angle < 0 && Math.abs(prevAngle) > Math.PI / 2 && Math.abs(angle) > Math.PI / 2)
  139.                         {
  140.                             intermediateAngle += Math.PI;
  141.                         }
  142.                         OTSPoint3D intermediatePoint =
  143.                                 new OTSPoint3D(prevPoint.x - Math.sin(intermediateAngle) * offset, prevPoint.y
  144.                                         + Math.cos(intermediateAngle) * offset);
  145.                         // Find any intersection points of the new segment and all previous segments
  146.                         OTSPoint3D prevSegFrom = null;
  147.                         int stopAt = tempPoints.size();
  148.                         for (int i = 0; i < stopAt; i++)
  149.                         {
  150.                             OTSPoint3D prevSegTo = tempPoints.get(i);
  151.                             if (null != prevSegFrom)
  152.                             {
  153.                                 OTSPoint3D prevSegIntersection =
  154.                                         OTSPoint3D.intersectionOfLineSegments(prevArcPoint, intermediatePoint, prevSegFrom,
  155.                                                 prevSegTo);
  156.                                 if (null != prevSegIntersection
  157.                                         && prevSegIntersection.horizontalDistanceSI(prevArcPoint) > circlePrecision
  158.                                         && prevSegIntersection.horizontalDistanceSI(prevSegFrom) > circlePrecision
  159.                                         && prevSegIntersection.horizontalDistanceSI(prevSegTo) > circlePrecision)
  160.                                 {
  161.                                     if (debugOffsetLine)
  162.                                     {
  163.                                         System.out.println("#inserting intersection in arc segment " + prevSegIntersection);
  164.                                     }
  165.                                     tempPoints.add(prevSegIntersection);
  166.                                 }
  167.                             }
  168.                             prevSegFrom = prevSegTo;
  169.                         }
  170.                         OTSPoint3D nextSegmentIntersection =
  171.                                 OTSPoint3D.intersectionOfLineSegments(prevSegFrom, intermediatePoint, segmentFrom, segmentTo);
  172.                         if (null != nextSegmentIntersection)
  173.                         {
  174.                             if (debugOffsetLine)
  175.                             {
  176.                                 System.out.println("#inserting intersection of arc segment with next segment "
  177.                                         + nextSegmentIntersection);
  178.                             }
  179.                             tempPoints.add(nextSegmentIntersection);
  180.                         }
  181.                         if (debugOffsetLine)
  182.                         {
  183.                             System.out.println("#inserting arc point " + intermediatePoint + " for angle "
  184.                                     + Math.toDegrees(intermediateAngle));
  185.                         }
  186.                         tempPoints.add(intermediatePoint);
  187.                         prevArcPoint = intermediatePoint;
  188.                     }
  189.                 }
  190.                 // Inside of curve of reference line.
  191.                 // Add the intersection point of each previous segment and the next segment
  192.                 OTSPoint3D pPoint = null;
  193.                 for (int i = 0; i < tempPoints.size(); i++)
  194.                 {
  195.                     OTSPoint3D p = tempPoints.get(i);
  196.                     if (null != pPoint)
  197.                     {
  198.                         double pAngle = Math.atan2(p.y - pPoint.y, p.x - pPoint.x);
  199.                         double angleDifference = angle - pAngle;
  200.                         if (Math.abs(angleDifference) > Math.PI)
  201.                         {
  202.                             angleDifference += Math.signum(angleDifference) * 2 * Math.PI;
  203.                         }
  204.                         if (debugOffsetLine)
  205.                         {
  206.                             System.out.println("#preceding segment " + pPoint + " to " + p + ", next segment " + segmentFrom
  207.                                     + " to " + segmentTo + " angleDifference " + Math.toDegrees(angleDifference));
  208.                         }
  209.                         if (Math.abs(angleDifference) > 0)// 0.01)
  210.                         {
  211.                             OTSPoint3D intersection = OTSPoint3D.intersectionOfLineSegments(pPoint, p, segmentFrom, segmentTo);
  212.                             if (null != intersection)
  213.                             {
  214.                                 if (tempPoints.size() - 1 == i)
  215.                                 {
  216.                                     if (debugOffsetLine)
  217.                                     {
  218.                                         System.out.println("#Replacing last point of preceding segment and "
  219.                                                 + "first point of next segment by their intersection " + intersection);
  220.                                     }
  221.                                     tempPoints.remove(tempPoints.size() - 1);
  222.                                     segmentFrom = intersection;
  223.                                 }
  224.                                 else
  225.                                 {
  226.                                     if (debugOffsetLine)
  227.                                     {
  228.                                         System.out.println("#Adding intersection of preceding segment and " + "next segment "
  229.                                                 + intersection);
  230.                                     }
  231.                                     tempPoints.add(intersection);
  232.                                 }
  233.                                 // tempPoints.set(tempPoints.size() - 1, intermediatePoint);
  234.                             }
  235.                         }
  236.                         else
  237.                         {
  238.                             if (debugOffsetLine)
  239.                             {
  240.                                 System.out.println("#Not adding intersection of preceding segment and this segment "
  241.                                         + "(angle too small)");
  242.                             }
  243.                             if (i == tempPoints.size() - 1)
  244.                             {
  245.                                 if (debugOffsetLine)
  246.                                 {
  247.                                     System.out.println("#Not adding segment");
  248.                                 }
  249.                                 addSegment = false;
  250.                             }
  251.                         }
  252.                     }
  253.                     pPoint = p;
  254.                 }
  255.             }
  256.             if (addSegment)
  257.             {
  258.                 if (debugOffsetLine)
  259.                 {
  260.                     System.out.println("#Adding segmentFrom " + segmentFrom);
  261.                 }
  262.                 tempPoints.add(segmentFrom);
  263.                 if (debugOffsetLine)
  264.                 {
  265.                     System.out.println("#Adding segmentTo " + segmentTo);
  266.                 }
  267.                 tempPoints.add(segmentTo);
  268.                 prevPoint = nextPoint;
  269.                 prevAngle = angle;
  270.             }
  271.         }
  272.         if (debugOffsetLine)
  273.         {
  274.             System.out.println("# before cleanup: \nc1,0,0\n");
  275.             if (tempPoints.size() > 0)
  276.             {
  277.                 OTSPoint3D p = tempPoints.get(0);
  278.                 System.out.println(String.format(Locale.US, "M %.3f,%.3f", p.x, p.y));
  279.                 for (int i = 1; i < tempPoints.size(); i++)
  280.                 {
  281.                     p = tempPoints.get(i);
  282.                     System.out.println(String.format(Locale.US, "L %.3f,%.3f", p.x, p.y));
  283.                 }
  284.             }
  285.         }
  286.         // Remove points that are closer than the specified offset
  287.         for (int index = 1; index < tempPoints.size() - 1; index++)
  288.         {
  289.             OTSPoint3D checkPoint = tempPoints.get(index);
  290.             prevPoint = null;
  291.             boolean tooClose = false;
  292.             boolean somewhereAtCorrectDistance = false;
  293.             for (int i = 0; i < filteredReferenceLine.size(); i++)
  294.             {
  295.                 OTSPoint3D p = filteredReferenceLine.get(i);
  296.                 if (null != prevPoint)
  297.                 {
  298.                     OTSPoint3D closestPoint = checkPoint.closestPointOnSegment(prevPoint, p);
  299.                     double distance = closestPoint.horizontalDistanceSI(checkPoint);
  300.                     if (distance < bufferOffset - circlePrecision)
  301.                     {
  302.                         if (debugOffsetLine)
  303.                         {
  304.                             System.out.print("#point " + checkPoint + " inside buffer (distance is " + distance + ") ");
  305.                         }
  306.                         tooClose = true;
  307.                         break;
  308.                     }
  309.                     else if (distance < bufferOffset + precision)
  310.                     {
  311.                         somewhereAtCorrectDistance = true;
  312.                     }
  313.                 }
  314.                 prevPoint = p;
  315.             }
  316.             if (tooClose || !somewhereAtCorrectDistance)
  317.             {
  318.                 if (debugOffsetLine)
  319.                 {
  320.                     System.out.println("#Removing " + checkPoint);
  321.                 }
  322.                 tempPoints.remove(index);
  323.                 index--;
  324.             }
  325.         }
  326.         if (debugOffsetLine)
  327.         {
  328.             System.out.println("#after cleanup " + tempPoints.size() + " points left");
  329.         }
  330.         // Fix the z-coordinate of all points that were added as intersections of segments.
  331.         for (int index = 0; index < tempPoints.size(); index++)
  332.         {
  333.             OTSPoint3D p = tempPoints.get(index);
  334.             if (Double.isNaN(p.z))
  335.             {
  336.                 tempPoints.set(index, new OTSPoint3D(p.x, p.y, 0));
  337.             }
  338.         }
  339.         try
  340.         {
  341.             return OTSLine3D.createAndCleanOTSLine3D(tempPoints);
  342.         }
  343.         catch (OTSGeometryException exception)
  344.         {
  345.             exception.printStackTrace();
  346.         }
  347.         return null;
  348.     }
  349. }