/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.units;

import javax.annotation.concurrent.Immutable;

/**
 * Provides support for the dimension of a quantity.  For example, the
 * dimension of the quantity "force" is "M.L.t-2".
 *
 * @author Steven R. Emmerson
 * Immutable.
 */
@Immutable
public final class
QuantityDimension
    extends	Dimension
{
    /**
     * Constructs from nothing.  Constructs a dimensionless dimension.
     */
    public
    QuantityDimension()
    {
	super();
    }

    /**
     * Constructs from a base quantity.
     * @param baseQuantity	The base quantity constituting the dimension.
     */
    public
    QuantityDimension(BaseQuantity baseQuantity)
    {
	super(new Factor(baseQuantity));
    }

    /**
     * Constructs from an array of Factor-s.  This is a trusted constructor
     * for use by subclasses only.
     * @param factors		The Factor-s constituting the dimension.
     */
    protected
    QuantityDimension(Factor[] factors)
    {
	super(factors);
    }

    /**
     * Multiplies this quantity dimension by another quantity dimension.
     * @param that		The other quantity dimension.
     * @return			The product of this quantity dimension with
     *				the other quantity dimension.
     */
    public QuantityDimension
    multiplyBy(QuantityDimension that)
    {
	return new QuantityDimension(mult(that));
    }

    /**
     * Divides this quantity dimension by another quantity dimension.
     * @param that		The quantity dimension to divide this
     *				quantity dimension by.
     * @return			The quotient of this quantity dimension and
     *				the other quantity dimension.
     */
    public QuantityDimension
    divideBy(QuantityDimension that)
    {
	return multiplyBy(that.raiseTo(-1));
    }

    /**
     * Raises this quantity dimension to a power.
     * @param power		The power to raise this quantity dimension by.
     * @return			The result of raising this quantity dimension
     *				to the power power.
     */
    public QuantityDimension
    raiseTo(int power)
    {
	return new QuantityDimension(pow(power));
    }

    /**
     * Tests this class.
     */
    public static void
    main(String[] args)
	throws	Exception
    {
	System.out.println("new QuantityDimension() = \"" +
	    new QuantityDimension() + '"');
	QuantityDimension	timeDimension =
	    new QuantityDimension(BaseQuantity.TIME);
	System.out.println("timeDimension = \"" + timeDimension + '"');
	QuantityDimension	lengthDimension =
	    new QuantityDimension(BaseQuantity.LENGTH);
	System.out.println("lengthDimension = \"" + lengthDimension + '"');
	System.out.println(
	    "lengthDimension.isReciprocalOf(timeDimension) = \"" +
	    lengthDimension.isReciprocalOf(timeDimension) + '"');
	QuantityDimension	hertzDimension = timeDimension.raiseTo(-1);
	System.out.println("hertzDimension = \"" + hertzDimension + '"');
	System.out.println(
	    "hertzDimension.isReciprocalOf(timeDimension) = \"" +
	    hertzDimension.isReciprocalOf(timeDimension) + '"');
	System.out.println("lengthDimension.divideBy(timeDimension) = \"" +
	    lengthDimension.divideBy(timeDimension) + '"');
	System.out.println(
	    "lengthDimension.divideBy(timeDimension).raiseTo(2) = \"" +
	    lengthDimension.divideBy(timeDimension).raiseTo(2) + '"');
    }
}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.unidata.geoloc.vertical;

import ucar.ma2.*;
import ucar.ma2.ArrayDouble.D1;
import ucar.nc2.*;
import ucar.nc2.constants.CDM;
import ucar.unidata.util.Parameter;

import java.io.IOException;
import java.util.List;

/**
 * Create a 3D height(z,y,x) array using the netCDF CF convention formula for
 * "Atmospheric Hybrid Height".
 *
height(x,y,z) = a(z) + b(z)*orog(x,y)
 *
 * @author murray
 * @see http://cf-pcmdi.llnl.gov/
 */
public class HybridHeight extends VerticalTransformImpl {

  /**
   * Surface pressure name identifier
   */
  public static final String OROG = "Orography_variableName";

  /**
   * The "a" variable name identifier
   */
  public static final String A = "A_variableName";

  /**
   * The "b" variable name identifier
   */
  public static final String B = "B_variableName";

  /**
   * ps, a, and b variables
   */
  private Variable aVar, bVar, orogVar;

  /**
   * a and b Arrays
   */
  private Array aArray = null,
      bArray = null;

  /**
   * Construct a coordinate transform for hybrid height
   *
   * @param ds      netCDF dataset
   * @param timeDim time dimension
   * @param params  list of transformation Parameters
   */
  public HybridHeight(NetcdfFile ds, Dimension timeDim, List<Parameter> params) {

    super(timeDim);
    String aName = getParameterStringValue(params, A);
    String bName = getParameterStringValue(params, B);
    String orogName = getParameterStringValue(params, OROG);

    aVar = ds.findVariable(aName);
    bVar = ds.findVariable(bName);
    orogVar = ds.findVariable(orogName);
    units = ds.findAttValueIgnoreCase(orogVar, CDM.UNITS, "none");

  }

  /**
   * Get the 3D vertical coordinate array for this time step.
   *
   * @param timeIndex the time index. Ignored if !isTimeDependent().
   * @return vertical coordinate array
   * @throws IOException           problem reading data
   * @throws InvalidRangeException not a valid time range
   */
  public ArrayDouble.D3 getCoordinateArray(int timeIndex)
      throws IOException, InvalidRangeException {
    Array orogArray = readArray(orogVar, timeIndex);

    if (null == aArray) {
      aArray = aVar.read();
      bArray = bVar.read();
    }

    int nz = (int) aArray.getSize();
    Index aIndex = aArray.getIndex();
    Index bIndex = bArray.getIndex();

    int[] shape2D = orogArray.getShape();
    int ny = shape2D[0];
    int nx = shape2D[1];
    Index orogIndex = orogArray.getIndex();

    ArrayDouble.D3 height = new ArrayDouble.D3(nz, ny, nx);

    for (int z = 0; z < nz; z++) {
      double az = aArray.getDouble(aIndex.set(z));
      double bz = bArray.getDouble(bIndex.set(z));

      for (int y = 0; y < ny; y++) {
        for (int x = 0; x < nx; x++) {
          double orog = orogArray.getDouble(orogIndex.set(y, x));
          height.set(z, y, x, az + bz * orog);
        }
      }
    }

    return height;
  }

  /**
   * Get the 1D vertical coordinate array for this time step and point
   *
   * @param timeIndex the time index. Ignored if !isTimeDependent().
   * @param xIndex    the x index
   * @param yIndex    the y index
   * @return vertical coordinate array
   * @throws java.io.IOException problem reading data
   * @throws ucar.ma2.InvalidRangeException _more_
   */
  public D1 getCoordinateArray1D(int timeIndex, int xIndex, int yIndex)
  		throws IOException, InvalidRangeException {

	  Array orogArray = readArray(orogVar, timeIndex);
	  if (null == aArray) {
		  aArray = aVar.read();
		  bArray = bVar.read();
	  }

	  int nz = (int) aArray.getSize();
	  Index aIndex = aArray.getIndex();
	  Index bIndex = bArray.getIndex();

	  Index orogIndex = orogArray.getIndex();
	  ArrayDouble.D1 height = new ArrayDouble.D1(nz);

	  for (int z = 0; z < nz; z++) {
		  double az = aArray.getDouble(aIndex.set(z));
		  double bz = bArray.getDouble(bIndex.set(z));

          double orog = orogArray.getDouble(orogIndex.set(yIndex, xIndex));
          height.set(z,  az + bz * orog);

	  }

	  return height;
  }

}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.nc2.iosp.grid;

import ucar.ma2.*;

import ucar.nc2.*;
import ucar.nc2.constants.AxisType;
import ucar.nc2.constants._Coordinate;
import ucar.nc2.units.SimpleUnit;

import java.util.*;

/**
 * A Vertical Coordinate variable for a Grid variable.
 *
 * @author caron
 */
public class GridVertCoord implements Comparable<GridVertCoord> {

  static private org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(GridVertCoord.class);

  /**
   * typical record for this vertical coordinate
   */
  protected GridRecord typicalRecord;

  protected String levelName;

  protected GridTableLookup lookup;

  protected int seq = 0;

  protected double[] coordValues = null;

  protected boolean usesBounds = false;

  protected boolean isVerticalCoordinate = false;

  /**
     * vertical pressure factors
     */
  protected double[] factors = null;

  /**
   * positive  direction
   */
  private String positive = "up";

  /**
   * units
   */
  protected String units;

  /**
   * levels
   */
  protected List<LevelCoord> levels = new ArrayList<LevelCoord>();  // LevelCoord

  /**
   * Create a new GridVertCoord with the given name.
   * Used by deprecated GridIndex2NC.makeDefinedCoord()
   *
   * @param name name
   */
  protected GridVertCoord(String name) {
    this.levelName = name;
  }

  /**
   * Create a new GridVertCoord with the appropriate params
   *
   * @param records   list of GridRecords that make up this coord
   * @param levelName the name of the level
   * @param lookup    the lookup table
   * @param hcs Horizontal coordinate
   */
  protected GridVertCoord(List<GridRecord> records, String levelName, GridTableLookup lookup, GridHorizCoordSys hcs) {
    this.typicalRecord = records.get(0);
    this.levelName = levelName;
    this.lookup = lookup;
    this.isVerticalCoordinate = lookup.isVerticalCoordinate(typicalRecord);

    //isVerticalCoordinate = lookup.isVerticalCoordinate(typicalRecord);
    positive = lookup.isPositiveUp(typicalRecord) ? "up" : "down";
    units = lookup.getLevelUnit(typicalRecord);

    usesBounds = lookup.isLayer(this.typicalRecord);

    for (GridRecord record : records) {

      if (coordIndex(record) < 0) {
        levels.add(new LevelCoord(record.getLevel1(), record.getLevel2()));

        /* check if assumption violated
       if (!isVerticalCoordinate && (levels.size() > 1)) {
         if (GridServiceProvider.debugVert) {
           logger.warn( "GribCoordSys: unused level coordinate has > 1 levels = "
                   + levelName + " " + record.getLevelType1() + " "
                   + levels.size());
         }
       } */
      }
    }

    Collections.sort(levels);
      if (positive.equals("down")) {
        Collections.reverse(levels);
      }

    if (GridServiceProvider.debugVert) {
      System.out.println("GribVertCoord: " + getVariableName() + "("
          + typicalRecord.getLevelType1()
          + ") isVertDimensionUsed= " + isVertDimensionUsed()
          + " positive=" + positive + " units=" + units);
    }
  }

  /*
   * Create a new GridVertCoord for a layer
   * Used by deprecated GridIndex2NC.makeDefinedCoord()
   *
   * @deprecated
   * @param record    layer record
   * @param levelName name of this level
   * @param lookup    lookup table
   * @param level1    level 1
   * @param level2    level 2
   *
  GridVertCoord(GridRecord record, String levelName, GridTableLookup lookup, double[] level1, double[] level2) {
    this.typicalRecord = record;
    this.levelName = levelName;
    this.lookup = lookup;

    //dontUseVertical    = !lookup.isVerticalCoordinate(record);
    positive = lookup.isPositiveUp(record) ? "up" : "down";
    units = lookup.getLevelUnit(record);
    usesBounds = lookup.isLayer(this.typicalRecord);

    levels = new ArrayList<LevelCoord>(level1.length);
    for (int i = 0; i < level1.length; i++) {
      levels.add(new LevelCoord(level1[i], (level2 == null) ? 0.0  : level2[i]));
    }

    Collections.sort(levels);
    if (positive.equals("down")) {
      Collections.reverse(levels);
    }
    //isVerticalCoordinate = (levels.size() > 1);
  } */

