package contouring; import java.util.ArrayList; import java.util.List; /** * <p> * An object used to generate a list of contour lines or paths from a set of * gridded three dimensional data. * </p> * * <p> * Based on contour_plot.c from NeXTcontour1.4 by Thomas H. Pulliam, * pulliam@rft29.nas.nasa.gov, MS 202A-1 NASA Ames Research Center, Moffett * Field, CA 94035. I don't know how the original Fortran code looked like or * where it came from, other than that NeXTcontour1.4 is based on Pieter * Bunings' PLOT3D package for Computational Fluid Dynamics. * </p> * * <p> * Ported from C to Java by Joseph A. Huwaldt, November 16, 2000. * </p> * * <p> * Modified by: Joseph A. Huwaldt * </p> * * @author Joseph A. Huwaldt Date: November 11, 2000 * @version November 23, 2000 * * @author Marc Suchard * **/ public class ContourGenerator { // Debug flag. private static final boolean DEBUG = false; // Error messages. private static final String kCancelMsg = "Method ContourGenerator.getContours() canceled by user."; private static final String kInconsistantArrMsg = "Inconsistant array sizes."; private static final String kArrSizeMsg = "Data arrays must have more than one row or column."; private static final String kNegLogDataMsg = "Function data must be > 0 for logarithmic intervals."; // Path buffer size. private static final int kBufSize = 1000; // The minimum number of points allowed in a contour path. private static final int kMinNumPoints = 3; // A list of contour paths. private List<ContourPath> pathList = new ArrayList<ContourPath>(); // A flag to indicate that the contours have been computed or not. private boolean cCalculated = false; // Data arrays used for generating the contours. private double[][] xArray, yArray, funcArray; // Data arrays used when generating contours for 1D X & Y arrays. private double[] xArr1D, yArr1D; // Array of contour attributes, one for each contour level. private ContourAttrib[] cAttr; // The fraction of the task that is completed. private float fracComplete = 0; /** * Used to indicate that the user wishes to cancel the calculation of * contours. **/ private boolean isCanceled = false; // Variables in the original FORTRAN program. private double[] pathbufxt, pathbufyt; private int[] pathbufia; private int lnstrt; // lnstrt=1 indicates starting a new line. private int ignext; private int icont; // Current contour level index. private double cont; // The current contour level. private int iss, iee, jss, jee; // i & j start and end index values. private int ima; // ima tells which boundary region we are on. private int iae; // Index to last element in the IA list. private int ibeg, jbeg; private int gi, gj; // Indexes into data arrays. private double fij; // Data value at i,j in data array. private int idir; // Indicates current direction. private int np = 0; // Number of points in current contour line. private double wx = 0, wy = 0; // Starting point of a contour line. /** * Construct a ContourGenerator object using the specified data arrays and * the specified attribute array. This constructor allows you to use data on * an uneven X, Y grid. * * @param xArr * 2D array containing the grid x coordinate data. * @param yArr * 2D array containing the grid y coordinate data. * @param fArr * 2D array containing the grid function (z) data. * @param cAttr * Array containing attributes of the contour levels. **/ public ContourGenerator(double[][] xArr, double[][] yArr, double[][] fArr, ContourAttrib[] cAttr) { // Make sure input data is reasonable. if (yArr.length != xArr.length || yArr.length != fArr.length) throw new IllegalArgumentException(kInconsistantArrMsg); if (yArr[0].length != xArr[0].length || yArr[0].length != fArr[0].length) throw new IllegalArgumentException(kInconsistantArrMsg); if (xArr.length <= 1 || xArr[0].length <= 1) throw new IllegalArgumentException(kArrSizeMsg); this.cAttr = cAttr; xArray = xArr; yArray = yArr; funcArray = fArr; } /** * Construct a ContourGenerator object using the specified data arrays and * the specified attribute array. This constructor allows you to use data on * an evenly spaced grid where "X" values are invarient with "Y" and "Y" * values are invarient with "X". This often occures where the data is on an * evenly spaced cartesian grid. * * @param xArr * 1D array containing the grid x coordinate data. * @param yArr * 1D array containing the grid y coordinate data. * @param fArr * 2D array containing the grid function (z) data. * @param cAttr * Array containing attributes of the contour levels. **/ public ContourGenerator(double[] xArr, double[] yArr, double[][] fArr, ContourAttrib[] cAttr) { // Make sure input data is reasonable. if (yArr.length != fArr.length || xArr.length != fArr[0].length) throw new IllegalArgumentException(kInconsistantArrMsg); if (xArr.length <= 1) throw new IllegalArgumentException(kArrSizeMsg); this.cAttr = cAttr; xArr1D = xArr; yArr1D = yArr; funcArray = fArr; } /** * Construct a ContourGenerator object using the specified data arrays. * Contour attributes, including the interval, are generated automatically. * This constructor allows you to use data on an uneven X, Y grid. * * @param xArr * 2D array containing the grid x coordinate data. * @param yArr * 2D array containing the grid y coordinate data. * @param fArr * 2D array containing the grid function (z) data. * @param nc * The number of contour levels to generate. * @param logInterval * Uses a logarithmic contour interval if true, and uses a linear * interval if false. **/ public ContourGenerator(double[][] xArr, double[][] yArr, double[][] fArr, int nc, boolean logInterval) { // Make sure input data is reasonable. if (yArr.length != xArr.length || yArr.length != fArr.length) throw new IllegalArgumentException(kInconsistantArrMsg); if (yArr[0].length != xArr[0].length || yArr[0].length != fArr[0].length) throw new IllegalArgumentException(kInconsistantArrMsg); if (xArr.length <= 1 || xArr[0].length <= 1) throw new IllegalArgumentException(kArrSizeMsg); xArray = xArr; yArray = yArr; funcArray = fArr; if (logInterval) findLogIntervals(nc); else findLinearIntervals(nc); } /** * Construct a ContourGenerator object using the specified data arrays. * Contour attributes, including the interval, are generated automatically. * This constructor allows you to use data on an evenly spaced grid where * "X" values are invarient with "Y" and "Y" values are invarient with "X". * This often occures where the data is on an evenly spaced cartesian grid. * * @param xArr * 1D array containing the grid x coordinate data. * @param yArr * 1D array containing the grid y coordinate data. * @param fArr * 2D array containing the grid function (z) data. * @param nc * The number of contour levels to generate. * @param logInterval * Uses a logarithmic contour interval if true, and uses a linear * interval if false. **/ public ContourGenerator(double[] xArr, double[] yArr, double[][] fArr, int nc, boolean logInterval) { // Make sure input data is reasonable. if (yArr.length != fArr.length || xArr.length != fArr[0].length) throw new IllegalArgumentException(kInconsistantArrMsg); if (xArr.length <= 1) throw new IllegalArgumentException(kArrSizeMsg); xArr1D = xArr; yArr1D = yArr; funcArray = fArr; if (logInterval) findLogIntervals(nc); else findLinearIntervals(nc); } /** * Generate the contour paths and return them as an array of ContourPath * objects. If there is a lot of data, this method method may take a long * time, so be patient. Progress can be checked from another thread by * calling "getProgress()". * * @return An array of contour path objects. * @throws InterruptedException * if the user cancels this process (by calling "cancel()" from * another thread). **/ public ContourPath[] getContours() throws InterruptedException { if (!cCalculated) { isCanceled = false; pathList.clear(); // Go off an compute the contour paths. computeContours(); // Now turn loose all our data arrays to be garbage collected. cAttr = null; xArray = yArray = funcArray = null; xArr1D = yArr1D = null; // Set our "done" flags. cCalculated = true; fracComplete = 1; } // Turn our pathList into an array and return the array. int size = pathList.size(); ContourPath[] arr = new ContourPath[size]; for (int i = 0; i < size; ++i) arr[i] = (ContourPath) pathList.get(i); return arr; } /** * Returns true if the contour generation process is done. False if it is * not. **/ public boolean done() { return cCalculated; } /** * Call this method to cancel the generation of contours. **/ public void cancel() { isCanceled = true; } /** * Returns the progress of the currently executing contour generation * process: 0.0 (just starting) to 1.0 (done). **/ public float getProgress() { return fracComplete; } /** * Find contour intervals that are linearly spaced through the data. **/ private void findLinearIntervals(int nc) { // Find min and max Z values. double zMin = Double.MAX_VALUE; double zMax = -zMin; int ni = funcArray.length; for (int i = 0; i < ni; ++i) { int nj = funcArray[i].length; for (int j = 0; j < nj; ++j) { double zVal = funcArray[i][j]; zMin = Math.min(zMin, zVal); zMax = Math.max(zMax, zVal); } } // Allocate memory for contour attribute array. cAttr = new ContourAttrib[nc]; // Determine contour levels. double delta = (zMax - zMin) / (nc + 1); for (int i = 0; i < nc; i++) { cAttr[i] = new ContourAttrib(zMin + (i + 1) * delta); if (DEBUG) System.out.println("level[" + i + "] = " + (zMin + (i + 1) * delta)); } } /** * Find contour intervals that are logarithmically spaced through the data. **/ private void findLogIntervals(int nc) { // Find min and max Z values. double zMin = Double.MAX_VALUE; double zMax = -zMin; int ni = funcArray.length; for (int i = 0; i < ni; ++i) { int nj = funcArray[i].length; for (int j = 0; j < nj; ++j) { double zVal = funcArray[i][j]; zMin = Math.min(zMin, zVal); zMax = Math.max(zMax, zVal); } } if (zMin < 0) throw new IllegalArgumentException(kNegLogDataMsg); // Allocate memory for contour attribute array. cAttr = new ContourAttrib[nc]; // Determine contour levels. double temp = Math.log(zMin); double delta = (Math.log(zMax) - temp) / (nc + 1); for (int i = 0; i < nc; i++) cAttr[i] = new ContourAttrib(Math.exp(temp + (i + 1) * delta)); } /** * Computes contour lines for gridded data and stores information about * those contours. The result of this routine is a list of contour lines or * paths. **/ private void computeContours() throws InterruptedException { int ncont = cAttr.length; // Number of contour levels. // Find the number of data points in "I" and "J" directions. int nx = 0, ny = 0; if (xArray != null) { ny = xArray.length; nx = xArray[0].length; } else { nx = xArr1D.length; ny = yArr1D.length; } // Allocate temporary storage space for path buffers. pathbufxt = new double[kBufSize]; pathbufyt = new double[kBufSize]; pathbufia = new int[kBufSize * 3]; // lnstrt=1 (line start) means we're starting a new line. lnstrt = 1; ignext = 0; // Loop through each contour level. for (icont = 0; icont < ncont; ++icont) { // Check to see if the user has canceled. if (isCanceled) throw new InterruptedException(kCancelMsg); // Begin working on this contour level. cont = cAttr[icont].getLevel(); iss = 1; iee = nx; jss = 1; jee = ny; boolean subDivFlg = false; /* L110 */do { // Find where function increases through the contour level. FlagContourPassings(); boolean L10flg = false; /* L210 */do { if (!L10flg) { /* * Search along the boundaries for contour line starts. * IMA tells which boundary of the region we're on. */ ima = 1; ibeg = iss - 1; jbeg = jss; } /* L6 */imaLoop: do { if (!L10flg) { boolean imb = false; boolean doneFlg = false; do { switch (ima) { case 1: ++ibeg; if (ibeg == iee) ima = 2; break; case 2: ++jbeg; if (jbeg == jee) ima = 3; break; case 3: --ibeg; if (ibeg == iss) ima = 4; break; case 4: --jbeg; if (jbeg == jss) ima = 5; break; case 5: continue imaLoop; } if (funcArray[jbeg - 1][ibeg - 1] <= cont) { imb = true; doneFlg = false; } else if (imb == true) doneFlg = true; } while (!doneFlg); // Got a start point. gi = ibeg; // x index of starting point. gj = jbeg; // y index of starting point. fij = funcArray[jbeg - 1][ibeg - 1]; // z value of // starting // point. // Round the corner if necessary. /* * Look different directions to see which way the * contour line went: 4 1-|-3 2 */ switch (ima) { case 1: Routine_L21(); break; case 2: if (gj != jss) { if (!Routine_L31()) Routine_L21(); } else Routine_L21(); break; case 3: if (gi != iee) { if (!Routine_L41()) Routine_L21(); } else { if (!Routine_L31()) Routine_L21(); } break; case 4: if (gj != jee) { if (!Routine_L51()) Routine_L21(); } else { if (!Routine_L41()) Routine_L21(); } break; case 5: if (!Routine_L51()) Routine_L21(); break; } } // end if(!L10flg) // This is the end of a contour line. After this, we'll // start a // new line. L10flg = false; /* L90 */lnstrt = 1; // Contour line start flag. ignext = 0; accumContour(np, icont, pathbufxt, pathbufyt, cAttr[icont]); // If we're not done looking along the boundaries, // go look there some more. } while (ima != 5); // Otherwise, get the next start out of IA. /* L91 */if (iae != 0) { int ntmp3 = iae; for (int iia = 1; iia <= ntmp3; ++iia) { if (pathbufia[iia - 1] != 0) { // This is how we start in the middle of the // region, using IA. gi = pathbufia[iia - 1] / 1000; gj = pathbufia[iia - 1] - gi * 1000; fij = funcArray[gj - 1][gi - 1]; pathbufia[iia - 1] = 0; Routine_L21(); L10flg = true; break; } } } } while (L10flg); /* * And if there are no more of these, we're done with this * region. If we've subdivided, update the region pointers and * go back for more. */ subDivFlg = false; if (iee == nx) { if (jee != ny) { jss = jee; jee = ny; subDivFlg = true; } } else { iss = iee; iee = nx; subDivFlg = true; } } while (subDivFlg); // Update progress information. fracComplete = (float) (icont + 1) / (float) (ncont); // Loop back for the next contour level. } // Next icont // Turn loose temporary arrays used to generate contours. pathbufxt = null; pathbufyt = null; pathbufia = null; } /** * Flag points in IA where the the function increases through the contour * level, not including the boundaries. This is so we have a list of at * least one point on each contour line that doesn't intersect a boundary. **/ private void FlagContourPassings() { iae = 0; int ntmp2 = jee - 1; for (int j = jss + 1; j <= ntmp2; ++j) { boolean imb = false; int iaend = iae; int ntmp3 = iee; for (int i = iss; i <= ntmp3; ++i) { if (funcArray[j - 1][i - 1] <= cont) imb = true; else if (imb == true) { ++iae; pathbufia[iae - 1] = i * 1000 + j; imb = false; /* * Check if the IA array is full. If so, the subdividing * algorithm goes like this: if we've marked at least one J * row, drop back to the last completed J and call that the * region. If we haven't even finished one J row, our region * just extends to this I location. */ if (iae == kBufSize * 3) { if (j > jss + 1) { iae = iaend; jee = j; } else { // Compute minimum. jee = Math.min(j + 1, jee); iee = i; } // Break out of i & j loops. return; } } } // Next i } // Next j } /** * This function represents the block of code in the original FORTRAN * program that comes after line 21. **/ private void Routine_L21() { while (true) { --gi; if (gi < iss) return; // Goto L90. idir = 1; if (funcArray[gj - 1][gi - 1] <= cont) { // Wipe this point out of IA if it's in the list. /* L52 */if (iae != 0) { int ij = gi * 1000 + gj + 1000; int ntmp3 = iae; for (int iia = 1; iia <= ntmp3; ++iia) { if (pathbufia[iia - 1] == ij) { pathbufia[iia - 1] = 0; break; } } } doInterpolation(); return; // Goto L90. } fij = funcArray[gj - 1][gi - 1]; if (Routine_L31()) return; // Goto L90 } } /** * This function represents the block of code in the original FORTRAN * program that comes after line 31. **/ private boolean Routine_L31() { --gj; if (gj < jss) return true; idir = 2; if (funcArray[gj - 1][gi - 1] <= cont) { doInterpolation(); return true; } fij = funcArray[gj - 1][gi - 1]; return (Routine_L41()); } /** * This function represents the block of code in the original FORTRAN * program that comes after line 41. **/ private boolean Routine_L41() { ++gi; if (gi > iee) return true; idir = 3; if (funcArray[gj - 1][gi - 1] <= cont) { doInterpolation(); return true; } fij = funcArray[gj - 1][gi - 1]; return (Routine_L51()); } /** * This function represents the block of code in the original FORTRAN * program that comes after line 51. **/ private boolean Routine_L51() { ++gj; idir = 4; if (gj > jee) return true; if (funcArray[gj - 1][gi - 1] <= cont) { doInterpolation(); return true; } fij = funcArray[gj - 1][gi - 1]; return false; } /** * Do interpolation for X, Y coordinates. * * This function represents the block of code in the original FORTRAN * program that comes after line 60. **/ private void doInterpolation() { // Do interpolation for X,Y coordinates. double func = funcArray[gj - 1][gi - 1]; double xyf = (cont - func) / (fij - func); /* * This tests for a contour point coinciding with a grid point. In this * case the contour routine comes up with the same physical coordinate * twice. If If we don't trap it, it can (in some cases significantly) * increase the number of points in a contour line. Also, if this * happens on the first point in a line, the second point could be * misinterpreted as the end of a (circling) contour line. */ if (xyf == 0) ++ignext; double wxx = 0, wyy = 0; double xVal = 0, yVal = 0; if (xArray != null) { // We have 2D arrays for the X & Y grid points. xVal = xArray[gj - 1][gi - 1]; yVal = yArray[gj - 1][gi - 1]; switch (idir) { case 1: // East wxx = xVal + xyf * (xArray[gj - 1][gi + 1 - 1] - xVal); wyy = yVal + xyf * (yArray[gj - 1][gi + 1 - 1] - yVal); break; case 2: // North wxx = xVal + xyf * (xArray[gj + 1 - 1][gi - 1] - xVal); wyy = yVal + xyf * (yArray[gj + 1 - 1][gi - 1] - yVal); break; case 3: // West wxx = xVal + xyf * (xArray[gj - 1][gi - 1 - 1] - xVal); wyy = yVal + xyf * (yArray[gj - 1][gi - 1 - 1] - yVal); break; case 4: // South wxx = xVal + xyf * (xArray[gj - 1 - 1][gi - 1] - xVal); wyy = yVal + xyf * (yArray[gj - 1 - 1][gi - 1] - yVal); break; } } else { // We have 1D arrays for the X & Y grid points. xVal = xArr1D[gi - 1]; yVal = yArr1D[gj - 1]; switch (idir) { case 1: // East wxx = xVal + xyf * (xArr1D[gi + 1 - 1] - xVal); wyy = yVal; break; case 2: // North wxx = xVal; wyy = yVal + xyf * (yArr1D[gj + 1 - 1] - yVal); break; case 3: // West wxx = xVal + xyf * (xArr1D[gi - 1 - 1] - xVal); wyy = yVal; break; case 4: // South wxx = xVal; wyy = yVal + xyf * (yArr1D[gj - 1 - 1] - yVal); break; } } if (DEBUG) { System.out.println("i, j = " + gi + "," + gj); System.out.println("cont = " + (float) cont + ", fij = " + (float) fij + ", func = " + (float) func + ", xyf = " + (float) xyf); System.out.println("xVal = " + (float) xVal + ", yVal = " + (float) yVal); System.out.println("wxx = " + (float) wxx + ", wyy = " + (float) wyy); } // Figure out what to do with this point. if (lnstrt == 1) { // This is the 1st point in the contour line. np = 1; pathbufxt[np - 1] = wxx; pathbufyt[np - 1] = wyy; // Save starting point as wx, wy. wx = wxx; wy = wyy; // Clear the first point flag, we've got one now. lnstrt = 0; } else { boolean skipFlg = false; // Second point and after comes here. // Add a point to this line. Check for duplicate point first. if (ignext == 2) { if (wxx == pathbufxt[np - 1] && wyy == pathbufyt[np - 1]) { ignext = 0; skipFlg = true; } else ignext = 1; } if (!skipFlg) { // Increment # of points in contour. ++np; pathbufxt[np - 1] = wxx; pathbufyt[np - 1] = wyy; // See if the temporary array xt, yt are full. if (np == kBufSize) { accumContour(np, icont, pathbufxt, pathbufyt, cAttr[icont]); // Last point becomes 1st point to continue. pathbufxt[0] = pathbufxt[np - 1]; pathbufyt[0] = pathbufyt[np - 1]; np = 1; } // Check to see if we're back to the intial point. if (wxx == wx && wyy == wy) return; } } // Search for the next point on this line. /* L67 */switch (idir) { case 1: ++gi; if (!Routine_L51()) Routine_L21(); break; case 2: ++gj; Routine_L21(); break; case 3: --gi; if (!Routine_L31()) Routine_L21(); break; case 4: --gj; if (!Routine_L41()) Routine_L21(); break; } return; } /** * Accumulate contour paths, as they are generated, into an overall list of * contours. * * @param np * The number of points in the contour path buffers. * @param icont * The index to the current contour level. * @param x * ,y Buffers containing x & y coordinates of contour points. * @param cAttr * The attributes for this particular contour level. **/ private void accumContour(int np, int icont, double[] x, double[] y, ContourAttrib cAttr) { // To few points for a contour line. if (np < kMinNumPoints) return; // Copy over coordinate points from buffers to their own arrays. double[] xArr = new double[np]; double[] yArr = new double[np]; System.arraycopy(x, 0, xArr, 0, np); System.arraycopy(y, 0, yArr, 0, np); // Create a new contour path and add it to the list. ContourPath path = new ContourPath(cAttr, icont, xArr, yArr); pathList.add(path); } }