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