  /**
   * Set the sequence number
   *
   * @param seq the sequence number
   */
  void setSequence(int seq) {
    this.seq = seq;
  }

  /**
   * Set the level name
   *
   * @return the level name
   */
  String getLevelName() {
    return levelName;
  }

  /**
   * Get the variable name
   *
   * @return the variable name
   */
  public String getVariableName() {
    return (seq == 0) ? levelName : levelName + seq;  // more than one with same levelName
  }

  /**
   * Get the number of levels
   *
   * @return number of levels
   */
  int getNLevels() {
    return levels.size();
  }

  /**
   * vert coordinates are used when nlevels > 1, otherwise use isVerticalCoordinate
   * @return if vert dimension should be used
   */
  boolean isVertDimensionUsed() {
    return (getNLevels() == 1) ? isVerticalCoordinate : true;
  }

  /**
   * Match levels
   *
   * @param records records to match
   * @return true if they have the same levels
   */
  boolean matchLevels(List<GridRecord> records) {

    // first create a new list
    List<LevelCoord> levelList = new ArrayList<LevelCoord>(records.size());
    for (GridRecord record : records) {
      LevelCoord lc = new LevelCoord(record.getLevel1(), record.getLevel2());
      if (!levelList.contains(lc)) {
        levelList.add(lc);
      }
    }

    Collections.sort(levelList);
    if (positive.equals("down")) {
      Collections.reverse(levelList);
    }

    // gotta equal existing list
    return levelList.equals(levels);
  }

  /**
   * Add this coord as a dimension to the netCDF file
   *
   * @param ncfile file to add to
   * @param g      group in the file
   */
  void addDimensionsToNetcdfFile(NetcdfFile ncfile, Group g) {
    if (!isVertDimensionUsed())
      return;

    int nlevs = levels.size();
    if ( coordValues != null )
      nlevs = coordValues.length;
    ncfile.addDimension(g, new Dimension(getVariableName(), nlevs, true));
  }

  protected String getLevelDesc() {
    return lookup.getLevelDescription(typicalRecord);
  }

  protected void addExtraAttributes(Variable v) {
    v.addAttribute(new Attribute("level_type", Integer.toString(typicalRecord.getLevelType1())));
  }

  /**
   * Add this coord as a variable in the netCDF file
   *
   * @param ncfile netCDF file to add to
   * @param g      group in file
   */
  void addToNetcdfFile(NetcdfFile ncfile, Group g) {
    if (!isVertDimensionUsed()) {
      typicalRecord = null; // allow gc
      return;
    }

    if (g == null) {
      g = ncfile.getRootGroup();
    }

    // coordinate axis
    Variable v = new Variable(ncfile, g, null, getVariableName());
    v.setDataType(DataType.DOUBLE);

    String desc =  getLevelDesc();
    v.addAttribute(new Attribute("long_name", desc));
    v.addAttribute(new Attribute("units", lookup.getLevelUnit(typicalRecord)));

    // positive attribute needed for CF-1 Height and Pressure
    if (positive != null) {
      v.addAttribute(new Attribute("positive", positive));
    }

    if (units != null) {
      AxisType axisType;
      if (SimpleUnit.isCompatible("millibar", units)) {
        axisType = AxisType.Pressure;
      } else if (SimpleUnit.isCompatible("m", units)) {
        axisType = AxisType.Height;
      } else {
        axisType = AxisType.GeoZ;
      }

      addExtraAttributes(v);
      v.addAttribute(new Attribute(_Coordinate.AxisType, axisType.toString()));
    }

    if (coordValues == null) {
      coordValues = new double[levels.size()];
      for (int i = 0; i < levels.size(); i++) {
        LevelCoord lc = (LevelCoord) levels.get(i);
        coordValues[i] = lc.mid;
      }
    }
    Array dataArray = Array.factory(DataType.DOUBLE, new int[]{coordValues.length}, coordValues);

    v.setDimensions(getVariableName());
    v.setCachedData(dataArray, true);

    ncfile.addVariable(g, v);

    if (usesBounds) {
      Dimension bd = ucar.nc2.dataset.DatasetConstructor.getBoundsDimension(ncfile);

      String bname = getVariableName() + "_bounds";
      v.addAttribute(new Attribute("bounds", bname));
      v.addAttribute(new Attribute(_Coordinate.ZisLayer, "true"));

      Variable b = new Variable(ncfile, g, null, bname);
      b.setDataType(DataType.DOUBLE);
      b.setDimensions(getVariableName() + " " + bd.getShortName());
      b.addAttribute(new Attribute("long_name",
          "bounds for " + v.getFullName()));
      b.addAttribute(new Attribute("units",
          lookup.getLevelUnit(typicalRecord)));

      Array boundsArray = Array.factory(DataType.DOUBLE,
          new int[]{coordValues.length,
              2});
      ucar.ma2.Index ima = boundsArray.getIndex();
      for (int i = 0; i < coordValues.length; i++) {
        LevelCoord lc = (LevelCoord) levels.get(i);
        boundsArray.setDouble(ima.set(i, 0), lc.value1);
        boundsArray.setDouble(ima.set(i, 1), lc.value2);
      }
      b.setCachedData(boundsArray, true);

      ncfile.addVariable(g, b);
    }

    if (factors != null) {
      // check if already created
      if (g == null) {
        g = ncfile.getRootGroup();
      }
      if ( g.findVariable ( "hybrida" ) != null)
        return ;
      v.addAttribute(new Attribute("standard_name", "atmosphere_hybrid_sigma_pressure_coordinate" ));
      v.addAttribute(new Attribute("formula_terms", "ap: hybrida b: hybridb ps: Pressure" ));
      // create  hybrid factor variables
      // add hybrida variable
      Variable ha = new Variable(ncfile, g, null, "hybrida");
      ha.setDataType(DataType.DOUBLE);
      ha.addAttribute(new Attribute("long_name",  "level_a_factor" ));
      ha.addAttribute(new Attribute("units", ""));
      ha.setDimensions(getVariableName());
      // add data
      int middle = factors.length / 2;
      double[] adata;
      double[] bdata;
      if( levels.size() < middle ) { // only partial data wanted
        adata = new double[ levels.size() ];
        bdata = new double[ levels.size() ];
      } else {
        adata = new double[ middle ];
        bdata = new double[ middle ];
      }
      for( int i = 0; i < middle && i < levels.size(); i++ )
        adata[ i ] = factors[ i ];
      Array haArray = Array.factory(DataType.DOUBLE, new int[]{adata.length},adata);
      ha.setCachedData(haArray, true);
      ncfile.addVariable(g, ha);

      // add hybridb variable
      Variable hb = new Variable(ncfile, g, null, "hybridb");
      hb.setDataType(DataType.DOUBLE);
      hb.addAttribute(new Attribute("long_name",  "level_b_factor" ));
      hb.addAttribute(new Attribute("units", ""));
      hb.setDimensions(getVariableName());
      // add data
      for( int i = 0; i < middle && i < levels.size(); i++ )
        bdata[ i ] = factors[ i + middle ];
      Array hbArray = Array.factory(DataType.DOUBLE, new int[]{bdata.length},bdata);
      hb.setCachedData(hbArray, true);
      ncfile.addVariable(g, hb);

      /*  // TODO: delete next time modifying code
      double[] adata = new double[ middle ];
      for( int i = 0; i < middle; i++ )
        adata[ i ] = factors[ i ];
      Array haArray = Array.factory(DataType.DOUBLE, new int[]{adata.length}, adata);
      ha.setCachedData(haArray, true);
      ncfile.addVariable(g, ha);

      // add hybridb variable
      Variable hb = new Variable(ncfile, g, null, "hybridb");
      hb.setDataType(DataType.DOUBLE);
      hb.addAttribute(new Attribute("long_name",  "level_b_factor" ));
      //hb.addAttribute(new Attribute("standard_name", "atmosphere_hybrid_sigma_pressure_coordinate" ));
      hb.addAttribute(new Attribute("units", ""));
      hb.setDimensions(getVariableName());
      // add data
      double[] bdata = new double[ middle ];
      for( int i = 0; i < middle; i++ )
        bdata[ i ] = factors[ i + middle ];
      Array hbArray = Array.factory(DataType.DOUBLE, new int[]{bdata.length}, bdata);
      hb.setCachedData(hbArray, true);
      ncfile.addVariable(g, hb);
      */
    }

    // allow gc
    // typicalRecord = null;
  }

  /**
   * Get the index of the particular record
   *
   * @param record record in question
   * @return the index or -1 if not found
   */
  int getIndex(GridRecord record) {
    if (!isVertDimensionUsed())
      return 0;

    return coordIndex(record);
  }

  /**
   * Compare this to another
   *
   * @param gv the other GridVertCoord
   * @return the comparison
   */
  public int compareTo(GridVertCoord gv) {
    return getLevelName().compareToIgnoreCase(gv.getLevelName());
  }

  public double getCoord(int i) {
    return (coordValues == null) ? 0.0 : coordValues[i];
  }

  /**
   * A level coordinate
   */
  protected class LevelCoord implements Comparable {

    /**
     * midpoint
     */
    public double mid;

    /**
     * top/bottom values
     */
    public double value1, value2;

    /**
     * Create a new LevelCoord
     *
     * @param value1 top
     * @param value2 bottom
     */
    LevelCoord(double value1, double value2) {
      this.value1 = value1;
      this.value2 = value2;
      if (usesBounds && (value1 > value2)) {
        this.value1 = value2;
        this.value2 = value1;
      }
      mid = usesBounds ? (value1 + value2) / 2 : value1;
    }

    /**
     * Compare to another LevelCoord
     *
     * @param o another LevelCoord
     * @return the comparison
     */
    public int compareTo(Object o) {
      LevelCoord other = (LevelCoord) o;
      // if (nearlyEquals(value1, other.value1) && nearlyEquals(value2, other.value2)) return 0;
      if (mid < other.mid) {
        return -1;
      }
      if (mid > other.mid) {
        return 1;
      }
      return 0;
    }

    /**
     * Check for equality
     *
     * @param oo object in question
     * @return true if equal
     */
    public boolean equals(Object oo) {
      if (this == oo) {
        return true;
      }
      if (!(oo instanceof LevelCoord)) {
        return false;
      }
      LevelCoord other = (LevelCoord) oo;
      return (ucar.nc2.util.Misc.nearlyEquals(value1, other.value1)
          && ucar.nc2.util.Misc.nearlyEquals(value2, other.value2));
    }

    /**
     * Generate a hashcode
     *
     * @return the hashcode
     */
    public int hashCode() {
      return (int) (value1 * 100000 + value2 * 100);
    }
  }

