<commit_before>/*
 * Copyright 1998-2009 University Corporation for Atmospheric Research/Unidata
 *
 * Portions of this software were developed by the Unidata Program at the
 * University Corporation for Atmospheric Research.
 *
 * Access and use of this software shall impose the following obligations
 * and understandings on the user. The user is granted the right, without
 * any fee or cost, to use, copy, modify, alter, enhance and distribute
 * this software, and any derivative works thereof, and its supporting
 * documentation for any purpose whatsoever, provided that this entire
 * notice appears in all copies of the software, derivative works and
 * supporting documentation.  Further, UCAR requests that the user credit
 * UCAR/Unidata in any publications that result from the use of this
 * software or in any product that includes this software. The names UCAR
 * and/or Unidata, however, may not be used in any advertising or publicity
 * to endorse or promote any products or commercial entity unless specific
 * written permission is obtained from UCAR/Unidata. The user also
 * understands that UCAR/Unidata is not obligated to provide the user with
 * any support, consulting, training or assistance of any kind with regard
 * to the use, operation and performance of this software nor to provide
 * the user with any updates, revisions, new versions or "bug fixes."
 *
 * THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
 */

// $Id: GridHorizCoordSys.java 70 2006-07-13 15:16:05Z caron $

package ucar.nc2.iosp.grid;


import ucar.ma2.Array;
import ucar.ma2.DataType;

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

import ucar.unidata.geoloc.*;
import ucar.unidata.geoloc.projection.*;
import ucar.unidata.util.GaussianLatitudes;
import ucar.unidata.util.StringUtil;
import ucar.grid.GridTableLookup;
import ucar.grid.GridDefRecord;
import ucar.grib.grib2.Grib2GridTableLookup;
import ucar.grib.grib1.Grib1GridTableLookup;

import java.util.*;


/**
 * A horizontal coordinate system created from a GridDefRecord
 * <p/>
 * <p/>
 * <p> Note on "false_easting" and "fale_northing" projection parameters:
 * <ul><li>false_easting(northing) = The value added to all x (y) values in the rectangular coordinates for a map projection.
 * This value frequently is assigned to eliminate negative numbers.
 * Expressed in the unit of measure identified in Planar Coordinate Units.
 * <li>We dont currently use, assuming that the x and y are just fine as negetive numbers.
 *
 * @author caron
 * @version $Revision: 70 $ $Date: 2006-07-13 15:16:05Z $
 */
public class GridHorizCoordSys {

  /**
   * lookup table
   */
  private GridTableLookup lookup;

  /**
   * the grid definition object
   */
  private GridDefRecord gds;

  /**
   * group for this system
   */
  private Group g;

  /**
   * grid name, shape name and id
   */
  private String grid_name, shape_name, id;

  /**
   * flags
   */
  private boolean isLatLon = true,
      isGaussian = false;

  /**
   * GridVariables that have this GridHorizCoordSys
   */
  Map<String,GridVariable> varHash = new HashMap<String,GridVariable>(200);

  /**
   * List of GridVariable, sorted by product desc
   */
  Map<String, List<GridVariable>> productHash = new HashMap<String, List<GridVariable>>(100);

  /**
   * GridVertCoordSys
   */
  //HashMap vcsHash = new HashMap(30); // GridVertCoordSys

  /**
   * startx, starty
   */
  private double startx, starty;
  private double dx, dy;
  /**
   * projection
   */
  private ProjectionImpl proj;

  /**
   * list of attributes
   */ //TODO: generics
  private ArrayList attributes = new ArrayList();

  /**
   * Create a new GridHorizCoordSys with the grid definition and lookup
   *
   * @param gds    grid definition
   * @param lookup lookup table for names
   * @param g      Group for this coord system
   */
  GridHorizCoordSys(GridDefRecord gds, GridTableLookup lookup, Group g) {
    this.gds = gds;
    //this.dx = gds.getDouble(GridDefRecord.DX) * .001;
    //this.dy = gds.getDouble(GridDefRecord.DY) * .001;
    this.lookup = lookup;
    this.g = g;

    this.grid_name = AbstractIOServiceProvider.createValidNetcdfObjectName(
        lookup.getGridName(gds));
    this.shape_name = lookup.getShapeName(gds);
    this.g = g;
    isLatLon = lookup.isLatLon(gds);
    grid_name = StringUtil.replace(grid_name, ' ', "_");
    id = (g == null)
        ? grid_name
        : g.getName();

    if (isLatLon
        && (lookup.getProjectionType(gds) == GridTableLookup.GaussianLatLon)) {
      isGaussian = true;

      double np = gds.getDouble( GridDefRecord.NP ); // # lats between pole and equator  (octet 26/27)
      np = (Double.isNaN(np)) ? 90 : np;
      //System.out.println( "np ="+np );
      gds.addParam(GridDefRecord.DY, String.valueOf(np));  // fake - need to get actual gaussian calculation here
    }
  }

