80#include <wx/datetime.h>
82#include <wx/jsonval.h>
84#include <wx/progdlg.h>
88#include "model/track.h"
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"
100std::vector<Track *> g_TrackList;
102#if defined(__UNIX__) && \
107 void Reset() { clock_gettime(CLOCK_REALTIME, &tp); }
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;
121TrackPoint::TrackPoint(
double lat,
double lon, wxString ts)
122 : m_lat(lat), m_lon(lon), m_GPXTrkSegNo(1) {
126TrackPoint::TrackPoint(
double lat,
double lon, wxDateTime dt)
127 : m_lat(lat), m_lon(lon), m_GPXTrkSegNo(1) {
133 : m_lat(orig->m_lat), m_lon(orig->m_lon), m_GPXTrkSegNo(1) {
134 SetCreateTime(orig->GetCreateTime());
137TrackPoint::~TrackPoint() {}
139wxDateTime TrackPoint::GetCreateTime() {
140 wxDateTime CreateTimeX;
141 ParseGPXDateTime(CreateTimeX, wxString(m_stimestring.c_str()));
145void TrackPoint::SetCreateTime(wxDateTime dt) {
148 ts = dt.FormatISODate()
150 .Append(dt.FormatISOTime())
156void TrackPoint::SetCreateTime(wxString ts) {
158 m_stimestring = ts.mb_str();
167double _distance2(vector2D &a, vector2D &b) {
168 return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
170double _distance(vector2D &a, vector2D &b) {
return sqrt(_distance2(a, b)); }
176 m_width = WIDTH_UNDEFINED;
177 m_style = wxPENSTYLE_INVALID;
179 m_GUID = pWayPointMan->CreateGUID(NULL);
180 m_bIsInLayer =
false;
183 m_HyperlinkList =
new HyperlinkList;
184 m_HighlightedTrackPoint = -1;
188 for (
size_t i = 0; i < TrackPoints.size(); i++)
delete TrackPoints[i];
190 delete m_HyperlinkList;
193#define TIMER_TRACK1 778
196EVT_TIMER(TIMER_TRACK1, ActiveTrack::OnTimerTrack)
200 m_TimerTrack.SetOwner(
this, TIMER_TRACK1);
204 SetPrecision(g_nTrackPrecision);
206 m_prev_time = wxInvalidDateTime;
207 m_lastStoredTP = NULL;
209 wxDateTime now = wxDateTime::Now();
211 trackPointState = firstPoint;
212 m_lastStoredTP = NULL;
216 m_CurrentTrackSeg = 0;
220ActiveTrack::~ActiveTrack() { Stop(); }
222void ActiveTrack::SetPrecision(
int prec) {
224 switch (m_nPrecision) {
226 m_allowedMaxAngle = 10;
227 m_allowedMaxXTE = 0.008;
229 m_minTrackpoint_delta = .004;
233 m_allowedMaxAngle = 10;
234 m_allowedMaxXTE = 0.004;
236 m_minTrackpoint_delta = .002;
240 m_allowedMaxAngle = 10;
241 m_allowedMaxXTE = 0.0015;
243 m_minTrackpoint_delta = .001;
249void ActiveTrack::Start(
void) {
252 m_TimerTrack.Start(1000, wxTIMER_CONTINUOUS);
257void ActiveTrack::Stop(
bool do_add_point) {
264 delta = DistGreatCircle(gLat, gLon, m_lastStoredTP->m_lat,
265 m_lastStoredTP->m_lon);
267 if (delta > m_minTrackpoint_delta) AddPointNow(
true);
276Track *ActiveTrack::DoExtendDaily() {
277 Track *pExtendTrack = NULL;
281 if (!pLastPoint->GetCreateTime().IsValid())
return NULL;
283 for (
Track *ptrack : g_TrackList) {
284 if (!ptrack->m_bIsInLayer && ptrack->m_GUID != m_GUID) {
285 TrackPoint *track_node = ptrack->GetLastPoint();
286 if (!track_node->GetCreateTime().IsValid())
288 if (track_node->GetCreateTime() <= pLastPoint->GetCreateTime()) {
290 track_node->GetCreateTime() > pExtendPoint->GetCreateTime()) {
291 pExtendPoint = track_node;
292 pExtendTrack = ptrack;
297 if (pExtendTrack && pExtendTrack->GetPoint(0)
299 .FromTimezone(wxDateTime::GMT0)
300 .IsSameDate(pLastPoint->GetCreateTime().FromTimezone(
301 wxDateTime::GMT0))) {
303 if (pLastPoint->GetCreateTime() == pExtendPoint->GetCreateTime()) begin = 2;
304 pSelect->DeleteAllSelectableTrackSegments(pExtendTrack);
305 wxString suffix = _T(
"");
306 if (GetName().IsNull()) {
307 suffix = pExtendTrack->GetName();
308 if (suffix.IsNull()) suffix = wxDateTime::Today().FormatISODate();
310 pExtendTrack->Clone(
this, begin, GetnPoints(), suffix);
311 pSelect->AddAllSelectableTrackSegments(pExtendTrack);
312 pSelect->DeleteAllSelectableTrackSegments(
this);
316 if (GetName().IsNull()) SetName(wxDateTime::Today().FormatISODate());
321void Track::Clone(
Track *psourcetrack,
int start_nPoint,
int end_nPoint,
322 const wxString &suffix) {
323 if (psourcetrack->m_bIsInLayer)
return;
325 m_TrackNameString = psourcetrack->m_TrackNameString + suffix;
326 m_TrackStartString = psourcetrack->m_TrackStartString;
327 m_TrackEndString = psourcetrack->m_TrackEndString;
329 bool b_splitting = GetnPoints() == 0;
333 startTrkSegNo = psourcetrack->GetPoint(start_nPoint)->m_GPXTrkSegNo;
335 startTrkSegNo = GetLastPoint()->m_GPXTrkSegNo;
338 for (i = start_nPoint; i <= end_nPoint; i++) {
339 TrackPoint *psourcepoint = psourcetrack->GetPoint(i);
343 AddPoint(ptargetpoint);
348void ActiveTrack::AdjustCurrentTrackPoint(
TrackPoint *prototype) {
350 *m_lastStoredTP = *prototype;
351 m_prev_time = prototype->GetCreateTime().FromUTC();
355void ActiveTrack::OnTimerTrack(wxTimerEvent &event) {
360 m_prev_dist = DistGreatCircle(gLat, gLon, m_lastStoredTP->m_lat,
361 m_lastStoredTP->m_lon);
365 bool b_addpoint =
false;
367 if ((m_TrackTimerSec > 0.) && ((
double)m_track_run >= m_TrackTimerSec) &&
368 (m_prev_dist > m_minTrackpoint_delta)) {
376 if ((trackPointState == firstPoint) && !g_bTrackDaily) {
377 wxDateTime now = wxDateTime::Now();
378 if (TrackPoints.empty()) TrackPoints.front()->SetCreateTime(now.ToUTC());
381 m_TimerTrack.Start(1000, wxTIMER_CONTINUOUS);
384void ActiveTrack::AddPointNow(
bool do_add_point) {
385 wxDateTime now = wxDateTime::Now();
387 if (m_prev_dist < 0.0005)
388 if (!do_add_point)
return;
390 if (m_prev_time.IsValid())
391 if (m_prev_time == now)
392 if (!do_add_point)
return;
394 vector2D gpsPoint(gLon, gLat);
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;
413 switch (trackPointState) {
415 TrackPoint *pTrackPoint = AddNewPoint(gpsPoint, now.ToUTC());
416 m_lastStoredTP = pTrackPoint;
417 trackPointState = secondPoint;
418 do_add_point =
false;
422 vector2D pPoint(gLon, gLat);
423 skipPoints.push_back(pPoint);
424 skipTimes.push_back(now.ToUTC());
425 trackPointState = potentialPoint;
428 case potentialPoint: {
429 if (gpsPoint == skipPoints[skipPoints.size() - 1])
break;
431 unsigned int xteMaxIndex = 0;
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);
444 if (xteMax > m_allowedMaxXTE) {
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);
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();
464 if (GetnPoints() > 2) {
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);
478 m_removeTP = m_fixedTP;
479 m_fixedTP = m_prevFixedTP;
484 skipPoints.push_back(gpsPoint);
485 skipTimes.push_back(now.ToUTC());
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);
501void Track::ClearHighlights() { m_HighlightedTrackPoint = -1; }
504 if (nWhichPoint < (
int)TrackPoints.size())
505 return TrackPoints[nWhichPoint];
511 if (TrackPoints.empty())
return NULL;
513 return TrackPoints.back();
516static double heading_diff(
double x) {
517 if (x > 180)
return 360 - x;
518 if (x < -180)
return -360 + x;
526double Track::ComputeScale(
int left,
int right) {
527 const double z = WGS84_semimajor_axis_meters * mercator_k0;
528 const double mult = DEGREE * z;
533 double lata = TrackPoints[left]->m_lat, lona = TrackPoints[left]->m_lon;
534 double latb = TrackPoints[right]->m_lat, lonb = TrackPoints[right]->m_lon;
536 double bx = heading_diff(lonb - lona), by = latb - lata;
538 double lengthSquared = bx * bx + by * by;
542 if (lengthSquared > 3)
return INFINITY;
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;
548 double vx = heading_diff(lon - lona);
549 double vy = lat - lata;
550 double dist = vx * vx + vy * vy;
552 if (dist > max_dist) max_dist = dist;
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;
559 double vx = heading_diff(lon - lona);
560 double vy = lat - lata;
561 double t = (vx * bx + vy * by) * invLengthSquared;
565 dist = vx * vx + vy * vy;
567 double wx = heading_diff(lona - lon);
568 double wy = lata - lat;
569 dist = wx * wx + wy * wy;
571 double projx = vx - t * bx;
572 double projy = vy - t * by;
573 dist = projx * projx + projy * projy;
576 if (dist > max_dist) max_dist = dist;
580 return max_dist * mult * mult;
587 TrackPoints.push_back(pNewPoint);
592void Track::Finalize() {
593 if (SubTracks.size())
598 int n = TrackPoints.size() - 1;
601 std::vector<SubTrack> new_level;
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;
611 for (
int i = 0; i < n; i++) {
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);
617 int left = i << level;
618 int right = wxMin(left + (1 << level), TrackPoints.size() - 1);
619 new_level[i].m_scale = ComputeScale(left, right);
622 SubTracks.push_back(new_level);
624 if (n > 1 && n & 1) n++;
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);
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);
643 SubTracks[level].push_back(
SubTrack());
644 SubTracks[level][pos].m_box = box;
648 SubTracks[level][pos].m_scale = 0;
650 int left = pos << level;
651 int right = wxMin(left + (1 << level), TrackPoints.size() - 1);
652 SubTracks[level][pos].m_scale = ComputeScale(left, right);
655 if (pos > 0) InsertSubTracks(box, level + 1, pos >> 1);
664void Track::AddPointFinalized(
TrackPoint *pNewPoint) {
665 TrackPoints.push_back(pNewPoint);
667 int pos = TrackPoints.size() - 1;
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);
677TrackPoint *Track::AddNewPoint(vector2D point, wxDateTime time) {
680 AddPointFinalized(tPoint);
682 NavObjectChanges::getInstance()->AddNewTrackPoint(
687 v[
"lat"] = tPoint->m_lat;
688 v[
"lon"] = tPoint->m_lon;
689 v[
"Track_ID"] = m_GUID;
690 std::string msg_id(
"OCPN_TRK_POINT_ADDED");
691 JsonEvent::getInstance().Notify(msg_id, std::make_shared<wxJSONValue>(v));
696void Track::DouglasPeuckerReducer(std::vector<TrackPoint *> &list,
697 std::vector<bool> &keeplist,
int from,
int to,
699 keeplist[from] =
true;
702 int maxdistIndex = -1;
705 for (
int i = from + 1; i < to; i++) {
706 double dist = 1852.0 * GetXTE(list[from], list[to], list[i]);
708 if (dist > maxdist) {
714 if (maxdist > delta) {
715 DouglasPeuckerReducer(list, keeplist, from, maxdistIndex, delta);
716 DouglasPeuckerReducer(list, keeplist, maxdistIndex, to, delta);
720double Track::Length() {
723 for (
size_t i = 0; i < TrackPoints.size(); i++) {
726 const double offsetLat = 1e-6;
727 const double deltaLat = l->m_lat - t->m_lat;
728 if (fabs(deltaLat) > offsetLat)
729 total += DistGreatCircle(l->m_lat, l->m_lon, t->m_lat, t->m_lon);
731 total += DistGreatCircle(l->m_lat + copysign(offsetLat, deltaLat),
732 l->m_lon, t->m_lat, t->m_lon);
740int Track::Simplify(
double maxDelta) {
743 std::vector<TrackPoint *> pointlist;
744 std::vector<bool> keeplist;
746 ::wxBeginBusyCursor();
748 for (
size_t i = 0; i < TrackPoints.size(); i++) {
751 pointlist.push_back(trackpoint);
752 keeplist.push_back(
false);
755 DouglasPeuckerReducer(pointlist, keeplist, 0, pointlist.size() - 1, maxDelta);
757 pSelect->DeleteAllSelectableTrackSegments(
this);
761 for (
size_t i = 0; i < pointlist.size(); i++) {
763 TrackPoints.push_back(pointlist[i]);
771 pSelect->AddAllSelectableTrackSegments(
this);
778Route *Track::RouteFromTrack(wxGenericProgressDialog *pprog) {
787 wxString icon = _T(
"xmblue");
788 if (g_TrackDeltaDistance >= 0.1) icon = _T(
"diamond");
792 int nPoints = TrackPoints.size();
793 bool isProminent =
true;
794 double delta_dist = 0.;
795 double delta_hdg, xte;
796 double leg_speed = 0.1;
798 leg_speed = g_PlanSpeed;
802 pWP_dst =
new RoutePoint(pWP_src->m_lat, pWP_src->m_lon, icon, _T (
"" ),
804 route->AddPoint(pWP_dst);
806 pWP_dst->m_bShowName =
false;
808 pSelect->AddSelectableRoutePoint(pWP_dst->m_lat, pWP_dst->m_lon, pWP_dst);
812 for (
size_t i = 1; i < TrackPoints.size();) {
815 pWP_dst->m_lat = pWP_prev->m_lat;
816 pWP_dst->m_lon = pWP_prev->m_lon;
823 DistanceBearingMercator(prp->m_lat, prp->m_lon, pWP_prev->m_lat,
824 pWP_prev->m_lon, &delta_hdg, &delta_dist);
826 if ((delta_dist > (leg_speed * 6.0)) && !prp_OK) {
827 int delta_inserts = floor(delta_dist / (leg_speed * 4.0));
828 delta_dist = delta_dist / (delta_inserts + 1);
832 while (delta_inserts--) {
833 ll_gc_ll(pWP_prev->m_lat, pWP_prev->m_lon, delta_hdg, delta_dist, &tlat,
835 pWP_dst =
new RoutePoint(tlat, tlon, icon, _T (
"" ), wxEmptyString);
836 route->AddPoint(pWP_dst);
837 pWP_dst->m_bShowName =
false;
838 pSelect->AddSelectableRoutePoint(pWP_dst->m_lat, pWP_dst->m_lon,
841 pSelect->AddSelectableRouteSegment(pWP_prev->m_lat, pWP_prev->m_lon,
842 pWP_dst->m_lat, pWP_dst->m_lon,
843 pWP_prev, pWP_dst, route);
856 if (delta_dist >= (leg_speed * 4.0)) isProminent =
true;
857 if (!prp_OK) prp_OK = prp;
859 while (prpnodeX < TrackPoints.size()) {
862 xte = GetXTE(pWP_src, prpX, prp);
863 if (isProminent || (xte > g_TrackDeltaDistance)) {
864 pWP_dst =
new RoutePoint(prp_OK->m_lat, prp_OK->m_lon, icon, _T (
"" ),
867 route->AddPoint(pWP_dst);
868 pWP_dst->m_bShowName =
false;
870 pSelect->AddSelectableRoutePoint(pWP_dst->m_lat, pWP_dst->m_lon,
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);
879 prpnodeX = TrackPoints.size();
883 if (prpnodeX != TrackPoints.size()) prpnodeX--;
884 if (back_ic-- <= 0) {
885 prpnodeX = TrackPoints.size();
893 DistanceBearingMercator(prp->m_lat, prp->m_lon, pWP_prev->m_lat,
894 pWP_prev->m_lon, NULL, &delta_dist);
896 if (!((delta_dist > (g_TrackDeltaDistance)) && !prp_OK)) {
900 int iProg = (i * 100) / nPoints;
901 if (pprog && (iProg > dProg)) {
903 pprog->Update(dProg);
908 if (delta_dist >= g_TrackDeltaDistance) {
910 new RoutePoint(TrackPoints.back()->m_lat, TrackPoints.back()->m_lon,
911 icon, _T (
"" ), wxEmptyString);
912 route->AddPoint(pWP_dst);
914 pWP_dst->m_bShowName =
false;
916 pSelect->AddSelectableRoutePoint(pWP_dst->m_lat, pWP_dst->m_lon, pWP_dst);
918 pSelect->AddSelectableRouteSegment(pWP_prev->m_lat, pWP_prev->m_lon,
919 pWP_dst->m_lat, pWP_dst->m_lon, pWP_prev,
922 route->m_RouteNameString = m_TrackNameString;
923 route->m_RouteStartString = m_TrackStartString;
924 route->m_RouteEndString = m_TrackEndString;
925 route->m_bDeleteOnArrival =
false;
930double Track::GetXTE(
double fm1Lat,
double fm1Lon,
double fm2Lat,
double fm2Lon,
931 double toLat,
double toLon) {
937 double brg1, dist1, brg2, dist2;
938 DistanceBearingMercator(toLat, toLon, fm1Lat, fm1Lon, &brg1, &dist1);
939 w.x = dist1 * sin(brg1 * PI / 180.);
940 w.y = dist1 * cos(brg1 * PI / 180.);
942 DistanceBearingMercator(toLat, toLon, fm2Lat, fm2Lon, &brg2, &dist2);
943 v.x = dist2 * sin(brg2 * PI / 180.);
944 v.y = dist2 * cos(brg2 * PI / 180.);
949 const double lengthSquared = _distance2(v, w);
950 if (lengthSquared == 0.0) {
952 return _distance(p, v);
962 double t = vDotProduct(&a, &b) / lengthSquared;
965 return _distance(p, v);
967 return _distance(p, w);
968 vector2D projection = v + t * (w - v);
969 return _distance(p, projection);
973 if (!fm1 || !fm2 || !to)
return 0.0;
974 if (fm1 == to)
return 0.0;
975 if (fm2 == to)
return 0.0;
976 return GetXTE(fm1->m_lat, fm1->m_lon, fm2->m_lat, fm2->m_lon, to->m_lat,
981wxString Track::GetIsoDateTime(
const wxString label_for_invalid_date)
const {
984 if ((
int)TrackPoints.size() > 0) rp = TrackPoints[0];
985 if (rp && rp->GetCreateTime().IsValid())
986 name = rp->GetCreateTime().FormatISOCombined(
' ');
988 name = label_for_invalid_date;
992wxString Track::GetDateTime(
const wxString label_for_invalid_date)
const {
995 if ((
int)TrackPoints.size() > 0) rp = TrackPoints[0];
996 if (rp && rp->GetCreateTime().IsValid())
997 name = ocpn::toUsrDateTimeFormat(rp->GetCreateTime());
999 name = label_for_invalid_date;
Represents an active track that is currently being recorded.
Represents a waypoint or mark within the navigation system.
Represents a navigational route in the navigation system.
Represents a single point in a track.
Represents a track, which is a series of connected track points.
The JSON value class implementation.
PlugIn Object Definition/API.