  /**
   * Get the coordinate index for the record
   *
   * @param record record in question
   * @return index or -1 if not found
   */
  private int coordIndex(GridRecord record) {
    double val = record.getLevel1();
    double val2 = record.getLevel2();
    if (usesBounds && (val > val2)) {
      val = record.getLevel2();
      val2 = record.getLevel1();
    }

    for (int i = 0; i < levels.size(); i++) {
      LevelCoord lc = (LevelCoord) levels.get(i);
      if (usesBounds) {
        if (ucar.nc2.util.Misc.nearlyEquals(lc.value1, val) && ucar.nc2.util.Misc.nearlyEquals(lc.value2, val2)) {
          return i;
        }
      } else {
        if (ucar.nc2.util.Misc.nearlyEquals(lc.value1, val)) {
          return i;
        }
      }
    }
    return -1;
  }

  @Override
  public String toString() {
    return "GridVertCoord{" +
            "levelName='" + levelName + '\'' +
            ", seq=" + seq +
            '}';
  }
}


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.nc2.iosp.nowrad;

//~--- non-JDK imports --------------------------------------------------------

import ucar.ma2.*;
import ucar.nc2.Variable;
import ucar.nc2.iosp.AbstractIOServiceProvider;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;

/**
 * Created by IntelliJ IDEA.
 * User: yuanho
 * Date: Feb 10, 2010
 * Time: 11:22:03 AM
 * To change this template use File | Settings | File Templates.
 */
public class NOWRadiosp extends AbstractIOServiceProvider {
  final static int DEF_WBITS = 15;
  final static int Z_DEFLATED = 8;

  // used for writing
  protected int fileUsed = 0;    // how much of the file is written to ?
  protected int recStart = 0;    // where the record data starts
  protected boolean debug = false,
          debugSize = false,
          debugSPIO = false;
  protected boolean showHeaderBytes = false;
  protected HashMap dimHash = new HashMap(50);
  protected boolean fill;

  // private Nidsheader.Vinfo myInfo;
  protected NOWRadheader headerParser;
  private int pcode;
  protected boolean readonly;

  /**
   * checking the file
   *
   * @param raf
   * @return the valid of file checking
   */
  public boolean isValidFile(ucar.unidata.io.RandomAccessFile raf) {
    NOWRadheader localHeader = new NOWRadheader();

    return (localHeader.isValidFile(raf));
  }

  public String getFileTypeId() {
    return "NOWRAD";
  }

  public String getFileTypeDescription() {
    return "NOWRAD Products";
  }

  /**
   * Open the file and read the header part
   *
   * @param raf
   * @param file
   * @param cancelTask
   * @throws java.io.IOException
   */
  public void open(ucar.unidata.io.RandomAccessFile raf, ucar.nc2.NetcdfFile file,
                   ucar.nc2.util.CancelTask cancelTask)
          throws IOException {
    super.open(raf, ncfile, cancelTask);
    headerParser = new NOWRadheader();

    try {
      headerParser.read(this.raf, ncfile);
    } catch (Exception e) {
    }

    // myInfo = headerParser.getVarInfo();
    pcode = 0;
    ncfile.finish();
  }

  /**
   * Read the data for each variable passed in
   *
   * @param v2
   * @param section
   * @return output data
   * @throws IOException
   * @throws ucar.ma2.InvalidRangeException
   */
  public Array readData(Variable v2, Section section) throws IOException, InvalidRangeException {

    // subset
    Object data;
    Array outputData;
    byte[] vdata = null;
    NOWRadheader.Vinfo vinfo;
    ByteBuffer bos;
    List<Range> ranges = section.getRanges();

    vinfo = (NOWRadheader.Vinfo) v2.getSPobject();
    vdata = headerParser.getData((int) vinfo.hoff);

    bos = ByteBuffer.wrap(vdata);
    data = readOneScanData(bos, vinfo, v2.getShortName());
    outputData = Array.factory(v2.getDataType(), v2.getShape(), data);
    outputData = outputData.flip(1);

    // outputData = outputData.flip(2);
    return (outputData.sectionNoReduce(ranges).copy());

    // return outputData;
  }

  /**
   * Read one scan radar data
   *
   * @param bos   Data buffer
   * @param vinfo variable info
   * @return the data object of scan data
   */

  // all the work is here, so can be called recursively
  public Object readOneScanData(ByteBuffer bos, NOWRadheader.Vinfo vinfo, String vName)
          throws IOException, InvalidRangeException {
    int doff = (int) vinfo.hoff;
    int npixel = vinfo.yt * vinfo.xt;
    byte[] rdata = null;
    byte[] ldata = new byte[vinfo.xt];
    byte[] pdata = new byte[npixel];
    byte[] b2 = new byte[2];

    bos.position(doff);

    // begining of image data
    if ((DataType.unsignedByteToShort(bos.get()) != 0xF0) || (bos.get() != 0x0C)) {
      return null;
    }

    int ecode;
    int color;
    int datapos;
    int offset = 0;
    int roffset = 0;
    boolean newline = true;
    int linenum = 0;

    while (true) {

      // line number
      if (newline) {
        bos.get(b2);
        linenum = (DataType.unsignedByteToShort(b2[1]) << 8) + DataType.unsignedByteToShort(b2[0]);

        // System.out.println("Line Number = " + linenum);
      }

      // int linenum = bytesToInt(b2[0], b2[1], true);
      // System.out.println("Line Number = " + linenum);
      // if(linenum == 1225)
      //   System.out.println(" HHHHH");
      short b = DataType.unsignedByteToShort(bos.get());

      color = b & 0xF;
      ecode = b >> 4;
      datapos = bos.position();

      int datarun;

      if (ecode == 0xF) {
        byte bb1 = bos.get(datapos - 2);
        byte bb2 = bos.get(datapos);

        if ((color == 0x0) && (bb1 == 0x00) && (bb2 == 0x00)) {
          datapos += 1;
        }

        bos.position(datapos);
        datarun = 0;
      } else if (ecode == 0xE) {
        byte b0 = bos.get(datapos);

        datarun = DataType.unsignedByteToShort(b0) + 1;
        datapos += 1;
        bos.position(datapos);
      } else if (ecode == 0xD) {
        b2[0] = bos.get(datapos);
        b2[1] = bos.get(datapos + 1);
        datarun = (DataType.unsignedByteToShort(b2[1]) << 8) + DataType.unsignedByteToShort(b2[0]) + 1;
        datapos += 2;
        bos.position(datapos);
      } else {
        datarun = ecode + 1;
      }

      // move the unpacked data in the data line
      rdata = new byte[datarun];

      for (int i = 0; i < datarun; i++) {
        rdata[i] = (byte) color;
      }

      System.arraycopy(rdata, 0, ldata, roffset, datarun);
      roffset = roffset + datarun;

      // System.out.println("run ecode = " + ecode + " and data run " + datarun + " and totalrun " + roffset);
      // check to see if the beginning of the next line or at the end of the file
      short c0 = DataType.unsignedByteToShort(bos.get());

      if (c0 == 0x00) {
        short c1 = DataType.unsignedByteToShort(bos.get());
        short c2 = DataType.unsignedByteToShort(bos.get());

        // System.out.println("c1 and c2 " + c1 + " " + c2);

        if ((c0 == 0x00) && (c1 == 0xF0) && (c2 == 0x0C)) {
          // beginning of next line
          //  System.out.println("linenum   " + linenum + "   and this line total " + roffset);
          //  if (roffset != 3661) {
          //      System.out.println("ERROR missing data, this line total only " + roffset);
          //  }
          System.arraycopy(ldata, 0, pdata, offset, roffset);
          offset = offset + vinfo.xt;
          roffset = 0;
          newline = true;
          ldata = new byte[vinfo.xt];
        } else if ((c1 == 0xF0) && (c2 == 0x02)) {
          // end of the file
          break;
        } else {
          datapos = bos.position() - 3;
          bos.position(datapos);
          newline = false;
        }
      } else {
        newline = false;
        datapos = bos.position();
        bos.position(datapos - 1);
      }
    }

    return pdata;
  }

  int getUInt(byte[] b, int num) {
    int base = 1;
    int i;
    int word = 0;
    int bv[] = new int[num];

    for (i = 0; i < num; i++) {
      bv[i] = DataType.unsignedByteToShort(b[i]);
    }

    /*
    * Calculate the integer value of the byte sequence
    */
    for (i = num - 1; i >= 0; i--) {
      word += base * bv[i];
      base *= 256;
    }

    return word;
  }

  public static int bytesToInt(short a, short b, boolean swapBytes) {

    // again, high order bit is expressed left into 32-bit form
    if (swapBytes) {
      return (a & 0xff) + ((int) b << 8);
    } else {
      return ((int) a << 8) + (b & 0xff);
    }
  }

  public static int bytesToInt(byte a, byte b, boolean swapBytes) {

    // again, high order bit is expressed left into 32-bit form
    if (swapBytes) {
      return (a & 0xff) + ((int) b << 8);
    } else {
      return ((int) a << 8) + (b & 0xff);
    }
  }

  /**
   * Read data from encoded values and run len into regular data array
   *
   * @param ddata is encoded data values
   * @return the data array of row data
   */
  public byte[] readOneRowData(byte[] ddata, int rLen, int xt) throws IOException, InvalidRangeException {
    int run;
    byte[] bdata = new byte[xt];
    int nbin = 0;
    int total = 0;

    for (run = 0; run < rLen; run++) {
      int drun = DataType.unsignedByteToShort(ddata[run]) >> 4;
      byte dcode1 = (byte) (DataType.unsignedByteToShort(ddata[run]) & 0Xf);

      for (int i = 0; i < drun; i++) {
        bdata[nbin++] = dcode1;
        total++;
      }
    }

    if (total < xt) {
      for (run = total; run < xt; run++) {
        bdata[run] = 0;
      }
    }

    return bdata;
  }

  int getUInt(byte[] b, int offset, int num) {
    int base = 1;
    int i;
    int word = 0;
    int bv[] = new int[num];

    for (i = 0; i < num; i++) {
      bv[i] = DataType.unsignedByteToShort(b[offset + i]);
    }

    /*
    * Calculate the integer value of the byte sequence
    */
    for (i = num - 1; i >= 0; i--) {
      word += base * bv[i];
      base *= 256;
    }

    return word;
  }

  int getInt(byte[] b, int offset, int num) {
    int base = 1;
    int i;
    int word = 0;
    int bv[] = new int[num];

    for (i = 0; i < num; i++) {
      bv[i] = DataType.unsignedByteToShort(b[offset + i]);
    }

    if (bv[0] > 127) {
      bv[0] -= 128;
      base = -1;
    }

    /*
    * Calculate the integer value of the byte sequence
    */

    for (i = num - 1; i >= 0; i--) {
      word += base * bv[i];
      base *= 256;
    }

    return word;
  }

  @Override
  public void reacquire() throws IOException {
    super.reacquire();
    headerParser.raf = this.raf;
  }
}


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package thredds.server.opendap;

import opendap.dap.DAPNode;
import ucar.nc2.*;
import ucar.ma2.DataType;

import opendap.servers.*;
import opendap.dap.BaseType;
import ucar.nc2.dataset.CoordinateAxis;
import ucar.nc2.dataset.NetcdfDataset;

import java.util.*;

/**
 * NcDDS is a specialization of ServerDDS for netcdf files.
 * This creates a ServerDDS from the netcdf file.
 *
 * @author jcaron
 */

public class NcDDS extends ServerDDS {

  // Handle the case of potential grids when the array has duplicate dims
  static protected final boolean HANDLE_DUP_DIM_GRIDS = true;

