OTSShape.java

  1. package org.opentrafficsim.core.geometry;

  2. import java.awt.geom.Line2D;
  3. import java.awt.geom.Path2D;
  4. import java.awt.geom.Point2D;
  5. import java.awt.geom.Rectangle2D;
  6. import java.util.ArrayList;
  7. import java.util.Arrays;
  8. import java.util.List;

  9. import org.locationtech.jts.geom.Coordinate;
  10. import org.locationtech.jts.geom.Geometry;
  11. import org.locationtech.jts.geom.LineString;

  12. /**
  13.  * <p>
  14.  * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  15.  * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  16.  * </p>
  17.  * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
  18.  * initial version Mar 31, 2016 <br>
  19.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  20.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  21.  */
  22. public class OTSShape extends OTSLine3D
  23. {
  24.     /** */
  25.     private static final long serialVersionUID = 20160331;

  26.     /** The underlying shape (only constructed if needed). */
  27.     private Path2D shape = null;

  28.     /**
  29.      * Construct a new OTSShape (closed shape).
  30.      * @param points OTSPoint3D...; the array of points to construct this OTSLine3D from.
  31.      * @throws OTSGeometryException when the provided points do not constitute a valid line (too few points or identical
  32.      *             adjacent points)
  33.      */
  34.     public OTSShape(final OTSPoint3D... points) throws OTSGeometryException
  35.     {
  36.         super(points);
  37.     }

  38.     /**
  39.      * Construct a new OTSShape (closed shape) from an array of Coordinate.
  40.      * @param coordinates Coordinate[]; the array of coordinates to construct this OTSLine3D from
  41.      * @throws OTSGeometryException when the provided points do not constitute a valid line (too few points or identical
  42.      *             adjacent points)
  43.      */
  44.     public OTSShape(final Coordinate[] coordinates) throws OTSGeometryException
  45.     {
  46.         super(coordinates);
  47.     }

  48.     /**
  49.      * Construct a new OTSShape (closed shape) from a LineString.
  50.      * @param lineString LineString; the lineString to construct this OTSLine3D from.
  51.      * @throws OTSGeometryException when the provided LineString does not constitute a valid line (too few points or identical
  52.      *             adjacent points)
  53.      */
  54.     public OTSShape(final LineString lineString) throws OTSGeometryException
  55.     {
  56.         super(lineString);
  57.     }

  58.     /**
  59.      * Construct a new OTSShape (closed shape) from a Geometry.
  60.      * @param geometry Geometry; the geometry to construct this OTSLine3D from
  61.      * @throws OTSGeometryException when the provided Geometry do not constitute a valid line (too few points or identical
  62.      *             adjacent points)
  63.      */
  64.     public OTSShape(final Geometry geometry) throws OTSGeometryException
  65.     {
  66.         super(geometry);
  67.     }

  68.     /**
  69.      * Construct a new OTSShape (closed shape) from a List&lt;OTSPoint3D&gt;.
  70.      * @param pointList List&lt;OTSPoint3D&gt;; the list of points to construct this OTSLine3D from.
  71.      * @throws OTSGeometryException when the provided points do not constitute a valid line (too few points or identical
  72.      *             adjacent points)
  73.      */
  74.     public OTSShape(final List<OTSPoint3D> pointList) throws OTSGeometryException
  75.     {
  76.         super(pointList);
  77.     }

  78.     /**
  79.      * Construct a new OTSShape (closed shape) from a Path2D.
  80.      * @param path Path2D; the Path2D to construct this OTSLine3D from.
  81.      * @throws OTSGeometryException when the provided points do not constitute a valid line (too few points or identical
  82.      *             adjacent points)
  83.      */
  84.     public OTSShape(final Path2D path) throws OTSGeometryException
  85.     {
  86.         super(path);
  87.     }

  88.     /**
  89.      * @return shape
  90.      */
  91.     public final Path2D getShape()
  92.     {
  93.         if (this.shape == null)
  94.         {
  95.             calculateShape();
  96.         }
  97.         return this.shape;
  98.     }

  99.     /**
  100.      * calculate the java.awt.Shape, and close it if needed.
  101.      */
  102.     private void calculateShape()
  103.     {
  104.         this.shape = new Path2D.Double();
  105.         this.shape.moveTo(getPoints()[0].x, getPoints()[0].y);
  106.         for (int i = 1; i < getPoints().length; i++)
  107.         {
  108.             this.shape.lineTo(getPoints()[i].x, getPoints()[i].y);
  109.         }
  110.         this.shape.closePath();
  111.     }

  112.     /**
  113.      * @param point OTSPoint3D; the point to check if it is inside the shape
  114.      * @return whether the point is inside the shape
  115.      */
  116.     public final boolean contains(final OTSPoint3D point)
  117.     {
  118.         return getShape().contains(point.x, point.y);
  119.     }

  120.     /**
  121.      * Check if this OTSShape completely covers a rectangular region.
  122.      * @param rectangle Rectangle2D; the rectangular region
  123.      * @return boolean; true if this OTSShape completely covers the region; false otherwise (or when the implementation of
  124.      *         java.awt.geom.Path2D.contains found it prohibitively expensive to decide. Let us hope that this cannot happen
  125.      *         with OTSShape objects. Peter has been unable to find out when this might happen.
  126.      */
  127.     public final boolean contains(final Rectangle2D rectangle)
  128.     {
  129.         return getShape().contains(rectangle);
  130.     }

  131.     /**
  132.      * @param otsShape OTSShape; the shape to test the intersection with
  133.      * @return whether the shapes intersect or whether one shape contains the other
  134.      */
  135.     public final boolean intersects(final OTSShape otsShape)
  136.     {
  137.         // step 1: quick check to see if the bounds intersect
  138.         if (!getEnvelope().intersects(otsShape.getEnvelope()))
  139.         {
  140.             return false;
  141.         }

  142.         // step 2: quick check to see if any of the points of shape 1 is in shape 2
  143.         for (OTSPoint3D p : getPoints())
  144.         {
  145.             if (otsShape.contains(p))
  146.             {
  147.                 return true;
  148.             }
  149.         }

  150.         // step 3: quick check to see if any of the points of shape 2 is in shape 1
  151.         for (OTSPoint3D p : otsShape.getPoints())
  152.         {
  153.             if (contains(p))
  154.             {
  155.                 return true;
  156.             }
  157.         }

  158.         // step 4: see if any of the lines of shape 1 and shape 2 intersect (expensive!)
  159.         Point2D prevPoint = getPoints()[this.size() - 1].getPoint2D();
  160.         // for (int i = 0; i < getPoints().length - 1; i++)
  161.         // {
  162.         for (OTSPoint3D point : this.getPoints())
  163.         {
  164.             Point2D nextPoint = point.getPoint2D();
  165.             Line2D.Double line1 = new Line2D.Double(prevPoint, nextPoint);
  166.             // Line2D.Double line1 = new Line2D.Double(this.getPoints()[i].getPoint2D(), this.getPoints()[i + 1].getPoint2D());
  167.             // for (int j = 0; j < otsShape.getPoints().length - 1; j++)
  168.             // {
  169.             Point2D otherPrevPoint = otsShape.getPoints()[otsShape.size() - 1].getPoint2D();
  170.             for (OTSPoint3D otherPoint : otsShape.getPoints())
  171.             {
  172.                 Point2D otherNextPoint = otherPoint.getPoint2D();
  173.                 // Line2D.Double line2 =
  174.                 // new Line2D.Double(otsShape.getPoints()[j].getPoint2D(), otsShape.getPoints()[j + 1].getPoint2D());
  175.                 Line2D.Double line2 = new Line2D.Double(otherPrevPoint, otherNextPoint);
  176.                 if (line1.intersectsLine(line2))
  177.                 {
  178.                     double p1x = line1.getX1(), p1y = line1.getY1(), d1x = line1.getX2() - p1x, d1y = line1.getY2() - p1y;
  179.                     double p2x = line2.getX1(), p2y = line2.getY1(), d2x = line2.getX2() - p2x, d2y = line2.getY2() - p2y;

  180.                     double det = d2x * d1y - d2y * d1x;
  181.                     if (det == 0)
  182.                     {
  183.                         /*- lines (partially) overlap, indicate 0, 1 or 2 (!) cross points
  184.                          situations:
  185.                          X============X        X============X        X============X        X=======X      X====X  
  186.                                 X---------X       X------X           X----X                X-------X           X----X
  187.                          a. 2 intersections    b. 2 intersections    c. 1 intersection     d. 0 inters.   e. 0 inters.
  188.                          */
  189.                         Point2D p1s = line1.getP1(), p1e = line1.getP2(), p2s = line2.getP1(), p2e = line2.getP2();
  190.                         if ((p1s.equals(p2s) && p1e.equals(p2e)) || (p1s.equals(p2e) && p1e.equals(p2s)))
  191.                         {
  192.                             return true; // situation d.
  193.                         }
  194.                         if (p1s.equals(p2s) && line1.ptLineDist(p2e) > 0 && line2.ptLineDist(p1e) > 0)
  195.                         {
  196.                             return true; // situation e.
  197.                         }
  198.                         if (p1e.equals(p2e) && line1.ptLineDist(p2s) > 0 && line2.ptLineDist(p1s) > 0)
  199.                         {
  200.                             return true; // situation e.
  201.                         }
  202.                         if (p1s.equals(p2e) && line1.ptLineDist(p2s) > 0 && line2.ptLineDist(p1e) > 0)
  203.                         {
  204.                             return true; // situation e.
  205.                         }
  206.                         if (p1e.equals(p2s) && line1.ptLineDist(p2e) > 0 && line2.ptLineDist(p1s) > 0)
  207.                         {
  208.                             return true; // situation e.
  209.                         }
  210.                     }
  211.                     else
  212.                     {
  213.                         double z = (d2x * (p2y - p1y) + d2y * (p1x - p2x)) / det;
  214.                         if (Math.abs(z) < 10.0 * Math.ulp(1.0) || Math.abs(z - 1.0) > 10.0 * Math.ulp(1.0))
  215.                         {
  216.                             return true; // intersection at end point
  217.                         }
  218.                     }

  219.                 }
  220.                 otherPrevPoint = otherNextPoint;
  221.             }
  222.             prevPoint = nextPoint;
  223.         }
  224.         return false;
  225.     }

  226.     /**
  227.      * Create an OTSLine3D, while cleaning repeating successive points.
  228.      * @param points OTSPoint3D[]; the coordinates of the line as OTSPoint3D
  229.      * @return the line
  230.      * @throws OTSGeometryException when number of points &lt; 2
  231.      */
  232.     public static OTSShape createAndCleanOTSShape(final OTSPoint3D[] points) throws OTSGeometryException
  233.     {
  234.         if (points.length < 2)
  235.         {
  236.             throw new OTSGeometryException(
  237.                     "Degenerate OTSLine3D; has " + points.length + " point" + (points.length != 1 ? "s" : ""));
  238.         }
  239.         return createAndCleanOTSShape(new ArrayList<>(Arrays.asList(points)));
  240.     }

  241.     /**
  242.      * Create an OTSLine3D, while cleaning repeating successive points.
  243.      * @param pointList List&lt;OTSPoint3D&gt;; list of the coordinates of the line as OTSPoint3D; any duplicate points in this
  244.      *            list are removed (this method may modify the provided list)
  245.      * @return OTSLine3D; the line
  246.      * @throws OTSGeometryException when number of non-equal points &lt; 2
  247.      */
  248.     public static OTSShape createAndCleanOTSShape(final List<OTSPoint3D> pointList) throws OTSGeometryException
  249.     {
  250.         // clean successive equal points
  251.         int i = 1;
  252.         while (i < pointList.size())
  253.         {
  254.             if (pointList.get(i - 1).equals(pointList.get(i)))
  255.             {
  256.                 pointList.remove(i);
  257.             }
  258.             else
  259.             {
  260.                 i++;
  261.             }
  262.         }
  263.         return new OTSShape(pointList);
  264.     }

  265.     /**
  266.      * Small test.
  267.      * @param args String[]; empty
  268.      * @throws OTSGeometryException when construction fails
  269.      */
  270.     public static void main(final String[] args) throws OTSGeometryException
  271.     {
  272.         OTSShape s1 = new OTSShape(new OTSPoint3D(0, 0), new OTSPoint3D(10, 0), new OTSPoint3D(10, 10), new OTSPoint3D(0, 10));
  273.         OTSShape s2 = new OTSShape(new OTSPoint3D(5, 5), new OTSPoint3D(15, 5), new OTSPoint3D(15, 15), new OTSPoint3D(5, 15));
  274.         System.out.println("s1.intersect(s2): " + s1.intersects(s2));
  275.         System.out.println("s1.intersect(s1): " + s1.intersects(s1));
  276.         OTSShape s3 =
  277.                 new OTSShape(new OTSPoint3D(25, 25), new OTSPoint3D(35, 25), new OTSPoint3D(35, 35), new OTSPoint3D(25, 35));
  278.         System.out.println("s1.intersect(s3): " + s1.intersects(s3));
  279.     }

  280.     /** {@inheritDoc} */
  281.     @Override
  282.     public final String toString()
  283.     {
  284.         return "OTSShape [shape=" + this.shape + "]";
  285.     }
  286. }