Skip to content

Instantly share code, notes, and snippets.

@echeipesh
Created February 22, 2019 21:03
Show Gist options
  • Save echeipesh/e5f0150ae47d7c8438b8b8e95ced9f02 to your computer and use it in GitHub Desktop.
Save echeipesh/e5f0150ae47d7c8438b8b8e95ced9f02 to your computer and use it in GitHub Desktop.
PixelBounds
/*
* Copyright 2018 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.contrib.vlm
import scala.collection.mutable
import spire.syntax.cfor._
import spire.math._
import spire.syntax._
import spire.implicits._
case class PixelBounds[A: Integral](colMin: A, rowMin: A, colMax: A, rowMax: A) {
def width: A = colMax - colMin + Integral[A].one
def height: A = rowMax - rowMin + Integral[A].one
def size: A = width * height
def isEmpty: Boolean = size == 0
/**
* Return true if the present [[PixelBounds]] contains the position
* pointed to by the given column and row, otherwise false.
*
* @param col The column
* @param row The row
*/
def contains(col: A, row: A): Boolean =
(colMin <= col && col <= colMax) &&
(rowMin <= row && row <= rowMax)
/**
* Returns true if the present [[PixelBounds]] and the given one
* intersect (including their boundaries), otherwise returns false.
*
* @param other The other PixelBounds
*/
def intersects(other: PixelBounds[A]): Boolean =
!(colMax < other.colMin || other.colMax < colMin) &&
!(rowMax < other.rowMin || other.rowMax < rowMin)
/**
* Creates a new [[PixelBounds]] using a buffer around this
* PixelBounds.
*
* @note This will not buffer past 0 regardless of how much the buffer
* falls below it.
*
* @param bufferSize The amount this PixelBounds should be buffered by.
*/
def buffer(bufferSize: Int): PixelBounds[A] =
buffer(bufferSize, bufferSize)
/**
* Creates a new [[PixelBounds]] using a buffer around this
* PixelBounds.
*
* @note This will not buffer past 0 regardless of how much the buffer
* falls below it.
*
* @param colBuffer The amount the cols within this PixelBounds should be buffered.
* @param rowBuffer The amount the rows within this PixelBounds should be buffered.
* @param clamp Determines whether or not to clamp the PixelBounds to the grid
* such that it no value will be under 0; defaults to true. If false,
* then the resulting PixelBounds can contain negative values outside
* of the grid boundaries.
*/
def buffer(colBuffer: A, rowBuffer: A, clamp: Boolean = true): PixelBounds[A] =
PixelBounds(
if (clamp) Integral[A].max(colMin - colBuffer, 0) else colMin - colBuffer,
if (clamp) Integral[A].max(rowMin - rowBuffer, 0) else rowMin - rowBuffer,
colMax + colBuffer,
rowMax + rowBuffer
)
/**
* Offsets this [[PixelBounds]] to a new location relative to its current
* position
*
* @param boundsOffset The amount the PixelBounds should be shifted.
*/
def offset(boundsOffset: A): PixelBounds[A] =
offset(boundsOffset, boundsOffset)
/**
* Offsets this [[PixelBounds]] to a new location relative to its current
* position
*
* @param colOffset The amount the cols should be shifted.
* @param rowOffset The amount the rows should be shifted.
*/
def offset(colOffset: A, rowOffset: A): PixelBounds[A] =
PixelBounds(
colMin + colOffset,
rowMin + rowOffset,
colMax + colOffset,
rowMax + rowOffset
)
/**
* Another name for the 'minus' method.
*
* @param other The other PixelBounds
*/
def -(other: PixelBounds[A]): Seq[PixelBounds[A]] = minus(other)
/**
* Returns the difference of the present [[PixelBounds]] and the
* given one. This returns a sequence, because the difference may
* consist of more than one PixelBounds.
*
* @param other The other PixelBounds
*/
def minus(other: PixelBounds[A]): Seq[PixelBounds[A]] =
if(!intersects(other)) {
Seq(this)
} else {
val overlapColMin =
if(colMin < other.colMin) other.colMin
else colMin
val overlapColMax =
if(colMax < other.colMax) colMax
else other.colMax
val overlapRowMin =
if(rowMin < other.rowMin) other.rowMin
else rowMin
val overlapRowMax =
if(rowMax < other.rowMax) rowMax
else other.rowMax
val result = mutable.ListBuffer[PixelBounds[A]]()
// Left cut
if(colMin < overlapColMin) {
result += PixelBounds(colMin, rowMin, overlapColMin - 1, rowMax)
}
// Right cut
if(overlapColMax < colMax) {
result += PixelBounds(overlapColMax + 1, rowMin, colMax, rowMax)
}
// Top cut
if(rowMin < overlapRowMin) {
result += PixelBounds(overlapColMin, rowMin, overlapColMax, overlapRowMin - 1)
}
// Bottom cut
if(overlapRowMax < rowMax) {
result += PixelBounds(overlapColMin, overlapRowMax + 1, overlapColMax, rowMax)
}
result
}
/**
* Return the intersection of the present [[PixelBounds]] and the
* given [[PixelBounds]].
*
* @param other The other PixelBounds
*/
def intersection(other: PixelBounds[A]): Option[PixelBounds[A]] =
if(!intersects(other)) {
None
} else {
Some(
PixelBounds(
Integral[A].max(colMin, other.colMin),
Integral[A].max(rowMin, other.rowMin),
Integral[A].min(colMax, other.colMax),
Integral[A].min(rowMax, other.rowMax)
)
)
}
/** Return the union of PixelBounds. */
def combine(other: PixelBounds[A]): PixelBounds[A] =
PixelBounds(
colMin = Integral[A].min(this.colMin, other.colMin),
rowMin = Integral[A].min(this.rowMin, other.rowMin),
colMax = Integral[A].max(this.colMax, other.colMax),
rowMax = Integral[A].max(this.rowMax, other.rowMax)
)
/** Empty PixelBounds contain nothing, though non empty PixelBounds contains iteslf */
def contains(other: PixelBounds[A]): Boolean =
if (colMin == 0 && colMax == 0 && rowMin == 0 && rowMax == 0)
false
else
other.colMin >= colMin &&
other.rowMin >= rowMin &&
other.colMax <= colMax &&
other.rowMax <= rowMax
/** Split into windows, covering original PixelBounds */
def split(cols: A, rows: A)(implicit ev: NumberTag[A]): Iterator[PixelBounds[A]] = {
for {
windowRowMin <- Interval.closed(rowMin, rowMax).iterator(rows)
windowColMin <- Interval.closed(colMin, colMax).iterator(cols)
} yield {
PixelBounds(
colMin = windowColMin,
rowMin = windowRowMin,
colMax = Integral[A].min(windowColMin + cols - 1, colMax),
rowMax = Integral[A].min(windowRowMin + rows - 1, rowMax)
)
}
}
def toInt: PixelBounds[Int] = {
require(colMin <= Int.MaxValue && colMax <= Int.MaxValue
&& rowMin <= Int.MaxValue && rowMax <= Int.MaxValue,
s"$this exceeds Int.MaxValue")
PixelBounds(colMin.toInt, rowMin.toInt, colMax.toInt, rowMax.toInt)
}
def toLong: PixelBounds[Long] = {
PixelBounds(colMin.toLong, rowMin.toLong, colMax.toLong, rowMax.toLong)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment