/*
* Copyright (C) 2014 Jan Seibert (jan.seibert@geo.uzh.ch) and
* Marc Vis (marc.vis@geo.uzh.ch)
*
* 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.awt.HeadlessException;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JOptionPane;
import whitebox.geospatialfiles.WhiteboxRaster;
import whitebox.interfaces.WhiteboxPlugin;
import whitebox.interfaces.WhiteboxPluginHost;
/**
* This tool can be used to extract grid values at specified XY-coordinates.
* @author Dr. John Lindsay email: jlindsay@uoguelph.ca
*/
public class ExtractValuesAtXYCoords implements WhiteboxPlugin {
private WhiteboxPluginHost myHost = null;
private String[] args;
double gridRes = 1;
/**
* 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 "ExtractValuesAtXYCoords";
}
/**
* 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 "Extract values at XY coordinates";
}
/**
* Used to retrieve a short description of what the plugin tool does.
* @return String containing the plugin's description.
*/
@Override
public String getToolDescription() {
return "Writes raster values at specified XY coordinates (and neighbouring statistics) to a text file.";
}
/**
* Used to identify which toolboxes this plugin tool should be listed in.
* @return Array of Strings.
*/
@Override
public String[] getToolbox() {
String[] ret = { "StatisticalTools" };
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 inputTextFile = null;
String inputRasterFiles = null;
String outputTextFile = null;
boolean interpolateValues = false;
boolean includeStatistics = false;
String[] gatHeaderFile;
List<WhiteboxRaster> gatGrids = new ArrayList<>();
String fileName;
int numberOfLines = 0;
int counter;
String tempLine;
String[] line;
int id;
double xCoord;
double yCoord;
String name;
int xGridcell;
int yGridcell;
double deltaX;
double deltaY;
int dXi;
int dYi;
double average;
double w;
double[] ww = new double[4];
double value;
int[] xd = new int[]{0, -1, -1, -1, 0, 1, 1, 1};
int[] yd = new int[]{-1, -1, 0, 1, 1, 1, 0, -1};
double min;
double max;
double sum;
int sumCount;
double mean = 0;
double neighbourValue;
FileWriter streamWriter = null;
String outputLine;
float progress = 0;
if (args.length <= 0) {
showFeedback("Plugin parameters have not been set.");
return;
}
for (int i = 0; i < args.length; i++) {
if (i == 0) {
inputTextFile = args[i];
} else if (i == 1) {
inputRasterFiles = args[i];
} else if (i == 2) {
outputTextFile = args[i];
} else if (i == 3) {
interpolateValues = Boolean.parseBoolean(args[i]);
} else if (i == 4) {
includeStatistics = Boolean.parseBoolean(args[i]);
}
}
// check to see that the inputHeader and outputHeader are not null.
if ((inputTextFile == null) || (inputRasterFiles == null) || (outputTextFile == null)) {
showFeedback("One or more of the input parameters have not been set properly.");
return;
}
try {
// Generate a list with GATGrid objects
gatHeaderFile = inputRasterFiles.split(";");
for (int i = 0; i <= gatHeaderFile.length - 1; i++) {
WhiteboxRaster gatGrid = new WhiteboxRaster(gatHeaderFile[i], "r");
gatGrids.add(gatGrid);
}
// Create streamReader and StreamWriter
BufferedReader bufferedReader = new BufferedReader(new FileReader(inputTextFile));
streamWriter = new FileWriter(outputTextFile, false);
// Generate the header line of the output file
outputLine = "ID" + "\t" + "X" + "\t" + "Y";
for (WhiteboxRaster gatGrid : gatGrids) {
fileName = gatGrid.getShortHeaderFile();
outputLine = outputLine + "\t" + fileName;
if (includeStatistics) {
outputLine = outputLine + "\t" + fileName + "_min" + "\t" + fileName + "_max" + "\t" + fileName + "_mean";
}
}
outputLine = outputLine + "\t" + "Name";
streamWriter.write(outputLine + System.lineSeparator());
// Get the number of lines in the input file
while ((tempLine = bufferedReader.readLine()) != null) {
numberOfLines = numberOfLines + 1;
}
// Reset the streamreader to the beginning of the file
bufferedReader.close();
bufferedReader = new BufferedReader(new FileReader(inputTextFile));
// Read the headerline of the input file
tempLine = bufferedReader.readLine();
counter = 1;
// Loop through all other lines of the input file
while ((tempLine = bufferedReader.readLine()) != null) {
line = tempLine.split("\t");
counter = counter + 1;
if ((line.length != 3) && (line.length != 4)) {
JOptionPane.showMessageDialog(null, "Error in input file. Line " + counter + " contains an unexpected number of elements.");
return;
}
if (! IsInteger(line[0]) || ! IsDouble(line[1]) || ! IsDouble(line[2])) {
JOptionPane.showMessageDialog(null, "Error in input file. Line " + counter + " contains a value of an expected type.");
return;
}
id = Integer.parseInt(line[0]);
xCoord = Double.parseDouble(line[1]);
yCoord = Double.parseDouble(line[2]);
if (line.length == 4) {
name = line[3];
} else {
name = "";
}
outputLine = id + "\t" + xCoord + "\t" + yCoord;
for (WhiteboxRaster gatGrid : gatGrids) {
gridRes = gatGrid.getCellSizeX();
xGridcell = (int)((xCoord - gatGrid.getWest()) / gridRes);
yGridcell = (int)((yCoord - gatGrid.getSouth()) / gridRes);
deltaX = xCoord - ((xGridcell + 0.5) * gridRes + gatGrid.getWest());
deltaY = yCoord - ((yGridcell + 0.5) * gridRes + gatGrid.getSouth());
// Convert the yGridcell value, since gatGrid has its (0,0) coordinate in the upper left corner.
yGridcell = InvertYCoord(gatGrid, yGridcell);
if (deltaX > 0) {
dXi = 1;
} else {
dXi = -1;
}
if (deltaY > 0) {
dYi = -1;
} else {
dYi = 1;
}
deltaX = Math.abs(deltaX);
deltaY = Math.abs(deltaY);
if (interpolateValues) {
// Interpolate the values of the 4 grid cells closest to the user specified XY-coordinate.
if ((gatGrid.getValue(yGridcell, xGridcell) == gatGrid.getNoDataValue()) || (gatGrid.getValue(yGridcell + dYi, xGridcell) == gatGrid.getNoDataValue()) || (gatGrid.getValue(yGridcell, xGridcell + dXi) == gatGrid.getNoDataValue()) || (gatGrid.getValue(yGridcell + dYi, xGridcell + dXi) == gatGrid.getNoDataValue())) {
value = gatGrid.getNoDataValue();
} else if (deltaX==0 && deltaY==0) {
value = gatGrid.getValue(yGridcell, xGridcell);
} else {
ww[0] = 1 / Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
average = gatGrid.getValue(yGridcell, xGridcell) * ww[0];
w = ww[0];
ww[1] = 1 / Math.sqrt(Math.pow(deltaX, 2) + Math.pow(gridRes - deltaY, 2));
average = average + gatGrid.getValue(yGridcell + dYi, xGridcell) * ww[1];
w = w + ww[1];
ww[2] = 1 / Math.sqrt(Math.pow(gridRes - deltaX, 2) + Math.pow(deltaY, 2));
average = average + gatGrid.getValue(yGridcell, xGridcell + dXi) * ww[2];
w = w + ww[2];
ww[3] = 1 / Math.sqrt(Math.pow(gridRes - deltaX, 2) + Math.pow(gridRes - deltaY, 2));
average = average + gatGrid.getValue(yGridcell + dYi, xGridcell + dXi) * ww[3];
w = w + ww[3];
value = average / w;
}
} else {
value = gatGrid.getValue(yGridcell, xGridcell);
}
outputLine = outputLine + "\t" + value;
if (includeStatistics) {
// Compute min, max and mean values for the block of 3x3 cells around the XY-coordinate
value = gatGrid.getValue(yGridcell, xGridcell);
if (value == gatGrid.getNoDataValue()) {
min = Double.MAX_VALUE;
max = Double.MIN_VALUE;
sum = 0;
sumCount = 0;
mean = gatGrid.getNoDataValue();
} else {
min = value;
max = value;
sum = value;
sumCount = 1;
}
for (int c = 0; c < 8; c++) {
neighbourValue = gatGrid.getValue(yGridcell + yd[c], xGridcell + xd[c]);
if (neighbourValue != gatGrid.getNoDataValue()) {
if (neighbourValue < min) {
min = neighbourValue;
}
if (neighbourValue > max) {
max = neighbourValue;
}
sum = sum + neighbourValue;
sumCount = sumCount + 1;
}
}
if (sumCount == 0) {
min = gatGrid.getNoDataValue();
max = gatGrid.getNoDataValue();
} else {
mean = sum / sumCount;
}
outputLine = outputLine + "\t" + min + "\t" + max + "\t" + mean;
}
}
if (name != "") {
outputLine = outputLine + "\t" + name;
}
streamWriter.write(outputLine + System.lineSeparator());
if (cancelOp) {
cancelOperation();
return;
}
progress = (float) (100f * counter / numberOfLines);
updateProgress("", (int) progress);
}
for (WhiteboxRaster gatGrid : gatGrids) {
gatGrid.close();
}
streamWriter.close();
} catch (IOException | HeadlessException | NumberFormatException e) {
showFeedback(e.getMessage());
} finally {
updateProgress("Progress: ", 0);
// tells the main application that this process is completed.
amIActive = false;
myHost.pluginComplete();
}
}
public boolean IsInteger(String input) {
try {
Integer.parseInt(input);
return true;
}
catch (Exception e) {
return false;
}
}
public boolean IsDouble(String input) {
try {
Double.parseDouble(input);
return true;
}
catch (Exception e) {
return false;
}
}
private int InvertYCoord(WhiteboxRaster gatGrid, int coord) {
return (gatGrid.getNumberRows() - coord - 1);
}
}