The Air Map Class

This creates and updates the 300 by 300 pixel map area on the right hand side of the applet window. The map is double-buffered. All drawing methods draw on an image object out of sight of the user. Then, once finished, the updated map is copied onto the map canvas by the canvas's paint() method.

The map is basic, simple and sparse. It would be possible to lay in background map tiles showing greater geographic detail such as coastlines, and relief and rivers. However, in the context of this system such things are of little relevance and it is felt that they would serve only to cloud the vital information the map currently provides.

/**
  * Navigation Information Display Panel for Moving Map package
  * @author Robert J Morton
  * @version 19 December 1997
*/

/* To display the map for the moving map package. Provides main
   canvas on which to display the map plus an image of the map.
   The image is updated unseen, then copied to the canvas once
   each cyclic update of the map has been completed. */

import java.awt.*;

class airmap extends Canvas implements navconst {
   private static airmap am;              //reference to air map object
   private aircraft ac;                   //reference to current aircraft object
   private waypnt wc;                     //reference to current waypoint object
   private selmap sm;                     //reference to map selector panel
   private int X = 150, Y = 150;          //maximum x and y from centre of canvas
   private int X1 = X >> 1;               //To test whether city name should be displayed
   private int X2 = X + X1;               // on the left or right of the 'city'.
   private int x = 100, y = 100;          //pixel co-ordinates of current geographic feature
   private int z = 7, hz = 3;             //diameter of city blob, and half of it
   private Image I;                       //reference to off-screen image used to update the map
   private Graphics i;                    //graphics context of this off-screen image
   private Font font;                     //font for on-map lettering
   private FontMetrics fm;                //letter dimensions etc for the above font
   private int fh;                        //font height (total height of a line of text)
   private int fa;                        //half font ascent (height of letters above base line)
   private int Nw;                        //half-width of the letter 'N' for the compass cross
   private Color GCC;                     //create special bright green for circle
   private Color RCC;                     //create special bright red for circle
   private Color BGC;                     //create special map background colour
   private Color SRC;                     //create special colour for selected radial line
   private double Scale;                  //map scale in pixels per kilometre
   private double InM = 50;               //length of standard outbound radial line in km
   private double OutM = 30;              //length of standard outbound radial line in km
   private double h;                      //screen x-pixel of a geographic feature
   private double v;                      //screen y-pixel of a geographic feature
   private double Dst;                    //aircraft's current distance from current waypoint
   private double ah;                     //aircraft's current heading
   private int CX = X + 115;              //x co-ordinate of the centre of the compass cross
   private int CY = Y + 115;              //y co-ordinate of the centre of the compass cross

   airmap(Image a, Graphics g, Font f, FontMetrics m) {
      am = this;                                 //reference to airmap object
      I = a;                                     //set reference to off-screen image
      i = g;                                     //set graphics context of off-screen image
      font = f;                                  //set font for on-map lettering
      fm = m;                                    //set letter dimensions etc for above font
      fa = fm.getAscent() >> 1;                  //get the font's ascent (ht above baseline)
      GCC = new java.awt.Color( 72, 255,  72);   //create special bright green for circle
      RCC = new java.awt.Color(255,  72,  72);   //create special bright red for circle
      BGC = new java.awt.Color(  0,   0, 160);   //create special map background colour
      SRC = new java.awt.Color(  0, 255, 255);   //create special colour for selected radial line
      Nw = fm.stringWidth("N") / 2;              //half the width of the letter 'N' for the compass
   }

   public void paint(Graphics g) {               //Reproduce the off-screen image
      g.drawImage(I, 0, 0, null);                // onto the visible window canvas.
   }

   public void update(Graphics g) {              //UPDATE THE OFF-SCREEN IMAGE
      ac = aircraft.getCurrent();                //get reference to current aircraft object
      i.setColor(BGC);                           //set background colour for map image
      i.fillRect(0, 0, 300, 300);                //paint image in its background colour
      i.setFont(font);                           //set font for name and compass
      ah = ac.getHdg();                          //get aircraft's current  heading
      wc = waypnt.getCurrent();                  //reference to current waypoint object
      sm = selmap.getCurrent();                  //get reference to map selector panel
      Scale = sm.getScale();                     //get the current value of the map scaling factor
      int I = waypnt.getTotal();                 //number of waypoints in the database 
      for(int k = 0 ; k < I ; k++) {             //for each geographic feature in the database
         waypnt w = waypnt.getRef(k);            //reference the next waypoint
         if(DBtoXY(w)) {                         //if feature is within map window range
            if(w == wc) drawNavConstructs();     //if it is current waypoint, draw navigation constructs
            i.setColor(Color.lightGray);         //set colour for waypoint blob
            i.fillOval(x - hz, y - hz, z, z);    //draw it in the off-screen image
            String s = w.getName();              //annotate it with its name
            if(x < X2)                           //if it is less that 3/4 the way across the map
               x += hz + 3;                      //put its name on the right
            else                                 //otherwise
               x -= hz + 2 + fm.stringWidth(s);  //put its name on the left
            i.drawString(s, x, y + fa);          //display the feature's name
         }
         if(w == wc && Dst > w.getRange())       //if beyond radio range of current waypoint
             drawGuideBars();                    //draw the GPS/Inertial guidance bars.
      }
      drawCrossHairs();                          //draw aircraft cross-hairs at centre of map
      drawCompass();                             //draw compass in bottom right of map
      paint(g);                                  //display the 'cleared' image
   }

/* COMPUTE X,Y SCREEN CO-ORDINATES FROM RANGE AND BEARING
   Assumes logical co-ordinates are at centre of 300 by 300
   pixel canvas. Returns false if feature out of screen range. */