  /**
   * Get the ID
   *
   * @return the ID
   */
  String getID() {
    return id;
  }  // unique within the file

  /**
   * Get the grid name
   *
   * @return the grid name
   */
  String getGridName() {
    return grid_name;
  }  // used in CF-1 attributes

  /**
   * Get the group
   *
   * @return the group
   */
  Group getGroup() {
    return g;
  }

  /**
   * Is this a lat/lon grid
   *
   * @return true if is lat/lon
   */
  boolean isLatLon() {
    return isLatLon;
  }

  /**
   * Get the number of x points
   *
   * @return the number of x points
   */
  int getNx() {
    return gds.getInt(GridDefRecord.NX);
  }

  /**
   * Get the number of Y points
   *
   * @return the number of Y points
   */
  int getNy() {
    return gds.getInt(GridDefRecord.NY);
  }

  /**
   * Get the X spacing in kilometers
   *
   * @return the X spacing in kilometers
   */
  private double getDxInKm() {
    return getGridSpacingInKm(GridDefRecord.DX);
    //return dx;
  }

  /**
   * Get the Y spacing in kilometers
   *
   * @return the Y spacing in kilometers
   */
  private double getDyInKm() {
    return getGridSpacingInKm(GridDefRecord.DY);
    //return dy;
  }

  /**
   * Get the grid spacing in kilometers
   *
   * @return the grid spacing in kilometers
   */
  private double getGridSpacingInKm(String type) {
    double value = gds.getDouble(type);
    if (Double.isNaN(value)) return value;
    String gridUnit = gds.getParam(GridDefRecord.GRID_UNITS);
    SimpleUnit unit = null;

    if (gridUnit == null || gridUnit.equals("")) {
      unit = SimpleUnit.meterUnit;
    } else {
      unit = SimpleUnit.factory(gridUnit);
    }
    if (unit != null && SimpleUnit.isCompatible(unit.getUnitString(), "km")) {
      value =  unit.convertTo(value, SimpleUnit.kmUnit);
    }
    return value;

  }

  /**
   * Add the dimensions associated with this coord sys to the netCDF file
   *
   * @param ncfile netCDF file to add to
   */
  void addDimensionsToNetcdfFile(NetcdfFile ncfile) {

    if (isLatLon) {
      ncfile.addDimension(g, new Dimension("lat", gds.getInt(GridDefRecord.NY), true));
      ncfile.addDimension(g, new Dimension("lon", gds.getInt(GridDefRecord.NX), true));
    } else {
      ncfile.addDimension(g, new Dimension("y", gds.getInt(GridDefRecord.NY), true));
      ncfile.addDimension(g, new Dimension("x", gds.getInt(GridDefRecord.NX), true));
    }
  }

  /**
   * Add the variables to the netCDF file
   *
   * @param ncfile the netCDF file
   */
  void addToNetcdfFile(NetcdfFile ncfile) {

    if (isLatLon) {
      double dy = (gds.getDouble(GridDefRecord.LA2) < gds.getDouble(GridDefRecord.LA1))
          ? -gds.getDouble(GridDefRecord.DY) : gds.getDouble(GridDefRecord.DY);
      if (isGaussian) {
        addGaussianLatAxis(ncfile, "lat", "degrees_north",
            "latitude coordinate", "latitude", AxisType.Lat);
      } else {
        addCoordAxis(ncfile, "lat", gds.getInt(GridDefRecord.NY),
            gds.getDouble(GridDefRecord.LA1), dy, "degrees_north",
            "latitude coordinate", "latitude", AxisType.Lat);
      }
      addCoordAxis(ncfile, "lon", gds.getInt(GridDefRecord.NX),
          gds.getDouble(GridDefRecord.LO1), gds.getDouble(GridDefRecord.DX), "degrees_east",
          "longitude coordinate", "longitude", AxisType.Lon);
      addCoordSystemVariable(ncfile, "latLonCoordSys", "time lat lon");

    } else {
      makeProjection(ncfile);
      double[] yData, xData; //TODO: can these be removed
      if (lookup.getProjectionType(gds) == GridTableLookup.RotatedLatLon) {
        double dy = (gds.getDouble("La2") < gds.getDouble(GridDefRecord.LA1)
            ? -gds.getDouble(GridDefRecord.DY) : gds.getDouble(GridDefRecord.DY));

        yData = addCoordAxis(ncfile, "y", gds.getInt(GridDefRecord.NY),
            gds.getDouble(GridDefRecord.LA1), dy, "degrees",
            "y coordinate of projection", "projection_y_coordinate", AxisType.GeoY);
        xData = addCoordAxis(ncfile, "x", gds.getInt(GridDefRecord.NX),
            gds.getDouble(GridDefRecord.LO1), gds.getDouble(GridDefRecord.DX), "degrees",
            "x coordinate of projection", "projection_x_coordinate", AxisType.GeoX);
      } else {
        yData = addCoordAxis(ncfile, "y", gds.getInt(GridDefRecord.NY),
            starty, getDyInKm(), "km", "y coordinate of projection",
            "projection_y_coordinate", AxisType.GeoY);
        xData = addCoordAxis(ncfile, "x", gds.getInt(GridDefRecord.NX),
            startx, getDxInKm(), "km", "x coordinate of projection",
            "projection_x_coordinate", AxisType.GeoX);
      }
    }
  }

