MZ renderer
arcgissamples\cartography\MZRenderer.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.cartography;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.text.NumberFormat;

import com.esri.arcgis.carto.IFeatureIDSet;
import com.esri.arcgis.carto.IFeatureRenderer;
import com.esri.arcgis.carto.ILegendGroup;
import com.esri.arcgis.carto.ILegendInfo;
import com.esri.arcgis.carto.ILegendItem;
import com.esri.arcgis.carto.LegendClass;
import com.esri.arcgis.carto.LegendGroup;
import com.esri.arcgis.carto.SimpleRenderer;
import com.esri.arcgis.display.IDisplay;
import com.esri.arcgis.display.ISimpleMarkerSymbol;
import com.esri.arcgis.display.ISimpleTextSymbol;
import com.esri.arcgis.display.ISymbol;
import com.esri.arcgis.display.RgbColor;
import com.esri.arcgis.display.SimpleLineSymbol;
import com.esri.arcgis.display.SimpleMarkerSymbol;
import com.esri.arcgis.display.TextSymbol;
import com.esri.arcgis.display.esriSimpleLineStyle;
import com.esri.arcgis.display.esriSimpleMarkerStyle;
import com.esri.arcgis.display.esriTextHorizontalAlignment;
import com.esri.arcgis.display.esriTextVerticalAlignment;
import com.esri.arcgis.geodatabase.IFeature;
import com.esri.arcgis.geodatabase.IFeatureClass;
import com.esri.arcgis.geodatabase.IFeatureCursor;
import com.esri.arcgis.geodatabase.IFeatureDraw;
import com.esri.arcgis.geodatabase.IQueryFilter;
import com.esri.arcgis.geodatabase.esriDrawStyle;
import com.esri.arcgis.geometry.IPoint;
import com.esri.arcgis.geometry.IPointCollection;
import com.esri.arcgis.geometry.esriGeometryType;
import com.esri.arcgis.interop.AutomationException;
import com.esri.arcgis.interop.extn.ArcGISExtension;
import com.esri.arcgis.system.IDocumentVersionSupportGEN;
import com.esri.arcgis.system.ITrackCancel;
import com.esri.arcgis.system.esriArcGISVersion;
import com.esri.arcgis.system.esriDrawPhase;

@ArcGISExtension
public class MZRenderer implements IFeatureRenderer, ILegendInfo, Externalizable, IDocumentVersionSupportGEN{
  /**
   * MODETYPE_MEASURES constant
   */
  public static final int MODETYPE_MEASURES = 0;

  /**
   * MODETYPE_ZVALUES constant
   */
  public static final int MODETYPE_ZVALUES = 1;

  /**
   * ORIENTATIONTYPE_ALONG constant
   */
  public static final int ORIENTATIONTYPE_ALONG = 0;

  /**
   * ORIENTATIONTYPE_HORIZONTAL constant
   */
  public static final int ORIENTATIONTYPE_HORIZONTAL = 1;

  /**
   * ORIENTATIONTYPE_PERPENDICULAR constant
   */
  public static final int ORIENTATIONTYPE_PERPENDICULAR = 2;
  private ISimpleTextSymbol textSymbol;
  private ILegendGroup legendGroup;
  private int renderMode;
  private int labelInterval;
  private int markerOrientation;
  private int labelOrientation;

