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