Dynamic tracking
arcgissamples\display\MyDynamicLayer.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.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;

import javax.swing.Timer;

import com.esri.arcgis.carto.IDynamicLayer;
import com.esri.arcgis.carto.ILayer;
import com.esri.arcgis.display.CharacterMarkerSymbol;
import com.esri.arcgis.display.IDisplay;
import com.esri.arcgis.display.IDynamicCompoundMarker;
import com.esri.arcgis.display.IDynamicDisplay;
import com.esri.arcgis.display.IDynamicGlyph;
import com.esri.arcgis.display.IDynamicGlyphFactory;
import com.esri.arcgis.display.IDynamicSymbolProperties;
import com.esri.arcgis.display.RgbColor;
import com.esri.arcgis.display.esriDynamicDrawPhase;
import com.esri.arcgis.display.esriDynamicGlyphType;
import com.esri.arcgis.display.esriDynamicSymbolRotationAlignment;
import com.esri.arcgis.display.esriDynamicSymbolType;
import com.esri.arcgis.geodatabase.IGeoDataset;
import com.esri.arcgis.geometry.Envelope;
import com.esri.arcgis.geometry.IEnvelopeGEN;
import com.esri.arcgis.geometry.ISpatialReference;
import com.esri.arcgis.geometry.Point;
import com.esri.arcgis.interop.AutomationException;
import com.esri.arcgis.support.ms.stdole.StdFont;
import com.esri.arcgis.system.ITrackCancel;
import com.esri.arcgis.system.IUID;
import com.esri.arcgis.system.esriDrawPhase;

public class MyDynamicLayer implements IDynamicLayer, ILayer, IGeoDataset{

  private static final long serialVersionUID = 1L;
  private Point point = null;
  private boolean dynamicGlyphsCreated = false;
  private ActionListener task = null;
  private double extentMaxX = 1000.0;
  private double extentMinX =0;
  private double extentMaxY = 1000.0;
  private double extentMinY= 0;

  private HashMap<Integer, NavigationData> dataMap;
  private String name="";
  private IEnvelopeGEN extent=null;
  private ISpatialReference spatialRef = null;
  private boolean isCached = false;
  @SuppressWarnings("unused")
  private boolean isValid, isVisible = true;
  private double maximumScale, minimumScale = 0;
  private boolean showTips = false;
  private Timer timer = null;

  private IDynamicSymbolProperties dynamicSymbolProps = null;
  private IDynamicGlyphFactory dynamicGlyphFactory= null;
  private IDynamicCompoundMarker dynamicCompoundMarker =null;
  private IDynamicGlyph[] markerGlyphs = null;
  private IDynamicGlyph textglyphs = null;
  private boolean isDirtyImmediate;
  private boolean isDirtyCompiled;

