@Override
  public void operate(GeoGrid src, GeoGrid dst, int startRow, int endRow) {

    final int cols = src.getCols();
    final int rows = src.getRows();
    final double cellSize = src.getCellSize();
    final double gg = cellSize * cellSize;
    final int firstInteriorRow = Math.max(1, startRow);
    final int lastInteriorRow = Math.min(rows - 1, endRow);

    // top row
    if (startRow == 0) {
      for (int col = 0; col < cols; col++) {
        operateBorder(src, dst, col, 0, cellSize);
      }
    }

    // bottom row
    if (endRow == rows - 1) {
      for (int col = 0; col < cols; col++) {
        operateBorder(src, dst, col, rows - 1, cellSize);
      }
    }

    // left column
    for (int row = firstInteriorRow; row < lastInteriorRow; row++) {
      operateBorder(src, dst, 0, row, cellSize);
    }

    // right column
    for (int row = firstInteriorRow; row < lastInteriorRow; row++) {
      operateBorder(src, dst, cols - 1, row, cellSize);
    }

    // interior of grid
    float[][] srcGrid = src.getGrid();
    float[][] dstGrid = dst.getGrid();
    for (int row = firstInteriorRow; row < lastInteriorRow; row++) {
      for (int col = 1; col < cols - 1; col++) {

        final float z1 = srcGrid[row - 1][col - 1]; // top left
        final float z2 = srcGrid[row - 1][col]; // top
        final float z3 = srcGrid[row - 1][col + 1]; // top right
        final float z4 = srcGrid[row][col - 1]; // left
        final float z5 = srcGrid[row][col]; // center
        final float z6 = srcGrid[row][col + 1]; // right
        final float z7 = srcGrid[row + 1][col - 1]; // bottom left
        final float z8 = srcGrid[row + 1][col]; // bottom
        final float z9 = srcGrid[row + 1][col + 1]; // bottom right

        final double a = gg * ((z1 + z3 + z4 + z6 + z7 + z9) / 6 - (z2 + z5 + z8) / 3);
        final double b = gg * ((z1 + z2 + z3 + z7 + z8 + z9) / 6 - (z4 + z5 + z6) / 3);
        final double a_b = a - b;
        final double c = (z3 + z7 - z1 - z9) / 4 * gg;

        final double v = -a - b + Math.sqrt(a_b * a_b + c * c);
        dstGrid[row][col] = v < 0f ? 0f : (float) v;
      }
    }
  }
  private void operateBorder(GeoGrid src, GeoGrid dst, int col, int row, double cellSize) {
    float[][] srcGrid = src.getGrid();
    float[][] dstGrid = dst.getGrid();
    final int cols = src.getCols();
    final int rows = src.getRows();

    final int rm = row - 1 < 0 ? 0 : row - 1;
    final int rp = row + 1 >= rows ? rows - 1 : row + 1;
    final int cm = col - 1 < 0 ? 0 : col - 1;
    final int cp = col + 1 >= cols ? cols - 1 : col + 1;

    final float z1 = srcGrid[rm][cm]; // top left
    final float z2 = srcGrid[rm][col]; // top
    final float z3 = srcGrid[rm][cp]; // top right
    final float z4 = srcGrid[row][cm]; // left
    final float z5 = srcGrid[row][col]; // center
    final float z6 = srcGrid[row][cp]; // right
    final float z7 = srcGrid[rp][cm]; // bottom left
    final float z8 = srcGrid[rp][col]; // bottom
    final float z9 = srcGrid[rp][cp]; // bottom right

    final double gg = cellSize * cellSize;
    final double a = gg * ((z1 + z3 + z4 + z6 + z7 + z9) / 6 - (z2 + z5 + z8) / 3);
    final double b = gg * ((z1 + z2 + z3 + z7 + z8 + z9) / 6 - (z4 + z5 + z6) / 3);
    final double a_b = a - b;
    final double c = (z3 + z7 - z1 - z9) / 4 * gg;

    final double v = -a - b + Math.sqrt(a_b * a_b + c * c);
    dstGrid[row][col] = v < 0f ? 0f : (float) v;
  }
  public GeoObject operate(GeoGrid geoGrid, double firstContourLevel, double lastContourLevel) {
    flags = new boolean[geoGrid.getRows()][geoGrid.getCols()];

    GeoSet geoSet = new GeoSet();

    final int nlevels = (int) ((lastContourLevel - firstContourLevel) / interval) + 1;
    if (treatDegreeJump) {
      GeoSet levelGeoSet = new GeoSet();
      this.contourLevel(geoGrid, 0.f, levelGeoSet);
      levelGeoSet.setName(Float.toString(0.f));
      geoSet.add(levelGeoSet);
    }

    for (int i = 0; i < nlevels; ++i) {
      final double contourLevel = firstContourLevel + i * interval;
      GeoSet levelGeoSet = new GeoSet();
      this.contourLevel(geoGrid, contourLevel, levelGeoSet);
      levelGeoSet.setName(Double.toString(contourLevel));
      geoSet.add(levelGeoSet);
    }
    return geoSet;
  }
  public GeoPath traceSingleContourAtPoint(GeoGrid geoGrid, double x, double y) {

    this.flags = new boolean[geoGrid.getRows()][geoGrid.getCols()];

    double cellSize = geoGrid.getCellSize();
    double west = geoGrid.getWest();
    double north = geoGrid.getNorth();
    double level = geoGrid.getBicubicInterpol(x, y);

    int[] cell = new int[] {(int) ((x - west) / cellSize), (int) ((north - y) / cellSize)};
    GeoPath geoPath = traceContour(geoGrid.getGrid(), cell, level, west, north, cellSize);
    return geoPath;
  }
  private void contourLevel(GeoGrid geoGrid, double level, GeoSet levelGeoSet) {

    final int nbrCellsX = geoGrid.getCols() - 1;
    final int nbrCellsY = geoGrid.getRows() - 1;
    float[][] grid = geoGrid.getGrid();
    double west = geoGrid.getWest();
    double north = geoGrid.getNorth();
    double cellSize = geoGrid.getCellSize();

    for (int i = 0; i < this.flags.length; i++) {
      java.util.Arrays.fill(this.flags[i], false);
    }

    for (int y = 0; y < nbrCellsY; y++) {
      boolean[] flag_row = flags[y];
      for (int x = 0; x < nbrCellsX; x++) {
        if (flag_row[x] == false) {
          traceContour(grid, new int[] {x, y}, level, west, north, cellSize, levelGeoSet);
        }
      }
    }
  }
 public GeoObject operate(GeoGrid geoGrid, double firstContourLevel) {
   float[] minMax = geoGrid.getMinMax();
   return operate(geoGrid, firstContourLevel, minMax[1]);
 }
 public GeoObject operate(GeoGrid geoGrid) {
   float[] minMax = geoGrid.getMinMax();
   final double firstContourLevel = Math.ceil(minMax[0] / interval) * interval;
   return operate(geoGrid, firstContourLevel);
 }