True Airspeed Calculator Source Code


TrueAirspeedApplet.java

// author: craig cox
//         rea computing, inc.

import java.applet.Applet;
import java.awt.*;

// this class determines the true airspeed of an aircraft using groundspeeds obtained by
// flying on certain headings

public class TrueAirspeedApplet extends Applet
{
    private Button                     m_btnCompute;
    private Checkbox                   m_cbxShowIterations;
    private GroundtrackCanvas          m_cnvGroundtrack;
    private Label                      m_lblTrueAirspeed;
    private Label                      m_lblWind;
    private DoubleTextField            m_dtfEastGroundspeed;
    private DoubleTextField            m_dtfNorthGroundspeed;
    private DoubleTextField            m_dtfSouthGroundspeed;
    private DoubleTextField            m_dtfWestGroundspeed;
    private String                     m_sDirectionOrder = new String( );
    private TrueAirspeedWindEstimate   m_estimate = new TrueAirspeedWindEstimate( );

    // override the paint method to draw a border around the applet

    public void paint( Graphics g )
    {
        g.drawRect( 0, 0, this.size( ).width - 1, this.size( ).height - 1 );
        super.paint( g );
    }
    
    // override the init method to lay out the components in the applet

    public void init( )
    {
        GridBagLayout       gridbag = new GridBagLayout( );
        GridBagConstraints  c = new GridBagConstraints( );
        
        this.resize( 400, 220 );

        this.setLayout( gridbag );

        m_dtfNorthGroundspeed = new DoubleTextField( 5 );
        m_dtfNorthGroundspeed.allowNegative( false );
        c.gridx = 1;
        c.gridy = 0;
        c.gridwidth = 1;
        c.gridheight = 1;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.SOUTH;
        gridbag.setConstraints( m_dtfNorthGroundspeed, c );
        this.add( m_dtfNorthGroundspeed );

        m_dtfWestGroundspeed = new DoubleTextField( 5 );
        m_dtfWestGroundspeed.allowNegative( false );
        c.gridx = 0;
        c.gridy = 1;
        c.gridwidth = 1;
        c.gridheight = 1;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.EAST;
        gridbag.setConstraints( m_dtfWestGroundspeed, c );
        this.add( m_dtfWestGroundspeed );

        Canvas c1 = new GroundspeedCanvas( this );
        c.gridx = 1;
        c.gridy = 1;
        c.gridwidth = 1;
        c.gridheight = 1;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.CENTER;
        gridbag.setConstraints( c1, c );
        this.add( c1 );

        m_dtfEastGroundspeed = new DoubleTextField( 5 );
        m_dtfEastGroundspeed.allowNegative( false );
        c.gridx = 2;
        c.gridy = 1;
        c.gridwidth = 1;
        c.gridheight = 1;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.WEST;
        gridbag.setConstraints( m_dtfEastGroundspeed, c );
        this.add( m_dtfEastGroundspeed );

        m_dtfSouthGroundspeed = new DoubleTextField( 5 );
        m_dtfSouthGroundspeed.allowNegative( false );
        c.gridx = 1;
        c.gridy = 2;
        c.gridwidth = 1;
        c.gridheight = 1;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.NORTH;
        gridbag.setConstraints( m_dtfSouthGroundspeed, c );
        this.add( m_dtfSouthGroundspeed );

        Label l1 = new Label( "Enter groundspeeds above", Label.CENTER );
        c.gridx = 0;
        c.gridy = 3;
        c.gridwidth = 3;
        c.gridheight = 1;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.CENTER;
        gridbag.setConstraints( l1, c );
        this.add( l1 );

        m_btnCompute = new Button("Compute");
        m_btnCompute.disable( );
        c.gridx = 0;
        c.gridy = 4;
        c.gridwidth = 3;
        c.gridheight = GridBagConstraints.RELATIVE;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.CENTER;
        gridbag.setConstraints( m_btnCompute, c );
        this.add( m_btnCompute );

        m_cbxShowIterations = new Checkbox( "Show iterations", null, true );
        c.gridx = 0;
        c.gridy = 5;
        c.gridwidth = 3;
        c.gridheight = GridBagConstraints.REMAINDER;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.CENTER;
        gridbag.setConstraints( m_cbxShowIterations, c );
        this.add( m_cbxShowIterations );

        m_cnvGroundtrack = new GroundtrackCanvas( this );
        c.gridx = 4;
        c.gridy = 0;
        c.gridwidth = GridBagConstraints.REMAINDER;
        c.gridheight = 3;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.CENTER;
        gridbag.setConstraints( m_cnvGroundtrack, c );
        this.add( m_cnvGroundtrack );

        Label l2 = new Label( "Groundtrack displayed above", Label.CENTER );
        c.gridx = 4;
        c.gridy = 3;
        c.gridwidth = GridBagConstraints.REMAINDER;
        c.gridheight = 1;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.CENTER;
        gridbag.setConstraints( l2, c );
        this.add( l2 );

        Label l3 = new Label( "True airspeed: ", Label.RIGHT );
        c.gridx = 4;
        c.gridy = 4;
        c.gridwidth = GridBagConstraints.RELATIVE;
        c.gridheight = GridBagConstraints.RELATIVE;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.EAST;
        gridbag.setConstraints( l3, c );
        this.add( l3 );

        m_lblTrueAirspeed = new Label( "            ", Label.LEFT );
        c.gridx = 5;
        c.gridy = 4;
        c.gridwidth = GridBagConstraints.REMAINDER;
        c.gridheight = GridBagConstraints.RELATIVE;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.WEST;
        gridbag.setConstraints( m_lblTrueAirspeed, c );
        this.add( m_lblTrueAirspeed );

        Label l4 = new Label( "Wind: ", Label.RIGHT );
        c.gridx = 4;
        c.gridy = 5;
        c.gridwidth = GridBagConstraints.RELATIVE;
        c.gridheight = GridBagConstraints.REMAINDER;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.EAST;
        gridbag.setConstraints( l4, c );
        this.add( l4 );

        m_lblWind = new Label( "                        ", Label.LEFT );
        c.gridx = 5;
        c.gridy = 5;
        c.gridwidth = GridBagConstraints.REMAINDER;
        c.gridheight = GridBagConstraints.REMAINDER;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.WEST;
        gridbag.setConstraints( m_lblWind, c );
        this.add( m_lblWind );
    }