  /**
   * Add a coordinate axis
   *
   * @param ncfile        the netCDF file to add to
   * @param name          name of the axis
   * @param n             number of points
   * @param start         starting value
   * @param incr          increment between points
   * @param units         units
   * @param desc          description
   * @param standard_name standard name
   * @param axis          axis type
   * @return the coordinate values
   */
  private double[] addCoordAxis(NetcdfFile ncfile, String name, int n,
                                double start, double incr, String units,
                                String desc, String standard_name,
                                AxisType axis) {

    // ncfile.addDimension(g, new Dimension(name, n, true));

    Variable v = new Variable(ncfile, g, null, name);
    v.setDataType(DataType.DOUBLE);
    v.setDimensions(name);

    // create the data
    double[] data = new double[n];
    for (int i = 0; i < n; i++) {
      data[i] = start + incr * i;
    }
    Array dataArray = Array.factory(DataType.DOUBLE, new int[]{n}, data);
    v.setCachedData(dataArray, false);

    v.addAttribute(new Attribute("units", units));
    v.addAttribute(new Attribute("long_name", desc));
    v.addAttribute(new Attribute("standard_name", standard_name));
    v.addAttribute(new Attribute("grid_spacing", incr + " " + units));
    v.addAttribute(new Attribute(_Coordinate.AxisType, axis.toString()));

    ncfile.addVariable(g, v);
    return data;
  }

  /**
   * Add a gaussian lat axis
   *
   * @param ncfile        netCDF file to add to
   * @param name          name of the axis
   * @param units         units
   * @param desc          description
   * @param standard_name standard name
   * @param axis          axis type
   * @return the values
   */
  private double[] addGaussianLatAxis(NetcdfFile ncfile, String name,
    String units, String desc, String standard_name, AxisType axis) {

    double np = gds.getDouble( GridDefRecord.NUMBERPARALLELS );
    if (Double.isNaN(np)) {
      throw new IllegalArgumentException(
          "Gaussian LAt/Lon grid must have NumberParallels parameter");
    }
    double startLat = gds.getDouble(GridDefRecord.LA1);
    double endLat = gds.getDouble(GridDefRecord.LA2);

    int nlats = (int) (2 * np);
    GaussianLatitudes gaussLats = new GaussianLatitudes(nlats);

    int bestStartIndex = 0, bestEndIndex = 0;
    double bestStartDiff = Double.MAX_VALUE;
    double bestEndDiff = Double.MAX_VALUE;
    for (int i = 0; i < nlats; i++) {
      double diff = Math.abs(gaussLats.latd[i] - startLat);
      if (diff < bestStartDiff) {
        bestStartDiff = diff;
        bestStartIndex = i;
      }
      diff = Math.abs(gaussLats.latd[i] - endLat);
      if (diff < bestEndDiff) {
        bestEndDiff = diff;
        bestEndIndex = i;
      }
    }
    assert Math.abs(bestEndIndex - bestStartIndex + 1) == gds.getInt(GridDefRecord.NY);
    boolean goesUp = bestEndIndex > bestStartIndex;

    Variable v = new Variable(ncfile, g, null, name);
    v.setDataType(DataType.DOUBLE);
    v.setDimensions(name);

    // create the data
    int n = (int) gds.getInt(GridDefRecord.NY);
    int useIndex = bestStartIndex;
    double[] data = new double[n];
    double[] gaussw = new double[n];
    for (int i = 0; i < n; i++) {
      data[i] = gaussLats.latd[useIndex];
      gaussw[i] = gaussLats.gaussw[useIndex];
      if (goesUp) {
        useIndex++;
      } else {
        useIndex--;
      }
    }
    Array dataArray = Array.factory(DataType.DOUBLE, new int[]{n}, data);
    v.setCachedData(dataArray, false);

    v.addAttribute(new Attribute("units", units));
    v.addAttribute(new Attribute("long_name", desc));
    v.addAttribute(new Attribute("standard_name", standard_name));
    v.addAttribute(new Attribute("weights", "gaussw"));
    v.addAttribute(new Attribute(_Coordinate.AxisType, axis.toString()));
    ncfile.addVariable(g, v);

    v = new Variable(ncfile, g, null, "gaussw");
    v.setDataType(DataType.DOUBLE);
    v.setDimensions(name);
    v.addAttribute(new Attribute("long_name",
        "gaussian weights (unnormalized)"));
    dataArray = Array.factory(DataType.DOUBLE,
        new int[]{n}, gaussw);
    v.setCachedData(dataArray, false);
    ncfile.addVariable(g, v);

    return data;
  }

