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

geotrellis.raster.op.focal.FocalStrategy.scala Maven / Gradle / Ivy

The newest version!
package geotrellis.raster.op.focal

import scala.math._

import geotrellis._
import geotrellis.raster._

sealed trait TraversalStrategy
object TraversalStrategy {
  val ZigZag = new TraversalStrategy { }
  val ScanLine = new TraversalStrategy { }
  val SpiralZag = new TraversalStrategy { }
}
import TraversalStrategy._

/**
 * Focal strategy which moves a Cursor across the raster,
 * allowing a calculation to be done on each cell using the Cursor
 * to determine what neighboring cells are inside the focus's
 * neighborhood, what cells have been added since the last move, and
 * what cells have been removed since the last move.
 */
object CursorStrategy {
  def execute(r:Raster,
              n:Neighborhood,
              c:CursorCalculation[_],
              tOpt:Option[TraversalStrategy], 
              neighbors:Seq[Option[Raster]]):Unit = {
    val t = tOpt match {
      case None => ZigZag
      case Some(tStrategy) => tStrategy
    }
    // Get the tile raster
    val (rast,analysisArea) = TileWithNeighbors(r,neighbors)

    val cursor = Cursor(rast,n,analysisArea)
    execute(rast,cursor,c,t,analysisArea)
  }

  def execute(r:Raster,cursor:Cursor,c:CursorCalculation[_],t:TraversalStrategy,analysisArea:GridBounds):Unit = {
    t match {
      case ScanLine => handleScanLine(r, analysisArea, cursor,c)
      case SpiralZag => handleSpiralZag(r,analysisArea,cursor,c)
      case _ => handleZigZag(r,analysisArea,cursor,c)
    }
  }
  
  private def handleSpiralZag(r:Raster,analysisArea:GridBounds,cursor:Cursor,c:CursorCalculation[_]) = {
    var colMax = analysisArea.colMax
    var rowMax = analysisArea.rowMax
    var colMin = analysisArea.colMin
    var rowMin = analysisArea.rowMin

    var col = colMin
    var row = rowMin

    var xdirection = 1
    var ydirection = 1
    var done = false
    var zagTime = false

    cursor.centerOn(col,row)
    
    // Spiral around the raster.
    // Once we get down with dealing with borders,
    // zig zag over inner portion.
    while(!(done || zagTime)) {
      //Move right across top
      while(col < colMax) {
        c.calc(r,cursor)
        cursor.move(Movement.Right)
        col += 1
      }
      // Move down along right edge
      while(row < rowMax) {
        c.calc(r,cursor)
        cursor.move(Movement.Down)
        row += 1
      }
      //Move left across bottom
      while(col > colMin) {
        c.calc(r,cursor)
        cursor.move(Movement.Left)
        col -= 1
      }
      // Move up along left edge
      while(row > rowMin+1) {
        c.calc(r,cursor)
        cursor.move(Movement.Up)
        row -= 1
      }
      c.calc(r,cursor)
      rowMin += 1
      rowMax -= 1
      colMin += 1
      colMax -= 1

      if(rowMin == rowMax || colMin == colMax) { 
        done = true 
      } else {
        cursor.move(Movement.Right)
        col += 1
        if(col - cursor.extent >= 0) {
          zagTime = true
        }
      }
    }

    var direction = 1

    // Now zig zag across interior.
    while(row <= rowMax) {
      c.calc(r,cursor)
      col += direction
      if(col < colMin || colMax < col) {
        direction *= -1
        row += 1
        col += direction
        cursor.move(Movement.Down)
      } else {
        if(direction == 1) { cursor.move(Movement.Right) }
        else { cursor.move(Movement.Left) }
      }
    }
  }