  static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NcDDS.class);

  //private HashMap<String, BaseType> coordHash = new HashMap<String, BaseType>(50); // non grid coordinate variables
  // Track various subsets of the variables
  private Map<String, Variable> coordvars = new HashMap<>(50);
  private List<Variable> ddsvars = new ArrayList<>(50);   // list of currently active variables
  private Map<String, Variable> gridarrays = new HashMap<>(50);
  private Map<String, Variable> used = new HashMap<>(50);

  private Variable findVariable(String name) {
    for (Variable v : ddsvars) {
      if (v.getFullName().equals(name)) return v;
    }
    return null;
  }

  /**
   * Constructor
   *
   * @param name   name of the dataset, at bottom of DDS
   * @param ncfile create DDS from this
   */
  public NcDDS(String name, NetcdfFile ncfile) {
    super((name));

    if (ncfile instanceof NetcdfDataset) {
      NetcdfDataset ncd = (NetcdfDataset) ncfile;
      if (ncd.getEnhanceMode().contains(NetcdfDataset.Enhance.CoordSystems)) {
        createFromDataset((NetcdfDataset) ncfile);
        return;
      }
    }
    createFromFile(ncfile);
  }

  private void createFromFile(NetcdfFile ncfile) {
    // dup the variable set
    ddsvars = new ArrayList<>(ncfile.getVariables());

    // get coordinate variables
    for (Object o : ncfile.getDimensions()) {
      Dimension dim = (Dimension) o;
      Variable cv = findVariable(dim.getShortName());
      if ((cv != null) && cv.isCoordinateVariable()) {
        coordvars.put(dim.getShortName(), cv);
        if (log.isDebugEnabled())
          log.debug(" NcDDS adding coordinate variable " + cv.getFullName() + " for dimension " + dim.getShortName());
      }
    }

    // collect grid array variables and set of used (in grids) coordinate variables
    for (Variable v : ddsvars) {
      boolean isgridarray = (v.getRank() > 1) && (v.getDataType() != DataType.STRUCTURE) && (v.getParentStructure() == null);
      if (!isgridarray) continue;
      List<Dimension> dimset = v.getDimensions();
      int rank = dimset.size();
      for (int i = 0; isgridarray && i < rank; i++) {
        Dimension dim = dimset.get(i);
        if (dim.getShortName() == null)
          isgridarray = false;
        else {
          Variable gv = coordvars.get(dim.getShortName());
          if (gv == null)
            isgridarray = false;
        }
        if (HANDLE_DUP_DIM_GRIDS) {
          // Check for duplicate dims
          for (int j = i + 1; isgridarray && j < rank; j++) {
            if (dimset.get(j) == dim)
              isgridarray = false;
          }
        }
      }
      if (isgridarray) {
        gridarrays.put(v.getFullName(), v);
        for (Dimension dim : dimset) {
          Variable gv = coordvars.get(dim.getShortName());
          if (gv != null)
            used.put(gv.getFullName(), gv);
        }
      }
    }
    // remove the used coord vars from ddsvars (wrong for now; keep so that coord vars are top-level also)
    // for(Variable v: used.values()) ddsvars.remove(v);

    // Create the set of variables
    for (Object o1 : ddsvars) {
      Variable cv = (Variable) o1;
      BaseType bt;

      /* if (false && cv.isCoordinateVariable()) {
        if ((cv.getDataType() == DataType.CHAR))
          bt = (cv.getRank() > 1) ? new NcSDCharArray(cv) : new NcSDString(cv);
        else
          bt = new NcSDArray(cv, createScalarVariable(ncfile, cv));
      } */
      //if (bt == null)
      bt = createVariable(ncfile, cv);
      addVariable(bt);
    }
  }

  // take advantage of the work already done by NetcdfDataset
  private void createFromDataset(NetcdfDataset ncd) {
        // get coordinate variables, disjunct from variables
    for (CoordinateAxis axis : ncd.getCoordinateAxes()) {
      coordvars.put(axis.getShortName(), axis);
    }

    // dup the variable set
    ddsvars = new ArrayList<>(50);

    // collect grid array variables and set of coordinate variables used in grids
    for (Variable v : ncd.getVariables()) {
      if (coordvars.containsKey(v.getShortName())) continue;  // skip coordinate variables
      ddsvars.add(v);

      boolean isgridarray = (v.getRank() > 1) && (v.getDataType() != DataType.STRUCTURE) && (v.getParentStructure() == null);
      if (!isgridarray) continue;
      List<Dimension> dimset = v.getDimensions();
      int rank = dimset.size();
      for (int i = 0; isgridarray && i < rank; i++) {
        Dimension dim = dimset.get(i);
        if (dim.getShortName() == null)
          isgridarray = false;
        else {
          Variable gv = coordvars.get(dim.getShortName());
          if (gv == null)
            isgridarray = false;
        }
      }
      if (isgridarray) {
        gridarrays.put(v.getFullName(), v);
        for (Dimension dim : dimset) {
          Variable gv = coordvars.get(dim.getShortName());
          if (gv != null)
            used.put(gv.getFullName(), gv);
        }
      }
    }

        // Create the set of coordinates
    for (Variable cv : ncd.getCoordinateAxes()) {
      BaseType bt = createVariable(ncd, cv);
      addVariable(bt);
    }

    // Create the set of variables
    for (Variable cv : ddsvars) {
      BaseType bt = createVariable(ncd, cv);
      addVariable(bt);
    }
  }

  // turn Variable into opendap variable

  private BaseType createVariable(NetcdfFile ncfile, Variable v) {
    BaseType bt;
    if (v.getRank() == 0)  // scalar
      bt = createScalarVariable(ncfile, v);

    else if (v.getDataType() == DataType.CHAR) {
      if (v.getRank() > 1)
        bt = new NcSDCharArray(v);
      else
        bt = new NcSDString(v);

    } else if (v.getDataType() == DataType.STRING) {
      if (v.getRank() == 0)
        bt = new NcSDString(v);
      else
        bt = new NcSDArray(v, new NcSDString(v));

    } else  // non-char multidim array
      bt = createArray(ncfile, v);

    return bt;

  }

  private BaseType createScalarVariable(NetcdfFile ncfile, Variable v) {
    DataType dt = v.getDataType();
    if (dt == DataType.DOUBLE)
      return new NcSDFloat64(v);
    else if (dt == DataType.FLOAT)
      return new NcSDFloat32(v);
    else if (dt == DataType.INT)
      return new NcSDInt32(v);
    else if (dt == DataType.UINT)
      return new NcSDUInt32(v);
    else if (dt == DataType.SHORT)
      return new NcSDInt16(v);
    else if (dt == DataType.USHORT)
      return new NcSDUInt16(v);
    else if ((dt == DataType.BYTE) || (dt == DataType.UBYTE))
      return new NcSDByte(v);
    else if (dt == DataType.CHAR)
      return new NcSDString(v);
    else if (dt == DataType.STRING)
      return new NcSDString(v);
    else if (dt == DataType.STRUCTURE)
      return createStructure(ncfile, (Structure) v);
    else
      throw new UnsupportedOperationException("NcDDS Variable data type = " + dt);
  }

  private BaseType createArray(NetcdfFile ncfile, Variable v) {
    // all dimensions must have coord vars to be a grid, also must have the same name
    // no dim can be duplicated.
    boolean isGrid = (gridarrays.get(v.getFullName()) != null);
    NcSDArray arr = new NcSDArray(v, createScalarVariable(ncfile, v));
    if (isGrid) {
      ArrayList<BaseType> list = new ArrayList<>();
      list.add(arr); // Array is first element in the list
      List<Dimension> dimset = v.getDimensions();
      for (Dimension dim : dimset) {
        Variable v1 = used.get(dim.getShortName());
        assert (v1 != null);
        BaseType bt;
        if ((v1.getDataType() == DataType.CHAR))
          bt = (v1.getRank() > 1) ? new NcSDCharArray(v1) : new NcSDString(v1);
        else
          bt = new NcSDArray(v1, createScalarVariable(ncfile, v1));
        list.add(bt);
      }
      return new NcSDGrid(v.getShortName(), list);

    } else
      return arr;
  }

  private BaseType createStructure(NetcdfFile ncfile, Structure s) {
    ArrayList<BaseType> list = new ArrayList<>();
    for (Object o : s.getVariables()) {
      Variable nested = (Variable) o;
      list.add(createVariable(ncfile, nested));
    }
    return new NcSDStructure(s, list);
  }

  /**
   * Returns a clone of this <code>?</code>.
   * See BaseType.cloneDAG()
   *
   * @param map track previously cloned nodes
   * @return a clone of this object.
   */
  public DAPNode cloneDAG(CloneMap map)
          throws CloneNotSupportedException {
    NcDDS d = (NcDDS) super.cloneDAG(map);
    d.coordvars = coordvars;
    return d;
  }

}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.nc2.ui.geoloc;

import ucar.nc2.ui.widget.BAMutil;
import ucar.nc2.util.ListenerManager;
import ucar.unidata.geoloc.*;
import ucar.unidata.geoloc.projection.LatLonProjection;
import ucar.unidata.util.Format;
import ucar.util.prefs.ui.Debug;

import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.IOException;

/**
 * Implements a "navigated" JPanel within which a user can zoom and pan.
 *
 * The mapping of the screen area to world coordinates is called "navigation", and
 * it's NavigatedPanel's job to keep track of the navigation as the user zooms and pans.
 * It throws NewMapAreaEvent to indicate that the user has changed the Map area,
 * and the display needs to be redrawn. It throws PickEvents when the user double clicks
 * on the panel.

 *

 * NavigatedPanel has a standard JToolbar that can be displayed.
 * It also implements a "reference" point and fast updating of the
 * status of the mouse position relative to the reference point.

 *

 * A user typically adds a NavigatedPanel and its toolbar to its frame/applet, and
 * registers itself for NewMapAreaEvent's. See NPController class for an example.
 * When an event occurs, the user obtains
 * a Graphics2D (through the getBufferedImageGraphics() method) to draw into. The
 * AffineTransform of the Graphics2D has been set correctly to map projection coords
 * to screen coords, based on the current zoomed and panned Map area.
 * The renderer can use the AffineTransform if needed, but more typically just works in
 * projection coordinates. The renderer can also get a clipping rectangle by calling
 * g.getClip() and casting the Shape to a Rectangle2D, eg:
 *

 *       Rectangle2D clipRect = (Rectangle2D) g.getClip();
 *

 *
 * Our "world coordinates" are the same as java2D's "user coordinates".
 * In general, the world coordinate plane is a projective geometry surface, typically
 * in units of "km on the projection surface".
 * The transformation from lat/lon to the projection plane is handled by a ProjectionImpl object.
 * If a user selects a different projection, NavigatedPanel.setProjection() should be called.
 * The default projection is "Cylindrical Equidistant" or "LatLon" which simply maps lat/lon
 * degrees linearly to screen coordinates. A peculiarity of this projection is that the "seam"
 * of the cylinder shifts as the user pans around. Currently our implementation sends
 * a NewMapAreaEvent whenever this happens.

 *

 *

 *
 * @see NPController
 * @author John Caron
 */

public class NavigatedPanel extends JPanel {

  /* Implementation Notes:
     - NavigatedPanel uses an image to buffer the image.
     - geoSelection being kept in Projection coords; should probably be LatLon,
       following reference point implementation. (5/16/04)
     - bug where mouse event / image are sometimes not correctly registered when first starting up.
       goes away when you do a manual resize. (5/16/04) may depend on jgoodies widget being
       in parent layout ?
   */

  // public actions;
  public AbstractAction setReferenceAction;

