JEphem Informatic Trail JEphem source code SkyMap.java
//*********************************************************************************
// class jephem.tools.SkyMap
// Software released under the General Public License (version 2 or later), available at
// http://www.gnu.org/copyleft/gpl.html
//*********************************************************************************
package jephem.tools;

import jephem.tools.SkyMapBase;

import jephem.astro.sky.BSC5;
import jephem.astro.sky.Constellations;
import jephem.astro.sky.ConstellationConstants;
import jephem.astro.spacetime.SpaceConstants;
import jephem.astro.solarsystem.SolarSystemConstants;
import jephem.GlobalVar; // only used to retrieve BSC5 directory
import jephem.util.Debug;

import tig.GeneralConstants;
import tig.Formats;
import tig.maths.Maths;
import tig.maths.Matrix3;
import tig.maths.Vector3;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.geom.*;
import java.io.*;

/******************************************************************************
A visual representation of a sky map. This class permits to specify the visual gadgets of a sky map : grid display; constellation display...
<BR>The core functionalities of a sky map are implemented in its super class, {@link SkyMapBase}.
@author Thierry Graff
@history apr 23 2002 : creation.
@history apr 29 2002 : rotations of the view.
@history may 09 2002 : Split to give SkyMapBase and SkyMap (for clearer code).

@todo maybe put get / set GridInterval in radians directly
*********************************************************************************/
public class SkyMap extends SkyMapBase{

  //=================================================================================
  //                                      INSTANCE VARIABLES
  //=================================================================================

  // Grid diplay variables
  // WARNING : these variables should be accessed only through get / set, even by the code of this class
  // This way, the class can't be affected by eventual changes in SpaceConstants' constant values.
  private int[] _gridDisplay; // for each frame, can be NONE, FULL or PLANE
  private double[] _gridInterval; // in radians

  // Constellation display variables
  private boolean _displayConstellationAbbreviations;
  private boolean _displayConstellationBoundaries;
  private boolean _displayConstellationLines;

  //=================================================================================
  //                               PUBLIC CONSTANTS
  //=================================================================================

  // Display coordinate grid constants
  /** Constant to indicate that no grid must be displayed. */
  public static final int GRID_NONE = 0;
  /** Constant to indicate that only the reference plane must be displayed. */
  public static final int GRID_PLANE = 1;
  /** Constant to indicate that the whole grid must be displayed. */
  public static final int GRID_FULL = 2;

  //=================================================================================
  //                               PRIVATE CONSTANTS
  //=================================================================================
  private final static BasicStroke STROKE = new BasicStroke(1.0f);
  private final static BasicStroke STROKE2 = new BasicStroke(2.0f);
  private final static BasicStroke DASHED = new BasicStroke(1.0f,
                                                            BasicStroke.CAP_BUTT,
                                                            BasicStroke.JOIN_MITER,
                                                            10.0f, new float[]{5.0f}, 0.0f);
  //=================================================================================
  //                            CONSTRUCTORS
  //=================================================================================
  /** Unique constructor. The base frame is set to equatorial frame. */
  public SkyMap(){
    super();
    try {
      BSC5.setDataPath(GlobalVar.getDirectory(GlobalVar.DIR_DATA) + FS + "astro" + FS + "sky" + FS + "BSC5");

      _gridDisplay = new int[4];
      _gridInterval = new double[4];
      setGridDisplay(FRAME_HORIZONTAL_TOPOCENTRIC,  GRID_NONE);
      setGridDisplay(FRAME_EQUATORIAL,              GRID_FULL);
      setGridDisplay(FRAME_ECLIPTIC,                GRID_PLANE);
      setGridDisplay(FRAME_GALACTIC,                GRID_NONE);
      setGridInterval(FRAME_HORIZONTAL_TOPOCENTRIC, 30);
      setGridInterval(FRAME_EQUATORIAL,             10);
      setGridInterval(FRAME_ECLIPTIC,               10);
      setGridInterval(FRAME_GALACTIC,               30);

      _displayConstellationAbbreviations = true;
      _displayConstellationLines = false;
      _displayConstellationBoundaries = true;

      this.setBackground(Color.WHITE);

      this.addMouseListener(new SkyChartMouseListener());
    }
    catch(Exception e) {
      Debug.traceError(e);
    }
  }// end constructor

  //=================================================================================
  //                         GEOMETRICAL PUBLIC METHODS
  //=================================================================================