  /**
   * Make a projection and add it to the netCDF file
   *
   * @param ncfile netCDF file
   */
  private void makeProjection(NetcdfFile ncfile) {
    switch (lookup.getProjectionType(gds)) {

      case GridTableLookup.RotatedLatLon:
        makeRotatedLatLon(ncfile);
        break;

      case GridTableLookup.PolarStereographic:
        makePS();
        break;

      case GridTableLookup.LambertConformal:
        makeLC();
        break;

      case GridTableLookup.Mercator:
        makeMercator();
        break;

      case GridTableLookup.Orthographic:
        makeSpaceViewOrOthographic();
        break;

      default:
        throw new UnsupportedOperationException("unknown projection = "
            + gds.getInt(GridDefRecord.GRID_TYPE));
    }

    Variable v = new Variable(ncfile, g, null, grid_name);
    v.setDataType(DataType.CHAR);
    v.setDimensions(new ArrayList());  // scalar
    char[] data = new char[]{'d'};
    Array dataArray = Array.factory(DataType.CHAR, new int[0], data);
    v.setCachedData(dataArray, false);

    for (int i = 0; i < attributes.size(); i++) {
      Attribute att = (Attribute) attributes.get(i);
      v.addAttribute(att);
    }

    v.addAttribute(new Attribute("earth_shape", shape_name));
    if (gds.getInt(GridDefRecord.GRID_SHAPE_CODE) == 1) {
      // have to check both because Grib1 and Grib2 used different names
      double radius_spherical_earth = gds.getDouble(GridDefRecord.RADIUS_SPHERICAL_EARTH);
      if (Double.isNaN( radius_spherical_earth ))
         radius_spherical_earth = gds.getDouble( "radius_spherical_earth" );

      v.addAttribute(new Attribute("spherical_earth_radius_meters",
              new Double(radius_spherical_earth)));
    }
    addGDSparams(v);
    ncfile.addVariable(g, v);
  }

  /**
   * Add the GDS params to the variable as attributes
   *
   * @param v the GDS params.
   */
  private void addGDSparams(Variable v) {
    // add all the gds parameters
    java.util.Set keys = gds.getKeys();
    ArrayList<String> keyList = new ArrayList<String>(keys);
    Collections.sort(keyList);
    String pre =
        (( lookup instanceof Grib2GridTableLookup) ||
            ( lookup instanceof Grib1GridTableLookup) ) ? "GRIB" : "GDS";

    for (String key : keyList) {
      String name =
          AbstractIOServiceProvider.createValidNetcdfObjectName(pre +"_param_" + key);
          //AbstractIOServiceProvider.createValidNetcdfObjectName("GDS_param_" + key);

      String vals = gds.getParam(key);
      try {
        int vali = Integer.parseInt(vals);
        v.addAttribute(new Attribute(name, new Integer(vali)));
      } catch (Exception e) {
        try {
          double vald = Double.parseDouble(vals);
          v.addAttribute(new Attribute(name, new Double(vald)));
        } catch (Exception e2) {
          v.addAttribute(new Attribute(name, vals));
        }
      }
    }
  }