  // main delegates
  private Navigation navigate = null;
  private ProjectionImpl project = null;

  // ui stuff
  private BufferedImage bImage = null;
  private Color backColor = Color.white;
  private JLabel statusLabel = null;
  private myImageObserver imageObs = new myImageObserver();
  private NToolBar toolbar = null;

  // scheduled redraw
  private javax.swing.Timer redrawTimer = null;

  // state flags
  private boolean changedSinceDraw = true;
  private boolean changeable = true; // user allowed to change zoom/pan

  // dragging and zooming state
  private int startx, starty, deltax, deltay;
  private boolean panningMode = false;
  private boolean zoomingMode = false;
  private Rubberband zoomRB;

  // track reference point
  private boolean isReferenceMode = false, hasReference = false;
  private ProjectionPointImpl refWorld = new ProjectionPointImpl();
  private LatLonPointImpl refLatLon = new LatLonPointImpl();
  private Point2D refScreen = new Point2D.Double();
  private int referenceSize = 12;
  private Cursor referenceCursor = null;
  private static final int REFERENCE_CURSOR = -100;

  // selecting geo area state
  private ProjectionRect geoSelection = null;
  private boolean geoSelectionMode = false, moveSelectionMode = false;
  private RubberbandRectangleHandles selectionRB;

  // event management
  private ListenerManager lmPick;
  private ListenerManager lmMove;
  private ListenerManager lmMapArea;
  private ListenerManager lmProject;
  private ListenerManager lmGeoSelect;

  // some working objects to minimize excessive garbage collection
  private StringBuffer sbuff = new StringBuffer(100);
  private ProjectionPointImpl workW = new ProjectionPointImpl();
  private LatLonPointImpl workL = new LatLonPointImpl();
  private Point2D workS = new Point2D.Double();
  private Bearing workB = new Bearing();
  private Rectangle myBounds = new Rectangle();
  private ProjectionRect boundingBox = new ProjectionRect();

  // DnD
  private DropTarget dropTarget;

  //debug
  private int repaintCount = 0;
  private static final boolean debugDraw = false, debugEvent = false,
            debugThread = false, debugStatus = false;
  private static final boolean debugTime = false, debugPrinting = false,
            debugBB = false, debugZoom = false;
  private static final boolean debugBounds = false, debugSelection = false,
            debugNewProjection = false;

  /** The constructor. */
  public NavigatedPanel() {
    setDoubleBuffered(false); // we do our own double buffer

    // default navigation and projection
    navigate = new Navigation(this);
    project = new LatLonProjection("Cyl.Eq"); // default projection
    navigate.setMapArea(project.getDefaultMapArea());

    // toolbar actions
    makeActions();

    // listen for mouse events
    addMouseListener(new myMouseListener());
    addMouseMotionListener(new myMouseMotionListener());
    addMouseWheelListener(new myMouseWheelListener());

    // catch resize events
    addComponentListener(new ComponentAdapter() {
      public void componentResized(ComponentEvent e) {
        newScreenSize(getBounds());
      }
    });

    // rubberbanding
    zoomRB = new RubberbandRectangle( this, false);
    selectionRB = new RubberbandRectangleHandles( this, false);

    // DnD
    dropTarget = new DropTarget(this, DnDConstants.ACTION_COPY,
                                new myDropTargetListener());

    // manage Event Listener's
    lmPick = new ListenerManager(
        "ucar.nc2.ui.geoloc.PickEventListener",
        "ucar.nc2.ui.geoloc.PickEvent",
        "actionPerformed");

    lmMove = new ListenerManager(
        "ucar.nc2.ui.geoloc.CursorMoveEventListener",
        "ucar.nc2.ui.geoloc.CursorMoveEvent",
        "actionPerformed");

    lmMapArea = new ListenerManager(
        "ucar.nc2.ui.geoloc.NewMapAreaListener",
        "ucar.nc2.ui.geoloc.NewMapAreaEvent",
        "actionPerformed");

    lmProject = new ListenerManager(
        "ucar.nc2.ui.geoloc.NewProjectionListener",
        "ucar.nc2.ui.geoloc.NewProjectionEvent",
        "actionPerformed");

    lmGeoSelect = new ListenerManager(
        "ucar.nc2.ui.geoloc.GeoSelectionListener",
        "ucar.nc2.ui.geoloc.GeoSelectionEvent",
        "actionPerformed");
  }

 public LatLonRect getGeoSelectionLL() {
    return geoSelection == null ? null : project.projToLatLonBB( geoSelection);
  }
  public ProjectionRect getGeoSelection() { return geoSelection; }
  public void setGeoSelection(ProjectionRect bb) {
    geoSelection = bb;
  }
  public void setGeoSelection(LatLonRect llbb) {
    setGeoSelection( project.latLonToProjBB(llbb));
  }

  public void setGeoSelectionMode(boolean b) {
    geoSelectionMode = b;
  }

  // event management

    /** Register a NewMapAreaListener. */
  public void addNewMapAreaListener( NewMapAreaListener l) {
    lmMapArea.addListener(l);
  }
  /** Remove a NewMapAreaListener. */
  public void removeNewMapAreaListener( NewMapAreaListener l) {
    lmMapArea.removeListener(l);
  }

  /** Register a NewProjectionListener. The only time this is called is when the
   *  projection isLatLon, and a zoom or pan crosses the seam.
   */
  public void addNewProjectionListener( NewProjectionListener l) {
    lmProject.addListener(l);
  }
    /** Remove a NewProjectionListener. */
  public void removeNewProjectionListener( NewProjectionListener l) {
    lmProject.removeListener(l);
  }

    /** Register a CursorMoveEventListener. */
  public void addCursorMoveEventListener( CursorMoveEventListener l) {
    lmMove.addListener(l);
  }
    /** Remove a CursorMoveEventListener. */
  public void removeCursorMoveEventListener( CursorMoveEventListener l) {
    lmMove.removeListener(l);
  }
    /** Register a PickEventListener. */
  public void addPickEventListener( PickEventListener l) {
    lmPick.addListener(l);
  }
    /** Remove a PickEventListener. */
  public void removePickEventListener( PickEventListener l) {
    lmPick.removeListener(l);
  }
    /** Register a PickEventListener. */
  public void addGeoSelectionListener( GeoSelectionListener l) {
    lmGeoSelect.addListener(l);
  }
    /** Remove a PickEventListener. */
  public void removeGeoSelectionListener( GeoSelectionListener l) {
    lmGeoSelect.removeListener(l);
  }

  // called by Navigation
  void fireMapAreaEvent() {
    if (debugZoom)
      System.out.println("NP.fireMapAreaEvent ");

     // decide if we need a new Projection: for LatLonProjection only
    if (project.isLatLon()) {
      LatLonProjection llproj = (LatLonProjection) project;
      ProjectionRect box = getMapArea();
      double center = llproj.getCenterLon();
      double lonBeg = LatLonPointImpl.lonNormal(box.getMinX(), center);
      double lonEnd = lonBeg + box.getMaxX() - box.getMinX();
      boolean showShift = Debug.isSet("projection/LatLonShift") || debugNewProjection;
      if (showShift) System.out.println("projection/LatLonShift: min,max = "+ box.getMinX()+" "+
            box.getMaxX()+" beg,end= "+lonBeg+" "+lonEnd+ " center = "+center);

      if ( (lonBeg  center+180)) {  // got to do it
        double wx0 = box.getX() + box.getWidth()/2;
        llproj.setCenterLon( wx0);              // shift cylinder seam
        double newWx0 = llproj.getCenterLon();  // normalize wx0 to [-180,180]
        setWorldCenterX(newWx0);             // tell navigation panel to shift
        if (showShift)
          System.out.println("projection/LatLonShift: shift center to "+wx0+"->"+newWx0);

        // send projection event instead of map area event
        lmProject.sendEvent( new NewProjectionEvent(this, llproj));
        return;
      }
    }

    // send new map area event
    lmMapArea.sendEvent( new NewMapAreaEvent( this, getMapArea()));
  }

  // accessor methods
  /* Get the Navigation. */
  public Navigation getNavigation() { return navigate; }

  /* Get the background color of the NavigatedPanel. */
  public Color getBackgroundColor() { return backColor; }

  /** Set the Map Area. */
  public void setMapArea(ProjectionRect ma) {
    if (debugBB) System.out.println("NP.setMapArea "+ ma);
    navigate.setMapArea(ma);
  }
  /** Set the Map Area by converting LatLonRect to a ProjectionRect.*/
  public void setMapArea(LatLonRect llbb) {
    if (debugBB) System.out.println("NP.setMapArea (ll) "+ llbb);
    navigate.setMapArea( project.latLonToProjBB(llbb));
  }
  /** Get the current Map Area */
  public ProjectionRect getMapArea() {
    return navigate.getMapArea(boundingBox);
  }
  /** Get the current Map Area as a lat/lon bounding box */
  public LatLonRect getMapAreaLL() {
    return project.projToLatLonBB( getMapArea());
  }

  /** kludgy thing to shift LatLon seam */
  public void setWorldCenterX( double wx_center) {
    navigate.setWorldCenterX( wx_center);
  }

  /** set the center point of the MapArea */
  public void setLatLonCenterMapArea( double lat, double lon) {
    ProjectionPoint center = project.latLonToProj(lat, lon);

    ProjectionRect ma = getMapArea();
    ma.setX( center.getX() - ma.getWidth()/2);
    ma.setY( center.getY() - ma.getHeight()/2);

    setMapArea(ma);
  }

    /** Get the current Projection.*/
  public ProjectionImpl getProjectionImpl() { return project; }

    /** Set the Projection, change the Map Area to the projection's default. */
  public void setProjectionImpl( ProjectionImpl p) {
      // transfer selection region to new coord system
    if (geoSelection != null)  {
      LatLonRect geoLL = project.projToLatLonBB(geoSelection);
      setGeoSelection( p.latLonToProjBB( geoLL));
    }

      // switch projections
    project = p;
    navigate.setMapArea(project.getDefaultMapArea());
    if (Debug.isSet("projection/set") || debugNewProjection)
      System.out.println("projection/set NP="+ project);

      // transfer reference point to new coord system
    if (hasReference)  {
      refWorld.setLocation(project.latLonToProj( refLatLon));
    }

  }

    /** The status label is where the lat/lon position of the mouse is displayed. May be null.
      @param statusLabel the Jlabel to write into */
  public void setPositionLabel(JLabel statusLabel) { this.statusLabel = statusLabel; }

    /** @return the "Navigate" toolbar */
  public JToolBar getNavToolBar () { return new NToolBar(); }

    /** @return the "Move" toolbar */
  public JToolBar getMoveToolBar () { return new MoveToolBar(); }

    /** Add all of the toolbar's actions to a menu.
      @param menu the menu to add the actions to */
  public void addActionsToMenu (JMenu menu) {
    BAMutil.addActionToMenu( menu, zoomIn);
    BAMutil.addActionToMenu( menu, zoomOut);
    BAMutil.addActionToMenu( menu, zoomBack);
    BAMutil.addActionToMenu( menu, zoomDefault);

    menu.addSeparator();

    BAMutil.addActionToMenu( menu, moveUp);
    BAMutil.addActionToMenu( menu, moveDown);
    BAMutil.addActionToMenu( menu, moveRight);
    BAMutil.addActionToMenu( menu, moveLeft);

    menu.addSeparator();

    BAMutil.addActionToMenu( menu, setReferenceAction);
  }