    // if focus goes to or a key is released in a DoubleTextField, call a function to
    // process the value

    public boolean handleEvent( Event evt )
    {
        if ( evt.target instanceof DoubleTextField )
        {
            if ( evt.id == Event.GOT_FOCUS || evt.id == Event.KEY_RELEASE )
            {
                this.addDirection( ( DoubleTextField ) evt.target );
            }
        }
        return super.handleEvent( evt );
    }

    // if the compute button is pressed, disable appropriate components, compute the wind and
    // airspeed, and re-enable the components

    public boolean action( Event evt, Object obj )
    {
        if ( evt.target == m_btnCompute )
        {
            m_dtfNorthGroundspeed.disable( );
            m_dtfEastGroundspeed.disable( );
            m_dtfSouthGroundspeed.disable( );
            m_dtfWestGroundspeed.disable( );
            m_btnCompute.disable( );
            m_cbxShowIterations.disable( );

            this.compute( );
            
            m_dtfNorthGroundspeed.enable( );
            m_dtfEastGroundspeed.enable( );
            m_dtfSouthGroundspeed.enable( );
            m_dtfWestGroundspeed.enable( );
            m_btnCompute.enable( );
            m_cbxShowIterations.enable( );
        }
        
        return super.action( evt, obj );
    }

    // process a groundspeed entered in an DoubleTextField

    private void addDirection( DoubleTextField dtf )
    {
        char    cDirection;
        double  dSpeed;
        int     nIndex;
        int     nLength;
    
        // get the direction of the groundspeed

        if ( dtf == m_dtfNorthGroundspeed )
        {
            cDirection = 'N';
        }
        else if ( dtf == m_dtfWestGroundspeed )
        {
            cDirection = 'W';
        }
        else if ( dtf == m_dtfEastGroundspeed )
        {
            cDirection = 'E';
        }
        else
        {
            cDirection = 'S';
        }
        
        // append (or move) the direction to the end of the direction order string

        nLength = m_sDirectionOrder.length( );
        nIndex = m_sDirectionOrder.indexOf( cDirection );
        
        // remove the direction from the direction order string if it is already there
        
        if ( nIndex != -1 )
        {
            if ( nIndex == 0 )
            {
                m_sDirectionOrder = m_sDirectionOrder.substring( 1 );
            }
            else if ( nIndex == nLength - 1 )
            {
                m_sDirectionOrder = m_sDirectionOrder.substring( 0, nLength - 1 );
            }
            else
            {
                m_sDirectionOrder = m_sDirectionOrder.substring( 0, nIndex ) +
                    m_sDirectionOrder.substring( nIndex + 1, nLength );
            }
        }
        
        // if the groundspeed is valid, add the direction to the end of the direction order
        // string

        if ( dtf.hasValidValue( ) )
        {
            m_sDirectionOrder = m_sDirectionOrder + cDirection;
        }

        // get the value entered, or -1 if the value is invalid

        try
        {
            dSpeed = dtf.doubleValue( );
        }
        catch ( NumberFormatException e )
        {
            dSpeed = -1.0;
        }
        
        // set the groundspeed in the current estimate of wind and airspeed

        m_estimate.setGroundspeed( cDirection, dSpeed );

        // remove any displays for the wind or airspeed

        m_lblTrueAirspeed.setText( "" );
        m_lblWind.setText( "" );
        
        // set the paint mode of the groundtrack canvas to ignore wind and repaint it
        
        m_cnvGroundtrack.setPaintMode( false );
        m_cnvGroundtrack.repaint( );
        
        // the compute button is disabled if less than three groundspeeds have been entered

        if ( m_sDirectionOrder.length( ) < 3 )
        {
            m_btnCompute.disable( );
        }
        else
        {
            m_btnCompute.enable( );
        }
    }
    
