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-2023 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  15.  * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  16.  * </p>
  17.  * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  18.  * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  19.  */
  20. public class OtsShape extends OtsLine3d
  21. {
  22.     /** */
  23.     private static final long serialVersionUID = 20160331;

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

  26.     /**
  27.      * Construct a new OtsShape (closed shape).
  28.      * @param points OtsPoint3d...; the array of points to construct this OtsLine3d from.
  29.      * @throws OtsGeometryException when the provided points do not constitute a valid line (too few points or identical
  30.      *             adjacent points)
  31.      */
  32.     public OtsShape(final OtsPoint3d... points) throws OtsGeometryException
  33.     {
  34.         super(points);
  35.     }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  217.                 }
  218.                 otherPrevPoint = otherNextPoint;
  219.             }
  220.             prevPoint = nextPoint;
  221.         }
  222.         return false;
  223.     }

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

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

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

  278.     /** {@inheritDoc} */
  279.     @Override
  280.     public final String toString()
  281.     {
  282.         return "OtsShape [shape=" + super.toString() + "]";
  283.     }
  284. }