  /**
   * Add coordinate system variable
   *
   * @param ncfile netCDF file<commit_msg> grib refactor backwards compatiability with older gbx.s<commit_after>/*
 * Copyright 1998-2009 University Corporation for Atmospheric Research/Unidata
 *
 * Portions of this software were developed by the Unidata Program at the
 * University Corporation for Atmospheric Research.
 *
 * Access and use of this software shall impose the following obligations
 * and understandings on the user. The user is granted the right, without
 * any fee or cost, to use, copy, modify, alter, enhance and distribute
 * this software, and any derivative works thereof, and its supporting
 * documentation for any purpose whatsoever, provided that this entire
 * notice appears in all copies of the software, derivative works and
 * supporting documentation.  Further, UCAR requests that the user credit
 * UCAR/Unidata in any publications that result from the use of this
 * software or in any product that includes this software. The names UCAR
 * and/or Unidata, however, may not be used in any advertising or publicity
 * to endorse or promote any products or commercial entity unless specific
 * written permission is obtained from UCAR/Unidata. The user also
 * understands that UCAR/Unidata is not obligated to provide the user with
 * any support, consulting, training or assistance of any kind with regard
 * to the use, operation and performance of this software nor to provide
 * the user with any updates, revisions, new versions or "bug fixes."
 *
 * THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
 */

// $Id: GridHorizCoordSys.java 70 2006-07-13 15:16:05Z caron $

package ucar.nc2.iosp.grid;


import ucar.ma2.Array;
import ucar.ma2.DataType;

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

import ucar.unidata.geoloc.*;
import ucar.unidata.geoloc.projection.*;
import ucar.unidata.util.GaussianLatitudes;
import ucar.unidata.util.StringUtil;
import ucar.grid.GridTableLookup;
import ucar.grid.GridDefRecord;
import ucar.grib.grib2.Grib2GridTableLookup;
import ucar.grib.grib1.Grib1GridTableLookup;

import java.util.*;


/**
 * A horizontal coordinate system created from a GridDefRecord
 * <p/>
 * <p/>
 * <p> Note on "false_easting" and "fale_northing" projection parameters:
 * <ul><li>false_easting(northing) = The value added to all x (y) values in the rectangular coordinates for a map projection.
 * This value frequently is assigned to eliminate negative numbers.
 * Expressed in the unit of measure identified in Planar Coordinate Units.
 * <li>We dont currently use, assuming that the x and y are just fine as negetive numbers.
 *
 * @author caron
 * @version $Revision: 70 $ $Date: 2006-07-13 15:16:05Z $
 */
public class GridHorizCoordSys {

  /**
   * lookup table
   */
  private GridTableLookup lookup;

  /**
   * the grid definition object
   */
  private GridDefRecord gds;

  /**
   * group for this system
   */
  private Group g;

  /**
   * grid name, shape name and id
   */
  private String grid_name, shape_name, id;

  /**
   * flags
   */
  private boolean isLatLon = true,
      isGaussian = false;

  /**
   * GridVariables that have this GridHorizCoordSys
   */
  Map<String,GridVariable> varHash = new HashMap<String,GridVariable>(200);

  /**
   * List of GridVariable, sorted by product desc
   */
  Map<String, List<GridVariable>> productHash = new HashMap<String, List<GridVariable>>(100);

  /**
   * GridVertCoordSys
   */
  //HashMap vcsHash = new HashMap(30); // GridVertCoordSys

  /**
   * startx, starty
   */
  private double startx, starty;
  /**
   * projection
   */
  private ProjectionImpl proj;

  /**
   * list of attributes
   */ //TODO: generics
  private ArrayList attributes = new ArrayList();

  /**
   * Create a new GridHorizCoordSys with the grid definition and lookup
   *
   * @param gds    grid definition
   * @param lookup lookup table for names
   * @param g      Group for this coord system
   */
  GridHorizCoordSys(GridDefRecord gds, GridTableLookup lookup, Group g) {
    this.gds = gds;
    //this.dx = gds.getDouble(GridDefRecord.DX) * .001;
    //this.dy = gds.getDouble(GridDefRecord.DY) * .001;
    this.lookup = lookup;
    this.g = g;

    this.grid_name = AbstractIOServiceProvider.createValidNetcdfObjectName(
        lookup.getGridName(gds));
    this.shape_name = lookup.getShapeName(gds);
    this.g = g;
    isLatLon = lookup.isLatLon(gds);
    grid_name = StringUtil.replace(grid_name, ' ', "_");
    id = (g == null)
        ? grid_name
        : g.getName();

    if (isLatLon
        && (lookup.getProjectionType(gds) == GridTableLookup.GaussianLatLon)) {
      isGaussian = true;

      double np = gds.getDouble( GridDefRecord.NP ); // # lats between pole and equator  (octet 26/27)
      np = (Double.isNaN(np)) ? 90 : np;
      //System.out.println( "np ="+np );
      gds.addParam(GridDefRecord.DY, String.valueOf(np));  // fake - need to get actual gaussian calculation here
    }
  }

