// // GRIDnav.java // /* This source file is part of the edu.wisc.ssec.mcidas package and is Copyright (C) 1998 - 2017 by Tom Whittaker, Tommy Jasmin, Tom Rink, Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden and others. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ package edu.wisc.ssec.mcidas; /** * GRIDnav is the class for handling the navigation of McIDAS grids. * It is basically a Java version of GRDDEF.FOR. * @see <A HREF="http://www.ssec.wisc.edu/mcidas/doc/prog_man.html"> * McIDAS Programmer's Manual</A> * * @author Don Murray * */ public class GRIDnav implements java.io.Serializable { static final long serialVersionUID = 8741895066356394200L; /** Navigation type for pseudo-mercator grids */ final int PSEUDO_MERCATOR = 1; /** Navigation type for polar stero or lambert conformal conic grids */ final int PS_OR_LAMBERT_CONIC = 2; /** Navigation type for equidistant grids */ final int EQUIDISTANT = 3; /** Navigation type for pseudo-mercator (general case) grids */ final int PSEUDO_MERCATOR_GENERAL = 4; final int NO_NAV = 5; /** Navigation type for lambert conformal tangent grids */ final int LAMBERT_CONFORMAL_TANGENT = 6; final double EARTH_RADIUS = 6371.23; final double xrad = Math.PI/180.; /** "Row" index in row/column array */ public final int indexRow=1; /** "Column" index in row/column array */ public final int indexCol=0; /** "Latitude" index in latitude/longitude array */ public final int indexLat=0; /** "Longitude" index in latitude/longitude array */ public final int indexLon=1; /* Default start for grid calculations in McIDAS is (1,1) */ private int startRow = 1; private int startColumn = 1; /* (1,1) is located in the upper left. If lower left, it's flipped */ private boolean isRowFlipped = false; private double rowOffset = 0.0; /* common calculation variables */ private int navType; private double xnr; // number of rows private double xnc; // number of columns private double xnrow; // number of rows for calculations private double xncol; // number of columns for calculations private boolean wierd = false; // JTYP = 1, navType > 10 /* Merc and pseudo_merc parameters */ private double glamx; // max latitude private double glomx; // max longitude private double ginct; // grid increment in latitude private double gincn; // grid increment in longitude /* PS and CONF projection parameters */ private double xrowi; // row # of North Pole*10000 private double xcoli; // column # of North Pole* 10000 private double xqlon; // longitude parallel to columns private double xspace; // column spacing at standard latitude private double xh; // private double xfac; // private double xblat; // /* Equidistant params */ private double xrot; // rotation angle private double yspace; private double xblon; /** * Construct a new GRIDnav from a grid directory block * @param gridDirBlock grid header block * @throws McIDASException illegal grid header */ public GRIDnav(int[] gridDirBlock) throws McIDASException { if (gridDirBlock.length != GridDirectory.DIRSIZE) throw new McIDASException("Directory is not the right size"); int gridType = gridDirBlock[GridDirectory.NAV_BLOCK_INDEX]; navType = gridType%10; wierd = gridType/10 == 1; xnr = gridDirBlock[GridDirectory.ROWS_INDEX]; xnc = gridDirBlock[GridDirectory.COLS_INDEX]; xnrow = xnr; xncol = xnc; switch(navType) { case PSEUDO_MERCATOR: case PSEUDO_MERCATOR_GENERAL: glamx=gridDirBlock[34]/10000.; glomx=gridDirBlock[35]/10000.; ginct=gridDirBlock[38]/10000.; gincn= (navType == PSEUDO_MERCATOR_GENERAL) ? gridDirBlock[39]/10000. : ginct; if (wierd) { double x = xnr; xnr = xnc; xnc = x; } break; case PS_OR_LAMBERT_CONIC: xrowi = gridDirBlock[34]/10000.; // row # of the North pole*10000 xcoli = gridDirBlock[35]/10000.; // col # of the North pole*10000 xspace = gridDirBlock[36]/1000.; // column spacing at standard lat (m) xqlon = gridDirBlock[37]/10000.; // lon parallel to cols (deg*10000) double xt1 = gridDirBlock[38]/10000.; // first standard lat double xt2 = gridDirBlock[39]/10000.; // second standard lat xh = (xt1 >= 0) ? 1. : -1.; xt1 =(90.-xh*xt1)*xrad; xt2 =(90.-xh*xt2)*xrad; xfac =1.0; if (xt1 != xt2) xfac = (Math.log(Math.sin(xt1))-Math.log(Math.sin(xt2)))/ (Math.log(Math.tan(.5*xt1))-Math.log(Math.tan(.5*xt2))); xfac = 1.0/xfac; xblat = 6370. * Math.sin(xt1)/ (xspace*xfac*(Math.pow(Math.tan(xt1*.5),xfac))); if (wierd) { double x=xnr; xnr=xnc; xnc=x; x=xcoli; xcoli=xrowi; xrowi=xnr-x+1.0; xqlon=xqlon+90.; } break; case EQUIDISTANT: xrowi = 1.; xcoli = 1.; glamx = gridDirBlock[34]/10000.; // lat of (1,1) degrees*10000 glomx = gridDirBlock[35]/10000.; // lon of (1,1) degrees*10000 xrot = -xrad*gridDirBlock[36]/10000.; // clockwise rotation of col 1 xspace = gridDirBlock[37]/1000.; // column spacing yspace = gridDirBlock[38]/1000.; // row spacing xblat = EARTH_RADIUS*xrad/yspace; xblon = EARTH_RADIUS*xrad/xspace; if (wierd) { double x = xnr; xnr = xnc; xnc = x; } break; case LAMBERT_CONFORMAL_TANGENT: xrowi = gridDirBlock[34]/10000.; // row # of the North pole*10000 xcoli = gridDirBlock[35]/10000.; // col # of the North pole*10000 xspace = gridDirBlock[36]/1000.; // column spacing at standard lat (m) xqlon = gridDirBlock[37]/10000.; // lon parallel to cols (deg*10000) double xtl = gridDirBlock[38]/10000.; // standard lat xh = (xtl >= 0) ? 1. : -1.; xtl = (90. - xh * xtl) * xrad; xfac = Math.cos(xtl); xblat = EARTH_RADIUS * Math.tan(xtl) / (xspace * Math.pow(Math.tan(xtl*.5), xfac)); break; default: break; } } /** * converts from grid coordinates (x,y) or (col, row) to latitude/longitude * * @param rowcol array of row/col pairs. Where * rowcol[indexRow][] is a row and * rowcol[indexCol][] is a column. * * @return latlon[][] array of lat/long pairs. Output array is * latlon[indexLat][] of latitudes and * latlon[indexLon][] of longitudes. * */ public double[][] toLatLon(double[][] rowcol) { double[][] latlon = new double[2][rowcol[0].length]; double xlat = Double.NaN; // temp variable for lat double xlon = Double.NaN; // temp variable for lon double xrlon = 0.; double xldif, xedif; double radius; for (int i = 0; i < rowcol[0].length; i++) { // account for flipped coordinates double xrow = isRowFlipped ? rowOffset - rowcol[indexRow][i] + 1 : rowcol[indexRow][i]; // adjust row/col based on startRow/startCol xrow = xrow + (startRow - 1); double xcol = rowcol[indexCol][i] - (startColumn - 1); if (xrow > xnrow || xrow < 1.0 || xcol > xncol || xcol < 1.0) { xlat = Double.NaN; xlon = Double.NaN; } else { switch (navType) { case PSEUDO_MERCATOR: case PSEUDO_MERCATOR_GENERAL: if (wierd) { double x = xrow; xcol = xrow; xrow = xnr - x + 1.0; } xlat = glamx-((xrow-1.0)*ginct); xlon = glomx-((xcol-1.0)*gincn); break; case EQUIDISTANT: break; case PS_OR_LAMBERT_CONIC: case LAMBERT_CONFORMAL_TANGENT: xldif = xh * (xrow - xrowi) / xblat; xedif = (xcoli - xcol) / xblat; xrlon = 0.; if( !(xldif == 0 && xedif == 0)) xrlon = Math.atan2( xedif,xldif); xlon = xrlon / xfac / xrad + xqlon; if(xlon > 180.) xlon = xlon - 360.; radius = Math.sqrt( xldif * xldif + xedif * xedif); if( radius < 1.E-5 ) { xlat = xh * 90.; } else { xlat = xh * (90. - 2. * Math.atan( Math.exp( Math.log(radius) / xfac)) /xrad); } break; default: xlat=1.0-(xrow-1.0)/(xnr-1.0); xlon=1.0-(xcol-1.0)/(xnc-1.0); break; } } latlon[indexLat][i] = xlat; latlon[indexLon][i] = -xlon; // convert to east positive } return latlon; } /** * toRowCol converts latitude/longitude to grid row/col * * @param latlon array of lat/long pairs. Where latlon[indexLat][] * are latitudes and latlon[indexLon][] are longitudes. * * @return rowcol[][] array of row/col pairs. Where * rowcol[indexRow][] is a row and rowcol[indexCol][] * is an column. These are in 'grid' coordinates */ public double[][] toRowCol(double[][] latlon) { double[][] rowcol = new double[2][latlon[0].length]; double xrow, xcol, xlat, xlon; double xrlon, xclat, xrlat; double glomx1; double xldif, xedif, xdis, xangl, xange; for (int i = 0; i < latlon[0].length; i++) { xrow = Double.NaN; xcol = Double.NaN; xlat = latlon[indexLat][i]; xlon = -latlon[indexLon][i]; // convert to McIDAS (west pos) switch(navType) { case PSEUDO_MERCATOR: case PSEUDO_MERCATOR_GENERAL: glomx1 = glomx; if (glomx < 0 && glomx*xlon < 0) glomx1 = glomx + 360; xrow = (glamx-xlat)/ginct + 1.0; xcol = (glomx1-xlon)/gincn + 1.0; break; case EQUIDISTANT: xrlon = xlon-glomx; xrlat = xlat-glamx; xldif = xblat*xrlat; xedif = xrlon*xblon*Math.cos(xlat*xrad); xdis = Math.sqrt(xldif*xldif+xedif*xedif); if( xdis > .001) { xangl = Math.atan2(xldif,xedif)-90.*xrad; xange = Math.atan2(xldif,xedif)+90.*xrad; xldif = xdis*Math.cos(-xrot+xangl); xedif = xdis*Math.sin(-xrot+xange); } xrow = xrowi-xldif; xcol = xcoli-xedif; break; case PS_OR_LAMBERT_CONIC: case LAMBERT_CONFORMAL_TANGENT: xrlon = xlon - xqlon; if(xrlon > 180.) xrlon = xrlon - 360.; xrlon = xrlon * xfac * xrad; xclat = (90. - xh * xlat) * xrad * .5; xrlat = xblat * Math.pow(Math.tan(xclat), xfac); xrow = xh * xrlat * Math.cos(xrlon) + xrowi; xcol = -xrlat * Math.sin(xrlon) + xcoli; break; default: xrow = (1.0 - xlat)*(xnr-1.0)+1; xcol = (1.0 - xlon)*(xnc-1.0)+1; break; } if (xrow > xnrow || xrow < 1.0 || xcol > xncol || xcol < 1.0) { xrow = Double.NaN; xcol = Double.NaN; } else { // account for non (1,1) origin xrow = xrow - (startRow - 1); xcol = xcol + (startColumn - 1); // account for flipped coordinates if (isRowFlipped) xrow = rowOffset - xrow + 1; } rowcol[indexRow][i] = xrow; rowcol[indexCol][i] = xcol; } return rowcol; } /** * Determines whether or not the <code>Object</code> in question is * the same as this <code>AREAnav</code>. Right now, this returns * false until we can figure out when two navigations are equal. * Subclasses could override if desired. * * @param obj the AREAnav in question */ public boolean equals(Object obj) { if (! (obj instanceof GRIDnav)) return false; GRIDnav that = (GRIDnav) obj; return (Double.doubleToLongBits(this.xnr) == Double.doubleToLongBits(that.xnr) && Double.doubleToLongBits(this.xnc) == Double.doubleToLongBits(that.xnc) && this.navType == that.navType); } /** * define the starting row and column of another coordinate system -- * for example (0,0) * * @param startRow the starting row number in another * coordinate system * * @param startColumn the starting column number in another * coordinate system * */ public void setStart(int startRow, int startColumn) { this.startRow = startRow; this.startColumn = startColumn; } /** * specify whether the row coordinates are inverted and the row * offset. * * @param row ending row number * */ public void setFlipRowCoordinates(int row) { isRowFlipped = true; rowOffset = (double) row; } /** * Determine if navigation is using flipped coordinates. This would * mean that the start of the coordinate system is in the lower left * instead of the upper left. * * @return true if using flipped row coordinates, otherwise false */ public boolean isFlippedRowCoordinates() { return isRowFlipped; } /** * Get the row offset for flipped coordinates * * @return row offset */ public double getRowOffset() { return rowOffset; } /** * Get the row index * @return the row index in the returned arrays */ public int getRowIndex() { return indexRow; } /** * Get the column index * @return the column index in the returned arrays */ public int getColumnIndex() { return indexCol; } }