   private boolean DBtoXY(waypnt w) {            //COMPUTE X,Y PIXEL CO-ORDS FROM DISTANCE & BREARING
      if(!w.DandB()) return false;               //abort if waypoint is out of range
      if((Dst = w.getDst()) == 0) {              //if aircraft is at the waypoint
         x = X; y = Y;                           //set 'int' x and y co-ordinates to map centre
         h = 0; v = 0;                           //and also their 'double' versions
         return true;                            //and return that it is in range
      }
      double d = Dst * Scale;                    //distance of waypoint in km
      double b = w.getrBrg() - ac.getHdg();      //bearing of waypoint in radians less aircraft heading
      h = d * Math.sin(b);                       //screen x-pixel in double float
      v = d * Math.cos(b);                       //screen y-pixel in double float
      x = (int)(Math.rint(h));                   //rounded interger screen x-pixel
      y = (int)(Math.rint(v));                   //rounded interger screen y-pixel
      if(Math.abs(x) > X || Math.abs(y) > Y)     //safe screen limits of x and y
         return false;                           //waypoint is beyond screen range
      x += X; y = Y - y;                         //relate co-ordinates to centre of canvas
      return true;                               //return that it is within screen range
   }

   private void drawNavConstructs() {            //DRAW THE LINES AND CIRCLES ETC ON THE MAP
      double angle = wc.getOutRad() - ah;        //screen angle of outbound radial line
      double S = Math.sin(angle);                //sine of it
      double C = Math.cos(angle);                //cosine of it
      drawCircles(S, C);                         //draw the red and green guide circles
      drawOutRad(S, C);                          //draw the outboud radial line
      Corridor();                                //draw the approach corridor boundary lines
   }

/* Displays a magenta bar pointing from the aircraft to the waypoint it is currently approaching
   and an orange bar pointing from the aircraft back towards the waypoint it has just left. */

   private void drawGuideBars() {                //Shows rBrg to approaching and receding waypoints
      double Brg = wc.getrBrg() - ah;            //bearing (rBrg) from aircraft to current waypoint
      double bf = 0, bb = 0;                     //for bearings (rBrg) to front and back waypoints
      waypnt nw = wc.getNext(),                  //reference to next waypoint's object
             pw = wc.getPrev();                  //reference to previous waypoint's object
      if(wpenc.getCurrent().getToFlag()) {       //if approaching current waypoint
         pw.DandB();                             //get distance and bearing to previous waypoint
         bb = pw.getrBrg() - ah;                 //get bearing to previous waypoint (behind aircraft)
         bf = Brg;                               //get bearing to current waypoint (ahead of aircraft)
      } else {                                   //else if receding from current waypoint
         bb = Brg;                               //get bearing to current waypoint (behind aircraft)
         nw.DandB();                             //get distance and bearing of next waypoint
         bf = nw.getrBrg() - ah;                 //get bearing to next waypoint (ahead of aircraft)
      }
      i.setColor(Color.magenta);                 //color for pointer to waypoint ahead
      i.drawLine(X, Y, X + (int)(100 * Math.sin(bf)), Y - (int)(100 * Math.cos(bf)));
      i.setColor(Color.orange);                  //colour for pointer to waypoint behind
      i.drawLine(X, Y, X + (int)(100 * Math.sin(bb)), Y - (int)(100 * Math.cos(bb)));
   }

   private void drawCompass() {                  //SHOW LITTLE COMPASS CROSS AT BOTTOM RIGHT
      double a = Math.sin(ah),                   //sine and cosine of aircraft's heading
             b = Math.cos(ah);
      i.setColor(SRC);                           //colour in which to display the compass cross
      i.drawString("N",                          //draw the letter 'N' for North
         CX - (int)(23 * a) - Nw,
         CY - (int)(23 * b) + fa
      );
      int c = (int)(15 * a);                     //vertical & horizontal off-sets of the 
      int d = (int)(15 * b);                     //points of the compass cross
      i.drawLine(CX - c, CY - d, CX + c, CY + d);//draw north-south line
      i.drawLine(CX + d, CY - c, CX - d, CY + c);//draw east-west line
   }

