// 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( ); } }
// 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; } }
// 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 ); } }
// 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( ); } }
// 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( ); } }
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. |