Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/src/utils/geom/PositionVector.cpp
169678 views
1
/****************************************************************************/
2
// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
// Copyright (C) 2001-2025 German Aerospace Center (DLR) and others.
4
// This program and the accompanying materials are made available under the
5
// terms of the Eclipse Public License 2.0 which is available at
6
// https://www.eclipse.org/legal/epl-2.0/
7
// This Source Code may also be made available under the following Secondary
8
// Licenses when the conditions for such availability set forth in the Eclipse
9
// Public License 2.0 are satisfied: GNU General Public License, version 2
10
// or later which is available at
11
// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13
/****************************************************************************/
14
/// @file PositionVector.cpp
15
/// @author Daniel Krajzewicz
16
/// @author Jakob Erdmann
17
/// @author Michael Behrisch
18
/// @author Walter Bamberger
19
/// @date Sept 2002
20
///
21
// A list of positions
22
/****************************************************************************/
23
#include <config.h>
24
25
#include <queue>
26
#include <cmath>
27
#include <iostream>
28
#include <algorithm>
29
#include <numeric>
30
#include <cassert>
31
#include <iterator>
32
#include <limits>
33
#include <utils/common/StdDefs.h>
34
#include <utils/common/MsgHandler.h>
35
#include <utils/common/ToString.h>
36
#include "AbstractPoly.h"
37
#include "Position.h"
38
#include "PositionVector.h"
39
#include "GeomHelper.h"
40
#include "Boundary.h"
41
42
//#define DEBUG_MOVE2SIDE
43
44
// ===========================================================================
45
// static members
46
// ===========================================================================
47
const PositionVector PositionVector::EMPTY;
48
49
// ===========================================================================
50
// method definitions
51
// ===========================================================================
52
53
PositionVector::PositionVector() {}
54
55
56
PositionVector::PositionVector(const std::vector<Position>& v) {
57
std::copy(v.begin(), v.end(), std::back_inserter(*this));
58
}
59
60
61
PositionVector::PositionVector(const std::vector<Position>::const_iterator beg, const std::vector<Position>::const_iterator end) {
62
std::copy(beg, end, std::back_inserter(*this));
63
}
64
65
66
PositionVector::PositionVector(const Position& p1, const Position& p2) {
67
push_back(p1);
68
push_back(p2);
69
}
70
71
72
PositionVector::~PositionVector() {}
73
74
75
bool
76
PositionVector::around(const Position& p, double offset) const {
77
if (size() < 2) {
78
return false;
79
}
80
if (offset != 0) {
81
PositionVector tmp(*this);
82
tmp.scaleAbsolute(offset);
83
return tmp.around(p);
84
}
85
double angle = 0;
86
// iterate over all points, and obtain angle between current and next
87
for (const_iterator i = begin(); i != (end() - 1); i++) {
88
Position p1(
89
i->x() - p.x(),
90
i->y() - p.y());
91
Position p2(
92
(i + 1)->x() - p.x(),
93
(i + 1)->y() - p.y());
94
angle += GeomHelper::angle2D(p1, p2);
95
}
96
// add angle between last and first point
97
Position p1(
98
(end() - 1)->x() - p.x(),
99
(end() - 1)->y() - p.y());
100
Position p2(
101
begin()->x() - p.x(),
102
begin()->y() - p.y());
103
angle += GeomHelper::angle2D(p1, p2);
104
// if angle is less than PI, then point lying in Polygon
105
return (!(fabs(angle) < M_PI));
106
}
107
108
109
bool
110
PositionVector::overlapsWith(const AbstractPoly& poly, double offset) const {
111
if (
112
// check whether one of my points lies within the given poly
113
partialWithin(poly, offset) ||
114
// check whether the polygon lies within me
115
poly.partialWithin(*this, offset)) {
116
return true;
117
}
118
if (size() >= 2) {
119
for (const_iterator i = begin(); i != end() - 1; i++) {
120
if (poly.crosses(*i, *(i + 1))) {
121
return true;
122
}
123
}
124
if (size() > 2 && poly.crosses(back(), front())) {
125
return true;
126
}
127
}
128
return false;
129
}
130
131
132
double
133
PositionVector::getOverlapWith(const PositionVector& poly, double zThreshold) const {
134
double result = 0;
135
if ((size() == 0) || (poly.size() == 0)) {
136
return result;
137
}
138
// this points within poly
139
for (const_iterator i = begin(); i != end() - 1; i++) {
140
if (poly.around(*i)) {
141
Position closest = poly.positionAtOffset2D(poly.nearest_offset_to_point2D(*i));
142
if (fabs(closest.z() - (*i).z()) < zThreshold) {
143
result = MAX2(result, poly.distance2D(*i));
144
}
145
}
146
}
147
// polys points within this
148
for (const_iterator i = poly.begin(); i != poly.end() - 1; i++) {
149
if (around(*i)) {
150
Position closest = positionAtOffset2D(nearest_offset_to_point2D(*i));
151
if (fabs(closest.z() - (*i).z()) < zThreshold) {
152
result = MAX2(result, distance2D(*i));
153
}
154
}
155
}
156
return result;
157
}
158
159
160
bool
161
PositionVector::intersects(const Position& p1, const Position& p2) const {
162
if (size() < 2) {
163
return false;
164
}
165
for (const_iterator i = begin(); i != end() - 1; i++) {
166
if (intersects(*i, *(i + 1), p1, p2)) {
167
return true;
168
}
169
}
170
return false;
171
}
172
173
174
bool
175
PositionVector::intersects(const PositionVector& v1) const {
176
if (size() < 2) {
177
return false;
178
}
179
for (const_iterator i = begin(); i != end() - 1; i++) {
180
if (v1.intersects(*i, *(i + 1))) {
181
return true;
182
}
183
}
184
return false;
185
}
186
187
188
Position
189
PositionVector::intersectionPosition2D(const Position& p1, const Position& p2, const double withinDist) const {
190
for (const_iterator i = begin(); i != end() - 1; i++) {
191
double x, y, m;
192
if (intersects(*i, *(i + 1), p1, p2, withinDist, &x, &y, &m)) {
193
return Position(x, y);
194
}
195
}
196
return Position::INVALID;
197
}
198
199
200
Position
201
PositionVector::intersectionPosition2D(const PositionVector& v1) const {
202
for (const_iterator i = begin(); i != end() - 1; i++) {
203
if (v1.intersects(*i, *(i + 1))) {
204
return v1.intersectionPosition2D(*i, *(i + 1));
205
}
206
}
207
return Position::INVALID;
208
}
209
210
211
const Position&
212
PositionVector::operator[](int index) const {
213
/* bracket operators works as in Python. Examples:
214
- A = {'a', 'b', 'c', 'd'} (size 4)
215
- A [2] returns 'c' because 0 < 2 < 4
216
- A [100] throws an exception because 100 > 4
217
- A [-1] returns 'd' because 4 - 1 = 3
218
- A [-100] throws an exception because (4-100) < 0
219
*/
220
if (index >= 0 && index < (int)size()) {
221
return at(index);
222
} else if (index < 0 && -index <= (int)size()) {
223
return at((int)size() + index);
224
} else {
225
throw OutOfBoundsException("Index out of range in bracket operator of PositionVector");
226
}
227
}
228
229
230
Position&
231
PositionVector::operator[](int index) {
232
/* bracket operators works as in Python. Examples:
233
- A = {'a', 'b', 'c', 'd'} (size 4)
234
- A [2] returns 'c' because 0 < 2 < 4
235
- A [100] throws an exception because 100 > 4
236
- A [-1] returns 'd' because 4 - 1 = 3
237
- A [-100] throws an exception because (4-100) < 0
238
*/
239
if (index >= 0 && index < (int)size()) {
240
return at(index);
241
} else if (index < 0 && -index <= (int)size()) {
242
return at((int)size() + index);
243
} else {
244
throw OutOfBoundsException("Index out of range in bracket operator of PositionVector");
245
}
246
}
247
248
249
Position
250
PositionVector::positionAtOffset(double pos, double lateralOffset) const {
251
if (size() == 0) {
252
return Position::INVALID;
253
}
254
if (size() == 1) {
255
return front();
256
}
257
const_iterator i = begin();
258
double seenLength = 0;
259
do {
260
const double nextLength = (*i).distanceTo(*(i + 1));
261
if (seenLength + nextLength > pos) {
262
return positionAtOffset(*i, *(i + 1), pos - seenLength, lateralOffset);
263
}
264
seenLength += nextLength;
265
} while (++i != end() - 1);
266
if (lateralOffset == 0 || size() < 2) {
267
return back();
268
} else {
269
return positionAtOffset(*(end() - 2), *(end() - 1), (*(end() - 2)).distanceTo(*(end() - 1)), lateralOffset);
270
}
271
}
272
273
274
Position
275
PositionVector::sidePositionAtAngle(double pos, double lateralOffset, double angle) const {
276
if (size() == 0) {
277
return Position::INVALID;
278
}
279
if (size() == 1) {
280
return front();
281
}
282
const_iterator i = begin();
283
double seenLength = 0;
284
do {
285
const double nextLength = (*i).distanceTo(*(i + 1));
286
if (seenLength + nextLength > pos) {
287
return sidePositionAtAngle(*i, *(i + 1), pos - seenLength, lateralOffset, angle);
288
}
289
seenLength += nextLength;
290
} while (++i != end() - 1);
291
return sidePositionAtAngle(*(end() - 2), *(end() - 1), (*(end() - 2)).distanceTo(*(end() - 1)), lateralOffset, angle);
292
}
293
294
295
Position
296
PositionVector::positionAtOffset2D(double pos, double lateralOffset, bool extrapolateBeyond) const {
297
if (size() == 0) {
298
return Position::INVALID;
299
}
300
if (size() == 1) {
301
return front();
302
}
303
const_iterator i = begin();
304
double seenLength = 0;
305
do {
306
const double nextLength = (*i).distanceTo2D(*(i + 1));
307
if (seenLength + nextLength > pos) {
308
return positionAtOffset2D(*i, *(i + 1), pos - seenLength, lateralOffset, extrapolateBeyond);
309
}
310
seenLength += nextLength;
311
} while (++i != end() - 1);
312
if (extrapolateBeyond) {
313
return positionAtOffset2D(*(i - 1), *i, pos - seenLength + (*i).distanceTo2D(*(i - 1)), lateralOffset, extrapolateBeyond);
314
}
315
return back();
316
}
317
318
319
double
320
PositionVector::rotationAtOffset(double pos) const {
321
if ((size() == 0) || (size() == 1)) {
322
return INVALID_DOUBLE;
323
}
324
if (pos < 0) {
325
pos += length();
326
}
327
const_iterator i = begin();
328
double seenLength = 0;
329
do {
330
const Position& p1 = *i;
331
const Position& p2 = *(i + 1);
332
const double nextLength = p1.distanceTo(p2);
333
if (seenLength + nextLength > pos) {
334
return p1.angleTo2D(p2);
335
}
336
seenLength += nextLength;
337
} while (++i != end() - 1);
338
const Position& p1 = (*this)[-2];
339
const Position& p2 = back();
340
return p1.angleTo2D(p2);
341
}
342
343
344
double
345
PositionVector::rotationDegreeAtOffset(double pos) const {
346
return GeomHelper::legacyDegree(rotationAtOffset(pos));
347
}
348
349
350
double
351
PositionVector::slopeDegreeAtOffset(double pos) const {
352
if (size() == 0) {
353
return INVALID_DOUBLE;
354
}
355
const_iterator i = begin();
356
double seenLength = 0;
357
do {
358
const Position& p1 = *i;
359
const Position& p2 = *(i + 1);
360
const double nextLength = p1.distanceTo(p2);
361
if (seenLength + nextLength > pos) {
362
return RAD2DEG(p1.slopeTo2D(p2));
363
}
364
seenLength += nextLength;
365
} while (++i != end() - 1);
366
const Position& p1 = (*this)[-2];
367
const Position& p2 = back();
368
return RAD2DEG(p1.slopeTo2D(p2));
369
}
370
371
372
Position
373
PositionVector::positionAtOffset(const Position& p1, const Position& p2, double pos, double lateralOffset) {
374
const double dist = p1.distanceTo(p2);
375
if (pos < 0. || dist < pos) {
376
return Position::INVALID;
377
}
378
if (lateralOffset != 0) {
379
if (dist == 0.) {
380
return Position::INVALID;
381
}
382
const Position offset = sideOffset(p1, p2, -lateralOffset); // move in the same direction as Position::move2side
383
if (pos == 0.) {
384
return p1 + offset;
385
}
386
return p1 + (p2 - p1) * (pos / dist) + offset;
387
}
388
if (pos == 0.) {
389
return p1;
390
}
391
return p1 + (p2 - p1) * (pos / dist);
392
}
393
394
395
Position
396
PositionVector::sidePositionAtAngle(const Position& p1, const Position& p2, double pos, double lateralOffset, double angle) {
397
const double dist = p1.distanceTo(p2);
398
if (pos < 0. || dist < pos || dist == 0) {
399
return Position::INVALID;
400
}
401
angle -= DEG2RAD(90);
402
const Position offset(cos(angle) * lateralOffset, sin(angle) * lateralOffset);
403
return p1 + (p2 - p1) * (pos / dist) + offset;
404
}
405
406
407
Position
408
PositionVector::positionAtOffset2D(const Position& p1, const Position& p2, double pos, double lateralOffset, bool extrapolateBeyond) {
409
const double dist = p1.distanceTo2D(p2);
410
if ((pos < 0 || dist < pos) && !extrapolateBeyond) {
411
return Position::INVALID;
412
}
413
if (dist == 0) {
414
return p1;
415
}
416
if (lateralOffset != 0) {
417
const Position offset = sideOffset(p1, p2, -lateralOffset); // move in the same direction as Position::move2side
418
if (pos == 0.) {
419
return p1 + offset;
420
}
421
return p1 + (p2 - p1) * (pos / dist) + offset;
422
}
423
if (pos == 0.) {
424
return p1;
425
}
426
return p1 + (p2 - p1) * (pos / dist);
427
}
428
429
430
Boundary
431
PositionVector::getBoxBoundary() const {
432
Boundary ret;
433
for (const Position& i : *this) {
434
ret.add(i);
435
}
436
return ret;
437
}
438
439
440
Position
441
PositionVector::getPolygonCenter() const {
442
if (size() == 0) {
443
return Position::INVALID;
444
}
445
double x = 0;
446
double y = 0;
447
double z = 0;
448
for (const Position& i : *this) {
449
x += i.x();
450
y += i.y();
451
z += i.z();
452
}
453
return Position(x / (double) size(), y / (double) size(), z / (double)size());
454
}
455
456
457
Position
458
PositionVector::getCentroid() const {
459
if (size() == 0) {
460
return Position::INVALID;
461
} else if (size() == 1) {
462
return (*this)[0];
463
} else if (size() == 2) {
464
return ((*this)[0] + (*this)[1]) * 0.5;
465
}
466
PositionVector tmp = *this;
467
if (!isClosed()) { // make sure its closed
468
tmp.push_back(tmp[0]);
469
}
470
// shift to origin to increase numerical stability
471
Position offset = tmp[0];
472
Position result;
473
tmp.sub(offset);
474
const int endIndex = (int)tmp.size() - 1;
475
double div = 0; // 6 * area including sign
476
double x = 0;
477
double y = 0;
478
if (tmp.area() != 0) { // numerical instability ?
479
// http://en.wikipedia.org/wiki/Polygon
480
for (int i = 0; i < endIndex; i++) {
481
const double z = tmp[i].x() * tmp[i + 1].y() - tmp[i + 1].x() * tmp[i].y();
482
div += z; // area formula
483
x += (tmp[i].x() + tmp[i + 1].x()) * z;
484
y += (tmp[i].y() + tmp[i + 1].y()) * z;
485
}
486
div *= 3; // 6 / 2, the 2 comes from the area formula
487
result = Position(x / div, y / div);
488
} else {
489
// compute by decomposing into line segments
490
// http://en.wikipedia.org/wiki/Centroid#By_geometric_decomposition
491
double lengthSum = 0;
492
for (int i = 0; i < endIndex; i++) {
493
double length = tmp[i].distanceTo(tmp[i + 1]);
494
x += (tmp[i].x() + tmp[i + 1].x()) * length / 2;
495
y += (tmp[i].y() + tmp[i + 1].y()) * length / 2;
496
lengthSum += length;
497
}
498
if (lengthSum == 0) {
499
// it is probably only one point
500
result = tmp[0];
501
}
502
result = Position(x / lengthSum, y / lengthSum) + offset;
503
}
504
return result + offset;
505
}
506
507
508
void
509
PositionVector::scaleRelative(double factor) {
510
Position centroid = getCentroid();
511
for (int i = 0; i < static_cast<int>(size()); i++) {
512
(*this)[i] = centroid + (((*this)[i] - centroid) * factor);
513
}
514
}
515
516
517
void
518
PositionVector::scaleAbsolute(double offset) {
519
Position centroid = getCentroid();
520
for (int i = 0; i < static_cast<int>(size()); i++) {
521
(*this)[i] = centroid + (((*this)[i] - centroid) + offset);
522
}
523
}
524
525
526
Position
527
PositionVector::getLineCenter() const {
528
if (size() == 1) {
529
return (*this)[0];
530
} else {
531
return positionAtOffset(double((length() / 2.)));
532
}
533
}
534
535
536
double
537
PositionVector::length() const {
538
if (size() == 0) {
539
return 0;
540
}
541
double len = 0;
542
for (const_iterator i = begin(); i != end() - 1; i++) {
543
len += (*i).distanceTo(*(i + 1));
544
}
545
return len;
546
}
547
548
549
double
550
PositionVector::length2D() const {
551
if (size() == 0) {
552
return 0;
553
}
554
double len = 0;
555
for (const_iterator i = begin(); i != end() - 1; i++) {
556
len += (*i).distanceTo2D(*(i + 1));
557
}
558
return len;
559
}
560
561
562
double
563
PositionVector::area() const {
564
if (size() < 3) {
565
return 0;
566
}
567
double area = 0;
568
PositionVector tmp = *this;
569
if (!isClosed()) { // make sure its closed
570
tmp.push_back(tmp[0]);
571
}
572
const int endIndex = (int)tmp.size() - 1;
573
// http://en.wikipedia.org/wiki/Polygon
574
for (int i = 0; i < endIndex; i++) {
575
area += tmp[i].x() * tmp[i + 1].y() - tmp[i + 1].x() * tmp[i].y();
576
}
577
if (area < 0) { // we whether we had cw or ccw order
578
area *= -1;
579
}
580
return area / 2;
581
}
582
583
584
bool
585
PositionVector::partialWithin(const AbstractPoly& poly, double offset) const {
586
if (size() < 2) {
587
return false;
588
}
589
for (const_iterator i = begin(); i != end(); i++) {
590
if (poly.around(*i, offset)) {
591
return true;
592
}
593
}
594
return false;
595
}
596
597
598
bool
599
PositionVector::crosses(const Position& p1, const Position& p2) const {
600
return intersects(p1, p2);
601
}
602
603
604
std::pair<PositionVector, PositionVector>
605
PositionVector::splitAt(double where, bool use2D) const {
606
const double len = use2D ? length2D() : length();
607
if (size() < 2) {
608
throw InvalidArgument("Vector to short for splitting");
609
}
610
if (where < 0 || where > len) {
611
throw InvalidArgument("Invalid split position " + toString(where) + " for vector of length " + toString(len));
612
}
613
if (where <= POSITION_EPS || where >= len - POSITION_EPS) {
614
WRITE_WARNINGF(TL("Splitting vector close to end (pos: %, length: %)"), toString(where), toString(len));
615
}
616
PositionVector first, second;
617
first.push_back((*this)[0]);
618
double seen = 0;
619
const_iterator it = begin() + 1;
620
double next = use2D ? first.back().distanceTo2D(*it) : first.back().distanceTo(*it);
621
// see how many points we can add to first
622
while (where >= seen + next + POSITION_EPS) {
623
seen += next;
624
first.push_back(*it);
625
it++;
626
next = use2D ? first.back().distanceTo2D(*it) : first.back().distanceTo(*it);
627
}
628
if (fabs(where - (seen + next)) > POSITION_EPS || it == end() - 1) {
629
// we need to insert a new point because 'where' is not close to an
630
// existing point or it is to close to the endpoint
631
const Position p = (use2D
632
? positionAtOffset2D(first.back(), *it, where - seen)
633
: positionAtOffset(first.back(), *it, where - seen));
634
first.push_back(p);
635
second.push_back(p);
636
} else {
637
first.push_back(*it);
638
}
639
// add the remaining points to second
640
for (; it != end(); it++) {
641
second.push_back(*it);
642
}
643
assert(first.size() >= 2);
644
assert(second.size() >= 2);
645
assert(first.back() == second.front());
646
assert(fabs((use2D ? first.length2D() + second.length2D() : first.length() + second.length()) - len) < 2 * POSITION_EPS);
647
return std::pair<PositionVector, PositionVector>(first, second);
648
}
649
650
651
std::ostream&
652
operator<<(std::ostream& os, const PositionVector& geom) {
653
for (PositionVector::const_iterator i = geom.begin(); i != geom.end(); i++) {
654
if (i != geom.begin()) {
655
os << " ";
656
}
657
os << (*i);
658
}
659
return os;
660
}
661
662
663
void
664
PositionVector::sortAsPolyCWByAngle() {
665
// We take the centroid of the points as an origin for the angle computations
666
// that will follow but other points could be taken (the center of the bounding
667
// box of the polygon for instance). Each of these can potentially lead
668
// to a different result in the case of a non-convex polygon.
669
const Position centroid = std::accumulate(begin(), end(), Position(0, 0)) / (double)size();
670
sub(centroid);
671
std::sort(begin(), end(), as_poly_cw_sorter());
672
add(centroid);
673
}
674
675
676
void
677
PositionVector::add(double xoff, double yoff, double zoff) {
678
for (int i = 0; i < (int)size(); i++) {
679
(*this)[i].add(xoff, yoff, zoff);
680
}
681
}
682
683
684
void
685
PositionVector::sub(const Position& offset) {
686
add(-offset.x(), -offset.y(), -offset.z());
687
}
688
689
690
void
691
PositionVector::add(const Position& offset) {
692
add(offset.x(), offset.y(), offset.z());
693
}
694
695
696
PositionVector
697
PositionVector::added(const Position& offset) const {
698
PositionVector pv;
699
for (auto i1 = begin(); i1 != end(); ++i1) {
700
pv.push_back(*i1 + offset);
701
}
702
return pv;
703
}
704
705
706
void
707
PositionVector::mirrorX() {
708
for (int i = 0; i < (int)size(); i++) {
709
(*this)[i].mul(1, -1);
710
}
711
}
712
713
714
PositionVector::as_poly_cw_sorter::as_poly_cw_sorter() {}
715
716
717
int
718
PositionVector::as_poly_cw_sorter::operator()(const Position& p1, const Position& p2) const {
719
double angle1 = atAngle2D(p1);
720
double angle2 = atAngle2D(p2);
721
if (angle1 > angle2) {
722
return true;
723
}
724
if (angle1 == angle2) {
725
double squaredDistance1 = p1.dotProduct(p1);
726
double squaredDistance2 = p2.dotProduct(p2);
727
if (squaredDistance1 < squaredDistance2) {
728
return true;
729
}
730
}
731
return false;
732
}
733
734
735
double
736
PositionVector::as_poly_cw_sorter::atAngle2D(const Position& p) const {
737
double angle = atan2(p.y(), p.x());
738
return angle < 0.0 ? angle : angle + 2.0 * M_PI;
739
}
740
741
void
742
PositionVector::sortByIncreasingXY() {
743
std::sort(begin(), end(), increasing_x_y_sorter());
744
}
745
746
747
PositionVector::increasing_x_y_sorter::increasing_x_y_sorter() {}
748
749
750
int
751
PositionVector::increasing_x_y_sorter::operator()(const Position& p1, const Position& p2) const {
752
if (p1.x() != p2.x()) {
753
return p1.x() < p2.x();
754
}
755
return p1.y() < p2.y();
756
}
757
758
759
double
760
PositionVector::isLeft(const Position& P0, const Position& P1, const Position& P2) const {
761
return (P1.x() - P0.x()) * (P2.y() - P0.y()) - (P2.x() - P0.x()) * (P1.y() - P0.y());
762
}
763
764
765
void
766
PositionVector::append(const PositionVector& v, double sameThreshold) {
767
if ((size() > 0) && (v.size() > 0) && (back().distanceTo(v[0]) < sameThreshold)) {
768
copy(v.begin() + 1, v.end(), back_inserter(*this));
769
} else {
770
copy(v.begin(), v.end(), back_inserter(*this));
771
}
772
}
773
774
775
void
776
PositionVector::prepend(const PositionVector& v, double sameThreshold) {
777
if ((size() > 0) && (v.size() > 0) && (front().distanceTo(v.back()) < sameThreshold)) {
778
insert(begin(), v.begin(), v.end() - 1);
779
} else {
780
insert(begin(), v.begin(), v.end());
781
}
782
}
783
784
785
PositionVector
786
PositionVector::getSubpart(double beginOffset, double endOffset) const {
787
PositionVector ret;
788
Position begPos = front();
789
if (beginOffset > POSITION_EPS) {
790
begPos = positionAtOffset(beginOffset);
791
}
792
Position endPos = back();
793
if (endOffset < length() - POSITION_EPS) {
794
endPos = positionAtOffset(endOffset);
795
}
796
ret.push_back(begPos);
797
798
double seen = 0;
799
const_iterator i = begin();
800
// skip previous segments
801
while ((i + 1) != end()
802
&&
803
seen + (*i).distanceTo(*(i + 1)) < beginOffset) {
804
seen += (*i).distanceTo(*(i + 1));
805
i++;
806
}
807
// append segments in between
808
while ((i + 1) != end()
809
&&
810
seen + (*i).distanceTo(*(i + 1)) < endOffset) {
811
812
ret.push_back_noDoublePos(*(i + 1));
813
seen += (*i).distanceTo(*(i + 1));
814
i++;
815
}
816
// append end
817
ret.push_back_noDoublePos(endPos);
818
if (ret.size() == 1) {
819
ret.push_back(endPos);
820
}
821
return ret;
822
}
823
824
825
PositionVector
826
PositionVector::getSubpart2D(double beginOffset, double endOffset) const {
827
if (size() == 0) {
828
return PositionVector();
829
}
830
PositionVector ret;
831
Position begPos = front();
832
if (beginOffset > POSITION_EPS) {
833
begPos = positionAtOffset2D(beginOffset);
834
}
835
Position endPos = back();
836
if (endOffset < length2D() - POSITION_EPS) {
837
endPos = positionAtOffset2D(endOffset);
838
}
839
ret.push_back(begPos);
840
841
double seen = 0;
842
const_iterator i = begin();
843
// skip previous segments
844
while ((i + 1) != end()
845
&&
846
seen + (*i).distanceTo2D(*(i + 1)) < beginOffset) {
847
seen += (*i).distanceTo2D(*(i + 1));
848
i++;
849
}
850
// append segments in between
851
while ((i + 1) != end()
852
&&
853
seen + (*i).distanceTo2D(*(i + 1)) < endOffset) {
854
855
ret.push_back_noDoublePos(*(i + 1));
856
seen += (*i).distanceTo2D(*(i + 1));
857
i++;
858
}
859
// append end
860
ret.push_back_noDoublePos(endPos);
861
if (ret.size() == 1) {
862
ret.push_back(endPos);
863
}
864
return ret;
865
}
866
867
868
PositionVector
869
PositionVector::getSubpartByIndex(int beginIndex, int count) const {
870
if (size() == 0) {
871
return PositionVector();
872
}
873
if (beginIndex < 0) {
874
beginIndex += (int)size();
875
}
876
assert(count >= 0);
877
assert(beginIndex < (int)size());
878
assert(beginIndex + count <= (int)size());
879
PositionVector result;
880
for (int i = beginIndex; i < beginIndex + count; ++i) {
881
result.push_back((*this)[i]);
882
}
883
return result;
884
}
885
886
887
double
888
PositionVector::beginEndAngle() const {
889
if (size() == 0) {
890
return INVALID_DOUBLE;
891
}
892
return front().angleTo2D(back());
893
}
894
895
896
double
897
PositionVector::nearest_offset_to_point2D(const Position& p, bool perpendicular) const {
898
if (size() == 0) {
899
return INVALID_DOUBLE;
900
}
901
double minDist = std::numeric_limits<double>::max();
902
double nearestPos = GeomHelper::INVALID_OFFSET;
903
double seen = 0;
904
for (const_iterator i = begin(); i != end() - 1; i++) {
905
const double pos =
906
GeomHelper::nearest_offset_on_line_to_point2D(*i, *(i + 1), p, perpendicular);
907
const double dist2 = pos == GeomHelper::INVALID_OFFSET ? minDist : p.distanceSquaredTo2D(positionAtOffset2D(*i, *(i + 1), pos));
908
if (dist2 < minDist) {
909
nearestPos = pos + seen;
910
minDist = dist2;
911
}
912
if (perpendicular && i != begin() && pos == GeomHelper::INVALID_OFFSET) {
913
// even if perpendicular is set we still need to check the distance to the inner points
914
const double cornerDist2 = p.distanceSquaredTo2D(*i);
915
if (cornerDist2 < minDist) {
916
const double pos1 =
917
GeomHelper::nearest_offset_on_line_to_point2D(*(i - 1), *i, p, false);
918
const double pos2 =
919
GeomHelper::nearest_offset_on_line_to_point2D(*i, *(i + 1), p, false);
920
if (pos1 == (*(i - 1)).distanceTo2D(*i) && pos2 == 0.) {
921
nearestPos = seen;
922
minDist = cornerDist2;
923
}
924
}
925
}
926
seen += (*i).distanceTo2D(*(i + 1));
927
}
928
return nearestPos;
929
}
930
931
932
double
933
PositionVector::nearest_offset_to_point25D(const Position& p, bool perpendicular) const {
934
if (size() == 0) {
935
return INVALID_DOUBLE;
936
}
937
double minDist = std::numeric_limits<double>::max();
938
double nearestPos = GeomHelper::INVALID_OFFSET;
939
double seen = 0;
940
for (const_iterator i = begin(); i != end() - 1; i++) {
941
const double pos =
942
GeomHelper::nearest_offset_on_line_to_point2D(*i, *(i + 1), p, perpendicular);
943
const double dist = pos == GeomHelper::INVALID_OFFSET ? minDist : p.distanceTo2D(positionAtOffset2D(*i, *(i + 1), pos));
944
if (dist < minDist) {
945
const double pos25D = pos * (*i).distanceTo(*(i + 1)) / (*i).distanceTo2D(*(i + 1));
946
nearestPos = pos25D + seen;
947
minDist = dist;
948
}
949
if (perpendicular && i != begin() && pos == GeomHelper::INVALID_OFFSET) {
950
// even if perpendicular is set we still need to check the distance to the inner points
951
const double cornerDist = p.distanceTo2D(*i);
952
if (cornerDist < minDist) {
953
const double pos1 =
954
GeomHelper::nearest_offset_on_line_to_point2D(*(i - 1), *i, p, false);
955
const double pos2 =
956
GeomHelper::nearest_offset_on_line_to_point2D(*i, *(i + 1), p, false);
957
if (pos1 == (*(i - 1)).distanceTo2D(*i) && pos2 == 0.) {
958
nearestPos = seen;
959
minDist = cornerDist;
960
}
961
}
962
}
963
seen += (*i).distanceTo(*(i + 1));
964
}
965
return nearestPos;
966
}
967
968
969
Position
970
PositionVector::transformToVectorCoordinates(const Position& p, bool extend) const {
971
if (size() == 0) {
972
return Position::INVALID;
973
}
974
// @toDo this duplicates most of the code in nearest_offset_to_point2D. It should be refactored
975
if (extend) {
976
PositionVector extended = *this;
977
const double dist = 2 * distance2D(p);
978
extended.extrapolate(dist);
979
return extended.transformToVectorCoordinates(p) - Position(dist, 0);
980
}
981
double minDist = std::numeric_limits<double>::max();
982
double nearestPos = -1;
983
double seen = 0;
984
int sign = 1;
985
for (const_iterator i = begin(); i != end() - 1; i++) {
986
const double pos =
987
GeomHelper::nearest_offset_on_line_to_point2D(*i, *(i + 1), p, true);
988
const double dist = pos < 0 ? minDist : p.distanceTo2D(positionAtOffset(*i, *(i + 1), pos));
989
if (dist < minDist) {
990
nearestPos = pos + seen;
991
minDist = dist;
992
sign = isLeft(*i, *(i + 1), p) >= 0 ? -1 : 1;
993
}
994
if (i != begin() && pos == GeomHelper::INVALID_OFFSET) {
995
// even if perpendicular is set we still need to check the distance to the inner points
996
const double cornerDist = p.distanceTo2D(*i);
997
if (cornerDist < minDist) {
998
const double pos1 =
999
GeomHelper::nearest_offset_on_line_to_point2D(*(i - 1), *i, p, false);
1000
const double pos2 =
1001
GeomHelper::nearest_offset_on_line_to_point2D(*i, *(i + 1), p, false);
1002
if (pos1 == (*(i - 1)).distanceTo2D(*i) && pos2 == 0.) {
1003
nearestPos = seen;
1004
minDist = cornerDist;
1005
sign = isLeft(*(i - 1), *i, p) >= 0 ? -1 : 1;
1006
}
1007
}
1008
}
1009
seen += (*i).distanceTo2D(*(i + 1));
1010
}
1011
if (nearestPos != -1) {
1012
return Position(nearestPos, sign * minDist);
1013
} else {
1014
return Position::INVALID;
1015
}
1016
}
1017
1018
1019
int
1020
PositionVector::indexOfClosest(const Position& p, bool twoD) const {
1021
if (size() == 0) {
1022
return -1;
1023
}
1024
double minDist = std::numeric_limits<double>::max();
1025
double dist;
1026
int closest = 0;
1027
for (int i = 0; i < (int)size(); i++) {
1028
const Position& p2 = (*this)[i];
1029
dist = twoD ? p.distanceTo2D(p2) : p.distanceTo(p2);
1030
if (dist < minDist) {
1031
closest = i;
1032
minDist = dist;
1033
}
1034
}
1035
return closest;
1036
}
1037
1038
1039
int
1040
PositionVector::insertAtClosest(const Position& p, bool interpolateZ) {
1041
if (size() == 0) {
1042
return -1;
1043
}
1044
double minDist = std::numeric_limits<double>::max();
1045
int insertionIndex = 1;
1046
for (int i = 0; i < (int)size() - 1; i++) {
1047
const double length = GeomHelper::nearest_offset_on_line_to_point2D((*this)[i], (*this)[i + 1], p, false);
1048
const Position& outIntersection = PositionVector::positionAtOffset2D((*this)[i], (*this)[i + 1], length);
1049
const double dist = p.distanceTo2D(outIntersection);
1050
if (dist < minDist) {
1051
insertionIndex = i + 1;
1052
minDist = dist;
1053
}
1054
}
1055
// check if we have to adjust Position Z
1056
if (interpolateZ) {
1057
// obtain previous and next Z
1058
const double previousZ = (begin() + (insertionIndex - 1))->z();
1059
const double nextZ = (begin() + insertionIndex)->z();
1060
// insert new position using x and y of p, and the new z
1061
insert(begin() + insertionIndex, Position(p.x(), p.y(), ((previousZ + nextZ) / 2.0)));
1062
} else {
1063
insert(begin() + insertionIndex, p);
1064
}
1065
return insertionIndex;
1066
}
1067
1068
1069
int
1070
PositionVector::removeClosest(const Position& p) {
1071
if (size() == 0) {
1072
return -1;
1073
}
1074
double minDist = std::numeric_limits<double>::max();
1075
int removalIndex = 0;
1076
for (int i = 0; i < (int)size(); i++) {
1077
const double dist = p.distanceTo2D((*this)[i]);
1078
if (dist < minDist) {
1079
removalIndex = i;
1080
minDist = dist;
1081
}
1082
}
1083
erase(begin() + removalIndex);
1084
return removalIndex;
1085
}
1086
1087
1088
std::vector<double>
1089
PositionVector::intersectsAtLengths2D(const PositionVector& other) const {
1090
std::vector<double> ret;
1091
if (other.size() == 0) {
1092
return ret;
1093
}
1094
for (const_iterator i = other.begin(); i != other.end() - 1; i++) {
1095
std::vector<double> atSegment = intersectsAtLengths2D(*i, *(i + 1));
1096
copy(atSegment.begin(), atSegment.end(), back_inserter(ret));
1097
}
1098
return ret;
1099
}
1100
1101
1102
std::vector<double>
1103
PositionVector::intersectsAtLengths2D(const Position& lp1, const Position& lp2) const {
1104
std::vector<double> ret;
1105
if (size() == 0) {
1106
return ret;
1107
}
1108
double pos = 0;
1109
for (const_iterator i = begin(); i != end() - 1; i++) {
1110
const Position& p1 = *i;
1111
const Position& p2 = *(i + 1);
1112
double x, y, m;
1113
if (intersects(p1, p2, lp1, lp2, 0., &x, &y, &m)) {
1114
ret.push_back(Position(x, y).distanceTo2D(p1) + pos);
1115
}
1116
pos += p1.distanceTo2D(p2);
1117
}
1118
return ret;
1119
}
1120
1121
1122
void
1123
PositionVector::extrapolate(const double val, const bool onlyFirst, const bool onlyLast) {
1124
if (size() > 1) {
1125
Position& p1 = (*this)[0];
1126
Position& p2 = (*this)[1];
1127
const Position offset = (p2 - p1) * (val / p1.distanceTo(p2));
1128
if (!onlyLast) {
1129
p1.sub(offset);
1130
}
1131
if (!onlyFirst) {
1132
if (size() == 2) {
1133
p2.add(offset);
1134
} else {
1135
const Position& e1 = (*this)[-2];
1136
Position& e2 = (*this)[-1];
1137
e2.sub((e1 - e2) * (val / e1.distanceTo(e2)));
1138
}
1139
}
1140
}
1141
}
1142
1143
1144
void
1145
PositionVector::extrapolate2D(const double val, const bool onlyFirst) {
1146
if (size() > 1) {
1147
Position& p1 = (*this)[0];
1148
Position& p2 = (*this)[1];
1149
if (p1.distanceTo2D(p2) > 0) {
1150
const Position offset = (p2 - p1) * (val / p1.distanceTo2D(p2));
1151
p1.sub(offset);
1152
if (!onlyFirst) {
1153
if (size() == 2) {
1154
p2.add(offset);
1155
} else {
1156
const Position& e1 = (*this)[-2];
1157
Position& e2 = (*this)[-1];
1158
e2.sub((e1 - e2) * (val / e1.distanceTo2D(e2)));
1159
}
1160
}
1161
}
1162
}
1163
}
1164
1165
1166
PositionVector
1167
PositionVector::reverse() const {
1168
PositionVector ret;
1169
for (const_reverse_iterator i = rbegin(); i != rend(); i++) {
1170
ret.push_back(*i);
1171
}
1172
return ret;
1173
}
1174
1175
1176
Position
1177
PositionVector::sideOffset(const Position& beg, const Position& end, const double amount) {
1178
const double scale = amount / beg.distanceTo2D(end);
1179
return Position((beg.y() - end.y()) * scale, (end.x() - beg.x()) * scale);
1180
}
1181
1182
1183
void
1184
PositionVector::move2side(double amount, double maxExtension) {
1185
if (size() < 2) {
1186
return;
1187
}
1188
removeDoublePoints(POSITION_EPS, true);
1189
if (length2D() == 0 || amount == 0) {
1190
return;
1191
}
1192
PositionVector shape;
1193
std::vector<int> recheck;
1194
for (int i = 0; i < static_cast<int>(size()); i++) {
1195
if (i == 0) {
1196
const Position& from = (*this)[i];
1197
const Position& to = (*this)[i + 1];
1198
if (from != to) {
1199
shape.push_back(from - sideOffset(from, to, amount));
1200
#ifdef DEBUG_MOVE2SIDE
1201
if (gDebugFlag1) {
1202
std::cout << " " << i << "a=" << shape.back() << "\n";
1203
}
1204
#endif
1205
}
1206
} else if (i == static_cast<int>(size()) - 1) {
1207
const Position& from = (*this)[i - 1];
1208
const Position& to = (*this)[i];
1209
if (from != to) {
1210
shape.push_back(to - sideOffset(from, to, amount));
1211
#ifdef DEBUG_MOVE2SIDE
1212
if (gDebugFlag1) {
1213
std::cout << " " << i << "b=" << shape.back() << "\n";
1214
}
1215
#endif
1216
}
1217
} else {
1218
const Position& from = (*this)[i - 1];
1219
const Position& me = (*this)[i];
1220
const Position& to = (*this)[i + 1];
1221
PositionVector fromMe(from, me);
1222
fromMe.extrapolate2D(me.distanceTo2D(to));
1223
const double extrapolateDev = fromMe[1].distanceTo2D(to);
1224
if (fabs(extrapolateDev) < POSITION_EPS) {
1225
// parallel case, just shift the middle point
1226
shape.push_back(me - sideOffset(from, to, amount));
1227
#ifdef DEBUG_MOVE2SIDE
1228
if (gDebugFlag1) {
1229
std::cout << " " << i << "c=" << shape.back() << "\n";
1230
}
1231
#endif
1232
} else if (fabs(extrapolateDev - 2 * me.distanceTo2D(to)) < POSITION_EPS) {
1233
// counterparallel case, just shift the middle point
1234
PositionVector fromMe2(from, me);
1235
fromMe2.extrapolate2D(amount);
1236
shape.push_back(fromMe2[1]);
1237
#ifdef DEBUG_MOVE2SIDE
1238
if (gDebugFlag1) {
1239
std::cout << " " << i << "d=" << shape.back() << " " << i << "_from=" << from << " " << i << "_me=" << me << " " << i << "_to=" << to << "\n";
1240
}
1241
#endif
1242
} else {
1243
Position offsets = sideOffset(from, me, amount);
1244
Position offsets2 = sideOffset(me, to, amount);
1245
PositionVector l1(from - offsets, me - offsets);
1246
PositionVector l2(me - offsets2, to - offsets2);
1247
Position meNew = l1.intersectionPosition2D(l2[0], l2[1], maxExtension);
1248
if (meNew == Position::INVALID) {
1249
recheck.push_back(i);
1250
continue;
1251
}
1252
meNew = meNew + Position(0, 0, me.z());
1253
shape.push_back(meNew);
1254
#ifdef DEBUG_MOVE2SIDE
1255
if (gDebugFlag1) {
1256
std::cout << " " << i << "e=" << shape.back() << "\n";
1257
}
1258
#endif
1259
}
1260
// copy original z value
1261
shape.back().set(shape.back().x(), shape.back().y(), me.z());
1262
const double angle = localAngle(from, me, to);
1263
if (fabs(angle) > NUMERICAL_EPS) {
1264
const double length = from.distanceTo2D(me) + me.distanceTo2D(to);
1265
const double radius = length / angle;
1266
#ifdef DEBUG_MOVE2SIDE
1267
if (gDebugFlag1) {
1268
std::cout << " i=" << i << " a=" << RAD2DEG(angle) << " l=" << length << " r=" << radius << " t=" << amount * 1.8 << "\n";
1269
}
1270
#endif
1271
if ((radius < 0 && -radius < amount * 1.8) || fabs(RAD2DEG(angle)) > 170) {
1272
recheck.push_back(i);
1273
}
1274
}
1275
}
1276
}
1277
if (!recheck.empty()) {
1278
// try to adjust positions to avoid clipping
1279
shape = *this;
1280
for (int i = (int)recheck.size() - 1; i >= 0; i--) {
1281
shape.erase(shape.begin() + recheck[i]);
1282
}
1283
shape.move2side(amount, maxExtension);
1284
}
1285
*this = shape;
1286
}
1287
1288
1289
void
1290
PositionVector::move2sideCustom(std::vector<double> amount, double maxExtension) {
1291
if (size() < 2) {
1292
return;
1293
}
1294
if (length2D() == 0) {
1295
return;
1296
}
1297
if (size() != amount.size()) {
1298
throw InvalidArgument("Numer of offsets (" + toString(amount.size())
1299
+ ") does not match number of points (" + toString(size()) + ")");
1300
}
1301
PositionVector shape;
1302
for (int i = 0; i < static_cast<int>(size()); i++) {
1303
if (i == 0) {
1304
const Position& from = (*this)[i];
1305
const Position& to = (*this)[i + 1];
1306
if (from != to) {
1307
shape.push_back(from - sideOffset(from, to, amount[i]));
1308
}
1309
} else if (i == static_cast<int>(size()) - 1) {
1310
const Position& from = (*this)[i - 1];
1311
const Position& to = (*this)[i];
1312
if (from != to) {
1313
shape.push_back(to - sideOffset(from, to, amount[i]));
1314
}
1315
} else {
1316
const Position& from = (*this)[i - 1];
1317
const Position& me = (*this)[i];
1318
const Position& to = (*this)[i + 1];
1319
PositionVector fromMe(from, me);
1320
fromMe.extrapolate2D(me.distanceTo2D(to));
1321
const double extrapolateDev = fromMe[1].distanceTo2D(to);
1322
if (fabs(extrapolateDev) < POSITION_EPS) {
1323
// parallel case, just shift the middle point
1324
shape.push_back(me - sideOffset(from, to, amount[i]));
1325
} else if (fabs(extrapolateDev - 2 * me.distanceTo2D(to)) < POSITION_EPS) {
1326
// counterparallel case, just shift the middle point
1327
PositionVector fromMe2(from, me);
1328
fromMe2.extrapolate2D(amount[i]);
1329
shape.push_back(fromMe2[1]);
1330
} else {
1331
Position offsets = sideOffset(from, me, amount[i]);
1332
Position offsets2 = sideOffset(me, to, amount[i]);
1333
PositionVector l1(from - offsets, me - offsets);
1334
PositionVector l2(me - offsets2, to - offsets2);
1335
Position meNew = l1.intersectionPosition2D(l2[0], l2[1], maxExtension);
1336
if (meNew == Position::INVALID) {
1337
continue;
1338
}
1339
meNew = meNew + Position(0, 0, me.z());
1340
shape.push_back(meNew);
1341
}
1342
// copy original z value
1343
shape.back().set(shape.back().x(), shape.back().y(), me.z());
1344
}
1345
}
1346
*this = shape;
1347
}
1348
1349
double
1350
PositionVector::localAngle(const Position& from, const Position& pos, const Position& to) {
1351
return GeomHelper::angleDiff(from.angleTo2D(pos), pos.angleTo2D(to));
1352
}
1353
1354
double
1355
PositionVector::angleAt2D(int pos) const {
1356
if ((pos + 1) < (int)size()) {
1357
return (*this)[pos].angleTo2D((*this)[pos + 1]);
1358
}
1359
return INVALID_DOUBLE;
1360
}
1361
1362
1363
void
1364
PositionVector::openPolygon() {
1365
if ((size() > 1) && (front() == back())) {
1366
pop_back();
1367
}
1368
}
1369
1370
1371
void
1372
PositionVector::closePolygon() {
1373
if ((size() != 0) && ((*this)[0] != back())) {
1374
push_back((*this)[0]);
1375
}
1376
}
1377
1378
1379
std::vector<double>
1380
PositionVector::distances(const PositionVector& s, bool perpendicular) const {
1381
std::vector<double> ret;
1382
const_iterator i;
1383
for (i = begin(); i != end(); i++) {
1384
const double dist = s.distance2D(*i, perpendicular);
1385
if (dist != GeomHelper::INVALID_OFFSET) {
1386
ret.push_back(dist);
1387
}
1388
}
1389
for (i = s.begin(); i != s.end(); i++) {
1390
const double dist = distance2D(*i, perpendicular);
1391
if (dist != GeomHelper::INVALID_OFFSET) {
1392
ret.push_back(dist);
1393
}
1394
}
1395
return ret;
1396
}
1397
1398
1399
double
1400
PositionVector::distance2D(const Position& p, bool perpendicular) const {
1401
if (size() == 0) {
1402
return std::numeric_limits<double>::max();
1403
} else if (size() == 1) {
1404
return front().distanceTo2D(p);
1405
}
1406
const double nearestOffset = nearest_offset_to_point2D(p, perpendicular);
1407
if (nearestOffset == GeomHelper::INVALID_OFFSET) {
1408
return GeomHelper::INVALID_OFFSET;
1409
} else {
1410
return p.distanceTo2D(positionAtOffset2D(nearestOffset));
1411
}
1412
}
1413
1414
1415
void
1416
PositionVector::push_front(const Position& p) {
1417
if (empty()) {
1418
push_back(p);
1419
} else {
1420
insert(begin(), p);
1421
}
1422
}
1423
1424
1425
void
1426
PositionVector::pop_front() {
1427
if (empty()) {
1428
throw ProcessError("PositionVector is empty");
1429
} else {
1430
erase(begin());
1431
}
1432
}
1433
1434
1435
void
1436
PositionVector::push_back_noDoublePos(const Position& p) {
1437
if (size() == 0 || !p.almostSame(back())) {
1438
push_back(p);
1439
}
1440
}
1441
1442
1443
void
1444
PositionVector::push_front_noDoublePos(const Position& p) {
1445
if ((size() == 0) || !p.almostSame(front())) {
1446
push_front(p);
1447
}
1448
}
1449
1450
1451
void
1452
PositionVector::insert_noDoublePos(const std::vector<Position>::iterator& at, const Position& p) {
1453
if (at == begin()) {
1454
push_front_noDoublePos(p);
1455
} else if (at == end()) {
1456
push_back_noDoublePos(p);
1457
} else {
1458
if (!p.almostSame(*at) && !p.almostSame(*(at - 1))) {
1459
insert(at, p);
1460
}
1461
}
1462
}
1463
1464
1465
bool
1466
PositionVector::isClosed() const {
1467
return (size() >= 2) && ((*this)[0] == back());
1468
}
1469
1470
1471
bool
1472
PositionVector::isNAN() const {
1473
// iterate over all positions and check if is NAN
1474
for (auto i = begin(); i != end(); i++) {
1475
if (i->isNAN()) {
1476
return true;
1477
}
1478
}
1479
// all ok, then return false
1480
return false;
1481
}
1482
1483
1484
void
1485
PositionVector::round(int precision) {
1486
for (int i = 0; i < (int)size(); i++) {
1487
(*this)[i].round(precision);
1488
}
1489
}
1490
1491
1492
void
1493
PositionVector::removeDoublePoints(double minDist, bool assertLength, int beginOffset, int endOffset, bool resample) {
1494
int curSize = (int)size() - beginOffset - endOffset;
1495
if (curSize > 1) {
1496
iterator last = begin() + beginOffset;
1497
for (iterator i = last + 1; i != (end() - endOffset) && (!assertLength || curSize > 2);) {
1498
if (last->almostSame(*i, minDist)) {
1499
if (i + 1 == end() - endOffset) {
1500
// special case: keep the last point and remove the next-to-last
1501
if (resample && last > begin() && (last - 1)->distanceTo(*i) >= 2 * minDist) {
1502
// resample rather than remove point after a long segment
1503
const double shiftBack = minDist - last->distanceTo(*i);
1504
//if (gDebugFlag1) std::cout << " resample endOffset beforeLast=" << *(last - 1) << " last=" << *last << " i=" << *i;
1505
(*last) = positionAtOffset(*(last - 1), *last, (last - 1)->distanceTo(*last) - shiftBack);
1506
//if (gDebugFlag1) std::cout << " lastNew=" << *last;
1507
last = i;
1508
++i;
1509
} else {
1510
erase(last);
1511
i = end() - endOffset;
1512
}
1513
} else {
1514
if (resample && i + 1 != end() && last->distanceTo(*(i + 1)) >= 2 * minDist) {
1515
// resample rather than remove points before a long segment
1516
const double shiftForward = minDist - last->distanceTo(*i);
1517
//if (gDebugFlag1) std::cout << " resample last=" << *last << " i=" << *i << " next=" << *(i + 1);
1518
(*i) = positionAtOffset(*i, *(i + 1), shiftForward);
1519
//if (gDebugFlag1) std::cout << " iNew=" << *i << "\n";
1520
last = i;
1521
++i;
1522
} else {
1523
i = erase(i);
1524
}
1525
}
1526
curSize--;
1527
} else {
1528
last = i;
1529
++i;
1530
}
1531
}
1532
}
1533
}
1534
1535
1536
bool
1537
PositionVector::operator==(const PositionVector& v2) const {
1538
return static_cast<vp>(*this) == static_cast<vp>(v2);
1539
}
1540
1541
1542
bool
1543
PositionVector::operator!=(const PositionVector& v2) const {
1544
return static_cast<vp>(*this) != static_cast<vp>(v2);
1545
}
1546
1547
PositionVector
1548
PositionVector::operator-(const PositionVector& v2) const {
1549
if (length() != v2.length()) {
1550
WRITE_ERROR(TL("Trying to subtract PositionVectors of different lengths."));
1551
}
1552
PositionVector pv;
1553
auto i1 = begin();
1554
auto i2 = v2.begin();
1555
while (i1 != end()) {
1556
pv.add(*i1 - *i2);
1557
}
1558
return pv;
1559
}
1560
1561
PositionVector
1562
PositionVector::operator+(const PositionVector& v2) const {
1563
if (length() != v2.length()) {
1564
WRITE_ERROR(TL("Trying to add PositionVectors of different lengths."));
1565
}
1566
PositionVector pv;
1567
auto i1 = begin();
1568
auto i2 = v2.begin();
1569
while (i1 != end()) {
1570
pv.add(*i1 + *i2);
1571
}
1572
return pv;
1573
}
1574
1575
bool
1576
PositionVector::almostSame(const PositionVector& v2, double maxDiv) const {
1577
if (size() != v2.size()) {
1578
return false;
1579
}
1580
auto i2 = v2.begin();
1581
for (auto i1 = begin(); i1 != end(); i1++) {
1582
if (!i1->almostSame(*i2, maxDiv)) {
1583
return false;
1584
}
1585
i2++;
1586
}
1587
return true;
1588
}
1589
1590
bool
1591
PositionVector::hasElevation() const {
1592
if (size() < 2) {
1593
return false;
1594
}
1595
for (const_iterator i = begin(); i != end(); i++) {
1596
if ((*i).z() != 0) {
1597
return true;
1598
}
1599
}
1600
return false;
1601
}
1602
1603
1604
bool
1605
PositionVector::intersects(const Position& p11, const Position& p12, const Position& p21, const Position& p22, const double withinDist, double* x, double* y, double* mu) {
1606
const double eps = std::numeric_limits<double>::epsilon();
1607
const double denominator = (p22.y() - p21.y()) * (p12.x() - p11.x()) - (p22.x() - p21.x()) * (p12.y() - p11.y());
1608
const double numera = (p22.x() - p21.x()) * (p11.y() - p21.y()) - (p22.y() - p21.y()) * (p11.x() - p21.x());
1609
const double numerb = (p12.x() - p11.x()) * (p11.y() - p21.y()) - (p12.y() - p11.y()) * (p11.x() - p21.x());
1610
/* Are the lines coincident? */
1611
if (fabs(numera) < eps && fabs(numerb) < eps && fabs(denominator) < eps) {
1612
double a1;
1613
double a2;
1614
double a3;
1615
double a4;
1616
double a = -1e12;
1617
if (p11.x() != p12.x()) {
1618
a1 = p11.x() < p12.x() ? p11.x() : p12.x();
1619
a2 = p11.x() < p12.x() ? p12.x() : p11.x();
1620
a3 = p21.x() < p22.x() ? p21.x() : p22.x();
1621
a4 = p21.x() < p22.x() ? p22.x() : p21.x();
1622
} else {
1623
a1 = p11.y() < p12.y() ? p11.y() : p12.y();
1624
a2 = p11.y() < p12.y() ? p12.y() : p11.y();
1625
a3 = p21.y() < p22.y() ? p21.y() : p22.y();
1626
a4 = p21.y() < p22.y() ? p22.y() : p21.y();
1627
}
1628
if (a1 <= a3 && a3 <= a2) {
1629
if (a4 < a2) {
1630
a = (a3 + a4) / 2;
1631
} else {
1632
a = (a2 + a3) / 2;
1633
}
1634
}
1635
if (a3 <= a1 && a1 <= a4) {
1636
if (a2 < a4) {
1637
a = (a1 + a2) / 2;
1638
} else {
1639
a = (a1 + a4) / 2;
1640
}
1641
}
1642
if (a != -1e12) {
1643
if (x != nullptr) {
1644
if (p11.x() != p12.x()) {
1645
*mu = (a - p11.x()) / (p12.x() - p11.x());
1646
*x = a;
1647
*y = p11.y() + (*mu) * (p12.y() - p11.y());
1648
} else {
1649
*x = p11.x();
1650
*y = a;
1651
if (p12.y() == p11.y()) {
1652
*mu = 0;
1653
} else {
1654
*mu = (a - p11.y()) / (p12.y() - p11.y());
1655
}
1656
}
1657
}
1658
return true;
1659
}
1660
return false;
1661
}
1662
/* Are the lines parallel */
1663
if (fabs(denominator) < eps) {
1664
return false;
1665
}
1666
/* Is the intersection along the segments */
1667
double mua = numera / denominator;
1668
/* reduce rounding errors for lines ending in the same point */
1669
if (fabs(p12.x() - p22.x()) < eps && fabs(p12.y() - p22.y()) < eps) {
1670
mua = 1.;
1671
} else {
1672
const double offseta = withinDist / p11.distanceTo2D(p12);
1673
const double offsetb = withinDist / p21.distanceTo2D(p22);
1674
const double mub = numerb / denominator;
1675
if (mua < -offseta || mua > 1 + offseta || mub < -offsetb || mub > 1 + offsetb) {
1676
return false;
1677
}
1678
}
1679
if (x != nullptr) {
1680
*x = p11.x() + mua * (p12.x() - p11.x());
1681
*y = p11.y() + mua * (p12.y() - p11.y());
1682
*mu = mua;
1683
}
1684
return true;
1685
}
1686
1687
1688
void
1689
PositionVector::rotate2D(double angle) {
1690
const double s = sin(angle);
1691
const double c = cos(angle);
1692
for (int i = 0; i < (int)size(); i++) {
1693
const double x = (*this)[i].x();
1694
const double y = (*this)[i].y();
1695
const double z = (*this)[i].z();
1696
const double xnew = x * c - y * s;
1697
const double ynew = x * s + y * c;
1698
(*this)[i].set(xnew, ynew, z);
1699
}
1700
}
1701
1702
1703
void
1704
PositionVector::rotate2D(const Position& pos, double angle) {
1705
PositionVector aux = *this;
1706
aux.sub(pos);
1707
aux.rotate2D(angle);
1708
aux.add(pos);
1709
*this = aux;
1710
}
1711
1712
1713
void
1714
PositionVector::rotateAroundFirstElement2D(double angle) {
1715
if (size() > 1) {
1716
// translate position vector to (0,0), rotate, and traslate back again
1717
const Position offset = front();
1718
sub(offset);
1719
rotate2D(angle);
1720
add(offset);
1721
}
1722
}
1723
1724
1725
PositionVector
1726
PositionVector::simplified() const {
1727
PositionVector result = *this;
1728
bool changed = true;
1729
while (changed && result.size() > 3) {
1730
changed = false;
1731
for (int i = 0; i < (int)result.size(); i++) {
1732
const Position& p1 = result[i];
1733
const Position& p2 = result[(i + 2) % result.size()];
1734
const int middleIndex = (i + 1) % result.size();
1735
const Position& p0 = result[middleIndex];
1736
// https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Line_defined_by_two_points
1737
const double triangleArea2 = fabs((p2.y() - p1.y()) * p0.x() - (p2.x() - p1.x()) * p0.y() + p2.x() * p1.y() - p2.y() * p1.x());
1738
const double distIK = p1.distanceTo2D(p2);
1739
if (distIK > NUMERICAL_EPS && triangleArea2 / distIK < NUMERICAL_EPS) {
1740
changed = true;
1741
result.erase(result.begin() + middleIndex);
1742
break;
1743
}
1744
}
1745
}
1746
return result;
1747
}
1748
1749
1750
const PositionVector
1751
PositionVector::simplified2(const bool closed, const double eps) const {
1752
// this is a variation of the https://en.wikipedia.org/wiki/Visvalingam%E2%80%93Whyatt_algorithm
1753
// which uses the distance instead of the area
1754
// the benefits over the initial implementation are:
1755
// 3D support, no degenerate results for a sequence of small distances, keeping the longest part of a line
1756
// drawbacks: complexity of the code, speed
1757
if (size() < 3) {
1758
return *this;
1759
}
1760
auto calcScore = [&](const PositionVector & pv, int index) {
1761
if (!closed && (index == 0 || index == (int)pv.size() - 1)) {
1762
return eps + 1.;
1763
}
1764
const Position& p = pv[index];
1765
const Position& a = pv[(index + (int)pv.size() - 1) % pv.size()];
1766
const Position& b = pv[(index + 1) % pv.size()];
1767
const double distAB = a.distanceTo(b);
1768
if (distAB < MIN2(eps, NUMERICAL_EPS)) {
1769
// avoid division by 0 and degenerate cases due to very small baseline
1770
return (a.distanceTo(p) + b.distanceTo(p)) / 2.;
1771
}
1772
// https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Vector_formulation
1773
// calculating the distance of p to the line defined by a and b
1774
const Position dir = (b - a) / distAB;
1775
const double projectedLength = (a - p).dotProduct(dir);
1776
if (projectedLength <= -distAB) {
1777
return b.distanceTo(p);
1778
}
1779
if (projectedLength >= 0.) {
1780
return a.distanceTo(p);
1781
}
1782
const Position distVector = (a - p) - dir * projectedLength;
1783
return distVector.length();
1784
};
1785
std::vector<double> scores;
1786
double minScore = eps + 1.;
1787
int minIndex = -1;
1788
for (int i = 0; i < (int)size(); i++) {
1789
scores.push_back(calcScore(*this, i));
1790
if (scores.back() < minScore) {
1791
minScore = scores.back();
1792
minIndex = i;
1793
}
1794
}
1795
if (minScore >= eps) {
1796
return *this;
1797
}
1798
PositionVector result(*this);
1799
while (minScore < eps) {
1800
result.erase(result.begin() + minIndex);
1801
if (result.size() < 3) {
1802
break;
1803
}
1804
scores.erase(scores.begin() + minIndex);
1805
const int prevIndex = (minIndex + (int)result.size() - 1) % result.size();
1806
scores[prevIndex] = calcScore(result, prevIndex);
1807
scores[minIndex % result.size()] = calcScore(result, minIndex % result.size());
1808
minScore = eps + 1.;
1809
for (int i = 0; i < (int)result.size(); i++) {
1810
if (scores[i] < minScore) {
1811
minScore = scores[i];
1812
minIndex = i;
1813
}
1814
}
1815
}
1816
return result;
1817
}
1818
1819
1820
PositionVector
1821
PositionVector::getOrthogonal(const Position& p, double extend, bool before, double length, double deg) const {
1822
PositionVector result;
1823
PositionVector tmp = *this;
1824
tmp.extrapolate2D(extend);
1825
const double baseOffset = tmp.nearest_offset_to_point2D(p);
1826
if (baseOffset == GeomHelper::INVALID_OFFSET || size() < 2) {
1827
// fail
1828
return result;
1829
}
1830
Position base = tmp.positionAtOffset2D(baseOffset);
1831
const int closestIndex = tmp.indexOfClosest(base);
1832
const double closestOffset = tmp.offsetAtIndex2D(closestIndex);
1833
result.push_back(base);
1834
if (fabs(baseOffset - closestOffset) > NUMERICAL_EPS) {
1835
result.push_back(tmp[closestIndex]);
1836
if ((closestOffset < baseOffset) != before) {
1837
deg *= -1;
1838
}
1839
} else if (before) {
1840
// take the segment before closestIndex if possible
1841
if (closestIndex > 0) {
1842
result.push_back(tmp[closestIndex - 1]);
1843
} else {
1844
result.push_back(tmp[1]);
1845
deg *= -1;
1846
}
1847
} else {
1848
// take the segment after closestIndex if possible
1849
if (closestIndex < (int)size() - 1) {
1850
result.push_back(tmp[closestIndex + 1]);
1851
} else {
1852
result.push_back(tmp[-1]);
1853
deg *= -1;
1854
}
1855
}
1856
result = result.getSubpart2D(0, length);
1857
// rotate around base
1858
result.add(base * -1);
1859
result.rotate2D(DEG2RAD(deg));
1860
result.add(base);
1861
return result;
1862
}
1863
1864
1865
PositionVector
1866
PositionVector::smoothedZFront(double dist) const {
1867
PositionVector result = *this;
1868
if (size() == 0) {
1869
return result;
1870
}
1871
const double z0 = (*this)[0].z();
1872
// the z-delta of the first segment
1873
const double dz = (*this)[1].z() - z0;
1874
// if the shape only has 2 points it is as smooth as possible already
1875
if (size() > 2 && dz != 0) {
1876
dist = MIN2(dist, length2D());
1877
// check wether we need to insert a new point at dist
1878
Position pDist = positionAtOffset2D(dist);
1879
int iLast = indexOfClosest(pDist);
1880
// prevent close spacing to reduce impact of rounding errors in z-axis
1881
if (pDist.distanceTo2D((*this)[iLast]) > POSITION_EPS * 20) {
1882
iLast = result.insertAtClosest(pDist, false);
1883
}
1884
double dist2 = result.offsetAtIndex2D(iLast);
1885
const double dz2 = result[iLast].z() - z0;
1886
double seen = 0;
1887
for (int i = 1; i < iLast; ++i) {
1888
seen += result[i].distanceTo2D(result[i - 1]);
1889
result[i].set(result[i].x(), result[i].y(), z0 + dz2 * seen / dist2);
1890
}
1891
}
1892
return result;
1893
1894
}
1895
1896
1897
PositionVector
1898
PositionVector::interpolateZ(double zStart, double zEnd) const {
1899
PositionVector result = *this;
1900
if (size() == 0) {
1901
return result;
1902
}
1903
result[0].setz(zStart);
1904
result[-1].setz(zEnd);
1905
const double dz = zEnd - zStart;
1906
const double length = length2D();
1907
double seen = 0;
1908
for (int i = 1; i < (int)size() - 1; ++i) {
1909
seen += result[i].distanceTo2D(result[i - 1]);
1910
result[i].setz(zStart + dz * seen / length);
1911
}
1912
return result;
1913
}
1914
1915
1916
PositionVector
1917
PositionVector::resample(double maxLength, const bool adjustEnd) const {
1918
PositionVector result;
1919
if (maxLength == 0) {
1920
return result;
1921
}
1922
const double length = length2D();
1923
if (length < POSITION_EPS) {
1924
return result;
1925
}
1926
maxLength = length / ceil(length / maxLength);
1927
for (double pos = 0; pos <= length; pos += maxLength) {
1928
result.push_back(positionAtOffset2D(pos));
1929
}
1930
// check if we have to adjust last element
1931
if (adjustEnd && !result.empty() && (result.back() != back())) {
1932
// add last element
1933
result.push_back(back());
1934
}
1935
return result;
1936
}
1937
1938
1939
double
1940
PositionVector::offsetAtIndex2D(int index) const {
1941
if (index < 0 || index >= (int)size()) {
1942
return GeomHelper::INVALID_OFFSET;
1943
}
1944
double seen = 0;
1945
for (int i = 1; i <= index; ++i) {
1946
seen += (*this)[i].distanceTo2D((*this)[i - 1]);
1947
}
1948
return seen;
1949
}
1950
1951
1952
double
1953
PositionVector::getMaxGrade(double& maxJump) const {
1954
double result = 0;
1955
for (int i = 1; i < (int)size(); ++i) {
1956
const Position& p1 = (*this)[i - 1];
1957
const Position& p2 = (*this)[i];
1958
const double distZ = fabs(p1.z() - p2.z());
1959
const double dist2D = p1.distanceTo2D(p2);
1960
if (dist2D == 0) {
1961
maxJump = MAX2(maxJump, distZ);
1962
} else {
1963
result = MAX2(result, distZ / dist2D);
1964
}
1965
}
1966
return result;
1967
}
1968
1969
1970
double
1971
PositionVector::getMinZ() const {
1972
double minZ = std::numeric_limits<double>::max();
1973
for (const Position& i : *this) {
1974
minZ = MIN2(minZ, i.z());
1975
}
1976
return minZ;
1977
}
1978
1979
1980
PositionVector
1981
PositionVector::bezier(int numPoints) {
1982
// inspired by David F. Rogers
1983
assert(size() < 33);
1984
static const double fac[33] = {
1985
1.0, 1.0, 2.0, 6.0, 24.0, 120.0, 720.0, 5040.0, 40320.0, 362880.0, 3628800.0, 39916800.0, 479001600.0,
1986
6227020800.0, 87178291200.0, 1307674368000.0, 20922789888000.0, 355687428096000.0, 6402373705728000.0,
1987
121645100408832000.0, 2432902008176640000.0, 51090942171709440000.0, 1124000727777607680000.0,
1988
25852016738884976640000.0, 620448401733239439360000.0, 15511210043330985984000000.0,
1989
403291461126605635584000000.0, 10888869450418352160768000000.0, 304888344611713860501504000000.0,
1990
8841761993739701954543616000000.0, 265252859812191058636308480000000.0,
1991
8222838654177922817725562880000000.0, 263130836933693530167218012160000000.0
1992
};
1993
PositionVector ret;
1994
const int npts = (int)size();
1995
// calculate the points on the Bezier curve
1996
const double step = (double) 1.0 / (numPoints - 1);
1997
double t = 0.;
1998
Position prev;
1999
for (int i1 = 0; i1 < numPoints; i1++) {
2000
if ((1.0 - t) < 5e-6) {
2001
t = 1.0;
2002
}
2003
double x = 0., y = 0., z = 0.;
2004
for (int i = 0; i < npts; i++) {
2005
const double ti = (i == 0) ? 1.0 : pow(t, i);
2006
const double tni = (npts == i + 1) ? 1.0 : pow(1 - t, npts - i - 1);
2007
const double basis = fac[npts - 1] / (fac[i] * fac[npts - 1 - i]) * ti * tni;
2008
x += basis * at(i).x();
2009
y += basis * at(i).y();
2010
z += basis * at(i).z();
2011
}
2012
t += step;
2013
Position current(x, y, z);
2014
if (prev != current && !std::isnan(x) && !std::isnan(y) && !std::isnan(z)) {
2015
ret.push_back(current);
2016
}
2017
prev = current;
2018
}
2019
return ret;
2020
}
2021
2022
2023
bool PositionVector::isClockwiseOriented() {
2024
// The test is based on the computation of a signed area enclosed by the polygon.
2025
// If the polygon is in the upper (resp. the lower) half-plane and the area is
2026
// negatively (resp. positively) signed, then the polygon is CW oriented. In case
2027
// the polygon has points with both positive and negative y-coordinates, we translate
2028
// the polygon to apply the above simple area-based test.
2029
double area = 0.0;
2030
const double y_min = std::min_element(begin(), end(), [](Position p1, Position p2) {
2031
return p1.y() < p2.y();
2032
})->y();
2033
const double gap = y_min > 0.0 ? 0.0 : y_min;
2034
add(0., gap, 0.);
2035
const int last = (int)size() - 1;
2036
for (int i = 0; i < last; i++) {
2037
const Position& firstPoint = at(i);
2038
const Position& secondPoint = at(i + 1);
2039
area += (secondPoint.x() - firstPoint.x()) / (secondPoint.y() + firstPoint.y()) / 2.0;
2040
}
2041
area += (at(0).x() - at(last).x()) / (at(0).y() + at(last).y()) / 2.0;
2042
add(0., -gap, 0.);
2043
return area < 0.0;
2044
}
2045
2046
2047
/****************************************************************************/
2048
2049