    // get the current extimate for wind and airspeed

    public TrueAirspeedWindEstimate getEstimate( )
    {
        return m_estimate;
    }
    
    // get the direction order string

    public String getDirectionOrder( )
    {
        return m_sDirectionOrder;
    }
    
    // compute the actual wind and aircraft airspeed

    private void compute( )
    {
        // set the paint mode of the groundtrack canvas to take into account wind

        m_cnvGroundtrack.setPaintMode( true );
        
        // call the update function of the estimate of the wind and airspeed.  keep calling it
        // until it returns true, meaning the correct wind and airspeed have been determined.

        while ( !m_estimate.update( ) )
        {
            // if the user wants to see iterations, update the wind display, airspeed display,
            // and groundtrack for every successive guess

            if ( m_cbxShowIterations.getState( ) )
            {
                m_lblTrueAirspeed.setText( "" + ( int ) Math.round( m_estimate.getTrueAirspeed( ) ) );
                m_lblWind.setText( m_estimate.getWind( ) );
                m_cnvGroundtrack.update( m_cnvGroundtrack.getGraphics( ) );
                m_cnvGroundtrack.paint( m_cnvGroundtrack.getGraphics( ) );
            
                // to prevent the updates from occurring so fast that the user cannot see
                // them, sleep for a tenth of a second

                try
                {
                    Thread.sleep( 100 );
                }
                catch ( InterruptedException e )
                {
                }
            }
        }
        
        // display the final (actual) wind, airspeed, and groundtrack

        m_lblTrueAirspeed.setText( "" + ( int ) Math.round( m_estimate.getTrueAirspeed( ) ) );
        m_lblWind.setText( m_estimate.getWind( ) );
        m_cnvGroundtrack.repaint( );
    }

    // this applet displays best at 400x220

    public Dimension minimumSize( )
    {
        return new Dimension( 400, 220 );
    }

    public Dimension preferredSize( )
    {
        return this.minimumSize( );
    }
}

Back to top


TrueAirspeedWindEstimate.java

// author: craig cox
//         rea computing, inc.

// this class encapsulates the data for an estimate of the true wind and airspeed and
// contains a method to get a better estimate

public class TrueAirspeedWindEstimate
{
    private double m_dEastGroundspeed = -1.0;
    private double m_dNorthGroundspeed = -1.0;
    private double m_dSouthGroundspeed = -1.0;
    private double m_dSouthWind = 0.0;
    private double m_dTrueAirspeed = 0.0;
    private double m_dWestGroundspeed = -1.0;
    private double m_dWestWind = 0.0;

    // return the wind as a string in the format wind_direction/wind_speed
    
    public String getWind( )
    {
        int     nDirection;
        int     nSpeed;
        String  sReturn;
    
        // determine the wind direction and speed as integers

        nDirection = ( int ) Math.round( ( Math.atan2( -m_dWestWind, -m_dSouthWind )
            * 180 / Math.PI ) );
        nDirection = ( nDirection < 0 ) ? nDirection + 360 : nDirection;
        nSpeed = ( int ) Math.round( ( Math.sqrt( m_dSouthWind * m_dSouthWind +
            m_dWestWind * m_dWestWind ) ) );

        // convert the integer wind direction and speed to a string and return it

        sReturn = "00" + nDirection;
        sReturn = sReturn.substring( sReturn.length( ) - 3 ) + "/" + nSpeed;
        
        return sReturn;
    }
    
    // update the current estimates for wind and airspeed.  return true if the update is
    // complete, meaning the original estimate was already optimal and could not be improved.
    // return false if the update is not complete, meaning the new estimate is different (and
    // better) than the original.