  public void setEnabledActions(boolean b) {
    zoomIn.setEnabled( b);
    zoomOut.setEnabled( b);
    zoomBack.setEnabled( b);
    zoomDefault.setEnabled( b);
    moveUp.setEnabled( b);
    moveDown.setEnabled( b);
    moveRight.setEnabled( b);
    moveLeft.setEnabled( b);
  }

      // Make sure we dont get overwhelmed by redraw calls
      // from panning, so wait delay msecs before doing the redraw.
  private void redrawLater(int delay) {
    boolean already = (redrawTimer != null) && (redrawTimer.isRunning());
    if (debugThread) System.out.println( "redrawLater isRunning= "+ already);
    if (already)
      return;

      // initialize Timer the first time
    if (redrawTimer == null) {
      redrawTimer = new javax.swing.Timer(0, new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          drawG();
          redrawTimer.stop(); // one-shot timer
        }
      });
    }
      // start the timer running
    redrawTimer.setDelay(delay);
    redrawTimer.start();
  }

    /** sets whether the user can zoom/pan on this NavigatedPanel. Default = true.
     * @param mode  set to false if user can't zoom/pan
     */
  public void setChangeable (boolean mode) {
    if (mode == changeable)
      return;
    changeable = mode;
    if (toolbar != null)
      toolbar.setEnabled(mode);
  }

  /** catch repaints - for debugging */
  // note: I believe that the RepaintManager is not used on JPanel subclasses ???
  public void repaint(long tm, int x, int y, int width, int height) {
    if (debugDraw) System.out.println("REPAINT "+ repaintCount+
        " x "+ x+ " y "+ y+ " width "+ width+ " heit "+ height);
    if (debugThread) System.out.println(" thread = "+ Thread.currentThread());
    repaintCount++;
    super.repaint(tm, x, y, width, height);
  }

  /** System-triggered redraw. */
  public void paintComponent(Graphics g) {
    if (debugDraw) System.out.println( "System called paintComponent clip= "+ g.getClipBounds());
    draw( (Graphics2D) g);
  }

  /** This is used to do some fancy tricks with double buffering */
  public BufferedImage getBufferedImage() { return bImage; }

  /** User must get this Graphics2D and draw into it when panel needs redrawing */
  public Graphics2D getBufferedImageGraphics() {
    if (bImage == null)
      return null;
    Graphics2D g2 = bImage.createGraphics();

    // set clipping rectangle into boundingBox
    navigate.getMapArea( boundingBox);
    if (debugBB) System.out.println(" getBufferedImageGraphics BB = "+ boundingBox);

    // set graphics attributes
    g2.setTransform( navigate.getTransform());
    g2.setStroke(new BasicStroke(0.0f));      // default stroke size is one pixel
    g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
    Rectangle2D hr = new Rectangle2D.Double();
    hr.setRect(boundingBox.getX(), boundingBox.getY(), boundingBox.getWidth(), boundingBox.getHeight());
    g2.setClip( hr);                 // normalized coord system, because transform is applied
    g2.setBackground( backColor);

    return g2;
  }

  //////////////////////// printing ////////////////////////////////
  /** utility routine for printing.
   * @param pwidth : widtht of the page, units are arbitrary
   * @param pheight : height of the page, units are arbitrary
   * @return true if we want to rotate the page
   */
  public boolean wantRotate(double pwidth, double pheight) {
    return navigate.wantRotate(pwidth, pheight);
  }

  /** This calculates the Affine Transform that maps the current map area (in Projection Coordinates)
   * to a display area (in arbitrary units).
   * @param rotate should the page be rotated?
   * @param displayX            upper right corner of display area
   * @param displayY            upper right corner of display area
   * @param displayWidth   display area
   * @param displayHeight   display area
   *
   * see Navigation.calcTransform
   */
  public AffineTransform calcTransform(boolean rotate, double displayX, double displayY,
      double displayWidth, double displayHeight) {
    return navigate.calcTransform(rotate, displayX, displayY, displayWidth, displayHeight);
  }

    // LOOK! change this to an inner class ?
    /* Render to a printer. part of Printable interface
      @param g        the Graphics context
      @param pf       describes the page format
      @param pi       page number

  public int print(Graphics g, PageFormat pf, int pi) throws PrinterException {
    if (pi >= 1) {
      return Printable.NO_SUCH_PAGE;
    }
    Graphics2D g2 = (Graphics2D) g;
    g2.setColor(Color.black);

    double pheight = pf.getImageableHeight();
    double pwidth = pf.getImageableWidth();
    double px = pf.getImageableX();
    double py = pf.getImageableY();
    g2.drawRect( (int) px, (int) py, (int) pwidth, (int)pheight);

    AffineTransform orgAT = g2.getTransform();
    if (debugPrinting) System.out.println(" org transform = "+orgAT);

    //  set clipping rectangle LOOK ????
    //navigate.getMapArea( boundingBox);
    //g2.setClip( boundingBox);

    boolean rotate = navigate.wantRotate(pwidth, pheight);
    AffineTransform at2 = navigate.calcTransform(rotate, px, py, pwidth, pheight);
    g2.transform( at2);
    AffineTransform at = g2.getTransform();
    if (debugPrinting) System.out.println(" use transform = "+at);
    double scale = at.getScaleX();

    // if we need to rotate, also rotate the original transform
    if (rotate)
      orgAT.rotate( -Math.PI/2, px + pwidth/2, py + pheight/2);

      // set graphics attributes                           // LOOK! hanging printer
    //g2.setStroke(new BasicStroke((float)(2.0/scale)));  // default stroke size is two pixels
    g2.setStroke(new BasicStroke(0.0f));  // default stroke size is two pixels
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
    g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);

     // draw the image to the buffer
    //render.draw(g2, orgAT);

    if (debugPrinting) {
      System.out.println("  Graphics clip "+ g2.getClipBounds());
      System.out.println("  Page Format     "+ pf.getOrientation());
      System.out.println("  getH/W          "+ pf.getHeight()+ " "+ pf.getWidth());
      System.out.println("  getImageableH/W "+ pf.getImageableHeight()+ " "+ pf.getImageableWidth());
      System.out.println("  getImageableX/Y "+ pf.getImageableX()+ " "+ pf.getImageableY());

      /* Paper paper = pf.getPaper();
      System.out.println("  Paper     ");
      System.out.println("  getH/W          "+ paper.getHeight()+ " "+ paper.getWidth());
      System.out.println("  getImageableH/W "+ paper.getImageableHeight()+ " "+ paper.getImageableWidth());
      System.out.println("  getImageableX/Y "+ paper.getImageableX()+ " "+ paper.getImageableY());
    }

    return Printable.PAGE_EXISTS;
  }   */

  ///////////////////////////////////////////////////////////////////////////////////
  // private methods

      // when component resizes we need a new buffer
  private void newScreenSize(Rectangle b) {
    boolean sameSize = (b.width == myBounds.width) && (b.height == myBounds.height);
    if (debugBounds) System.out.println( "NavigatedPanel newScreenSize old= "+myBounds);
    if (sameSize && (b.x == myBounds.x) && (b.y == myBounds.y))
      return;

    myBounds.setBounds(b);
    if (sameSize)
      return;

    if (debugBounds) System.out.println( "  newBounds = " +b);

    // create new buffer the size of the window
    //if (bImage != null)
    //  bImage.dispose();

    if ((b.width > 0) && (b.height > 0)) {
      bImage = new BufferedImage(b.width, b.height, BufferedImage.TYPE_INT_RGB); // why RGB ?
    } else {                                                                     // why not device dependent?
      bImage = null;
    }

   navigate.setScreenSize(b.width, b.height);
  }

  // draw and drawG are like "paintImmediately()"
  public void drawG() {
    Graphics g = getGraphics();    // bypasses double buffering ?
    if (null != g) {
      draw( (Graphics2D) g);
      g.dispose();
    }
  }
  private void draw(Graphics2D g) {
    if (bImage == null)
      return;
    boolean redrawReference = true;
    Rectangle bounds = getBounds();

    if (panningMode) {
      if (debugDraw) System.out.println( "draw draggingMode ");
          // Clear the image.
      g.setBackground(backColor);
      g.clearRect(0, 0, bounds.width, bounds.height);
      g.drawImage( bImage, deltax, deltay, backColor, imageObs);
      redrawReference = false;
   } else {
      if (debugDraw) System.out.println( "draw copy ");
      g.drawImage( bImage, 0, 0, backColor, imageObs);
   }

   if (hasReference && redrawReference) {
     refWorld.setLocation(project.latLonToProj( refLatLon));
     navigate.worldToScreen(refWorld, refScreen);
     int px = (int) refScreen.getX();
     int py = (int) refScreen.getY();
     g.setColor( Color.red);
     g.setStroke(new BasicStroke(2.0f));
     // g.drawImage( referenceCursor.getImage(), px, py, Color.red, imageObs);
     g.drawLine(px, py-referenceSize, px, py+referenceSize);
     g.drawLine(px-referenceSize, py, px+referenceSize, py);
   }

   // clean up
   changedSinceDraw = false;
  }

  private void setCursor(int what) {
    if (what == REFERENCE_CURSOR) {
      if (null == referenceCursor) {
        referenceCursor = BAMutil.makeCursor("ReferencePoint");
        if (null == referenceCursor)
          return;
      }
      super.setCursor(referenceCursor);
    } else
      super.setCursor(Cursor.getPredefinedCursor(what));
  }

  private void showStatus(int mousex, int mousey) {
    if ((statusLabel == null) && !lmMove.hasListeners())
      return;

    workS.setLocation( mousex, mousey);
    navigate.screenToWorld( workS, workW);
    workL.set(project.projToLatLon( workW));
    if (lmMove.hasListeners())
      lmMove.sendEvent( new CursorMoveEvent(this, workW));

    if (statusLabel == null)
      return;

    sbuff.setLength(0);

    if (ucar.util.prefs.ui.Debug.isSet("projection/showPosition")) {
      sbuff.append(workW + " -> ");
    }

    sbuff.append(workL.toString(5));

    if (hasReference) {
        Bearing.calculateBearing(refLatLon, workL, workB);
        sbuff.append("  (");
        sbuff.append(Format.dfrac(workB.getAngle(), 0));
        sbuff.append(" deg ");
        sbuff.append(Format.d(workB.getDistance(), 4, 5));
        sbuff.append(" km)");
    }

    statusLabel.setText(sbuff.toString());
  }

  private void setReferenceMode() {
    if (isReferenceMode) {   // toggle
      isReferenceMode = false;
      setCursor(Cursor.DEFAULT_CURSOR);
      statusLabel.setToolTipText("position at cursor");
      drawG();
    } else {
      hasReference = false;
      isReferenceMode = true;
      setCursor(Cursor.CROSSHAIR_CURSOR);
      statusLabel.setToolTipText("position (bearing)");
    }
  }

  //////////////////////////////////////////////////////////////
  // inner classes

  /* myMouseListener and myMouseMotionListener implements the standard mouse behavior.

     A: pan:
       * press right : start panning mode
       * drag : pan image
       * release: redraw with new area

     B: zoom:
       * press left : start zooming mode
       * drag : draw rubberband
       * release: redraw with new area

     C: move:
       * show cursor location  / distance from reference point

     D. click:
       * (reference mode) : set reference point
       * (not reference mode): throw pick event.

     E: selection:
       * press left : find anchor point = diagonal corner
       * drag left : draw rubberband
       * press right : start move selection mode
       * drag right : move selection
       * release: throw new selection event
    */

  private class myMouseListener extends MouseAdapter {
    @Override
    public void mouseClicked( MouseEvent e) {
      // pick event
      if (isReferenceMode) {
        hasReference = true;
        refScreen.setLocation(e.getX(), e.getY());
        navigate.screenToWorld(refScreen, refWorld);
        refLatLon.set(project.projToLatLon(refWorld));
        setCursor(REFERENCE_CURSOR);
        drawG();
      } else {
        workS.setLocation(e.getX(), e.getY());
        navigate.screenToWorld(workS, workW);
        lmPick.sendEvent( new PickEvent(NavigatedPanel.this, workW));
      }
    }

    @Override
    public void mousePressed( MouseEvent e) {
      if (!changeable)
        return;

      startx = e.getX();
      starty = e.getY();
      if (debugSelection) System.out.println(" NP press="+e.getPoint());

      // geoSelectionMode
      if (geoSelectionMode && (geoSelection != null)) {
        if (e.getClickCount() == 2) {
          ProjectionPointImpl pp = new ProjectionPointImpl();
          navigate.screenToWorld(e.getPoint(), pp);
          // make geoSelection fit in screen
          geoSelection.setWidth( boundingBox.getWidth()/4);
          geoSelection.setHeight( boundingBox.getHeight()/4);
          // make it the center point
          geoSelection.setX( pp.getX() - geoSelection.getWidth()/2);
          geoSelection.setY( pp.getY() - geoSelection.getHeight()/2);
          lmGeoSelect.sendEvent( new GeoSelectionEvent( this, geoSelection));
          return;
        }

        // CTRL is down, but SHIFT is up
        int onmask = InputEvent.CTRL_DOWN_MASK;
        int offmask = InputEvent.SHIFT_DOWN_MASK;
        if (onmask == (e.getModifiersEx() & (onmask | offmask))) {
          Rectangle sw = navigate.worldToScreen(geoSelection);
          selectionRB.setRectangle(sw);
          if (debugSelection) System.out.println("NB start selection=" + geoSelection + " => " + sw);

          if (SwingUtilities.isRightMouseButton(e)) { // pan
            if (sw.contains( e.getPoint())) {
              moveSelectionMode = true;
              setCursor(Cursor.MOVE_CURSOR);
              selectionRB.setActive(true);
            }
          } else { // zoom
            if (selectionRB.anchor(e.getPoint())) {
              selectionRB.setActive(true);
              if (debugSelection) System.out.println("  anchor at =" + selectionRB.getAnchor());
            }
          }
          return;
        }
      } // geoSelectionMode

      if (!SwingUtilities.isRightMouseButton(e)) { // left and center mouse
        // zoom mapArea
        zoomRB.anchor(e.getPoint());
        zoomRB.setActive(true);
        zoomingMode = true;

      } else {  // right mouse = pan
        // pan mapArea
        panningMode = true;
        setCursor(Cursor.MOVE_CURSOR);
      }

      if (debugEvent) System.out.println( "mousePressed "+startx+" "+starty);
    }

    @Override
    public void mouseReleased( MouseEvent e) {
      if (!changeable)
        return;
      // System.out.println(" NP release="+e.getPoint());

      deltax = e.getX() - startx;
      deltay = e.getY() - starty;
      if (debugEvent) System.out.println( "mouseReleased "+e.getX()+" "+e.getY()+
                "="+deltax+" "+deltay);

      if (geoSelectionMode && selectionRB.isActive()) {
        selectionRB.setActive(false);
        selectionRB.done();
        moveSelectionMode = false;
        setCursor(Cursor.DEFAULT_CURSOR);

        if (!NavigatedPanel.this.contains(e.getPoint())) { // point is off the panel
          if (debugBounds) System.out.println("NP.select: point "+e.getPoint()+" out of bounds: "+myBounds);
          return;
        }

        geoSelection = navigate.screenToWorld( selectionRB.getAnchor(), selectionRB.getLast());

        // System.out.println(" NP selection rect= "+selectionRB.getAnchor()+" "+selectionRB.getLast()+
        //                   " => "+rect);
            // send new map area event
        lmGeoSelect.sendEvent( new GeoSelectionEvent( this, geoSelection));
      }

      if (panningMode) {
        navigate.pan(-deltax, -deltay);
        panningMode = false;
        setCursor(Cursor.DEFAULT_CURSOR);
      }

      if (zoomingMode) {
        zoomRB.setActive(false);
        zoomRB.end(e.getPoint());
        zoomingMode = false;
        if (!NavigatedPanel.this.contains(e.getPoint())) { // point is off the panel
          if (debugBounds) System.out.println("NP.zoom: point "+e.getPoint()+" out of bounds: "+myBounds);
          return;
        }
        // "start" must be upper left
        startx = Math.min(startx, e.getX());
        starty = Math.min(starty, e.getY());
        navigate.zoom(startx, starty, Math.abs(deltax), Math.abs(deltay));
      }
      //drawG();
    }
  } // end myMouseListener

  // mouseMotionListener
  private class myMouseMotionListener implements MouseMotionListener {
    @Override
    public void mouseDragged( MouseEvent e) {
      if (!changeable)
        return;
      deltax = e.getX() - startx;
      deltay = e.getY() - starty;
      if (debugEvent) System.out.println( "mouseDragged "+e.getX()+" "+e.getY()+
                "="+deltax+" "+deltay);

      if (zoomingMode) {
        zoomRB.stretch(e.getPoint());
        return;
      }

      if (moveSelectionMode) {
        selectionRB.move(deltax, deltay);
        return;
      }

      if (geoSelectionMode && !SwingUtilities.isRightMouseButton(e) && selectionRB.isActive()) {
        selectionRB.stretch(e.getPoint());
        return;
      }

      repaint();
      //redrawLater(100); // schedule redraw in 100 msecs
    }

    @Override
    public void mouseMoved( MouseEvent e) {
      showStatus(e.getX(), e.getY());
    }
  }

  private class myMouseWheelListener extends MouseAdapter {
    @Override
    public void mouseWheelMoved(MouseWheelEvent e) {
      if (e.getWheelRotation() < 0) {
        for (int rotation = e.getWheelRotation(); rotation < 0; ++rotation) {
          navigate.zoomIn();
          drawG();
        }
      } else {
        for (int rotation = e.getWheelRotation(); rotation > 0; --rotation) {
          navigate.zoomOut();
          drawG();
        }
      }
    }
  }

  // necessary for g.drawImage()
  private static class myImageObserver implements ImageObserver {
    public boolean imageUpdate(Image image, int flags, int x, int y, int width, int height) {
      return true;
    }
  }

  //DnD
  private class myDropTargetListener implements DropTargetListener {

    public void dragEnter(DropTargetDragEvent e) {
      System.out.println(" NP dragEnter active = "+ dropTarget.isActive());
      e.acceptDrag(DnDConstants.ACTION_COPY);
    }

    public void drop(DropTargetDropEvent e) {
      try {
        if(e.isDataFlavorSupported(DataFlavor.stringFlavor)){
          Transferable tr = e.getTransferable();
          e.acceptDrop (DnDConstants.ACTION_COPY_OR_MOVE);
          String s = (String)tr.getTransferData (DataFlavor.stringFlavor);
          //dropList.add(s);
          System.out.println(" NP myDropTargetListener got "+ s);
          e.dropComplete(true);
        } else {
          e.rejectDrop();
        }
      } catch (IOException io) {
        io.printStackTrace();
        e.rejectDrop();
      } catch (UnsupportedFlavorException ufe) {
        ufe.printStackTrace();
        e.rejectDrop();
      }
    }
    public void dragExit(DropTargetEvent e) {}
    public void dragOver(DropTargetDragEvent e) {}
    public void dropActionChanged(DropTargetDragEvent e) {}
  }

  //////////////////////////////////////////////////////////////////////////////
  // toolbars

  private AbstractAction zoomIn, zoomOut, zoomDefault, zoomBack;
  private AbstractAction moveUp, moveDown, moveLeft, moveRight;
  private void makeActions() {
      // add buttons/actions
    zoomIn = new AbstractAction() {
      public void actionPerformed(ActionEvent e) { navigate.zoomIn(); drawG();}
    };
    BAMutil.setActionProperties( zoomIn, "MagnifyPlus", "zoom In", false, 'I', KeyEvent.VK_ADD);

    zoomOut = new AbstractAction() {
      public void actionPerformed(ActionEvent e) { navigate.zoomOut(); drawG();}
    };
    BAMutil.setActionProperties( zoomOut, "MagnifyMinus", "zoom Out", false, 'O', KeyEvent.VK_SUBTRACT);

    zoomBack = new AbstractAction() {
      public void actionPerformed(ActionEvent e) { navigate.zoomPrevious();drawG();}
    };
    BAMutil.setActionProperties( zoomBack, "Undo", "Previous map area", false, 'P', KeyEvent.VK_BACK_SPACE);

    zoomDefault = new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        navigate.setMapArea(project.getDefaultMapArea());
        drawG();
      }
    };
    BAMutil.setActionProperties( zoomDefault, "Home", "Home map area", false, 'H', KeyEvent.VK_HOME);

    moveUp = new AbstractAction() {
      public void actionPerformed(ActionEvent e) { navigate.moveUp();drawG();}
    };
    BAMutil.setActionProperties( moveUp, "Up", "move view Up", false, 'U', KeyEvent.VK_UP);

    moveDown = new AbstractAction() {
      public void actionPerformed(ActionEvent e) { navigate.moveDown();drawG();}
    };
    BAMutil.setActionProperties( moveDown, "Down", "move view Down", false, 'D', KeyEvent.VK_DOWN);

    moveLeft = new AbstractAction() {
      public void actionPerformed(ActionEvent e) { navigate.moveLeft();drawG();}
    };
    BAMutil.setActionProperties( moveLeft, "Left", "move view Left", false, 'L', KeyEvent.VK_LEFT);

    moveRight = new AbstractAction() {
      public void actionPerformed(ActionEvent e) { navigate.moveRight(); drawG();}
    };
    BAMutil.setActionProperties( moveRight, "Right", "move view Right", false, 'R', KeyEvent.VK_RIGHT);

    setReferenceAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) { setReferenceMode(); drawG();}
    };
    BAMutil.setActionProperties( setReferenceAction, "ReferencePoint", "set reference Point", true, 'P', 0);
  }

  class NToolBar extends JToolBar {
    NToolBar() {
      AbstractButton b = BAMutil.addActionToContainer( this, zoomIn);
      b.setName("zoomIn");

      b = BAMutil.addActionToContainer( this, zoomOut);
      b.setName( "zoomOut");

      b = BAMutil.addActionToContainer( this, zoomBack);
      b.setName( "zoomBack");

      b = BAMutil.addActionToContainer( this, zoomDefault);
      b.setName( "zoomHome");
    }
  }

  class MoveToolBar extends JToolBar {
    MoveToolBar() {
      AbstractButton b = BAMutil.addActionToContainer( this, moveUp);
      b.setName( "moveUp");

      b = BAMutil.addActionToContainer( this, moveDown);
      b.setName( "moveDown");

      b = BAMutil.addActionToContainer( this, moveLeft);
      b.setName( "moveLeft");

      b = BAMutil.addActionToContainer( this, moveRight);
      b.setName( "moveRight");
    }
  }

 /*   public void setEnabled( boolean mode) {
      for (int i=0; i< getComponentCount(); i++) {
        Component c = getComponentAtIndex(i);
        c.setEnabled( mode);
      }
    }

    public void remove(String which) {
      // find which
      for (int i=0; i< getComponentCount(); i++) {
        Component c = getComponentAtIndex(i);
        if (which.equals(c.getName()))
          remove(c);
      }
    }

  } // end inner class */

} // end NavPanel


