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