/* * Contour.java * See http://www.mactech.com/articles/mactech/Vol.13/13.09/ContourPlottinginJava/index.html * Created on May 20, 2004, 7:49 PM */ package org.das2.math; import org.das2.dataset.VectorDataSetBuilder; import org.das2.dataset.TableDataSet; import org.das2.dataset.VectorDataSet; import org.das2.datum.Units; import org.das2.datum.DatumVector; import org.das2.datum.Datum; import java.awt.*; /** * Contouring based on code published in Javatech, Volume 13 Issue 9, "Contour Plotting In Java" * by David Rand. This code is based on Fortran code implementing 1978 article by W. V. Snyder. * W. V. Snyder, "Algorithm 531, Contour plotting [J6]," ACM Trans. Math. Softw. 4, 3 (Sept. 1978), 290-294. * @author Owner */ public class Contour { public static final String PLANE_X="x"; public static final String PLANE_Y="y"; final static public class ContourPlot { TableDataSet zz; Units zunits; long idx; /* monotonically increasing X Value */ public ContourPlot( TableDataSet tds, DatumVector contourValues ) { super(); this.zz= tds; xSteps = tds.getXLength(); ySteps = tds.getYLength(0); zunits= tds.getZUnits(); ncv= contourValues.getLength(); cv= new float[ncv]; for ( int i=0; i0 ) { double y1= zz.getYTagDouble(0, indx+1, zz.getYUnits() ); return y0 + fp * ( y1 - y0 ); } else { return y0; } } final double getXValue( double findex ) { findex--; /* one is first index */ int indx= (int)findex; if ( indx==zz.getXLength() ) indx--; double fp= findex - indx; double x0= zz.getXTagDouble( indx, zz.getXUnits() ); if ( fp>0 ) { double x1= zz.getXTagDouble( indx+1, zz.getXUnits() ); return x0 + fp * ( x1 - x0 ); } else { return x0; } } //------------------------------------------------------- // "DrawKernel" is the guts of drawing and is called // directly or indirectly by "ContourPlotKernel" in order // to draw a segment of a contour or to set the pen // position "prevXY". Its action depends on "iflag": // // iflag == 1 means Continue a contour // iflag == 2 means Start a contour at a boundary // iflag == 3 means Start a contour not at a boundary // iflag == 4 means Finish contour at a boundary // iflag == 5 means Finish closed contour not at boundary // iflag == 6 means Set pen position // // If the constant "SHOW_NUMBERS" is true then when // completing a contour ("iflag" == 4 or 5) the contour // index is drawn adjacent to where the contour ends. //------------------------------------------------------- void DrawKernel(VectorDataSetBuilder dsbuilder) { int prevU,prevV,u,v; if ((iflag == 1) || (iflag == 4) || (iflag == 5)) { } else { idx++ ; } dsbuilder.insertY(idx,cval); dsbuilder.insertY(idx,getXValue(xy[0]),PLANE_X); dsbuilder.insertY(idx,getYValue(xy[1]),PLANE_Y); idx++; } //------------------------------------------------------- // "DetectBoundary" //------------------------------------------------------- final void DetectBoundary() { ix = 1; if (ij[1-elle] != 1) { ii = ij[0] - i1[1-elle]; jj = ij[1] - i1[elle]; if ( !zunits.isFill(zz.getDouble(ii-1,jj-1,zunits)) ) { ii = ij[0] + i2[elle]; jj = ij[1] + i2[1-elle]; if ( !zunits.isFill(zz.getDouble(ii-1,jj-1,zunits)) ) ix = 0; } if (ij[1-elle] >= l1[1-elle]) { ix = ix + 2; return; } } ii = ij[0] + i1[1-elle]; jj = ij[1] + i1[elle]; if ( zunits.isFill( zz.getDouble(ii-1,jj-1,zunits) ) ) { ix = ix + 2; return; } if ( zunits.isFill( zz.getDouble(ij[0],ij[1],zunits) ) ) ix = ix + 2; } //------------------------------------------------------- // "Routine_label_020" corresponds to a block of code // starting at label 20 in Synder's subroutine "GCONTR". //------------------------------------------------------- boolean Routine_label_020() { l2[0] = ij[0]; l2[1] = ij[1]; l2[2] = -ij[0]; l2[3] = -ij[1]; idir = 0; nxidir = 1; k = 1; ij[0] = Math.abs(ij[0]); ij[1] = Math.abs(ij[1]); if ( zunits.isFill( zz.getDouble(ij[0]-1,ij[1]-1,zunits) ) ) { elle = idir % 2; ij[elle] = sign(ij[elle],l1[k-1]); return true; } elle = 0; return false; } //------------------------------------------------------- // "Routine_label_050" corresponds to a block of code // starting at label 50 in Synder's subroutine "GCONTR". //------------------------------------------------------- boolean Routine_label_050() { while (true) { if (ij[elle] >= l1[elle]) { if (++elle <= 1) continue; elle = idir % 2; ij[elle] = sign(ij[elle],l1[k-1]); if (Routine_label_150()) return true; continue; } ii = ij[0] + i1[elle]; jj = ij[1] + i1[1-elle]; if ( zunits.isFill( zz.getDouble(ii-1,jj-1,zunits) ) ) { if (++elle <= 1) continue; elle = idir % 2; ij[elle] = sign(ij[elle],l1[k-1]); if (Routine_label_150()) return true; continue; } break; } jump = false; return false; } //------------------------------------------------------- // "Routine_label_150" corresponds to a block of code // starting at label 150 in Synder's subroutine "GCONTR". //------------------------------------------------------- boolean Routine_label_150() { while (true) { //------------------------------------------------ // Lines from z[ij[0]-1][ij[1]-1] // to z[ij[0] ][ij[1]-1] // and z[ij[0]-1][ij[1]] // are not satisfactory. Continue the spiral. //------------------------------------------------ if (ij[elle] < l1[k-1]) { ij[elle]++; if (ij[elle] > l2[k-1]) { l2[k-1] = ij[elle]; idir = nxidir; nxidir = idir + 1; k = nxidir; if (nxidir > 3) nxidir = 0; } ij[0] = Math.abs(ij[0]); ij[1] = Math.abs(ij[1]); if ( zunits.isFill( zz.getDouble(ij[0]-1,ij[1]-1,zunits) ) ) { elle = idir % 2; ij[elle] = sign(ij[elle],l1[k-1]); continue; } elle = 0; return false; } if (idir != nxidir) { nxidir++; ij[elle] = l1[k-1]; k = nxidir; elle = 1 - elle; ij[elle] = l2[k-1]; if (nxidir > 3) nxidir = 0; continue; } if (ibkey != 0) return true; ibkey = 1; ij[0] = icur; ij[1] = jcur; if (Routine_label_020()) continue; return false; } } //------------------------------------------------------- // "Routine_label_200" corresponds to a block of code // starting at label 200 in Synder's subroutine "GCONTR". // It has return values 0, 1 or 2. //------------------------------------------------------- short Routine_label_200(VectorDataSetBuilder dsbuilder, boolean workSpace[]) { while (true) { xy[elle] = 1.0*ij[elle] + intersect[iedge-1]; xy[1-elle] = 1.0*ij[1-elle]; workSpace[2*(xSteps*(ySteps*cntrIndex+ij[1]-1) +ij[0]-1) + elle] = true; DrawKernel(dsbuilder); if (iflag >= 4) { icur = ij[0]; jcur = ij[1]; return 1; } ContinueContour(); if (!workSpace[2*(xSteps*(ySteps*cntrIndex +ij[1]-1)+ij[0]-1)+elle]) return 2; iflag = 5; // 5. Finish a closed contour iedge = ks + 2; if (iedge > 4) iedge = iedge - 4; intersect[iedge-1] = intersect[ks-1]; } } //------------------------------------------------------- // "CrossedByContour" is true iff the current segment in // the grid is crossed by one of the contour values and // has not already been processed for that value. //------------------------------------------------------- boolean CrossedByContour(boolean workSpace[]) { ii = ij[0] + i1[elle]; jj = ij[1] + i1[1-elle]; z1 = zz.getDouble(ij[0]-1,ij[1]-1,zunits); z2 = zz.getDouble(ii-1,jj-1,zunits); for (cntrIndex = 0; cntrIndex < ncv; cntrIndex++) { int i = 2*(xSteps*(ySteps*cntrIndex+ij[1]-1) + ij[0]-1) + elle; if (!workSpace[i]) { float x = cv[cntrIndex]; if ((x>Math.min(z1,z2)) && (x<=Math.max(z1,z2))) { workSpace[i] = true; return true; } } } return false; } //------------------------------------------------------- // "ContinueContour" continues tracing a contour. Edges // are numbered clockwise, the bottom edge being # 1. //------------------------------------------------------- void ContinueContour() { short local_k; ni = 1; if (iedge >= 3) { ij[0] = ij[0] - i3[iedge-1]; ij[1] = ij[1] - i3[iedge+1]; } for (local_k = 1; local_k < 5; local_k++) if (local_k != iedge) { ii = ij[0] + i3[local_k-1]; jj = ij[1] + i3[local_k]; z1 = zz.getDouble(ii-1,jj-1,zunits); ii = ij[0] + i3[local_k]; jj = ij[1] + i3[local_k+1]; z2 = zz.getDouble(ii-1,jj-1,zunits); if ((cval > Math.min(z1,z2) && (cval <= Math.max(z1,z2)))) { if ((local_k == 1) || (local_k == 4)) { double zz = z2; z2 = z1; z1 = zz; } intersect[local_k-1] = (cval - z1)/(z2 - z1); ni++; ks = local_k; } } if (ni != 2) { //------------------------------------------------- // The contour crosses all 4 edges of cell being // examined. Choose lines top-to-left & bottom-to- // right if interpolation point on top edge is // less than interpolation point on bottom edge. // Otherwise, choose the other pair. This method // produces the same results if axes are reversed. // The contour may close at any edge, but must not // cross itself inside any cell. //------------------------------------------------- ks = 5 - iedge; if (intersect[2] >= intersect[0]) { ks = 3 - iedge; if (ks <= 0) ks = ks + 4; } } //---------------------------------------------------- // Determine whether the contour will close or run // into a boundary at edge ks of the current cell. //---------------------------------------------------- elle = ks - 1; iflag = 1; // 1. Continue a contour jump = true; if (ks >= 3) { ij[0] = ij[0] + i3[ks-1]; ij[1] = ij[1] + i3[ks+1]; elle = ks - 3; } } //------------------------------------------------------- // "ContourPlotKernel" is the guts of this class and // corresponds to Synder's subroutine "GCONTR". //------------------------------------------------------- void ContourPlotKernel(VectorDataSetBuilder dsbuilder, boolean workSpace[]) { short val_label_200; l1[0] = xSteps; l1[1] = ySteps; l1[2] = -1;l1[3] = -1; i1[0] = 1; i1[1] = 0; i2[0] = 1; i2[1] = -1; i3[0] = 1; i3[1] = 0; i3[2] = 0; i3[3] = 1; i3[4] = 1; i3[5] = 0; prevXY[0] = 0.0; prevXY[1] = 0.0; xy[0] = 1.0; xy[1] = 1.0; cntrIndex = 0; prevIndex = -1; iflag = 6; DrawKernel(dsbuilder); icur = Math.max(1, Math.min((int)Math.floor(xy[0]), xSteps)); jcur = Math.max(1, Math.min((int)Math.floor(xy[1]), ySteps)); ibkey = 0; ij[0] = icur; ij[1] = jcur; if (Routine_label_020() && Routine_label_150()) return; if (Routine_label_050()) return; while (true) { DetectBoundary(); if (jump) { if (ix != 0) iflag = 4; // Finish contour at boundary iedge = ks + 2; if (iedge > 4) iedge = iedge - 4; intersect[iedge-1] = intersect[ks-1]; val_label_200 = Routine_label_200(dsbuilder,workSpace); if (val_label_200 == 1) { if (Routine_label_020() && Routine_label_150()) return; if (Routine_label_050()) return; continue; } if (val_label_200 == 2) continue; return; } if ((ix != 3) && (ix+ibkey != 0) && CrossedByContour(workSpace)) { // // An acceptable line segment has been found. // Follow contour until it hits a // boundary or closes. // iedge = elle + 1; cval = cv[cntrIndex]; if (ix != 1) iedge = iedge + 2; iflag = 2 + ibkey; intersect[iedge-1] = (cval - z1) / (z2 - z1); val_label_200 = Routine_label_200(dsbuilder,workSpace); if (val_label_200 == 1) { if (Routine_label_020() && Routine_label_150()) return; if (Routine_label_050()) return; continue; } if (val_label_200 == 2) continue; return; } if (++elle > 1) { elle = idir % 2; ij[elle] = sign(ij[elle],l1[k-1]); if (Routine_label_150()) return; } if (Routine_label_050()) return; } } } /** * returns a rank 1 dataset, a vector dataset, listing the points * of the contour paths. The data set will have three planes: * X, Y and the default plane is the Z. */ public static VectorDataSet contour( TableDataSet tds, DatumVector levels ) { if ( tds.tableCount()>1 ) { throw new IllegalArgumentException("TableDataSet can only have one table"); } Contour.ContourPlot cp= new ContourPlot( tds, levels ); return cp.performContour(); } public static VectorDataSet contour( TableDataSet tds, Datum level ) { if ( tds.tableCount()>1 ) { throw new IllegalArgumentException("TableDataSet can only have one table"); } Units units= level.getUnits(); double value= level.doubleValue(units); Contour.ContourPlot cp= new ContourPlot( tds, DatumVector.newDatumVector( new double[] { value }, units ) ); return cp.performContour(); } }