// 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. |
|