    public boolean update( )
    {
        double  dBestError = Double.MAX_VALUE;
        double  dBestSouthWind = 0.0;
        double  dBestTrueAirspeed = 0.0;
        double  dBestWestWind = 0.0;
        double  dExpectedEastGroundspeed;
        double  dExpectedGroundspeed;
        double  dExpectedNorthGroundspeed;
        double  dTestError;
        double  dTestSouthWind;
        double  dTestTrueAirspeed;
        double  dTestWestWind;

        // make 27 attempts at finding a better solution.  vary the current south wind by 1 or
        // 0, vary the current west wind by 1 or 0, and vary the current airspeed by 1 or 0.

        for ( dTestSouthWind = m_dSouthWind - 1.0;
              dTestSouthWind < m_dSouthWind + 2.0;
              dTestSouthWind++ )
        {
            for ( dTestWestWind = m_dWestWind - 1.0;
                  dTestWestWind < m_dWestWind + 2.0;
                  dTestWestWind++ )
            {
                for ( dTestTrueAirspeed = m_dTrueAirspeed - 1.0;
                      dTestTrueAirspeed < m_dTrueAirspeed + 2.0;
                      dTestTrueAirspeed++ )
                {
                    // accumulate an error which is the square of the difference between what
                    // we would have expected to see as a groundspeed on this estimate and
                    // what we actually saw

                    dTestError = 0.0;

                    if ( m_dNorthGroundspeed != -1 )
                    {
                        dExpectedNorthGroundspeed = dTestTrueAirspeed + dTestSouthWind;
                        dExpectedEastGroundspeed = dTestWestWind;
                        dExpectedGroundspeed = Math.sqrt( 
                            ( dExpectedNorthGroundspeed * dExpectedNorthGroundspeed ) + 
                            ( dExpectedEastGroundspeed * dExpectedEastGroundspeed ) );
                        dTestError +=
                            Math.pow( m_dNorthGroundspeed - dExpectedGroundspeed, 2.0 );
                    }

                    if ( m_dEastGroundspeed != -1 )
                    {
                        dExpectedNorthGroundspeed = dTestSouthWind;
                        dExpectedEastGroundspeed = dTestTrueAirspeed + dTestWestWind;
                        dExpectedGroundspeed = Math.sqrt( 
                            ( dExpectedNorthGroundspeed * dExpectedNorthGroundspeed ) + 
                            ( dExpectedEastGroundspeed * dExpectedEastGroundspeed ) );
                        dTestError +=
                            Math.pow( m_dEastGroundspeed - dExpectedGroundspeed, 2.0 );
                    }

                    if ( m_dSouthGroundspeed != -1 )
                    {
                        dExpectedNorthGroundspeed = -dTestTrueAirspeed + dTestSouthWind;
                        dExpectedEastGroundspeed = dTestWestWind;
                        dExpectedGroundspeed = Math.sqrt( 
                            ( dExpectedNorthGroundspeed * dExpectedNorthGroundspeed ) + 
                            ( dExpectedEastGroundspeed * dExpectedEastGroundspeed ) );
                        dTestError +=
                            Math.pow( m_dSouthGroundspeed - dExpectedGroundspeed, 2.0 );
                    }

                    if ( m_dWestGroundspeed != -1 )
                    {
                        dExpectedNorthGroundspeed = dTestSouthWind;
                        dExpectedEastGroundspeed = -dTestTrueAirspeed + dTestWestWind;
                        dExpectedGroundspeed = Math.sqrt( 
                            ( dExpectedNorthGroundspeed * dExpectedNorthGroundspeed ) + 
                            ( dExpectedEastGroundspeed * dExpectedEastGroundspeed ) );
                        dTestError +=
                            Math.pow( m_dWestGroundspeed - dExpectedGroundspeed, 2.0 );
                    }

                    // update the best wind and airspeed if the error for the new estimate is
                    // lower
                    
                    if ( ( dTestError < dBestError ) ||
                         ( dTestError == dBestError &&
                           dTestTrueAirspeed == m_dTrueAirspeed &&
                           dTestSouthWind == m_dSouthWind &&
                           dTestWestWind == m_dSouthWind ) )
                    {
                        dBestError = dTestError;
                        dBestTrueAirspeed = dTestTrueAirspeed;
                        dBestSouthWind = dTestSouthWind;
                        dBestWestWind = dTestWestWind;
                    }
                }
            }
        }
   
        // determine if the old estimate is still the best estimate

        if ( dBestTrueAirspeed == m_dTrueAirspeed &&
             dBestSouthWind == m_dSouthWind &&
             dBestWestWind == m_dWestWind )
        {
            // the old estimate is still the best estimate, so the best answer has been found,
            // so the update is complete, so return true
            
            return true;
        }
        else
        {
            // the new estimate is better than the old, so the best answer has not yet been
            // found (at least we do not know that yet), so the update is not complete, so
            // save the new best wind and airspeed and return false

            m_dTrueAirspeed = dBestTrueAirspeed;
            m_dSouthWind = dBestSouthWind;
            m_dWestWind = dBestWestWind;
            
            return false;
        }
    }
    