  //=================================================================================
  //                           GET / SET METHODS for visualization characteristics
  //=================================================================================

  //*************** get / set / change displayConstellationAbbreviations ***************
  /** Returns the flag indicating if the constellation abbreviations must be displayed on the map. */
  //public boolean getDisplayConstellationAbbreviations(){ return _displayConstellationAbbreviations; }
  /** Sets the flag indicating if the constellation abbreviations must be displayed on the map. */
  public void setDisplayConstellationAbbreviations(boolean displayConstellationAbbreviations){
    _displayConstellationAbbreviations = displayConstellationAbbreviations;
    this.repaint();
  }// end setDisplayConstellationAbbreviations
  /** Inverts the flag indicating if the constellation abbreviations must be displayed on the map. */
  public void changeDisplayConstellationAbbreviations(){
    _displayConstellationAbbreviations = !_displayConstellationAbbreviations;
    this.repaint();
  }// end changeDisplayConstellationAbbreviations

  //*************** get / set / change displayConstellationLines ***************
  /** Returns the flag indicating if the constellation lines must be displayed on the map. */
//  public boolean getDisplayConstellationLines(){ return _displayConstellationLines; }
  /** Sets the flag indicating if the constellation lines must be displayed on the map. */
  public void setDisplayConstellationLines(boolean displayConstellationLines){
    _displayConstellationLines = displayConstellationLines;
    this.repaint();
  }// end setDisplayConstellationLines
  /** Inverts the flag indicating if the constellation lines must be displayed on the map. */
  public void changeDisplayConstellationLines(){
    _displayConstellationLines = !_displayConstellationLines;
    this.repaint();
  }// end changeDisplayConstellationLines

  //*************** get / set / change displayConstellationBoundaries ***************
  /** Returns the flag indicating if the constellation boundaries must be displayed on the map. */
  //public boolean getDisplayConstellationBoundaries(){ return _displayConstellationBoundaries; }
  /** Sets the flag indicating if the constellation boundaries must be displayed on the map. */
  public void setDisplayConstellationBoundaries(boolean displayConstellationBoundaries){
    _displayConstellationBoundaries = displayConstellationBoundaries;
    this.repaint();
  }// end setDisplayConstellationBoundaries
  /** Inverts the flag indicating if the constellation boundaries must be displayed on the map. */
  public void changeDisplayConstellationBoundaries(){
    _displayConstellationBoundaries = !_displayConstellationBoundaries;
    this.repaint();
  }// end changeDisplayConstellationBoundaries

  //*************** getGridDisplay ***************
  /** Returns the "grid display" of this sky map.
  @param whichFrame One of the supported frames, using
                    {@link jephem.astro.spacetime.SpaceConstants}.<CODE>FRAME_XXX</CODE> constants.
  @return the grid display, expressed with <CODE>GRID_XXX</CODE> constants of this class.
  */
  public int getGridDisplay(int whichFrame){
    switch (whichFrame) {
      case FRAME_HORIZONTAL_TOPOCENTRIC : return _gridDisplay[0];
      case FRAME_EQUATORIAL :   return _gridDisplay[1];
      case FRAME_ECLIPTIC :   return _gridDisplay[2];
      case FRAME_GALACTIC : return _gridDisplay[3];
      default : throw new IllegalArgumentException("invalid 'whichFrame' parameter");
    }
  }// end getGridDisplay

  //*************** setGridDisplay ***************
  /** Sets the "grid display" of this sky map.
  @param whichFrame One of the supported frames, using
         {@link jephem.astro.spacetime.SpaceConstants}.<CODE>FRAME_XXX</CODE> constants.
  @param gridDisplay Expressed with <CODE>GRID_XXX</CODE> constants of this class.
  */
  public void setGridDisplay(int whichFrame, int gridDisplay){
    switch (whichFrame) {
      case FRAME_HORIZONTAL_TOPOCENTRIC : _gridDisplay[0] = gridDisplay; break;
      case FRAME_EQUATORIAL :   _gridDisplay[1] = gridDisplay; break;
      case FRAME_ECLIPTIC :   _gridDisplay[2] = gridDisplay; break;
      case FRAME_GALACTIC : _gridDisplay[3] = gridDisplay; break;
      default : throw new IllegalArgumentException("invalid 'whichFrame' parameter");
    }
  }// end setGridDisplay

