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