    // set the groundspeed in a certain direction

    public void setGroundspeed( char cDirection, double dGroundspeed )
    {
        int dSpeed = 0;
        int nSpeedCount = 0;
    
        // set the instance variables containing the groundspeeds in certain directions.  a
        // speed of -1.0 indicates that a groundspeed is not available.

        switch( cDirection )
        {
            case 'N':
            {
                m_dNorthGroundspeed = dGroundspeed;
                break;
            }
            
            case 'E':
            {
                m_dEastGroundspeed = dGroundspeed;
                break;
            }
            
            case 'S':
            {
                m_dSouthGroundspeed = dGroundspeed;
                break;
            }
            
            case 'W':
            {
                m_dWestGroundspeed = dGroundspeed;
                break;
            }
        }
        
        // use the north and south groundspeeds to estimate the south wind

        if ( m_dNorthGroundspeed != -1 && m_dSouthGroundspeed != -1 )
        {
            m_dSouthWind = ( m_dNorthGroundspeed - m_dSouthGroundspeed ) / 2;
        }
        else
        {
            m_dSouthWind = 0.0;
        }
        
        // use the east and west groundspeeds to estimate the west wind

        if ( m_dEastGroundspeed != -1 && m_dWestGroundspeed != -1 )
        {
            m_dWestWind = ( m_dEastGroundspeed - m_dWestGroundspeed ) / 2;
        }
        else
        {
            m_dWestWind = 0.0;
        }
        
        // use all groundspeeds to estimate the aircraft true airspeed

        if ( m_dNorthGroundspeed != -1 )
        {
            dSpeed += m_dNorthGroundspeed;
            nSpeedCount++;
        }
        
        if ( m_dEastGroundspeed != -1 )
        {
            dSpeed += m_dEastGroundspeed;
            nSpeedCount++;
        }
        
        if ( m_dSouthGroundspeed != -1 )
        {
            dSpeed += m_dSouthGroundspeed;
            nSpeedCount++;
        }
        
        if ( m_dWestGroundspeed != -1 )
        {
            dSpeed += m_dWestGroundspeed;
            nSpeedCount++;
        }
        
        if ( nSpeedCount > 0 )
        {
            m_dTrueAirspeed = dSpeed / nSpeedCount;
        }
        else
        {
            m_dTrueAirspeed = 0.0;
        }
    }

    // get the groundspeed in a certain direction

    public double getGroundspeed( char cDirection )
    {
        double dSpeed = -1.0;
        
        switch( cDirection )
        {
            case 'N':
            {
                dSpeed = m_dNorthGroundspeed;
                break;
            }
            
            case 'E':
            {
                dSpeed = m_dEastGroundspeed;
                break;
            }
            
            case 'S':
            {
                dSpeed = m_dSouthGroundspeed;
                break;
            }
            
            case 'W':
            {
                dSpeed = m_dWestGroundspeed;
                break;
            }
        }
        
        return dSpeed;
    }
    
    // get the current estimate for true airspeed

    public double getTrueAirspeed( )
    {
        return m_dTrueAirspeed;
    }
    
    // get the current estimate for wind in a certain direction

    public double getWind( char cDirection )
    {
        double dWind = -1.0;
        
        switch ( cDirection )
        {
            case 'S':
            {
                dWind = m_dSouthWind;
                break;
            }
            
            case 'W':
            {
                dWind = m_dWestWind;
                break;
            }
        }
        
        return dWind;
    }
}

Back to top


DoubleTextField.java

// author: craig cox
//         rea computing, inc.

import java.awt.*;

// a text field that accepts only doubles.  it can be initialized to accept only
// non-negative doubles.

public class DoubleTextField extends TextField
{
    private boolean m_bAllowNegative = true;
    private int     m_nSelectionEnd = 0;
    private int     m_nSelectionStart = 0;
    private String  m_sText = "";

    // create a text field that accepts only doubles
    
    public DoubleTextField( )
    {
        super( 9 );
    }
    
    public DoubleTextField( int cols )
    {
        super( cols );
    }
    