  //*************** changeGridDisplay ***************
  /** Changes the grid display for the specified frame and repaints. */
  public void changeGridDisplay(int frame){
    int gd = getGridDisplay(frame);
    switch (gd) {
      case GRID_NONE : setGridDisplay(frame, GRID_PLANE); break;
      case GRID_PLANE : setGridDisplay(frame, GRID_FULL); break;
      case GRID_FULL : setGridDisplay(frame, GRID_NONE); break;
    }
    repaint();
  }// end changeGridDisplay

  //*************** getGridInterval ***************
  /** Returns the "grid interval" of this sky map, <B>in degrees</B>.
  @param whichFrame One of the supported frames, using
         {@link jephem.astro.spacetime.SpaceConstants}.<CODE>FRAME_XXX</CODE> constants.
  */
  public double getGridInterval(int whichFrame){
    switch (whichFrame) {
      case FRAME_HORIZONTAL_TOPOCENTRIC : return Math.toDegrees(_gridInterval[0]);
      case FRAME_EQUATORIAL :   return Math.toDegrees(_gridInterval[1]);
      case FRAME_ECLIPTIC :   return Math.toDegrees(_gridInterval[2]);
      case FRAME_GALACTIC : return Math.toDegrees(_gridInterval[3]);
      default : throw new IllegalArgumentException("invalid 'whichFrame' parameter");
    }
  }// end getGridInterval

  //**************** setGridInterval ***************
  /** Sets the "grid interval" of this sky map, <B>in degrees</B>.
  @param whichFrame One of the supported frames, using
                    {@link jephem.astro.spacetime.SpaceConstants}.<CODE>FRAME_XXX</CODE> constants.
  @param gridDisplay Expressed with <CODE>GRID_XXX</CODE> constants of this class.
  */
  public void setGridInterval(int whichFrame, double gridInterval){
    switch (whichFrame) {
      case FRAME_HORIZONTAL_TOPOCENTRIC : _gridInterval[0] = Math.toRadians(gridInterval); break;
      case FRAME_EQUATORIAL :   _gridInterval[1] = Math.toRadians(gridInterval); break;
      case FRAME_ECLIPTIC :   _gridInterval[2] = Math.toRadians(gridInterval); break;
      case FRAME_GALACTIC : _gridInterval[3] = Math.toRadians(gridInterval); break;
      default : throw new IllegalArgumentException("invalid 'whichFrame' parameter");
    }
  }// end setGridInterval

