All Downloads are FREE. Search and download functionalities are using the official Maven repository.

geotrellis.raster.hydrology.Accumulation.scala Maven / Gradle / Ivy

/*
 * Copyright (c) 2014 Azavea.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package geotrellis.raster.hydrology

import geotrellis.raster._
import spire.syntax.cfor._
import scala.collection.mutable._


/**
  * An object which houses functions related to hydrological
  * accumulation.
  */
object Accumulation {

  /**
    * The 'value' parameter represents a set of directions.  In the
    * representation, a particular bit is asserted if there is flow in
    * the direction, otherwise it is not.
    *
    * @param  value  The set of directions
    * @param  dir    A particular direction
    */
  def doesFlow(value: Int, dir: Int): Boolean = {
    if(value < dir) {
      false
    }
    else {
      if((value >> (math.floor(math.log(dir) / math.log(2)).toInt)) % 2 == 1)
        true
      else
        false
    }
  }

  /**
    * Calculate the accumulation.  Given a location, accumulation
    * information, and information about flow directions, compute the
    * accumulation at the given point.
    *
    * @param  column         The column of the location
    * @param  row            The row of the location
    * @param  data           The accumulation information
    * @param  flowDirection  The flow direction information
    */
  def calcAcc(column: Int, row: Int, data: IntArrayTile, flowDirrection: Tile) = {
    var c = column
    var r = row
    var sum = data.get(c, r)
    val cols = data.cols
    val rows = data.rows
    var flag = 0

    // Map the flow direction that each neighboring cell must have to
    // flow into the current cell
    val map = Map[Int, (Int, Int)](
      16 -> (c+1, r),
      32 -> (c+1, r+1),
      64 -> (c, r+1),
      128 -> (c-1, r+1),
      1 -> (c-1, r),
      2 -> (c-1, r-1),
      4 -> (c, r-1),
      8 -> (c+1, r-1))

    if(sum == -1) {
      sum = 0
      val stack = new ArrayStack[(Int, Int)]()

      stack.push((c, r))
      stack.push((c, r))

      while(!stack.isEmpty || data.get(c, r) == -1) {
        sum = 0
        flag = 0

        // The neighboring cell
        var n = map(16)

        // right neighbor
        if(c + 1 < cols && doesFlow(flowDirrection.get(n._1, n._2), 16)) {
          if(data.get(n._1, n._2) == -1) {
            stack.push(n)
            flag = 1
          } else {
            sum += data.get(n._1, n._2)
          }
        }

        // bottom right neighbor
        n = map(32)
        if(c + 1 < cols && r + 1 < rows && doesFlow(flowDirrection.get(n._1, n._2), 32)) {
          if(data.get(n._1, n._2) == -1) {
            stack.push(n)
            flag = 1
          } else {
            sum += data.get(n._1, n._2) + 1
          }
        }

        // bottom neighbor
        n = map(64)
        if(r + 1 < rows && doesFlow(flowDirrection.get(c, r + 1), 64)) {
          if( data.get(n._1, n._2) == -1) {
            stack.push(n)
            flag = 1
          } else {
            sum += data.get(n._1, n._2) + 1
          }
        }

        // bottom left neighbor
        n = map (128)
        if(c - 1 >= 0 && r + 1 < rows && doesFlow(flowDirrection.get(c - 1, r + 1), 128)) {
          if(data.get(c - 1, r + 1) == -1) {
            stack.push((c - 1, r + 1))
            flag = 1
          } else {
            sum += data.get(c - 1, r + 1) + 1
          }
        }

        // left neighbor
        n = map(1)
        if(c - 1 >= 0 && doesFlow(flowDirrection.get(c - 1, r), 1)) {
          if(data.get(c - 1, r) == -1) {
            stack.push((c - 1, r))
            flag = 1
          } else {
            sum += data.get(c - 1, r) + 1
          }
        }

        // top left neighbor
        n = map(2)
        if(c - 1 >= 0 && r - 1 >= 0 && doesFlow(flowDirrection.get(c - 1, r - 1), 2)) {
          if(data.get(c - 1, r - 1) == -1) {
            stack.push((c - 1, r - 1))
            flag = 1
          } else {
            sum += data.get(c-1, r-1) + 1
          }
        }

        // top neighbor
        n = map(4)
        if(r - 1 >= 0 && doesFlow(flowDirrection.get(c, r - 1), 4)) {
          if(data.get(c, r - 1) == -1) {
            stack.push((c, r - 1))
            flag = 1
          } else {
            sum += data.get(c, r - 1) + 1
          }
        }

        // top right neighbor
        n = map(8)
        if(c + 1 < cols && r - 1 >= 0 && doesFlow(flowDirrection.get(c + 1, r - 1), 8)) {
          if(data.get(c + 1, r - 1) == -1) {
            stack.push((c + 1, r - 1))
            flag = 1
          } else {
            sum += data.get(c + 1, r - 1) + 1
          }
        }

        // set the calculated sum as the accumulation
        if (flag == 0) {
          data.set(c, r, sum)
        }
        if(!stack.isEmpty) {
          val t = stack.pop
          c = t._1
          r = t._2
        }
      }
    }
  }

  /**
    * Given a raster containing flow directions, compute the
    * accumulation at each point.
    */
  def apply(flowDirrection: Tile): Tile = {
    val cols = flowDirrection.cols
    val rows = flowDirrection.rows
    val tile = IntArrayTile(Array.ofDim[Int](cols * rows).fill(-1), cols, rows)

    cfor(0)(_ < rows, _ + 1) { row =>
      cfor(0)(_ < cols, _ + 1) { col =>
        Accumulation.calcAcc(col, row, tile, flowDirrection)
      }
    }

    tile
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy