]> git.mxchange.org Git - flightgear.git/blob - src/Navaids/route.cxx
fix trx and rx heights and improve calculations
[flightgear.git] / src / Navaids / route.cxx
1 // route.cxx - classes supporting waypoints and route structures
2
3 // Written by James Turner, started 2009.
4 //
5 // Copyright (C) 2009  Curtis L. Olson
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include "route.hxx"
26
27 // std
28 #include <map>
29 #include <fstream>
30
31 // Boost
32 #include <boost/algorithm/string/case_conv.hpp>
33 #include <boost/algorithm/string.hpp>
34
35 // SimGear
36 #include <simgear/structure/exception.hxx>
37 #include <simgear/xml/easyxml.hxx>
38 #include <simgear/misc/sg_path.hxx>
39 #include <simgear/magvar/magvar.hxx>
40 #include <simgear/timing/sg_time.hxx>
41
42 // FlightGear
43 #include <Main/globals.hxx>
44 #include <Navaids/procedure.hxx>
45 #include <Navaids/waypoint.hxx>
46 #include <Airports/simple.hxx>
47
48 using std::string;
49 using std::vector;
50 using std::endl;
51 using std::fstream;
52
53 namespace flightgear {
54
55 const double NO_MAG_VAR = -1000.0; // an impossible mag-var value
56
57 Waypt::Waypt(Route* aOwner) :
58   _altitudeFt(0.0),
59   _speed(0.0),
60   _altRestrict(RESTRICT_NONE),
61   _speedRestrict(RESTRICT_NONE),
62   _owner(aOwner),
63   _flags(0),
64   _magVarDeg(NO_MAG_VAR)
65 {
66 }
67
68 std::string Waypt::ident() const
69 {
70   return "";
71 }
72   
73 bool Waypt::flag(WayptFlag aFlag) const
74 {
75   return ((_flags & aFlag) != 0);
76 }
77         
78 void Waypt::setFlag(WayptFlag aFlag, bool aV)
79 {
80   _flags = (_flags & ~aFlag);
81   if (aV) _flags |= aFlag;
82 }
83
84 bool Waypt::matches(Waypt* aOther) const
85 {
86   assert(aOther);
87   if (ident() != aOther->ident()) { // cheap check first
88     return false;
89   }
90   
91   return matches(aOther->position());
92 }
93
94
95 bool Waypt::matches(const SGGeod& aPos) const
96 {
97   double d = SGGeodesy::distanceM(position(), aPos);
98   return (d < 100.0); // 100 metres seems plenty
99 }
100
101 void Waypt::setAltitude(double aAlt, RouteRestriction aRestrict)
102 {
103   _altitudeFt = aAlt;
104   _altRestrict = aRestrict;
105 }
106
107 void Waypt::setSpeed(double aSpeed, RouteRestriction aRestrict)
108 {
109   _speed = aSpeed;
110   _speedRestrict = aRestrict;
111 }
112
113 double Waypt::speedKts() const
114 {
115   assert(_speedRestrict != SPEED_RESTRICT_MACH);
116   return speed();
117 }
118   
119 double Waypt::speedMach() const
120 {
121   assert(_speedRestrict == SPEED_RESTRICT_MACH);
122   return speed();
123 }
124   
125 std::pair<double, double>
126 Waypt::courseAndDistanceFrom(const SGGeod& aPos) const
127 {
128   if (flag(WPT_DYNAMIC)) {
129     return std::make_pair(0.0, 0.0);
130   }
131   
132   double course, az2, distance;
133   SGGeodesy::inverse(aPos, position(), course, az2, distance);
134   return std::make_pair(course, distance);
135 }
136
137 double Waypt::magvarDeg() const
138 {
139   if (_magVarDeg == NO_MAG_VAR) {
140     // derived classes with a default pos must override this method
141     assert(!(position() == SGGeod()));
142     
143     double jd = globals->get_time_params()->getJD();
144     _magVarDeg = sgGetMagVar(position(), jd);
145   }
146   
147   return _magVarDeg;
148 }
149   
150 ///////////////////////////////////////////////////////////////////////////
151 // persistence
152
153 static RouteRestriction restrictionFromString(const char* aStr)
154 {
155   std::string l = boost::to_lower_copy(std::string(aStr));
156
157   if (l == "at") return RESTRICT_AT;
158   if (l == "above") return RESTRICT_ABOVE;
159   if (l == "below") return RESTRICT_BELOW;
160   if (l == "none") return RESTRICT_NONE;
161   if (l == "mach") return SPEED_RESTRICT_MACH;
162   
163   if (l.empty()) return RESTRICT_NONE;
164   throw sg_io_exception("unknown restriction specification:" + l, 
165     "Route restrictFromString");
166 }
167
168 static const char* restrictionToString(RouteRestriction aRestrict)
169 {
170   switch (aRestrict) {
171   case RESTRICT_AT: return "at";
172   case RESTRICT_BELOW: return "below";
173   case RESTRICT_ABOVE: return "above";
174   case RESTRICT_NONE: return "none";
175   case SPEED_RESTRICT_MACH: return "mach";
176   
177   default:
178     throw sg_exception("invalid route restriction",
179       "Route restrictToString");
180   }
181 }
182
183 Waypt* Waypt::createInstance(Route* aOwner, const std::string& aTypeName)
184 {
185   Waypt* r = NULL;
186   if (aTypeName == "basic") {
187     r = new BasicWaypt(aOwner);
188   } else if (aTypeName == "navaid") {
189     r = new NavaidWaypoint(aOwner);
190   } else if (aTypeName == "offset-navaid") {
191     r = new OffsetNavaidWaypoint(aOwner);
192   } else if (aTypeName == "hold") {
193     r = new Hold(aOwner);
194   } else if (aTypeName == "runway") {
195     r = new RunwayWaypt(aOwner);
196   } else if (aTypeName == "hdgToAlt") {
197     r = new HeadingToAltitude(aOwner);
198   } else if (aTypeName == "dmeIntercept") {
199     r = new DMEIntercept(aOwner);
200   } else if (aTypeName == "radialIntercept") {
201     r = new RadialIntercept(aOwner);
202   } else if (aTypeName == "vectors") {
203     r = new ATCVectors(aOwner);
204   } 
205
206   if (!r || (r->type() != aTypeName)) {
207     throw sg_exception("broken factory method for type:" + aTypeName,
208       "Waypt::createInstance");
209   }
210   
211   return r;
212 }
213
214 WayptRef Waypt::createFromProperties(Route* aOwner, SGPropertyNode_ptr aProp)
215 {
216   if (!aProp->hasChild("type")) {
217     throw sg_io_exception("bad props node, no type provided", 
218       "Waypt::createFromProperties");
219   }
220   
221   WayptRef nd(createInstance(aOwner, aProp->getStringValue("type")));
222   nd->initFromProperties(aProp);
223   return nd;
224 }
225   
226 void Waypt::saveAsNode(SGPropertyNode* n) const
227 {
228   n->setStringValue("type", type());
229   writeToProperties(n);
230 }
231
232 void Waypt::initFromProperties(SGPropertyNode_ptr aProp)
233 {
234   if (aProp->hasChild("generated")) {
235     setFlag(WPT_GENERATED, aProp->getBoolValue("generated")); 
236   }
237   
238   if (aProp->hasChild("overflight")) {
239     setFlag(WPT_OVERFLIGHT, aProp->getBoolValue("overflight")); 
240   }
241   
242   if (aProp->hasChild("arrival")) {
243     setFlag(WPT_ARRIVAL, aProp->getBoolValue("arrival")); 
244   }
245   
246   if (aProp->hasChild("departure")) {
247     setFlag(WPT_DEPARTURE, aProp->getBoolValue("departure")); 
248   }
249   
250   if (aProp->hasChild("miss")) {
251     setFlag(WPT_MISS, aProp->getBoolValue("miss")); 
252   }
253   
254   if (aProp->hasChild("alt-restrict")) {
255     _altRestrict = restrictionFromString(aProp->getStringValue("alt-restrict"));
256     _altitudeFt = aProp->getDoubleValue("altitude-ft");
257   }
258   
259   if (aProp->hasChild("speed-restrict")) {
260     _speedRestrict = restrictionFromString(aProp->getStringValue("speed-restrict"));
261     _speed = aProp->getDoubleValue("speed");
262   }
263   
264   
265 }
266
267 void Waypt::writeToProperties(SGPropertyNode_ptr aProp) const
268 {
269   if (flag(WPT_OVERFLIGHT)) {
270     aProp->setBoolValue("overflight", true);
271   }
272
273   if (flag(WPT_DEPARTURE)) {
274     aProp->setBoolValue("departure", true);
275   }
276   
277   if (flag(WPT_ARRIVAL)) {
278     aProp->setBoolValue("arrival", true);
279   }
280   
281   if (flag(WPT_MISS)) {
282     aProp->setBoolValue("miss", true);
283   }
284   
285   if (flag(WPT_GENERATED)) {
286     aProp->setBoolValue("generated", true);
287   }
288   
289   if (_altRestrict != RESTRICT_NONE) {
290     aProp->setStringValue("alt-restrict", restrictionToString(_altRestrict));
291     aProp->setDoubleValue("altitude-ft", _altitudeFt);
292   }
293   
294   if (_speedRestrict != RESTRICT_NONE) {
295     aProp->setStringValue("speed-restrict", restrictionToString(_speedRestrict));
296     aProp->setDoubleValue("speed", _speed);
297   }
298 }
299
300 void Route::dumpRouteToFile(const WayptVec& aRoute, const std::string& aName)
301 {
302   SGPath p = "/Users/jmt/Desktop/" + aName + ".kml";
303   std::fstream f;
304   f.open(p.str().c_str(), fstream::out | fstream::app);
305   if (!f.is_open()) {
306     SG_LOG(SG_GENERAL, SG_WARN, "unable to open:" << p.str());
307     return;
308   }
309   
310 // pre-amble
311   f << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
312       "<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n"
313     "<Document>\n";
314
315   dumpRouteToLineString(aName, aRoute, f);
316   
317 // post-amble
318   f << "</Document>\n" 
319     "</kml>" << endl;
320   f.close();
321 }
322
323 void Route::dumpRouteToLineString(const std::string& aIdent,
324   const WayptVec& aRoute, std::ostream& aStream)
325 {
326   // preamble
327   aStream << "<Placemark>\n";
328   aStream << "<name>" << aIdent << "</name>\n";
329   aStream << "<LineString>\n";
330   aStream << "<tessellate>1</tessellate>\n";
331   aStream << "<coordinates>\n";
332   
333   // waypoints
334   for (unsigned int i=0; i<aRoute.size(); ++i) {
335     SGGeod pos = aRoute[i]->position();
336     aStream << pos.getLongitudeDeg() << "," << pos.getLatitudeDeg() << " " << endl;
337   }
338   
339   // postable
340   aStream << "</coordinates>\n"
341     "</LineString>\n"
342     "</Placemark>\n" << endl;
343 }
344
345 ///////////////////////////////////////////////////////////////////////////
346
347 class NavdataVisitor : public XMLVisitor {
348 public:
349   NavdataVisitor(FGAirport* aApt, const SGPath& aPath);
350
351 protected:
352   virtual void startXML (); 
353   virtual void endXML   ();
354   virtual void startElement (const char * name, const XMLAttributes &atts);
355   virtual void endElement (const char * name);
356   virtual void data (const char * s, int len);
357   virtual void pi (const char * target, const char * data);
358   virtual void warning (const char * message, int line, int column);
359   virtual void error (const char * message, int line, int column);
360
361 private:
362   Waypt* buildWaypoint();
363   void processRunways(ArrivalDeparture* aProc, const XMLAttributes &atts);
364  
365   void finishApproach();
366   void finishSid();
367   void finishStar();
368   
369   FGAirport* _airport;
370   SGPath _path;
371   string _text; ///< last element text value
372   
373   SID* _sid;
374   STAR* _star;
375   Approach* _approach;
376
377   WayptVec _waypoints; ///< waypoint list for current approach/sid/star
378   WayptVec _transWaypts; ///< waypoint list for current transition
379   
380   string _wayptName;
381   string _wayptType;
382   string _ident; // id of segment under construction
383   string _transIdent;
384   double _longitude, _latitude, _altitude, _speed;
385   RouteRestriction _altRestrict;
386   
387   double _holdRadial; // inbound hold radial, or -1 if radial is 'inbound'
388   double _holdTD; ///< hold time (seconds) or distance (nm), based on flag below
389   bool _holdRighthanded;
390   bool _holdDistance; // true, TD is distance in nm; false, TD is time in seconds
391   
392   double _course, _radial, _dmeDistance;
393 };
394
395 void Route::loadAirportProcedures(const SGPath& aPath, FGAirport* aApt)
396 {
397   assert(aApt);
398   try {
399     NavdataVisitor visitor(aApt, aPath);
400     readXML(aPath.str(), visitor);
401   } catch (sg_io_exception& ex) {
402     SG_LOG(SG_GENERAL, SG_WARN, "failured parsing procedures: " << aPath.str() <<
403       "\n\t" << ex.getMessage() << "\n\tat:" << ex.getLocation().asString());
404   } catch (sg_exception& ex) {
405     SG_LOG(SG_GENERAL, SG_WARN, "failured parsing procedures: " << aPath.str() <<
406       "\n\t" << ex.getMessage());
407   }
408 }
409
410 NavdataVisitor::NavdataVisitor(FGAirport* aApt, const SGPath& aPath):
411   _airport(aApt),
412   _path(aPath),
413   _sid(NULL),
414   _star(NULL),
415   _approach(NULL)
416 {
417 }
418
419 void NavdataVisitor::startXML()
420 {
421 }
422
423 void NavdataVisitor::endXML()
424 {
425 }
426
427 void NavdataVisitor::startElement(const char* name, const XMLAttributes &atts)
428 {
429   _text.clear();
430   string tag(name);
431   if (tag == "Airport") {
432     string icao(atts.getValue("ICAOcode"));
433     if (_airport->ident() != icao) {
434       throw sg_format_exception("Airport and ICAO mismatch", icao, _path.str());
435     }
436   } else if (tag == "Sid") {
437     string ident(atts.getValue("Name"));
438     _sid = new SID(ident);
439     _waypoints.clear();
440     processRunways(_sid, atts);
441   } else if (tag == "Star") {
442     string ident(atts.getValue("Name"));
443     _star = new STAR(ident);
444     _waypoints.clear();
445     processRunways(_star, atts);
446   } else if ((tag == "Sid_Waypoint") ||
447       (tag == "App_Waypoint") ||
448       (tag == "Star_Waypoint") ||
449       (tag == "AppTr_Waypoint") ||
450       (tag == "SidTr_Waypoint") ||
451       (tag == "RwyTr_Waypoint"))
452   {
453     // reset waypoint data
454     _speed = 0.0;
455     _altRestrict = RESTRICT_NONE;
456     _altitude = 0.0;
457   } else if (tag == "Approach") {
458     _ident = atts.getValue("Name");
459     _waypoints.clear();
460     _approach = new Approach(_ident);
461   } else if ((tag == "Sid_Transition") || 
462              (tag == "App_Transition") ||
463              (tag == "Star_Transition")) {
464     _transIdent = atts.getValue("Name");
465     _transWaypts.clear();
466   } else if (tag == "RunwayTransition") {
467     _transIdent = atts.getValue("Runway");
468     _transWaypts.clear();
469   } else {
470     
471   }
472 }
473
474 void NavdataVisitor::processRunways(ArrivalDeparture* aProc, const XMLAttributes &atts)
475 {
476   string v("All");
477   if (atts.hasAttribute("Runways")) {
478     v = atts.getValue("Runways");
479   }
480   
481   if (v == "All") {
482     for (unsigned int r=0; r<_airport->numRunways(); ++r) {
483       aProc->addRunway(_airport->getRunwayByIndex(r));
484     }
485     return;
486   }
487   
488   vector<string> rwys;
489   boost::split(rwys, v, boost::is_any_of(" ,"));
490   for (unsigned int r=0; r<rwys.size(); ++r) {
491     FGRunway* rwy = _airport->getRunwayByIdent(rwys[r]);
492     aProc->addRunway(rwy);
493   }
494 }
495
496 void NavdataVisitor::endElement(const char* name)
497 {
498   string tag(name);
499   if ((tag == "Sid_Waypoint") ||
500       (tag == "App_Waypoint") ||
501       (tag == "Star_Waypoint"))
502   {
503     _waypoints.push_back(buildWaypoint());
504   } else if ((tag == "AppTr_Waypoint") || 
505              (tag == "SidTr_Waypoint") ||
506              (tag == "RwyTr_Waypoint") ||
507              (tag == "StarTr_Waypoint")) 
508   {
509     _transWaypts.push_back(buildWaypoint());
510   } else if (tag == "Sid_Transition") {
511     assert(_sid);
512     // SID waypoints are stored backwards, to share code with STARs
513     std::reverse(_transWaypts.begin(), _transWaypts.end());
514     Transition* t = new Transition(_transIdent, _sid, _transWaypts);
515     _sid->addTransition(t);
516   } else if (tag == "Star_Transition") {
517     assert(_star);
518     Transition* t = new Transition(_transIdent, _star, _transWaypts);
519     _star->addTransition(t);
520   } else if (tag == "App_Transition") {
521     assert(_approach);
522     Transition* t = new Transition(_transIdent, _approach, _transWaypts);
523     _approach->addTransition(t);
524   } else if (tag == "RunwayTransition") {
525     ArrivalDeparture* ad;
526     if (_sid) {
527       // SID waypoints are stored backwards, to share code with STARs
528       std::reverse(_transWaypts.begin(), _transWaypts.end());
529       ad = _sid;
530     } else {
531       ad = _star;
532     }
533     
534     Transition* t = new Transition(_transIdent, ad, _transWaypts);
535     FGRunwayRef rwy = _airport->getRunwayByIdent(_transIdent);
536     ad->addRunwayTransition(rwy, t);
537   } else if (tag == "Approach") {
538     finishApproach();
539   } else if (tag == "Sid") {
540     finishSid();
541   } else if (tag == "Star") {
542     finishStar();  
543   } else if (tag == "Longitude") {
544     _longitude = atof(_text.c_str());
545   } else if (tag == "Latitude") {
546     _latitude = atof(_text.c_str());
547   } else if (tag == "Name") {
548     _wayptName = _text;
549   } else if (tag == "Type") {
550     _wayptType = _text;
551   } else if (tag == "Speed") {
552     _speed = atoi(_text.c_str());
553   } else if (tag == "Altitude") {
554     _altitude = atof(_text.c_str());
555   } else if (tag == "AltitudeRestriction") {
556     if (_text == "at") {
557       _altRestrict = RESTRICT_AT;
558     } else if (_text == "above") {
559       _altRestrict = RESTRICT_ABOVE;
560     } else if (_text == "below") {
561       _altRestrict = RESTRICT_BELOW;
562     } else {
563       throw sg_format_exception("Unrecognized altitude restriction", _text);
564     }
565   } else if (tag == "Hld_Rad_or_Inbd") {
566     if (_text == "Inbd") {
567       _holdRadial = -1.0;
568     }
569   } else if (tag == "Hld_Time_or_Dist") {
570     _holdDistance = (_text == "Dist");
571   } else if (tag == "Hld_Rad_value") {
572     _holdRadial = atof(_text.c_str());
573   } else if (tag == "Hld_Turn") {
574     _holdRighthanded = (_text == "Right");
575   } else if (tag == "Hld_td_value") {
576     _holdTD = atof(_text.c_str());
577   } else if (tag == "Hdg_Crs_value") {
578     _course = atof(_text.c_str());
579   } else if (tag == "DMEtoIntercept") {
580     _dmeDistance = atof(_text.c_str());
581   } else if (tag == "RadialtoIntercept") {
582     _radial = atof(_text.c_str());
583   } else {
584     
585   }
586 }
587
588 Waypt* NavdataVisitor::buildWaypoint()
589 {
590   Waypt* wp = NULL;
591   if (_wayptType == "Normal") {
592     // new LatLonWaypoint
593     SGGeod pos(SGGeod::fromDeg(_longitude, _latitude));
594     wp = new BasicWaypt(pos, _wayptName, NULL);
595   } else if (_wayptType == "Runway") {
596     string ident = _wayptName.substr(2);
597     FGRunwayRef rwy = _airport->getRunwayByIdent(ident);
598     wp = new RunwayWaypt(rwy, NULL);
599   } else if (_wayptType == "Hold") {
600     SGGeod pos(SGGeod::fromDeg(_longitude, _latitude));
601     Hold* h = new Hold(pos, _wayptName, NULL);
602     wp = h;
603     if (_holdRighthanded) {
604       h->setRightHanded();
605     } else {
606       h->setLeftHanded();
607     }
608     
609     if (_holdDistance) {
610       h->setHoldDistance(_holdTD);
611     } else {
612       h->setHoldTime(_holdTD * 60.0);
613     }
614     
615     if (_holdRadial >= 0.0) {
616       h->setHoldRadial(_holdRadial);
617     }
618   } else if (_wayptType == "Vectors") {
619     wp = new ATCVectors(NULL, _airport);
620   } else if ((_wayptType == "Intc") || (_wayptType == "VorRadialIntc")) {
621     SGGeod pos(SGGeod::fromDeg(_longitude, _latitude));
622     wp = new RadialIntercept(NULL, _wayptName, pos, _course, _radial);
623   } else if (_wayptType == "DmeIntc") {
624     SGGeod pos(SGGeod::fromDeg(_longitude, _latitude));
625     wp = new DMEIntercept(NULL, _wayptName, pos, _course, _dmeDistance);
626   } else if (_wayptType == "ConstHdgtoAlt") {
627     wp = new HeadingToAltitude(NULL, _wayptName, _course);
628   } else {
629     SG_LOG(SG_GENERAL, SG_ALERT, "implement waypoint type:" << _wayptType);
630     throw sg_format_exception("Unrecognized waypt type", _wayptType);
631   }
632   
633   assert(wp);
634   if ((_altitude > 0.0) && (_altRestrict != RESTRICT_NONE)) {
635     wp->setAltitude(_altitude,_altRestrict);
636   }
637   
638   if (_speed > 0.0) {
639     wp->setSpeed(_speed, RESTRICT_AT); // or _BELOW?
640   }
641   
642   return wp;
643 }
644
645 void NavdataVisitor::finishApproach()
646 {
647   WayptVec::iterator it;
648   FGRunwayRef rwy;
649   
650 // find the runway node
651   for (it = _waypoints.begin(); it != _waypoints.end(); ++it) {
652     FGPositionedRef navid = (*it)->source();
653     if (!navid) {
654       continue;
655     }
656     
657     if (navid->type() == FGPositioned::RUNWAY) {
658       rwy = (FGRunway*) navid.get();
659       break;
660     }
661   }
662   
663   if (!rwy) {
664     throw sg_format_exception("Malformed approach, no runway waypt", _ident);
665   }
666   
667   WayptVec primary(_waypoints.begin(), it);
668   // erase all points up to and including the runway, to leave only the
669   // missed segments
670   _waypoints.erase(_waypoints.begin(), ++it);
671   
672   _approach->setRunway(rwy);
673   _approach->setPrimaryAndMissed(primary, _waypoints);
674   _airport->addApproach(_approach);
675   _approach = NULL;
676 }
677
678 void NavdataVisitor::finishSid()
679 {
680   // reverse order, because that's how we deal with commonality between
681   // STARs and SIDs. SID::route undoes  this
682   std::reverse(_waypoints.begin(), _waypoints.end());
683   _sid->setCommon(_waypoints);
684   _airport->addSID(_sid);
685   _sid = NULL;
686 }
687
688 void NavdataVisitor::finishStar()
689 {
690   _star->setCommon(_waypoints);
691   _airport->addSTAR(_star);
692   _star = NULL;
693 }
694
695 void NavdataVisitor::data (const char * s, int len)
696 {
697   _text += string(s, len);
698 }
699
700
701 void NavdataVisitor::pi (const char * target, const char * data) {
702   //cout << "Processing instruction " << target << ' ' << data << endl;
703 }
704
705 void NavdataVisitor::warning (const char * message, int line, int column) {
706   SG_LOG(SG_IO, SG_WARN, "Warning: " << message << " (" << line << ',' << column << ')');
707 }
708
709 void NavdataVisitor::error (const char * message, int line, int column) {
710   SG_LOG(SG_IO, SG_ALERT, "Error: " << message << " (" << line << ',' << column << ')');
711 }
712
713 } // of namespace flightgear