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