Add OpenGL heads-up display
arcgissamples\display\ShowHUDCommand.java
/* Copyright 2010 ESRI
* 
* All rights reserved under the copyright laws of the United States
* and applicable international laws, treaties, and conventions.
* 
* You may freely redistribute and use this sample code, with or
* without modification, provided you include the original copyright
* notice and use restrictions.
* 
* See the use restrictions.
* 
*/
package arcgissamples.display;

import java.io.IOException;
import java.text.DecimalFormat;
import java.text.NumberFormat;

import javax.media.opengl.GL;
import javax.media.opengl.GLContext;
import javax.media.opengl.GLDrawableFactory;

import com.esri.arcgis.controls.BaseCommand;
import com.esri.arcgis.controls.HookHelper;
import com.esri.arcgis.display.DisplayTransformation;
import com.esri.arcgis.display.IDisplay;
import com.esri.arcgis.display.IDynamicDisplay;
import com.esri.arcgis.display.IDynamicDrawScreen;
import com.esri.arcgis.display.IDynamicGlyph;
import com.esri.arcgis.display.IDynamicGlyphFactory;
import com.esri.arcgis.display.IDynamicMapEventsAdapter;
import com.esri.arcgis.display.IDynamicMapEventsAfterDynamicDrawEvent;
import com.esri.arcgis.display.IDynamicSymbolProperties;
import com.esri.arcgis.display.ISymbol;
import com.esri.arcgis.display.ITextSymbol;
import com.esri.arcgis.display.TextSymbol;
import com.esri.arcgis.display.esriDynamicMapDrawPhase;
import com.esri.arcgis.display.esriDynamicSymbolRotationAlignment;
import com.esri.arcgis.display.esriDynamicSymbolType;
import com.esri.arcgis.display.esriTextHorizontalAlignment;
import com.esri.arcgis.display.esriTextVerticalAlignment;
import com.esri.arcgis.system.tagRECT;
import com.esri.arcgis.geometry.IPoint;
import com.esri.arcgis.geometry.Point;
import com.esri.arcgis.interop.AutomationException;

class ShowHUDCommand extends BaseCommand {
  private static final long serialVersionUID = 1L;
  private boolean checked = false;

  @Override
  public boolean isChecked() {
    return this.checked;
  }

  private HookHelper captainHook;
  public void onCreate(Object hook) {
    try {
      this.captainHook = new HookHelper();
      this.captainHook.setHookByRef(hook);
    } catch (Exception e) {
      e.printStackTrace();
    }
    this.caption = "Show HUD";
    this.enabled = true;
    this.name = "Show HUD";
  }