   private void drawCrossHairs() {               //DISPLAY CENTRAL CROSS AND EDGE LINES ON MAP
      i.setColor(Color.white);                   //set colour for central cross-hairs
      i.drawLine(140, 150, 160, 150);            //draw the horizontal hair
      i.drawLine(150, 140, 150, 160);            //draw the vertical hair
      i.setColor(Color.lightGray);               //set colour for edge cross-hairs
      i.drawLine(150, 0, 150, 10);               //draw the top vertical hair
      i.drawLine(150, 290, 150, 300);            //draw the bottom vertical hair
      i.drawLine(0, 150, 10, 150);               //draw the left horizontal hair
      i.drawLine(290, 150, 300, 150);            //draw the right horizontal hair
   }

   private void drawCircles(double S, double C) {//DISPLAY THE GREEN AND RED GUIDE CIRCLES AT THE CURRENT WAYPOINT
      double r = ac.getTCR() * Scale;            //radius of aircraft's minimum comfortable turning circle in km
      double rS = r * S;                         //x off-set of centre of turning circle perpendicular to OutRad
      double rC = r * C;                         //y off-set of centre of turning circle perpendicular to OutRad
      double da = sm.getMapSize();               //get the appropriate angular increment for drawing the circles
      for(double a = 0; a < TwoPi; a += da) {    //for every degree or so around a guide circle...
         double xw = r * Math.sin(a);            //Absolute co-ordinates of one point on a generic guide circle
         double yw = r * Math.cos(a);            // which is centred on the waypoint.
         int
         p = x + (int)(Math.rint(xw + rC)),
         q = y - (int)(Math.rint(yw - rS));
         i.setColor(GCC);                        //colour for the green (right-hand) circle
         i.drawLine(p, q, p, q);                 //plot next point on green circle
         p = x + (int)(Math.rint(xw - rC));
         q = y - (int)(Math.rint(yw + rS));
         i.setColor(RCC);                        //colour for the red (left-land) circle
         i.drawLine(p, q, p, q);                 //plot next point on red circle
      }
   }

   private void drawOutRad(double S, double C) { //MARK OUTBOUND RADIAL FROM CURRENT WAYPOINT
      double r = wc.getDST() / 2;                //half the distance to next waypoint in km
      if(r > OutM) r = OutM;                     //impose an upper limit on length of displayed line
      r *= Scale;                                //adjust it to currently selected map scale
      i.setColor(SRC);                           //colour for the radial lines
      i.drawLine(x, y,                           //draw the outbound radial
         X + (int)(Math.rint(h + r * S)),        //x co-ordinate of end of outbound radial line
         Y - (int)(Math.rint(v + r * C))         //y co-ordinate of end of outbound radial line
      );
   }

   private void Corridor() {                     //MARK OUT CURRENT WAYPOINT'S APPROACH CORRIDOR
      double a = wc.getInRad() - ac.getHdg();    //screen angle of inbound radial line
      double S = Math.sin(a);                    //sine of InRad - aircraft heading
      double C = Math.cos(a);                    //cosine of InRad - aircraft heading
      double m = ac.getCM();                     //distance from waypoint to where approach corridor ends
      double r = wc.getPWD() / 2;                //half dist from prev waypoint (where approach corridor starts)
      if(r > InM) r = InM;                       //limit the displayed length of inbound corridor
      if(r > m) {                                //if there is space for an approach corridor
         r *= Scale; m *= Scale;                 //adjust its length to currently selected map scale
         double lx = r * S;                      //x component of corridor start
         double ly = r * C;                      //y component of corridor start
         double rx = m * S;                      //x component of corridor end
         double ry = m * C;                      //y component of corridor end
         i.drawLine(                             //draw the inbound radial line
            X + (int)(Math.rint(h + rx)),
            Y - (int)(Math.rint(v + ry)),
            X + (int)(Math.rint(h + lx)),
            Y - (int)(Math.rint(v + ly))
         );
         double w = wc.getDL() * Scale * 2;      //corridor width
         double wx = w * C;                      //x component of corridor width
         double wy = w * S;                      //y component of corridor width
         i.drawLine(                             //draw other boundary of approach corridor
            X + (int)(Math.rint(h + rx - wx)),
            Y - (int)(Math.rint(v + ry + wy)),
            X + (int)(Math.rint(h + lx - wx)),
            Y - (int)(Math.rint(v + ly + wy))
         );
      }
   }

   static airmap getCurrent() {return am;}       //return reference to current air map object
}

/*  Robert J Morton, the author of this program, 
    is a poor but Right Honourable Member of the
    Ancient and Noble Order of the Long-term Unemployed.

    Offers of work please to: robmorton@clara.net  */