    // specify whether or not the text field should accept negative doubles.  if this
    // method is not called, the text field will accept negative doubles.
    
    public void allowNegative( boolean myAllowNegative )
    {
        m_bAllowNegative = myAllowNegative;
    }

    // event handler

    synchronized public boolean handleEvent( Event evt )
    {
        int     nSelectionEnd;
        int     nSelectionStart;
        String  sText;
        
        sText = this.getText( );
        nSelectionStart = this.getSelectionStart( );
        nSelectionEnd = this.getSelectionEnd( );

        switch ( evt.id )
        {
            case Event.KEY_PRESS:
            {
                if ( this.isAcceptablePartialEntry( sText ) )
                {
                    m_sText = sText;
                    m_nSelectionStart = nSelectionStart;
                    m_nSelectionEnd = nSelectionEnd;
                }

                break;
            }

            case Event.KEY_RELEASE:
            {
                if ( this.isAcceptablePartialEntry( sText ) )
                {
                    m_sText = sText;
                    m_nSelectionStart = nSelectionStart;
                    m_nSelectionEnd = nSelectionEnd;
                }
                else
                {
                    this.setText( m_sText );
                    this.select( m_nSelectionStart, m_nSelectionEnd );
                }

                break;
            }

            case Event.LOST_FOCUS:
            {
                if ( !sText.equals( "" ) )
                {
                    try
                    {
                        this.doubleValue( sText );
                        m_sText = sText;
                    }
                    catch ( NumberFormatException e )
                    {
                        this.requestFocus( );
                    }
                }

                break;
            }
        }

        return super.handleEvent( evt );
    }

    // returns true if the text in the text field evaluates to a valid double

    public boolean hasValidValue( )
    {
        return this.isValidValue( this.getText( ) );
    }

    // returns true if there are no characters in the text field

    public boolean isEmpty( )
    {
        return this.getText( ).equals( "" );
    }

    // returns the double value of the text in the text field

    public double doubleValue( )
    {
        return this.doubleValue( this.getText( ) );
    }

    // creates a string representation of the object

    public String toString( )
    {
        return this.toString( this.getText( ) );
    }

    // returns true if the entered text could be part of a valid entry

    private boolean isAcceptablePartialEntry( String sText )
    {
        if ( this.isValidValue( sText ) || sText.equals( "" ) || sText.equals( "." ) )
        {
            return true;
        }

        if ( m_bAllowNegative && ( sText.equals( "-" ) || sText.equals( "-." ) ) )
        {
            return true;
        }

        return false;
    }

    // returns true if the passed string evaluates to a valid double.  if the text field
    // does not allow negatives, the existence of a minus sign, even for -0, makes the string
    // invalid.  the empty string is invalid.  the string "-" is invalid.

    private boolean isValidValue( String sText )
    {
        Double Value;

        if ( sText == null || sText.length( ) == 0 )
        {
            return false;
        }

        try
        {
            Value = new Double( sText );
        }
        catch ( NumberFormatException e )
        {
            return false;
        }

        if ( !m_bAllowNegative && sText.indexOf( "-" ) != -1 )
        {
            return false;
        }

        return true;
    }

    // returns the double value of the passed string
    
    private double doubleValue( String sText )
    {
        if ( !this.isValidValue( sText ) )
        {
            throw new NumberFormatException( this.toString( sText ) );
        }

        return new Double( sText ).doubleValue( );
    }

    // creates a string representation of the object, allowing the entered text to be passed
    // instead of gotten from the object

    private String toString( String sText)
    {
        return new String( "Class: " + this.getClass( ).getName( ) + "; Allow Negative: " +
            m_bAllowNegative + "; Text: " + sText );
    }
}

Back to top


GroundspeedCanvas.java

// author: craig cox
//         rea computing, inc.

import java.awt.*;

// this class is a canvas that displays arrows pointing to the cardinal headings

class GroundspeedCanvas extends Canvas
{
    Container    m_container;

    // constructor that stores the container that will hold the canvas

    GroundspeedCanvas( Container container )
    {
        super( );

        // store the container that will hold the canvas

        m_container = container;
    }

    // override the paint method to draw the arrows

