2002-10-02 15:27:49 +00:00
// ATCutils.cxx - Utility functions for the ATC / AI system
//
// Written by David Luff, started March 2002.
//
// Copyright (C) 2002 David C Luff - david.luff@nottingham.ac.uk
//
// 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., 675 Mass Ave, Cambridge, MA 02139, USA.
2002-04-04 20:50:05 +00:00
# include <math.h>
# include <simgear/math/point3d.hxx>
# include <simgear/constants.h>
2003-03-05 21:34:58 +00:00
# include <simgear/misc/sg_path.hxx>
# include <simgear/debug/logstream.hxx>
2002-04-04 20:50:05 +00:00
# include <plib/sg.h>
2002-12-04 20:05:30 +00:00
//#include <iomanip.h>
2002-10-02 15:27:49 +00:00
2003-03-05 21:34:58 +00:00
# include <Airports/runways.hxx>
# include <Main/globals.hxx>
2002-10-02 15:27:49 +00:00
# include "ATCutils.hxx"
2003-03-11 13:25:14 +00:00
# include "ATCProjection.hxx"
2002-10-02 15:27:49 +00:00
2002-12-04 20:05:30 +00:00
// Convert any number to spoken digits
string ConvertNumToSpokenDigits ( string n ) {
//cout << "n = " << n << endl;
string nums [ 10 ] = { " zero " , " one " , " two " , " three " , " four " , " five " , " six " , " seven " , " eight " , " nine " } ;
string pt = " decimal " ;
string str = " " ;
for ( unsigned int i = 0 ; i < n . length ( ) ; + + i ) {
//cout << "n.substr(" << i << ",1 = " << n.substr(i,1) << endl;
if ( n . substr ( i , 1 ) = = " " ) {
// do nothing
} else if ( n . substr ( i , 1 ) = = " . " ) {
str + = pt ;
} else {
str + = nums [ atoi ( ( n . substr ( i , 1 ) ) . c_str ( ) ) ] ;
}
if ( i ! = ( n . length ( ) - 1 ) ) { // ie. don't add a space at the end.
str + = " " ;
}
}
return ( str ) ;
}
2002-10-02 15:27:49 +00:00
2003-09-19 16:48:27 +00:00
// Convert an integer to spoken digits
string ConvertNumToSpokenDigits ( int n ) {
char buf [ 12 ] ; // should be big enough!!
sprintf ( buf , " %i " , n ) ;
string tempstr1 = buf ;
return ( ConvertNumToSpokenDigits ( tempstr1 ) ) ;
}
2002-12-04 20:05:30 +00:00
// Convert a 2 digit rwy number to a spoken-style string
string ConvertRwyNumToSpokenString ( int n ) {
string nums [ 10 ] = { " zero " , " one " , " two " , " three " , " four " , " five " , " six " , " seven " , " eight " , " nine " } ;
// Basic error/sanity checking
while ( n < 0 ) {
n + = 36 ;
}
while ( n > 36 ) {
n - = 36 ;
}
if ( n = = 0 ) {
n = 36 ; // Is this right?
}
string str = " " ;
int index = n / 10 ;
str + = nums [ index ] ;
n - = ( index * 10 ) ;
//str += "-";
str + = " " ; //Changed this for the benefit of the voice token parser - prefer the "-" in the visual output though.
str + = nums [ n ] ;
return ( str ) ;
2002-10-02 15:27:49 +00:00
}
2003-04-15 10:37:58 +00:00
// Assumes we get a two-digit string optionally appended with L, R or C
2003-03-05 21:34:58 +00:00
// eg 01 07L 29R 36
// Anything else is not guaranteed to be handled correctly!
string ConvertRwyNumToSpokenString ( string s ) {
if ( s . size ( ) < 3 ) {
return ( ConvertRwyNumToSpokenString ( atoi ( s . c_str ( ) ) ) ) ;
} else {
string r = ConvertRwyNumToSpokenString ( atoi ( s . substr ( 0 , 2 ) . c_str ( ) ) ) ;
2003-04-15 10:37:58 +00:00
if ( s . substr ( 2 , 1 ) = = " L " ) {
r + = " left " ;
} else if ( s . substr ( 2 , 1 ) = = " R " ) {
r + = " right " ;
} else if ( s . substr ( 2 , 1 ) = = " C " ) {
r + = " center " ;
} else {
SG_LOG ( SG_ATC , SG_WARN , " WARNING: Unknown suffix " < < s . substr ( 2 , 1 ) < < " from runway ID " < < s < < " in ConvertRwyNumToSpokenString(...) " ) ;
}
return ( r ) ;
2003-03-05 21:34:58 +00:00
}
}
2002-10-02 15:27:49 +00:00
// Return the phonetic letter of a letter represented as an integer 1->26
string GetPhoneticIdent ( int i ) {
2002-12-04 20:05:30 +00:00
// TODO - Check i is between 1 and 26 and wrap if necessary
2004-01-23 17:18:24 +00:00
return ( GetPhoneticIdent ( char ( ' a ' + ( i - 1 ) ) ) ) ;
}
// Return the phonetic letter of a character in the range a-z or A-Z.
// Currently always returns prefixed by lowercase.
string GetPhoneticIdent ( char c ) {
c = tolower ( c ) ;
// TODO - Check c is between a and z and wrap if necessary
switch ( c ) {
case ' a ' : return ( " alpha " ) ;
case ' b ' : return ( " bravo " ) ;
case ' c ' : return ( " charlie " ) ;
case ' d ' : return ( " delta " ) ;
case ' e ' : return ( " echo " ) ;
case ' f ' : return ( " foxtrot " ) ;
case ' g ' : return ( " golf " ) ;
case ' h ' : return ( " hotel " ) ;
case ' i ' : return ( " india " ) ;
case ' j ' : return ( " juliet " ) ;
case ' k ' : return ( " kilo " ) ;
case ' l ' : return ( " lima " ) ;
case ' m ' : return ( " mike " ) ;
case ' n ' : return ( " november " ) ;
case ' o ' : return ( " oscar " ) ;
case ' p ' : return ( " papa " ) ;
case ' q ' : return ( " quebec " ) ;
case ' r ' : return ( " romeo " ) ;
case ' s ' : return ( " sierra " ) ;
case ' t ' : return ( " tango " ) ;
case ' u ' : return ( " uniform " ) ;
case ' v ' : return ( " victor " ) ;
case ' w ' : return ( " whiskey " ) ;
case ' x ' : return ( " x-ray " ) ;
case ' y ' : return ( " yankee " ) ;
case ' z ' : return ( " zulu " ) ;
2002-12-04 20:05:30 +00:00
}
// We shouldn't get here
return ( " Error " ) ;
2002-10-02 15:27:49 +00:00
}
2002-04-04 20:50:05 +00:00
2004-01-23 17:18:24 +00:00
// Get the compass direction associated with a heading in degrees
// Currently returns 8 direction resolution (N, NE, E etc...)
// Might be modified in future to return 4, 8 or 16 resolution but defaulting to 8.
string GetCompassDirection ( double h ) {
while ( h < 0.0 ) h + = 360.0 ;
while ( h > 360.0 ) h - = 360.0 ;
if ( h < 22.5 | | h > 337.5 ) {
return ( " North " ) ;
} else if ( h < 67.5 ) {
return ( " North-East " ) ;
} else if ( h < 112.5 ) {
return ( " East " ) ;
} else if ( h < 157.5 ) {
return ( " South-East " ) ;
} else if ( h < 202.5 ) {
return ( " South " ) ;
} else if ( h < 247.5 ) {
return ( " South-West " ) ;
} else if ( h < 292.5 ) {
return ( " West " ) ;
} else {
return ( " North-West " ) ;
}
}
2003-04-15 10:37:58 +00:00
//================================================================================================================
2002-12-04 20:05:30 +00:00
// Given two positions (lat & lon in degrees), get the HORIZONTAL separation (in meters)
2002-04-04 20:50:05 +00:00
double dclGetHorizontalSeparation ( Point3D pos1 , Point3D pos2 ) {
2002-12-04 20:05:30 +00:00
double x ; //East-West separation
double y ; //North-South separation
double z ; //Horizontal separation - z = sqrt(x^2 + y^2)
double lat1 = pos1 . lat ( ) * SG_DEGREES_TO_RADIANS ;
double lon1 = pos1 . lon ( ) * SG_DEGREES_TO_RADIANS ;
double lat2 = pos2 . lat ( ) * SG_DEGREES_TO_RADIANS ;
double lon2 = pos2 . lon ( ) * SG_DEGREES_TO_RADIANS ;
y = sin ( fabs ( lat1 - lat2 ) ) * SG_EQUATORIAL_RADIUS_M ;
x = sin ( fabs ( lon1 - lon2 ) ) * SG_EQUATORIAL_RADIUS_M * ( cos ( ( lat1 + lat2 ) / 2.0 ) ) ;
z = sqrt ( x * x + y * y ) ;
return ( z ) ;
2002-04-04 20:50:05 +00:00
}
2002-10-02 15:27:49 +00:00
// Given a point and a line, get the HORIZONTAL shortest distance from the point to a point on the line.
// Expects to be fed orthogonal co-ordinates, NOT lat & lon !
2002-12-04 20:05:30 +00:00
// The units of the separation will be those of the input.
2002-10-02 15:27:49 +00:00
double dclGetLinePointSeparation ( double px , double py , double x1 , double y1 , double x2 , double y2 ) {
2002-12-04 20:05:30 +00:00
double vecx = x2 - x1 ;
double vecy = y2 - y1 ;
double magline = sqrt ( vecx * vecx + vecy * vecy ) ;
double u = ( ( px - x1 ) * ( x2 - x1 ) + ( py - y1 ) * ( y2 - y1 ) ) / ( magline * magline ) ;
double x0 = x1 + u * ( x2 - x1 ) ;
double y0 = y1 + u * ( y2 - y1 ) ;
vecx = px - x0 ;
vecy = py - y0 ;
double d = sqrt ( vecx * vecx + vecy * vecy ) ;
if ( d < 0 ) {
d * = - 1 ;
}
return ( d ) ;
2002-10-02 15:27:49 +00:00
}
2002-12-04 20:05:30 +00:00
// Given a position (lat/lon/elev), heading and vertical angle (degrees), and distance (meters), calculate the new position.
// This function assumes the world is spherical. If geodetic accuracy is required use the functions is sg_geodesy instead!
// Assumes that the ground is not hit!!! Expects heading and angle in degrees, distance in meters.
2002-04-04 20:50:05 +00:00
Point3D dclUpdatePosition ( Point3D pos , double heading , double angle , double distance ) {
2002-12-04 20:05:30 +00:00
//cout << setprecision(10) << pos.lon() << ' ' << pos.lat() << '\n';
heading * = DCL_DEGREES_TO_RADIANS ;
angle * = DCL_DEGREES_TO_RADIANS ;
double lat = pos . lat ( ) * DCL_DEGREES_TO_RADIANS ;
double lon = pos . lon ( ) * DCL_DEGREES_TO_RADIANS ;
double elev = pos . elev ( ) ;
//cout << setprecision(10) << lon*DCL_RADIANS_TO_DEGREES << ' ' << lat*DCL_RADIANS_TO_DEGREES << '\n';
double horiz_dist = distance * cos ( angle ) ;
double vert_dist = distance * sin ( angle ) ;
double north_dist = horiz_dist * cos ( heading ) ;
double east_dist = horiz_dist * sin ( heading ) ;
//cout << distance << ' ' << horiz_dist << ' ' << vert_dist << ' ' << north_dist << ' ' << east_dist << '\n';
double delta_lat = asin ( north_dist / ( double ) SG_EQUATORIAL_RADIUS_M ) ;
double delta_lon = asin ( east_dist / ( double ) SG_EQUATORIAL_RADIUS_M ) * ( 1.0 / cos ( lat ) ) ; // I suppose really we should use the average of the original and new lat but we'll assume that this will be good enough.
//cout << delta_lon*DCL_RADIANS_TO_DEGREES << ' ' << delta_lat*DCL_RADIANS_TO_DEGREES << '\n';
lat + = delta_lat ;
lon + = delta_lon ;
elev + = vert_dist ;
//cout << setprecision(10) << lon*DCL_RADIANS_TO_DEGREES << ' ' << lat*DCL_RADIANS_TO_DEGREES << '\n';
//cout << setprecision(15) << DCL_DEGREES_TO_RADIANS * DCL_RADIANS_TO_DEGREES << '\n';
return ( Point3D ( lon * DCL_RADIANS_TO_DEGREES , lat * DCL_RADIANS_TO_DEGREES , elev ) ) ;
2002-04-04 20:50:05 +00:00
}
2002-12-04 20:05:30 +00:00
// Get a heading in degrees from one lat/lon to another.
// This function assumes the world is spherical. If geodetic accuracy is required use the functions is sg_geodesy instead!
// Warning - at the moment we are not checking for identical points - currently it returns 90 in this instance.
double GetHeadingFromTo ( Point3D A , Point3D B ) {
double latA = A . lat ( ) * DCL_DEGREES_TO_RADIANS ;
double lonA = A . lon ( ) * DCL_DEGREES_TO_RADIANS ;
double latB = B . lat ( ) * DCL_DEGREES_TO_RADIANS ;
double lonB = B . lon ( ) * DCL_DEGREES_TO_RADIANS ;
double xdist = sin ( lonB - lonA ) * ( double ) SG_EQUATORIAL_RADIUS_M * cos ( ( latA + latB ) / 2.0 ) ;
double ydist = sin ( latB - latA ) * ( double ) SG_EQUATORIAL_RADIUS_M ;
2002-04-04 20:50:05 +00:00
2002-12-04 20:05:30 +00:00
if ( xdist > = 0 ) {
if ( ydist > 0 ) {
return ( atan ( xdist / ydist ) * DCL_RADIANS_TO_DEGREES ) ;
} else if ( ydist = = 0 ) {
return ( 90.0 ) ;
} else {
return ( 180.0 - atan ( xdist / fabs ( ydist ) ) * DCL_RADIANS_TO_DEGREES ) ;
}
} else {
if ( ydist > 0 ) {
return ( 360.0 - atan ( fabs ( xdist ) / ydist ) * DCL_RADIANS_TO_DEGREES ) ;
} else if ( ydist = = 0 ) {
return ( 270.0 ) ;
} else {
return ( 180.0 + atan ( xdist / ydist ) * DCL_RADIANS_TO_DEGREES ) ;
}
}
}
// Given a heading (in degrees), bound it from 0 -> 360
void dclBoundHeading ( double & hdg ) {
while ( hdg < 0.0 ) {
hdg + = 360.0 ;
}
while ( hdg > 360.0 ) {
hdg - = 360.0 ;
}
}
2003-03-05 21:34:58 +00:00
2003-04-15 10:37:58 +00:00
// smallest difference between two angles in degrees
// difference is negative if a1 > a2 and positive if a2 > a1
double GetAngleDiff_deg ( const double & a1 , const double & a2 ) {
double a3 = a2 - a1 ;
while ( a3 < 180.0 ) a3 + = 360.0 ;
while ( a3 > 180.0 ) a3 - = 360.0 ;
return a3 ;
}
//================================================================================================================
2003-03-05 21:34:58 +00:00
// Airport stuff. The next two functions are straight copies of their fg.... equivalents
// in fg_init.cxx, and are just here temporarily until some rationalisation occurs.
// find basic airport location info from airport database
bool dclFindAirportID ( const string & id , FGAirport * a ) {
2003-08-28 20:53:08 +00:00
FGAirport result ;
2003-03-05 21:34:58 +00:00
2003-08-28 20:53:08 +00:00
if ( id . length ( ) ) {
2003-03-05 21:34:58 +00:00
SG_LOG ( SG_GENERAL , SG_INFO , " Searching for airport code = " < < id ) ;
2003-08-28 20:53:08 +00:00
result = globals - > get_airports ( ) - > search ( id ) ;
2005-02-10 09:01:51 +00:00
if ( result . getId ( ) . empty ( ) ) {
2004-07-23 18:58:42 +00:00
SG_LOG ( SG_GENERAL , SG_WARN ,
2003-08-29 04:11:23 +00:00
" Failed to find " < < id < < " in basic.dat.gz " ) ;
2003-03-05 21:34:58 +00:00
return false ;
}
} else {
return false ;
}
2003-08-28 20:53:08 +00:00
* a = result ;
2003-03-05 21:34:58 +00:00
SG_LOG ( SG_GENERAL , SG_INFO ,
" Position for " < < id < < " is ( "
2005-02-10 09:01:51 +00:00
< < a - > getLongitude ( ) < < " , "
< < a - > getLatitude ( ) < < " ) " ) ;
2003-03-05 21:34:58 +00:00
return true ;
}
// get airport elevation
double dclGetAirportElev ( const string & id ) {
FGAirport a ;
// double lon, lat;
2004-01-23 17:18:24 +00:00
SG_LOG ( SG_ATC , SG_INFO ,
2003-03-05 21:34:58 +00:00
" Finding elevation for airport: " < < id ) ;
if ( dclFindAirportID ( id , & a ) ) {
2005-02-10 09:01:51 +00:00
return a . getElevation ( ) * SG_FEET_TO_METER ;
2003-03-05 21:34:58 +00:00
} else {
return - 9999.0 ;
}
}
2003-03-11 13:25:14 +00:00
2004-01-23 17:18:24 +00:00
// get airport position
Point3D dclGetAirportPos ( const string & id ) {
FGAirport a ;
// double lon, lat;
SG_LOG ( SG_ATC , SG_INFO ,
" Finding position for airport: " < < id ) ;
if ( dclFindAirportID ( id , & a ) ) {
2005-02-10 09:01:51 +00:00
return Point3D ( a . getLongitude ( ) , a . getLatitude ( ) , a . getElevation ( ) ) ;
2004-01-23 17:18:24 +00:00
} else {
return Point3D ( 0.0 , 0.0 , - 9999.0 ) ;
}
}
2003-03-11 13:25:14 +00:00
// Runway stuff
// Given a Point3D (lon/lat/elev) and an FGRunway struct, determine if the point lies on the runway
2003-03-31 16:23:23 +00:00
bool OnRunway ( Point3D pt , const FGRunway & rwy ) {
2003-03-11 13:25:14 +00:00
FGATCAlignedProjection ortho ;
2004-12-22 23:57:07 +00:00
Point3D centre ( rwy . _lon , rwy . _lat , 0.0 ) ; // We don't need the elev
ortho . Init ( centre , rwy . _heading ) ;
2003-03-11 13:25:14 +00:00
Point3D xyc = ortho . ConvertToLocal ( centre ) ;
Point3D xyp = ortho . ConvertToLocal ( pt ) ;
//cout << "Length offset = " << fabs(xyp.y() - xyc.y()) << '\n';
//cout << "Width offset = " << fabs(xyp.x() - xyc.x()) << '\n';
2004-12-22 23:57:07 +00:00
if ( ( fabs ( xyp . y ( ) - xyc . y ( ) ) < ( ( rwy . _length / 2.0 ) + 5.0 ) )
& & ( fabs ( xyp . x ( ) - xyc . x ( ) ) < ( rwy . _width / 2.0 ) ) ) {
2003-03-11 13:25:14 +00:00
return ( true ) ;
}
return ( false ) ;
}