From 5e8c0f9182b6c058e83537cf5234e940124973ac Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Wed, 3 Aug 2022 16:02:52 -0500 Subject: [PATCH 1/5] Add union to merge methods --- .../merge/MultibandTileMergeMethods.scala | 31 +++++++ .../raster/merge/RasterMergeMethods.scala | 12 +++ .../merge/RasterTileFeatureMergeMethods.scala | 3 + .../merge/SinglebandTileMergeMethods.scala | 65 +++++++++++++++ .../merge/TileFeatureMergeMethods.scala | 9 +++ .../raster/merge/TileMergeMethods.scala | 5 ++ .../raster/merge/TileUnionMethodsSpec.scala | 80 +++++++++++++++++++ 7 files changed, 205 insertions(+) create mode 100644 raster/src/test/scala/geotrellis/raster/merge/TileUnionMethodsSpec.scala diff --git a/raster/src/main/scala/geotrellis/raster/merge/MultibandTileMergeMethods.scala b/raster/src/main/scala/geotrellis/raster/merge/MultibandTileMergeMethods.scala index f7fcbce270..38f26de482 100644 --- a/raster/src/main/scala/geotrellis/raster/merge/MultibandTileMergeMethods.scala +++ b/raster/src/main/scala/geotrellis/raster/merge/MultibandTileMergeMethods.scala @@ -63,4 +63,35 @@ trait MultibandTileMergeMethods extends TileMergeMethods[MultibandTile] { ArrayMultibandTile(bands) } + + /** + * Union this [[MultibandTile]] with the other one. The output tile's + * extent will be the minimal extent which encompasses both input extents. + * A new MutlibandTile is returned. + * + * @param extent The extent of this MultiBandTile + * @param otherExtent The extent of the other MultiBandTile + * @param other The other MultiBandTile + * @param method The resampling method + * @param unionF The function which decides how values from rasters being combined will be transformed + * @return A new MultiBandTile, the result of the merge + */ + def union( + extent: Extent, + otherExtent: Extent, + other: MultibandTile, + method: ResampleMethod, + unionF: (Option[Double], Option[Double]) => Double + ): MultibandTile = { + val bands: Seq[Tile] = + for { + bandIndex <- 0 until self.bandCount + } yield { + val thisBand = self.band(bandIndex) + val thatBand = other.band(bandIndex) + thisBand.union(extent, otherExtent, thatBand, method, unionF) + } + + ArrayMultibandTile(bands) + } } diff --git a/raster/src/main/scala/geotrellis/raster/merge/RasterMergeMethods.scala b/raster/src/main/scala/geotrellis/raster/merge/RasterMergeMethods.scala index 068ed3fc9a..f95b4859dd 100644 --- a/raster/src/main/scala/geotrellis/raster/merge/RasterMergeMethods.scala +++ b/raster/src/main/scala/geotrellis/raster/merge/RasterMergeMethods.scala @@ -50,4 +50,16 @@ abstract class RasterMergeMethods[ */ def merge(other: Raster[T]): Raster[T] = merge(other, NearestNeighbor) + + /** + * Union this [[Raster]] with the other one. All places in the + * present raster that contain NODATA are filled-in with data from + * the other raster. A new Raster is returned. + * + * @param other The other Raster + * @param method The resampling method + * @return A new Raster, the result of the merge + */ + def union(other: Raster[T], method: ResampleMethod, unionF: (Option[Double], Option[Double]) => Double): Raster[T] = + Raster(self.tile.union(self.extent, other.extent, other.tile, method, unionF), self.extent.combine(other.extent)) } diff --git a/raster/src/main/scala/geotrellis/raster/merge/RasterTileFeatureMergeMethods.scala b/raster/src/main/scala/geotrellis/raster/merge/RasterTileFeatureMergeMethods.scala index e6bdade678..ee64d7ec1d 100644 --- a/raster/src/main/scala/geotrellis/raster/merge/RasterTileFeatureMergeMethods.scala +++ b/raster/src/main/scala/geotrellis/raster/merge/RasterTileFeatureMergeMethods.scala @@ -32,4 +32,7 @@ abstract class RasterTileFeatureMergeMethods[ def merge(other: TileFeature[Raster[T], D], method: ResampleMethod): TileFeature[Raster[T], D] = TileFeature(self.tile.merge(other.tile, method), Semigroup[D].combine(self.data, other.data)) + + def union(other: TileFeature[Raster[T], D], method: ResampleMethod, unionF: (Option[Double], Option[Double]) => Double): TileFeature[Raster[T], D] = + TileFeature(self.tile.union(other.tile, method, unionF), Semigroup[D].combine(self.data, other.data)) } diff --git a/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala b/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala index f8eb8b5790..81dcea6dca 100644 --- a/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala +++ b/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala @@ -154,4 +154,69 @@ trait SinglebandTileMergeMethods extends TileMergeMethods[Tile] { case _ => self } + + def union(extent: Extent, otherExtent: Extent, other: Tile, method: ResampleMethod, unionF: (Option[Double], Option[Double]) => Double): Tile = { + val unionInt = (l: Option[Double], r: Option[Double]) => unionF(l, r).toInt + + val combinedExtent = otherExtent combine extent + val re = RasterExtent(extent, self.cols, self.rows) + val gridBounds = re.gridBoundsFor(combinedExtent, false) + val targetCS = CellSize(combinedExtent, gridBounds.width, gridBounds.height) + val targetRE = RasterExtent(combinedExtent, targetCS) + val mutableTile = ArrayTile.empty(self.cellType, targetRE.cols, targetRE.rows) + + self.cellType match { + case BitCellType | ByteCellType | UByteCellType | ShortCellType | UShortCellType | IntCellType => + val interpolateLeft: (Double, Double) => Int = Resample(method, self, extent, targetCS).resample _ + val interpolateRight: (Double, Double) => Int = Resample(method, other, otherExtent, targetCS).resample _ + // Assume 0 as the transparent value + cfor(0)(_ < targetRE.rows, _ + 1) { row => + cfor(0)(_ < targetRE.cols, _ + 1) { col => + val (x,y) = targetRE.gridToMap(col, row) + val (l,r) = (interpolateLeft(x, y), interpolateRight(x, y)) + mutableTile.set(col, row, unionInt(Some(l.toDouble), Some(r.toDouble))) + } + } + case FloatCellType | DoubleCellType => + val interpolateLeft: (Double, Double) => Double = Resample(method, self, extent, targetCS).resampleDouble _ + val interpolateRight: (Double, Double) => Double = Resample(method, other, otherExtent, targetCS).resampleDouble _ + + // Assume 0.0 as the transparent value + cfor(0)(_ < targetRE.rows, _ + 1) { row => + cfor(0)(_ < targetRE.cols, _ + 1) { col => + val (x,y) = targetRE.gridToMap(col, row) + val (l,r) = (interpolateLeft(x, y), interpolateRight(x, y)) + mutableTile.setDouble(col, row, unionF(Some(l), Some(r))) + } + } + case x if x.isFloatingPoint => + val interpolateLeft: (Double, Double) => Double = Resample(method, self, extent, targetCS).resampleDouble _ + val interpolateRight: (Double, Double) => Double = Resample(method, other, otherExtent, targetCS).resampleDouble _ + cfor(0)(_ < targetRE.rows, _ + 1) { row => + cfor(0)(_ < targetRE.cols, _ + 1) { col => + val (x,y) = targetRE.gridToMap(col, row) + val l = interpolateLeft(x, y) + val r = interpolateRight(x, y) + val maybeL = if (isNoData(l)) None else Some(l) + val maybeR = if (isNoData(r)) None else Some(r) + mutableTile.setDouble(col, row, unionF(maybeL, maybeR)) + } + } + case _ => + val interpolateLeft: (Double, Double) => Int = Resample(method, self, extent, targetCS).resample _ + val interpolateRight: (Double, Double) => Int = Resample(method, other, otherExtent, targetCS).resample _ + cfor(0)(_ < targetRE.rows, _ + 1) { row => + cfor(0)(_ < targetRE.cols, _ + 1) { col => + val (x,y) = targetRE.gridToMap(col, row) + val l = interpolateLeft(x, y) + val r = interpolateRight(x, y) + val maybeL = if (isNoData(l)) None else Some(l.toDouble) + val maybeR = if (isNoData(r)) None else Some(r.toDouble) + mutableTile.set(col, row, unionInt(maybeL, maybeR)) + //if (l!=r) println(s"x => ${x}, y => ${y}, col => ${col}, row => ${row} | l,r => ${l}, ${r}") + } + } + } + mutableTile + } } diff --git a/raster/src/main/scala/geotrellis/raster/merge/TileFeatureMergeMethods.scala b/raster/src/main/scala/geotrellis/raster/merge/TileFeatureMergeMethods.scala index 7d47e0949e..ef67f042ca 100644 --- a/raster/src/main/scala/geotrellis/raster/merge/TileFeatureMergeMethods.scala +++ b/raster/src/main/scala/geotrellis/raster/merge/TileFeatureMergeMethods.scala @@ -31,4 +31,13 @@ abstract class TileFeatureMergeMethods[ def merge(extent: Extent, otherExtent: Extent, other: TileFeature[T, D], method: ResampleMethod): TileFeature[T, D] = TileFeature(self.tile.merge(extent, otherExtent, other.tile, method), Semigroup[D].combine(self.data, other.data)) + + def union( + extent: Extent, + otherExtent: Extent, + other: TileFeature[T, D], + method: ResampleMethod, + unionF: (Option[Double], Option[Double]) => Double + ): TileFeature[T, D] = + TileFeature(self.tile.union(extent, otherExtent, other.tile, method, unionF), Semigroup[D].combine(self.data, other.data)) } diff --git a/raster/src/main/scala/geotrellis/raster/merge/TileMergeMethods.scala b/raster/src/main/scala/geotrellis/raster/merge/TileMergeMethods.scala index 0604934cee..e00ba4ec13 100644 --- a/raster/src/main/scala/geotrellis/raster/merge/TileMergeMethods.scala +++ b/raster/src/main/scala/geotrellis/raster/merge/TileMergeMethods.scala @@ -78,4 +78,9 @@ trait TileMergeMethods[T] extends MethodExtensions[T] { def merge(extent: Extent, otherExtent: Extent, other: T): T = merge(extent, otherExtent, other, NearestNeighbor) + + def union(extent: Extent, otherExtent: Extent, other: T, method: ResampleMethod, unionF: (Option[Double], Option[Double]) => Double): T + + def union(extent: Extent, otherExtent: Extent, other: T, unionF: (Option[Double], Option[Double]) => Double): T = + union(extent, otherExtent, other, NearestNeighbor, unionF) } diff --git a/raster/src/test/scala/geotrellis/raster/merge/TileUnionMethodsSpec.scala b/raster/src/test/scala/geotrellis/raster/merge/TileUnionMethodsSpec.scala new file mode 100644 index 0000000000..758334cf35 --- /dev/null +++ b/raster/src/test/scala/geotrellis/raster/merge/TileUnionMethodsSpec.scala @@ -0,0 +1,80 @@ +/* + * Copyright 2016 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.raster.merge + +import geotrellis.raster._ +import geotrellis.raster.testkit._ +import geotrellis.raster.resample.NearestNeighbor +import geotrellis.vector.Extent + +import org.scalatest.matchers.should.Matchers +import org.scalatest.funspec.AnyFunSpec + +class TileUnionMethodsSpec extends AnyFunSpec + with Matchers + with TileBuilders + with RasterMatchers { + describe("SinglebandTileMergeMethods") { + + it("should union two tiles such that the extent of the output is equal to the minimum extent which covers both") { + val cellTypes: Seq[CellType] = + Seq( + BitCellType, + ByteCellType, + ByteConstantNoDataCellType, + ByteUserDefinedNoDataCellType(1.toByte), + UByteCellType, + UByteConstantNoDataCellType, + UByteUserDefinedNoDataCellType(1.toByte), + ShortCellType, + ShortConstantNoDataCellType, + ShortUserDefinedNoDataCellType(1.toShort), + UShortCellType, + UShortConstantNoDataCellType, + UShortUserDefinedNoDataCellType(1.toShort), + IntCellType, + IntConstantNoDataCellType, + IntUserDefinedNoDataCellType(1), + FloatCellType, + FloatConstantNoDataCellType, + FloatUserDefinedNoDataCellType(1.0f), + DoubleCellType, + DoubleConstantNoDataCellType, + DoubleUserDefinedNoDataCellType(1.0) + ) + + for(ct <- cellTypes) { + val arr = Array.ofDim[Double](100).fill(5.0) + arr(50) = 1.0 + arr(55) = 0.0 + arr(60) = Double.NaN + + val tile1 = + DoubleArrayTile(arr, 10, 10).convert(ct) + val e1 = Extent(0, 0, 1, 1) + val tile2 = + tile1.prototype(ct, tile1.cols, tile1.rows) + val e2 = Extent(1, 1, 2, 2) + val unioned = tile1.union(e1, e2, tile2, NearestNeighbor, (d1, d2) => d1.getOrElse(4)) + withClue(s"Failing on cell type $ct: ") { + unioned.rows shouldBe (20) + unioned.cols shouldBe (20) + } + } + } + } +} From bfdc085f6f05e4aa2957b82e02965b50ef94d010 Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Thu, 4 Aug 2022 13:00:24 -0500 Subject: [PATCH 2/5] Change unionF to unionFunc --- .../raster/merge/MultibandTileMergeMethods.scala | 6 +++--- .../geotrellis/raster/merge/RasterMergeMethods.scala | 4 ++-- .../raster/merge/RasterTileFeatureMergeMethods.scala | 4 ++-- .../raster/merge/SinglebandTileMergeMethods.scala | 8 ++++---- .../geotrellis/raster/merge/TileFeatureMergeMethods.scala | 4 ++-- .../scala/geotrellis/raster/merge/TileMergeMethods.scala | 6 +++--- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/raster/src/main/scala/geotrellis/raster/merge/MultibandTileMergeMethods.scala b/raster/src/main/scala/geotrellis/raster/merge/MultibandTileMergeMethods.scala index 38f26de482..a918ccef06 100644 --- a/raster/src/main/scala/geotrellis/raster/merge/MultibandTileMergeMethods.scala +++ b/raster/src/main/scala/geotrellis/raster/merge/MultibandTileMergeMethods.scala @@ -73,7 +73,7 @@ trait MultibandTileMergeMethods extends TileMergeMethods[MultibandTile] { * @param otherExtent The extent of the other MultiBandTile * @param other The other MultiBandTile * @param method The resampling method - * @param unionF The function which decides how values from rasters being combined will be transformed + * @param unionFunc The function which decides how values from rasters being combined will be transformed * @return A new MultiBandTile, the result of the merge */ def union( @@ -81,7 +81,7 @@ trait MultibandTileMergeMethods extends TileMergeMethods[MultibandTile] { otherExtent: Extent, other: MultibandTile, method: ResampleMethod, - unionF: (Option[Double], Option[Double]) => Double + unionFunc: (Option[Double], Option[Double]) => Double ): MultibandTile = { val bands: Seq[Tile] = for { @@ -89,7 +89,7 @@ trait MultibandTileMergeMethods extends TileMergeMethods[MultibandTile] { } yield { val thisBand = self.band(bandIndex) val thatBand = other.band(bandIndex) - thisBand.union(extent, otherExtent, thatBand, method, unionF) + thisBand.union(extent, otherExtent, thatBand, method, unionFunc) } ArrayMultibandTile(bands) diff --git a/raster/src/main/scala/geotrellis/raster/merge/RasterMergeMethods.scala b/raster/src/main/scala/geotrellis/raster/merge/RasterMergeMethods.scala index f95b4859dd..ba02221227 100644 --- a/raster/src/main/scala/geotrellis/raster/merge/RasterMergeMethods.scala +++ b/raster/src/main/scala/geotrellis/raster/merge/RasterMergeMethods.scala @@ -60,6 +60,6 @@ abstract class RasterMergeMethods[ * @param method The resampling method * @return A new Raster, the result of the merge */ - def union(other: Raster[T], method: ResampleMethod, unionF: (Option[Double], Option[Double]) => Double): Raster[T] = - Raster(self.tile.union(self.extent, other.extent, other.tile, method, unionF), self.extent.combine(other.extent)) + def union(other: Raster[T], method: ResampleMethod, unionFunc: (Option[Double], Option[Double]) => Double): Raster[T] = + Raster(self.tile.union(self.extent, other.extent, other.tile, method, unionFunc), self.extent.combine(other.extent)) } diff --git a/raster/src/main/scala/geotrellis/raster/merge/RasterTileFeatureMergeMethods.scala b/raster/src/main/scala/geotrellis/raster/merge/RasterTileFeatureMergeMethods.scala index ee64d7ec1d..2e08eaa224 100644 --- a/raster/src/main/scala/geotrellis/raster/merge/RasterTileFeatureMergeMethods.scala +++ b/raster/src/main/scala/geotrellis/raster/merge/RasterTileFeatureMergeMethods.scala @@ -33,6 +33,6 @@ abstract class RasterTileFeatureMergeMethods[ def merge(other: TileFeature[Raster[T], D], method: ResampleMethod): TileFeature[Raster[T], D] = TileFeature(self.tile.merge(other.tile, method), Semigroup[D].combine(self.data, other.data)) - def union(other: TileFeature[Raster[T], D], method: ResampleMethod, unionF: (Option[Double], Option[Double]) => Double): TileFeature[Raster[T], D] = - TileFeature(self.tile.union(other.tile, method, unionF), Semigroup[D].combine(self.data, other.data)) + def union(other: TileFeature[Raster[T], D], method: ResampleMethod, unionFunc: (Option[Double], Option[Double]) => Double): TileFeature[Raster[T], D] = + TileFeature(self.tile.union(other.tile, method, unionFunc), Semigroup[D].combine(self.data, other.data)) } diff --git a/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala b/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala index 81dcea6dca..9e396cb4da 100644 --- a/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala +++ b/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala @@ -155,8 +155,8 @@ trait SinglebandTileMergeMethods extends TileMergeMethods[Tile] { self } - def union(extent: Extent, otherExtent: Extent, other: Tile, method: ResampleMethod, unionF: (Option[Double], Option[Double]) => Double): Tile = { - val unionInt = (l: Option[Double], r: Option[Double]) => unionF(l, r).toInt + def union(extent: Extent, otherExtent: Extent, other: Tile, method: ResampleMethod, unionFunc: (Option[Double], Option[Double]) => Double): Tile = { + val unionInt = (l: Option[Double], r: Option[Double]) => unionFunc(l, r).toInt val combinedExtent = otherExtent combine extent val re = RasterExtent(extent, self.cols, self.rows) @@ -186,7 +186,7 @@ trait SinglebandTileMergeMethods extends TileMergeMethods[Tile] { cfor(0)(_ < targetRE.cols, _ + 1) { col => val (x,y) = targetRE.gridToMap(col, row) val (l,r) = (interpolateLeft(x, y), interpolateRight(x, y)) - mutableTile.setDouble(col, row, unionF(Some(l), Some(r))) + mutableTile.setDouble(col, row, unionFunc(Some(l), Some(r))) } } case x if x.isFloatingPoint => @@ -199,7 +199,7 @@ trait SinglebandTileMergeMethods extends TileMergeMethods[Tile] { val r = interpolateRight(x, y) val maybeL = if (isNoData(l)) None else Some(l) val maybeR = if (isNoData(r)) None else Some(r) - mutableTile.setDouble(col, row, unionF(maybeL, maybeR)) + mutableTile.setDouble(col, row, unionFunc(maybeL, maybeR)) } } case _ => diff --git a/raster/src/main/scala/geotrellis/raster/merge/TileFeatureMergeMethods.scala b/raster/src/main/scala/geotrellis/raster/merge/TileFeatureMergeMethods.scala index ef67f042ca..db8658b1d9 100644 --- a/raster/src/main/scala/geotrellis/raster/merge/TileFeatureMergeMethods.scala +++ b/raster/src/main/scala/geotrellis/raster/merge/TileFeatureMergeMethods.scala @@ -37,7 +37,7 @@ abstract class TileFeatureMergeMethods[ otherExtent: Extent, other: TileFeature[T, D], method: ResampleMethod, - unionF: (Option[Double], Option[Double]) => Double + unionFunc: (Option[Double], Option[Double]) => Double ): TileFeature[T, D] = - TileFeature(self.tile.union(extent, otherExtent, other.tile, method, unionF), Semigroup[D].combine(self.data, other.data)) + TileFeature(self.tile.union(extent, otherExtent, other.tile, method, unionFunc), Semigroup[D].combine(self.data, other.data)) } diff --git a/raster/src/main/scala/geotrellis/raster/merge/TileMergeMethods.scala b/raster/src/main/scala/geotrellis/raster/merge/TileMergeMethods.scala index e00ba4ec13..163e93c843 100644 --- a/raster/src/main/scala/geotrellis/raster/merge/TileMergeMethods.scala +++ b/raster/src/main/scala/geotrellis/raster/merge/TileMergeMethods.scala @@ -79,8 +79,8 @@ trait TileMergeMethods[T] extends MethodExtensions[T] { merge(extent, otherExtent, other, NearestNeighbor) - def union(extent: Extent, otherExtent: Extent, other: T, method: ResampleMethod, unionF: (Option[Double], Option[Double]) => Double): T + def union(extent: Extent, otherExtent: Extent, other: T, method: ResampleMethod, unionFunc: (Option[Double], Option[Double]) => Double): T - def union(extent: Extent, otherExtent: Extent, other: T, unionF: (Option[Double], Option[Double]) => Double): T = - union(extent, otherExtent, other, NearestNeighbor, unionF) + def union(extent: Extent, otherExtent: Extent, other: T, unionFunc: (Option[Double], Option[Double]) => Double): T = + union(extent, otherExtent, other, NearestNeighbor, unionFunc) } From d22e985e1332bee621f234f94c8dbd019c9e71e0 Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Thu, 4 Aug 2022 13:22:27 -0500 Subject: [PATCH 3/5] Remove code duplication in merge logic --- .../merge/SinglebandTileMergeMethods.scala | 63 +++++++++---------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala b/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala index 9e396cb4da..686c63aea1 100644 --- a/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala +++ b/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala @@ -99,15 +99,15 @@ trait SinglebandTileMergeMethods extends TileMergeMethods[Tile] { val gridBounds = re.gridBoundsFor(sharedExtent) val targetCS = CellSize(sharedExtent, gridBounds.width, gridBounds.height) + val interpolate = Resample(method, other, otherExtent, targetCS) + self.cellType match { case BitCellType | ByteCellType | UByteCellType | ShortCellType | UShortCellType | IntCellType => - val interpolate: (Double, Double) => Int = Resample(method, other, otherExtent, targetCS).resample _ - // Assume 0 as the transparent value cfor(0)(_ < self.rows, _ + 1) { row => cfor(0)(_ < self.cols, _ + 1) { col => if (self.get(col, row) == 0) { val (x, y) = re.gridToMap(col, row) - val v = interpolate(x, y) + val v = interpolate.resample(x, y) if(isData(v)) { mutableTile.set(col, row, v) } @@ -115,13 +115,11 @@ trait SinglebandTileMergeMethods extends TileMergeMethods[Tile] { } } case FloatCellType | DoubleCellType => - val interpolate: (Double, Double) => Double = Resample(method, other, otherExtent, targetCS).resampleDouble _ - // Assume 0.0 as the transparent value cfor(0)(_ < self.rows, _ + 1) { row => cfor(0)(_ < self.cols, _ + 1) { col => if (self.getDouble(col, row) == 0.0) { val (x, y) = re.gridToMap(col, row) - val v = interpolate(x, y) + val v = interpolate.resampleDouble(x, y) if(isData(v)) { mutableTile.setDouble(col, row, v) } @@ -129,22 +127,20 @@ trait SinglebandTileMergeMethods extends TileMergeMethods[Tile] { } } case x if x.isFloatingPoint => - val interpolate: (Double, Double) => Double = Resample(method, other, otherExtent, targetCS).resampleDouble _ cfor(0)(_ < self.rows, _ + 1) { row => cfor(0)(_ < self.cols, _ + 1) { col => if (isNoData(self.getDouble(col, row))) { val (x, y) = re.gridToMap(col, row) - mutableTile.setDouble(col, row, interpolate(x, y)) + mutableTile.setDouble(col, row, interpolate.resampleDouble(x, y)) } } } case _ => - val interpolate: (Double, Double) => Int = Resample(method, other, otherExtent, targetCS).resample _ cfor(0)(_ < self.rows, _ + 1) { row => cfor(0)(_ < self.cols, _ + 1) { col => if (isNoData(self.get(col, row))) { val (x, y) = re.gridToMap(col, row) - mutableTile.set(col, row, interpolate(x, y)) + mutableTile.set(col, row, interpolate.resample(x, y)) } } } @@ -156,7 +152,10 @@ trait SinglebandTileMergeMethods extends TileMergeMethods[Tile] { } def union(extent: Extent, otherExtent: Extent, other: Tile, method: ResampleMethod, unionFunc: (Option[Double], Option[Double]) => Double): Tile = { - val unionInt = (l: Option[Double], r: Option[Double]) => unionFunc(l, r).toInt + val unionInt: (Option[Int], Option[Int]) => Int = + (l: Option[Int], r: Option[Int]) => { + unionFunc(l.map(_.toDouble), r.map(_.toDouble)).toInt + } val combinedExtent = otherExtent combine extent val re = RasterExtent(extent, self.cols, self.rows) @@ -165,53 +164,53 @@ trait SinglebandTileMergeMethods extends TileMergeMethods[Tile] { val targetRE = RasterExtent(combinedExtent, targetCS) val mutableTile = ArrayTile.empty(self.cellType, targetRE.cols, targetRE.rows) + val interpolateLeft = Resample(method, self, extent, targetCS) + val interpolateRight = Resample(method, other, otherExtent, targetCS) + self.cellType match { case BitCellType | ByteCellType | UByteCellType | ShortCellType | UShortCellType | IntCellType => - val interpolateLeft: (Double, Double) => Int = Resample(method, self, extent, targetCS).resample _ - val interpolateRight: (Double, Double) => Int = Resample(method, other, otherExtent, targetCS).resample _ - // Assume 0 as the transparent value cfor(0)(_ < targetRE.rows, _ + 1) { row => cfor(0)(_ < targetRE.cols, _ + 1) { col => val (x,y) = targetRE.gridToMap(col, row) - val (l,r) = (interpolateLeft(x, y), interpolateRight(x, y)) - mutableTile.set(col, row, unionInt(Some(l.toDouble), Some(r.toDouble))) + val l = interpolateLeft.resample(x, y) + val r = interpolateRight.resample(x, y) + val maybeL = Some(l) + val maybeR = Some(r) + val v = unionInt(maybeL, maybeR) + mutableTile.set(col, row, v) } } case FloatCellType | DoubleCellType => - val interpolateLeft: (Double, Double) => Double = Resample(method, self, extent, targetCS).resampleDouble _ - val interpolateRight: (Double, Double) => Double = Resample(method, other, otherExtent, targetCS).resampleDouble _ - - // Assume 0.0 as the transparent value cfor(0)(_ < targetRE.rows, _ + 1) { row => cfor(0)(_ < targetRE.cols, _ + 1) { col => val (x,y) = targetRE.gridToMap(col, row) - val (l,r) = (interpolateLeft(x, y), interpolateRight(x, y)) - mutableTile.setDouble(col, row, unionFunc(Some(l), Some(r))) + val l = interpolateLeft.resampleDouble(x, y) + val r = interpolateRight.resampleDouble(x, y) + val maybeL = Some(l) + val maybeR = Some(r) + val v = unionFunc(maybeL, maybeR) + mutableTile.setDouble(col, row, v) } } case x if x.isFloatingPoint => - val interpolateLeft: (Double, Double) => Double = Resample(method, self, extent, targetCS).resampleDouble _ - val interpolateRight: (Double, Double) => Double = Resample(method, other, otherExtent, targetCS).resampleDouble _ cfor(0)(_ < targetRE.rows, _ + 1) { row => cfor(0)(_ < targetRE.cols, _ + 1) { col => val (x,y) = targetRE.gridToMap(col, row) - val l = interpolateLeft(x, y) - val r = interpolateRight(x, y) + val l = interpolateLeft.resampleDouble(x, y) + val r = interpolateRight.resampleDouble(x, y) val maybeL = if (isNoData(l)) None else Some(l) val maybeR = if (isNoData(r)) None else Some(r) mutableTile.setDouble(col, row, unionFunc(maybeL, maybeR)) } } case _ => - val interpolateLeft: (Double, Double) => Int = Resample(method, self, extent, targetCS).resample _ - val interpolateRight: (Double, Double) => Int = Resample(method, other, otherExtent, targetCS).resample _ cfor(0)(_ < targetRE.rows, _ + 1) { row => cfor(0)(_ < targetRE.cols, _ + 1) { col => val (x,y) = targetRE.gridToMap(col, row) - val l = interpolateLeft(x, y) - val r = interpolateRight(x, y) - val maybeL = if (isNoData(l)) None else Some(l.toDouble) - val maybeR = if (isNoData(r)) None else Some(r.toDouble) + val l = interpolateLeft.resample(x, y) + val r = interpolateRight.resample(x, y) + val maybeL = if (isNoData(l)) None else Some(l) + val maybeR = if (isNoData(r)) None else Some(r) mutableTile.set(col, row, unionInt(maybeL, maybeR)) //if (l!=r) println(s"x => ${x}, y => ${y}, col => ${col}, row => ${row} | l,r => ${l}, ${r}") } From f9cc308d7199be5c87872b1f0dd6e6c325840651 Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Thu, 4 Aug 2022 13:38:48 -0500 Subject: [PATCH 4/5] Clean up semigroup usage --- .../raster/merge/SinglebandTileMergeMethods.scala | 1 - .../geotrellis/raster/merge/TileFeatureMergeMethods.scala | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala b/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala index 686c63aea1..9b62c5ebb8 100644 --- a/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala +++ b/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala @@ -212,7 +212,6 @@ trait SinglebandTileMergeMethods extends TileMergeMethods[Tile] { val maybeL = if (isNoData(l)) None else Some(l) val maybeR = if (isNoData(r)) None else Some(r) mutableTile.set(col, row, unionInt(maybeL, maybeR)) - //if (l!=r) println(s"x => ${x}, y => ${y}, col => ${col}, row => ${row} | l,r => ${l}, ${r}") } } } diff --git a/raster/src/main/scala/geotrellis/raster/merge/TileFeatureMergeMethods.scala b/raster/src/main/scala/geotrellis/raster/merge/TileFeatureMergeMethods.scala index db8658b1d9..d2f6a7984f 100644 --- a/raster/src/main/scala/geotrellis/raster/merge/TileFeatureMergeMethods.scala +++ b/raster/src/main/scala/geotrellis/raster/merge/TileFeatureMergeMethods.scala @@ -19,7 +19,9 @@ package geotrellis.raster.merge import geotrellis.raster._ import geotrellis.raster.resample._ import geotrellis.vector._ + import cats.Semigroup +import cats.syntax.semigroup._ abstract class TileFeatureMergeMethods[ @@ -27,10 +29,10 @@ abstract class TileFeatureMergeMethods[ D: Semigroup ](val self: TileFeature[T, D]) extends TileMergeMethods[TileFeature[T, D]] { def merge(other: TileFeature[T, D], baseCol: Int, baseRow: Int): TileFeature[T, D] = - TileFeature(self.tile.merge(other.tile, baseCol, baseRow), Semigroup[D].combine(self.data, other.data)) + TileFeature(self.tile.merge(other.tile, baseCol, baseRow), self.data combine other.data) def merge(extent: Extent, otherExtent: Extent, other: TileFeature[T, D], method: ResampleMethod): TileFeature[T, D] = - TileFeature(self.tile.merge(extent, otherExtent, other.tile, method), Semigroup[D].combine(self.data, other.data)) + TileFeature(self.tile.merge(extent, otherExtent, other.tile, method), self.data combine other.data) def union( extent: Extent, @@ -39,5 +41,5 @@ abstract class TileFeatureMergeMethods[ method: ResampleMethod, unionFunc: (Option[Double], Option[Double]) => Double ): TileFeature[T, D] = - TileFeature(self.tile.union(extent, otherExtent, other.tile, method, unionFunc), Semigroup[D].combine(self.data, other.data)) + TileFeature(self.tile.union(extent, otherExtent, other.tile, method, unionFunc), self.data combine other.data) } From 2f64ec17f251dc9c03331459cea0cc322beb79aa Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Mon, 15 Aug 2022 15:41:41 -0500 Subject: [PATCH 5/5] Update changelog and docstring --- CHANGELOG.md | 1 + .../raster/merge/SinglebandTileMergeMethods.scala | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa39c42a81..c5a159d8c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add RasterSourceRDD.tiledLayerRDD within the geometry intersection [#3474](https://github.com/locationtech/geotrellis/pull/3474) - Expose AWS_REQUEST_PAYER environment variable [#3479](https://github.com/locationtech/geotrellis/pull/3479) +- Add union/outer join merger of tiles [#3482](https://github.com/locationtech/geotrellis/pull/3482) ### Changed - Migration to CE3 and other major dependencies upgrade [#3389](https://github.com/locationtech/geotrellis/pull/3389) diff --git a/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala b/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala index 9b62c5ebb8..2518d4fed9 100644 --- a/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala +++ b/raster/src/main/scala/geotrellis/raster/merge/SinglebandTileMergeMethods.scala @@ -151,6 +151,15 @@ trait SinglebandTileMergeMethods extends TileMergeMethods[Tile] { self } + /** Unions this tile with another tile, preserving the non-overlapping + * portions of each tile extent. + * + * This method requires a union function to be provided which decides + * how values will be added to the output raster. A `None` represents + * one of the two input rasters not having a value at the resampled location. + * Two `None` values mean that both tiles are missing values at the + * cell in question. + */ def union(extent: Extent, otherExtent: Extent, other: Tile, method: ResampleMethod, unionFunc: (Option[Double], Option[Double]) => Double): Tile = { val unionInt: (Option[Int], Option[Int]) => Int = (l: Option[Int], r: Option[Int]) => {