-
Notifications
You must be signed in to change notification settings - Fork 113
/
OsmNogoPolygon.java
488 lines (435 loc) · 16.8 KB
/
OsmNogoPolygon.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
/**********************************************************************************************
Copyright (C) 2018 Norbert Truchsess norbert.truchsess@t-online.de
The following methods are based on work of Dan Sunday published at:
http://geomalgorithms.com/a03-_inclusion.html
cn_PnPoly, wn_PnPoly, inSegment, intersect2D_2Segments
**********************************************************************************************/
package btools.router;
import java.util.ArrayList;
import java.util.List;
import btools.util.CheapRuler;
public class OsmNogoPolygon extends OsmNodeNamed {
public final static class Point {
public final int y;
public final int x;
Point(final int lon, final int lat) {
x = lon;
y = lat;
}
}
public final List<Point> points = new ArrayList<>();
public final boolean isClosed;
public OsmNogoPolygon(boolean closed) {
this.isClosed = closed;
this.isNogo = true;
this.name = "";
}
public final void addVertex(int lon, int lat) {
points.add(new Point(lon, lat));
}
/**
* calcBoundingCircle is inspired by the algorithm described on
* http://geomalgorithms.com/a08-_containers.html
* (fast computation of bounding circly in c). It is not as fast (the original
* algorithm runs in linear time), as it may do more iterations but it takes
* into account the coslat-factor being used for the linear approximation that
* is also used in other places of brouter does change when moving the centerpoint
* with each iteration.
* This is done to ensure the calculated radius being used
* in RoutingContext.calcDistance will actually contain the whole polygon.
* <p>
* For reasonable distributed vertices the implemented algorithm runs in O(n*ln(n)).
* As this is only run once on initialization of OsmNogoPolygon this methods
* overall usage of cpu is neglegible in comparism to the cpu-usage of the
* actual routing algoritm.
*/
public void calcBoundingCircle() {
int cxmin, cxmax, cymin, cymax;
cxmin = cymin = Integer.MAX_VALUE;
cxmax = cymax = Integer.MIN_VALUE;
// first calculate a starting center point as center of boundingbox
for (int i = 0; i < points.size(); i++) {
final Point p = points.get(i);
if (p.x < cxmin) {
cxmin = p.x;
}
if (p.x > cxmax) {
cxmax = p.x;
}
if (p.y < cymin) {
cymin = p.y;
}
if (p.y > cymax) {
cymax = p.y;
}
}
int cx = (cxmax + cxmin) / 2; // center of circle
int cy = (cymax + cymin) / 2;
double[] lonlat2m = CheapRuler.getLonLatToMeterScales(cy); // conversion-factors at the center of circle
double dlon2m = lonlat2m[0];
double dlat2m = lonlat2m[1];
double rad = 0; // radius
double dmax = 0; // length of vector from center to point
int i_max = -1;
do {
// now identify the point outside of the circle that has the greatest distance
for (int i = 0; i < points.size(); i++) {
final Point p = points.get(i);
// to get precisely the same results as in RoutingContext.calcDistance()
// it's crucial to use the factors of the center!
final double x1 = (cx - p.x) * dlon2m;
final double y1 = (cy - p.y) * dlat2m;
final double dist = Math.sqrt(x1 * x1 + y1 * y1);
if (dist <= rad) {
continue;
}
if (dist > dmax) {
// new maximum distance found
dmax = dist;
i_max = i;
}
}
if (i_max < 0) {
break; // leave loop when no point outside the circle is found any more.
}
final double dd = 0.5 * (1 - rad / dmax);
final Point p = points.get(i_max); // calculate new radius to just include this point
cx += (int) (dd * (p.x - cx) + 0.5); // shift center toward point
cy += (int) (dd * (p.y - cy) + 0.5);
// get new factors at shifted centerpoint
lonlat2m = CheapRuler.getLonLatToMeterScales(cy);
dlon2m = lonlat2m[0];
dlat2m = lonlat2m[1];
final double x1 = (cx - p.x) * dlon2m;
final double y1 = (cy - p.y) * dlat2m;
dmax = rad = Math.sqrt(x1 * x1 + y1 * y1);
i_max = -1;
}
while (true);
ilon = cx;
ilat = cy;
radius = rad * 1.001 + 1.0; // ensure the outside-of-enclosing-circle test in RoutingContext.calcDistance() is not passed by segments ending very close to the radius due to limited numerical precision
}
/**
* tests whether a segment defined by lon and lat of two points does either
* intersect the polygon or any of the endpoints (or both) are enclosed by
* the polygon. For this test the winding-number algorithm is
* being used. That means a point being within an overlapping region of the
* polygon is also taken as being 'inside' the polygon.
*
* @param lon0 longitude of start point
* @param lat0 latitude of start point
* @param lon1 longitude of end point
* @param lat1 latitude of start point
* @return true if segment or any of it's points are 'inside' of polygon
*/
public boolean intersects(int lon0, int lat0, int lon1, int lat1) {
final Point p0 = new Point(lon0, lat0);
final Point p1 = new Point(lon1, lat1);
int i_last = points.size() - 1;
Point p2 = points.get(isClosed ? i_last : 0);
for (int i = isClosed ? 0 : 1; i <= i_last; i++) {
Point p3 = points.get(i);
// does it intersect with at least one of the polygon's segments?
if (intersect2D_2Segments(p0, p1, p2, p3) > 0) {
return true;
}
p2 = p3;
}
return false;
}
public boolean isOnPolyline(long px, long py) {
int i_last = points.size() - 1;
Point p1 = points.get(0);
for (int i = 1; i <= i_last; i++) {
final Point p2 = points.get(i);
if (isOnLine(px, py, p1.x, p1.y, p2.x, p2.y)) {
return true;
}
p1 = p2;
}
return false;
}
public static boolean isOnLine(long px, long py, long p0x, long p0y, long p1x, long p1y) {
final double v10x = px - p0x;
final double v10y = py - p0y;
final double v12x = p1x - p0x;
final double v12y = p1y - p0y;
if (v10x == 0) { // P0->P1 vertical?
if (v10y == 0) { // P0 == P1?
return true;
}
if (v12x != 0) { // P1->P2 not vertical?
return false;
}
return (v12y / v10y) >= 1; // P1->P2 at least as long as P1->P0?
}
if (v10y == 0) { // P0->P1 horizontal?
if (v12y != 0) { // P1->P2 not horizontal?
return false;
}
// if ( P10x == 0 ) // P0 == P1? already tested
return (v12x / v10x) >= 1; // P1->P2 at least as long as P1->P0?
}
final double kx = v12x / v10x;
if (kx < 1) {
return false;
}
return kx == v12y / v10y;
}
/* Copyright 2001 softSurfer, 2012 Dan Sunday, 2018 Norbert Truchsess
This code may be freely used and modified for any purpose providing that
this copyright notice is included with it. SoftSurfer makes no warranty for
this code, and cannot be held liable for any real or imagined damage
resulting from its use. Users of this code must verify correctness for
their application. */
/**
* winding number test for a point in a polygon
*
* @param px longitude of the point to check
* @param py latitude of the point to check
* @return a boolean whether the point is within the polygon or not.
*/
public boolean isWithin(final long px, final long py) {
int wn = 0; // the winding number counter
// loop through all edges of the polygon
final int i_last = points.size() - 1;
final Point p0 = points.get(isClosed ? i_last : 0);
long p0x = p0.x; // need to use long to avoid overflow in products
long p0y = p0.y;
for (int i = isClosed ? 0 : 1; i <= i_last; i++) { // edge from v[i] to v[i+1]
final Point p1 = points.get(i);
final long p1x = p1.x;
final long p1y = p1.y;
if (isOnLine(px, py, p0x, p0y, p1x, p1y)) {
return true;
}
if (p0y <= py) // start y <= p.y
{
if (p1y > py) { // an upward crossing, p left of edge
if (((p1x - p0x) * (py - p0y) - (px - p0x) * (p1y - p0y)) > 0) {
++wn; // have a valid up intersect
}
}
} else { // start y > p.y (no test needed)
if (p1y <= py) { // a downward crossing, p right of edge
if (((p1x - p0x) * (py - p0y) - (px - p0x) * (p1y - p0y)) < 0) {
--wn; // have a valid down intersect
}
}
}
p0x = p1x;
p0y = p1y;
}
return wn != 0;
}
/**
* Compute the length of the segment within the polygon.
*
* @param lon1 Integer longitude of the first point of the segment.
* @param lat1 Integer latitude of the first point of the segment.
* @param lon2 Integer longitude of the last point of the segment.
* @param lat2 Integer latitude of the last point of the segment.
* @return The length, in meters, of the portion of the segment which is
* included in the polygon.
*/
public double distanceWithinPolygon(int lon1, int lat1, int lon2, int lat2) {
double distance = 0.;
// Extremities of the segments
final Point p1 = new Point(lon1, lat1);
final Point p2 = new Point(lon2, lat2);
Point previousIntersectionOnSegment = null;
if (isWithin(lon1, lat1)) {
// Start point of the segment is within the polygon, this is the first
// "intersection".
previousIntersectionOnSegment = p1;
}
// Loop over edges of the polygon to find intersections
int i_last = points.size() - 1;
for (int i = (isClosed ? 0 : 1), j = (isClosed ? i_last : 0); i <= i_last; j = i++) {
Point edgePoint1 = points.get(j);
Point edgePoint2 = points.get(i);
int intersectsEdge = intersect2D_2Segments(p1, p2, edgePoint1, edgePoint2);
if (isClosed && intersectsEdge == 1) {
// Intersects with a (closed) polygon edge on a single point
// Distance is zero when crossing a polyline.
// Let's find this intersection point
int xdiffSegment = lon1 - lon2;
int xdiffEdge = edgePoint1.x - edgePoint2.x;
int ydiffSegment = lat1 - lat2;
int ydiffEdge = edgePoint1.y - edgePoint2.y;
int div = xdiffSegment * ydiffEdge - xdiffEdge * ydiffSegment;
long dSegment = (long) lon1 * (long) lat2 - (long) lon2 * (long) lat1;
long dEdge = (long) edgePoint1.x * (long) edgePoint2.y - (long) edgePoint2.x * (long) edgePoint1.y;
// Coordinates of the intersection
Point intersection = new Point(
(int) ((dSegment * xdiffEdge - dEdge * xdiffSegment) / div),
(int) ((dSegment * ydiffEdge - dEdge * ydiffSegment) / div)
);
if (
previousIntersectionOnSegment != null
&& isWithin(
(intersection.x + previousIntersectionOnSegment.x) >> 1,
(intersection.y + previousIntersectionOnSegment.y) >> 1
)
) {
// There was a previous match within the polygon and this part of the
// segment is within the polygon.
distance += CheapRuler.distance(
previousIntersectionOnSegment.x, previousIntersectionOnSegment.y,
intersection.x, intersection.y
);
}
previousIntersectionOnSegment = intersection;
} else if (intersectsEdge == 2) {
// Segment and edge overlaps
// FIXME: Could probably be done in a smarter way
distance += Math.min(
CheapRuler.distance(p1.x, p1.y, p2.x, p2.y),
Math.min(
CheapRuler.distance(edgePoint1.x, edgePoint1.y, edgePoint2.x, edgePoint2.y),
Math.min(
CheapRuler.distance(p1.x, p1.y, edgePoint2.x, edgePoint2.y),
CheapRuler.distance(edgePoint1.x, edgePoint1.y, p2.x, p2.y)
)
)
);
// FIXME: We could store intersection.
previousIntersectionOnSegment = null;
}
}
if (
previousIntersectionOnSegment != null
&& isWithin(lon2, lat2)
) {
// Last point is within the polygon, add the remaining missing distance.
distance += CheapRuler.distance(
previousIntersectionOnSegment.x, previousIntersectionOnSegment.y,
lon2, lat2
);
}
return distance;
}
/* Copyright 2001 softSurfer, 2012 Dan Sunday, 2018 Norbert Truchsess
This code may be freely used and modified for any purpose providing that
this copyright notice is included with it. SoftSurfer makes no warranty for
this code, and cannot be held liable for any real or imagined damage
resulting from its use. Users of this code must verify correctness for
their application. */
/**
* inSegment(): determine if a point is inside a segment
*
* @param p a point
* @param seg_p0 starting point of segment
* @param seg_p1 ending point of segment
* @return 1 = P is inside S
* 0 = P is not inside S
*/
private static boolean inSegment(final Point p, final Point seg_p0, final Point seg_p1) {
final int sp0x = seg_p0.x;
final int sp1x = seg_p1.x;
if (sp0x != sp1x) { // S is not vertical
final int px = p.x;
if (sp0x <= px && px <= sp1x) {
return true;
}
if (sp0x >= px && px >= sp1x) {
return true;
}
} else // S is vertical, so test y coordinate
{
final int sp0y = seg_p0.y;
final int sp1y = seg_p1.y;
final int py = p.y;
if (sp0y <= py && py <= sp1y) {
return true;
}
if (sp0y >= py && py >= sp1y) {
return true;
}
}
return false;
}
/* Copyright 2001 softSurfer, 2012 Dan Sunday, 2018 Norbert Truchsess
This code may be freely used and modified for any purpose providing that
this copyright notice is included with it. SoftSurfer makes no warranty for
this code, and cannot be held liable for any real or imagined damage
resulting from its use. Users of this code must verify correctness for
their application. */
/**
* intersect2D_2Segments(): find the 2D intersection of 2 finite segments
*
* @param s1p0 start point of segment 1
* @param s1p1 end point of segment 1
* @param s2p0 start point of segment 2
* @param s2p1 end point of segment 2
* @return 0=disjoint (no intersect)
* 1=intersect in unique point I0
* 2=overlap in segment from I0 to I1
*/
private static int intersect2D_2Segments(final Point s1p0, final Point s1p1, final Point s2p0, final Point s2p1) {
final long ux = s1p1.x - s1p0.x; // vector u = S1P1-S1P0 (segment 1)
final long uy = s1p1.y - s1p0.y;
final long vx = s2p1.x - s2p0.x; // vector v = S2P1-S2P0 (segment 2)
final long vy = s2p1.y - s2p0.y;
final long wx = s1p0.x - s2p0.x; // vector w = S1P0-S2P0 (from start of segment 2 to start of segment 1
final long wy = s1p0.y - s2p0.y;
final double d = ux * vy - uy * vx;
// test if they are parallel (includes either being a point)
if (d == 0) // S1 and S2 are parallel
{
if ((ux * wy - uy * wx) != 0 || (vx * wy - vy * wx) != 0) {
return 0; // they are NOT collinear
}
// they are collinear or degenerate
// check if they are degenerate points
final boolean du = ((ux == 0) && (uy == 0));
final boolean dv = ((vx == 0) && (vy == 0));
if (du && dv) // both segments are points
{
return (wx == 0 && wy == 0) ? 0 : 1; // return 0 if they are distinct points
}
if (du) // S1 is a single point
{
return inSegment(s1p0, s2p0, s2p1) ? 1 : 0; // is it part of S2?
}
if (dv) // S2 a single point
{
return inSegment(s2p0, s1p0, s1p1) ? 1 : 0; // is it part of S1?
}
// they are collinear segments - get overlap (or not)
double t0, t1; // endpoints of S1 in eqn for S2
final int w2x = s1p1.x - s2p0.x; // vector w2 = S1P1-S2P0 (from start of segment 2 to end of segment 1)
final int w2y = s1p1.y - s2p0.y;
if (vx != 0) {
t0 = wx / vx;
t1 = w2x / vx;
} else {
t0 = wy / vy;
t1 = w2y / vy;
}
if (t0 > t1) // must have t0 smaller than t1
{
final double t = t0; // swap if not
t0 = t1;
t1 = t;
}
if (t0 > 1 || t1 < 0) {
return 0; // NO overlap
}
t0 = t0 < 0 ? 0 : t0; // clip to min 0
t1 = t1 > 1 ? 1 : t1; // clip to max 1
return (t0 == t1) ? 1 : 2; // return 1 if intersect is a point
}
// the segments are skew and may intersect in a point
// get the intersect parameter for S1
final double sI = (vx * wy - vy * wx) / d;
if (sI < 0 || sI > 1) // no intersect with S1
{
return 0;
}
// get the intersect parameter for S2
final double tI = (ux * wy - uy * wx) / d;
return (tI < 0 || tI > 1) ? 0 : 1; // return 0 if no intersect with S2
}
}