package edu.oregonstate.cartography.grid.operators; import edu.oregonstate.cartography.grid.Grid; /** * * @author Bernhard Jenny, Cartography and Geovisualization Group, Oregon State * University */ public class PlanObliqueOperator implements GridOperator { /** * shearing factor: y' = y + k * z where y' is the new position of a point * at y, and z is the elevation at y. */ private final double k; /** * the elevation that will not shift, usually the minimum elevation in the * grid. */ private final float refElevation; public PlanObliqueOperator(double inclinationAngleDeg, float refElevation) { k = 1d / Math.tan(Math.toRadians(inclinationAngleDeg)); this.refElevation = refElevation; } @Override public Grid operate(Grid grid) { if (grid == null) { return null; } // number of rows int nRows = grid.getRows(); // number of columns int nCols = grid.getCols(); // y coordinate of northern most vertices in grid double north = grid.getNorth(); // grid cell size double cellSize = grid.getCellSize(); // shearing factor plus conversion factor for cell size in degrees double elevationScale = k; if (cellSize < 0.1) { elevationScale /= Math.PI / 180d * 6371000; } // init sheared grid Grid shearedGrid = new Grid(nCols, nRows, grid.getCellSize()); shearedGrid.setSouth(grid.getSouth()); shearedGrid.setWest(grid.getWest()); // fill sheared grid: iterate over all columns for (int col = 0; col < nCols; col++) { // keep track of the last vertex of the source grid that has been sheared. // this accelerates the algorithm, because we don't need to start // each search at the lower grid border, but can instead continue // searching at the last found vertex. int prevRow = nRows - 1; // find the first valid grid value in the current column (from the bottom) // and remember its value double prevZ = Double.NaN; for (; prevRow >= 0; prevRow--) { if (Double.isNaN(grid.getValue(col, prevRow))) { shearedGrid.setValue(Float.NaN, col, prevRow); } else { break; } } // store the sheared y coordinate of the grid vertex below the current vertex double prevY = north - prevRow * cellSize; // iterate over all rows, from bottom to top for (int row = prevRow; row >= 0; row--) { // the vertical y coordinate where an elevation value is needed double targetY = north - row * cellSize; // vertically traverse the column towards the upper border, starting // at the last visited vertex for (int r = prevRow; r >= 0; r--) { // the elevation for the current vertex double z = grid.getValue(col, r); // move vertically across patches of void values if (Double.isNaN(z)) { while (--prevRow >= 0 && Float.isNaN(grid.getValue(col, prevRow))) { } prevZ = Double.NaN; break; } // shear the y coordinate double y = north - r * cellSize + (z - refElevation) * elevationScale; // if the sheared y coordinate is vertically higher than the // y coordinate where an elevation value is needed, we have // found the next upper vertex if (y > targetY) { // linearly interpolate the elevations of the vertices // that are vertically above and below double w = (targetY - prevY) / (y - prevY); shearedGrid.setValue( w * z + (1d - w) * prevZ, col, row);; break; } // the next target vertex might again fall between the same // pair of vertices, so only update prevRow now. prevRow = r; // store the sheared y coordinate and the elevation if the current // vertex is not occluded by a previously sheared vertex if (y >= prevY) { prevY = y; prevZ = z; } } } } return shearedGrid; } @Override public String getName() { return "Plan oblique relief"; } }