Skip to content

Commit f67563f

Browse files
authored
Add Orientation Index stress test (#1237)
1 parent ce77bcf commit f67563f

File tree

2 files changed

+224
-0
lines changed

2 files changed

+224
-0
lines changed

benchmarks/algorithm/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ target_link_libraries(perf_unaryunion_segments geos)
1818

1919
if (BUILD_BENCHMARKS)
2020

21+
add_executable(stress_orientation OrientationIndexStressTest.cpp)
22+
target_link_libraries(stress_orientation geos)
23+
2124
if (benchmark_FOUND)
2225
add_executable(perf_orientation OrientationIndexPerfTest.cpp
2326
${PROJECT_SOURCE_DIR}/src/algorithm/CGAlgorithmsDD.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
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

Comments
 (0)