  /**
   * Get the ID
   *
   * @return the ID
   */
  String getID() {
    return id;
  }  // unique within the file

  /**
   * Get the grid name
   *
   * @return the grid name
   */
  String getGridName() {
    return grid_name;
  }  // used in CF-1 attributes

  /**
   * Get the group
   *
   * @return the group
   */
  Group getGroup() {
    return g;
  }

  /**
   * Is this a lat/lon grid
   *
   * @return true if is lat/lon
   */
  boolean isLatLon() {
    return isLatLon;
  }

  /**
   * Get the number of x points
   *
   * @return the number of x points
   */
  int getNx() {
    return gds.getInt(GridDefRecord.NX);
  }

  /**
   * Get the number of Y points
   *
   * @return the number of Y points
   */
  int getNy() {
    return gds.getInt(GridDefRecord.NY);
  }

  /**
   * Get the X spacing in kilometers
   *
   * @return the X spacing in kilometers
   */
  private double getDxInKm() {
    return getGridSpacingInKm(GridDefRecord.DX);
    //return dx;
  }

  /**
   * Get the Y spacing in kilometers
   *
   * @return the Y spacing in kilometers
   */
  private double getDyInKm() {
    return getGridSpacingInKm(GridDefRecord.DY);
    //return dy;
  }

  /**
   * Get the grid spacing in kilometers
   *
   * @return the grid spacing in kilometers
   */
  private double getGridSpacingInKm(String type) {
    double value = gds.getDouble(type);
    if (Double.isNaN(value)) return value;
    String gridUnit = gds.getParam(GridDefRecord.GRID_UNITS);
    SimpleUnit unit = null;

    if (gridUnit == null || gridUnit.equals("")) {
      unit = SimpleUnit.meterUnit;
    } else {
      unit = SimpleUnit.factory(gridUnit);
    }
    if (unit != null && SimpleUnit.isCompatible(unit.getUnitString(), "km")) {
      value =  unit.convertTo(value, SimpleUnit.kmUnit);
    }
    return value;

  }

  /**
   * Add the dimensions associated with this coord sys to the netCDF file
   *
   * @param ncfile netCDF file to add to
   */
  void addDimensionsToNetcdfFile(NetcdfFile ncfile) {

    if (isLatLon) {
      ncfile.addDimension(g, new Dimension("lat", gds.getInt(GridDefRecord.NY), true));
      ncfile.addDimension(g, new Dimension("lon", gds.getInt(GridDefRecord.NX), true));
    } else {
      ncfile.addDimension(g, new Dimension("y", gds.getInt(GridDefRecord.NY), true));
      ncfile.addDimension(g, new Dimension("x", gds.getInt(GridDefRecord.NX), true));
    }
  }

  /**
   * Add the variables to the netCDF file
   *
   * @param ncfile the netCDF file
   */
  void addToNetcdfFile(NetcdfFile ncfile) {

    if (isLatLon) {
      double dy = (gds.getDouble(GridDefRecord.LA2) < gds.getDouble(GridDefRecord.LA1))
          ? -gds.getDouble(GridDefRecord.DY) : gds.getDouble(GridDefRecord.DY);
      if (isGaussian) {
        addGaussianLatAxis(ncfile, "lat", "degrees_north",
            "latitude coordinate", "latitude", AxisType.Lat);
      } else {
        addCoordAxis(ncfile, "lat", gds.getInt(GridDefRecord.NY),
            gds.getDouble(GridDefRecord.LA1), dy, "degrees_north",
            "latitude coordinate", "latitude", AxisType.Lat);
      }
      addCoordAxis(ncfile, "lon", gds.getInt(GridDefRecord.NX),
          gds.getDouble(GridDefRecord.LO1), gds.getDouble(GridDefRecord.DX), "degrees_east",
          "longitude coordinate", "longitude", AxisType.Lon);
      addCoordSystemVariable(ncfile, "latLonCoordSys", "time lat lon");

    } else {
      makeProjection(ncfile);
      double[] yData, xData; //TODO: can these be removed
      if (lookup.getProjectionType(gds) == GridTableLookup.RotatedLatLon) {
        double dy = (gds.getDouble("La2") < gds.getDouble(GridDefRecord.LA1)
            ? -gds.getDouble(GridDefRecord.DY) : gds.getDouble(GridDefRecord.DY));

        yData = addCoordAxis(ncfile, "y", gds.getInt(GridDefRecord.NY),
            gds.getDouble(GridDefRecord.LA1), dy, "degrees",
            "y coordinate of projection", "projection_y_coordinate", AxisType.GeoY);
        xData = addCoordAxis(ncfile, "x", gds.getInt(GridDefRecord.NX),
            gds.getDouble(GridDefRecord.LO1), gds.getDouble(GridDefRecord.DX), "degrees",
            "x coordinate of projection", "projection_x_coordinate", AxisType.GeoX);
      } else {
        yData = addCoordAxis(ncfile, "y", gds.getInt(GridDefRecord.NY),
            starty, getDyInKm(), "km", "y coordinate of projection",
            "projection_y_coordinate", AxisType.GeoY);
        xData = addCoordAxis(ncfile, "x", gds.getInt(GridDefRecord.NX),
            startx, getDxInKm(), "km", "x coordinate of projection",
            "projection_x_coordinate", AxisType.GeoX);
      }
    }
  }

