1 
2 /*
3  * Copyright (c) 2016 Vivid Solutions.
4  *
5  * All rights reserved. This program and the accompanying materials
6  * are made available under the terms of the Eclipse Public License 2.0
7  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
8  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
9  * and the Eclipse Distribution License is available at
10  *
11  * http://www.eclipse.org/org/documents/edl-v10.php.
12  */
13 package org.locationtech.jts.geom;
14 
15 import org.locationtech.jts.geom.impl.CoordinateArraySequenceFactory;
16 import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory;
17 import org.locationtech.jts.io.WKTReader;
18 
19 import junit.framework.TestCase;
20 import junit.textui.TestRunner;
21 
22 import java.util.Random;
23 
24 
25 /**
26  * @version 1.7
27  */
28 public class CoordinateSequencesTest extends TestCase {
29 
30   private PrecisionModel precisionModel = new PrecisionModel();
31   private GeometryFactory geometryFactory = new GeometryFactory(precisionModel, 0);
32   WKTReader reader = new WKTReader(geometryFactory);
33 
34   private static final double[][] ordinateValues = {
35           {75.76,77.43},{41.35,90.75},{73.74,41.67},{20.87,86.49},{17.49,93.59},{67.75,80.63},
36           {63.01,52.57},{32.9,44.44},{79.36,29.8},{38.17,88.0},{19.31,49.71},{57.03,19.28},
37           {63.76,77.35},{45.26,85.15},{51.71,50.38},{92.16,19.85},{64.18,27.7},{64.74,65.1},
38           {80.07,13.55},{55.54,94.07}};
39 
main(String args[])40   public static void main(String args[]) {
41     TestRunner.run(CoordinateSequencesTest.class);
42   }
43 
CoordinateSequencesTest(String name)44   public CoordinateSequencesTest(String name) { super(name); }
45 
testCopyToLargerDim()46   public void testCopyToLargerDim()
47   {
48     PackedCoordinateSequenceFactory csFactory = new PackedCoordinateSequenceFactory();
49     CoordinateSequence cs2D = createTestSequence(csFactory, 10,  2);
50     CoordinateSequence cs3D = csFactory.create(10,  3);
51     CoordinateSequences.copy(cs2D,  0, cs3D, 0, cs3D.size());
52     assertTrue(CoordinateSequences.isEqual(cs2D, cs3D));
53   }
54 
testCopyToSmallerDim()55   public void testCopyToSmallerDim()
56   {
57     PackedCoordinateSequenceFactory csFactory = new PackedCoordinateSequenceFactory();
58     CoordinateSequence cs3D = createTestSequence(csFactory, 10,  3);
59     CoordinateSequence cs2D = csFactory.create(10,  2);
60     CoordinateSequences.copy(cs3D,  0, cs2D, 0, cs2D.size());
61     assertTrue(CoordinateSequences.isEqual(cs2D, cs3D));
62  }
63 
64 
testScrollRing()65   public void testScrollRing() {
66     System.out.println("Testing scrolling of closed ring");
67     doTestScrollRing(CoordinateArraySequenceFactory.instance(), 2);
68     doTestScrollRing(CoordinateArraySequenceFactory.instance(), 3);
69     doTestScrollRing(PackedCoordinateSequenceFactory.DOUBLE_FACTORY, 2);
70     doTestScrollRing(PackedCoordinateSequenceFactory.DOUBLE_FACTORY, 4);
71     doTestScrollRing(PackedCoordinateSequenceFactory.FLOAT_FACTORY, 2);
72     doTestScrollRing(PackedCoordinateSequenceFactory.FLOAT_FACTORY, 4);
73   }
74 
testScroll()75   public void testScroll() {
76     System.out.println("Testing scrolling of circular string");
77     doTestScroll(CoordinateArraySequenceFactory.instance(), 2);
78     doTestScroll(CoordinateArraySequenceFactory.instance(), 3);
79     doTestScroll(PackedCoordinateSequenceFactory.DOUBLE_FACTORY, 2);
80     doTestScroll(PackedCoordinateSequenceFactory.DOUBLE_FACTORY, 4);
81     doTestScroll(PackedCoordinateSequenceFactory.FLOAT_FACTORY, 2);
82     doTestScroll(PackedCoordinateSequenceFactory.FLOAT_FACTORY, 4);
83   }
84 
testIndexOf()85   public void testIndexOf() {
86     System.out.println("Testing indexOf");
87     doTestIndexOf(CoordinateArraySequenceFactory.instance(), 2);
88     doTestIndexOf(PackedCoordinateSequenceFactory.DOUBLE_FACTORY, 5);
89     doTestIndexOf(PackedCoordinateSequenceFactory.FLOAT_FACTORY, 7);
90   }
91 
testMinCoordinateIndex()92   public void testMinCoordinateIndex() {
93     System.out.println("Testing minCoordinateIndex");
94     doTestMinCoordinateIndex(CoordinateArraySequenceFactory.instance(), 2);
95     doTestMinCoordinateIndex(PackedCoordinateSequenceFactory.DOUBLE_FACTORY, 5);
96     doTestMinCoordinateIndex(PackedCoordinateSequenceFactory.FLOAT_FACTORY, 7);
97   }
98 
testIsRing()99   public void testIsRing() {
100     System.out.println("Testing isRing");
101     doTestIsRing(CoordinateArraySequenceFactory.instance(), 2);
102     doTestIsRing(PackedCoordinateSequenceFactory.DOUBLE_FACTORY, 5);
103     doTestIsRing(PackedCoordinateSequenceFactory.FLOAT_FACTORY, 7);
104   }
105 
testCopy()106   public void testCopy() {
107     System.out.println("Testing copy");
108     doTestCopy(CoordinateArraySequenceFactory.instance(), 2);
109     doTestCopy(PackedCoordinateSequenceFactory.DOUBLE_FACTORY, 5);
110     doTestCopy(PackedCoordinateSequenceFactory.FLOAT_FACTORY, 7);
111   }
112 
testReverse()113   public void testReverse() {
114     System.out.println("Testing reverse");
115     doTestReverse(CoordinateArraySequenceFactory.instance(), 2);
116     doTestReverse(PackedCoordinateSequenceFactory.DOUBLE_FACTORY, 5);
117     doTestReverse(PackedCoordinateSequenceFactory.FLOAT_FACTORY, 7);
118   }
119 
120   /**
121    * Method used to create a {@link this.ordinateValues}.
122    * Usage: remove first 't' and run as unit test.
123    * Note: When parameters are changed, some unit tests may need to be
124    * changed, too. <p>
125    * This is especially true for the (@link testMinCoordinateIndex) test,
126    * which assumes that the coordinates in the sequence are all within an
127    * envelope of [Env(10, 100, 10, 100)].
128    * </p>.
129    *
130    * @deprecated only use to update {@link this.ordinateValues}
131    */
ttestCreateRandomOrdinates()132   public void ttestCreateRandomOrdinates() {
133     CoordinateSequence sequence = createRandomTestSequence(CoordinateArraySequenceFactory.instance(), 20,
134             2, new Random(7),
135             new Envelope(10, 100, 10, 100), new PrecisionModel(100));
136     StringBuilder ordinates;
137     ordinates = new StringBuilder("\tprivate static final double[][] ordinateValues = {");
138     for (int i = 0; i < sequence.size(); i++) {
139       if (i%6 == 0) ordinates.append("\n\t\t");
140       ordinates.append('{');
141       ordinates.append(sequence.getOrdinate(i, 0));
142       ordinates.append(',');
143       ordinates.append(sequence.getOrdinate(i, 1));
144       if (i < sequence.size()-1) ordinates.append("},"); else ordinates.append('}');
145     }
146     ordinates.append("};");
147 
148     System.out.println(ordinates.toString());
149     assertTrue(true);
150   }
151 
createSequenceFromOrdinates(CoordinateSequenceFactory csFactory, int dim)152   private static CoordinateSequence createSequenceFromOrdinates(CoordinateSequenceFactory csFactory, int dim) {
153     CoordinateSequence sequence = csFactory.create(ordinateValues.length, dim);
154     for (int i = 0; i < ordinateValues.length; i++) {
155       sequence.setOrdinate(i, 0, ordinateValues[i][0]);
156       sequence.setOrdinate(i, 1, ordinateValues[i][1]);
157     }
158     return fillNonPlanarDimensions(sequence);
159   }
160 
createTestSequence(CoordinateSequenceFactory csFactory, int size, int dim)161   private static CoordinateSequence createTestSequence(CoordinateSequenceFactory csFactory, int size, int dim)
162   {
163     CoordinateSequence cs = csFactory.create(size,  dim);
164     // initialize with a data signature where coords look like [1, 10, 100, ...]
165     for (int i = 0; i < size; i++) {
166       for (int d = 0; d < dim; d++) {
167         cs.setOrdinate(i, d, i * Math.pow(10, d));
168       }
169     }
170     return cs;
171   }
172 
173   /**
174    * @deprecated only use to update in conjunction with {@link this.ttestCreateRandomOrdinates}
175    */
createRandomTestSequence(CoordinateSequenceFactory csFactory, int size, int dim, Random rnd, Envelope range, PrecisionModel pm)176   private static CoordinateSequence createRandomTestSequence(CoordinateSequenceFactory csFactory, int size, int dim,
177                                                    Random rnd, Envelope range, PrecisionModel pm)
178   {
179     CoordinateSequence cs = csFactory.create(size,  dim);
180     for (int i = 0; i < size; i++) {
181         cs.setOrdinate(i, 0, pm.makePrecise(range.getWidth() * rnd.nextDouble() + range.getMinX()));
182         cs.setOrdinate(i, 1, pm.makePrecise(range.getHeight() * rnd.nextDouble() + range.getMinY()));
183     }
184 
185     return fillNonPlanarDimensions(cs);
186   }
187 
doTestReverse(CoordinateSequenceFactory factory, int dimension)188   private static void doTestReverse(CoordinateSequenceFactory factory, int dimension) {
189 
190     // arrange
191     CoordinateSequence sequence = createSequenceFromOrdinates(factory, dimension);
192     CoordinateSequence reversed = sequence.copy();
193 
194     // act
195     CoordinateSequences.reverse(reversed);
196 
197     // assert
198     for (int i = 0; i < sequence.size(); i++)
199       checkCoordinateAt(sequence, i, reversed, sequence.size() - i - 1, dimension);
200   }
201 
doTestCopy(CoordinateSequenceFactory factory, int dimension)202   private static void doTestCopy(CoordinateSequenceFactory factory, int dimension) {
203 
204     // arrange
205     CoordinateSequence sequence = createSequenceFromOrdinates(factory, dimension);
206     if (sequence.size() <= 7) {
207       System.out.println("sequence has a size of " + sequence.size() + ". Execution of this test needs a sequence "+
208               "with more than 6 coordinates.");
209       return;
210     }
211 
212     CoordinateSequence fullCopy = factory.create(sequence.size(), dimension);
213     CoordinateSequence partialCopy = factory.create(sequence.size() - 5, dimension);
214 
215     // act
216     CoordinateSequences.copy(sequence, 0, fullCopy, 0, sequence.size());
217     CoordinateSequences.copy(sequence, 2, partialCopy, 0, partialCopy.size());
218 
219     // assert
220     for (int i = 0; i < fullCopy.size(); i++)
221       checkCoordinateAt(sequence, i, fullCopy, i, dimension);
222     for (int i = 0; i < partialCopy.size(); i++)
223       checkCoordinateAt(sequence, 2 + i, partialCopy, i, dimension);
224 
225     // ToDo test if dimensions don't match
226   }
227 
doTestIsRing(CoordinateSequenceFactory factory, int dimension)228   private static void doTestIsRing(CoordinateSequenceFactory factory, int dimension) {
229 
230     // arrange
231     CoordinateSequence ring = createCircle(factory, dimension, new Coordinate(), 5);
232     CoordinateSequence noRing = createCircularString(factory, dimension, new Coordinate(), 5,
233             0.1, 22);
234     CoordinateSequence empty = createAlmostRing(factory, dimension, 0);
235     CoordinateSequence incomplete1 = createAlmostRing(factory, dimension, 1);
236     CoordinateSequence incomplete2 = createAlmostRing(factory, dimension, 2);
237     CoordinateSequence incomplete3 = createAlmostRing(factory, dimension, 3);
238     CoordinateSequence incomplete4a = createAlmostRing(factory, dimension, 4);
239     CoordinateSequence incomplete4b = CoordinateSequences.ensureValidRing(factory, incomplete4a);
240 
241     // act
242     boolean isRingRing = CoordinateSequences.isRing(ring);
243     boolean isRingNoRing = CoordinateSequences.isRing(noRing);
244     boolean isRingEmpty = CoordinateSequences.isRing(empty);
245     boolean isRingIncomplete1 = CoordinateSequences.isRing(incomplete1);
246     boolean isRingIncomplete2 = CoordinateSequences.isRing(incomplete2);
247     boolean isRingIncomplete3 = CoordinateSequences.isRing(incomplete3);
248     boolean isRingIncomplete4a = CoordinateSequences.isRing(incomplete4a);
249     boolean isRingIncomplete4b = CoordinateSequences.isRing(incomplete4b);
250 
251     // assert
252     assertTrue(isRingRing);
253     assertTrue(!isRingNoRing);
254     assertTrue(isRingEmpty);
255     assertTrue(!isRingIncomplete1);
256     assertTrue(!isRingIncomplete2);
257     assertTrue(!isRingIncomplete3);
258     assertTrue(!isRingIncomplete4a);
259     assertTrue(isRingIncomplete4b);
260   }
261 
doTestIndexOf(CoordinateSequenceFactory factory, int dimension)262   private static void doTestIndexOf(CoordinateSequenceFactory factory, int dimension) {
263 
264     // arrange
265     CoordinateSequence sequence = createSequenceFromOrdinates(factory, dimension);
266 
267     // act & assert
268     Coordinate[] coordinates = sequence.toCoordinateArray();
269     for (int i = 0; i < sequence.size(); i++)
270       assertEquals(i, CoordinateSequences.indexOf(coordinates[i], sequence));
271 
272   }
273 
doTestMinCoordinateIndex(CoordinateSequenceFactory factory, int dimension)274   private static void doTestMinCoordinateIndex(CoordinateSequenceFactory factory, int dimension) {
275 
276     CoordinateSequence sequence = createSequenceFromOrdinates(factory, dimension);
277     if (sequence.size() <= 6) {
278       System.out.println("sequence has a size of " + sequence.size() + ". Execution of this test needs a sequence "+
279               "with more than 5 coordinates.");
280       return;
281     }
282 
283     int minIndex = sequence.size() / 2;
284     sequence.setOrdinate(minIndex, 0, 5);
285     sequence.setOrdinate(minIndex, 1, 5);
286 
287     assertEquals(minIndex, CoordinateSequences.minCoordinateIndex(sequence));
288     assertEquals(minIndex, CoordinateSequences.minCoordinateIndex(sequence, 2, sequence.size()-2));
289 
290   }
291 
doTestScroll(CoordinateSequenceFactory factory, int dimension)292   private static void doTestScroll(CoordinateSequenceFactory factory, int dimension) {
293 
294     // arrange
295     CoordinateSequence sequence = createCircularString(factory, dimension, new Coordinate(20, 20), 7d,
296             0.1, 22);
297     CoordinateSequence scrolled = sequence.copy();
298 
299     // act
300     CoordinateSequences.scroll(scrolled, 12);
301 
302     // assert
303     int io = 12;
304     for (int is = 0; is < scrolled.size() - 1; is++) {
305       checkCoordinateAt(sequence, io, scrolled, is, dimension);
306       io++;
307       io%=scrolled.size();
308     }
309   }
310 
doTestScrollRing(CoordinateSequenceFactory factory, int dimension)311   private static void doTestScrollRing(CoordinateSequenceFactory factory, int dimension) {
312 
313     // arrange
314     //System.out.println("Testing '" + factory.getClass().getSimpleName() + "' with dim=" +dimension );
315     CoordinateSequence sequence = createCircle(factory, dimension, new Coordinate(10, 10), 9d);
316     CoordinateSequence scrolled = sequence.copy();
317 
318     // act
319     CoordinateSequences.scroll(scrolled, 12);
320 
321     // assert
322     int io = 12;
323     for (int is = 0; is < scrolled.size() - 1; is++) {
324       checkCoordinateAt(sequence, io, scrolled, is, dimension);
325       io++;
326       io%=scrolled.size()-1;
327     }
328     checkCoordinateAt(scrolled, 0, scrolled, scrolled.size()-1, dimension);
329   }
330 
checkCoordinateAt(CoordinateSequence seq1, int pos1, CoordinateSequence seq2, int pos2, int dim)331   private static void checkCoordinateAt(CoordinateSequence seq1, int pos1,
332                                         CoordinateSequence seq2, int pos2, int dim) {
333     assertEquals("unexpected x-ordinate at pos " + pos2,
334             seq1.getOrdinate(pos1, 0), seq2.getOrdinate(pos2, 0));
335     assertEquals("unexpected y-ordinate at pos " + pos2,
336             seq1.getOrdinate(pos1, 1), seq2.getOrdinate(pos2, 1));
337 
338     // check additional ordinates
339     for (int j = 2; j < dim; j++) {
340       assertEquals("unexpected "+ j + "-ordinate at pos " + pos2,
341               seq1.getOrdinate(pos1, j), seq2.getOrdinate(pos2, j));
342     }
343   }
344 
createAlmostRing(CoordinateSequenceFactory factory, int dimension, int num)345   private static CoordinateSequence createAlmostRing(CoordinateSequenceFactory factory, int dimension, int num) {
346 
347     if (num > 4) num = 4;
348 
349     CoordinateSequence sequence = factory.create(num, dimension);
350     if (num == 0) return fillNonPlanarDimensions(sequence);
351 
352     sequence.setOrdinate(0, 0, 10);
353     sequence.setOrdinate(0, 0, 10);
354     if (num == 1) return fillNonPlanarDimensions(sequence);
355 
356     sequence.setOrdinate(0, 0, 20);
357     sequence.setOrdinate(0, 0, 10);
358     if (num == 2) return fillNonPlanarDimensions(sequence);
359 
360     sequence.setOrdinate(0, 0, 20);
361     sequence.setOrdinate(0, 0, 20);
362     if (num == 3) return fillNonPlanarDimensions(sequence);
363 
364     sequence.setOrdinate(0, 0, 10.0000000000001);
365     sequence.setOrdinate(0, 0,  9.9999999999999);
366     return fillNonPlanarDimensions(sequence);
367 
368   }
369 
fillNonPlanarDimensions(CoordinateSequence seq)370   private static CoordinateSequence fillNonPlanarDimensions(CoordinateSequence seq) {
371 
372     if (seq.getDimension() < 3)
373       return seq;
374 
375     for (int i = 0; i < seq.size(); i++)
376       for (int j = 2; j < seq.getDimension(); j++)
377         seq.setOrdinate(i, j, i* Math.pow(10, j-1));
378 
379     return seq;
380   }
381 
createCircle(CoordinateSequenceFactory factory, int dimension, Coordinate center, double radius)382   private static CoordinateSequence createCircle(CoordinateSequenceFactory factory, int dimension,
383                                                  Coordinate center, double radius) {
384     // Get a complete circular string
385     CoordinateSequence res = createCircularString(factory, dimension, center, radius, 0d,49);
386 
387     // ensure it is closed
388     for (int i = 0; i < dimension; i++)
389       res.setOrdinate(48, i, res.getOrdinate(0, i));
390 
391     return res;
392   }
createCircularString(CoordinateSequenceFactory factory, int dimension, Coordinate center, double radius, double startAngle, int numPoints)393   private static CoordinateSequence createCircularString(CoordinateSequenceFactory factory, int dimension,
394                                                          Coordinate center, double radius, double startAngle,
395                                                          int numPoints) {
396     final int numSegmentsCircle = 48;
397     final double angleCircle = 2 * Math.PI;
398     final double angleStep = angleCircle / numSegmentsCircle;
399 
400     CoordinateSequence sequence = factory.create(numPoints, dimension);
401     PrecisionModel pm = new PrecisionModel(100);
402     double angle = startAngle;
403     for (int i = 0; i < numPoints; i++)
404     {
405       double dx = Math.cos(angle) * radius;
406       sequence.setOrdinate(i, 0, pm.makePrecise(center.x +dx));
407       double dy = Math.sin(angle) * radius;
408       sequence.setOrdinate(i, 1, pm.makePrecise(center.y +dy));
409 
410       // set other ordinate values to predictable values
411       for (int j = 2; j < dimension; j++ )
412         sequence.setOrdinate(i, j, Math.pow(10, j-1)*i);
413 
414       angle += angleStep;
415       angle %= angleCircle;
416     }
417 
418     return sequence;
419   }
420 }
421