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 or applets, contact the webmaster.
For the address of the webmaster, look at the first few comments of a Java file.
To obtain all Java files, download the True Airspeed Calculator ZIP file.
The True Airspeed Calculator ZIP file is available on the Distribution Policy page.

Copyright 2001 REA Computing, Inc.