  //*****************************************************************************
  //****************************** paintComponent *******************************
  //*****************************************************************************
  /** Paints the sky map on its display area. */
  public void paintComponent(Graphics g){
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;
    calcRHW(); // actualisation of _r, _h, _w

    // Draw the colored disk representing the sky background
    g2.setPaint(new Color(3, 6, 113));
    g2.fill(new Ellipse2D.Double(_w/2 - _r, _h/2 - _r, 2*_r, 2*_r));

    int i;

    // ***** Star display *****
    Vector3 vC, vE, vP; // vectors containing cartesian coords
    int sd; // star diameter
    double ra, dec; // alpha, delta
    // variables for conversion between magnitude and star diameter in pixels
    double a1, b1;
    double d0 = 7, d7 = 1; // d0 : diameter of star of mag 0 ; d7 : diameter of star of mag 7
    a1 = (d7 - d0)/7; b1 = d0;

    g2.setPaint(Color.WHITE);

    // Loop on the stars
    for (i = 0; i < BSC5.NB_STARS; i++){
      // get the star coordinates
      dec = BSC5.getDoubleData(i, BSC5.DEC);
      ra = BSC5.getDoubleData(i, BSC5.RA);
      vC = getCartesianCoords(ra, dec); // get cartesian coords In the sky frame.
      // change from sky frame to the eye frame (use eye matrix).
      vE = Vector3.mul(getEyeMatrix(), vC);
      if(vE.x2 > 0){
        // normally : vS = eyeToScreen(vE); vP = screenToPixel(vS);
        // here, directly from vE to vP as an orthogonal projection is used
        vP = screenToPixel(vE);
        // Draw the star
        sd = (int)(a1*BSC5.getDoubleData(i, BSC5.MAG) + b1);
        g2.fill(new Ellipse2D.Double(vP.x0 - sd/2, vP.x1 - sd/2, sd, sd));
      }
    }// end loop on the stars

    // ***** Constellation display *****
    if(_displayConstellationBoundaries){
      g2.setStroke(DASHED);
      g2.setPaint(Color.YELLOW);
      for (i = 0; i < NB_CONSTELLATIONS; i++) {
        drawConstellationBoundaries(g2, i);
      }
      g2.setStroke(STROKE);
    }

    // ***** Grid display *****
    g2.setPaint(Color.WHITE);
    int interval;
    // Equatorial grid
    if(getGridDisplay(FRAME_EQUATORIAL) == GRID_PLANE){
      g2.setStroke(STROKE2);
      drawCircle(g2, 0, 0, 0); // draw equator plane
      g2.setStroke(STROKE);
    }
    else if(getGridDisplay(FRAME_EQUATORIAL) == GRID_FULL){
      interval = (int)Math.ceil(getGridInterval(FRAME_EQUATORIAL));
      // display parallels
      g2.setStroke(STROKE2);
      drawCircle(g2, 0, 0, 0); // draw equator plane
      g2.setStroke(STROKE);
      for (i = interval; i < 90; i+=interval){
        drawCircle(g2, 0, 0, (double)i);
      }
      for (i = -interval; i > -90; i-=interval){
        drawCircle(g2, 0, 0, (double)i);
      }
      // display meridians
      for (i = 0; i < 360; i+=interval){
        drawCircle(g2, (double)i, 90, 0);
      }
    }
    // Ecliptic grid
    if(getGridDisplay(FRAME_ECLIPTIC) == GRID_PLANE){
      g2.setStroke(STROKE2);
      drawCircle(g2, 0, SolarSystemConstants.E0_2000, 0); // draw ecliptic
      g2.setStroke(STROKE);
    }
    else if(getGridDisplay(FRAME_ECLIPTIC) == GRID_FULL){
      interval = (int)Math.ceil(getGridInterval(FRAME_ECLIPTIC));
      // display parallels
      g2.setStroke(STROKE2);
      drawCircle(g2, 0, SolarSystemConstants.E0_2000, 0); // draw ecliptic
      g2.setStroke(STROKE);
      for (i = interval; i < 90; i+=interval){
        drawCircle(g2, 0, SolarSystemConstants.E0_2000, (double)i);
      }
      for (i = -interval; i > -90; i-=interval){
        drawCircle(g2, 0, SolarSystemConstants.E0_2000, (double)i);
      }
      // display meridians
      for (i = 0; i < 90; i+=interval){
        drawCircle(g2, (double)i, 90 + SolarSystemConstants.E0_2000, 0);
      }
    }

  }// end paintComponent

  //=================================================================================
  //                                      PRIVATE METHODS
  //=================================================================================

  //*************** drawConstellationBoundaries ***************
  /** Auxiliary method of paint() to draws a constellation.
  @param whichConst use ConstellationConstants.
  */
  private void drawConstellationBoundaries(Graphics2D g2, int whichConst){
    double[][] theConst = Constellations.getBoundaries(whichConst);
    Vector3 v0, v1, vP0, vP1; // P stands for Pixel
    v0 = getCartesianCoords(theConst[0][0], theConst[0][1]);
    v0 = Vector3.mul(getEyeMatrix(), v0); // v0 in the eye frame
    for(int i = 1; i < theConst.length; i++){
      v1 = getCartesianCoords(theConst[i][0], theConst[i][1]);
      v1 = Vector3.mul(getEyeMatrix(), v1); // v1 in the eye frame
      if(v0.x2 > 0 && v1.x2 > 0){
        vP0 = screenToPixel(v0);// Transform to pixel coords
        vP1 = screenToPixel(v1);// Transform to pixel coords
        g2.draw(new Line2D.Double(vP0.x0, vP0.x1, vP1.x0, vP1.x1));
      }
      v0 = Vector3.doClone(v1);
    }
  }// end drawConstellationBoundaries


  //=================================================================================
  //                                      INNER CLASSES
  //=================================================================================

  class SkyChartMouseListener extends MouseAdapter{
    public void mousePressed(MouseEvent e){
      String display;
      Vector3 vS = pixelToScreen(new Vector3(e.getX(), e.getY(), 0));
      Vector3 vE = screenToEye(vS);
      Vector3 vC = eyeToSky(vE);
      vC = getSphericalCoords(vC);
      display = "(ra, dec) = (" + Formats.doubleToDMS(vC.x1) + ", " + Formats.doubleToDMS(vC.x2) + ")";
      GlobalVar.setStatusText(display);
    }// end MousePressed
  } // end class SkyChartMouseListener

}// end class SkyMap