    public void paint( Graphics g )
    {
        int         nWidth;
        int         nHeight;
    
        // get the width and height of this canvas

        nWidth = this.size( ).width;
        nHeight = this.size( ).height;
        
        // draw the east/west lines and arrowheads

        g.drawLine( 5, nHeight / 2, nWidth - 6, nHeight / 2 );
        g.drawLine( 5, nHeight / 2, 10, nHeight / 2 - 5 );
        g.drawLine( 5, nHeight / 2, 10, nHeight / 2 + 5 );
        g.drawLine( nWidth - 6, nHeight / 2, nWidth - 11, nHeight / 2 - 5 );
        g.drawLine( nWidth - 6, nHeight / 2, nWidth - 11, nHeight / 2 + 5 );
        
        // draw the north/south lines and arrowheads

        g.drawLine( nWidth / 2, 5, nWidth / 2, nHeight - 6 );
        g.drawLine( nWidth / 2, 5, nWidth / 2 - 5, 10 );
        g.drawLine( nWidth / 2, 5, nWidth / 2 + 5, 10 );
        g.drawLine( nWidth / 2, nHeight - 6, nWidth / 2 - 5, nHeight - 11 );
        g.drawLine( nWidth / 2, nHeight - 6, nWidth / 2 + 5, nHeight - 11 );
    }

    // the preferred and minimum sizes for the canvas are one-fifth the container width and
    // one-third the container height

    public Dimension minimumSize( )
    {
        Dimension d = m_container.size( );

        return new Dimension( d.width / 5, d.height / 3 );
    }

    public Dimension preferredSize( )
    {
        return this.minimumSize( );
    }

}

Back to top


GroundtrackCanvas.java

// author: craig cox
//         rea computing, inc.

import java.awt.*;
import java.util.Vector;

// this class is a canvas that displays the groundtrack of the aircraft, either no-wind or
// wind-corrected

class GroundtrackCanvas extends Canvas
{
    private boolean             m_bApplyWind = false;
    private TrueAirspeedApplet  m_applet;
    private Vector              i_vTrack = new Vector( );

    // constructor that stores the TrueAirspeedApplet containing the canvas

    GroundtrackCanvas( TrueAirspeedApplet myApplet )
    {
        m_applet = myApplet;
    }
    
    // set the mode so the canvas displays the no-wind (false) or wind-corrected (true)
    // ground track

    public void setPaintMode( boolean myApplyWind )
    {
        m_bApplyWind = myApplyWind;
    }
    
    // override the paint method to display the ground track