  @Override
  public void onClick() {
    this.checked = !this.checked;
    try {
      com.esri.arcgis.carto.Map map = (com.esri.arcgis.carto.Map) captainHook
      .getFocusMap();

      if (this.checked) {
        //Enable dynamic mode
        map.setDynamicMapEnabled(true);

        //Add the dynamic map events listener
        //This is where we draw the HUD in the AfterDynamicDraw event.
        map.addIDynamicMapEventsListener(new IDynamicMapEventsAdapter() {
          private static final long serialVersionUID = 1L;

          private GL gl = null;

          private GLContext context = null;

          private IDisplay Display;

          private tagRECT deviceFrame;

          private DisplayTransformation displayXformation;

          private IDynamicGlyph textGlyph = null;

          private IDynamicGlyphFactory glyphFactory = null;

          private IDynamicDrawScreen drawScreen = null;

          private IDynamicSymbolProperties symbolProperties = null;

          private IPoint anchor = null;

          private NumberFormat formatter;

          public void afterDynamicDraw(
              IDynamicMapEventsAfterDynamicDrawEvent theEvent)
          throws IOException, AutomationException {
            if (theEvent.getDynamicMapDrawPhase() != esriDynamicMapDrawPhase.esriDMDPDynamicLayers)
              return;
            Display = theEvent.getDisplay();
            displayXformation = (DisplayTransformation) Display
            .getDisplayTransformation();
            // get the device frame size
            deviceFrame = displayXformation.getDeviceFrame();
            float rotation = (float) displayXformation
            .getRotation();

            try {
              if (context == null) {
                // Tell JOGL that we are going to use
                // our own OpenGL Context
                context = GLDrawableFactory.getFactory()
                .createExternalGLContext();
                context.makeCurrent();
                gl = context.getGL();

                // need to cache all the DynamicDisplay
                // stuff
                IDynamicDisplay dynamicDisplay = theEvent
                .getDynamicDisplay();
                glyphFactory = dynamicDisplay
                .getDynamicGlyphFactory();
                drawScreen = (IDynamicDrawScreen) dynamicDisplay;
                symbolProperties = (IDynamicSymbolProperties) dynamicDisplay;

                // set the screen coordinates of the
                // char symbol
                anchor = new Point();

                textGlyph = createDynamicGlyphs(glyphFactory);

                formatter = DecimalFormat
                .getNumberInstance();
                formatter.setMaximumFractionDigits(2);

              }
              // draw the OpenGL compass
              gl.glPushMatrix();
              gl.glLoadIdentity();

              // use OpenGL to do the drawings
              drawTicks(deviceFrame, rotation);

              gl.glPopMatrix();

              // please note that while you are rotating the
              // map, the numbers showing in the HUD are
              // different than the number reported by the
              // rotation tool. The reason for that is that
              // the reportad map rotation is the mathematical
              // angle while the HUD shows the angle from
              // the north (the map's azimuth).
              drawAzimuths(deviceFrame, rotation);
            } catch (Exception e) {
              e.printStackTrace();
            }

          }

          private void drawAzimuths(tagRECT deviceFrame,
              float angle) throws Exception {
            // need to draw the current azimuth
            symbolProperties.setColor(
                esriDynamicSymbolType.esriDSymbolText, 0.0f,
                0.8f, 0.0f, 1.0f); // Green
            // assign the item's glyph to the dynamic-symbol
            symbolProperties.setDynamicGlyphByRef(
                esriDynamicSymbolType.esriDSymbolText,
                textGlyph);

            // get the floor of the azimuth
            float azimuth = 360.0f - angle;
            float floorAzi = (int) (azimuth / 10.0f) * 10.0f;
            double deltaAzi = (angle - (float) ((int) (angle / 10.0f) * 10.0f)) * 6.0; // (the
            // shift
            // to
            // the
            // X
            // axis)

            double xmin = (double) deviceFrame.left;
            double xmax = (double) deviceFrame.right;
            double ymin = (double) deviceFrame.top;
            double xmiddle = (xmax + xmin) / 2.0;

            double x = xmiddle - 150.0 + deltaAzi;

            double dAzStartMiddle = (150.0 - 2.0 * deltaAzi) / 6.0;
            dAzStartMiddle = (int) (dAzStartMiddle / 60.0) * 60.0 + 10.0;
            int azi = (int) (floorAzi - dAzStartMiddle) - 5;
            double delta = 60.0;
            symbolProperties.setHeading(
                esriDynamicSymbolType.esriDSymbolText, 0.0f);
            symbolProperties
            .setRotationAlignment(
                esriDynamicSymbolType.esriDSymbolText,
                esriDynamicSymbolRotationAlignment.esriDSRAScreen);
            for (int i = 0; i < 5; i++) {
              anchor.putCoords(x, ymin + 28.0);

              if (azi > 360)
                azi -= 360;
              else if (azi < 0)
                azi += 360;
              drawScreen.drawScreenText(anchor, new Integer(
                  azi).toString());

              azi += 10;
              x += delta;
            }

            // need to draw the current azimuth
            symbolProperties.setColor(
                esriDynamicSymbolType.esriDSymbolText, 0.0f,
                0.0f, 0.0f, 1.0f); // Black
            anchor.putCoords(xmiddle, ymin + 95.0);

            drawScreen.drawScreenText(anchor, formatter
                .format(azimuth));
          }

          private void drawTicks(tagRECT deviceFrame2,
              float azimuth) {
            // get the floor of the azimuth
            float floorAzi = (int) (azimuth / 10.0f) * 10.0f;
            float deltaAzi = (azimuth - floorAzi) * 6.0f;
            float deltaAziSmall = (azimuth - ((int) (azimuth / 2.0f) * 2.0f)) * 6.0f;

            float delta = 60.0f;
            float deltaSmall = 12.0f;
            float xmin = (float) deviceFrame.left;
            float xmax = (float) deviceFrame.right;
            float ymin = (float) deviceFrame.top;
            float xmiddle = (xmax + xmin) / 2.0f;

            gl.glDisable(GL.GL_TEXTURE_2D);

            // draw a line from left to right
            gl.glColor3f(0.0f, 0.5f, 0.0f);
            gl.glLineWidth(1.5f);
            gl.glBegin(GL.GL_LINES);
            gl.glVertex2f(xmiddle - 150.0f, ymin + 40.0f);
            gl.glVertex2f(xmiddle + 150.0f, ymin + 40.0f);
            gl.glEnd();

            // draw the 10 degrees big ticks
            float x = xmiddle - 150.0f + deltaAzi;

            for (int i = 0; i < 5; i++) {
              gl.glBegin(GL.GL_LINES);
              gl.glVertex2f(x, ymin + 40.0f);
              gl.glVertex2f(x, ymin + 80.0f);
              gl.glEnd();
              x += delta;
            }

            // draw the 2 degrees small ticks
            x = xmiddle - 150.0f + deltaAziSmall;
            gl.glLineWidth(1.0f);
            for (int i = 0; i < 25; i++) {
              gl.glBegin(GL.GL_LINES);
              gl.glVertex2f(x, ymin + 40.0f);
              gl.glVertex2f(x, ymin + 60.0f);
              gl.glEnd();
              x += deltaSmall;
            }

            gl.glLineWidth(2.0f);
            gl.glColor3f(0.0f, 0.0f, 0.0f);
            gl.glBegin(GL.GL_TRIANGLES);
            gl.glVertex2f(xmiddle, ymin + 40.0f);
            gl.glVertex2f(xmiddle - 8.0f, ymin + 60.0f);
            gl.glVertex2f(xmiddle + 8.0f, ymin + 60.0f);
            gl.glEnd();

            gl.glEnable(GL.GL_TEXTURE_2D);
          }

          private IDynamicGlyph createDynamicGlyphs(
              IDynamicGlyphFactory glyphFactory) {
            try {
              // create the text glyph using a text symbol
              ITextSymbol textSymbol = new TextSymbol();
              textSymbol.setSize(15.0);
              textSymbol.setAngle(0.0);
              textSymbol
              .setVerticalAlignment(esriTextVerticalAlignment.esriTVACenter);
              textSymbol
              .setHorizontalAlignment(esriTextHorizontalAlignment.esriTHACenter);
              return glyphFactory
              .createDynamicGlyph((ISymbol) textSymbol);
            } catch (Exception e) {
              e.printStackTrace();
              return null;
            }

          }

        });
      } else {
        //Disable dynamic mode
        map.setDynamicMapEnabled(false);
      }
      this.captainHook.getActiveView().getScreenDisplay().updateWindow();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

}