// route.cxx - classes supporting waypoints and route structures // Written by James Turner, started 2009. // // Copyright (C) 2009 Curtis L. Olson // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "config.h" #include "route.hxx" // std #include #include // SimGear #include #include #include #include #include #include #include // FlightGear #include
#include "Main/fg_props.hxx" #include #include #include #include #include using std::string; using std::vector; using std::endl; using std::fstream; namespace flightgear { const double NO_MAG_VAR = -1000.0; // an impossible mag-var value bool isMachRestrict(RouteRestriction rr) { return (rr == SPEED_RESTRICT_MACH) || (rr == SPEED_COMPUTED_MACH); } namespace { double magvarDegAt(const SGGeod& pos) { double jd = globals->get_time_params()->getJD(); return sgGetMagVar(pos, jd) * SG_RADIANS_TO_DEGREES; } WayptRef intersectionFromString(FGPositionedRef p1, const SGGeod& basePosition, const double magvar, const string_list& pieces) { assert(pieces.size() == 4); // navid/radial/navid/radial notation FGPositionedRef p2 = FGPositioned::findClosestWithIdent(pieces[2], basePosition); if (!p2) { SG_LOG(SG_NAVAID, SG_INFO, "Unable to find FGPositioned with ident:" << pieces[2]); return {}; } double r1 = atof(pieces[1].c_str()), r2 = atof(pieces[3].c_str()); r1 += magvar; r2 += magvar; SGGeod intersection; bool ok = SGGeodesy::radialIntersection(p1->geod(), r1, p2->geod(), r2, intersection); if (!ok) { SG_LOG(SG_NAVAID, SG_INFO, "no valid intersection for:" << pieces[0] << "/" << pieces[2]); return {}; } std::string name = p1->ident() + "-" + p2->ident(); return new BasicWaypt(intersection, name, nullptr); } WayptRef viaFromString(const SGGeod& basePosition, const std::string& target) { assert(target.find("VIA-") == 0); string_list pieces(simgear::strutils::split(target.substr(4), "/")); if (pieces.size() != 2) { SG_LOG(SG_NAVAID, SG_WARN, "Malformed VIA specification string:" << target); return {}; } // TO navaid is pieces[1] FGPositionedRef nav = FGPositioned::findClosestWithIdent(pieces[1], basePosition, nullptr); if (!nav) { SG_LOG(SG_NAVAID, SG_WARN, "TO navaid:" << pieces[3] << " unknown"); return {}; } // airway ident is pieces[1] AirwayRef airway = Airway::findByIdentAndNavaid(pieces[0], nav); if (!airway) { SG_LOG(SG_NAVAID, SG_WARN, "Unknown airway:" << pieces[0]); return {}; } return new Via(nullptr, airway, nav); } } // namespace Waypt::Waypt(RouteBase* aOwner) : _owner(aOwner), _magVarDeg(NO_MAG_VAR) { } Waypt::~Waypt() { } std::string Waypt::ident() const { return {}; } bool Waypt::flag(WayptFlag aFlag) const { return ((_flags & aFlag) != 0); } void Waypt::setFlag(WayptFlag aFlag, bool aV) { if (aFlag == 0) { throw sg_range_exception("invalid waypoint flag set"); } _flags = (_flags & ~aFlag); if (aV) _flags |= aFlag; } bool Waypt::matches(Waypt* aOther) const { assert(aOther); if (ident() != aOther->ident()) { // cheap check first return false; } return matches(aOther->position()); } bool Waypt::matches(FGPositioned* aPos) const { if (!aPos) return false; // if w ehave no source, match on position and ident if (!source()) { return (ident() == aPos->ident()) && matches(aPos->geod()); } return (aPos == source()); } bool Waypt::matches(const SGGeod& aPos) const { double d = SGGeodesy::distanceM(position(), aPos); return (d < 100.0); // 100 metres seems plenty } void Waypt::setAltitude(double aAlt, RouteRestriction aRestrict) { _altitudeFt = aAlt; _altRestrict = aRestrict; } void Waypt::setSpeed(double aSpeed, RouteRestriction aRestrict) { _speed = aSpeed; _speedRestrict = aRestrict; } double Waypt::speedKts() const { assert(_speedRestrict != SPEED_RESTRICT_MACH); return speed(); } double Waypt::speedMach() const { assert(_speedRestrict == SPEED_RESTRICT_MACH); return speed(); } double Waypt::magvarDeg() const { if (_magVarDeg == NO_MAG_VAR) { // derived classes with a default pos must override this method assert(!(position() == SGGeod())); double jd = globals->get_time_params()->getJD(); _magVarDeg = sgGetMagVar(position(), jd) * SG_RADIANS_TO_DEGREES; } return _magVarDeg; } double Waypt::headingRadialDeg() const { return 0.0; } std::string Waypt::icaoDescription() const { return ident(); } /////////////////////////////////////////////////////////////////////////// // persistence static RouteRestriction restrictionFromString(const char* aStr) { std::string l = simgear::strutils::lowercase(aStr); if (l == "at") return RESTRICT_AT; if (l == "above") return RESTRICT_ABOVE; if (l == "below") return RESTRICT_BELOW; if (l == "none") return RESTRICT_NONE; if (l == "mach") return SPEED_RESTRICT_MACH; if (l.empty()) return RESTRICT_NONE; throw sg_io_exception("unknown restriction specification:" + l, "Route restrictFromString"); } const char* restrictionToString(RouteRestriction aRestrict) { switch (aRestrict) { case RESTRICT_AT: return "at"; case RESTRICT_BELOW: return "below"; case RESTRICT_ABOVE: return "above"; case RESTRICT_NONE: return "none"; case SPEED_RESTRICT_MACH: return "mach"; default: throw sg_exception("invalid route restriction", "Route restrictToString"); } } WayptRef Waypt::createInstance(RouteBase* aOwner, const std::string& aTypeName) { WayptRef r; if (aTypeName == "basic") { r = new BasicWaypt(aOwner); } else if (aTypeName == "navaid") { r = new NavaidWaypoint(aOwner); } else if (aTypeName == "offset-navaid") { r = new OffsetNavaidWaypoint(aOwner); } else if (aTypeName == "hold") { r = new Hold(aOwner); } else if (aTypeName == "runway") { r = new RunwayWaypt(aOwner); } else if (aTypeName == "hdgToAlt") { r = new HeadingToAltitude(aOwner); } else if (aTypeName == "dmeIntercept") { r = new DMEIntercept(aOwner); } else if (aTypeName == "radialIntercept") { r = new RadialIntercept(aOwner); } else if (aTypeName == "vectors") { r = new ATCVectors(aOwner); } else if (aTypeName == "discontinuity") { r = new Discontinuity(aOwner); } else if (aTypeName == "via") { r = new Via(aOwner); } if (!r || (r->type() != aTypeName)) { throw sg_exception("broken factory method for type:" + aTypeName, "Waypt::createInstance"); } return r; } WayptRef Waypt::createFromProperties(RouteBase* aOwner, SGPropertyNode_ptr aProp) { if (!aProp->hasChild("type")) { SG_LOG(SG_GENERAL, SG_WARN, "Bad waypoint node: missing type"); return {}; } flightgear::AirwayRef via; if (aProp->hasChild("airway")) { auto level = Airway::Both; if (aProp->hasValue("network")) { level = static_cast(aProp->getIntValue("network")); } via = flightgear::Airway::findByIdent(aProp->getStringValue("airway"), level); if (via) { // override owner if we are from an airway aOwner = via.get(); } } WayptRef nd(createInstance(aOwner, aProp->getStringValue("type"))); if (nd->initFromProperties(aProp)) { return nd; } SG_LOG(SG_GENERAL, SG_WARN, "failed to create waypoint, trying basic"); // if we failed to make the waypoint, try again making a basic waypoint. // this handles the case where a navaid waypoint is missing, for example // we also reject navaids that don't look correct (too far form the specified // lat-lon, eg see https://sourceforge.net/p/flightgear/codetickets/1814/ ) // and again fallback to here. WayptRef bw(new BasicWaypt(aOwner)); if (bw->initFromProperties(aProp)) { return bw; } return {}; // total failure } WayptRef Waypt::fromLatLonString(RouteBase* aOwner, const std::string& target) { // permit lat,lon/radial/offset format string_list pieces(simgear::strutils::split(target, "/")); if ((pieces.size() != 1) && (pieces.size() != 3)) { return {}; } SGGeod g; const bool defaultToLonLat = true; // parseStringAsGeod would otherwise default to lat,lon if (!simgear::strutils::parseStringAsGeod(pieces[0], &g, defaultToLonLat)) { return {}; } if (pieces.size() == 3) { // process offset const double bearing = std::stod(pieces[1]); const double distanceNm = std::stod(pieces[2]); g = SGGeodesy::direct(g, bearing, distanceNm * SG_NM_TO_METER); } // build a short name const int lonDeg = static_cast(g.getLongitudeDeg()); const int latDeg = static_cast(g.getLatitudeDeg()); char buf[32]; char ew = (lonDeg < 0) ? 'W' : 'E'; char ns = (latDeg < 0) ? 'S' : 'N'; snprintf(buf, 32, "%c%03d%c%03d", ew, std::abs(lonDeg), ns, std::abs(latDeg)); return new BasicWaypt(g, buf, aOwner); } WayptRef Waypt::createFromString(RouteBase* aOwner, const std::string& s, const SGGeod& aVicinity) { auto vicinity = aVicinity; if (!vicinity.isValid()) { vicinity = globals->get_aircraft_position(); } auto target = simgear::strutils::uppercase(s); // extract altitude double altFt = 0.0; RouteRestriction altSetting = RESTRICT_NONE; size_t pos = target.find('@'); if (pos != string::npos) { altFt = std::stof(target.substr(pos + 1)); target = target.substr(0, pos); if (fgGetString("/sim/startup/units") == "meter") { altFt *= SG_METER_TO_FEET; } altSetting = RESTRICT_AT; } // check for lon,lat WayptRef wpt = fromLatLonString(aOwner, target); const double magvar = magvarDegAt(vicinity); if (wpt) { // already handled in the lat/lon test above } else if (target.find("VIA-") == 0) { wpt = viaFromString(vicinity, target); } else { FGPositioned::TypeFilter filter({FGPositioned::Type::AIRPORT, FGPositioned::Type::HELIPORT, FGPositioned::Type::SEAPORT, FGPositioned::Type::NDB, FGPositioned::Type::VOR, FGPositioned::Type::FIX, FGPositioned::Type::WAYPOINT}); string_list pieces(simgear::strutils::split(target, "/")); FGPositionedRef p = FGPositioned::findClosestWithIdent(pieces.front(), vicinity, &filter); if (!p) { SG_LOG(SG_NAVAID, SG_INFO, "Unable to find FGPositioned with ident:" << pieces.front()); return {}; } if (pieces.size() == 1) { wpt = new NavaidWaypoint(p, aOwner); } else if (pieces.size() == 3) { // navaid/radial/distance-nm notation double radial = atof(pieces[1].c_str()), distanceNm = atof(pieces[2].c_str()); radial += magvar; wpt = new OffsetNavaidWaypoint(p, aOwner, radial, distanceNm); } else if (pieces.size() == 2) { FGAirport* apt = dynamic_cast(p.ptr()); if (!apt) { SG_LOG(SG_NAVAID, SG_INFO, "Waypoint is not an airport:" << pieces.front()); return {}; } if (!apt->hasRunwayWithIdent(pieces[1])) { SG_LOG(SG_NAVAID, SG_INFO, "No runway: " << pieces[1] << " at " << pieces[0]); return {}; } FGRunway* runway = apt->getRunwayByIdent(pieces[1]); wpt = new NavaidWaypoint(runway, aOwner); } else if (pieces.size() == 4) { wpt = intersectionFromString(p, vicinity, magvar, pieces); } } if (!wpt) { SG_LOG(SG_NAVAID, SG_INFO, "Unable to parse waypoint:" << target); return {}; } if (altSetting != RESTRICT_NONE) { wpt->setAltitude(altFt, altSetting); } return wpt; } void Waypt::saveAsNode(SGPropertyNode* n) const { n->setStringValue("type", type()); writeToProperties(n); } bool Waypt::initFromProperties(SGPropertyNode_ptr aProp) { if (aProp->hasChild("generated")) { setFlag(WPT_GENERATED, aProp->getBoolValue("generated")); } if (aProp->hasChild("overflight")) { setFlag(WPT_OVERFLIGHT, aProp->getBoolValue("overflight")); } if (aProp->hasChild("arrival")) { setFlag(WPT_ARRIVAL, aProp->getBoolValue("arrival")); } if (aProp->hasChild("approach")) { setFlag(WPT_APPROACH, aProp->getBoolValue("approach")); } if (aProp->hasChild("departure")) { setFlag(WPT_DEPARTURE, aProp->getBoolValue("departure")); } if (aProp->hasChild("miss")) { setFlag(WPT_MISS, aProp->getBoolValue("miss")); } if (aProp->hasChild("airway")) { setFlag(WPT_VIA, true); } if (aProp->hasChild("alt-restrict")) { _altRestrict = restrictionFromString(aProp->getStringValue("alt-restrict").c_str()); _altitudeFt = aProp->getDoubleValue("altitude-ft"); } if (aProp->hasChild("speed-restrict")) { _speedRestrict = restrictionFromString(aProp->getStringValue("speed-restrict").c_str()); _speed = aProp->getDoubleValue("speed"); } return true; } void Waypt::writeToProperties(SGPropertyNode_ptr aProp) const { if (flag(WPT_OVERFLIGHT)) { aProp->setBoolValue("overflight", true); } if (flag(WPT_DEPARTURE)) { aProp->setBoolValue("departure", true); } if (flag(WPT_ARRIVAL)) { aProp->setBoolValue("arrival", true); } if (flag(WPT_APPROACH)) { aProp->setBoolValue("approach", true); } if (flag(WPT_VIA) && _owner) { flightgear::AirwayRef awy = (flightgear::Airway*) _owner; aProp->setStringValue("airway", awy->ident()); aProp->setIntValue("network", awy->level()); } if (flag(WPT_MISS)) { aProp->setBoolValue("miss", true); } if (flag(WPT_GENERATED)) { aProp->setBoolValue("generated", true); } if (_altRestrict != RESTRICT_NONE) { aProp->setStringValue("alt-restrict", restrictionToString(_altRestrict)); aProp->setDoubleValue("altitude-ft", _altitudeFt); } if (_speedRestrict != RESTRICT_NONE) { aProp->setStringValue("speed-restrict", restrictionToString(_speedRestrict)); aProp->setDoubleValue("speed", _speed); } } RouteBase::~RouteBase() { } void RouteBase::dumpRouteToKML(const WayptVec& aRoute, const std::string& aName) { SGPath p = "/Users/jmt/Desktop/" + aName + ".kml"; sg_ofstream f(p); if (!f.is_open()) { SG_LOG(SG_NAVAID, SG_WARN, "unable to open:" << p); return; } // pre-amble f << "\n" "\n" "\n"; dumpRouteToKMLLineString(aName, aRoute, f); // post-amble f << "\n" "" << endl; f.close(); } void RouteBase::dumpRouteToKMLLineString(const std::string& aIdent, const WayptVec& aRoute, std::ostream& aStream) { // preamble aStream << "\n"; aStream << "" << aIdent << "\n"; aStream << "\n"; aStream << "1\n"; aStream << "\n"; // waypoints for (unsigned int i=0; iposition(); aStream << pos.getLongitudeDeg() << "," << pos.getLatitudeDeg() << " " << endl; } // postable aStream << "\n" "\n" "\n" << endl; } void RouteBase::loadAirportProcedures(const SGPath& aPath, FGAirport* aApt) { assert(aApt); try { NavdataVisitor visitor(aApt, aPath); readXML(aPath, visitor); } catch (sg_io_exception& ex) { SG_LOG(SG_NAVAID, SG_WARN, "failure parsing procedures: " << aPath << "\n\t" << ex.getMessage() << "\n\tat:" << ex.getLocation().asString()); } catch (sg_exception& ex) { SG_LOG(SG_NAVAID, SG_WARN, "failure parsing procedures: " << aPath << "\n\t" << ex.getMessage()); } } } // of namespace flightgear