  private def handleZigZag(r:Raster,analysisArea:GridBounds,cursor:Cursor,c:CursorCalculation[_]) = {
    val colMax = analysisArea.colMax
    val rowMax = analysisArea.rowMax
    val colMin = analysisArea.colMin
    val rowMin = analysisArea.rowMin

    var col = colMin
    var row = rowMin

    var direction = 1

    cursor.centerOn(col, row)

    while(row <= rowMax) {
      c.calc(r,cursor)
      col += direction
      if(col < colMin || colMax < col) {
        direction *= -1
        row += 1
        col += direction
        cursor.move(Movement.Down)
      } else {
        if(direction == 1) { cursor.move(Movement.Right) }
        else { cursor.move(Movement.Left) }
      }
    }
  }

  private def handleScanLine(r:Raster,analysisArea:GridBounds,cursor:Cursor,c:CursorCalculation[_]) = {
    val colMax = analysisArea.colMax
    val rowMax = analysisArea.rowMax
    val colMin = analysisArea.colMin
    val rowMin = analysisArea.rowMin

    // set initial state of col and row
    var col = colMin
    var row = rowMin
    cursor.centerOn(col, row)

    while(row <= rowMax) {
      c.calc(r,cursor)
      col += 1
      if(colMax < col) {
        row += 1
        col = colMin
        cursor.centerOn(col,row)
      } else {
        cursor.move(Movement.Right)
      }
    }
  }
}

/**
 * Focal strategy that implements a more strict mechanism that informs the user
 * what cells have been added or removed. This strategy is more performant,
 * but can only be used for Square or Circle neighborhoods.
 */ 
object CellwiseStrategy {
  def execute(r:Raster, 
              n:Square,
              c:CellwiseCalculation[_], 
              tOpt:Option[TraversalStrategy], 
              neighbors:Seq[Option[Raster]]):Unit = {
    val t = tOpt match {
      case None => ScanLine
      case Some(tStrategy) => tStrategy
    }
    val (rast,analysisArea) = TileWithNeighbors(r,neighbors)
    execute(rast,n,c,t,analysisArea)
  }

  def execute(r:Raster,n:Square,calc:CellwiseCalculation[_],t:TraversalStrategy,analysisArea:GridBounds):Unit = {
    t match {
      case _ => handleScanLine(r,n.extent,calc,analysisArea)
    }
  }

  private def handleScanLine(r:Raster,n:Int, calc:CellwiseCalculation[_], analysisArea:GridBounds) = {
    val rowMin = analysisArea.rowMin
    val colMin = analysisArea.colMin
    val rowMax = analysisArea.rowMax
    val rowBorderMax = r.rows - 1
    val colMax = analysisArea.colMax
    val colBorderMax = r.cols - 1

    val analysisOffsetCols = analysisArea.colMin
    val analysisOffsetRows = analysisArea.rowMin

    var focusRow = rowMin
    while (focusRow <= rowMax) {
      val curRowMin = max(0, focusRow - n)
      val curRowMax = min(rowBorderMax, focusRow + n )

      calc.reset()
      val curColMax = min(colBorderMax, colMin + n)
      val curColMin = max(0, colMin - n)
      var curRow = curRowMin
      while (curRow <= curRowMax) {
        var curCol = curColMin
        while (curCol <= curColMax) {
          calc.add(r, curCol, curRow)
          curCol += 1
        }
        curRow += 1
      }

      // offset output col & row to analysis area coordinates
      calc.setValue(0, focusRow - rowMin) 

      var focusCol = colMin + 1
      while (focusCol <= colMax) {
        // Remove the western most column that is no longer part of the neighborhood
        val oldWestCol = focusCol - n - 1
        if (oldWestCol >= 0) {
          var yy = curRowMin
          while (yy <= curRowMax) {
            calc.remove(r, oldWestCol, yy)
            yy += 1
          }
        }

        // Add the eastern most column that is now part of the neighborhood
        val newEastCol = focusCol + n
        if (newEastCol <= colBorderMax) {
            var yy = curRowMin
            while (yy <= curRowMax) {
              calc.add(r, newEastCol, yy)
              yy += 1
            }
          }

        // offset output col & row to analysis area coordinates
        calc.setValue(focusCol - colMin, focusRow - rowMin)
        focusCol += 1
      }
      focusRow += 1
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy