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