1
+ /* *********************************************************************
2
+ *
3
+ * GEOS - Geometry Engine Open Source
4
+ * http://geos.osgeo.org
5
+ *
6
+ * Copyright (C) 2025 Martin Davis
7
+ *
8
+ * This is free software; you can redistribute and/or modify it under
9
+ * the terms of the GNU Lesser General Public Licence as published
10
+ * by the Free Software Foundation.
11
+ * See the COPYING file for more information.
12
+ *
13
+ **********************************************************************/
14
+
15
+ /* --------------------------------------------------------------
16
+ Stress test for the Orientation Index implementation.
17
+
18
+ Usage: stress_orientation [ -v ] [ -d ]
19
+ -d - run diagonal line segment tests
20
+ -v - displays the input and results from each test
21
+
22
+ A robust orientation index implementation should be internally consistent
23
+ - i.e. it should produce the same result for the 3 possible
24
+ permutations of the input coordinates which have the same orientation:
25
+
26
+ p0-p1 / p2 p1-p2 / p0 p2-p0 / p1
27
+
28
+ Also, the reverse orientations should themselves be consistent,
29
+ and be opposite in sign to the forward orientation.
30
+
31
+ The robust implementation uses DoubleDouble arithmetic and a filter to improve computation time.
32
+ It is compared to the simple Floating-Point orientation computation, which is not robust.
33
+
34
+ Two kinds of test generators are provided:
35
+ - random line segments with midpoints
36
+ - points at increasing decimal intervals along a diagonal line segment LINESTRING(2 0, 0 2)
37
+ --------------------------------------------------------------*/
38
+
39
+ #include < geos/algorithm/Orientation.h>
40
+ #include < geos/geom/Coordinate.h>
41
+ #include < geos/geom/LineSegment.h>
42
+ #include < geos/io/WKTWriter.h>
43
+
44
+ #include < iomanip>
45
+
46
+ using namespace geos ::algorithm;
47
+ using namespace geos ::geom;
48
+ using namespace geos ::io;
49
+
50
+ int
51
+ orientationIndexFP (const Coordinate& p1, const Coordinate& p2,const Coordinate& q){
52
+ double dx1 = p2.x -p1.x ;
53
+ double dy1 = p2.y -p1.y ;
54
+ double dx2 = q.x -p2.x ;
55
+ double dy2 = q.y -p2.y ;
56
+ double det = dx1 * dy2 - dx2 * dy1;
57
+ if (det > 0.0 ) return 1 ;
58
+ if (det < 0.0 ) return -1 ;
59
+ return 0 ;
60
+ }
61
+
62
+ bool isVerbose = false ;
63
+ bool isDiagonalTest = false ;
64
+
65
+ std::size_t count;
66
+ std::size_t failDD;
67
+ std::size_t failFP;
68
+
69
+ void parseFlag (char * arg) {
70
+ char flag = arg[1 ];
71
+ switch (flag) {
72
+ case ' v' :
73
+ isVerbose = true ; break ;
74
+ case ' d' :
75
+ isDiagonalTest = true ; break ;
76
+ }
77
+ }
78
+
79
+ void parseArgs (int argc, char ** argv) {
80
+ if (argc <= 1 ) return ;
81
+ int i = 1 ;
82
+ // -- parse flags
83
+ while (i < argc && argv[i][0 ] == ' -' && isalpha (argv[i][1 ])) {
84
+ parseFlag (argv[i]);
85
+ i++;
86
+ }
87
+ }
88
+
89
+ char orientSym (int orientationIndex) {
90
+ if (orientationIndex < 0 ) return ' -' ;
91
+ if (orientationIndex > 0 ) return ' +' ;
92
+ return ' 0' ;
93
+ }
94
+
95
+ bool isConsistent (std::string tag, Coordinate p0, Coordinate p1, Coordinate p2,
96
+ std::function<int (Coordinate p0, Coordinate p1, Coordinate p2)> orientFunc )
97
+ {
98
+ int orient0 = orientFunc (p0, p1, p2);
99
+ int orient1 = orientFunc (p1, p2, p0);
100
+ int orient2 = orientFunc (p2, p0, p1);
101
+ bool isConsistentForward = orient0 == orient1 && orient0 == orient2;
102
+
103
+ int orientRev0 = orientFunc (p1, p0, p2);
104
+ int orientRev1 = orientFunc (p0, p2, p1);
105
+ int orientRev2 = orientFunc (p2, p1, p0);
106
+ bool isConsistentRev = orientRev0 == orientRev1 && orientRev0 == orientRev2;
107
+
108
+ bool isConsistent = isConsistentForward && isConsistentRev;
109
+ if (isConsistent) {
110
+ bool isOpposite = orient0 == -orientRev0;
111
+ isConsistent &= isOpposite;
112
+ }
113
+
114
+ if (isVerbose) {
115
+ std::string consistentInd = isConsistent ? " " : " <!" ;
116
+ std::cout << tag << " : "
117
+ << orientSym (orient0) << orientSym (orient1) << orientSym (orient2) << " "
118
+ << orientSym (orientRev0) << orientSym (orientRev1) << orientSym (orientRev2) << " "
119
+ << " " << consistentInd << " " ;
120
+ }
121
+ return isConsistent;
122
+ }
123
+
124
+ bool isConsistentDD (Coordinate p0, Coordinate p1, Coordinate p2)
125
+ {
126
+ return isConsistent (" DD" , p0, p1, p2,
127
+ [](Coordinate p0, Coordinate p1, Coordinate p2) -> int {
128
+ return Orientation::index (p0, p1, p2);
129
+ });
130
+ }
131
+
132
+ bool isConsistentFP (Coordinate p0, Coordinate p1, Coordinate p2)
133
+ {
134
+ return isConsistent (" FP" , p0, p1, p2,
135
+ [](Coordinate p0, Coordinate p1, Coordinate p2) -> int {
136
+ return orientationIndexFP (p0, p1, p2);
137
+ });
138
+ }
139
+
140
+ Coordinate randomCoord () {
141
+ double x = (10.0 * random ()) / RAND_MAX;
142
+ double y = (10.0 * random ()) / RAND_MAX;
143
+ return Coordinate (x, y);
144
+ }
145
+
146
+ void checkTest (Coordinate p0, Coordinate p1, Coordinate p2)
147
+ {
148
+ bool isCorrectDD = isConsistentDD (p0, p1, p2);
149
+ bool isCorrectFP = isConsistentFP (p0, p1, p2);
150
+
151
+ if (isVerbose) {
152
+ std::cout << std::setprecision (20 ) << " "
153
+ << " LINESTRING ( " << p0.x << " " << p0.y << " , " << p1.x << " " << p1.y << " )"
154
+ << " - " << " POINT ( " << p2.x << " " << p2.y << " )"
155
+ << std::endl;
156
+ }
157
+
158
+ count++;
159
+ if (! isCorrectDD) failDD++;
160
+ if (! isCorrectFP) failFP++;
161
+ }
162
+
163
+ void reportStats (std::string tag = " " ) {
164
+ std::cout << tag << " Num tests: " << count
165
+ << " DD fail = " << failDD << " (" << round ((100.0 * failDD / (double ) count)) << " %)"
166
+ << " FP fail = " << failFP << " (" << round ((100.0 * failFP / (double ) count)) << " %)"
167
+ << std::endl;
168
+ }
169
+
170
+ void runDiagonalTests ()
171
+ {
172
+ const int DIAG_SIZE = 2 ;
173
+ Coordinate p0 = Coordinate (DIAG_SIZE, 0 );
174
+ Coordinate p1 = Coordinate (0 , DIAG_SIZE);
175
+
176
+ const int MAX_PRECISION = 3 ;
177
+ int d = 10 ;
178
+ for (int i = 0 ; i < MAX_PRECISION; i++) {
179
+ int num_points = DIAG_SIZE * d;
180
+ for (int ix = 0 ; ix <= num_points; ix++) {
181
+ int iy = num_points - ix;
182
+ double x = ix / (double ) d;
183
+ double y = iy / (double ) d;
184
+ Coordinate p2 = Coordinate (x, y);
185
+ checkTest (p0, p1, p2);
186
+ }
187
+ d *= 10 ;
188
+ reportStats ();
189
+ }
190
+ }
191
+
192
+ void runRandomTests ()
193
+ {
194
+ srand (static_cast <unsigned > (time (0 )));
195
+
196
+ const size_t MAX_ITER = 10'000'000 ;
197
+
198
+ for (size_t i = 1 ; i <= MAX_ITER; i++) {
199
+ Coordinate p0 = randomCoord ();
200
+ Coordinate p1 = randomCoord ();
201
+ Coordinate p2 = Coordinate (LineSegment::midPoint (p0, p1));
202
+ checkTest (p0, p1, p2);
203
+
204
+ if (i % 10'000 == 0 ) {
205
+ reportStats ();
206
+ }
207
+ }
208
+ }
209
+
210
+ int main (int argc, char ** argv) {
211
+ if (argc > 1 ) {
212
+ parseArgs (argc, argv);
213
+ }
214
+ if (isDiagonalTest) {
215
+ runDiagonalTests ();
216
+ }
217
+ else {
218
+ runRandomTests ();
219
+ }
220
+ reportStats (" Final: " );
221
+ }
0 commit comments