  /**
   * Add a coordinate axis
   *
   * @param ncfile        the netCDF file to add to
   * @param name          name of the axis
   * @param n             number of points
   * @param start         starting value
   * @param incr          increment between points
   * @param units         units
   * @param desc          description
   * @param standard_name standard name
   * @param axis          axis type
   * @return the coordinate values
   */
  private double[] addCoordAxis(NetcdfFile ncfile, String name, int n,
                                double start, double incr, String units,
                                String desc, String standard_name,
                                AxisType axis) {

    // ncfile.addDimension(g, new Dimension(name, n, true));

    Variable v = new Variable(ncfile, g, null, name);
    v.setDataType(DataType.DOUBLE);
    v.setDimensions(name);

    // create the data
    double[] data = new double[n];
    for (int i = 0; i < n; i++) {
      data[i] = start + incr * i;
    }
    Array dataArray = Array.factory(DataType.DOUBLE, new int[]{n}, data);
    v.setCachedData(dataArray, false);

    v.addAttribute(new Attribute("units", units));
    v.addAttribute(new Attribute("long_name", desc));
    v.addAttribute(new Attribute("standard_name", standard_name));
    v.addAttribute(new Attribute("grid_spacing", incr + " " + units));
    v.addAttribute(new Attribute(_Coordinate.AxisType, axis.toString()));

    ncfile.addVariable(g, v);
    return data;
  }

  /**
   * Add a gaussian lat axis
   *
   * @param ncfile        netCDF file to add to
   * @param name          name of the axis
   * @param units         units
   * @param desc          description
   * @param standard_name standard name
   * @param axis          axis type
   * @return the values
   */
  private double[] addGaussianLatAxis(NetcdfFile ncfile, String name,
    String units, String desc, String standard_name, AxisType axis) {

    double np = gds.getDouble( GridDefRecord.NUMBERPARALLELS );
    if (Double.isNaN(np)) {
      throw new IllegalArgumentException(
          "Gaussian LAt/Lon grid must have NumberParallels parameter");
    }
    double startLat = gds.getDouble(GridDefRecord.LA1);
    double endLat = gds.getDouble(GridDefRecord.LA2);

    int nlats = (int) (2 * np);
    GaussianLatitudes gaussLats = new GaussianLatitudes(nlats);

    int bestStartIndex = 0, bestEndIndex = 0;
    double bestStartDiff = Double.MAX_VALUE;
    double bestEndDiff = Double.MAX_VALUE;
    for (int i = 0; i < nlats; i++) {
      double diff = Math.abs(gaussLats.latd[i] - startLat);
      if (diff < bestStartDiff) {
        bestStartDiff = diff;
        bestStartIndex = i;
      }
      diff = Math.abs(gaussLats.latd[i] - endLat);
      if (diff < bestEndDiff) {
        bestEndDiff = diff;
        bestEndIndex = i;
      }
    }
    assert Math.abs(bestEndIndex - bestStartIndex + 1) == gds.getInt(GridDefRecord.NY);
    boolean goesUp = bestEndIndex > bestStartIndex;

    Variable v = new Variable(ncfile, g, null, name);
    v.setDataType(DataType.DOUBLE);
    v.setDimensions(name);

    // create the data
    int n = (int) gds.getInt(GridDefRecord.NY);
    int useIndex = bestStartIndex;
    double[] data = new double[n];
    double[] gaussw = new double[n];
    for (int i = 0; i < n; i++) {
      data[i] = gaussLats.latd[useIndex];
      gaussw[i] = gaussLats.gaussw[useIndex];
      if (goesUp) {
        useIndex++;
      } else {
        useIndex--;
      }
    }
    Array dataArray = Array.factory(DataType.DOUBLE, new int[]{n}, data);
    v.setCachedData(dataArray, false);

    v.addAttribute(new Attribute("units", units));
    v.addAttribute(new Attribute("long_name", desc));
    v.addAttribute(new Attribute("standard_name", standard_name));
    v.addAttribute(new Attribute("weights", "gaussw"));
    v.addAttribute(new Attribute(_Coordinate.AxisType, axis.toString()));
    ncfile.addVariable(g, v);

    v = new Variable(ncfile, g, null, "gaussw");
    v.setDataType(DataType.DOUBLE);
    v.setDimensions(name);
    v.addAttribute(new Attribute("long_name",
        "gaussian weights (unnormalized)"));
    dataArray = Array.factory(DataType.DOUBLE,
        new int[]{n}, gaussw);
    v.setCachedData(dataArray, false);
    ncfile.addVariable(g, v);

    return data;
  }

