Skip to content

Commit

Permalink
Cleaned up no-data handling and fleshed out tests.
Browse files Browse the repository at this point in the history
Signed-off-by: Simeon H.K. fitch <fitch@astraea.io>
  • Loading branch information
metasim committed Sep 7, 2017
1 parent 5d26405 commit 1672326
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 40 deletions.
@@ -1,16 +1,33 @@
/*
* Copyright 2017 Astraea, Inc.
*
* 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.render.ascii

import geotrellis.raster.render.ascii.AsciiArtEncoder.Settings
import geotrellis.raster.testkit.{RasterMatchers, TileBuilders}
import geotrellis.raster.io.geotiff.{GeoTiffTestUtils, SinglebandGeoTiff}
import geotrellis.raster.render.ascii.AsciiArtEncoder.Palette
import geotrellis.raster.testkit.TileBuilders
import org.scalatest.{FunSuite, Inspectors, Matchers}

/**
* Simple tests to check basic ASCII art rendering.
*
* @since 9/6/17
*/
class RenderAsciiTests extends FunSuite with Matchers with TileBuilders with Inspectors {

test("generate ASCII art from tile") {
test("generate ASCII art from graident tile") {
val palettes = Seq(
AsciiArtEncoder.Palette.WIDE,
AsciiArtEncoder.Palette.NARROW,
Expand All @@ -21,9 +38,69 @@ class RenderAsciiTests extends FunSuite with Matchers with TileBuilders with Ins
)
forEvery(palettes) { palette
val sample = createConsecutiveTile(palette.length)
println("-" * 80)
println(sample.renderAscii(Settings(palette)))
println("-" * 80)
val render = sample.renderAscii(palette)
forAll(palette.values) { c
assert(render.contains(c))
}
}
}

test("generate ASCII art from image")(new GeoTiffTestUtils {
val tiff = SinglebandGeoTiff(geoTiffPath("alaska-polar-3572.tif"))
// format: off
val expected =
"""∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘████ ∘∘∘∘∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘█████ ████ ∘∘∘∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘████████████▜ █▜ ∘∘∘∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘█████▖▖▖▖█████▖▖ █ ∘∘∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘██████████▜▖ ▖██▖█▖ ▖██ ∘∘∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘████████████▜▖▖▖▖▖███▖▖▖▖▖ █▖ █ ∘∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘██████████████████████▖ █████ ▜█ ██ ∘∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘████████████████████▜▜███▖▜█████ ∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘∘∘∘∘∘████████████████████████▜█▜███████ ∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘████████████████████████████▜▖▖██████▖ ∘∘∘∘∘∘∘
|∘∘∘∘██████████████████████████████▖▖▖ ▜▜▖▖ ▖ ▖ ▖ ∘∘∘∘∘∘∘
|█████████████████████████████▜██▜▖▜▜▖ ███ ∘∘∘∘∘∘
|████████████████████████████▜█▖█▜▚▜▜▖▖ ▖ ▖ ▖▖ ▖ ∘∘∘∘∘∘
|██████████████████████████████▜▚▜▚▜█▖▖▖▖▖▖▖▖▖ ▖▖▖▖▖▖▖▖▖▖ ▖▖ ▖▖ ∘∘∘∘∘
|∘█████████████████████████████▜▚▚▚▚▖▖▚▚▚▚▖▖█▖ ▖▖▖▖▚█▜█▜▜▖▖ ▖ ∘∘∘∘∘
|∘████████████▜█████████████████▚▚▜▜▚▜▜▜▖▚▚▖▖▖▖▚▚▚█████████▜▖▖▖▖▖ ∘∘∘∘
|∘█████████████▖████████████████▚▚▜▜▜▚▚▚▚▚▚▚▚▚▖▜████████████████▖▖ ▖ ▖ ∘∘∘∘
|∘∘█████████████████████████████▚▚▚▜▚▚▚▚▜▜▜▜▜▚▚████████████████▖▖▖ ▖▖▖▖▖▖ ∘∘∘
|∘∘███████████████████████████▜█▚▚▚▚▚▜▜▜▜▜▜▜▜▜▚█████████▜▜▜▜▖▖▖▖▖▖ ▖▖█▜▜█▖ ∘∘∘
|∘∘▖▖████████████████████████▚▚▜▜▚▚▚▚▜▜▜▜▜▜▜▜▜▜▜█████▜▜▜▜▚▚▚▚▚▚▚▖▖▚▚▖▜██▖▖ ▖ ∘∘∘
|∘∘∘█████████████████████████▜▚▚▚▚▚▚▚▜▜▜▜▜▜▜▜▜▚██▜▜██▜▜▜▜▜▜▜█▖▚▚▖▖██████▖▖▖▖ ∘∘
|∘∘∘▖███████████████████████▚▚▚▚▜▚▚▚▚▜▜▜▜▜▜▜▜▚▚▚███▚▜▜▜▜▜█████▜▚▚▚██████▖▜█▜▜ ∘∘
|∘∘∘█████████████████████████▚▜▚▚▚▚▚▚▜▜▜▜▜▚▚▚▚▚▚▜▜▜██▚▜██▚▜▜█▜▜▜████████████▖ ∘∘
|∘∘∘∘ ▖██████████████████████▚▜▜▚▚▚▚▚▚▚▚▚▚▖▚▚▚▚▚▚▚▚▚▜█▚█▚▜▜▜▚▚▚█████████████▖ ∘
|∘∘∘∘ ▖████████▖▖████████████▜▚▜▚▚▚▚▚▚▚▜▚▖▚▚▚▚▚▚▚▚▜▚▜▚▜▜▚▖▖▜▜▜▜▚▜▚███████████ ∘
|∘∘∘∘∘ ▖███████▜▚▚▖▖██████████▜▜▚▜▚▚▜▚▚▚▜▜▚▚▚▜▜▜█▚▚▚▚▚▚██▜█▚▜▜▚▚▚▚▚▚▜████████▜ ∘
|∘∘∘∘∘ ▖▖▖▖█▜▖▖▖▖▖█████████▚▜▜▜▜▚▚▚▚▚▚▚▜▜▜▚▜█▜████▚▜████▚▚▜▜▚▚█████████ ██▜ ▜
|∘∘∘∘∘ ▜▖▖▜▜▖▖▖▖▖▖▖▖▖███████████▚▚▚▜▚▚▚▚▚▚▚▜▜▜▚█▚▚██▜██████▜▚▚▚█████████▖█▜█████
|∘∘∘∘∘∘ ▖█▖▖▖▖▖▖▖ ▖▖▖▖▖▖█████████▜▜▚▚▚▚▖▚▚▚▚▚▚▚▚▜████████████████████▖▖▖██▖█████
|∘∘∘∘∘∘ ▖ ▖▜████▜█████████▜▚▚███████▜█████▜██████████████████▖█▜▖████∘∘∘
|∘∘∘∘∘∘∘ ▖███▜█▖ ▖████▚▖██▚▚▜███████████████▚████████▜▖█████████∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘ ▖▖▖▚▖▜▜███████████████████████████████∘∘∘∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘ ▖▖▖▖▜████████████████████████████∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘ ▖▖▖▖██████▜████████████████∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘∘ ▖▖▖▖▜██▖ ▖▖▜▜█████∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘∘∘ ▖▜▖ ▖ ∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘∘∘ ∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘∘∘∘ ▖ ∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘∘∘∘ ∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘
|∘∘∘∘∘∘∘∘∘∘∘∘ ∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘∘"""
.stripMargin
// format: on
val scaled = tiff.tile.resample(80, 40)
val palette = Palette.STIPLED
val render = scaled.renderAscii(palette)

println(render)

forEvery(render.toCharArray.distinct.filter(_ != '\n'))(c
assert(c == palette.nodata || palette.values.contains(c))
)

assert(render === expected)
})
}
Expand Up @@ -27,8 +27,8 @@ import geotrellis.util.MethodExtensions
*/
trait AsciiRenderMethods extends MethodExtensions[Tile] {

def renderAscii(settings: AsciiArtEncoder.Settings = AsciiArtEncoder.Settings()): String =
AsciiArtEncoder.encode(self, settings)
def renderAscii(palette: AsciiArtEncoder.Palette = AsciiArtEncoder.Palette.WIDE): String =
AsciiArtEncoder.encode(self, palette)

/**
* Return ASCII representation of the tile, emitting a string matrix of
Expand Down
@@ -1,3 +1,19 @@
/*
* Copyright 2017 Astraea, Inc.
*
* 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.render.ascii

import geotrellis.raster.Tile
Expand All @@ -12,46 +28,48 @@ import spire.syntax.cfor.cfor
*/
object AsciiArtEncoder {

def encode(tile: Tile, settings: Settings): String = {
val palette = settings.palette
/** Convert the given tile into an ASCII art string using the given palette of
* character codes as fill values. */
def encode(tile: Tile, palette: AsciiArtEncoder.Palette): String = {
val palSize = palette.length
val opts = ColorMap.Options.DEFAULT
.copy(noDataColor = palette.nodata)
val colorMap = if(tile.cellType.isFloatingPoint) {
val hist = tile.histogramDouble(palSize)
ColorMap.fromQuantileBreaks(hist, palette.colorRamp)
ColorMap.fromQuantileBreaks(hist, palette.colorRamp, opts)
}
else {
val hist = tile.histogram
ColorMap.fromQuantileBreaks(hist, palette.colorRamp)
ColorMap.fromQuantileBreaks(hist, palette.colorRamp, opts)
}

val intEncoded = colorMap.render(tile)

val sb = new StringBuilder

cfor(0)(_ < tile.rows, _ + 1) { row =>
cfor(0)(_ < tile.cols, _ + 1) { col =>
val p = intEncoded.get(col, row)
sb += p.toChar
}
sb += '\n'
}
//sb.dropRight(1)
sb.toString
sb.dropRight(1).toString
}

case class Settings(palette: Palette = Palette.WIDE)

case class Palette(values: Array[Char]) {
case class Palette(values: Array[Char], nodata: Char = '∘') {
def colorRamp: ColorRamp = ColorRamp(values.map(_.toInt))
def length: Int = values.length
}

object Palette {
def apply(str: String): Palette = apply(str.toCharArray)
val WIDE = Palette(" .'`^\",:;Il!i><~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$")
val NARROW = Palette( " .:-=+*#%@")
val HATCHING = Palette(" ...,,;;---===+++xxxXX##")
val FILLED = Palette(" ▤▦▩█")
val STIPLED = Palette(" ▖▚▜█")
val BINARY = Palette(" #")
val BINARY = Palette("")

def apply(str: String): Palette = apply(str.toCharArray)
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2017 Azavea
* Copyright 2016-2017 Azavea
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -72,7 +72,28 @@ case object NumericEncoder {
formatTileString(buff, max, tile.cols, tile.rows)
}

/** Helper. */
/**
* Returns an ASCII string representation of a subset of the tile, with cell
* values encoded as hexadecimal integers.
*/
def rangeEncode(tile: Tile, colMin: Int, colMax: Int, rowMin: Int, rowMax: Int): String = {
val buff = ArrayBuffer[String]()

for (row <- rowMin to rowMax) {
for (col <- colMin to colMax) {
val z = tile.get(row, col)
if (isNoData(z)) {
buff += ".."
} else {
buff += "%02X".formatLocal(Locale.ENGLISH, z)
}
}
buff += "\n"
}
buff.toString
}

/** Stringization helper. */
private def formatTileString(buff: ArrayBuffer[String], cols: Int, rows: Int, maxSize: Int) = {
val sb = new StringBuilder
val limit = math.max(6, maxSize)
Expand All @@ -88,23 +109,5 @@ case object NumericEncoder {
sb.toString
}

/**
* Returns an ASCII string representation of a subset of the tile, with cell
* values encoded as hexadecimal integers.
*/
def rangeEncode(tile: Tile, colMin: Int, colMax: Int, rowMin: Int, rowMax: Int): String = {
var s = ""
for (row <- rowMin to rowMax) {
for (col <- colMin to colMax) {
val z = tile.get(row, col)
if (isNoData(z)) {
s += ".."
} else {
s += "%02X".formatLocal(Locale.ENGLISH, z)
}
}
s += "\n"
}
s
}

}

0 comments on commit 1672326

Please sign in to comment.