    public void paint( Graphics g )
    {
        double                      dScale;
        int                         nDirectionCount;
        int                         nDx;
        int                         nDy;
        int                         nHeight;
        int                         nMaxX;
        int                         nMaxY;
        int                         nMinX;
        int                         nMinY;
        int                         nOldX;
        int                         nOldY;
        int                         nSouthWind;
        int                         nTrueAirspeed;
        int                         nWestWind;
        int                         nWidth;
        int                         nX;
        int                         nXOffset;
        int                         nY;
        int                         nYOffset;
        String                      sDirectionOrder = m_applet.getDirectionOrder( );
        TrueAirspeedWindEstimate    estimate = m_applet.getEstimate( );
        Vector                      v = new Vector( );
    
        // get the estimated airspeed and wind (the southerly and westerly components)

        nTrueAirspeed = ( int ) Math.round( estimate.getTrueAirspeed( ) );
        nSouthWind = ( int ) Math.round( estimate.getWind( 'S' ) );
        nWestWind = ( int ) Math.round( estimate.getWind( 'W' ) );
        
        nDx = 0;
        nDy = 0;
        nMinX = 0;
        nMaxX = 0;
        nMinY = 0;
        nMaxY = 0;
        nX = 0;
        nY = 0;
        
        // loop through the groundspeed directions.  nDx is the amount the aircraft moves to
        // the east.  nDy is the amount the aircraft moves to the north.  if a no-wind
        // groundtrack is being drawn, use only the groundspeed of the aircraft.  if a
        // wind-corrected groundtrack is being drawn, use the current estimate for wind and
        // airspeed to determine how far the aircraft has moved north (or south) and east (or
        // west).

        nDirectionCount = sDirectionOrder.length( );
        
        for ( int i = 0; i < nDirectionCount; i++ )
        {
            switch ( sDirectionOrder.charAt( i ) )
            {
                case 'N':
                {
                    if ( m_bApplyWind )
                    {
                        nDx = nWestWind;
                        nDy = nTrueAirspeed + nSouthWind;
                    }
                    else
                    {
                        nDx = 0;
                        nDy = ( int ) Math.round( estimate.getGroundspeed( 'N' ) );
                    }
                    
                    break;
                }
                
                case 'E':
                {
                    if ( m_bApplyWind )
                    {
                        nDx = nTrueAirspeed + nWestWind;
                        nDy = nSouthWind;
                    }
                    else
                    {
                        nDx = ( int ) Math.round( estimate.getGroundspeed( 'E' ) );
                        nDy = 0;
                    }
                    
                    break;
                }
                
                case 'S':
                {
                    if ( m_bApplyWind )
                    {
                        nDx = nWestWind;
                        nDy = nSouthWind - nTrueAirspeed;
                    }
                    else
                    {
                        nDx = 0;
                        nDy = ( int ) Math.round( -estimate.getGroundspeed( 'S' ) );
                    }
                    
                    break;
                }
                
                case 'W':
                {
                    if ( m_bApplyWind )
                    {
                        nDx = nWestWind - nTrueAirspeed;
                        nDy = nSouthWind;
                    }
                    else
                    {
                        nDx = ( int ) Math.round( -estimate.getGroundspeed( 'W' ) );
                        nDy = 0;
                    }
                    
                    break;
                }
            }

            // compute the new position of the aircraft the bounds of the box that contains
            // this and all previous positions

            nMaxX = Math.max( nMaxX, nX + nDx );
            nMinX = Math.min( nMinX, nX + nDx );
            nMaxY = Math.max( nMaxY, nY - nDy );
            nMinY = Math.min( nMinY, nY - nDy );
            nX += nDx;
            nY -= nDy;
            
            // add the new position to a vector

            v.addElement( new Point( nX, nY ) );
        }
        
        // get the dimensions of the canvas

        nWidth = this.size( ).width;
        nHeight = this.size( ).height;
        
        // determine the scale and offset necessary so all aircrafts positions will be
        // centered on the canvas

        dScale = Math.min( ( nWidth - 20 ) / ( double ) ( nMaxX - nMinX ),
            ( nHeight - 20 ) / ( double ) ( nMaxY - nMinY ) );
        nXOffset = ( int ) ( ( nWidth / 2 ) - ( dScale * ( ( ( double ) nMinX + nMaxX ) / 2 ) ) );
        nYOffset = ( int ) ( ( nHeight / 2 ) - ( dScale * ( ( ( double ) nMinY + nMaxY ) / 2 ) ) );
        
        nOldX = nXOffset;
        nOldY = nYOffset;

        // draw the lines representing the groundtracks of the airplane
        
        for ( int i = 0; i < v.size( ); i++ )
        {
            nX = ( int ) ( ( ( ( ( Point ) v.elementAt( i ) ).x ) * dScale ) + nXOffset );
            nY = ( int ) ( ( ( ( ( Point ) v.elementAt( i ) ).y ) * dScale ) + nYOffset );
            
            g.drawLine( nOldX, nOldY, nX, nY );
            
            nOldX = nX;
            nOldY = nY;
        }
        
        // draw arrowhead if any legs were drawn
        
        if ( nDx != 0 || nDy != 0 )
        {
            if ( Math.abs( Math.abs( ( double ) nDy / nDx ) - Math.sqrt( 2.0 ) ) < 1 )
            {
                // closer to diagonal
                
                nX = ( nDx < 0 ) ? nX - 1 : nX + 1;
                nY = ( nDy < 0 ) ? nY + 1 : nY - 1;
                        
                g.drawLine( nX, nY, ( nDx < 0 ) ? nX + 7 : nX - 7, nY );
                g.drawLine( nX, nY, nX, ( nDy < 0 ) ? nY - 7 : nY + 7 );
            }
            else if ( Math.abs( nDy ) > Math.abs( nDx ) )
            {
                // closer to vertical

                if ( nDy < 0 )
                {
                    g.drawLine( nX, nY + 1, nX - 5, nY - 4 );
                    g.drawLine( nX, nY + 1, nX + 5, nY - 4 );
                }
                else
                {
                    g.drawLine( nX, nY - 1, nX - 5, nY + 4 );
                    g.drawLine( nX, nY - 1, nX + 5, nY + 4 );
                }
            }
            else
            {
                // closer to horizontal
                
                if ( nDx < 0 )
                {
                    g.drawLine( nX - 1, nY, nX + 4, nY - 5 );
                    g.drawLine( nX - 1, nY, nX + 4, nY + 5 );
                }
                else
                {
                    g.drawLine( nX + 1, nY, nX - 4, nY - 5 );
                    g.drawLine( nX + 1, nY, nX - 4, nY + 5 );
                }
            }   
        }
    }

    // the preferred and minimum sizes of canvas are one-third the width and one-half the
    // height of the applet

    public Dimension minimumSize( )
    {
        Dimension d = m_applet.size( );

        return new Dimension( d.width / 3, d.height / 2 );
    }

    public Dimension preferredSize( )
    {
        return this.minimumSize( );
    }
}

Back to top


For comments or questions about this web site, contact the webmaster at

Copyright 2001 REA Computing, Inc.