OpenCPN Partial API docs
Loading...
Searching...
No Matches
route.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 *
5 ***************************************************************************
6 * Copyright (C) 2013 by David S. Register *
7 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program; if not, write to the *
20 * Free Software Foundation, Inc., *
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
22 **************************************************************************/
23
24// For compilers that support precompilation, includes "wx.h".
25#include <wx/wxprec.h>
26
27#ifndef WX_PRECOMP
28#include <wx/wx.h>
29#endif // precompiled headers
30
31#ifndef WX_PRECOMP
32#include "wx/wx.h"
33#endif // precompiled headers
34
35#include <wx/arrstr.h>
36#include <wx/datetime.h>
37#include <wx/gdicmn.h>
38#include <wx/log.h>
39#include <wx/pen.h>
40#include <wx/string.h>
41
42#include "cutil.h"
43#include "georef.h"
44#include "georef.h"
45#include "nav_object_database.h"
46#include "route.h"
47#include "routeman.h"
48#include "select.h"
49
50extern WayPointman *pWayPointMan;
51extern Routeman *g_pRouteMan;
52extern int g_route_line_width;
53extern Select *pSelect;
54extern double g_n_arrival_circle_radius;
55extern float g_GLMinSymbolLineWidth;
56extern double g_PlanSpeed;
57extern wxString g_default_routepoint_icon;
58extern bool g_bAllowShipToActive;
59;
60
61#include <wx/listimpl.cpp>
62WX_DEFINE_LIST(RouteList);
63
64Route::Route() {
65 m_bRtIsSelected = false;
66 m_bRtIsActive = false;
67 m_pRouteActivePoint = NULL;
68 m_bIsBeingEdited = false;
69 m_bIsBeingCreated = false;
70 m_nm_sequence = 1;
71 m_route_length = 0.0;
72 m_route_time = 0.0;
73 m_bVisible = true;
74 m_bListed = true;
75 m_bDeleteOnArrival = false;
76 m_width = WIDTH_UNDEFINED;
77 m_style = wxPENSTYLE_INVALID;
78 m_hiliteWidth = 0;
79
80 pRoutePointList = new RoutePointList;
81 m_GUID = pWayPointMan->CreateGUID(NULL);
82 m_btemp = false;
83
84 m_ArrivalRadius = g_n_arrival_circle_radius; // Nautical Miles
85
86 m_LayerID = 0;
87 m_bIsInLayer = false;
88
89 m_Colour = wxEmptyString;
90
91 m_lastMousePointIndex = 0;
92 m_NextLegGreatCircle = false;
93
94 m_PlannedSpeed = ROUTE_DEFAULT_SPEED;
95 if (g_PlanSpeed != ROUTE_DEFAULT_SPEED) m_PlannedSpeed = g_PlanSpeed;
96
97 m_PlannedDeparture = RTE_UNDEF_DEPARTURE;
98 m_TimeDisplayFormat = RTE_TIME_DISP_PC;
99 m_HyperlinkList = new HyperlinkList;
100
101 m_bsharedWPViz = false;
102}
103
104Route::~Route() {
105 pRoutePointList->DeleteContents(false); // do not delete Marks
106 delete pRoutePointList;
107 delete m_HyperlinkList;
108}
109
110// The following is used only for route splitting, assumes just created, empty
111// route
112//
113void Route::CloneRoute(Route *psourceroute, int start_nPoint, int end_nPoint,
114 const wxString &suffix,
115 const bool duplicate_first_point) {
116 m_RouteNameString = psourceroute->m_RouteNameString + suffix;
117 m_RouteStartString = psourceroute->m_RouteStartString;
118 m_RouteEndString = psourceroute->m_RouteEndString;
119
120 int i;
121 for (i = start_nPoint; i <= end_nPoint; i++) {
122 if (!psourceroute->m_bIsInLayer &&
123 !(i == start_nPoint && duplicate_first_point)) {
124 AddPoint(psourceroute->GetPoint(i), false);
125 }
126 else {
127 RoutePoint *psourcepoint = psourceroute->GetPoint(i);
128 RoutePoint *ptargetpoint = new RoutePoint(
129 psourcepoint->m_lat, psourcepoint->m_lon, psourcepoint->GetIconName(),
130 psourcepoint->GetName(), wxEmptyString, false);
131 ptargetpoint->m_bShowName =
132 psourcepoint->m_bShowName; // do not change new wpt's name visibility
133 AddPoint(ptargetpoint, false);
134 }
135 }
136
137 FinalizeForRendering();
138}
139
140void Route::AddPoint(RoutePoint *pNewPoint, bool b_rename_in_sequence,
141 bool b_deferBoxCalc) {
142 if (pNewPoint->m_bIsolatedMark) {
143 pNewPoint->SetShared(true);
144 }
145 pNewPoint->m_bIsolatedMark = false; // definitely no longer isolated
146 pNewPoint->m_bIsInRoute = true;
147
148 RoutePoint *prev = GetLastPoint();
149 pRoutePointList->Append(pNewPoint);
150
151 if (!b_deferBoxCalc) FinalizeForRendering();
152
153 if (prev) UpdateSegmentDistance(prev, pNewPoint);
154
155 if (b_rename_in_sequence && pNewPoint->GetName().IsEmpty() &&
156 !pNewPoint->IsShared()) {
157 wxString name;
158 name.Printf(_T("%03d"), GetnPoints());
159 pNewPoint->SetName(name);
160 pNewPoint->m_bDynamicName = true;
161 }
162 return;
163}
164
165void Route::AddPointAndSegment(RoutePoint *pNewPoint, bool b_rename_in_sequence,
166 bool b_deferBoxCalc) {
167 int npoints = GetnPoints();
168 RoutePoint *newpoint = pNewPoint;
169 if (newpoint->m_bIsInLayer) {
170 newpoint = new RoutePoint(pNewPoint->m_lat, pNewPoint->m_lon,
171 pNewPoint->GetIconName(), pNewPoint->GetName(), wxEmptyString, false);
172 newpoint->m_bShowName = pNewPoint->m_bShowName; //do not change new wpt's name visibility
173 }
174 AddPoint(newpoint, false);
175 if (npoints != 0) {
176 double rlat = GetPoint(npoints)->m_lat;
177 double rlon = GetPoint(npoints)->m_lon;
178 npoints = GetnPoints();
179 pSelect->AddSelectableRouteSegment(rlat, rlon,
180 GetPoint(npoints)->m_lat, GetPoint(npoints)->m_lon, GetPoint(npoints - 1), GetPoint(npoints), this);
181 }
182 m_lastMousePointIndex = GetnPoints();
183}
184
185void Route::InsertPointAndSegment(RoutePoint *pNewPoint, int insert_after, bool bRenamePoints, bool b_deferBoxCalc)
186{
187 {
188 bool add = false;
189
190 if (pNewPoint->m_bIsolatedMark) {
191 pNewPoint->SetShared(true);
192 }
193 pNewPoint->m_bIsolatedMark = false; // definitely no longer isolated
194 pNewPoint->m_bIsInRoute = true;
195
196 if (insert_after >= GetnPoints() - 1) {
197 wxLogMessage(wxT("Error insert after last point"));
198 return;
199 }
200
201 int insert = insert_after++;
202 pNewPoint->m_bIsInRoute = true;
203 pNewPoint->m_bDynamicName = true;
204 pNewPoint->SetNameShown(false);
205 pRoutePointList->Insert(insert, pNewPoint);
206 if (bRenamePoints) RenameRoutePoints();
207 m_lastMousePointIndex = GetnPoints();
208 FinalizeForRendering();
209 UpdateSegmentDistances();
210 return;
211 }
212}
213
214RoutePoint *Route::GetPoint(int nWhichPoint) {
215 RoutePoint *prp;
216 wxRoutePointListNode *node = pRoutePointList->GetFirst();
217
218 int i = 1;
219 while (node) {
220 prp = node->GetData();
221 if (i == nWhichPoint) {
222 return prp;
223 }
224 i++;
225 node = node->GetNext();
226 }
227
228 return (NULL);
229}
230
231RoutePoint *Route::GetPoint(const wxString &guid) {
232 RoutePoint *prp;
233 wxRoutePointListNode *node = pRoutePointList->GetFirst();
234
235 while (node) {
236 prp = node->GetData();
237 if (guid == prp->m_GUID) return prp;
238
239 node = node->GetNext();
240 }
241
242 return (NULL);
243}
244
245static void TestLongitude(double lon, double min, double max, bool &lonl,
246 bool &lonr) {
247 double clon = (min + max) / 2;
248 if (min - lon > 180) lon += 360;
249
250 lonl = lonr = false;
251 if (lon < min) {
252 if (lon < clon - 180)
253 lonr = true;
254 else
255 lonl = true;
256 } else if (lon > max) {
257 if (lon > clon + 180)
258 lonl = true;
259 else
260 lonr = true;
261 }
262}
263
264bool Route::ContainsSharedWP() {
265 for (wxRoutePointListNode *node = pRoutePointList->GetFirst(); node;
266 node = node->GetNext()) {
267 RoutePoint *prp = node->GetData();
268 if (prp->IsShared()) return true;
269 }
270 return false;
271}
272
273// FIXME (leamas): can this be moved to GUI?
274int s_arrow_icon[] = {0, 0, 5, 2, 18, 6, 12, 0, 18, -6, 5, -2, 0, 0};
275void Route::ClearHighlights(void) {
276 RoutePoint *prp = NULL;
277 wxRoutePointListNode *node = pRoutePointList->GetFirst();
278
279 while (node) {
280 prp = node->GetData();
281 if (prp) prp->m_bPtIsSelected = false;
282 node = node->GetNext();
283 }
284}
285
286RoutePoint *Route::InsertPointBefore(RoutePoint *pRP, double rlat, double rlon,
287 bool bRenamePoints) {
288 RoutePoint *newpoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
289 GetNewMarkSequenced(), wxEmptyString);
290 newpoint->m_bIsInRoute = true;
291 newpoint->m_bDynamicName = true;
292 newpoint->SetNameShown(false);
293
294 int nRP = pRoutePointList->IndexOf(pRP);
295 pRoutePointList->Insert(nRP, newpoint);
296
297 if (bRenamePoints) RenameRoutePoints();
298
299 FinalizeForRendering();
300 UpdateSegmentDistances();
301
302 return (newpoint);
303}
304
305RoutePoint *Route::InsertPointAfter(RoutePoint *pRP, double rlat, double rlon,
306 bool bRenamePoints) {
307 int nRP = pRoutePointList->IndexOf(pRP);
308 if (nRP >= GetnPoints() - 1) return NULL;
309 nRP++;
310
311 RoutePoint *newpoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
312 GetNewMarkSequenced(), wxEmptyString);
313 newpoint->m_bIsInRoute = true;
314 newpoint->m_bDynamicName = true;
315 newpoint->SetNameShown(false);
316
317 pRoutePointList->Insert(nRP, newpoint);
318
319 if (bRenamePoints) RenameRoutePoints();
320
321 FinalizeForRendering();
322 UpdateSegmentDistances();
323
324 return (newpoint);
325}
326
327wxString Route::GetNewMarkSequenced(void) {
328 wxString ret;
329 ret.Printf(_T ( "NM%03d" ), m_nm_sequence);
330 m_nm_sequence++;
331
332 return ret;
333}
334
335RoutePoint *Route::GetLastPoint() {
336 if (pRoutePointList->IsEmpty()) return NULL;
337
338 return pRoutePointList->GetLast()->GetData();
339}
340
341int Route::GetIndexOf(RoutePoint *prp) {
342 int ret = pRoutePointList->IndexOf(prp) + 1;
343 if (ret == wxNOT_FOUND)
344 return 0;
345 else
346 return ret;
347}
348
349void Route::DeletePoint(RoutePoint *rp, bool bRenamePoints) {
350 // n.b. must delete Selectables and update config before deleting the
351 // point
352 if (rp->m_bIsInLayer) return;
353
354 pSelect->DeleteAllSelectableRoutePoints(this);
355 pSelect->DeleteAllSelectableRouteSegments(this);
356 NavObjectChanges::getInstance()->DeleteWayPoint(rp);
357
358 pRoutePointList->DeleteObject(rp);
359
360 delete rp;
361
362 if (bRenamePoints) RenameRoutePoints();
363
364 if (GetnPoints() > 1) {
365 pSelect->AddAllSelectableRouteSegments(this);
366 pSelect->AddAllSelectableRoutePoints(this);
367
368 NavObjectChanges::getInstance()->UpdateRoute(this);
369
370 FinalizeForRendering();
371 UpdateSegmentDistances();
372 }
373}
374
375void Route::RemovePoint(RoutePoint *rp, bool bRenamePoints) {
376 if (rp->m_bIsActive && this->IsActive()) // FS#348
377 g_pRouteMan->DeactivateRoute();
378
379 pSelect->DeleteAllSelectableRoutePoints(this);
380 pSelect->DeleteAllSelectableRouteSegments(this);
381
382 pRoutePointList->DeleteObject(rp);
383
384 // check all other routes to see if this point appears in any other route
385 Route *pcontainer_route = FindRouteContainingWaypoint(rp);
386
387 if (pcontainer_route == NULL) {
388 rp->m_bIsInRoute = false; // Take this point out of this (and only) route
389 rp->m_bDynamicName = false;
390 rp->m_bIsolatedMark = true; // This has become an isolated mark
391 }
392
393 if (bRenamePoints) RenameRoutePoints();
394
395 // if ( m_nPoints > 1 )
396 {
397 pSelect->AddAllSelectableRouteSegments(this);
398 pSelect->AddAllSelectableRoutePoints(this);
399
400 NavObjectChanges::getInstance()->UpdateRoute(this);
401
402 FinalizeForRendering();
403 UpdateSegmentDistances();
404 }
405}
406
407void Route::DeSelectRoute() {
408 wxRoutePointListNode *node = pRoutePointList->GetFirst();
409
410 RoutePoint *rp;
411 while (node) {
412 rp = node->GetData();
413 rp->m_bPtIsSelected = false;
414
415 node = node->GetNext();
416 }
417}
418
419void Route::ReloadRoutePointIcons() {
420 wxRoutePointListNode *node = pRoutePointList->GetFirst();
421
422 RoutePoint *rp;
423 while (node) {
424 rp = node->GetData();
425 rp->ReLoadIcon();
426
427 node = node->GetNext();
428 }
429}
430
431void Route::FinalizeForRendering() { RBBox.Invalidate(); }
432
433LLBBox &Route::GetBBox(void) {
434 if (RBBox.GetValid()) return RBBox;
435
436 double bbox_lonmin, bbox_lonmax, bbox_latmin, bbox_latmax;
437
438 wxRoutePointListNode *node = pRoutePointList->GetFirst();
439 RoutePoint *data = node->GetData();
440
441 if (data->m_wpBBox.GetValid()) {
442 bbox_lonmax = data->m_wpBBox.GetMaxLon();
443 bbox_lonmin = data->m_wpBBox.GetMinLon();
444 bbox_latmax = data->m_wpBBox.GetMaxLat();
445 bbox_latmin = data->m_wpBBox.GetMinLat();
446 } else {
447 bbox_lonmax = bbox_lonmin = data->m_lon;
448 bbox_latmax = bbox_latmin = data->m_lat;
449 }
450
451 double lastlon = data->m_lon, wrap = 0;
452
453 node = node->GetNext();
454 while (node) {
455 data = node->GetData();
456
457 if (lastlon - data->m_lon > 180)
458 wrap += 360;
459 else if (data->m_lon - lastlon > 180)
460 wrap -= 360;
461
462 double lon = data->m_lon + wrap;
463
464 if (lon > bbox_lonmax) bbox_lonmax = lon;
465 if (lon < bbox_lonmin) bbox_lonmin = lon;
466
467 if (data->m_lat > bbox_latmax) bbox_latmax = data->m_lat;
468 if (data->m_lat < bbox_latmin) bbox_latmin = data->m_lat;
469
470 lastlon = data->m_lon;
471 node = node->GetNext();
472 }
473
474 if (bbox_lonmin < -360)
475 bbox_lonmin += 360, bbox_lonmax += 360;
476 else if (bbox_lonmax > 360)
477 bbox_lonmin -= 360, bbox_lonmax -= 360;
478
479 if (bbox_lonmax - bbox_lonmin > 360) bbox_lonmin = -180, bbox_lonmax = 180;
480
481 RBBox.Set(bbox_latmin, bbox_lonmin, bbox_latmax, bbox_lonmax);
482
483 return RBBox;
484}
485
486/*
487 Update a single route segment lengths
488 Also, compute total route length by summing segment distances.
489 */
490void Route::UpdateSegmentDistance(RoutePoint *prp0, RoutePoint *prp,
491 double planspeed) {
492 double slat1 = prp0->m_lat, slon1 = prp0->m_lon;
493 double slat2 = prp->m_lat, slon2 = prp->m_lon;
494
495 // Calculate the absolute distance from 1->2
496
497 double dd;
498 double br;
499 // why are we using mercator rather than great circle here?? [sean 8-11-2015]
500 DistanceBearingMercator(slat2, slon2, slat1, slon1, &br, &dd);
501
502 prp->SetCourse(br);
503 prp->SetDistance(dd);
504
505 // And store in Point 2
506 prp->m_seg_len = dd;
507
508 m_route_length += dd;
509
510 // If Point1 Description contains VMG, store it for Properties Dialog in
511 // Point2 If Point1 Description contains ETD, store it in Point1
512
513 if (planspeed > 0.) {
514 wxDateTime etd;
515
516 double legspeed = planspeed;
517 if (prp->GetPlannedSpeed() > 0.1 && prp->GetPlannedSpeed() < 1000.)
518 legspeed = prp->GetPlannedSpeed();
519 if (legspeed > 0.1 && legspeed < 1000.) {
520 m_route_time += 3600. * dd / legspeed;
521 prp->m_seg_vmg = legspeed;
522 }
523 wxLongLong duration = wxLongLong(3600.0 * prp->m_seg_len / prp->m_seg_vmg);
524 prp->SetETE(duration);
525 wxTimeSpan ts(0, 0, duration);
526 if (!prp0->GetManualETD().IsValid()) {
527 prp0->m_manual_etd = false;
528 if (prp0->GetETA().IsValid()) {
529 prp0->m_seg_etd = prp0->GetETA();
530 } else {
531 prp0->m_seg_etd =
532 m_PlannedDeparture + wxTimeSpan(0, 0, m_route_time - duration);
533 }
534 }
535
536 prp->m_seg_eta = prp0->GetETD() + ts;
537 if (!prp->m_manual_etd || !prp->GetETD().IsValid()) {
538 prp->m_seg_etd = prp->m_seg_eta;
539 prp->m_manual_etd = false;
540 }
541 }
542}
543
544/*
545 Update the route segment lengths, storing each segment length in <destination>
546 point. Also, compute total route length by summing segment distances.
547 */
548void Route::UpdateSegmentDistances(double planspeed) {
549 wxPoint rpt, rptn;
550
551 m_route_length = 0.0;
552 m_route_time = 0.0;
553
554 wxRoutePointListNode *node = pRoutePointList->GetFirst();
555
556 if (node) {
557 // Route start point
558 RoutePoint *prp0 = node->GetData();
559 if (!prp0->m_manual_etd) {
560 prp0->m_seg_eta = m_PlannedDeparture;
561 prp0->m_seg_etd = m_PlannedDeparture;
562 }
563 node = node->GetNext();
564
565 while (node) {
566 RoutePoint *prp = node->GetData();
567 UpdateSegmentDistance(prp0, prp, planspeed);
568
569 prp0 = prp;
570
571 node = node->GetNext();
572 }
573 }
574}
575
576void Route::Reverse(bool bRenamePoints) {
577 // Reverse the GUID list
578 wxArrayString RoutePointGUIDList;
579
580 int ncount = pRoutePointList->GetCount();
581 for (int i = 0; i < ncount; i++)
582 RoutePointGUIDList.Add(GetPoint(ncount - i)->m_GUID);
583
584 pRoutePointList->DeleteContents(false);
585 pRoutePointList->Clear();
586 m_route_length = 0.0;
587
588 // iterate over the RoutePointGUIDs
589 for (unsigned int ip = 0; ip < RoutePointGUIDList.GetCount(); ip++) {
590 wxString GUID = RoutePointGUIDList[ip];
591
592 // And on the RoutePoints themselves
593 wxRoutePointListNode *prpnode = pWayPointMan->GetWaypointList()->GetFirst();
594 while (prpnode) {
595 RoutePoint *prp = prpnode->GetData();
596
597 if (prp->m_GUID == GUID) {
598 AddPoint(prp);
599 break;
600 }
601 prpnode = prpnode->GetNext(); // RoutePoint
602 }
603 }
604
605 if (bRenamePoints) RenameRoutePoints();
606
607 // Switch start/end strings. anders, 2010-01-29
608 wxString tmp = m_RouteStartString;
609 m_RouteStartString = m_RouteEndString;
610 m_RouteEndString = tmp;
611}
612
613void Route::SetVisible(bool visible, bool includeWpts) {
614 m_bVisible = visible;
615
616 if (!includeWpts) return;
617
618 wxRoutePointListNode *node = pRoutePointList->GetFirst();
619 RoutePoint *rp;
620 while (node) {
621 rp = node->GetData();
622
623 // if this is a "shared" point, then do not turn off visibility.
624 // This step keeps the point available for selection to other routes,
625 // or may be manaully hidden in route-manager dialog.
626 if (rp->IsShared()) {
627 if (visible) rp->SetVisible(visible);
628 }
629 node = node->GetNext();
630 }
631}
632
633void Route::SetListed(bool visible) { m_bListed = visible; }
634
635void Route::AssembleRoute(void) {}
636
637void Route::ShowWaypointNames(bool bshow) {
638 wxRoutePointListNode *node = pRoutePointList->GetFirst();
639
640 while (node) {
641 RoutePoint *prp = node->GetData();
642 prp->SetNameShown(bshow);
643
644 node = node->GetNext();
645 }
646}
647
648bool Route::AreWaypointNamesVisible() {
649 bool bvis = false;
650 wxRoutePointListNode *node = pRoutePointList->GetFirst();
651
652 while (node) {
653 RoutePoint *prp = node->GetData();
654 if (prp->GetNameShown()) bvis = true;
655
656 node = node->GetNext();
657 }
658
659 return bvis;
660}
661
662void Route::RenameRoutePoints(void) {
663 // iterate on the route points.
664 // If dynamically named, rename according to current list position
665
666 wxRoutePointListNode *node = pRoutePointList->GetFirst();
667
668 int i = 1;
669 while (node) {
670 RoutePoint *prp = node->GetData();
671 if (prp->m_bDynamicName) {
672 wxString name;
673 name.Printf(_T ( "%03d" ), i);
674 prp->SetName(name);
675 }
676
677 node = node->GetNext();
678 i++;
679 }
680}
681
682// Is this route equal to another, meaning,
683// Do all routepoint positions and names match?
684bool Route::IsEqualTo(Route *ptargetroute) {
685 wxRoutePointListNode *pthisnode = (this->pRoutePointList)->GetFirst();
686 wxRoutePointListNode *pthatnode = (ptargetroute->pRoutePointList)->GetFirst();
687
688 if (NULL == pthisnode) return false;
689
690 if (this->m_bIsInLayer || ptargetroute->m_bIsInLayer) return false;
691
692 if (this->GetnPoints() != ptargetroute->GetnPoints()) return false;
693
694 while (pthisnode) {
695 if (NULL == pthatnode) return false;
696
697 RoutePoint *pthisrp = pthisnode->GetData();
698 RoutePoint *pthatrp = pthatnode->GetData();
699
700 if ((fabs(pthisrp->m_lat - pthatrp->m_lat) > 1.0e-6) ||
701 (fabs(pthisrp->m_lon - pthatrp->m_lon) > 1.0e-6))
702 return false;
703
704 if (!pthisrp->GetName().IsSameAs(pthatrp->GetName())) return false;
705
706 pthisnode = pthisnode->GetNext();
707 pthatnode = pthatnode->GetNext();
708 }
709
710 return true; // success, they are the same
711}
Definition: route.h:70
Definition: select.h:51