@Override
  public void run() {
    /*
     * This tool places the nodes (vertices) from a shapefile of polygons or
     * lines into a shapefile of Point ShapeType.
     */

    amIActive = true;
    String inputFile;
    String outputFile;
    int progress;
    int i, n;
    int numFeatures;
    int oneHundredthTotal;
    ShapeType shapeType, outputShapeType;
    GeometryFactory factory = new GeometryFactory();
    double distTolerance = 10;
    boolean loseNoFeatures = false;

    if (args.length <= 0) {
      showFeedback("Plugin parameters have not been set.");
      return;
    }

    inputFile = args[0];
    outputFile = args[1];
    distTolerance = Double.parseDouble(args[2]);
    loseNoFeatures = Boolean.parseBoolean(args[3]);

    // check to see that the inputHeader and outputHeader are not null.
    if ((inputFile == null) || (outputFile == null)) {
      showFeedback("One or more of the input parameters have not been set properly.");
      return;
    }

    try {
      // set up the input shapefile.
      ShapeFile input = new ShapeFile(inputFile);
      shapeType = input.getShapeType();

      // make sure that the shapetype is either a flavour of polyline or polygon.
      if (shapeType.getBaseType() != ShapeType.POLYGON
          && shapeType.getBaseType() != ShapeType.POLYLINE) {
        showFeedback("This tool only works with shapefiles of a polygon or line base shape type.");
        return;
      }

      // set up the output files of the shapefile and the dbf
      if (shapeType.getBaseType() == ShapeType.POLYGON) {
        outputShapeType = ShapeType.POLYGON;
      } else if (shapeType.getBaseType() == ShapeType.POLYLINE) {
        outputShapeType = ShapeType.POLYLINE;
      } else {
        showFeedback("This tool only works with shapefiles of a polygon or line base shape type.");
        return;
      }

      int numOutputFields = input.getAttributeTable().getFieldCount() + 1;
      int numInputFields = input.getAttributeTable().getFieldCount();
      DBFField[] inputFields = input.getAttributeTable().getAllFields();
      DBFField fields[] = new DBFField[numOutputFields];

      fields[0] = new DBFField();
      fields[0].setName("PARENT_ID");
      fields[0].setDataType(DBFField.DBFDataType.NUMERIC);
      fields[0].setFieldLength(10);
      fields[0].setDecimalCount(0);

      System.arraycopy(inputFields, 0, fields, 1, numInputFields);

      ShapeFile output = new ShapeFile(outputFile, outputShapeType, fields);

      numFeatures = input.getNumberOfRecords();
      oneHundredthTotal = numFeatures / 100;
      n = 0;
      progress = 0;
      com.vividsolutions.jts.geom.Geometry[] recJTS = null;
      int recordNum;
      for (ShapeFileRecord record : input.records) {
        recordNum = record.getRecordNumber();
        Object[] attData = input.getAttributeTable().getRecord(recordNum - 1);
        // featureNum++;
        recJTS = record.getGeometry().getJTSGeometries();

        ArrayList<com.vividsolutions.jts.geom.Geometry> geomList = new ArrayList<>();
        for (int a = 0; a < recJTS.length; a++) {
          geomList.add(recJTS[a]);
        }

        DouglasPeuckerSimplifier dps =
            new DouglasPeuckerSimplifier(factory.buildGeometry(geomList));
        dps.setDistanceTolerance(distTolerance);
        com.vividsolutions.jts.geom.Geometry outputGeom = dps.getResultGeometry();

        if (outputGeom.isEmpty() && loseNoFeatures) {
          outputGeom = factory.buildGeometry(geomList);
        }
        if (!outputGeom.isEmpty()) {
          for (int a = 0; a < outputGeom.getNumGeometries(); a++) {
            // parentRecNum = 0;
            com.vividsolutions.jts.geom.Geometry g = outputGeom.getGeometryN(a);
            if (g instanceof com.vividsolutions.jts.geom.Polygon && !g.isEmpty()) {
              com.vividsolutions.jts.geom.Polygon p = (com.vividsolutions.jts.geom.Polygon) g;
              ArrayList<ShapefilePoint> pnts = new ArrayList<>();
              int[] parts = new int[p.getNumInteriorRing() + 1];

              Coordinate[] buffCoords = p.getExteriorRing().getCoordinates();
              if (!Topology.isLineClosed(buffCoords)) {
                System.out.println("Exterior ring not closed.");
              }
              if (Topology.isClockwisePolygon(buffCoords)) {
                for (i = 0; i < buffCoords.length; i++) {
                  pnts.add(new ShapefilePoint(buffCoords[i].x, buffCoords[i].y));
                }
              } else {
                for (i = buffCoords.length - 1; i >= 0; i--) {
                  pnts.add(new ShapefilePoint(buffCoords[i].x, buffCoords[i].y));
                }
              }

              for (int b = 0; b < p.getNumInteriorRing(); b++) {
                parts[b + 1] = pnts.size();
                buffCoords = p.getInteriorRingN(b).getCoordinates();
                if (!Topology.isLineClosed(buffCoords)) {
                  System.out.println("Interior ring not closed.");
                }
                if (Topology.isClockwisePolygon(buffCoords)) {
                  for (i = buffCoords.length - 1; i >= 0; i--) {
                    pnts.add(new ShapefilePoint(buffCoords[i].x, buffCoords[i].y));
                  }
                } else {
                  for (i = 0; i < buffCoords.length; i++) {
                    pnts.add(new ShapefilePoint(buffCoords[i].x, buffCoords[i].y));
                  }
                }
              }

              PointsList pl = new PointsList(pnts);
              whitebox.geospatialfiles.shapefile.Polygon wbPoly =
                  new whitebox.geospatialfiles.shapefile.Polygon(parts, pl.getPointsArray());
              Object[] rowData = new Object[numOutputFields];
              rowData[0] = new Double(recordNum - 1);
              System.arraycopy(attData, 0, rowData, 1, numInputFields);
              output.addRecord(wbPoly, rowData);
            } else if (g instanceof com.vividsolutions.jts.geom.LineString && !g.isEmpty()) {
              LineString ls = (LineString) g;
              ArrayList<ShapefilePoint> pnts = new ArrayList<>();

              int[] parts = {0};

              Coordinate[] coords = ls.getCoordinates();
              for (i = 0; i < coords.length; i++) {
                pnts.add(new ShapefilePoint(coords[i].x, coords[i].y));
              }

              PointsList pl = new PointsList(pnts);
              whitebox.geospatialfiles.shapefile.PolyLine wbGeometry =
                  new whitebox.geospatialfiles.shapefile.PolyLine(parts, pl.getPointsArray());
              Object[] rowData = new Object[numOutputFields];
              rowData[0] = new Double(recordNum - 1);
              System.arraycopy(attData, 0, rowData, 1, numInputFields);
              output.addRecord(wbGeometry, rowData);
            }
          }
        }
        n++;
        if (n >= oneHundredthTotal) {
          n = 0;
          if (cancelOp) {
            cancelOperation();
            return;
          }
          progress++;
          updateProgress(progress);
        }
      }

      output.write();

      // returning a header file string displays the image.
      updateProgress("Displaying vector: ", 0);
      returnData(outputFile);

    } catch (OutOfMemoryError oe) {
      myHost.showFeedback("An out-of-memory error has occurred during operation.");
    } catch (Exception e) {
      myHost.showFeedback("An error has occurred during operation. See log file for details.");
      myHost.logException("Error in " + getDescriptiveName(), e);
    } finally {
      updateProgress("Progress: ", 0);
      // tells the main application that this process is completed.
      amIActive = false;
      myHost.pluginComplete();
    }
  }
  /** Used to execute this plugin tool. */
  @Override
  public void run() {
    amIActive = true;

    String streamsHeader;
    String pointerHeader;
    String outputFileName;
    int row, col, x, y;
    double xCoord, yCoord;
    int progress;
    int c;
    int[] dX = new int[] {1, 1, 1, 0, -1, -1, -1, 0};
    int[] dY = new int[] {-1, 0, 1, 1, 1, 0, -1, -1};
    double[] inflowingVals = new double[] {16, 32, 64, 128, 1, 2, 4, 8};
    boolean flag;
    double flowDir;
    double previousFlowDir;
    double linkLength;
    double streamValue;

    if (args.length <= 0) {
      showFeedback("Plugin parameters have not been set.");
      return;
    }

    streamsHeader = args[0];
    pointerHeader = args[1];
    outputFileName = args[2];

    // check to see that the inputHeader and outputHeader are not null.
    if ((streamsHeader == null) || (pointerHeader == null) || (outputFileName == null)) {
      showFeedback("One or more of the input parameters have not been set properly.");
      return;
    }

    try {
      WhiteboxRaster streams = new WhiteboxRaster(streamsHeader, "r");
      int rows = streams.getNumberRows();
      int cols = streams.getNumberColumns();
      double noData = streams.getNoDataValue();
      double gridResX = streams.getCellSizeX();
      double gridResY = streams.getCellSizeY();
      double diagGridRes = Math.sqrt(gridResX * gridResX + gridResY * gridResY);
      double[] gridLengths =
          new double[] {
            diagGridRes,
            gridResX,
            diagGridRes,
            gridResY,
            diagGridRes,
            gridResX,
            diagGridRes,
            gridResY
          };
      double east = streams.getEast() - gridResX / 2.0;
      double west = streams.getWest() + gridResX / 2.0;
      double EWRange = east - west;
      double north = streams.getNorth() - gridResY / 2.0;
      double south = streams.getSouth() + gridResY / 2.0;
      double NSRange = north - south;

      WhiteboxRaster pntr = new WhiteboxRaster(pointerHeader, "r");

      if (pntr.getNumberRows() != rows || pntr.getNumberColumns() != cols) {
        showFeedback("The input images must be of the same dimensions.");
        return;
      }

      DBFField fields[] = new DBFField[3];

      fields[0] = new DBFField();
      fields[0].setName("FID");
      fields[0].setDataType(DBFField.DBFDataType.NUMERIC);
      fields[0].setFieldLength(10);
      fields[0].setDecimalCount(0);

      fields[1] = new DBFField();
      fields[1].setName("STRM_VAL");
      fields[1].setDataType(DBFField.DBFDataType.NUMERIC);
      fields[1].setFieldLength(10);
      fields[1].setDecimalCount(3);

      fields[2] = new DBFField();
      fields[2].setName("Length");
      fields[2].setDataType(DBFField.DBFDataType.NUMERIC);
      fields[2].setFieldLength(10);
      fields[2].setDecimalCount(3);

      // set up the output files of the shapefile and the dbf
      ShapeFile output = new ShapeFile(outputFileName, ShapeType.POLYLINE, fields);

      byte numNeighbouringStreamCells;
      int FID = 0;
      for (row = 0; row < rows; row++) {
        for (col = 0; col < cols; col++) {
          streamValue = streams.getValue(row, col);
          if (streamValue > 0) {
            // see if it is a headwater location
            numNeighbouringStreamCells = 0;
            for (c = 0; c < 8; c++) {
              x = col + dX[c];
              y = row + dY[c];
              if (streams.getValue(y, x) > 0 && pntr.getValue(y, x) == inflowingVals[c]) {
                numNeighbouringStreamCells++;
              }
            }
            if (numNeighbouringStreamCells != 1) {
              // it's the start of a link.
              FID++;
              linkLength = 0;
              int[] parts = {0};
              PointsList points = new PointsList();
              x = col;
              y = row;
              previousFlowDir = -99;
              flag = true;
              do {
                // find the downslope neighbour
                flowDir = pntr.getValue(y, x);
                if (flowDir > 0) {
                  if (flowDir != previousFlowDir) {
                    // it's a bend in the stream so add this point
                    xCoord = west + ((double) x / cols) * EWRange;
                    yCoord = north - ((double) y / rows) * NSRange;
                    points.addPoint(xCoord, yCoord);

                    previousFlowDir = flowDir;
                  }

                  // update the row and column values to the
                  // cell that the flowpath leads to.
                  c = (int) (Math.log(flowDir) / LnOf2);
                  if (c > 7) {
                    showFeedback(
                        "An unexpected value has "
                            + "been identified in the pointer "
                            + "image. This tool requires a "
                            + "pointer grid that has been "
                            + "created using either the D8 "
                            + "or Rho8 tools.");
                    return;
                  }

                  x += dX[c];
                  y += dY[c];

                  linkLength += gridLengths[c];

                  if (streams.getValue(y, x) <= 0) { // it's not a stream cell
                    flag = false;
                  } else {
                    // is it a confluence
                    numNeighbouringStreamCells = 0;
                    int x2, y2;
                    for (int d = 0; d < 8; d++) {
                      x2 = x + dX[d];
                      y2 = y + dY[d];
                      if (streams.getValue(y2, x2) > 0
                          && pntr.getValue(y2, x2) == inflowingVals[d]) {
                        numNeighbouringStreamCells++;
                      }
                    }
                    if (numNeighbouringStreamCells > 1) {
                      // It's a confluence and you should stop here.
                      flag = false;
                    }
                  }

                } else {
                  flag = false;
                }

                if (!flag) {
                  // it's the end of the stream link so
                  // add the point.
                  xCoord = west + ((double) x / cols) * EWRange;
                  yCoord = north - ((double) y / rows) * NSRange;
                  points.addPoint(xCoord, yCoord);
                }

              } while (flag);

              // add the line to the shapefile.
              PolyLine line = new PolyLine(parts, points.getPointsArray());
              Object[] rowData = new Object[3];
              rowData[0] = new Double(FID);
              rowData[1] = new Double(streamValue);
              rowData[2] = new Double(linkLength / 1000.0);
              output.addRecord(line, rowData);
            }
          }
        }
        if (cancelOp) {
          cancelOperation();
          return;
        }
        progress = (int) (100f * row / (rows - 1));
        updateProgress(progress);
      }
      output.write();

      pntr.close();
      streams.close();

      // returning a header file string displays the image.
      returnData(outputFileName);

    } catch (OutOfMemoryError oe) {
      myHost.showFeedback("An out-of-memory error has occurred during operation.");
    } catch (Exception e) {
      myHost.showFeedback("An error has occurred during operation. See log file for details.");
      myHost.logException("Error in " + getDescriptiveName(), e);
    } finally {
      updateProgress("Progress: ", 0);
      // tells the main application that this process is completed.
      amIActive = false;
      myHost.pluginComplete();
    }
  }