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