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

geotrellis.raster.costdistance.CostDistanceWithPaths.scala Maven / Gradle / Ivy

package geotrellis.raster.costdistance

import java.util.{BitSet, PriorityQueue}

import geotrellis.raster._
import geotrellis.vector._
import spire.syntax.cfor._

import scala.collection.mutable.ArrayBuffer

case class CostDistanceWithPathsResult(
  res: Array[Seq[Int]],
  costs: Array[Double],
  tileDimension: (Int, Int)) {

  val (cols, rows) = tileDimension

  def getPath(dest: (Int, Int)): (Double, Seq[Line]) = {
    val (col, row) = dest
    val idx = row * cols + col
    val start = Seq(idx)
    val paths = getPath(start)

    val res = Array.ofDim[Line](paths.size)
    cfor(0)(_ < paths.size, _ + 1) { i =>
      val path = paths(i)
      res(i) = Line(path.map(idx => ((idx % cols).toDouble, (idx / cols).toDouble)))
    }

    (costs(idx), res)
  }

  def getPath(prevPath: Seq[Int]): Seq[Seq[Int]] = {
    var paths = Seq[Seq[Int]]()
    val parents = res(prevPath.head)

    if (parents.isEmpty) paths :+ prevPath
    else {
      cfor(0)(_ < parents.size, _ + 1) { i =>
        val parent = parents(i)
        paths = paths ++ getPath(parent +: prevPath)
      }

      paths
    }
  }

}

object CostDistanceWithPaths {

  def apply(cost: Tile, source: (Int, Int)): CostDistanceWithPathsResult =
    new CostDistanceWithPaths(cost.toArrayTile, source).compute

}

private class CostDistanceWithPaths(cost: ArrayTile, source: (Int, Int)) {

  val tileArray = cost

  val Sqrt2 = math.sqrt(2)

  val (cols, rows) = cost.dimensions

  @inline final def indexToCoordinates(idx: Int) = (idx % cols, idx / cols)

  @inline final def coordinatesToIndex(c: Int, r: Int) = r * cols + c

  @inline final def getTileCost(sIdx: Int, eIdx: Int) = {
    val v1 = cost(sIdx)
    val v2 = cost(eIdx)

    val r = v1 + v2

    val absDifference = math.abs(sIdx - eIdx)

    if (absDifference > 1 && absDifference != cols) r / Sqrt2
    else r / 2.0
  }

  @inline final def getNeighbors(idx: Int) = {
    val (col, row) = indexToCoordinates(idx)
    val buff = ArrayBuffer[Int]()

    val isRight = col == cols - 1
    val isLeft = col == 0
    val isTop = row == 0
    val isBottom = row == rows - 1

    if (!isLeft) buff += idx - 1
    if (!isRight) buff += idx + 1
    if (!isTop) buff += idx - cols
    if (!isBottom) buff += idx + cols
    if (!isLeft && !isTop) buff += idx - cols - 1
    if (!isLeft && !isBottom) buff += idx + cols - 1
    if (!isRight && !isTop) buff += idx - cols + 1
    if (!isRight && !isBottom) buff += idx + cols + 1

    buff.toArray
  }

  class Graph {

    val neighbors = {
      val vertices = cols * rows
      val res = Array.ofDim[Array[(Int, Double)]](vertices)

      cfor(0)(_ < vertices, _ + 1) { idx =>
        val neighborIndices = getNeighbors(idx)
        val neighbors = Array.ofDim[(Int, Double)](neighborIndices.size)

        cfor(0)(_ < neighborIndices.size, _ + 1) { i =>
          val other = neighborIndices(i)
          val cost = getTileCost(idx, other)
          neighbors(i) = ((other, cost))
        }

        res(idx) = neighbors
      }

      res
    }

    def getCost(s: Int, e: Int): Double = {
      val sNeighbors = neighbors(s)
      var res = Double.MaxValue
      cfor(0)(_ < sNeighbors.size, _ + 1) { i =>
        val (other, c) = sNeighbors(i)
        if (other == e) res = c
      }

      res
    }

  }

  def compute: CostDistanceWithPathsResult = {
    val (sourceCol, sourceRow) = source
    val sourceIdx = coordinatesToIndex(sourceCol, sourceRow)

    val marked = new BitSet(cols * rows)
    val pathCosts = Array.ofDim[Double](cols * rows).fill(Double.MaxValue)
    pathCosts(sourceIdx) = 0

    val rememberParents = Array.ofDim[Seq[Int]](cols * rows)

    val priorityQueue = new PriorityQueue(cols * rows, new java.util.Comparator[Int] {

      override def equals(a: Any) = a.equals(this)

      def compare(a: Int, b: Int) = pathCosts(a).compareTo(pathCosts(b))

    })

    val graph = new Graph

    cfor(0)(_ < cols * rows, _ + 1) { idx =>
      rememberParents(idx) = Seq()
    }

    priorityQueue.add(sourceIdx)

    while (!priorityQueue.isEmpty) {
      val current = priorityQueue.poll
      marked.flip(current)

      val neighbors = getNeighbors(current)

      cfor(0)(_ < neighbors.size, _ + 1) { i =>
        val neighbor = neighbors(i)
        if (!marked.get(neighbor)) {
          val alt = pathCosts(current) + graph.getCost(current, neighbor)
          val oldCost = pathCosts(neighbor)
          if (alt < oldCost) {
            pathCosts(neighbor) = alt
            rememberParents(neighbor) = Seq(current)
            priorityQueue.add(neighbor)
          } else if (alt == oldCost)
            rememberParents(neighbor) = rememberParents(neighbor) :+ current
        }
      }
    }

    CostDistanceWithPathsResult(rememberParents, pathCosts, cost.dimensions)
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy