1 // route.cxx - classes supporting waypoints and route structures
3 // Written by James Turner, started 2009.
5 // Copyright (C) 2009 Curtis L. Olson
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.
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.
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.
32 #include <boost/algorithm/string/case_conv.hpp>
33 #include <boost/algorithm/string.hpp>
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>
43 #include <Main/globals.hxx>
44 #include <Navaids/procedure.hxx>
45 #include <Navaids/waypoint.hxx>
46 #include <Airports/simple.hxx>
53 namespace flightgear {
55 const double NO_MAG_VAR = -1000.0; // an impossible mag-var value
57 Waypt::Waypt(Route* aOwner) :
60 _altRestrict(RESTRICT_NONE),
61 _speedRestrict(RESTRICT_NONE),
64 _magVarDeg(NO_MAG_VAR)
72 std::string Waypt::ident() const
77 bool Waypt::flag(WayptFlag aFlag) const
79 return ((_flags & aFlag) != 0);
82 void Waypt::setFlag(WayptFlag aFlag, bool aV)
84 _flags = (_flags & ~aFlag);
85 if (aV) _flags |= aFlag;
88 bool Waypt::matches(Waypt* aOther) const
91 if (ident() != aOther->ident()) { // cheap check first
95 return matches(aOther->position());
99 bool Waypt::matches(const SGGeod& aPos) const
101 double d = SGGeodesy::distanceM(position(), aPos);
102 return (d < 100.0); // 100 metres seems plenty
105 void Waypt::setAltitude(double aAlt, RouteRestriction aRestrict)
108 _altRestrict = aRestrict;
111 void Waypt::setSpeed(double aSpeed, RouteRestriction aRestrict)
114 _speedRestrict = aRestrict;
117 double Waypt::speedKts() const
119 assert(_speedRestrict != SPEED_RESTRICT_MACH);
123 double Waypt::speedMach() const
125 assert(_speedRestrict == SPEED_RESTRICT_MACH);
129 std::pair<double, double>
130 Waypt::courseAndDistanceFrom(const SGGeod& aPos) const
132 if (flag(WPT_DYNAMIC)) {
133 return std::make_pair(0.0, 0.0);
136 double course, az2, distance;
137 SGGeodesy::inverse(aPos, position(), course, az2, distance);
138 return std::make_pair(course, distance);
141 double Waypt::magvarDeg() const
143 if (_magVarDeg == NO_MAG_VAR) {
144 // derived classes with a default pos must override this method
145 assert(!(position() == SGGeod()));
147 double jd = globals->get_time_params()->getJD();
148 _magVarDeg = sgGetMagVar(position(), jd) * SG_RADIANS_TO_DEGREES;
154 double Waypt::headingRadialDeg() const
159 ///////////////////////////////////////////////////////////////////////////
162 static RouteRestriction restrictionFromString(const char* aStr)
164 std::string l = boost::to_lower_copy(std::string(aStr));
166 if (l == "at") return RESTRICT_AT;
167 if (l == "above") return RESTRICT_ABOVE;
168 if (l == "below") return RESTRICT_BELOW;
169 if (l == "none") return RESTRICT_NONE;
170 if (l == "mach") return SPEED_RESTRICT_MACH;
172 if (l.empty()) return RESTRICT_NONE;
173 throw sg_io_exception("unknown restriction specification:" + l,
174 "Route restrictFromString");
177 static const char* restrictionToString(RouteRestriction aRestrict)
180 case RESTRICT_AT: return "at";
181 case RESTRICT_BELOW: return "below";
182 case RESTRICT_ABOVE: return "above";
183 case RESTRICT_NONE: return "none";
184 case SPEED_RESTRICT_MACH: return "mach";
187 throw sg_exception("invalid route restriction",
188 "Route restrictToString");
192 Waypt* Waypt::createInstance(Route* aOwner, const std::string& aTypeName)
195 if (aTypeName == "basic") {
196 r = new BasicWaypt(aOwner);
197 } else if (aTypeName == "navaid") {
198 r = new NavaidWaypoint(aOwner);
199 } else if (aTypeName == "offset-navaid") {
200 r = new OffsetNavaidWaypoint(aOwner);
201 } else if (aTypeName == "hold") {
202 r = new Hold(aOwner);
203 } else if (aTypeName == "runway") {
204 r = new RunwayWaypt(aOwner);
205 } else if (aTypeName == "hdgToAlt") {
206 r = new HeadingToAltitude(aOwner);
207 } else if (aTypeName == "dmeIntercept") {
208 r = new DMEIntercept(aOwner);
209 } else if (aTypeName == "radialIntercept") {
210 r = new RadialIntercept(aOwner);
211 } else if (aTypeName == "vectors") {
212 r = new ATCVectors(aOwner);
215 if (!r || (r->type() != aTypeName)) {
216 throw sg_exception("broken factory method for type:" + aTypeName,
217 "Waypt::createInstance");
223 WayptRef Waypt::createFromProperties(Route* aOwner, SGPropertyNode_ptr aProp)
225 if (!aProp->hasChild("type")) {
226 throw sg_io_exception("bad props node, no type provided",
227 "Waypt::createFromProperties");
230 WayptRef nd(createInstance(aOwner, aProp->getStringValue("type")));
231 nd->initFromProperties(aProp);
235 void Waypt::saveAsNode(SGPropertyNode* n) const
237 n->setStringValue("type", type());
238 writeToProperties(n);
241 void Waypt::initFromProperties(SGPropertyNode_ptr aProp)
243 if (aProp->hasChild("generated")) {
244 setFlag(WPT_GENERATED, aProp->getBoolValue("generated"));
247 if (aProp->hasChild("overflight")) {
248 setFlag(WPT_OVERFLIGHT, aProp->getBoolValue("overflight"));
251 if (aProp->hasChild("arrival")) {
252 setFlag(WPT_ARRIVAL, aProp->getBoolValue("arrival"));
255 if (aProp->hasChild("departure")) {
256 setFlag(WPT_DEPARTURE, aProp->getBoolValue("departure"));
259 if (aProp->hasChild("miss")) {
260 setFlag(WPT_MISS, aProp->getBoolValue("miss"));
263 if (aProp->hasChild("alt-restrict")) {
264 _altRestrict = restrictionFromString(aProp->getStringValue("alt-restrict"));
265 _altitudeFt = aProp->getDoubleValue("altitude-ft");
268 if (aProp->hasChild("speed-restrict")) {
269 _speedRestrict = restrictionFromString(aProp->getStringValue("speed-restrict"));
270 _speed = aProp->getDoubleValue("speed");
276 void Waypt::writeToProperties(SGPropertyNode_ptr aProp) const
278 if (flag(WPT_OVERFLIGHT)) {
279 aProp->setBoolValue("overflight", true);
282 if (flag(WPT_DEPARTURE)) {
283 aProp->setBoolValue("departure", true);
286 if (flag(WPT_ARRIVAL)) {
287 aProp->setBoolValue("arrival", true);
290 if (flag(WPT_MISS)) {
291 aProp->setBoolValue("miss", true);
294 if (flag(WPT_GENERATED)) {
295 aProp->setBoolValue("generated", true);
298 if (_altRestrict != RESTRICT_NONE) {
299 aProp->setStringValue("alt-restrict", restrictionToString(_altRestrict));
300 aProp->setDoubleValue("altitude-ft", _altitudeFt);
303 if (_speedRestrict != RESTRICT_NONE) {
304 aProp->setStringValue("speed-restrict", restrictionToString(_speedRestrict));
305 aProp->setDoubleValue("speed", _speed);
309 void Route::dumpRouteToFile(const WayptVec& aRoute, const std::string& aName)
311 SGPath p = "/Users/jmt/Desktop/" + aName + ".kml";
313 f.open(p.str().c_str(), fstream::out | fstream::app);
315 SG_LOG(SG_GENERAL, SG_WARN, "unable to open:" << p.str());
320 f << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
321 "<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n"
324 dumpRouteToLineString(aName, aRoute, f);
332 void Route::dumpRouteToLineString(const std::string& aIdent,
333 const WayptVec& aRoute, std::ostream& aStream)
336 aStream << "<Placemark>\n";
337 aStream << "<name>" << aIdent << "</name>\n";
338 aStream << "<LineString>\n";
339 aStream << "<tessellate>1</tessellate>\n";
340 aStream << "<coordinates>\n";
343 for (unsigned int i=0; i<aRoute.size(); ++i) {
344 SGGeod pos = aRoute[i]->position();
345 aStream << pos.getLongitudeDeg() << "," << pos.getLatitudeDeg() << " " << endl;
349 aStream << "</coordinates>\n"
351 "</Placemark>\n" << endl;
354 ///////////////////////////////////////////////////////////////////////////
356 class NavdataVisitor : public XMLVisitor {
358 NavdataVisitor(FGAirport* aApt, const SGPath& aPath);
361 virtual void startXML ();
362 virtual void endXML ();
363 virtual void startElement (const char * name, const XMLAttributes &atts);
364 virtual void endElement (const char * name);
365 virtual void data (const char * s, int len);
366 virtual void pi (const char * target, const char * data);
367 virtual void warning (const char * message, int line, int column);
368 virtual void error (const char * message, int line, int column);
371 Waypt* buildWaypoint(Route* owner);
372 void processRunways(ArrivalDeparture* aProc, const XMLAttributes &atts);
374 void finishApproach();
380 string _text; ///< last element text value
385 Transition* _transition;
386 Procedure* _procedure;
388 WayptVec _waypoints; ///< waypoint list for current approach/sid/star
389 WayptVec _transWaypts; ///< waypoint list for current transition
393 string _ident; // id of segment under construction
395 double _longitude, _latitude, _altitude, _speed;
396 RouteRestriction _altRestrict;
398 double _holdRadial; // inbound hold radial, or -1 if radial is 'inbound'
399 double _holdTD; ///< hold time (seconds) or distance (nm), based on flag below
400 bool _holdRighthanded;
401 bool _holdDistance; // true, TD is distance in nm; false, TD is time in seconds
403 double _course, _radial, _dmeDistance;
406 void Route::loadAirportProcedures(const SGPath& aPath, FGAirport* aApt)
410 NavdataVisitor visitor(aApt, aPath);
411 readXML(aPath.str(), visitor);
412 } catch (sg_io_exception& ex) {
413 SG_LOG(SG_GENERAL, SG_WARN, "failure parsing procedures: " << aPath.str() <<
414 "\n\t" << ex.getMessage() << "\n\tat:" << ex.getLocation().asString());
415 } catch (sg_exception& ex) {
416 SG_LOG(SG_GENERAL, SG_WARN, "failure parsing procedures: " << aPath.str() <<
417 "\n\t" << ex.getMessage());
421 NavdataVisitor::NavdataVisitor(FGAirport* aApt, const SGPath& aPath):
432 void NavdataVisitor::startXML()
436 void NavdataVisitor::endXML()
440 void NavdataVisitor::startElement(const char* name, const XMLAttributes &atts)
444 if (tag == "Airport") {
445 string icao(atts.getValue("ICAOcode"));
446 if (_airport->ident() != icao) {
447 throw sg_format_exception("Airport and ICAO mismatch", icao, _path.str());
449 } else if (tag == "Sid") {
450 string ident(atts.getValue("Name"));
451 _sid = new SID(ident);
454 processRunways(_sid, atts);
455 } else if (tag == "Star") {
456 string ident(atts.getValue("Name"));
457 _star = new STAR(ident);
460 processRunways(_star, atts);
461 } else if ((tag == "Sid_Waypoint") ||
462 (tag == "App_Waypoint") ||
463 (tag == "Star_Waypoint") ||
464 (tag == "AppTr_Waypoint") ||
465 (tag == "SidTr_Waypoint") ||
466 (tag == "RwyTr_Waypoint"))
468 // reset waypoint data
470 _altRestrict = RESTRICT_NONE;
472 } else if (tag == "Approach") {
473 _ident = atts.getValue("Name");
475 _approach = new Approach(_ident);
476 _procedure = _approach;
477 } else if ((tag == "Sid_Transition") ||
478 (tag == "App_Transition") ||
479 (tag == "Star_Transition")) {
480 _transIdent = atts.getValue("Name");
481 _transition = new Transition(_transIdent, _procedure);
482 _transWaypts.clear();
483 } else if (tag == "RunwayTransition") {
484 _transIdent = atts.getValue("Runway");
485 _transition = new Transition(_transIdent, _procedure);
486 _transWaypts.clear();
492 void NavdataVisitor::processRunways(ArrivalDeparture* aProc, const XMLAttributes &atts)
495 if (atts.hasAttribute("Runways")) {
496 v = atts.getValue("Runways");
500 for (unsigned int r=0; r<_airport->numRunways(); ++r) {
501 aProc->addRunway(_airport->getRunwayByIndex(r));
507 boost::split(rwys, v, boost::is_any_of(" ,"));
508 for (unsigned int r=0; r<rwys.size(); ++r) {
509 FGRunway* rwy = _airport->getRunwayByIdent(rwys[r]);
510 aProc->addRunway(rwy);
514 void NavdataVisitor::endElement(const char* name)
517 if ((tag == "Sid_Waypoint") ||
518 (tag == "App_Waypoint") ||
519 (tag == "Star_Waypoint"))
521 _waypoints.push_back(buildWaypoint(_procedure));
522 } else if ((tag == "AppTr_Waypoint") ||
523 (tag == "SidTr_Waypoint") ||
524 (tag == "RwyTr_Waypoint") ||
525 (tag == "StarTr_Waypoint"))
527 _transWaypts.push_back(buildWaypoint(_transition));
528 } else if (tag == "Sid_Transition") {
530 // SID waypoints are stored backwards, to share code with STARs
531 std::reverse(_transWaypts.begin(), _transWaypts.end());
532 _transition->setPrimary(_transWaypts);
533 _sid->addTransition(_transition);
534 } else if (tag == "Star_Transition") {
536 _transition->setPrimary(_transWaypts);
537 _star->addTransition(_transition);
538 } else if (tag == "App_Transition") {
540 _transition->setPrimary(_transWaypts);
541 _approach->addTransition(_transition);
542 } else if (tag == "RunwayTransition") {
543 ArrivalDeparture* ad;
545 // SID waypoints are stored backwards, to share code with STARs
546 std::reverse(_transWaypts.begin(), _transWaypts.end());
552 _transition->setPrimary(_transWaypts);
553 FGRunwayRef rwy = _airport->getRunwayByIdent(_transIdent);
554 ad->addRunwayTransition(rwy, _transition);
555 } else if (tag == "Approach") {
557 } else if (tag == "Sid") {
559 } else if (tag == "Star") {
561 } else if (tag == "Longitude") {
562 _longitude = atof(_text.c_str());
563 } else if (tag == "Latitude") {
564 _latitude = atof(_text.c_str());
565 } else if (tag == "Name") {
567 } else if (tag == "Type") {
569 } else if (tag == "Speed") {
570 _speed = atoi(_text.c_str());
571 } else if (tag == "Altitude") {
572 _altitude = atof(_text.c_str());
573 } else if (tag == "AltitudeRestriction") {
575 _altRestrict = RESTRICT_AT;
576 } else if (_text == "above") {
577 _altRestrict = RESTRICT_ABOVE;
578 } else if (_text == "below") {
579 _altRestrict = RESTRICT_BELOW;
581 throw sg_format_exception("Unrecognized altitude restriction", _text);
583 } else if (tag == "Hld_Rad_or_Inbd") {
584 if (_text == "Inbd") {
587 } else if (tag == "Hld_Time_or_Dist") {
588 _holdDistance = (_text == "Dist");
589 } else if (tag == "Hld_Rad_value") {
590 _holdRadial = atof(_text.c_str());
591 } else if (tag == "Hld_Turn") {
592 _holdRighthanded = (_text == "Right");
593 } else if (tag == "Hld_td_value") {
594 _holdTD = atof(_text.c_str());
595 } else if (tag == "Hdg_Crs_value") {
596 _course = atof(_text.c_str());
597 } else if (tag == "DMEtoIntercept") {
598 _dmeDistance = atof(_text.c_str());
599 } else if (tag == "RadialtoIntercept") {
600 _radial = atof(_text.c_str());
606 Waypt* NavdataVisitor::buildWaypoint(Route* owner)
609 if (_wayptType == "Normal") {
610 // new LatLonWaypoint
611 SGGeod pos(SGGeod::fromDeg(_longitude, _latitude));
612 wp = new BasicWaypt(pos, _wayptName, owner);
613 } else if (_wayptType == "Runway") {
614 string ident = _wayptName.substr(2);
615 FGRunwayRef rwy = _airport->getRunwayByIdent(ident);
616 wp = new RunwayWaypt(rwy, owner);
617 } else if (_wayptType == "Hold") {
618 SGGeod pos(SGGeod::fromDeg(_longitude, _latitude));
619 Hold* h = new Hold(pos, _wayptName, owner);
621 if (_holdRighthanded) {
628 h->setHoldDistance(_holdTD);
630 h->setHoldTime(_holdTD * 60.0);
633 if (_holdRadial >= 0.0) {
634 h->setHoldRadial(_holdRadial);
636 } else if (_wayptType == "Vectors") {
637 wp = new ATCVectors(owner, _airport);
638 } else if ((_wayptType == "Intc") || (_wayptType == "VorRadialIntc")) {
639 SGGeod pos(SGGeod::fromDeg(_longitude, _latitude));
640 wp = new RadialIntercept(owner, _wayptName, pos, _course, _radial);
641 } else if (_wayptType == "DmeIntc") {
642 SGGeod pos(SGGeod::fromDeg(_longitude, _latitude));
643 wp = new DMEIntercept(owner, _wayptName, pos, _course, _dmeDistance);
644 } else if (_wayptType == "ConstHdgtoAlt") {
645 wp = new HeadingToAltitude(owner, _wayptName, _course);
646 } else if (_wayptType == "PBD") {
647 SGGeod pos(SGGeod::fromDeg(_longitude, _latitude)), pos2;
649 SGGeodesy::direct(pos, _course, _dmeDistance, pos2, az2);
650 wp = new BasicWaypt(pos2, _wayptName, owner);
652 SG_LOG(SG_GENERAL, SG_ALERT, "implement waypoint type:" << _wayptType);
653 throw sg_format_exception("Unrecognized waypt type", _wayptType);
657 if ((_altitude > 0.0) && (_altRestrict != RESTRICT_NONE)) {
658 wp->setAltitude(_altitude,_altRestrict);
662 wp->setSpeed(_speed, RESTRICT_AT); // or _BELOW?
668 void NavdataVisitor::finishApproach()
670 WayptVec::iterator it;
673 // find the runway node
674 for (it = _waypoints.begin(); it != _waypoints.end(); ++it) {
675 FGPositionedRef navid = (*it)->source();
680 if (navid->type() == FGPositioned::RUNWAY) {
681 rwy = (FGRunway*) navid.get();
687 throw sg_format_exception("Malformed approach, no runway waypt", _ident);
690 WayptVec primary(_waypoints.begin(), it);
691 // erase all points up to and including the runway, to leave only the
693 _waypoints.erase(_waypoints.begin(), ++it);
695 _approach->setRunway(rwy);
696 _approach->setPrimaryAndMissed(primary, _waypoints);
697 _airport->addApproach(_approach);
701 void NavdataVisitor::finishSid()
703 // reverse order, because that's how we deal with commonality between
704 // STARs and SIDs. SID::route undoes this
705 std::reverse(_waypoints.begin(), _waypoints.end());
706 _sid->setCommon(_waypoints);
707 _airport->addSID(_sid);
711 void NavdataVisitor::finishStar()
713 _star->setCommon(_waypoints);
714 _airport->addSTAR(_star);
718 void NavdataVisitor::data (const char * s, int len)
720 _text += string(s, len);
724 void NavdataVisitor::pi (const char * target, const char * data) {
725 //cout << "Processing instruction " << target << ' ' << data << endl;
728 void NavdataVisitor::warning (const char * message, int line, int column) {
729 SG_LOG(SG_IO, SG_WARN, "Warning: " << message << " (" << line << ',' << column << ')');
732 void NavdataVisitor::error (const char * message, int line, int column) {
733 SG_LOG(SG_IO, SG_ALERT, "Error: " << message << " (" << line << ',' << column << ')');
736 } // of namespace flightgear