/*
 * Copyright (c) 1998-2019 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.nc2.ui.op;

import ucar.nc2.Dimension;
import ucar.nc2.*;
import ucar.nc2.ui.widget.BAMutil;
import ucar.nc2.ui.widget.MultilineTooltip;

import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToolTip;
import javax.swing.JTree;
import javax.swing.ToolTipManager;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

/**
 * A Tree View of the groups and variables inside a NetcdfFile.
 *
 *
 * @author caron
 */
public class DatasetTreeView extends JPanel {

    private final static org.slf4j.Logger logger
                = org.slf4j.LoggerFactory.getLogger (MethodHandles.lookup ( ).lookupClass ( ));

    // ui
    private JTree tree;
    private DatasetTreeModel model;
    private NetcdfFile currentDataset;

/**
 * Constructor.
 */
    public DatasetTreeView() {
        // the catalog tree
        tree = new JTree() {
            public JToolTip createToolTip() { return new MultilineTooltip(); }
        };
        tree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode(null, false)));
        tree.setCellRenderer(new MyTreeCellRenderer());

        tree.addMouseListener( new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                int selRow = tree.getRowForLocation(e.getX(), e.getY());
                if (selRow != -1) {
                    TreeNode node = (TreeNode) tree.getLastSelectedPathComponent();
                    if (node instanceof VariableNode) {
                        VariableIF v = ((VariableNode) node).var;
                        firePropertyChangeEvent(new PropertyChangeEvent(this, "Selection", null, v));
                    }
                }

                if ((selRow != -1) && (e.getClickCount() == 2)) {
                    //acceptSelected();
                }
            }
        });

        tree.putClientProperty("JTree.lineStyle", "Angled");
        tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        tree.setToggleClickCount(1);
        ToolTipManager.sharedInstance().registerComponent(tree);

       // layout
        setLayout(new BorderLayout());
        add(new JScrollPane(tree), BorderLayout.CENTER);
    }