  /**
   * Make a projection and add it to the netCDF file
   *
   * @param ncfile netCDF file
   */
  private void makeProjection(NetcdfFile ncfile) {
    switch (lookup.getProjectionType(gds)) {

      case GridTableLookup.RotatedLatLon:
        makeRotatedLatLon(ncfile);
        break;

      case GridTableLookup.PolarStereographic:
        makePS();
        break;

      case GridTableLookup.LambertConformal:
        makeLC();
        break;

      case GridTableLookup.Mercator:
        makeMercator();
        break;

      case GridTableLookup.Orthographic:
        makeSpaceViewOrOthographic();
        break;

      default:
        throw new UnsupportedOperationException("unknown projection = "
            + gds.getInt(GridDefRecord.GRID_TYPE));
    }

    Variable v = new Variable(ncfile, g, null, grid_name);
    v.setDataType(DataType.CHAR);
    v.setDimensions(new ArrayList());  // scalar
    char[] data = new char[]{'d'};
    Array dataArray = Array.factory(DataType.CHAR, new int[0], data);
    v.setCachedData(dataArray, false);

    for (int i = 0; i < attributes.size(); i++) {
      Attribute att = (Attribute) attributes.get(i);
      v.addAttribute(att);
    }

    v.addAttribute(new Attribute("earth_shape", shape_name));
    if (gds.getInt(GridDefRecord.GRID_SHAPE_CODE) == 1) {
      // have to check both because Grib1 and Grib2 used different names
      double radius_spherical_earth = gds.getDouble(GridDefRecord.RADIUS_SPHERICAL_EARTH);
      if (Double.isNaN( radius_spherical_earth ))
         radius_spherical_earth = gds.getDouble( "radius_spherical_earth" );

      v.addAttribute(new Attribute("spherical_earth_radius_meters",
              new Double(radius_spherical_earth)));
    }
    addGDSparams(v);
    ncfile.addVariable(g, v);
  }

  /**
   * Add the GDS params to the variable as attributes
   *
   * @param v the GDS params.
   */
  private void addGDSparams(Variable v) {
    // add all the gds parameters
    java.util.Set keys = gds.getKeys();
    ArrayList<String> keyList = new ArrayList<String>(keys);
    Collections.sort(keyList);
    String pre =
        (( lookup instanceof Grib2GridTableLookup) ||
            ( lookup instanceof Grib1GridTableLookup) ) ? "GRIB" : "GDS";

    for (String key : keyList) {
      String name =
          AbstractIOServiceProvider.createValidNetcdfObjectName(pre +"_param_" + key);

      String vals = gds.getParam(key);
      try {
        int vali = Integer.parseInt(vals);
        v.addAttribute(new Attribute(name, new Integer(vali)));
      } catch (Exception e) {
        try {
          double vald = Double.parseDouble(vals);
          v.addAttribute(new Attribute(name, new Double(vald)));
        } catch (Exception e2) {
          v.addAttribute(new Attribute(name, vals));
        }
      }
    }
  }

  /**
   * Add coordinate system variable
   *
   * @param ncfile netCDF file
{'fraction_non_alphanumeric': 0.11055189669552214, 'fraction_numerical': 0.005636881309136924, 'mean_word_length': 2.8960224089635855, 'pattern_counts': {'":': 0, '<': 47, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4738045', 'n_tokens_mistral': 11274, 'n_tokens_neox': 10918, 'n_words': 2976}