org.jgrasstools.gears.libs.modules.GridNode Maven / Gradle / Ivy
The newest version!
/*
* This file is part of JGrasstools (http://www.jgrasstools.org)
* (C) HydroloGIS - www.hydrologis.com
*
* JGrasstools 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 .
*/
package org.jgrasstools.gears.libs.modules;
import static java.lang.Math.pow;
import static java.lang.Math.sqrt;
import static org.jgrasstools.gears.libs.modules.JGTConstants.doubleNovalue;
import java.util.ArrayList;
import java.util.List;
import javax.media.jai.iterator.RandomIter;
import org.jgrasstools.gears.utils.math.NumericsUtilities;
/**
* A node in the grid environment of a digital elevation model.
*
* @author Andrea Antonello (www.hydrologis.com)
*/
public class GridNode extends Node {
public final double elevation;
public final double xRes;
public final double yRes;
private boolean isPit = false;
private boolean isOutlet = false;
private double eElev;
private double enElev;
private double nElev;
private double nwElev;
private double wElev;
private double wsElev;
private double sElev;
private double seElev;
/**
* The constructor.
*
* @param elevationIter the elevation model raster iter.
* @param cols the cols of the raster.
* @param rows the rows of the raster.
* @param xRes the x resolution of the raster.
* @param yRes the y resolution of the raster.
* @param col the col of the current {@link GridNode node}.
* @param row the row of the current {@link GridNode node}.
*/
public GridNode( RandomIter elevationIter, int cols, int rows, double xRes, double yRes, int col, int row ) {
super(elevationIter, cols, rows, col, row);
this.xRes = xRes;
this.yRes = yRes;
if (isInRaster(col, row)) {
elevation = gridIter.getSampleDouble(col, row, 0);
if (JGTConstants.isNovalue(elevation)) {
isValid = false;
} else {
isValid = true;
}
} else {
elevation = doubleNovalue;
isValid = false;
}
double tmpMin = Double.POSITIVE_INFINITY;
int index = -1;
for( int c = -1; c <= 1; c++ ) {
for( int r = -1; r <= 1; r++ ) {
index++;
if (c == 0 && r == 0) {
continue;
}
int newC = col + c;
int newR = row + r;
double tmp = doubleNovalue;
if (!isInRaster(newC, newR)) {
touchesBound = true;
} else {
try {
tmp = gridIter.getSampleDouble(newC, newR, 0);
} catch (ArrayIndexOutOfBoundsException e) {
// touches - might be tiled, which needs better support
touchesBound = true;
}
}
switch( index ) {
case 0:
nwElev = tmp;
break;
case 1:
wElev = tmp;
break;
case 2:
wsElev = tmp;
break;
case 3:
nElev = tmp;
break;
case 4:
throw new RuntimeException();
case 5:
sElev = tmp;
break;
case 6:
enElev = tmp;
break;
case 7:
eElev = tmp;
break;
case 8:
seElev = tmp;
break;
default:
throw new RuntimeException();
}
if (JGTConstants.isNovalue(tmp)) {
touchesBound = true;
} else {
if (tmp < tmpMin) {
tmpMin = tmp;
}
}
}
}
if (elevation < tmpMin) {
isPit = true;
}
}
@Override
public String toString() {
return "GridNode [\n\tcol=" + col + //
", \n\trow=" + row + //
", \n\televation=" + elevation + //
", \n\tisValid=" + isValid() + //
", \n\ttouchesBounds=" + touchesBound + //
"\n]";
}
/**
* @return true
if this node can't flow anywhere following the steepest path downstream.
*/
public boolean isOutlet() {
return isOutlet;
}
/**
* @return true
if all cells around the node are higher than the current.
*/
public boolean isPit() {
return isPit;
}
/**
* Get a window of values surrounding the current node.
*
* Notes:
*
* - the size has to be odd, so that the current node can be in the center.
* If the size is even, size+1 will be used.
* - values outside the boundaries of the raster will be set to novalue.
* No exception is thrown.
*
*
* @param size the size of the window. The window will be a matrix window[size][size].
* @param doCircular if true
the window values are set to novalue
* were necessary to make it circular.
* @return the read window.
*/
public double[][] getWindow( int size, boolean doCircular ) {
if (size % 2 == 0) {
size++;
}
double[][] window = new double[size][size];
int delta = (size - 1) / 2;
if (!doCircular) {
for( int c = -delta; c <= delta; c++ ) {
int tmpCol = col + c;
for( int r = -delta; r <= delta; r++ ) {
int tmpRow = row + r;
GridNode n = new GridNode(gridIter, cols, rows, xRes, yRes, tmpCol, tmpRow);
window[r + delta][c + delta] = n.elevation;
}
}
} else {
double radius = delta; // rows + half cell
for( int c = -delta; c <= delta; c++ ) {
int tmpCol = col + c;
for( int r = -delta; r <= delta; r++ ) {
int tmpRow = row + r;
double distance = sqrt(c * c + r * r);
if (distance <= radius) {
GridNode n = new GridNode(gridIter, cols, rows, xRes, yRes, tmpCol, tmpRow);
window[r + delta][c + delta] = n.elevation;
} else {
window[r + delta][c + delta] = doubleNovalue;
}
}
}
}
return window;
}
/**
* Get the value of the elevation in one of the surrounding direction.
*
* @param direction the {@link Direction}.
* @return the elevation value.
*/
public double getElevationAt( Direction direction ) {
switch( direction ) {
case E:
return eElev;
case W:
return wElev;
case N:
return nElev;
case S:
return sElev;
case EN:
return enElev;
case NW:
return nwElev;
case WS:
return wsElev;
case SE:
return seElev;
default:
throw new IllegalArgumentException();
}
}
/**
* Get next downstream {@link GridNode node} following the steepest path.
*
* @return the next downstream node or null
if it is an outlet.
*/
public GridNode goDownstreamSP() {
Direction[] orderedDirs = Direction.getOrderedDirs();
double maxSlope = Double.NEGATIVE_INFINITY;
GridNode nextNode = null;
for( int i = 0; i < orderedDirs.length; i++ ) {
Direction direction = orderedDirs[i];
int newCol = col + direction.col;
int newRow = row + direction.row;
if (isInRaster(newCol, newRow)) {
GridNode node = new GridNode(gridIter, cols, rows, xRes, yRes, newCol, newRow);
if (node.isValid()) {
double slopeTo = getSlopeTo(node);
if (slopeTo > 0 && slopeTo > maxSlope) {
nextNode = node;
maxSlope = slopeTo;
}
}
}
}
if (nextNode == null) {
isOutlet = true;
}
return nextNode;
}
// /**
// * Get next upstream {@link GridNode node}, based on least cost.
// *
// * @return the next least cost, upstream node.
// */
// public GridNode goLeastCostUpstream() {
//
// return null;
// }
/**
* Gets all surrounding {@link GridNode nodes}, starting from the most eastern.
*
* Note that the list contains all 8 directions, but some might be null, if outside a boundary
*
* @return the nodes surrounding the current node.
*/
public List getSurroundingNodes() {
List nodes = new ArrayList();
Direction[] orderedDirs = Direction.getOrderedDirs();
for( int i = 0; i < orderedDirs.length; i++ ) {
Direction direction = orderedDirs[i];
int newCol = col + direction.col;
int newRow = row + direction.row;
if (isInRaster(newCol, newRow)) {
GridNode node = new GridNode(gridIter, cols, rows, xRes, yRes, newCol, newRow);
if (node.isValid()) {
nodes.add(node);
} else {
nodes.add(null);
}
} else {
nodes.add(null);
}
}
return nodes;
}
/**
* Get a neighbor node at a certain direction.
*
* @param direction the direction to get the node at.
* @return the node.
*/
public GridNode getNodeAt( Direction direction ) {
int newCol = col + direction.col;
int newRow = row + direction.row;
GridNode node = new GridNode(gridIter, cols, rows, xRes, yRes, newCol, newRow);
return node;
}
/**
* Checks if the supplied node is adjacent to the current.
*
* @return the {@link Direction} if the two cells touch, else null
.
*/
public Direction isNeighborOf( GridNode otherNode ) {
Direction[] orderedDirs = Direction.getOrderedDirs();
for( int i = 0; i < orderedDirs.length; i++ ) {
Direction direction = orderedDirs[i];
int newCol = col + direction.col;
int newRow = row + direction.row;
if (otherNode.col == newCol && otherNode.row == newRow) {
return direction;
}
}
return null;
}
/**
* Checks if the supplied node is adjacent to the current and has the same value.
*
* @return the {@link Direction} if the two cells touch and have the same value, else null
.
*/
public Direction isSameValueNeighborOf( GridNode otherNode ) {
Direction direction = isNeighborOf(otherNode);
if (direction != null && NumericsUtilities.dEq(elevation, otherNode.elevation)) {
return direction;
}
return null;
}
/**
* Gets only the valid surrounding {@link GridNode nodes}, starting from the most eastern.
*
* @return the valid nodes surrounding the current node.
*/
public List getValidSurroundingNodes() {
List nodes = new ArrayList();
Direction[] orderedDirs = Direction.getOrderedDirs();
for( int i = 0; i < orderedDirs.length; i++ ) {
Direction direction = orderedDirs[i];
int newCol = col + direction.col;
int newRow = row + direction.row;
if (isInRaster(newCol, newRow)) {
GridNode node = new GridNode(gridIter, cols, rows, xRes, yRes, newCol, newRow);
if (node.isValid()) {
nodes.add(node);
}
}
}
return nodes;
}
/**
* Gets all surrounding {@link GridNode nodes} that DO flow into this node by steepest path rule.
*
* @return the nodes that flow into this node.
*/
public List getEnteringNodesSP() {
List nodes = new ArrayList();
List surroundingNodes = getSurroundingNodes();
for( GridNode flowNode : surroundingNodes ) {
if (flowNode != null) {
GridNode downstream = flowNode.goDownstreamSP();
if (downstream.isValid() && this.equals(downstream)) {
nodes.add(flowNode);
}
}
}
return nodes;
}
/**
* Gets all surrounding {@link GridNode nodes} that do NOT flow into this node by steepest path rule.
*
* @return the nodes that flow into this node.
*/
public List getNonEnteringNodesSP() {
List nodes = new ArrayList();
List surroundingNodes = getSurroundingNodes();
for( GridNode flowNode : surroundingNodes ) {
if (flowNode != null) {
GridNode downstream = flowNode.goDownstreamSP();
if (!downstream.isValid() || !this.equals(downstream)) {
nodes.add(flowNode);
}
}
}
return nodes;
}
private boolean isInRaster( int col, int row ) {
if (col < 0 || col >= cols || row < 0 || row >= rows) {
return false;
}
return true;
}
/**
* Calculates the slope from the current to the supplied point.
*
* @param node the node to which to calculate the slope to.
* @return the slope.
*/
public double getSlopeTo( GridNode node ) {
double slope = (elevation - node.elevation) / getDistance(node);
return slope;
}
public double getDistance( GridNode node ) {
return sqrt(pow((node.col - col) * xRes, 2.0) + pow((node.row - row) * yRes, 2.0));
}
public double getEastElev() {
return eElev;
}
public double getENElev() {
return enElev;
}
public double getNorthElev() {
return nElev;
}
public double getNWElev() {
return nwElev;
}
public double getWestElev() {
return wElev;
}
public double getWSElev() {
return wsElev;
}
public double getSouthElev() {
return sElev;
}
public double getSEElev() {
return seElev;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + col;
long temp;
temp = Double.doubleToLongBits(elevation);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + row;
return result;
}
@Override
public boolean equals( Object obj ) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
GridNode other = (GridNode) obj;
if (col != other.col || row != other.row)
return false;
if (Double.doubleToLongBits(elevation) != Double.doubleToLongBits(other.elevation))
return false;
return true;
}
}