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