View Javadoc
1   package org.opentrafficsim.core.geometry;
2   
3   import static org.junit.Assert.assertEquals;
4   import static org.junit.Assert.assertFalse;
5   import static org.junit.Assert.assertNotEquals;
6   import static org.junit.Assert.assertTrue;
7   import static org.junit.Assert.fail;
8   
9   import java.awt.geom.Path2D;
10  import java.awt.geom.PathIterator;
11  import java.awt.geom.Rectangle2D;
12  import java.util.ArrayList;
13  import java.util.List;
14  
15  import org.junit.Test;
16  import org.locationtech.jts.geom.Coordinate;
17  import org.locationtech.jts.geom.CoordinateSequence;
18  import org.locationtech.jts.geom.Geometry;
19  import org.locationtech.jts.geom.GeometryFactory;
20  import org.locationtech.jts.geom.LineString;
21  import org.locationtech.jts.geom.impl.CoordinateArraySequence;
22  
23  /**
24   * Test the OTSShape class.
25   * <p>
26   * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
27   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
28   * <p>
29   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jan 9, 2017 <br>
30   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
31   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
32   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
33   */
34  public class OTSShapeTest
35  {
36      /**
37       * Test the OTSShape class.
38       * @throws OTSGeometryException if this happens uncaught; this test has failed
39       */
40      @Test
41      public final void testOTSShape() throws OTSGeometryException
42      {
43          OTSPoint3D p1 = new OTSPoint3D(1, 2, 3);
44          OTSPoint3D p2 = new OTSPoint3D(10, 2, 3);
45          OTSPoint3D p3 = new OTSPoint3D(11, 22, 3);
46  
47          OTSShape s = new OTSShape(p1, p2, p3);
48          verifyShape(s, true, p1, p2, p3);
49          s = new OTSShape(new Coordinate[] {p1.getCoordinate(), p2.getCoordinate(), p3.getCoordinate()});
50          verifyShape(s, true, p1, p2, p3);
51          GeometryFactory factory = new GeometryFactory();
52          CoordinateSequence cs =
53                  new CoordinateArraySequence(new Coordinate[] {p1.getCoordinate(), p2.getCoordinate(), p3.getCoordinate()});
54          LineString ls = new LineString(cs, factory);
55          s = new OTSShape(ls);
56          verifyShape(s, true, p1, p2, p3);
57          Geometry g = ls;
58          s = new OTSShape(g);
59          verifyShape(s, true, p1, p2, p3);
60          List<OTSPoint3D> list = new ArrayList<>();
61          list.add(p1);
62          list.add(p2);
63          list.add(p3);
64          s = new OTSShape(list);
65          verifyShape(s, true, p1, p2, p3);
66          Path2D path = new Path2D.Double();
67          path.moveTo(p1.x, p1.y);
68          path.lineTo(p2.x, p2.y);
69          path.lineTo(p3.x, p3.y);
70          s = new OTSShape(path);
71          verifyShape(s, false, p1, p2, p3);
72          Path2D shape = s.getShape();
73          PathIterator pi = shape.getPathIterator(null);
74          assertFalse(pi.isDone());
75          double[] coords = new double[6];
76          assertEquals("must be SEG_MOVETO", PathIterator.SEG_MOVETO, pi.currentSegment(coords));
77          assertEquals("x", p1.x, coords[0], 0.00001);
78          assertEquals("y", p1.y, coords[1], 0.00001);
79          pi.next();
80          assertFalse(pi.isDone());
81          assertEquals("must be SEG_LINETO", PathIterator.SEG_LINETO, pi.currentSegment(coords));
82          assertEquals("x", p2.x, coords[0], 0.00001);
83          assertEquals("y", p2.y, coords[1], 0.00001);
84          pi.next();
85          assertFalse(pi.isDone());
86          assertEquals("must be SEG_LINETO", PathIterator.SEG_LINETO, pi.currentSegment(coords));
87          assertEquals("x", p3.x, coords[0], 0.00001);
88          assertEquals("y", p3.y, coords[1], 0.00001);
89          pi.next();
90          assertFalse(pi.isDone());
91          assertEquals("must be SEG_CLOSE", PathIterator.SEG_CLOSE, pi.currentSegment(coords));
92          pi.next();
93          assertTrue(pi.isDone());
94          Path2D shape2 = s.getShape();
95          assertEquals("repeated get returns same thing", shape, shape2);
96          OTSPoint3D insidePoint = new OTSPoint3D(5, 5, 5);
97          assertTrue("shape contains inSide point", s.contains(insidePoint));
98          OTSPoint3D outsidePoint = new OTSPoint3D(-1, -2, -3);
99          assertFalse("shape does not contains unrelated point", s.contains(outsidePoint));
100         outsidePoint = new OTSPoint3D(0, 5, 5);
101         assertFalse("shape does not contains unrelated point", s.contains(outsidePoint));
102         outsidePoint = new OTSPoint3D(12, 5, 5);
103         assertFalse("shape does not contains unrelated point", s.contains(outsidePoint));
104         outsidePoint = new OTSPoint3D(5, 30, 5);
105         assertFalse("shape does not contains unrelated point", s.contains(outsidePoint));
106         Rectangle2D inRect = new Rectangle2D.Double(5, 5, 1, 1);
107         assertTrue("rectangle is inside shape", s.contains(inRect));
108         Rectangle2D partlyOutRect = new Rectangle2D.Double(5, 5, 20, 1);
109         assertFalse("Rectangle is not fully inside shape", s.contains(partlyOutRect));
110         partlyOutRect = new Rectangle2D.Double(5, 5, 1, 30);
111         assertFalse("Rectangle is not fully inside shape", s.contains(partlyOutRect));
112         assertTrue("toString result contains class name", s.toString().contains("OTSShape"));
113         path.closePath();
114         OTSShape s2 = new OTSShape(path);
115         // System.out.println("s:  " + s);
116         // System.out.println("s2: " + s2);
117         assertNotEquals("shape from closed path is not equal to shape from unclosed path", s, s2);
118         assertEquals("Size of shape from closed path is one longer than size of shape from unclosed path", s.size() + 1,
119                 s2.size());
120         for (int index = 0; index < s.size(); index++)
121         {
122             assertEquals("point at index matches", s.getCoordinates()[index], s2.getCoordinates()[index]);
123         }
124         assertEquals("Last coordinate of closed shape equals first coordinate", s2.getCoordinates()[0],
125                 s2.getCoordinates()[s2.size() - 1]);
126     }
127 
128     /**
129      * Test the intersects OTSShape method.
130      * @throws OTSGeometryException if that happens this test has failed
131      */
132     @Test
133     public final void testIntersects() throws OTSGeometryException
134     {
135         double radius = 10;
136         double cx = 5;
137         double cy = -5;
138         OTSShape reference = new OTSShape(makePolygon(cx, cy, radius, 18));
139         for (int dx = -20; dx <= 20; dx++)
140         {
141             for (int dy = -20; dy <= 20; dy++)
142             {
143                 boolean hit = true;
144                 double distance = Math.sqrt(dx * dx + dy * dy);
145                 double radius2 = 2;
146                 if (distance > radius + radius2)
147                 {
148                     hit = false;
149                 }
150                 else if (distance > radius + radius2 - 0.1)
151                 {
152                     continue; // too close to be sure
153                 }
154                 OTSShape other = new OTSShape(makePolygon(cx + dx, cy + dy, radius2, 16));
155                 if (hit)
156                 {
157                     assertTrue("shapes hit", reference.intersects(other));
158                 }
159                 else
160                 {
161                     assertFalse("shapes do not hit", reference.intersects(other));
162                 }
163             }
164         }
165         reference =
166                 new OTSShape(new OTSPoint3D[] {new OTSPoint3D(0, 0, 0), new OTSPoint3D(10, 0, 0), new OTSPoint3D(10, 10, 0)});
167         // Make shapes that overlap along the X axis
168         for (int dx = -20; dx <= 20; dx++)
169         {
170             OTSShape other = new OTSShape(
171                     new OTSPoint3D[] {new OTSPoint3D(dx, 0, 0), new OTSPoint3D(dx + 5, 0, 0), new OTSPoint3D(dx, -20, 0)});
172             boolean hit = dx >= -5 && dx <= 10;
173             if (hit)
174             {
175                 assertTrue("shapes hit", reference.intersects(other));
176             }
177             else
178             {
179                 assertFalse("shapes do not hit", reference.intersects(other));
180             }
181         }
182         // Make shapes that overlap along the Y axis
183         for (int dy = -20; dy <= 20; dy++)
184         {
185             OTSShape other = new OTSShape(
186                     new OTSPoint3D[] {new OTSPoint3D(20, dy, 0), new OTSPoint3D(10, dy, 0), new OTSPoint3D(10, dy + 10, 0)});
187             boolean hit = dy >= -10 && dy <= 10;
188             if (hit)
189             {
190                 assertTrue("shapes hit", reference.intersects(other));
191             }
192             else
193             {
194                 assertFalse("shapes do not hit", reference.intersects(other));
195             }
196         }
197         // Make vertical and horizontal box
198         OTSShape vertical = new OTSShape(new OTSPoint3D[] {new OTSPoint3D(-1, -10, 0), new OTSPoint3D(1, -10, 0),
199                 new OTSPoint3D(1, 10, 0), new OTSPoint3D(-1, 10, 0), new OTSPoint3D(-1, -10, 0)});
200         OTSShape horizontal = new OTSShape(new OTSPoint3D[] {new OTSPoint3D(-10, -1, 0), new OTSPoint3D(10, -1, 0),
201                 new OTSPoint3D(10, 1, 0), new OTSPoint3D(-10, 1, 0), new OTSPoint3D(-10, -1, 0)});
202         assertTrue("shapes hit", vertical.intersects(horizontal));
203     }
204 
205     /**
206      * Test the create and clean constructors.
207      * @throws OTSGeometryException should not happen uncaught
208      */
209     @Test
210     public final void testCleanConstructors() throws OTSGeometryException
211     {
212         try
213         {
214             OTSShape.createAndCleanOTSShape(new OTSPoint3D[] {});
215             fail("empty array should have thrown an OTSGeometryException");
216         }
217         catch (OTSGeometryException oge)
218         {
219             // Ignore expected exception
220         }
221         try
222         {
223             OTSShape.createAndCleanOTSShape(new OTSPoint3D[] {new OTSPoint3D(1, 2, 3)});
224             fail("array of one point should have thrown an OTSGeometryException");
225         }
226         catch (OTSGeometryException oge)
227         {
228             // Ignore expected exception
229         }
230         try
231         {
232             OTSShape.createAndCleanOTSShape(new OTSPoint3D[] {new OTSPoint3D(1, 2, 3), new OTSPoint3D(1, 2, 3)});
233             fail("array of two identical points should have thrown an OTSGeometryException");
234         }
235         catch (OTSGeometryException oge)
236         {
237             // Ignore expected exception
238         }
239         try
240         {
241             OTSShape.createAndCleanOTSShape(
242                     new OTSPoint3D[] {new OTSPoint3D(1, 2, 3), new OTSPoint3D(1, 2, 3), new OTSPoint3D(1, 2, 3)});
243             fail("array of three identical points should have thrown an OTSGeometryException");
244         }
245         catch (OTSGeometryException oge)
246         {
247             // Ignore expected exception
248         }
249         // FIXME: No geometry exception if thrown for two points that only differ in Z
250         OTSShape.createAndCleanOTSShape(new OTSPoint3D[] {new OTSPoint3D(1, 2, 3), new OTSPoint3D(1, 2, 1)});
251         OTSShape.createAndCleanOTSShape(
252                 new OTSPoint3D[] {new OTSPoint3D(1, 2, 3), new OTSPoint3D(1, 3, 3), new OTSPoint3D(1, 2, 3)});
253         List<OTSPoint3D> points = new ArrayList<>();
254         points.add(new OTSPoint3D(1, 2, 3));
255         points.add(new OTSPoint3D(1, 2, 3));
256         points.add(new OTSPoint3D(1, 2, 3));
257         points.add(new OTSPoint3D(1, 2, 3));
258         try
259         {
260             OTSShape.createAndCleanOTSShape(points);
261             fail("list of four identical points should have thrown an OTSGeometryException");
262         }
263         catch (OTSGeometryException oge)
264         {
265             // Ignore expected exception
266         }
267         // FIXME: do we really want this behavior?
268         assertEquals("list now has only one point", 1, points.size());
269     }
270 
271     /**
272      * Construct a list of OTSPoin3D spread out regularly over a circle.
273      * @param centerX double; center X of the circle
274      * @param centerY double; center Y of the circle
275      * @param radius double; radius of the circle
276      * @param size int; number of points in the polygon
277      * @return List&lt;OTSPoin3D&gt;; the points that lie on a regular polygon
278      * @throws OTSGeometryException if the number of points is too small or the radius is 0
279      */
280     private List<OTSPoint3D> makePolygon(final double centerX, final double centerY, final double radius, final int size)
281             throws OTSGeometryException
282     {
283         List<OTSPoint3D> points = new ArrayList<>(size);
284         for (int i = 0; i < size; i++)
285         {
286             double angle = Math.PI * 2 * i / size;
287             points.add(new OTSPoint3D(centerX + radius * Math.cos(angle), centerY + radius * Math.sin(angle)));
288         }
289         return points;
290     }
291 
292     /**
293      * Verify that an OTSShape contains exactly the given points in the given order.
294      * @param shape OTSShape; the shape to test
295      * @param verifyZ boolean; if true; also check the Z coordinates; if false; check that all Z coordinates are 0
296      * @param points OTSPoint3D...; the points to expect
297      * @throws OTSGeometryException if that happens; this test has failed
298      */
299     private void verifyShape(final OTSShape shape, final boolean verifyZ, final OTSPoint3D... points)
300             throws OTSGeometryException
301     {
302         assertEquals("shape contains correct number of points", shape.size(), points.length);
303         for (int i = 0; i < points.length; i++)
304         {
305             assertEquals("point 1 matches x", points[i].x, shape.get(i).x, 0.0001);
306             assertEquals("point 1 matches y", points[i].y, shape.get(i).y, 0.0001);
307             if (verifyZ)
308             {
309                 assertEquals("point 1 matches z", points[i].z, shape.get(i).z, 0.0001);
310             }
311             else
312             {
313                 assertEquals("point 1 z is 0", 0, shape.get(i).z, 0.0000001);
314             }
315         }
316     }
317 
318 }