OpenCPN Partial API docs
Loading...
Searching...
No Matches
track.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: Navigation Utility Functions
5 * Authors: David Register
6 * Sean D'Epagnier
7 * Project: OpenCPN
8 * Purpose: Navigation Utility Functions
9 * Author: David Register
10 *
11 ***************************************************************************
12 * Copyright (C) 2016 by David S. Register *
13 * *
14 * This program is free software; you can redistribute it and/or modify *
15 * it under the terms of the GNU General Public License as published by *
16 * the Free Software Foundation; either version 2 of the License, or *
17 * (at your option) any later version. *
18 * *
19 * This program is distributed in the hope that it will be useful, *
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
22 * GNU General Public License for more details. *
23 * *
24 * You should have received a copy of the GNU General Public License *
25 * along with this program; if not, write to the *
26 * Free Software Foundation, Inc., *
27 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
28 **************************************************************************/
29
30/* Tracks are broken into SubTracks to allow for efficient rendering and
31 selection on tracks with thousands or millions of track points
32
33 Each level of subtracks has exactly half the number of the previous level
34 forming a binary tree of subtracks.
35 The 0th level contains n-1 subtracks where n is the number of track points
36
37For example, a track with 5 points:
38
39Subtracks[2] 0
40 __/ \__
41 / \
42Subtracks[1] 0 1
43 / \ / \
44Subtracks[0] 0 1 2 3
45 / \ / \ / \ / \
46TrackPoints 0 1 2 3 5
47
48
49The BoundingBox for Subtracks[2][0] will include the entire track and is the
50starting point for assembling the track.
51
52Subtracks[1][0] is from 0 to 2
53Subtracks[1][1] is from 2 to 5
54Subtracks[0][2] is from 2 to 3
55
56The scale factor in Subtracks[2] will determine if it's allowed to just
57draw a simple line segment from 0 to 5, or if we need to recurse to find
58more detail.
59
60At large scale factors, a long track will mostly be off-screen, so
61the bounding box tests quickly eliminate the invisible sections.
62
63At small scale factors, the scale test allows representing a section
64of track using a single line segment greatly reducing the number of
65segments rendered. The scale is set so the difference is less than 1 pixel
66and mostly impossible to notice.
67
68In practice, I never exceed 170 segments in all cases assembling a real track
69with over 86,000 segments. If the track is particularly not-straight, and
70the size of the screen particularly large (lots of pixels) the number
71of segments will be higher, though it should be managable with tracks with
72millions of points.
73*/
74
75#include <memory>
76#include <string>
77#include <vector>
78
79#include <wx/colour.h>
80#include <wx/datetime.h>
81#include <wx/event.h>
82#include <wx/jsonval.h>
83#include <wx/pen.h>
84#include <wx/progdlg.h>
85#include <wx/string.h>
86#include <wx/utils.h>
87
88#include "model/track.h"
89
90#include "model/config_vars.h"
91#include "model/georef.h"
92#include "model/json_event.h"
93#include "model/nav_object_database.h"
94#include "model/navutil_base.h"
95#include "model/own_ship.h"
96#include "model/routeman.h"
97#include "model/select.h"
98
99std::vector<Track *> g_TrackList;
100
101#if defined(__UNIX__) && \
102 !defined(__WXOSX__) // high resolution stopwatch for profiling
103class OCPNStopWatch {
104public:
105 OCPNStopWatch() { Reset(); }
106 void Reset() { clock_gettime(CLOCK_REALTIME, &tp); }
107
108 double GetTime() {
109 timespec tp_end;
110 clock_gettime(CLOCK_REALTIME, &tp_end);
111 return (tp_end.tv_sec - tp.tv_sec) * 1.e3 +
112 (tp_end.tv_nsec - tp.tv_nsec) / 1.e6;
113 }
114
115private:
116 timespec tp;
117};
118#endif
119
120TrackPoint::TrackPoint(double lat, double lon, wxString ts)
121 : m_lat(lat), m_lon(lon), m_GPXTrkSegNo(1) {
122 SetCreateTime(ts);
123}
124
125TrackPoint::TrackPoint(double lat, double lon, wxDateTime dt)
126 : m_lat(lat), m_lon(lon), m_GPXTrkSegNo(1) {
127 SetCreateTime(dt);
128}
129
130// Copy Constructor
131TrackPoint::TrackPoint(TrackPoint *orig)
132 : m_lat(orig->m_lat), m_lon(orig->m_lon), m_GPXTrkSegNo(1) {
133 SetCreateTime(orig->GetCreateTime());
134}
135
136TrackPoint::~TrackPoint() {}
137
138wxDateTime TrackPoint::GetCreateTime() {
139 wxDateTime CreateTimeX;
140 ParseGPXDateTime(CreateTimeX, wxString(m_stimestring.c_str()));
141 return CreateTimeX;
142}
143
144void TrackPoint::SetCreateTime(wxDateTime dt) {
145 wxString ts;
146 if (dt.IsValid())
147 ts = dt.FormatISODate()
148 .Append(_T("T"))
149 .Append(dt.FormatISOTime())
150 .Append(_T("Z"));
151
152 SetCreateTime(ts);
153}
154
155void TrackPoint::SetCreateTime(wxString ts) {
156 if (ts.Length()) {
157 m_stimestring = ts.mb_str();
158 } else
159 m_stimestring = "";
160}
161
162//---------------------------------------------------------------------------------
163// Track Implementation
164//---------------------------------------------------------------------------------
165
166double _distance2(vector2D &a, vector2D &b) {
167 return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
168}
169double _distance(vector2D &a, vector2D &b) { return sqrt(_distance2(a, b)); }
170
171Track::Track() {
172 m_bVisible = true;
173 m_bListed = true;
174
175 m_width = WIDTH_UNDEFINED;
176 m_style = wxPENSTYLE_INVALID;
177
178 m_GUID = pWayPointMan->CreateGUID(NULL);
179 m_bIsInLayer = false;
180 m_btemp = false;
181
182 m_HyperlinkList = new HyperlinkList;
183 m_HighlightedTrackPoint = -1;
184}
185
186Track::~Track(void) {
187 for (size_t i = 0; i < TrackPoints.size(); i++) delete TrackPoints[i];
188
189 delete m_HyperlinkList;
190}
191
192#define TIMER_TRACK1 778
193
194BEGIN_EVENT_TABLE(ActiveTrack, wxEvtHandler)
195EVT_TIMER(TIMER_TRACK1, ActiveTrack::OnTimerTrack)
196END_EVENT_TABLE()
197
199 m_TimerTrack.SetOwner(this, TIMER_TRACK1);
200 m_TimerTrack.Stop();
201 m_bRunning = false;
202
203 SetPrecision(g_nTrackPrecision);
204
205 m_prev_time = wxInvalidDateTime;
206 m_lastStoredTP = NULL;
207
208 wxDateTime now = wxDateTime::Now();
209 // m_ConfigRouteNum = now.GetTicks(); // a unique number....
210 trackPointState = firstPoint;
211 m_lastStoredTP = NULL;
212 m_removeTP = NULL;
213 m_fixedTP = NULL;
214 m_track_run = 0;
215 m_CurrentTrackSeg = 0;
216 m_prev_dist = 999.0;
217}
218
219ActiveTrack::~ActiveTrack() { Stop(); }
220
221void ActiveTrack::SetPrecision(int prec) {
222 m_nPrecision = prec;
223 switch (m_nPrecision) {
224 case 0: { // Low
225 m_allowedMaxAngle = 10;
226 m_allowedMaxXTE = 0.008;
227 m_TrackTimerSec = 8;
228 m_minTrackpoint_delta = .004;
229 break;
230 }
231 case 1: { // Medium
232 m_allowedMaxAngle = 10;
233 m_allowedMaxXTE = 0.004;
234 m_TrackTimerSec = 4;
235 m_minTrackpoint_delta = .002;
236 break;
237 }
238 case 2: { // High
239 m_allowedMaxAngle = 10;
240 m_allowedMaxXTE = 0.0015;
241 m_TrackTimerSec = 2;
242 m_minTrackpoint_delta = .001;
243 break;
244 }
245 }
246}
247
248void ActiveTrack::Start(void) {
249 if (!m_bRunning) {
250 AddPointNow(true); // Add initial point
251 m_TimerTrack.Start(1000, wxTIMER_CONTINUOUS);
252 m_bRunning = true;
253 }
254}
255
256void ActiveTrack::Stop(bool do_add_point) {
257 if (m_bRunning) {
258 if (do_add_point)
259 AddPointNow(true); // Force add last point
260 else {
261 double delta = 0.0;
262 if (m_lastStoredTP)
263 delta = DistGreatCircle(gLat, gLon, m_lastStoredTP->m_lat,
264 m_lastStoredTP->m_lon);
265
266 if (delta > m_minTrackpoint_delta) AddPointNow(true); // Add last point
267 }
268 }
269
270 m_TimerTrack.Stop();
271 m_bRunning = false;
272 m_track_run = 0;
273}
274
275Track *ActiveTrack::DoExtendDaily() {
276 Track *pExtendTrack = NULL;
277 TrackPoint *pExtendPoint = NULL;
278
279 TrackPoint *pLastPoint = GetPoint(0);
280 if (!pLastPoint->GetCreateTime().IsValid()) return NULL;
281
282 for (Track *ptrack : g_TrackList) {
283 if (!ptrack->m_bIsInLayer && ptrack->m_GUID != m_GUID) {
284 TrackPoint *track_node = ptrack->GetLastPoint();
285 if (!track_node->GetCreateTime().IsValid())
286 continue; // Skip this bad track
287 if (track_node->GetCreateTime() <= pLastPoint->GetCreateTime()) {
288 if (!pExtendPoint ||
289 track_node->GetCreateTime() > pExtendPoint->GetCreateTime()) {
290 pExtendPoint = track_node;
291 pExtendTrack = ptrack;
292 }
293 }
294 }
295 }
296 if (pExtendTrack && pExtendTrack->GetPoint(0)
297 ->GetCreateTime()
298 .FromTimezone(wxDateTime::GMT0)
299 .IsSameDate(pLastPoint->GetCreateTime().FromTimezone(
300 wxDateTime::GMT0))) {
301 int begin = 1;
302 if (pLastPoint->GetCreateTime() == pExtendPoint->GetCreateTime()) begin = 2;
303 pSelect->DeleteAllSelectableTrackSegments(pExtendTrack);
304 wxString suffix = _T("");
305 if (GetName().IsNull()) {
306 suffix = pExtendTrack->GetName();
307 if (suffix.IsNull()) suffix = wxDateTime::Today().FormatISODate();
308 }
309 pExtendTrack->Clone(this, begin, GetnPoints(), suffix);
310 pSelect->AddAllSelectableTrackSegments(pExtendTrack);
311 pSelect->DeleteAllSelectableTrackSegments(this);
312
313 return pExtendTrack;
314 } else {
315 if (GetName().IsNull()) SetName(wxDateTime::Today().FormatISODate());
316 return NULL;
317 }
318}
319
320void Track::Clone(Track *psourcetrack, int start_nPoint, int end_nPoint,
321 const wxString &suffix) {
322 if (psourcetrack->m_bIsInLayer) return;
323
324 m_TrackNameString = psourcetrack->m_TrackNameString + suffix;
325 m_TrackStartString = psourcetrack->m_TrackStartString;
326 m_TrackEndString = psourcetrack->m_TrackEndString;
327
328 bool b_splitting = GetnPoints() == 0;
329
330 int startTrkSegNo;
331 if (b_splitting) {
332 startTrkSegNo = psourcetrack->GetPoint(start_nPoint)->m_GPXTrkSegNo;
333 } else {
334 startTrkSegNo = GetLastPoint()->m_GPXTrkSegNo;
335 }
336 int i;
337 for (i = start_nPoint; i <= end_nPoint; i++) {
338 TrackPoint *psourcepoint = psourcetrack->GetPoint(i);
339 if (psourcepoint) {
340 TrackPoint *ptargetpoint = new TrackPoint(psourcepoint);
341
342 AddPoint(ptargetpoint);
343 }
344 }
345}
346
347void ActiveTrack::AdjustCurrentTrackPoint(TrackPoint *prototype) {
348 if (prototype) {
349 *m_lastStoredTP = *prototype;
350 m_prev_time = prototype->GetCreateTime().FromUTC();
351 }
352}
353
354void ActiveTrack::OnTimerTrack(wxTimerEvent &event) {
355 m_TimerTrack.Stop();
356 m_track_run++;
357
358 if (m_lastStoredTP)
359 m_prev_dist = DistGreatCircle(gLat, gLon, m_lastStoredTP->m_lat,
360 m_lastStoredTP->m_lon);
361 else
362 m_prev_dist = 999.0;
363
364 bool b_addpoint = false;
365
366 if ((m_TrackTimerSec > 0.) && ((double)m_track_run >= m_TrackTimerSec) &&
367 (m_prev_dist > m_minTrackpoint_delta)) {
368 b_addpoint = true;
369 m_track_run = 0;
370 }
371
372 if (b_addpoint)
373 AddPointNow();
374 else // continuously update track beginning point timestamp if no movement.
375 if ((trackPointState == firstPoint) && !g_bTrackDaily) {
376 wxDateTime now = wxDateTime::Now();
377 if (TrackPoints.empty()) TrackPoints.front()->SetCreateTime(now.ToUTC());
378 }
379
380 m_TimerTrack.Start(1000, wxTIMER_CONTINUOUS);
381}
382
383void ActiveTrack::AddPointNow(bool do_add_point) {
384 wxDateTime now = wxDateTime::Now();
385
386 if (m_prev_dist < 0.0005) // avoid zero length segs
387 if (!do_add_point) return;
388
389 if (m_prev_time.IsValid())
390 if (m_prev_time == now) // avoid zero time segs
391 if (!do_add_point) return;
392
393 vector2D gpsPoint(gLon, gLat);
394
395 // Check if gps point is not too far from the last point
396 // which, if it is the case, means that there is probably a GPS bug on the two
397 // positions... So, it is better not to add this false new point.
398
399 // Calculate the distance between two points of the track based on georef lib
400 if (g_trackFilterMax) {
401 if (trackPointState == potentialPoint) {
402 double distToLastGpsPoint = DistLoxodrome(
403 m_lastStoredTP->m_lat, m_lastStoredTP->m_lon, gLat, gLon);
404 if (distToLastGpsPoint > g_trackFilterMax) return;
405 }
406 }
407
408 // The dynamic interval algorithm will gather all track points in a queue,
409 // and analyze the cross track errors for each point before actually adding
410 // a point to the track.
411
412 switch (trackPointState) {
413 case firstPoint: {
414 TrackPoint *pTrackPoint = AddNewPoint(gpsPoint, now.ToUTC());
415 m_lastStoredTP = pTrackPoint;
416 trackPointState = secondPoint;
417 do_add_point = false;
418 break;
419 }
420 case secondPoint: {
421 vector2D pPoint(gLon, gLat);
422 skipPoints.push_back(pPoint);
423 skipTimes.push_back(now.ToUTC());
424 trackPointState = potentialPoint;
425 break;
426 }
427 case potentialPoint: {
428 if (gpsPoint == skipPoints[skipPoints.size() - 1]) break;
429
430 unsigned int xteMaxIndex = 0;
431 double xteMax = 0;
432
433 // Scan points skipped so far and see if anyone has XTE over the
434 // threshold.
435 for (unsigned int i = 0; i < skipPoints.size(); i++) {
436 double xte = GetXTE(m_lastStoredTP->m_lat, m_lastStoredTP->m_lon, gLat,
437 gLon, skipPoints[i].lat, skipPoints[i].lon);
438 if (xte > xteMax) {
439 xteMax = xte;
440 xteMaxIndex = i;
441 }
442 }
443 if (xteMax > m_allowedMaxXTE) {
444 TrackPoint *pTrackPoint =
445 AddNewPoint(skipPoints[xteMaxIndex], skipTimes[xteMaxIndex]);
446 pSelect->AddSelectableTrackSegment(
447 m_lastStoredTP->m_lat, m_lastStoredTP->m_lon, pTrackPoint->m_lat,
448 pTrackPoint->m_lon, m_lastStoredTP, pTrackPoint, this);
449
450 m_prevFixedTP = m_fixedTP;
451 m_fixedTP = m_removeTP;
452 m_removeTP = m_lastStoredTP;
453 m_lastStoredTP = pTrackPoint;
454 for (unsigned int i = 0; i <= xteMaxIndex; i++) {
455 skipPoints.pop_front();
456 skipTimes.pop_front();
457 }
458
459 // Now back up and see if we just made 3 points in a straight line and
460 // the middle one (the next to last) point can possibly be eliminated.
461 // Here we reduce the allowed XTE as a function of leg length. (Half the
462 // XTE for very short legs).
463 if (GetnPoints() > 2) {
464 double dist =
465 DistGreatCircle(m_fixedTP->m_lat, m_fixedTP->m_lon,
466 m_lastStoredTP->m_lat, m_lastStoredTP->m_lon);
467 double xte = GetXTE(m_fixedTP, m_lastStoredTP, m_removeTP);
468 if (xte < m_allowedMaxXTE / wxMax(1.0, 2.0 - dist * 2.0)) {
469 TrackPoints.pop_back();
470 TrackPoints.pop_back();
471 TrackPoints.push_back(m_lastStoredTP);
472 pSelect->DeletePointSelectableTrackSegments(m_removeTP);
473 pSelect->AddSelectableTrackSegment(
474 m_fixedTP->m_lat, m_fixedTP->m_lon, m_lastStoredTP->m_lat,
475 m_lastStoredTP->m_lon, m_fixedTP, m_lastStoredTP, this);
476 delete m_removeTP;
477 m_removeTP = m_fixedTP;
478 m_fixedTP = m_prevFixedTP;
479 }
480 }
481 }
482
483 skipPoints.push_back(gpsPoint);
484 skipTimes.push_back(now.ToUTC());
485 break;
486 }
487 }
488
489 // Check if this is the last point of the track.
490 if (do_add_point) {
491 TrackPoint *pTrackPoint = AddNewPoint(gpsPoint, now.ToUTC());
492 pSelect->AddSelectableTrackSegment(
493 m_lastStoredTP->m_lat, m_lastStoredTP->m_lon, pTrackPoint->m_lat,
494 pTrackPoint->m_lon, m_lastStoredTP, pTrackPoint, this);
495 }
496
497 m_prev_time = now;
498}
499
500void Track::ClearHighlights() { m_HighlightedTrackPoint = -1; }
501
502TrackPoint *Track::GetPoint(int nWhichPoint) {
503 if (nWhichPoint < (int)TrackPoints.size())
504 return TrackPoints[nWhichPoint];
505 else
506 return NULL;
507}
508
509TrackPoint *Track::GetLastPoint() {
510 if (TrackPoints.empty()) return NULL;
511
512 return TrackPoints.back();
513}
514
515static double heading_diff(double x) {
516 if (x > 180) return 360 - x;
517 if (x < -180) return -360 + x;
518 return x;
519}
520
521/* Computes the scale factor when these particular segments
522 essentially are smaller than 1 pixel, This is assuming
523 a simplistic flat projection, it might be useful to
524 add a mercator or other term, but this works in practice */
525double Track::ComputeScale(int left, int right) {
526 const double z = WGS84_semimajor_axis_meters * mercator_k0;
527 const double mult = DEGREE * z;
528 // could multiply by a smaller factor to get
529 // better performance with loss of rendering track accuracy
530
531 double max_dist = 0;
532 double lata = TrackPoints[left]->m_lat, lona = TrackPoints[left]->m_lon;
533 double latb = TrackPoints[right]->m_lat, lonb = TrackPoints[right]->m_lon;
534
535 double bx = heading_diff(lonb - lona), by = latb - lata;
536
537 double lengthSquared = bx * bx + by * by;
538
539 // avoid this calculation for large distances... slight optimization
540 // at building with expense rendering zoomed out. is it needed?
541 if (lengthSquared > 3) return INFINITY;
542
543 if (lengthSquared == 0.0) {
544 for (int i = left + 1; i < right; i++) {
545 double lat = TrackPoints[i]->m_lat, lon = TrackPoints[i]->m_lon;
546 // v == w case
547 double vx = heading_diff(lon - lona);
548 double vy = lat - lata;
549 double dist = vx * vx + vy * vy;
550
551 if (dist > max_dist) max_dist = dist;
552 }
553 } else {
554 double invLengthSquared = 1 / lengthSquared;
555 for (int i = left + 1; i < right; i++) {
556 double lat = TrackPoints[i]->m_lat, lon = TrackPoints[i]->m_lon;
557
558 double vx = heading_diff(lon - lona);
559 double vy = lat - lata;
560 double t = (vx * bx + vy * by) * invLengthSquared;
561 double dist;
562
563 if (t < 0.0)
564 dist = vx * vx + vy * vy; // Beyond the 'v' end of the segment
565 else if (t > 1.0) {
566 double wx = heading_diff(lona - lon);
567 double wy = lata - lat;
568 dist = wx * wx + wy * wy; // Beyond the 'w' end of the segment
569 } else {
570 double projx = vx - t * bx; // Projection falls on the segment
571 double projy = vy - t * by;
572 dist = projx * projx + projy * projy;
573 }
574
575 if (dist > max_dist) max_dist = dist;
576 }
577 }
578
579 return max_dist * mult * mult;
580}
581
582/* Add a point to a track, should be iterated
583 on to build up a track from data. If a track
584 is being slowing enlarged, see AddPointFinalized below */
585void Track::AddPoint(TrackPoint *pNewPoint) {
586 TrackPoints.push_back(pNewPoint);
587 SubTracks.clear(); // invalidate subtracks
588}
589
590/* ensures the SubTracks are valid for assembly use */
591void Track::Finalize() {
592 if (SubTracks.size()) // subtracks already computed
593 return;
594
595 // OCPNStopWatch sw1;
596
597 int n = TrackPoints.size() - 1;
598 int level = 0;
599 while (n > 0) {
600 std::vector<SubTrack> new_level;
601 new_level.resize(n);
602 if (level == 0)
603 for (int i = 0; i < n; i++) {
604 new_level[i].m_box.SetFromSegment(
605 TrackPoints[i]->m_lat, TrackPoints[i]->m_lon,
606 TrackPoints[i + 1]->m_lat, TrackPoints[i + 1]->m_lon);
607 new_level[i].m_scale = 0;
608 }
609 else {
610 for (int i = 0; i < n; i++) {
611 int p = i << 1;
612 new_level[i].m_box = SubTracks[level - 1][p].m_box;
613 if (p + 1 < (int)SubTracks[level - 1].size())
614 new_level[i].m_box.Expand(SubTracks[level - 1][p + 1].m_box);
615
616 int left = i << level;
617 int right = wxMin(left + (1 << level), TrackPoints.size() - 1);
618 new_level[i].m_scale = ComputeScale(left, right);
619 }
620 }
621 SubTracks.push_back(new_level);
622
623 if (n > 1 && n & 1) n++;
624 n >>= 1;
625 level++;
626 }
627 // if(TrackPoints.size() > 100)
628 // printf("fin time %f %d\n", sw1.GetTime(), (int)TrackPoints.size());
629}
630
631// recursive subtracks fixer for appending a single point
632void Track::InsertSubTracks(LLBBox &box, int level, int pos) {
633 if (level == (int)SubTracks.size()) {
634 std::vector<SubTrack> new_level;
635 if (level > 0) box.Expand(SubTracks[level - 1][0].m_box);
636 new_level.push_back(SubTrack());
637 new_level[pos].m_box = box;
638 SubTracks.push_back(new_level);
639 } else if (pos < (int)SubTracks[level].size())
640 SubTracks[level][pos].m_box.Expand(box);
641 else {
642 SubTracks[level].push_back(SubTrack());
643 SubTracks[level][pos].m_box = box;
644 }
645
646 if (level == 0)
647 SubTracks[level][pos].m_scale = 0;
648 else {
649 int left = pos << level;
650 int right = wxMin(left + (1 << level), TrackPoints.size() - 1);
651 SubTracks[level][pos].m_scale = ComputeScale(left, right);
652 }
653
654 if (pos > 0) InsertSubTracks(box, level + 1, pos >> 1);
655}
656
657/* This function adds a new point ensuring the resulting track is finalized
658 The runtime of this routine is O(log(n)) which is an improvment over
659 blowing away the subtracks and calling Finalize which is O(n),
660 but should not be used for building a large track O(n log(n)) which
661 _is_ worse than blowing the subtracks and calling Finalize.
662*/
663void Track::AddPointFinalized(TrackPoint *pNewPoint) {
664 TrackPoints.push_back(pNewPoint);
665
666 int pos = TrackPoints.size() - 1;
667
668 if (pos > 0) {
669 LLBBox box;
670 box.SetFromSegment(TrackPoints[pos - 1]->m_lat, TrackPoints[pos - 1]->m_lon,
671 TrackPoints[pos]->m_lat, TrackPoints[pos]->m_lon);
672 InsertSubTracks(box, 0, pos - 1);
673 }
674}
675
676TrackPoint *Track::AddNewPoint(vector2D point, wxDateTime time) {
677 TrackPoint *tPoint = new TrackPoint(point.lat, point.lon, time);
678
679 AddPointFinalized(tPoint);
680
681 NavObjectChanges::getInstance()->AddNewTrackPoint(
682 tPoint, m_GUID); // This will update the "changes" file only
683
684 // send a wxJson message to all plugins
685 wxJSONValue v;
686 v["lat"] = tPoint->m_lat;
687 v["lon"] = tPoint->m_lon;
688 v["Track_ID"] = m_GUID;
689 std::string msg_id("OCPN_TRK_POINT_ADDED");
690 JsonEvent::getInstance().Notify(msg_id, std::make_shared<wxJSONValue>(v));
691
692 return tPoint;
693}
694
695void Track::DouglasPeuckerReducer(std::vector<TrackPoint *> &list,
696 std::vector<bool> &keeplist, int from, int to,
697 double delta) {
698 keeplist[from] = true;
699 keeplist[to] = true;
700
701 int maxdistIndex = -1;
702 double maxdist = 0;
703
704 for (int i = from + 1; i < to; i++) {
705 double dist = 1852.0 * GetXTE(list[from], list[to], list[i]);
706
707 if (dist > maxdist) {
708 maxdist = dist;
709 maxdistIndex = i;
710 }
711 }
712
713 if (maxdist > delta) {
714 DouglasPeuckerReducer(list, keeplist, from, maxdistIndex, delta);
715 DouglasPeuckerReducer(list, keeplist, maxdistIndex, to, delta);
716 }
717}
718
719double Track::Length() {
720 TrackPoint *l = NULL;
721 double total = 0.0;
722 for (size_t i = 0; i < TrackPoints.size(); i++) {
723 TrackPoint *t = TrackPoints[i];
724 if (l) {
725 const double offsetLat = 1e-6;
726 const double deltaLat = l->m_lat - t->m_lat;
727 if (fabs(deltaLat) > offsetLat)
728 total += DistGreatCircle(l->m_lat, l->m_lon, t->m_lat, t->m_lon);
729 else
730 total += DistGreatCircle(l->m_lat + copysign(offsetLat, deltaLat),
731 l->m_lon, t->m_lat, t->m_lon);
732 }
733 l = t;
734 }
735
736 return total;
737}
738
739int Track::Simplify(double maxDelta) {
740 int reduction = 0;
741
742 std::vector<TrackPoint *> pointlist;
743 std::vector<bool> keeplist;
744
745 ::wxBeginBusyCursor();
746
747 for (size_t i = 0; i < TrackPoints.size(); i++) {
748 TrackPoint *trackpoint = TrackPoints[i];
749
750 pointlist.push_back(trackpoint);
751 keeplist.push_back(false);
752 }
753
754 DouglasPeuckerReducer(pointlist, keeplist, 0, pointlist.size() - 1, maxDelta);
755
756 pSelect->DeleteAllSelectableTrackSegments(this);
757 SubTracks.clear();
758 TrackPoints.clear();
759
760 for (size_t i = 0; i < pointlist.size(); i++) {
761 if (keeplist[i])
762 TrackPoints.push_back(pointlist[i]);
763 else {
764 delete pointlist[i];
765 reduction++;
766 }
767 }
768 Finalize();
769
770 pSelect->AddAllSelectableTrackSegments(this);
771
772 // UpdateSegmentDistances();
773 ::wxEndBusyCursor();
774 return reduction;
775}
776
777Route *Track::RouteFromTrack(wxGenericProgressDialog *pprog) {
778 Route *route = new Route();
779
780 TrackPoint *pWP_src = TrackPoints.front();
781 size_t prpnodeX;
782 RoutePoint *pWP_dst, *pWP_prev;
783 TrackPoint *prp_OK =
784 NULL; // last routepoint known not to exceed xte limit, if not yet added
785
786 wxString icon = _T("xmblue");
787 if (g_TrackDeltaDistance >= 0.1) icon = _T("diamond");
788
789 int next_ic = 0;
790 int back_ic = 0;
791 int nPoints = TrackPoints.size();
792 bool isProminent = true;
793 double delta_dist = 0.;
794 double delta_hdg, xte;
795 double leg_speed = 0.1;
796
797 leg_speed = g_PlanSpeed;
798
799 // add first point
800
801 pWP_dst = new RoutePoint(pWP_src->m_lat, pWP_src->m_lon, icon, _T ( "" ),
802 wxEmptyString);
803 route->AddPoint(pWP_dst);
804
805 pWP_dst->m_bShowName = false;
806
807 pSelect->AddSelectableRoutePoint(pWP_dst->m_lat, pWP_dst->m_lon, pWP_dst);
808 pWP_prev = pWP_dst;
809 // add intermediate points as needed
810 int dProg = 0;
811 for (size_t i = 1; i < TrackPoints.size();) {
812 TrackPoint *prp = TrackPoints[i];
813 prpnodeX = i;
814 pWP_dst->m_lat = pWP_prev->m_lat;
815 pWP_dst->m_lon = pWP_prev->m_lon;
816 pWP_prev = pWP_dst;
817
818 delta_dist = 0.0;
819 delta_hdg = 0.0;
820 back_ic = next_ic;
821
822 DistanceBearingMercator(prp->m_lat, prp->m_lon, pWP_prev->m_lat,
823 pWP_prev->m_lon, &delta_hdg, &delta_dist);
824
825 if ((delta_dist > (leg_speed * 6.0)) && !prp_OK) {
826 int delta_inserts = floor(delta_dist / (leg_speed * 4.0));
827 delta_dist = delta_dist / (delta_inserts + 1);
828 double tlat = 0.0;
829 double tlon = 0.0;
830
831 while (delta_inserts--) {
832 ll_gc_ll(pWP_prev->m_lat, pWP_prev->m_lon, delta_hdg, delta_dist, &tlat,
833 &tlon);
834 pWP_dst = new RoutePoint(tlat, tlon, icon, _T ( "" ), wxEmptyString);
835 route->AddPoint(pWP_dst);
836 pWP_dst->m_bShowName = false;
837 pSelect->AddSelectableRoutePoint(pWP_dst->m_lat, pWP_dst->m_lon,
838 pWP_dst);
839
840 pSelect->AddSelectableRouteSegment(pWP_prev->m_lat, pWP_prev->m_lon,
841 pWP_dst->m_lat, pWP_dst->m_lon,
842 pWP_prev, pWP_dst, route);
843
844 pWP_prev = pWP_dst;
845 }
846 prpnodeX = i;
847 pWP_dst = pWP_prev;
848 next_ic = 0;
849 delta_dist = 0.0;
850 back_ic = next_ic;
851 prp_OK = prp;
852 isProminent = true;
853 } else {
854 isProminent = false;
855 if (delta_dist >= (leg_speed * 4.0)) isProminent = true;
856 if (!prp_OK) prp_OK = prp;
857 }
858 while (prpnodeX < TrackPoints.size()) {
859 TrackPoint *prpX = TrackPoints[prpnodeX];
860 // TrackPoint src(pWP_prev->m_lat, pWP_prev->m_lon);
861 xte = GetXTE(pWP_src, prpX, prp);
862 if (isProminent || (xte > g_TrackDeltaDistance)) {
863 pWP_dst = new RoutePoint(prp_OK->m_lat, prp_OK->m_lon, icon, _T ( "" ),
864 wxEmptyString);
865
866 route->AddPoint(pWP_dst);
867 pWP_dst->m_bShowName = false;
868
869 pSelect->AddSelectableRoutePoint(pWP_dst->m_lat, pWP_dst->m_lon,
870 pWP_dst);
871
872 pSelect->AddSelectableRouteSegment(pWP_prev->m_lat, pWP_prev->m_lon,
873 pWP_dst->m_lat, pWP_dst->m_lon,
874 pWP_prev, pWP_dst, route);
875
876 pWP_prev = pWP_dst;
877 next_ic = 0;
878 prpnodeX = TrackPoints.size();
879 prp_OK = NULL;
880 }
881
882 if (prpnodeX != TrackPoints.size()) prpnodeX--;
883 if (back_ic-- <= 0) {
884 prpnodeX = TrackPoints.size();
885 }
886 }
887
888 if (prp_OK) {
889 prp_OK = prp;
890 }
891
892 DistanceBearingMercator(prp->m_lat, prp->m_lon, pWP_prev->m_lat,
893 pWP_prev->m_lon, NULL, &delta_dist);
894
895 if (!((delta_dist > (g_TrackDeltaDistance)) && !prp_OK)) {
896 i++;
897 next_ic++;
898 }
899 int iProg = (i * 100) / nPoints;
900 if (pprog && (iProg > dProg)) {
901 dProg = iProg;
902 pprog->Update(dProg);
903 }
904 }
905
906 // add last point, if needed
907 if (delta_dist >= g_TrackDeltaDistance) {
908 pWP_dst =
909 new RoutePoint(TrackPoints.back()->m_lat, TrackPoints.back()->m_lon,
910 icon, _T ( "" ), wxEmptyString);
911 route->AddPoint(pWP_dst);
912
913 pWP_dst->m_bShowName = false;
914
915 pSelect->AddSelectableRoutePoint(pWP_dst->m_lat, pWP_dst->m_lon, pWP_dst);
916
917 pSelect->AddSelectableRouteSegment(pWP_prev->m_lat, pWP_prev->m_lon,
918 pWP_dst->m_lat, pWP_dst->m_lon, pWP_prev,
919 pWP_dst, route);
920 }
921 route->m_RouteNameString = m_TrackNameString;
922 route->m_RouteStartString = m_TrackStartString;
923 route->m_RouteEndString = m_TrackEndString;
924 route->m_bDeleteOnArrival = false;
925
926 return route;
927}
928
929double Track::GetXTE(double fm1Lat, double fm1Lon, double fm2Lat, double fm2Lon,
930 double toLat, double toLon) {
931 vector2D v, w, p;
932
933 // First we get the cartesian coordinates to the line endpoints, using
934 // the current position as origo.
935
936 double brg1, dist1, brg2, dist2;
937 DistanceBearingMercator(toLat, toLon, fm1Lat, fm1Lon, &brg1, &dist1);
938 w.x = dist1 * sin(brg1 * PI / 180.);
939 w.y = dist1 * cos(brg1 * PI / 180.);
940
941 DistanceBearingMercator(toLat, toLon, fm2Lat, fm2Lon, &brg2, &dist2);
942 v.x = dist2 * sin(brg2 * PI / 180.);
943 v.y = dist2 * cos(brg2 * PI / 180.);
944
945 p.x = 0.0;
946 p.y = 0.0;
947
948 const double lengthSquared = _distance2(v, w);
949 if (lengthSquared == 0.0) {
950 // v == w case
951 return _distance(p, v);
952 }
953
954 // Consider the line extending the segment, parameterized as v + t (w - v).
955 // We find projection of origo onto the line.
956 // It falls where t = [(p-v) . (w-v)] / |w-v|^2
957
958 vector2D a = p - v;
959 vector2D b = w - v;
960
961 double t = vDotProduct(&a, &b) / lengthSquared;
962
963 if (t < 0.0)
964 return _distance(p, v); // Beyond the 'v' end of the segment
965 else if (t > 1.0)
966 return _distance(p, w); // Beyond the 'w' end of the segment
967 vector2D projection = v + t * (w - v); // Projection falls on the segment
968 return _distance(p, projection);
969}
970
971double Track::GetXTE(TrackPoint *fm1, TrackPoint *fm2, TrackPoint *to) {
972 if (!fm1 || !fm2 || !to) return 0.0;
973 if (fm1 == to) return 0.0;
974 if (fm2 == to) return 0.0;
975 return GetXTE(fm1->m_lat, fm1->m_lon, fm2->m_lat, fm2->m_lon, to->m_lat,
976 to->m_lon);
977 ;
978}
Represents an active track that is currently being recorded.
Definition track.h:203
Definition route.h:75
Represents a single point in a track.
Definition track.h:52
Represents a track, which is a series of connected track points.
Definition track.h:78
The JSON value class implementation.
Definition jsonval.h:84