  public MZRenderer() {
    try {
      initialize();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public void initialize() throws Exception {

    textSymbol = new TextSymbol();
    // create data structure to store symbols in legendGroup
    legendGroup = new LegendGroup();
    legendGroup.setHeading("MZ Line Renderer");
    // set renderer defaults
    renderMode = MODETYPE_MEASURES;
    labelInterval = 5;
    markerOrientation = ORIENTATIONTYPE_HORIZONTAL;
    labelOrientation = ORIENTATIONTYPE_HORIZONTAL;
    
    // build 3 legend classes, two for markers and one for lines.
    // for each, create and assign symbol, and add to pLegendGroup.
    // (0) marker symbol for vertices
    LegendClass legendClass0 = new LegendClass();
    RgbColor colorRed = new RgbColor();
    colorRed.setRGB(0xFF); // red
    SimpleMarkerSymbol simpleMSym = new SimpleMarkerSymbol();
    simpleMSym.setColor(colorRed);
    simpleMSym.setStyle(esriSimpleMarkerStyle.esriSMSSquare);
    simpleMSym.setSize(6.0);
    legendClass0.setSymbolByRef(simpleMSym);
    legendClass0.setLabel("M value at vertex");
    legendGroup.addClass(legendClass0);

    // (1) marker symbol for end vertices (endpoints)
    LegendClass legendClass1 = new LegendClass();
    RgbColor colorGreen = new RgbColor();
    colorGreen.setRGB(0xFF00); // green
    SimpleMarkerSymbol simpleMSym1 = new SimpleMarkerSymbol();
    simpleMSym1.setColor(colorGreen);
    simpleMSym1.setStyle(esriSimpleMarkerStyle.esriSMSSquare);
    simpleMSym1.setSize(6.0);
    legendClass1.setSymbolByRef(simpleMSym1);
    legendClass1.setLabel("M value at endpoint");
    legendGroup.addClass(legendClass1);
    
    // (2) line symbol
    LegendClass legendClass2 = new LegendClass();
    RgbColor colorBlue = new RgbColor();
    colorBlue.setRGB(0xFF0000); // blue
    SimpleLineSymbol simpleLSym = new SimpleLineSymbol();
    simpleLSym.setColor(colorBlue);
    simpleLSym.setStyle(esriSimpleLineStyle.esriSLSSolid);
    simpleLSym.setWidth(0.5);
    legendClass2.setSymbolByRef(simpleLSym);
    legendClass2.setLabel("Line");
    legendGroup.addClass(legendClass2);
    legendGroup.setVisible(true);
  }

  public void terminate() {
    legendGroup = null;
  }

  public boolean canRender(IFeatureClass featClass, IDisplay display) throws IOException, AutomationException {
    return (featClass.getShapeType() == esriGeometryType.esriGeometryPolyline);
  }

  public void draw(IFeatureCursor featureCursor, int drawPhase,IDisplay display, ITrackCancel trackCancel) throws IOException,AutomationException {
    // do not draw features if no display
    if (display == null)
      return;

    if (drawPhase == esriDrawPhase.esriDPGeography) {
      ISymbol blueSymbol = legendGroup.esri_getClass(2).getSymbol(); // for line
      ISymbol iRedSymbol = legendGroup.esri_getClass(0).getSymbol();
      ISimpleMarkerSymbol redSymbol = (ISimpleMarkerSymbol) (iRedSymbol);
      ISymbol iGreenSymbol = legendGroup.esri_getClass(1).getSymbol();
      ISimpleMarkerSymbol greenSymbol = (ISimpleMarkerSymbol) (iGreenSymbol);

      double dblVOffset = (redSymbol.getSize() / 2) + 2; // offset for vertices
      double dblEOffset = (greenSymbol.getSize() / 2) + 2; // offset for endpoints

      NumberFormat numberFormat = NumberFormat.getNumberInstance();
      numberFormat.setMaximumFractionDigits(2);

      // while there are still more polyline features and drawing has not been canceled
      for (IFeature feat = featureCursor.nextFeature(); feat != null; feat = featureCursor.nextFeature()) {
        // draw the line feature
        IFeatureDraw featDraw = (IFeatureDraw) feat;
        display.setSymbol(blueSymbol);
        featDraw.draw(drawPhase, display, blueSymbol, true, null,esriDrawStyle.esriDSNormal);

        // using IPointCollection, loop through and draw the vertices
        // for the feature
        IPointCollection pointCollect = (IPointCollection) feat.getShape();

        int nPoints = pointCollect.getPointCount();
        for (int i = 0; i < nPoints; i++) {
          IPoint point = pointCollect.getPoint(i);
          IPoint nextPoint = null;
          if (i < nPoints - 1)
            nextPoint = pointCollect.getPoint(i + 1); // for calculation of angles get next vertex
          else
            nextPoint = pointCollect.getPoint(i - 1); // for calculation of angles get previous vertext

          double angle = arithAngle(point, nextPoint);
          double markAngle = orientAngle(angle, markerOrientation); // angle for marker
          double labAngle = orientAngle(angle, labelOrientation); // angle for label

          redSymbol.setAngle(markAngle);
          greenSymbol.setAngle(markAngle);

          if ((i != 0) && (i != (nPoints - 1))) {// no first and no last vertex
            display.setSymbol((ISymbol) redSymbol);
            display.drawPoint(point);
            if ((i % this.labelInterval) == 0)
              drawMyText(point, display, labAngle, dblVOffset,numberFormat);
          } else { // others
            display.setSymbol((ISymbol) greenSymbol);
            display.drawPoint(point);
            drawMyText(point, display, labAngle, dblEOffset,numberFormat);
          }
        }

        if (trackCancel != null)
          if (!trackCancel.esri_continue())
            break;
      }
    }
  }

  public ISymbol getSymbolByFeature(IFeature arg0) throws IOException, AutomationException {
    ISymbol symbol = legendGroup.esri_getClass(2).getSymbol(); // return the line symbol only
    return symbol;
  }

  public boolean isRenderPhase(int drawPhase) throws IOException,AutomationException {
    return (drawPhase == esriDrawPhase.esriDPGeography);
  }

  public void prepareFilter(IFeatureClass arg0, IQueryFilter arg1)throws IOException, AutomationException {
  }

  public void setExclusionSetByRef(IFeatureIDSet arg0) throws IOException,AutomationException {
  }

  public ILegendGroup getLegendGroup(int arg0) throws IOException,AutomationException {
    return legendGroup;
  }

  public int getLegendGroupCount() throws IOException, AutomationException {
    if (legendGroup == null)
      return 0;
    else
      return 1;
  }

  public ILegendItem getLegendItem() throws IOException, AutomationException {
    return null;
  }

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

  public void setSymbolsAreGraduated(boolean arg0) throws IOException,AutomationException {
  }

  public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
    legendGroup = (ILegendGroup) in.readObject();
    renderMode = Integer.parseInt((String) in.readObject());
    labelInterval = Integer.parseInt((String) in.readObject());
    markerOrientation = Integer.parseInt((String) in.readObject());
    labelOrientation = Integer.parseInt((String) in.readObject());
  }

  public void writeExternal(ObjectOutput out) throws IOException {
    out.writeObject(legendGroup);
    out.writeObject(Integer.toString(renderMode));
    out.writeObject(Integer.toString(labelInterval));
    out.writeObject(Integer.toString(markerOrientation));
    out.writeObject(Integer.toString(labelOrientation));

  }

  // ********** Utility Methods

  /**
   * Method returns the direction of vector from pPoint to pNextPoint in
   * degrees arithmetic coordinates used (East = 0, North = 90, West = 180,
   * South = 270).
   * @param point first point
   * @param nextPoint second point
   */
  private double arithAngle(IPoint point, IPoint nextPoint)
      throws IOException, AutomationException {
    double slope;
    double angleRadians;
    double xDiff = nextPoint.getX() - point.getX();
    double yDiff = nextPoint.getY() - point.getY();
    if (xDiff > 0) { // quadrants I or IV
      slope = yDiff / xDiff;
      angleRadians = Math.atan(slope);
    } else if (xDiff < 0) { // quadrants II or III
      slope = yDiff / xDiff;
      // rotate 180 degrees to get left hand quadrants
      angleRadians = Math.atan(slope) - Math.PI;
    } else { // dblXDiff = 0 ' special case of perfectly vertical line
      if (yDiff >= 0)
        angleRadians = Math.PI / 2;
      else
        angleRadians = -1 * (Math.PI / 2);
    }
    // convert radians to degrees and return
    return angleRadians * 180 / Math.PI;
  }

  /**
   * Method calculates new angle depend on the orientation.
   * 
   * @param angle in degrees
   * @param orientation (ORIENTATIONTYPE_ALONG, ORIENTATIONTYPE_HORIZONTAL or ORIENTATIONTYPE_PERPENDICULAR)
   * @return new angle in degrees
   */
  private double orientAngle(double angle, int orientation) {
    switch (orientation) {
    case ORIENTATIONTYPE_ALONG:
      return angle;
    case ORIENTATIONTYPE_HORIZONTAL:
      return 0;
    case ORIENTATIONTYPE_PERPENDICULAR:
      return angle - 90;
    }
    return 0;
  }

  /**
   * Method draws text label on map.
   */
  private void drawMyText(IPoint point, IDisplay display, double angle,double offset, NumberFormat numberFormat) throws IOException,AutomationException {
    textSymbol.setHorizontalAlignment(esriTextHorizontalAlignment.esriTHALeft); // left side of label closest to point
    textSymbol.setXOffset(offset);// apply offset along x-axis if other
    textSymbol.setVerticalAlignment(esriTextVerticalAlignment.esriTVABottom); // bottom of label closest to point
    textSymbol.setAngle(angle);
    display.setSymbol((ISymbol) this.textSymbol);
    // get value from pPoint, and draw it.
    // Note that most sample data have no M or Z values.
    // We provide fake values if necessary (if Not a Number)
    if (this.renderMode == MODETYPE_MEASURES) {
      if (Double.isNaN(point.getM())) {
        point.setM(point.getX());
      }
      display.drawText(point, numberFormat.format(point.getM()));
    }
    if (this.renderMode == MODETYPE_ZVALUES) {
      if (Double.isNaN(point.getZ())) {
        point.setZ(point.getY());
      }
      display.drawText(point, numberFormat.format(point.getZ()));
    }
  }

  // *********************** Public Methods
  /**
   * The method returns rederer mode.
   * @return renderer mode (MODETYPE_MEASURES or MODETYPE_ZVALUES)
   */
  public int getRenderMode() {
    return renderMode;
  }

  /**
   * Method sets renderer mode.
   * @param inMode (MODETYPE_MEASURES or MODETYPE_ZVALUES)
   */
  public void setRenderMode(int inMode) throws IOException,AutomationException {
    renderMode = inMode;
    // renderMode changes so change labels
    if (renderMode == MODETYPE_MEASURES) { // M values
      legendGroup.esri_getClass(0).setLabel("M value at vertex");
      legendGroup.esri_getClass(1).setLabel("M value at endpoint");
    } else { // Z values
      legendGroup.esri_getClass(0).setLabel("Z value at vertex");
      legendGroup.esri_getClass(1).setLabel("Z value at endpoint");
    }
  }

  /**
   * The method sets label inteval.
   */
  public void setLabelInterval(int intInInterval) {
    labelInterval = intInInterval;
  }

  public int getLabelInterval() {
    return labelOrientation;
  }

  /**
   * The method sets label orientation.
   * @param orientation(ORIENTATIONTYPE_ALONG, ORIENTATIONTYPE_HORIZONTAL or ORIENTATIONTYPE_PERPENDICULAR)
   */
  public void setMarkerOrientation(int orientation) {
    markerOrientation = orientation;
  }

  public int getMarkerOrientation() {
    return markerOrientation;
  }

  /**
   * The method sets label orientation.
   * @param orientation (ORIENTATIONTYPE_ALONG, ORIENTATIONTYPE_HORIZONTAL or ORIENTATIONTYPE_PERPENDICULAR)
   */
  
  public void setLabelOrientation(int orientation) {
    labelOrientation = orientation;
  }

  public int getLabelOrientation() {
    return labelOrientation;
  }

  public Object convertToSupportedObject(int arg0) throws IOException,AutomationException {
    SimpleRenderer simpleRend = new SimpleRenderer();
    SimpleLineSymbol simpleLineSymbol = new SimpleLineSymbol();
    simpleLineSymbol.setStyle(esriSimpleLineStyle.esriSLSDash);
    simpleRend.setSymbolByRef(simpleLineSymbol);
    return simpleRend;
  }

  public boolean isSupportedAtVersion(int arg0) throws IOException,AutomationException {
    // Support all versions above or equal 9.3.1.
    if (arg0 >= esriArcGISVersion.esriArcGISVersion93)
      return true;
    else
      return false;
  }

}