/* This tool has been replaced by an alternate tool written in Rust as part of
the whitebox_tools suite. The new tool uses a different and better algorithm.
*/
/*
* Copyright (C) 2011-2012 Dr. John Lindsay <jlindsay@uoguelph.ca>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package plugins;
import java.io.File;
import java.util.List;
import java.util.PriorityQueue;
import whitebox.geospatialfiles.WhiteboxRaster;
import whitebox.interfaces.WhiteboxPlugin;
import whitebox.interfaces.WhiteboxPluginHost;
import whitebox.structures.KdTree;
import whitebox.utilities.FileUtilities;
/**
* This tool can be used to create a bare-earth DEM from a fine-resolution digital surface model.
*
* @author Dr. John Lindsay email: jlindsay@uoguelph.ca
*/
public class RemoveOffTerrainObjects implements WhiteboxPlugin {
private WhiteboxPluginHost myHost = null;
private String[] args;
int OTOMaxSize = 10;
double minOTOHeight = 0;
/**
* Used to retrieve the plugin tool's name. This is a short, unique name
* containing no spaces.
*
* @return String containing plugin name.
*/
@Override
public String getName() {
return "RemoveOffTerrainObjects";
}
/**
* Used to retrieve the plugin tool's descriptive name. This can be a longer
* name (containing spaces) and is used in the interface to list the tool.
*
* @return String containing the plugin descriptive name.
*/
@Override
public String getDescriptiveName() {
return "Remove Off-Terrain Objects";
}
/**
* Used to retrieve a short description of what the plugin tool does.
*
* @return String containing the plugin's description.
*/
@Override
public String getToolDescription() {
return "Removes off-terrain objects like buildings from DEMs.";
}
/**
* Used to identify which toolboxes this plugin tool should be listed in.
*
* @return Array of Strings.
*/
@Override
public String[] getToolbox() {
String[] ret = {"TerrainAnalysis", "LidarTools"};
return ret;
}
/**
* Sets the WhiteboxPluginHost to which the plugin tool is tied. This is the
* class that the plugin will send all feedback messages, progress updates,
* and return objects.
*
* @param host The WhiteboxPluginHost that called the plugin tool.
*/
@Override
public void setPluginHost(WhiteboxPluginHost host) {
myHost = host;
}
/**
* Used to communicate feedback pop-up messages between a plugin tool and
* the main Whitebox user-interface.
*
* @param feedback String containing the text to display.
*/
private void showFeedback(String message) {
if (myHost != null) {
myHost.showFeedback(message);
} else {
System.out.println(message);
}
}
/**
* Used to communicate a return object from a plugin tool to the main
* Whitebox user-interface.
*
* @return Object, such as an output WhiteboxRaster.
*/
private void returnData(Object ret) {
if (myHost != null) {
myHost.returnData(ret);
}
}
private int previousProgress = 0;
private String previousProgressLabel = "";
/**
* Used to communicate a progress update between a plugin tool and the main
* Whitebox user interface.
*
* @param progressLabel A String to use for the progress label.
* @param progress Float containing the progress value (between 0 and 100).
*/
private void updateProgress(String progressLabel, int progress) {
if (myHost != null && ((progress != previousProgress)
|| (!progressLabel.equals(previousProgressLabel)))) {
myHost.updateProgress(progressLabel, progress);
}
previousProgress = progress;
previousProgressLabel = progressLabel;
}
/**
* Used to communicate a progress update between a plugin tool and the main
* Whitebox user interface.
*
* @param progress Float containing the progress value (between 0 and 100).
*/
private void updateProgress(int progress) {
if (myHost != null && progress != previousProgress) {
myHost.updateProgress(progress);
}
previousProgress = progress;
}
/**
* Sets the arguments (parameters) used by the plugin.
*
* @param args An array of string arguments.
*/
@Override
public void setArgs(String[] args) {
this.args = args.clone();
}
private boolean cancelOp = false;
/**
* Used to communicate a cancel operation from the Whitebox GUI.
*
* @param cancel Set to true if the plugin should be canceled.
*/
@Override
public void setCancelOp(boolean cancel) {
cancelOp = cancel;
}
private void cancelOperation() {
showFeedback("Operation cancelled.");
updateProgress("Progress: ", 0);
}
private boolean amIActive = false;
/**
* Used by the Whitebox GUI to tell if this plugin is still running.
*
* @return a boolean describing whether or not the plugin is actively being
* used.
*/
@Override
public boolean isActive() {
return amIActive;
}
/**
* Used to execute this plugin tool.
*/
@Override
public void run() {
amIActive = true;
String inputHeader = null;
String outputHeader = null;
String outputData = null;
WhiteboxRaster DEMGrid = null;
String inputFilesString = null;
String suffix = "no OTOs";
String[] gridFiles;
WhiteboxRaster output = null;
int row, col;
int progress = 0;
double noData = 0;
int rows, cols;
int a, b;
double z1, z2, w1;
double[][] subGridDEM;
int colOffset, rowOffset;
boolean flag;
double minEdgeSlope = 0;
int loopNum = 0;
int numCellsChanged = 0;
int prevNumCellsChanged = 0;
boolean[] activeTile = new boolean[1];
int[][] tileCorners = new int[2][1];
boolean didSomethingHappen = false;
int currentTile, numTiles = 0;
long numValidCells = 0;
double cumulativeChange = 0;
boolean iterateRemoveOTOs = false;
try {
// read the input parameters
if (args.length <= 0) {
showFeedback("Plugin parameters have not been set.");
return;
}
inputFilesString = args[0];
suffix = args[1];
OTOMaxSize = (int)(Math.round(Double.valueOf(args[2])));
int halfOTOMaxSize = OTOMaxSize / 2;
double[] data = new double[OTOMaxSize + 2];
minEdgeSlope = Double.valueOf(args[3]);
iterateRemoveOTOs = Boolean.parseBoolean(args[4]);
if (suffix.equals("")) {
suffix = "no OTOs";
}
// check to see that the inputHeader and outputHeader are not null.
if ((inputFilesString == null)) {
showFeedback("One or more of the input parameters have not been set properly.");
return;
}
if (OTOMaxSize < 5) {
OTOMaxSize = 5;
}
gridFiles = inputFilesString.split(";");
int numPointFiles = gridFiles.length;
for (int j = 0; j < numPointFiles; j++) {
inputHeader = gridFiles[j];
outputHeader = gridFiles[j].replace(".dep", " " + suffix + ".dep");
outputData = outputHeader.replace(".dep", ".tas");
String tempHeaderFile = inputHeader.replace(".dep", "_temp.dep");
String tempDataFile = inputHeader.replace(".dep", "_temp.tas");
FileUtilities.copyFile(new File(inputHeader), new File(tempHeaderFile));
FileUtilities.copyFile(new File(inputHeader.replace(".dep", ".tas")), new File(tempDataFile));
do {
numValidCells = 0;
loopNum++;
prevNumCellsChanged = numCellsChanged;
numCellsChanged = 0;
DEMGrid = new WhiteboxRaster(tempHeaderFile, "rw");
rows = DEMGrid.getNumberRows();
cols = DEMGrid.getNumberColumns();
noData = DEMGrid.getNoDataValue();
double resolution = (DEMGrid.getCellSizeX() + DEMGrid.getCellSizeY()) / 2;
if (loopNum == 1) {
minOTOHeight = Math.tan(minEdgeSlope * Math.PI / 180) * resolution;
if (minOTOHeight < 0) {
minOTOHeight = 0;
}
}
cumulativeChange = 0;
updateProgress("DEM " + (j + 1) + " Loop number " + loopNum + ":", -1);
//***********************************************************************
// If this is the first time around, trim the peaks that intersect the
// edges and figure out how many tiles there are.
//***********************************************************************
if (loopNum == 1) {
FileUtilities.copyFile(new File(tempHeaderFile), new File(outputHeader));
FileUtilities.copyFile(new File(tempDataFile), new File(outputData));
output = new WhiteboxRaster(outputHeader, "rw");
double minVal = output.getMinimumValue();
for (col = 0; col < cols; col++) {
if (output.getValue(0, col) != noData) {
output.setValue(0, col, minVal);
}
if (output.getValue(rows - 1, col) != noData) {
output.setValue(rows - 1, col, minVal);
}
}
for (row = 0; row < rows; row++) {
if (output.getValue(row, 0) != noData) {
output.setValue(row, 0, minVal);
}
if (output.getValue(row, cols - 1) != noData) {
output.setValue(row, cols - 1, minVal);
}
}
// // first trim the edge-intersecting peaks.
// colOffset = 0;
// flag = true;
// do {
// // top row
// data[0] = noData;
// data[OTOMaxSize + 1] = noData;
// a = 0;
// for (col = colOffset; col < colOffset + OTOMaxSize; col++) {
// data[a + 1] = output.getValue(0, col);
// if (data[a + 1] != noData) { numValidCells++; }
// a++;
// }
//
// if (numValidCells > 2) {
// cleavePeaks1D(data, noData);
// }
//
// a = 0;
// for (col = colOffset; col < colOffset + OTOMaxSize; col++) {
// z1 = output.getValue(0, col);
// z2 = data[a + 1];
// if (z2 < z1) {
// output.setValue(0, col, z2);
// }
// a++;
// }
//
// // bottom row
// data[0] = noData;
// data[OTOMaxSize + 1] = noData;
// numValidCells = 0;
// a = 0;
// for (col = colOffset; col < colOffset + OTOMaxSize; col++) {
// data[a + 1] = output.getValue(rows - 1, col);
// if (data[a + 1] != noData) { numValidCells++; }
// a++;
// }
//
// if (numValidCells > 2) {
// cleavePeaks1D(data, noData);
// }
//
// a = 0;
// for (col = colOffset; col < colOffset + OTOMaxSize; col++) {
// z1 = output.getValue(rows - 1, col);
// z2 = data[a + 1];
// if (z2 < z1) {
// output.setValue(rows - 1, col, z2);
// }
// a++;
// }
//
// colOffset += halfOTOMaxSize;
// if (colOffset > cols - 1) {
// flag = false;
// }
// } while (flag);
//
// rowOffset = 0;
// flag = true;
// do {
// // left column
// data[0] = noData;
// data[OTOMaxSize + 1] = noData;
// numValidCells = 0;
// a = 0;
// for (row = rowOffset; row < rowOffset + OTOMaxSize; row++) {
// data[a + 1] = output.getValue(row, 0);
// if (data[a + 1] != noData) { numValidCells++; }
// a++;
// }
//
// if (numValidCells > 2) {
// cleavePeaks1D(data, noData);
// }
//
// a = 0;
// for (row = rowOffset; row < rowOffset + OTOMaxSize; row++) {
// z1 = output.getValue(row, 0);
// z2 = data[a + 1];
// if (z2 < z1) {
// output.setValue(row, 0, z2);
// }
// a++;
// }
//
// // right coloumn
// data[0] = noData;
// data[OTOMaxSize + 1] = noData;
// numValidCells = 0;
// a = 0;
// for (row = rowOffset; row < rowOffset + OTOMaxSize; row++) {
// data[a + 1] = output.getValue(row, cols - 1);
// if (data[a + 1] != noData) { numValidCells++; }
// a++;
// }
//
// if (numValidCells > 2) {
// cleavePeaks1D(data, noData);
// }
//
// a = 0;
// for (row = rowOffset; row < rowOffset + OTOMaxSize; row++) {
// z1 = output.getValue(row, cols - 1);
// z2 = data[a + 1];
// if (z2 < z1) {
// output.setValue(row, cols - 1, z2);
// }
// a++;
// }
//
// rowOffset += halfOTOMaxSize;
// if (rowOffset > rows - 1) {
// flag = false;
// }
// } while (flag);
colOffset = -1; //Why minus one? It's because if we don't create sub-grids that are just beyond the edge of
//the DEM, then we won't be able to find any OTOs that intersect top and left edges of the DEM.
rowOffset = -1;
flag = true;
numTiles = -1;
do {
numTiles++;
//move the SubGrids over by half the OTOMaxSize value
colOffset += halfOTOMaxSize;
if (colOffset > cols - 1) {
colOffset = -1;
rowOffset += halfOTOMaxSize;
if (rowOffset > rows - 1) {
flag = false;
}
}
} while (flag);
activeTile = new boolean[numTiles + 1];
tileCorners = new int[2][numTiles + 1];
colOffset = -1;
rowOffset = -1;
flag = true;
currentTile = -1;
do {
currentTile++;
activeTile[currentTile] = true; // All tiles are initially active.
tileCorners[0][currentTile] = colOffset;
tileCorners[1][currentTile] = rowOffset;
//move the SubGrids over by half the OTOMaxSize value
colOffset += halfOTOMaxSize;
if (colOffset > cols - 1) {
colOffset = -1;
rowOffset += halfOTOMaxSize;
if (rowOffset > rows - 1) {
flag = false;
}
}
} while (flag);
} else {
output = new WhiteboxRaster(outputHeader, "rw");
}
//**************************************************
// Find all of the peaks in each tile
//**************************************************
colOffset = -1; //Why minus one? It's because if we don't
//create sub-grids that are just beyond the edge of
//the DEM, then we won't be able to find any OTOs that
//intersect top and left edges of the DEM.
rowOffset = -1;
flag = true;
currentTile = -1;
do {
currentTile++;
if (activeTile[currentTile]) { // If a previous scan did not
//identify any peaks, don't bother looking this time either.
// subGridDEM will have a buffer row of noData values around it.
// subGridCleavedDEM will not have this buffer.
subGridDEM = new double[OTOMaxSize + 2][OTOMaxSize + 2];
for (row = 0; row < OTOMaxSize + 2; row++) {
subGridDEM[row][0] = noData;
subGridDEM[row][OTOMaxSize + 1] = noData;
}
for (col = 0; col < OTOMaxSize + 2; col++) {
subGridDEM[0][col] = noData;
subGridDEM[OTOMaxSize + 1][col] = noData;
}
//fill subGridDEM with the DEM data for this tile
numValidCells = 0;
a = 0; // the column number in the subgrids
b = 0; // the row number in the subgrids
for (row = rowOffset; row < rowOffset + OTOMaxSize; row++) {
for (col = colOffset; col < colOffset + OTOMaxSize; col++) {
subGridDEM[b + 1][a + 1] = DEMGrid.getValue(row, col);
if (subGridDEM[b + 1][a + 1] != noData) { numValidCells++; }
a++;
if (a == OTOMaxSize) {
a = 0;
}
}
b++;
}
if (numValidCells > 3) {
cleavePeaks2D(subGridDEM, noData);
}
//update the outputgrid
a = 0;
b = 0;
didSomethingHappen = false;
for (row = rowOffset; row < rowOffset + OTOMaxSize; row++) {
for (col = colOffset; col < colOffset + OTOMaxSize; col++) {
z2 = subGridDEM[b + 1][a + 1];
w1 = output.getValue(row, col);
if (z2 < w1) { // have to compensate for precision errors in representing decimal digits.
output.setValue(row, col, z2);
cumulativeChange += (w1 - z2) * (w1 - z2);
didSomethingHappen = true;
numCellsChanged++;
}
a++;
if (a == OTOMaxSize) {
a = 0;
}
}
b++;
}
if (!didSomethingHappen) {
activeTile[currentTile] = false;
}
}
//move the SubGrids over by half the OTOMaxSize value
colOffset += halfOTOMaxSize;
if (colOffset > cols - 1) {
colOffset = -1;
rowOffset += halfOTOMaxSize;
if (rowOffset > rows - 1) {
flag = false;
}
if (cancelOp) {
cancelOperation();
return;
}
//progress = (int)(rowOffset * 100d / (rows - 1));
//updateProgress((int) progress);
}
progress = (int) (currentTile * 100d / (numTiles - 1d)); //(rowOffset * 100d / (rows - 1));
updateProgress((int) progress);
} while (flag);
DEMGrid.close();
output.close();
if (numCellsChanged > 0) {
FileUtilities.copyFile(new File(outputHeader), new File(tempHeaderFile));
FileUtilities.copyFile(new File(outputHeader.replace(".dep", ".tas")), new File(tempDataFile));
}
} while ((numCellsChanged > 0) && (numCellsChanged != prevNumCellsChanged)
&& (loopNum < 501) && (cumulativeChange > 0.5) && iterateRemoveOTOs);
if ((new File(tempHeaderFile)).exists()) {
(new File(tempHeaderFile)).delete();
}
if ((new File(tempDataFile)).exists()) {
(new File(tempDataFile)).delete();
}
}
// returning a header file string displays the image.
returnData(gridFiles[0].replace(".dep", " " + suffix + ".dep"));
} 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();
}
}
private void cleavePeaks2D(double[][] input, double noData) {
try {
int row_n, col_n;
int row, col;
double z_n, z1, z2;
//long k = 0;
GridCell gc = null;
double z;
int[] Dy = {-1, 0, 1, 1, 1, 0, -1, -1};
int[] Dx = {1, 1, 1, 0, -1, -1, -1, 0};
boolean flag = false;
double initialValue = Double.NEGATIVE_INFINITY;
double[][] output = new double[OTOMaxSize][OTOMaxSize];
boolean somethingDone = false;
int i;
//*******************************************
// initialize and fill the priority queue.
//*******************************************
PriorityQueue<GridCell> queue = new PriorityQueue<>(OTOMaxSize * OTOMaxSize);
for (row = 0; row < OTOMaxSize; row++) {
for (col = 0; col < OTOMaxSize; col++) {
output[row][col] = initialValue;
z = input[row + 1][col + 1];
if (z != noData) {
flag = false;
for (i = 0; i < 8; i++) {
row_n = row + Dy[i];
col_n = col + Dx[i];
z_n = input[row_n + 1][col_n + 1];
if (z_n == noData) {
// it's an edge cell.
flag = true;
}
}
if (flag) {
gc = new GridCell(row, col, -z);
queue.add(gc);
output[row][col] = z;
}
} else {
//k++;
output[row][col] = noData;
}
}
if (cancelOp) {
cancelOperation();
return;
}
}
//*******************************************
// now cleave peaks in the subgrid
//*******************************************
do {
gc = queue.poll();
row = gc.row;
col = gc.col;
z = -gc.z;
for (i = 0; i < 8; i++) {
row_n = row + Dy[i];
col_n = col + Dx[i];
z_n = input[row_n + 1][col_n + 1];
if ((z_n != noData) && (output[row_n][col_n] == initialValue)) {
if (z_n >= z) {
z_n = z;
somethingDone = true;
}
output[row_n][col_n] = z_n;
gc = new GridCell(row_n, col_n, -z_n);
queue.add(gc);
}
}
//k++;
} while (queue.isEmpty() == false);
if (somethingDone) {
//*******************************************
// flag the altered cells
//*******************************************
byte[][] modifiedCells = new byte[OTOMaxSize][OTOMaxSize];
for (row = 0; row < OTOMaxSize; row++) {
for (col = 0; col < OTOMaxSize; col++) {
z1 = output[row][col];
z2 = input[row + 1][col + 1];
if (z1 < z2) {
if ((z2 - z1) < minOTOHeight) {
modifiedCells[row][col] = 2; //it has been lowered by less than the required amount to be considered an OTO
} else {
modifiedCells[row][col] = 1; //it has been lowered by more than the required amount to be considered an OTO
}
}
}
}
//*******************************************
// scan the grid adding back the shallow-sloped hills.
//*******************************************
double minOTOHeightSqr = minOTOHeight * minOTOHeight;
double Dz = 0;
byte d = 0;
do {
flag = false;
if (d > 3) {
d = 0;
}
switch (d) {
case 0:
for (row = 0; row < OTOMaxSize; row++) {
for (col = 0; col < OTOMaxSize; col++) {
if (modifiedCells[row][col] == 2) {
//scan the eight neighbours of cell (row, col)
for (i = 0; i < 8; i++) {
row_n = row + Dy[i];
col_n = col + Dx[i];
if (row_n >= 0 && row_n < OTOMaxSize
&& col_n >= 0 && col_n < OTOMaxSize) {
if (modifiedCells[row_n][col_n] == 1) {
Dz = (input[row_n + 1][col_n + 1] - input[row + 1][col + 1])
* (input[row_n + 1][col_n + 1] - input[row + 1][col + 1]);
if (Dz < minOTOHeightSqr) {
modifiedCells[row_n][col_n] = 2;
flag = true;
}
}
}
}
}
}
}
case 1:
for (row = OTOMaxSize - 1; row >= 0; row--) {
for (col = OTOMaxSize - 1; col >= 0; col--) {
if (modifiedCells[row][col] == 2) {
//scan the eight neighbours of cell (row, col)
for (i = 0; i < 8; i++) {
row_n = row + Dy[i];
col_n = col + Dx[i];
if (row_n >= 0 && row_n < OTOMaxSize
&& col_n >= 0 && col_n < OTOMaxSize) {
if (modifiedCells[row_n][col_n] == 1) {
Dz = (input[row_n + 1][col_n + 1] - input[row + 1][col + 1])
* (input[row_n + 1][col_n + 1] - input[row + 1][col + 1]);
if (Dz < minOTOHeightSqr) {
modifiedCells[row_n][col_n] = 2;
flag = true;
}
}
}
}
}
}
}
case 2:
for (row = OTOMaxSize - 1; row >= 0; row--) {
for (col = 0; col < OTOMaxSize; col++) {
if (modifiedCells[row][col] == 2) {
//scan the eight neighbours of cell (row, col)
for (i = 0; i < 8; i++) {
row_n = row + Dy[i];
col_n = col + Dx[i];
if (row_n >= 0 && row_n < OTOMaxSize
&& col_n >= 0 && col_n < OTOMaxSize) {
if (modifiedCells[row_n][col_n] == 1) {
Dz = (input[row_n + 1][col_n + 1] - input[row + 1][col + 1])
* (input[row_n + 1][col_n + 1] - input[row + 1][col + 1]);
if (Dz < minOTOHeightSqr) {
modifiedCells[row_n][col_n] = 2;
flag = true;
}
}
}
}
}
}
}
default: // 3
for (row = 0; row < OTOMaxSize; row++) {
for (col = OTOMaxSize - 1; col >= 0; col--) {
if (modifiedCells[row][col] == 2) {
//scan the eight neighbours of cell (row, col)
for (i = 0; i < 8; i++) {
row_n = row + Dy[i];
col_n = col + Dx[i];
if (row_n >= 0 && row_n < OTOMaxSize
&& col_n >= 0 && col_n < OTOMaxSize) {
if (modifiedCells[row_n][col_n] == 1) {
Dz = (input[row_n + 1][col_n + 1] - input[row + 1][col + 1])
* (input[row_n + 1][col_n + 1] - input[row + 1][col + 1]);
if (Dz < minOTOHeightSqr) {
modifiedCells[row_n][col_n] = 2;
flag = true;
}
}
}
}
}
}
}
}
d++;
} while (flag);
//*******************************************
// Copy the output grid back into the intput grid.
//*******************************************
for (row = 0; row < OTOMaxSize; row++) {
for (col = 0; col < OTOMaxSize; col++) {
if (modifiedCells[row][col] == 1) {
input[row + 1][col + 1] = initialValue; //output[row][col];
}
}
}
//*******************************************
// Interpolate new values.
//*******************************************
int numEdges = 0;
for (row = 0; row < OTOMaxSize; row++) {
for (col = 0; col < OTOMaxSize; col++) {
if (modifiedCells[row][col] != 1) {
// does it have a modified neighbour?
for (i = 0; i < 8; i++) {
row_n = row + Dy[i];
col_n = col + Dx[i];
if (row_n >= 0 && row_n < OTOMaxSize
&& col_n >= 0 && col_n < OTOMaxSize) {
if (modifiedCells[row_n][col_n] == 1) {
modifiedCells[row][col] = 3;
numEdges++;
}
}
}
}
}
}
KdTree<Double> tree = new KdTree.SqrEuclid<>(2, numEdges);
for (row = 0; row < OTOMaxSize; row++) {
for (col = 0; col < OTOMaxSize; col++) {
if (modifiedCells[row][col] == 3) {
double[] entry = {row, col};
tree.addPoint(entry, input[row + 1][col + 1]);
}
}
}
List<KdTree.Entry<Double>> results;
double sumWeights;
for (row = 0; row < OTOMaxSize; row++) {
for (col = 0; col < OTOMaxSize; col++) {
if (modifiedCells[row][col] == 1) {
double[] entry = {row, col};
results = tree.nearestNeighbor(entry, 12, true);
sumWeights = 0;
for (i = 0; i < results.size(); i++) {
sumWeights += 1 / (results.get(i).distance * results.get(i).distance);
}
z = 0;
for (i = 0; i < results.size(); i++) {
z += (1 / (results.get(i).distance * results.get(i).distance)) / sumWeights * results.get(i).value;
}
input[row + 1][col + 1] = z;
}
}
}
}
} 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);
}
}
private void cleavePeaks1D(double[] input, double noData) {
try {
int row_n;
int row;
double z_n, z1, z2;
int[] Dy = {1, -1};
GridCell gc = null;
double z;
boolean flag = false;
double initialValue = Double.NEGATIVE_INFINITY;
double[] output = new double[OTOMaxSize];
boolean somethingDone = false;
int i;
//*******************************************
// initialize and fill the priority queue.
//*******************************************
PriorityQueue<GridCell> queue = new PriorityQueue<>(OTOMaxSize);
for (row = 0; row < OTOMaxSize; row++) {
output[row] = initialValue;
z = input[row + 1];
if (z != noData) {
flag = false;
for (i = 0; i < 2; i++) {
row_n = row + Dy[i];
z_n = input[row_n + 1];
if (z_n == noData) {
// it's an edge cell.
flag = true;
}
}
if (flag) {
gc = new GridCell(row, 0, -z);
queue.add(gc);
output[row] = z;
}
} else {
output[row] = noData;
}
}
//*******************************************
// now cleave peaks in the subgrid
//*******************************************
do {
gc = queue.poll();
row = gc.row;
z = -gc.z;
for (i = 0; i < 2; i++) {
row_n = row + Dy[i];
z_n = input[row_n + 1];
if ((z_n != noData) && (output[row_n] == initialValue)) {
if (z_n >= z) {
z_n = z;
somethingDone = true;
}
output[row_n] = z_n;
gc = new GridCell(row_n, 0, -z_n);
queue.add(gc);
}
}
} while (queue.isEmpty() == false);
if (somethingDone) {
//*******************************************
// flag the altered cells
//*******************************************
byte[] modifiedCells = new byte[OTOMaxSize];
for (row = 0; row < OTOMaxSize; row++) {
z1 = output[row];
z2 = input[row + 1];
if (z1 < z2) {
if ((z2 - z1) < minOTOHeight) {
modifiedCells[row] = 2; //it has been lowered by less than the required amount to be considered an OTO
} else {
modifiedCells[row] = 1; //it has been lowered by more than the required amount to be considered an OTO
}
}
}
//*******************************************
// scan the line adding back the shallow-sloped hills.
//*******************************************
double minOTOHeightSqr = minOTOHeight * minOTOHeight;
double Dz = 0;
byte d = 0;
do {
flag = false;
if (d > 1) {
d = 0;
}
switch (d) {
case 0:
for (row = 0; row < OTOMaxSize; row++) {
if (modifiedCells[row] == 2) {
for (i = 0; i < 2; i++) {
row_n = row + Dy[i];
if (row_n >= 0 && row_n < OTOMaxSize) {
if (modifiedCells[row_n] == 1) {
Dz = (input[row_n + 1] - input[row + 1])
* (input[row_n + 1] - input[row + 1]);
if (Dz < minOTOHeightSqr) {
modifiedCells[row_n] = 2;
flag = true;
}
}
}
}
}
}
default: // 1
for (row = OTOMaxSize - 1; row >= 0; row--) {
if (modifiedCells[row] == 2) {
for (i = 0; i < 2; i++) {
row_n = row + Dy[i];
if (row_n >= 0 && row_n < OTOMaxSize) {
if (modifiedCells[row_n] == 1) {
Dz = (input[row_n + 1] - input[row + 1])
* (input[row_n + 1] - input[row + 1]);
if (Dz < minOTOHeightSqr) {
modifiedCells[row_n] = 2;
flag = true;
}
}
}
}
}
}
}
d++;
} while (flag);
//*******************************************
// Copy the output grid back into the intput grid.
//*******************************************
// for (row = 0; row < OTOMaxSize; row++) {
// if (modifiedCells[row] == 1) {
// input[row + 1] = initialValue;
// }
// }
//*******************************************
// Interpolate new values.
//*******************************************
int numEdges = 0;
for (row = 0; row < OTOMaxSize; row++) {
if (modifiedCells[row] != 1 && input[row + 1] != noData) {
// does it have a modified neighbour?
for (i = 0; i < 2; i++) {
row_n = row + Dy[i];
if (row_n >= 0 && row_n < OTOMaxSize) {
if (modifiedCells[row_n] == 1) {
modifiedCells[row] = 3;
numEdges++;
}
}
}
}
}
KdTree<Double> tree = new KdTree.SqrEuclid<>(1, numEdges);
for (row = 0; row < OTOMaxSize; row++) {
if (modifiedCells[row] == 3) {
double[] entry = {row};
tree.addPoint(entry, input[row + 1]);
}
}
List<KdTree.Entry<Double>> results;
double sumWeights;
for (row = 0; row < OTOMaxSize; row++) {
if (modifiedCells[row] == 1) {
double[] entry = {row};
results = tree.nearestNeighbor(entry, 2, true);
sumWeights = 0;
for (i = 0; i < results.size(); i++) {
sumWeights += 1 / (results.get(i).distance);
}
z = 0;
for (i = 0; i < results.size(); i++) {
z += (1 / (results.get(i).distance)) / sumWeights * results.get(i).value;
}
input[row + 1] = z;
}
}
}
} 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);
}
}
class GridCell implements Comparable<GridCell> {
public int row;
public int col;
public double z;
public GridCell(int Row, int Col, double Z) {
row = Row;
col = Col;
z = Z;
}
@Override
public int compareTo(GridCell cell) {
final int BEFORE = -1;
final int EQUAL = 0;
final int AFTER = 1;
if (this.z < cell.z) {
return BEFORE;
} else if (this.z > cell.z) {
return AFTER;
}
if (this.row < cell.row) {
return BEFORE;
} else if (this.row > cell.row) {
return AFTER;
}
if (this.col < cell.col) {
return BEFORE;
} else if (this.col > cell.col) {
return AFTER;
}
return EQUAL;
}
}
// this is only used for debugging the tool
public static void main(String[] args) {
RemoveOffTerrainObjects me = new RemoveOffTerrainObjects();
args = new String[5];
// args[0] = "/Users/johnlindsay/Documents/research/OTOpaper/Take3/data/Rondeau/420_4687_NN_filled.dep";
args[0] = "/Users/johnlindsay/Documents/research/OTOpaper/Take3/data/PEC/Picton data/1km183270487302008GROUPEALTA_filled.dep";
args[1] = "no OTOs";
args[2] = "350";
args[3] = "7.5";
args[4] = "true";
me.setArgs(args);
me.run();
}
}