/**
 *
 */
    private void firePropertyChangeEvent(PropertyChangeEvent event) {
        firePropertyChange(event.getPropertyName(), event.getOldValue(), event.getNewValue());
    }

/**
 *
 */
    public void setFile( NetcdfFile ds) {
        if (ds != currentDataset) {
            currentDataset = ds;
            model = new DatasetTreeModel(ds);
            tree.setModel(model);
        }
    }

/**
 &
 */
    public void clear() {
        currentDataset = null;
        model = null;
        tree.setModel(null);
    }

/**
 * Set the currently selected Variable.
 *
 * @param v select this Variable, must be already in the tree.
 */
    public void setSelected( VariableIF v ) {
        if (v == null) { return; }

        // construct chain of variables
        final List<VariableIF> vchain = new ArrayList<>();
        vchain.add( v);

        VariableIF vp = v;
        while (vp.isMemberOfStructure()) {
            vp = vp.getParentStructure();
            vchain.add( 0, vp); // reverse
        }

        // construct chain of groups
        final List<Group> gchain = new ArrayList<>();
        Group gp = vp.getParentGroup();

        gchain.add( gp);
        while (gp.getParentGroup() != null) {
            gp = gp.getParentGroup();
            gchain.add( 0, gp); // reverse
        }

        final List<Object> pathList = new ArrayList<>();

        // start at root, work down through the nested groups, if any
        GroupNode gnode = (GroupNode) model.getRoot();
        pathList.add( gnode);
        Group parentGroup = gchain.get(0); // always the root group

        for (int i=1; i < gchain.size(); i++) {
            parentGroup = gchain.get(i);
            gnode = gnode.findNestedGroup( parentGroup);
            assert gnode != null;
            pathList.add( gnode);
        }

        vp = vchain.get(0);
        VariableNode vnode = gnode.findNestedVariable( vp);
        if (vnode == null) { return; } // not found
        pathList.add( vnode);

        // now work down through the structure members, if any
        for (int i=1; i < vchain.size(); i++) {
            vp = vchain.get(i);
            vnode = vnode.findNestedVariable( vp);
            if (vnode == null) { return; } // not found
            pathList.add(vnode);
        }

        // convert to TreePath, and select it
        final Object[] paths = pathList.toArray();
        final TreePath treePath = new TreePath(paths);
        tree.setSelectionPath( treePath);
        tree.scrollPathToVisible( treePath);
    }

/**
 * make an NetcdfFile into a TreeModel
 */
    private class DatasetTreeModel extends DefaultTreeModel {
        DatasetTreeModel (NetcdfFile file) {
            super( new GroupNode( null, file.getRootGroup()), false);
        }
    }

/**
 *
 */
    private class GroupNode implements TreeNode {
        private Group group;
        private GroupNode parent;
        private List<Object> children = null;

    /**
     *
     */
        GroupNode( GroupNode parent, Group group) {
            this.parent = parent;
            this.group = group;

            logger.debug("new={}", group.getFullName());

            //firePropertyChangeEvent(new PropertyChangeEvent(this, "TreeNode", null, group));
        }

    /**
     *
     */
        public Enumeration children() {
            if (children == null) { makeChildren(); }
            return Collections.enumeration(children);
        }

        public boolean getAllowsChildren() { return true; }

        public TreeNode getChildAt(int index) { return (TreeNode) children.get(index); }

        public int getChildCount() {
            if (children == null) { makeChildren(); }
            return children.size();
        }

        void makeChildren() {
            children = new ArrayList<>();

            List dims = group.getDimensions();
            for (int i = 0; i < dims.size(); i++) {
                children.add( new DimensionNode( this, (Dimension) dims.get(i)));
            }

            List vars = group.getVariables();
            for (int i = 0; i < vars.size(); i++) {
                children.add( new VariableNode( this, (VariableIF) vars.get(i)));
            }

            List groups = group.getGroups();
            for (int i = 0; i < groups.size(); i++) {
                children.add( new GroupNode( this, (Group) groups.get(i)));
            }

            logger.debug("children={}", group.getFullName());
        }

        public int getIndex(TreeNode child) {
            logger.debug("getIndex={} {}", group.getFullName(), child);
            return children.indexOf(child);
        }

        public TreeNode getParent() { return parent; }

        public boolean isLeaf() { return false; }

        public String toString() {
            if (parent == null) {
                // root group
                return currentDataset.getLocation();
            }
            else {
                return group.getShortName();
            }
        }

        public GroupNode findNestedGroup( Group g) {
            if (children == null) { makeChildren(); }
            for (int i = 0; i < children.size(); i++) {
                if (children.get(i) instanceof GroupNode) {
                    final GroupNode elem = (GroupNode) children.get(i);
                    if (elem.group == g) { return elem; }
                }
            }
            return null;
        }

        public VariableNode findNestedVariable( VariableIF v) {
            if (children == null) { makeChildren(); }
            for (int i = 0; i < children.size(); i++) {
                final TreeNode node = (TreeNode) children.get(i);
                if (node instanceof VariableNode ) {
                    final VariableNode vnode= (VariableNode) node;
                    if (vnode.var == v) { return vnode; }
                }
            }
            return null;
        }

        public String getToolTipText() {
            return group.getNameAndAttributes();
        }

  }

/**
 *
 */
    private class VariableNode implements TreeNode {
        private VariableIF var;
        private TreeNode parent;
        private List<Object> children = null;

    /**
     *
     */
        VariableNode( TreeNode parent, VariableIF var) {
            this.parent = parent;
            this.var = var;

            logger.debug("new var={}", var.getShortName());

            //firePropertyChangeEvent(new PropertyChangeEvent(this, "TreeNode", null, var));
        }

    /**
     *
     */
        public Enumeration children() {
            if (children == null) { makeChildren(); }
            return Collections.enumeration(children);
        }

        public boolean getAllowsChildren() { return true; }

        public TreeNode getChildAt(int index) { return (TreeNode) children.get(index); }

        public int getChildCount() {
          if (children == null) { makeChildren(); }
          return children.size();
        }

        void makeChildren() {
            children = new ArrayList<>();

            if (var instanceof Structure) {
                final Structure s = (Structure) var;
                final List vars = s.getVariables();
                for (int i = 0; i < vars.size(); i++) {
                    children.add( new VariableNode( this, (VariableIF) vars.get(i)));
                }
            }
            logger.debug("children={}", var.getShortName());
        }

        public int getIndex(TreeNode child) {
            logger.debug("getIndex={} {}", var.getShortName(), child);
            return children.indexOf(child);
        }

        public TreeNode getParent() { return parent; }

        public boolean isLeaf() { return (getChildCount() == 0); }

        public String toString() { return var.getShortName(); }

        public VariableNode findNestedVariable( VariableIF v) {
            if (children == null) { makeChildren(); }
            for (int i = 0; i < children.size(); i++) {
                final VariableNode elem = (VariableNode) children.get(i);
                if (elem.var == v) { return elem; }
            }
            return null;
        }

        public String getToolTipText() {
            return var.toString();
        }
    }

/**
 *
 */
    private static class DimensionNode implements TreeNode {
        private Dimension d;
        private TreeNode parent;

    /**
     *
     */
        DimensionNode( TreeNode parent, Dimension d) {
            this.parent = parent;
            this.d = d;
        }

        public Enumeration children() { return null;}

        public boolean getAllowsChildren() { return false; }

        public TreeNode getChildAt(int index) { return null; }

        public int getChildCount() { return 0; }

        public int getIndex(TreeNode child) { return 0; }

        public TreeNode getParent() { return parent; }

        public boolean isLeaf() { return true; }

        public String toString() { return d.getShortName(); }

        public String getToolTipText() {
            return d.toString();
        }
  }

/**
 * this is to get different icons
 */
    private static class MyTreeCellRenderer extends DefaultTreeCellRenderer {
        ImageIcon structIcon, dimIcon;
        String tooltipText;

    /**
     *
     */
        public MyTreeCellRenderer() {
            structIcon = BAMutil.getIcon( "Structure", true);
            dimIcon = BAMutil.getIcon( "Dimension", true);
        }

    /**
     *
     */
        public Component getTreeCellRendererComponent(JTree tree, Object value,
                    boolean selected, boolean expanded,boolean leaf, int row, boolean hasFocus) {

            final Component c = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);

            if (value instanceof VariableNode) {
                final VariableNode node = (VariableNode) value;
                tooltipText = node.getToolTipText();

                if (node.var instanceof Structure) {
                    final Structure s = (Structure) node.var;
                    setIcon( structIcon);
                    tooltipText = s.getNameAndAttributes();
                }
                else {
                    tooltipText = node.getToolTipText();
                }
            }
            else if (value instanceof DimensionNode) {
                final DimensionNode node = (DimensionNode) value;
                tooltipText = node.getToolTipText();
                setIcon( dimIcon);
            }
            else if (value instanceof GroupNode) {
                GroupNode node = (GroupNode) value;
                tooltipText = node.getToolTipText();
            }

            return c;
        }

        public String getToolTipText() { return tooltipText; }

    }
}