Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GridExtent rounding issues #3248

Closed
pomadchin opened this issue Jun 2, 2020 · 6 comments
Closed

GridExtent rounding issues #3248

pomadchin opened this issue Jun 2, 2020 · 6 comments
Assignees
Labels

Comments

@pomadchin
Copy link
Member

pomadchin commented Jun 2, 2020

In the GridExtent funtions we use floors, ceils and rounds pretty inconsistently.

QGIS and GDAL use ceils and floors with some tolerance that helps to handle corner cases better.

This task is about doublecheking how we work with rounding floating numbers, and consider usage of the ceilWithTolerance as well (a long with the floorWithTolerance function we already use).

@jisantuc
Copy link
Contributor

jisantuc commented Jun 16, 2020

👋 I put a test tif (thanks @notthatbreezy) at s3://geotrellis-test/3248/failing-withResolutions.tif. You can reproduce the error with this ammonite script that mimics the process for calculating a histogram in Raster Foundry

import $ivy.`org.locationtech.geotrellis::geotrellis-raster:3.3.0`
import $ivy.`org.locationtech.geotrellis::geotrellis-s3:3.3.0`
import $ivy.`org.locationtech.geotrellis::geotrellis-vector:3.3.0`

import geotrellis.raster._
import geotrellis.vector._
import geotrellis.raster.geotiff._

def viableCellSizes(
        rs: RasterSource,
        baseCellSize: CellSize,
        sizes: List[CellSize],
        target: Long
    ): List[CellSize] = {
      val width = rs.cols
      val height = rs.rows

      val isGood: CellSize => Boolean = (cs: CellSize) => {
        val cellCount = width / (cs.width / baseCellSize.width) * (height / (cs.height / baseCellSize.height))
        println(s"Cell count for $cs: $cellCount")
        cellCount < target
      }

      sizes.filter(isGood)
    }

val rsSb = GeoTiffRasterSource("trl-transformed-cog.tif")
val viableSb = viableCellSizes(rsSb, rsSb.cellSize, rsSb.resolutions, 400000)

viableSb.headOption map { res => println(s"Single band 1: ${rsSb.gridExtent.withResolution(res)}") }
viableSb.lastOption map { res => println(s"Single band 2: ${rsSb.gridExtent.withResolution(res)}") }

The error to expect is:

java.lang.IllegalArgumentException: requirement failed: Extent(4114885.5876404666, -770072.0469938816, 4129027.727803043, -758864.1346488822) at CellSize(64.57598247751807,64.41328933907688) does not match 219x175
  scala.Predef$.require(Predef.scala:281)
  geotrellis.raster.GridExtent.<init>(GridExtent.scala:48)
  geotrellis.raster.GridExtent.withResolution(GridExtent.scala:133)
  geotrellis.raster.GridExtent.withResolution(GridExtent.scala:144)
  geotrellis.raster.GridExtent.withResolution$mcJ$sp(GridExtent.scala:143)
  ammonite.$file.tiffreader$minusresample$.$anonfun$res_10$1(tiffreader-resample.sc:31)
  ammonite.$file.tiffreader$minusresample$.$anonfun$res_10$1$adapted(tiffreader-resample.sc:31)
  scala.Option.map(Option.scala:230)
  ammonite.$file.tiffreader$minusresample$.<init>(tiffreader-resample.sc:31)
  ammonite.$file.tiffreader$minusresample$.<clinit>(tiffreader-resample.sc)

@CloudNiner CloudNiner self-assigned this Jun 16, 2020
@CloudNiner
Copy link
Contributor

CloudNiner commented Jun 16, 2020

@jisantuc I downloaded the test tiff and copied your ammonite script and was able to reproduce the issue there using geotrellis 3.3.0.

I then loaded up master branch and created a new test case in GridExtentSpec:

it("should not crash in withResolution") {
      def viableCellSizes(rs: RasterSource,
                          baseCellSize: CellSize,
                          sizes: List[CellSize],
                          targetCellCount: Long): List[CellSize] = {
        val width = rs.cols
        val height = rs.rows

        val isGood: CellSize => Boolean = (cs: CellSize) => {
          val cellCount = width / (cs.width / baseCellSize.width) * (height / (cs.height / baseCellSize.height))
          println(s"Cell count for $cs: $cellCount")
          cellCount < targetCellCount
        }

        sizes.filter(isGood)
      }
      val path = baseGeoTiffPath("failing-withResolutions.tif")
      val rsSb = GeoTiffRasterSource(path)
      val viableSb = viableCellSizes(rsSb, rsSb.cellSize, rsSb.resolutions, 400000)
      viableSb.headOption map { res => println(s"Single band 1: ${rsSb.gridExtent.withResolution(res)}") }
      viableSb.headOption map { res => println(s"Single band 1: ${rsSb.gridExtent.withResolution(res)}") }
    }

And this passes:
Screen Shot 2020-06-16 at 3 40 41 PM

In the absence of further information, I'm going to guess that at least one of the fixes in @pomadchin's #3252 addressed this issue.

@pomadchin
Copy link
Member Author

pomadchin commented Jun 16, 2020

@CloudNiner are you sure about that? It fails for me on the master branch:

 git reset --hard cf6e8596a0b21c4d3598525080b98dc087c8f898
HEAD is now at cf6e8596a Fix MosaicRasterSource, GDALRasterSource and GeoTiffResampleRasterSource behavior (#3252)

scala> viableSb.lastOption map { res => println(s"Single band 2: ${rsSb.gridExtent.withResolution(res)}") }
java.lang.IllegalArgumentException: requirement failed: Extent(4114885.5876404666, -770072.0469938816, 4129027.727803043, -758864.1346488822) at CellSize(64.57598247751807,64.41328933907688) does not match 219x175
  at scala.Predef$.require(Predef.scala:281)
  at geotrellis.raster.GridExtent.<init>(GridExtent.scala:48)
  at geotrellis.raster.GridExtent.withResolution(GridExtent.scala:133)
  at geotrellis.raster.GridExtent.withResolution(GridExtent.scala:144)
  at geotrellis.raster.GridExtent.withResolution$mcJ$sp(GridExtent.scala:143)
  at .$anonfun$res1$1(<console>:38)
  at .$anonfun$res1$1$adapted(<console>:38)
  at scala.Option.map(Option.scala:230)
  ... 36 elided

In #3252 I intentionally didn't touch GridExtent internal logic.

@CloudNiner
Copy link
Contributor

Whew. I can reproduce this now. Looks like I somehow ended up with an incorrect copy of the test case.

CloudNiner added a commit that referenced this issue Jun 17, 2020
Built from RasterSource in test case of #3248
CloudNiner added a commit that referenced this issue Jun 17, 2020
Built from RasterSource in test case of #3248
CloudNiner added a commit that referenced this issue Jun 17, 2020
Built from RasterSource in test case of #3248
CloudNiner added a commit that referenced this issue Jun 18, 2020
Built from RasterSource in test case of #3248
@CloudNiner
Copy link
Contributor

CloudNiner commented Jun 19, 2020

@jisantuc The PR in #3250 should address this specific issue once merged, but here's some background on this issue in this PR comment: #3250 (comment) along with a discussion around how usage of withResolution can lead to unexpected results in this new issue #3261. Not sure if this affects your code, but wanted to highlight it nonetheless.

CloudNiner added a commit that referenced this issue Jun 19, 2020
* Test case for failing withResolution

Built from RasterSource in test case of #3248

* Use [ceil|floor]WithTolerance methods consistently

In the same manner as QGIS and GDAL in order to
correctly compute rows, cols, and cell sizes for
given extents.

* Use math.round in GridExtent.withResolution

So that when we construct new GridExtents in
withResolution, there isn't a mismatch between
the expectation in the constructor.

Before, withResolution used ceil when constructor
used round.

A treatise on whether round is the right choice,
and what withResolution should actually be doing,
is left for future work.

* Use GridExtent constructors in combine

So that we're consistently using round
to estimate cols and rows based on cell size.

Remove use of ceilWithTolerance as it's now unclear
if it helps or hinders us in the places its still
used. It can always be re-added in case we hit edge
cases that still fail later.

* Add GridExtent.withResolution docstrings

Also fixup scaladoc refs in file.

Update CHANGELOG
@pomadchin
Copy link
Member Author

pomadchin commented Jun 22, 2020

Closed in #3250 and the follow up discussions would be done here #3261

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants