X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FATCDCL%2Ftower.cxx;h=e8d0b0db46a319bbb4f3951c664aa6310d7e76fb;hb=a99ea1c7b5fb0ba69810bbad9b6aca8b4cdc116b;hp=e61109bfb10ef6072bf28112b6013ff94f0861f3;hpb=667e64e1ebc86a0c53112b92b53475898f315c36;p=flightgear.git diff --git a/src/ATCDCL/tower.cxx b/src/ATCDCL/tower.cxx index e61109bfb..e8d0b0db4 100644 --- a/src/ATCDCL/tower.cxx +++ b/src/ATCDCL/tower.cxx @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -112,7 +113,7 @@ TowerPlaneRec::TowerPlaneRec(const PlaneRec& p) : plane = p; } -TowerPlaneRec::TowerPlaneRec(const Point3D& pt) : +TowerPlaneRec::TowerPlaneRec(const SGGeod& pt) : planePtr(NULL), clearedToLand(false), clearedToLineUp(false), @@ -140,10 +141,10 @@ TowerPlaneRec::TowerPlaneRec(const Point3D& pt) : isUser(false) { plane.callsign = "UNKNOWN"; - pos = pt; + pos = pt; } -TowerPlaneRec::TowerPlaneRec(const PlaneRec& p, const Point3D& pt) : +TowerPlaneRec::TowerPlaneRec(const PlaneRec& p, const SGGeod& pt) : planePtr(NULL), clearedToLand(false), clearedToLineUp(false), @@ -171,7 +172,7 @@ TowerPlaneRec::TowerPlaneRec(const PlaneRec& p, const Point3D& pt) : isUser(false) { plane = p; - pos = pt; + pos = pt; } @@ -319,9 +320,9 @@ void FGTower::Init() { DoRwyDetails(); // TODO - this currently assumes only one active runway. - rwyOccupied = OnActiveRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0)); + rwyOccupied = OnActiveRunway(SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0)); - if(!OnAnyRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0), false)) { + if(!OnAnyRunway(SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0), false)) { //cout << ident << " ADD 0\n"; current_atcdialog->add_entry(ident, "@AP Tower, @CS @MI miles @CD of the airport for full stop@AT", "Contact tower for VFR arrival (full stop)", TOWER, @@ -448,22 +449,6 @@ void FGTower::Update(double dt) { // Call the base class update for the response time handling. FGATC::Update(dt); - - /* - if(ident == "KEMT") { - // For AI debugging convienience - may be removed - Point3D user_pos; - user_pos.setlon(user_lon_node->getDoubleValue()); - user_pos.setlat(user_lat_node->getDoubleValue()); - user_pos.setelev(user_elev_node->getDoubleValue()); - Point3D user_ortho_pos = ortho.ConvertToLocal(user_pos); - fgSetDouble("/AI/user/ortho-x", user_ortho_pos.x()); - fgSetDouble("/AI/user/ortho-y", user_ortho_pos.y()); - fgSetDouble("/AI/user/elev", user_elev_node->getDoubleValue()); - } - */ - - //cout << "Done T" << endl; } void FGTower::ReceiveUserCallback(int code) { @@ -506,7 +491,7 @@ void FGTower::Respond() { // Should we clear staight in or for downwind entry? // For now we'll clear straight in if greater than 1km from a line drawn through the threshold perpendicular to the rwy. // Later on we might check the actual heading and direct some of those to enter on downwind or base. - Point3D op = ortho.ConvertToLocal(t->pos); + SGVec3d op = ortho.ConvertToLocal(t->pos); float gp = fgGetFloat("/gear/gear/position-norm"); if(gp < 1) t->gearWasUp = true; // This will be needed on final to tell "Gear down, ready to land." @@ -557,7 +542,7 @@ void FGTower::Respond() { trns += " Cleared for take-off" + wtr; t->clearedToTakeOff = true; } else { - if(!OnAnyRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0), true)) { + if(!OnAnyRunway(SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0), true)) { // TODO: Check if any AI Planes on final and tell something like: "After the landing CALLSIGN line up runway two eight right" trns += " Line up runway " + ConvertRwyNumToSpokenString(activeRwy); t->clearedToTakeOff = false; @@ -971,16 +956,16 @@ void FGTower::CheckCircuitList(double dt) { TowerPlaneRec* t = *circuitListItr; //cout << ident << ' ' << circuitList.size() << ' ' << t->plane.callsign << " " << t->leg << " eta " << t->eta << '\n'; if(t->isUser) { - t->pos.setlon(user_lon_node->getDoubleValue()); - t->pos.setlat(user_lat_node->getDoubleValue()); - t->pos.setelev(user_elev_node->getDoubleValue()); + t->pos.setLongitudeDeg(user_lon_node->getDoubleValue()); + t->pos.setLatitudeDeg(user_lat_node->getDoubleValue()); + t->pos.setElevationM(user_elev_node->getDoubleValue()); //cout << ident << ' ' << circuitList.size() << ' ' << t->plane.callsign << " " << t->leg << " eta " << t->eta << '\n'; } else { - t->pos = t->planePtr->GetPos(); // We should probably only set the pos's on one walk through the traffic list in the update function, to save a few CPU should we end up duplicating this. + t->pos = t->planePtr->getPos(); // We should probably only set the pos's on one walk through the traffic list in the update function, to save a few CPU should we end up duplicating this. t->landingType = t->planePtr->GetLandingOption(); //cout << "AI plane landing option is " << t->landingType << '\n'; } - Point3D tortho = ortho.ConvertToLocal(t->pos); + SGVec3d tortho = ortho.ConvertToLocal(t->pos); if(t->isUser) { // Need to figure out which leg he's on //cout << "rwy.hdg = " << rwy.hdg << " user hdg = " << user_hdg_node->getDoubleValue(); @@ -1223,11 +1208,11 @@ void FGTower::CheckRunwayList(double dt) { rwyListItr = rwyList.begin(); TowerPlaneRec* t = *rwyListItr; if(t->isUser) { - t->pos.setlon(user_lon_node->getDoubleValue()); - t->pos.setlat(user_lat_node->getDoubleValue()); - t->pos.setelev(user_elev_node->getDoubleValue()); + t->pos.setLongitudeDeg(user_lon_node->getDoubleValue()); + t->pos.setLatitudeDeg(user_lat_node->getDoubleValue()); + t->pos.setElevationM(user_elev_node->getDoubleValue()); } else { - t->pos = t->planePtr->GetPos(); // We should probably only set the pos's on one walk through the traffic list in the update function, to save a few CPU should we end up duplicating this. + t->pos = t->planePtr->getPos(); // We should probably only set the pos's on one walk through the traffic list in the update function, to save a few CPU should we end up duplicating this. } bool on_rwy = OnActiveRunway(t->pos); if(!on_rwy) { @@ -1287,15 +1272,15 @@ void FGTower::CheckApproachList(double dt) { //cout << "t = " << t << endl; //cout << "Checking " << t->plane.callsign << endl; if(t->isUser) { - t->pos.setlon(user_lon_node->getDoubleValue()); - t->pos.setlat(user_lat_node->getDoubleValue()); - t->pos.setelev(user_elev_node->getDoubleValue()); + t->pos.setLongitudeDeg(user_lon_node->getDoubleValue()); + t->pos.setLatitudeDeg(user_lat_node->getDoubleValue()); + t->pos.setElevationM(user_elev_node->getDoubleValue()); } else { // TODO - set/update the position if it's an AI plane } doThresholdETACalc(); // We need this here because planes in the lists are not guaranteed to *always* have the correct ETA //cout << "eta is " << t->eta << ", rwy is " << (rwyList.size() ? "occupied " : "clear ") << '\n'; - Point3D tortho = ortho.ConvertToLocal(t->pos); + SGVec3d tortho = ortho.ConvertToLocal(t->pos); if(t->eta < 12 && rwyList.size() && !(t->instructedToGoAround)) { // TODO - need to make this more sophisticated // eg. is the plane accelerating down the runway taking off [OK], @@ -1424,11 +1409,11 @@ void FGTower::CheckDepartureList(double dt) { //cout << "Dep list, checking " << t->plane.callsign; double distout; // meters - if(t->isUser) distout = dclGetHorizontalSeparation(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())); - else distout = dclGetHorizontalSeparation(Point3D(lon, lat, elev), t->planePtr->GetPos()); + if(t->isUser) distout = dclGetHorizontalSeparation(SGGeod::fromDegM(lon, lat, elev), SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())); + else distout = dclGetHorizontalSeparation(SGGeod::fromDegM(lon, lat, elev), t->planePtr->getPos()); //cout << " distout = " << distout << '\n'; if(t->isUser && !(t->clearedToTakeOff)) { // HACK - we use clearedToTakeOff to check if ATC already contacted with plane (and cleared take-off) or not - if(!OnAnyRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0), false)) { + if(!OnAnyRunway(SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0), false)) { current_atcdialog->remove_entry(ident, USER_REQUEST_TAKE_OFF, TOWER); t->clearedToTakeOff = true; // FIXME } @@ -1510,70 +1495,58 @@ void FGTower::DoRwyDetails() { // Based on the airport-id and wind get the active runway - //wind - double hdg = wind_from_hdg->getDoubleValue(); - double speed = wind_speed_knots->getDoubleValue(); - hdg = (speed == 0.0 ? 270.0 : hdg); - //cout << "Heading = " << hdg << '\n'; - - FGRunway runway; - bool rwyGood = globals->get_runways()->search(ident, int(hdg), &runway); - if(rwyGood) { - //cout << "RUNWAY GOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOD\n"; - activeRwy = runway._rwy_no; - rwy.rwyID = runway._rwy_no; - SG_LOG(SG_ATC, SG_INFO, "Active runway for airport " << ident << " is " << activeRwy); - - // Get the threshold position - double other_way = runway._heading - 180.0; - while(other_way <= 0.0) { - other_way += 360.0; - } - // move to the +l end/center of the runway - //cout << "Runway center is at " << runway._lon << ", " << runway._lat << '\n'; - Point3D origin = Point3D(runway._lon, runway._lat, aptElev); - Point3D ref = origin; - double tshlon, tshlat, tshr; - double tolon, tolat, tor; - rwy.length = runway._length * SG_FEET_TO_METER; - rwy.width = runway._width * SG_FEET_TO_METER; - geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way, - rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr ); - geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), runway._heading, - rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor ); - // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user. - // now copy what we need out of runway into rwy - rwy.threshold_pos = Point3D(tshlon, tshlat, aptElev); - Point3D takeoff_end = Point3D(tolon, tolat, aptElev); - //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n'; - //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n'; - rwy.hdg = runway._heading; - // Set the projection for the local area based on this active runway - ortho.Init(rwy.threshold_pos, rwy.hdg); - rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero - rwy.end2ortho = ortho.ConvertToLocal(takeoff_end); - - // Set the pattern direction - // TODO - we'll check for a facilities file with this in eventually - for now assume left traffic except - // for certain circumstances (RH parallel rwy). - rwy.patternDirection = -1; // Left - if(rwy.rwyID.size() == 3) { - rwy.patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1); - } - //cout << "Doing details, rwy.patterDirection is " << rwy.patternDirection << '\n'; - } else { - SG_LOG(SG_ATC, SG_ALERT, "Help - can't get good runway in FGTower!!"); - activeRwy = "NN"; - } + const FGAirport* apt = fgFindAirportID(ident); + assert(apt); + FGRunway* runway = apt->getActiveRunwayForUsage(); + + activeRwy = runway->ident(); + rwy.rwyID = runway->ident(); + SG_LOG(SG_ATC, SG_INFO, "In FGGround, active runway for airport " << ident << " is " << activeRwy); + + // Get the threshold position + double other_way = runway->headingDeg() - 180.0; + while(other_way <= 0.0) { + other_way += 360.0; + } + // move to the +l end/center of the runway + //cout << "Runway center is at " << runway._lon << ", " << runway._lat << '\n'; + double tshlon, tshlat, tshr; + double tolon, tolat, tor; + rwy.length = runway->lengthM(); + geo_direct_wgs_84 ( aptElev, runway->latitude(), runway->longitude(), other_way, + rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr ); + geo_direct_wgs_84 ( aptElev, runway->latitude(), runway->longitude(), runway->headingDeg(), + rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor ); + + // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user. + // now copy what we need out of runway into rwy + rwy.threshold_pos = SGGeod::fromDegM(tshlon, tshlat, aptElev); + SGGeod takeoff_end = SGGeod::fromDegM(tolon, tolat, aptElev); + //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n'; + //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n'; + rwy.hdg = runway->headingDeg(); + // Set the projection for the local area based on this active runway + ortho.Init(rwy.threshold_pos, rwy.hdg); + rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero + rwy.end2ortho = ortho.ConvertToLocal(takeoff_end); + + // Set the pattern direction + // TODO - we'll check for a facilities file with this in eventually - for now assume left traffic except + // for certain circumstances (RH parallel rwy). + rwy.patternDirection = -1; // Left + if(rwy.rwyID.size() == 3) { + rwy.patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1); + } + //cout << "Doing details, rwy.patterDirection is " << rwy.patternDirection << '\n'; } // Figure out if a given position lies on the active runway // Might have to change when we consider more than one active rwy. -bool FGTower::OnActiveRunway(const Point3D& pt) { +bool FGTower::OnActiveRunway(const SGGeod& pt) { // TODO - check that the centre calculation below isn't confused by displaced thesholds etc. - Point3D xyc((rwy.end1ortho.x() + rwy.end2ortho.x())/2.0, (rwy.end1ortho.y() + rwy.end2ortho.y())/2.0, 0.0); - Point3D xyp = ortho.ConvertToLocal(pt); + SGVec3d xyc((rwy.end1ortho.x() + rwy.end2ortho.x())/2.0, (rwy.end1ortho.y() + rwy.end2ortho.y())/2.0, 0.0); + SGVec3d xyp = ortho.ConvertToLocal(pt); //cout << "Length offset = " << fabs(xyp.y() - xyc.y()) << '\n'; //cout << "Width offset = " << fabs(xyp.x() - xyc.x()) << '\n'; @@ -1588,7 +1561,7 @@ bool FGTower::OnActiveRunway(const Point3D& pt) { // Figure out if a given position lies on any runway or not // Only call this at startup - reading the runways database is expensive and needs to be fixed! -bool FGTower::OnAnyRunway(const Point3D& pt, bool onGround) { +bool FGTower::OnAnyRunway(const SGGeod& pt, bool onGround) { ATCData ad; double dist = current_commlist->FindClosest(lon, lat, elev, ad, TOWER, 7.0); if(dist < 0.0) { @@ -1596,28 +1569,27 @@ bool FGTower::OnAnyRunway(const Point3D& pt, bool onGround) { } // Based on the airport-id, go through all the runways and check for a point in them - - // TODO - do we actually need to search for the airport - surely we already know our ident and - // can just search runways of our airport??? - //cout << "Airport ident is " << ad.ident << '\n'; - FGRunway runway; - bool rwyGood = globals->get_runways()->search(ad.ident, &runway); - if(!rwyGood) { - SG_LOG(SG_ATC, SG_WARN, "Unable to find any runways for airport ID " << ad.ident << " in FGTower"); - } - bool on = false; - while(runway._id == ad.ident) { - on = OnRunway(pt, runway); - //cout << "Runway " << runway._rwy_no << ": On = " << (on ? "true\n" : "false\n"); - if(on) { - if(onGround == false) - return(true); - if(runway._rwy_no != "xx") - return(true); - } - globals->get_runways()->next(&runway); - } - return(on); + + const FGAirport* apt = fgFindAirportID(ad.ident); + assert(apt); + + for (unsigned int i=0; inumRunways(); ++i) { + if (OnRunway(pt, apt->getRunwayByIndex(i))) { + return true; + } + } + + // if onGround is true, we only match real runways, so we're done + if (onGround) return false; + + // try taxiways as well + for (unsigned int i=0; inumTaxiways(); ++i) { + if (OnRunway(pt, apt->getTaxiwayByIndex(i))) { + return true; + } + } + + return false; } @@ -1842,7 +1814,7 @@ void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) { // Sign convention - dist_out is -ve for approaching planes and +ve for departing planes // dist_across is +ve in the pattern direction - ie a plane correctly on downwind will have a +ve dist_across - Point3D op = ortho.ConvertToLocal(tpr->pos); + SGVec3d op = ortho.ConvertToLocal(tpr->pos); //if(printout) { //if(!tpr->isUser) cout << "Orthopos is " << op.x() << ", " << op.y() << ' '; //cout << "opType is " << tpr->opType << '\n'; @@ -1962,7 +1934,7 @@ void FGTower::doThresholdETACalc() { // Do the approach list first for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) { TowerPlaneRec* tpr = *twrItr; - if(!(tpr->isUser)) tpr->pos = tpr->planePtr->GetPos(); + if(!(tpr->isUser)) tpr->pos = tpr->planePtr->getPos(); //cout << "APP: "; CalcETA(tpr); } @@ -1970,7 +1942,7 @@ void FGTower::doThresholdETACalc() { //cout << "Circuit list size is " << circuitList.size() << '\n'; for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) { TowerPlaneRec* tpr = *twrItr; - if(!(tpr->isUser)) tpr->pos = tpr->planePtr->GetPos(); + if(!(tpr->isUser)) tpr->pos = tpr->planePtr->getPos(); //cout << "CIRC: "; CalcETA(tpr); } @@ -2061,7 +2033,7 @@ void FGTower::ContactAtHoldShort(const PlaneRec& plane, FGAIPlane* requestee, to t->clearedToLineUp = false; t->clearedToTakeOff = false; t->opType = operation; - t->pos = requestee->GetPos(); + t->pos = requestee->getPos(); //cout << "Hold Short reported by " << plane.callsign << '\n'; SG_LOG(SG_ATC, SG_BULK, "Hold Short reported by " << plane.callsign); @@ -2093,7 +2065,7 @@ void FGTower::RegisterAIPlane(const PlaneRec& plane, FGAIPlane* ai, const tower_ t->planePtr = ai; t->opType = op; t->leg = lg; - t->pos = ai->GetPos(); + t->pos = ai->getPos(); CalcETA(t); @@ -2127,9 +2099,9 @@ void FGTower::VFRArrivalContact(const string& ID, const LandingType& opt) { //cout << "NOT t\n"; t = new TowerPlaneRec; t->isUser = true; - t->pos.setlon(user_lon_node->getDoubleValue()); - t->pos.setlat(user_lat_node->getDoubleValue()); - t->pos.setelev(user_elev_node->getDoubleValue()); + t->pos.setLongitudeDeg(user_lon_node->getDoubleValue()); + t->pos.setLatitudeDeg(user_lat_node->getDoubleValue()); + t->pos.setElevationM(user_elev_node->getDoubleValue()); } else { //cout << "IS t\n"; // Oops - the plane is already registered with this tower - maybe we took off and flew a giant circuit without @@ -2173,7 +2145,7 @@ void FGTower::VFRArrivalContact(const PlaneRec& plane, FGAIPlane* requestee, con t->plane = plane; t->planePtr = requestee; t->landingType = lt; - t->pos = requestee->GetPos(); + t->pos = requestee->getPos(); //cout << "Hold Short reported by " << plane.callsign << '\n'; SG_LOG(SG_ATC, SG_BULK, "VFR arrival contact made by " << plane.callsign); @@ -2399,12 +2371,12 @@ void FGTower::ReportDownwind(const string& ID) { RemoveFromAppList(ID); t->leg = DOWNWIND; if(t->isUser) { - t->pos.setlon(user_lon_node->getDoubleValue()); - t->pos.setlat(user_lat_node->getDoubleValue()); - t->pos.setelev(user_elev_node->getDoubleValue()); + t->pos.setLongitudeDeg(user_lon_node->getDoubleValue()); + t->pos.setLatitudeDeg(user_lat_node->getDoubleValue()); + t->pos.setElevationM(user_elev_node->getDoubleValue()); } else { // ASSERT(t->planePtr != NULL); - t->pos = t->planePtr->GetPos(); + t->pos = t->planePtr->getPos(); } CalcETA(t); AddToCircuitList(t); @@ -2428,12 +2400,12 @@ void FGTower::ReportGoingAround(const string& ID) { RemoveFromAppList(ID); t->leg = CLIMBOUT; if(t->isUser) { - t->pos.setlon(user_lon_node->getDoubleValue()); - t->pos.setlat(user_lat_node->getDoubleValue()); - t->pos.setelev(user_elev_node->getDoubleValue()); + t->pos.setLongitudeDeg(user_lon_node->getDoubleValue()); + t->pos.setLatitudeDeg(user_lat_node->getDoubleValue()); + t->pos.setElevationM(user_elev_node->getDoubleValue()); } else { // ASSERT(t->planePtr != NULL); - t->pos = t->planePtr->GetPos(); + t->pos = t->planePtr->getPos(); } CalcETA(t); AddToCircuitList(t); @@ -2556,7 +2528,7 @@ string FGTower::GenText(const string& m, int c) { if (rwyOccupied) { tmp = "Ready for take-off"; } else { - if (OnAnyRunway(Point3D(user_lon_node->getDoubleValue(), + if (OnAnyRunway(SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0),true)) { tmp = "Request take-off clearance"; } else { @@ -2568,7 +2540,7 @@ string FGTower::GenText(const string& m, int c) { else if ( strcmp ( tag, "@MI" ) == 0 ) { char buf[10]; //sprintf( buf, "%3.1f", tpars.miles ); - int dist_miles = (int)dclGetHorizontalSeparation(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())) / 1600; + int dist_miles = (int)dclGetHorizontalSeparation(SGGeod::fromDegM(lon, lat, elev), SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())) / 1600; sprintf(buf, "%i", dist_miles); strcat( &dum[0], &buf[0] ); } @@ -2588,7 +2560,7 @@ string FGTower::GenText(const string& m, int c) { } } else if(strcmp(tag, "@CD") == 0) { // @CD = compass direction - double h = GetHeadingFromTo(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())); + double h = GetHeadingFromTo(SGGeod::fromDegM(lon, lat, elev), SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())); while(h < 0.0) h += 360.0; while(h > 360.0) h -= 360.0; if(h < 22.5 || h > 337.5) {