// 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( ); } }
![]() |
![]() Copyright 2001 REA Computing, Inc. |
![]() |