  public MyDynamicLayer(HashMap<Integer, NavigationData> map) {

    try {
      //set the name of the layer
      this.setName("Tracks");
      //instantiate necessary variables
      point = new Point();
      markerGlyphs = new IDynamicGlyph[3];
      this.dataMap=map;
    } catch (IOException e) {
      System.out.println("Exception in MyDynamicLayer#MyDynamicLayer");
      e.printStackTrace();
    }
    //Create a timer and attach a listener that would fire events every 30ms.    
    task = new ActionListener() {
      public void actionPerformed(ActionEvent evt) {

        try {
          //The dirty flag is set to true every 30ms
          //to force the DD framework to redraw the layer  
          setDynamicLayerDirty(esriDynamicDrawPhase.esriDDPImmediate, true);

        } catch (Exception e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      }
    };
    timer = new Timer(30, task);
  }

  /*
   * @see com.esri.arcgis.carto.IDynamicLayer#drawDynamicLayer(int, com.esri.arcgis.display.IDisplay, com.esri.arcgis.display.IDynamicDisplay)
   * This method is invoked by the DD framework to render the dynamic layer
   */
  public void drawDynamicLayer(int phase, IDisplay display, IDynamicDisplay dynamicDisplay) throws IOException, AutomationException {
    if (display == null) {
      System.out.println("Error, could not render layer");
      return;
    }
    if (!isVisible()||!isValid()) {
      System.out.println("Not Visisble or Not valid: Could not render layer");
      return;
    }
    //It is recommended to draw layers only in one phase for performance reasons
    if (esriDynamicDrawPhase.esriDDPImmediate != phase)
    {
      return;
    }

    //get the display fitted bounds to generate random location for the items
    extentMaxX = ((Envelope)display.getDisplayTransformation().getFittedBounds()).getXMax();
    extentMaxY = ((Envelope)display.getDisplayTransformation().getFittedBounds()).getYMax();
    extentMinX = ((Envelope)display.getDisplayTransformation().getFittedBounds()).getXMin();
    extentMinY = ((Envelope)display.getDisplayTransformation().getFittedBounds()).getYMin();

    //updates the current location of all 100 dynamic items
    onUpdate();   

    //Draw the symbols in the random generated location
    drawDynamicSymbols(dynamicDisplay);
    //the dirty flag is set to false to avoid drawing in subsequent draw cycles.
    this.setDynamicLayerDirty(esriDynamicDrawPhase.esriDDPImmediate, false);


  }
  /*
   * This method draws 100 items on a random location 
   * generated from the visible bounds of the current map
   */
  private void drawDynamicSymbols(IDynamicDisplay dynamicDisplay) throws IOException {

    if (!dynamicGlyphsCreated)
    {
      dynamicSymbolProps = (IDynamicSymbolProperties)dynamicDisplay;
      dynamicGlyphFactory = dynamicDisplay.getDynamicGlyphFactory();
      dynamicCompoundMarker = (IDynamicCompoundMarker)dynamicDisplay;
      //Glyphs are expensive hence should be created only once
      this.createDynamicSymbols(dynamicGlyphFactory);
      dynamicGlyphsCreated = true;
    }

    double X, Y, heading;
    int type;
    //draw the 100 items whose location is provided by the dataMap
    for(int i=0;i<this.dataMap.size();i++){
      NavigationData navData = this.dataMap.get(new Integer(i));
      X = navData.x;
      Y =  navData.y;
      heading = navData.azimuth;
      type = navData.type;
      point.putCoords(X,Y);
      //set the symbol's properties
      switch (type)
      {
      case 0:
        //set the heading of the current symbols' text
        dynamicSymbolProps.setHeading(esriDynamicSymbolType.esriDSymbolText, (float)heading);

        //set the symbol alignment so that it will align with the screen
        dynamicSymbolProps.setRotationAlignment(esriDynamicSymbolType.esriDSymbolMarker, esriDynamicSymbolRotationAlignment.esriDSRAScreen);

        //set the text alignment so that it will also align with the screen
        dynamicSymbolProps.setRotationAlignment(esriDynamicSymbolType.esriDSymbolText, esriDynamicSymbolRotationAlignment.esriDSRAScreen);

        //scale the item
        dynamicSymbolProps.setScale(esriDynamicSymbolType.esriDSymbolMarker, 0.8f,0.8F);
        //set the items' color (blue)
        dynamicSymbolProps.setColor(esriDynamicSymbolType.esriDSymbolMarker, 0.0f, 0.0f, 1.0f, 1.0f); // Blue
        //assign the item's glyph to the dynamic-symbol
        dynamicSymbolProps.setDynamicGlyphByRef(esriDynamicSymbolType.esriDSymbolMarker, markerGlyphs[0]);
        dynamicSymbolProps.setDynamicGlyphByRef(esriDynamicSymbolType.esriDSymbolText, textglyphs);

        //set the color of the text
        dynamicSymbolProps.setColor(esriDynamicSymbolType.esriDSymbolText, 1.0f, 0.0f, 0.0f, 1.0f); // Yellow

        //draw the item as a compound marker. This mean that you do not have to draw the items and its
        //accompanying labels separately, and thus allow you to write less code as well as get better
        //performance.
        dynamicCompoundMarker.drawCompoundMarker6
        (point,
            "TOP",
            "BOTTOM",
            "Item " + i,
            String.valueOf(heading),
            String.valueOf(X),   String.valueOf(Y));
        break;
      case 1:
        //set the heading of the current symbol
        dynamicSymbolProps.setHeading(esriDynamicSymbolType.esriDSymbolMarker, (float)heading);

        //set the symbol alignment so that it will align towards the symbol heading
        dynamicSymbolProps.setRotationAlignment(esriDynamicSymbolType.esriDSymbolMarker, esriDynamicSymbolRotationAlignment.esriDSRANorth);

        dynamicSymbolProps.setScale(esriDynamicSymbolType.esriDSymbolMarker, 1.0f,1.0F);
        dynamicSymbolProps.setColor(esriDynamicSymbolType.esriDSymbolMarker, 0.0f, 1.0f, 0.6f, 1.0f); // GREEN
        dynamicSymbolProps.setDynamicGlyphByRef(esriDynamicSymbolType.esriDSymbolMarker, markerGlyphs[1]);

        //draw the current location
        dynamicDisplay.drawMarker(point);
        break;
      case 2:
        //set the heading of the current symbol
        dynamicSymbolProps.setHeading(esriDynamicSymbolType.esriDSymbolMarker, (float)heading + 180.0f);

        //set the symbol alignment so that it will align with towards the symbol heading
        dynamicSymbolProps.setRotationAlignment(esriDynamicSymbolType.esriDSymbolMarker, esriDynamicSymbolRotationAlignment.esriDSRANorth);

        dynamicSymbolProps.setScale(esriDynamicSymbolType.esriDSymbolMarker, 1.5f, 1.5F);
        dynamicSymbolProps.setColor(esriDynamicSymbolType.esriDSymbolMarker, 1.0f, 1.0f, 1.0f, 1.0f); // WHITE
        dynamicSymbolProps.setDynamicGlyphByRef(esriDynamicSymbolType.esriDSymbolMarker, markerGlyphs[2]);

        //draw the current location
        dynamicDisplay.drawMarker(point);
        break;
      }
    }
  }

  /*
   * This method creates three different dynamic glyphs from graphic resources
   */
  private void createDynamicSymbols(IDynamicGlyphFactory dynamicGlyphFactory) throws IOException, AutomationException
  {

    // Create Character Marker Symbols

    StdFont fontEsri = new StdFont();
    fontEsri.setName("ESRI Environmental & Icons");
    fontEsri.setSize(32);
    CharacterMarkerSymbol characterMarkerSymbol = new CharacterMarkerSymbol();
    RgbColor color = new RgbColor();
    color.setRed(255);
    color.setGreen(255);
    color.setBlue(255);
    characterMarkerSymbol.setColor(color);
    characterMarkerSymbol.setFont(fontEsri);
    characterMarkerSymbol.setSize(40);
    characterMarkerSymbol.setAngle(0);
    characterMarkerSymbol.setCharacterIndex(36);

    //Create glyphs from character marker symbol
    markerGlyphs[0] = dynamicGlyphFactory.createDynamicGlyph(characterMarkerSymbol);
    textglyphs = dynamicGlyphFactory.getDynamicGlyph(1,esriDynamicGlyphType.esriDGlyphText,1);
    characterMarkerSymbol.setSize(32);
    characterMarkerSymbol.setCharacterIndex(224);
    markerGlyphs[1] = dynamicGlyphFactory.createDynamicGlyph(characterMarkerSymbol);

    //Create gylph from bitmap

    // Sets the transparency color
    RgbColor tranparencyColor = new RgbColor();
    tranparencyColor.setRed( 0);
    tranparencyColor.setGreen(0);
    tranparencyColor.setBlue(0);
    tranparencyColor.setGreen(255);
    tranparencyColor.setBlue(255);

    String devKitHome = System.getenv("AGSDEVKITJAVA");
    if(devKitHome == null){
      System.err.println("The Java ArcObjects SDK has not been installed.");
      System.err.println("Please install the ArcObjects SDK for Java, then re-run this sample.");
      System.err.println("Exiting the application...");
      System.exit(-1);
    }
    
    String bitmapPath = devKitHome + "java" + File.separator + "samples" + File.separator
                     + "data" + File.separator + "display" + File.separator
                     + "B2.bmp";
    
    markerGlyphs[2] =  dynamicGlyphFactory.createDynamicGlyphFromFile(esriDynamicGlyphType.esriDGlyphMarker,bitmapPath,tranparencyColor);

  }

  /*
   * This method updates the location of the 100 dynamic items stored in the hash map
   */
  public void onUpdate(){
    try{
      double X, Y, stepX, stepY, heading;

      //iterate through the layers' records
      for (int i=0;i<this.dataMap.size();i++)
      {
        NavigationData navdata = this.dataMap.get(new Integer(i));
        //get the current item location and the item's steps
        X =navdata.x;
        Y = navdata.y;
        stepX = navdata.stepx;
        stepY = navdata.stepy;

        //increment the item's location
        X += stepX;
        Y += stepY;

        //test that the item's location is within the fitted bounds
        if (X > extentMaxX) stepX = -Math.abs(stepX);
        if (X < extentMinX) stepX = Math.abs(stepX);
        if (Y > extentMaxY) stepY = -Math.abs(stepY);
        if (Y < extentMinY) stepY = Math.abs(stepY);

        //calculate the item's heading
        heading = (360.0 + 90.0 - Math.atan2(stepY, stepX) *( 180 / Math.PI)) % 360.0;

        //update the item's record
        navdata.x = X;
        navdata.y = Y;
        navdata.stepx = stepX;
        navdata.stepy= stepY;
        navdata.azimuth= heading;
        this.dataMap.put(new Integer(i), navdata);

      }
    }catch(Exception e){
      e.printStackTrace();
    }
  }
  /*
   * This method starts the timer and fires the timer event at specified intervals
   */
  public void start(){
    timer.start();
  }
  /*
   * This method stops the timer
   */
  public void stop(){
    timer.stop();
  }

  /*
   * @see com.esri.arcgis.carto.IDynamicLayer#isDynamicLayerDirty(int)
   * This method is called by the DD framework every draw cycle to check whether the layer is dirty
   * If the layer is dirty the DD framework calls the drawDynamicLayer() method to render the layer
   */
  public boolean isDynamicLayerDirty(int phase) throws IOException, AutomationException {
    if (phase == esriDynamicDrawPhase.esriDDPImmediate)
      return isDirtyImmediate;

    return isDirtyCompiled;
  }

  /*
   * @see com.esri.arcgis.carto.IDynamicLayer#setDynamicLayerDirty(int, boolean)
   * This method sets the dirty flag for the dynamic layer for the corresponding phases
   */

  public void setDynamicLayerDirty(int phase, boolean dirtyFlag) throws IOException, AutomationException {
    if (phase == esriDynamicDrawPhase.esriDDPImmediate)
      isDirtyImmediate = dirtyFlag;
    else
      isDirtyCompiled=dirtyFlag;
  }
  /*
   * @see com.esri.arcgis.carto.IDynamicLayer#getDynamicRecompileRate()
   * This method is called by the DD framework to determine the recompile rate for the layer
   * Since the layer is always drawn in immediate phase it is set to -1
   */
  public int getDynamicRecompileRate() throws IOException, AutomationException {
    return -1;
  }

  public int getSupportedDrawPhases() throws IOException, AutomationException {
    return esriDrawPhase.esriDPAnnotation;
  }
  /*
   * @see com.esri.arcgis.carto.ILayer#draw(int, com.esri.arcgis.display.IDisplay, com.esri.arcgis.system.ITrackCancel)
   * This method is not implemented and hence will not draw in non-dynamic mode
   */
  public void draw(int arg0, IDisplay arg1, ITrackCancel arg2) throws IOException, AutomationException{

  }

  public String getName() throws IOException, AutomationException {
    return name;
  }

  public void setName(String name) throws IOException, AutomationException {
    this.name = name;
  }

  public boolean isValid() throws IOException, AutomationException {
    return true;
  }

  public double getMinimumScale() throws IOException, AutomationException {
    return minimumScale;
  }

  public void setMinimumScale(double scale) throws IOException, AutomationException {
    minimumScale = scale;    
  }

  public double getMaximumScale() throws IOException, AutomationException {
    return maximumScale;
  }

  public void setMaximumScale(double scale) throws IOException, AutomationException {
    maximumScale = scale;
  }

  public boolean isVisible() throws IOException, AutomationException {
    return isVisible;
  }

  public void setVisible(boolean visible) throws IOException, AutomationException {
    isVisible = visible;
  }

  public boolean isShowTips() throws IOException, AutomationException {
    return showTips;
  }

  public void setShowTips(boolean showTips) throws IOException, AutomationException {
    this.showTips = showTips;
  }

  public boolean isCached() throws IOException, AutomationException {

    return isCached;
  }

  public void setCached(boolean cache) throws IOException, AutomationException {
    isCached = cache;  
  }

  public void setSpatialReferenceByRef(ISpatialReference spatialRef) throws IOException, AutomationException {
    this.spatialRef = spatialRef;  
  }

  public ISpatialReference getSpatialReference() throws IOException, AutomationException {
    return spatialRef;
  }

  public Envelope getExtent() throws IOException, AutomationException {
    return (Envelope)extent;
  }

  public IUID getID() throws IOException, AutomationException {
    return null;
  }

  public Envelope getAreaOfInterest() throws IOException, AutomationException {
    return (Envelope)extent;
  }


  public String getTipText(double arg0, double arg1, double arg2) throws IOException, AutomationException {
    return null;
  }


}