
geotrellis.raster.effects.MosaicRasterSourceIO.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of geotrellis-effects_2.12 Show documentation
Show all versions of geotrellis-effects_2.12 Show documentation
GeoTrellis Server is a set of components designed to simplify viewing, processing, and serving raster data from arbitrary sources with an emphasis on doing so in a functional style.
The newest version!
/*
* Copyright 2021 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.effects
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
import geotrellis.raster.{MosaicRasterSource => MosaicRasterSourceS, _}
import geotrellis.vector._
import geotrellis.raster.resample._
import geotrellis.proj4.CRS
import geotrellis.raster.io.geotiff.OverviewStrategy
import cats.syntax.parallel._
import cats.data.NonEmptyList
import cats.effect.IO
import cats.effect.unsafe.IORuntime
/**
* Single threaded instance of a reader for reading windows out of collections of rasters
*
* @param sources
* The underlying [[RasterSource]]s that you'll use for data access
* @param crs
* The crs to reproject all [[RasterSource]]s to anytime we need information about their data Since MosaicRasterSources represent collections of
* [[RasterSource]]s, we don't know in advance whether they'll have the same CRS. crs allows specifying the CRS on read instead of having to make
* sure at compile time that you're threading CRSes through everywhere correctly.
*/
abstract class MosaicRasterSourceIO extends RasterSource {
implicit def runtime: IORuntime
implicit val logger: Logger[IO] = Slf4jLogger.getLogger
def sources: NonEmptyList[RasterSource]
lazy val sourcesIO: NonEmptyList[IO[RasterSource]] = sources.map(IO.blocking(_))
def crs: CRS
def gridExtent: GridExtent[Long]
import MosaicRasterSourceS._
val targetCellType = None
/**
* The bandCount of the first [[RasterSource]] in sources
*
* If this value is larger than the bandCount of later [[RasterSource]]s in sources, reads of all bands will fail. It is a client's responsibility
* to construct mosaics that can be read.
*/
def bandCount: Int = sources.head.bandCount
def cellType: CellType =
sourcesIO
.parTraverse(_.flatMap { rs =>
logger
.trace(s"${rs.name}::cellType: ${Thread.currentThread().getName}")
.as(rs.cellType)
})
.map(_.toList.reduce(_ union _))
.unsafeRunSync()
/**
* All available RasterSources metadata.
*/
def metadata: MosaicMetadata = MosaicMetadata(name, crs, bandCount, cellType, gridExtent, resolutions, sources)
def attributes: Map[String, String] = Map.empty
def attributesForBand(band: Int): Map[String, String] = Map.empty
/**
* All available resolutions for all RasterSources in this MosaicRasterSourceFixed
*
* @see
* [[geotrellis.raster.RasterSource.resolutions]]
*/
def resolutions: List[CellSize] =
sourcesIO
.parTraverse(_.flatMap { rs =>
logger
.trace(s"${rs.name}::resolutions: ${Thread.currentThread().getName}")
.as(rs.resolutions)
})
.map(_.reduce)
.unsafeRunSync()
/**
* Create a new MosaicRasterSourceFixed with sources transformed according to the provided crs, options, and strategy, and a new crs
*
* @see
* [[geotrellis.raster.RasterSource.reproject]]
*/
def reprojection(
targetCRS: CRS,
resampleTarget: ResampleTarget = DefaultTarget,
method: ResampleMethod = ResampleMethod.DEFAULT,
strategy: OverviewStrategy = OverviewStrategy.DEFAULT
): RasterSource =
MosaicRasterSourceIO
.instance(
sourcesIO.parTraverse(_.map(_.reproject(targetCRS, resampleTarget, method, strategy))).unsafeRunSync(),
targetCRS,
name,
attributes
)
def read(extent: Extent, bands: Seq[Int]): Option[Raster[MultibandTile]] =
sourcesIO
.parTraverse {
_.flatMap { rs =>
logger
.trace(s"${rs.name}::read($extent, $bands): ${Thread.currentThread().getName}")
.as(rs.read(extent, bands))
}
}
.map(_.reduce)
.unsafeRunSync()
def read(bounds: GridBounds[Long], bands: Seq[Int]): Option[Raster[MultibandTile]] = {
/**
* The passed bounds are relative to the [[MosaicRasterSourceIO]] bounds. However, each [[RasterSource]] has its own [[GridBounds]]. Before
* passing [[GridBounds]] into each underlying [[RasterSource]] we need to map them into the each [[RasterSource]] relative grid space.
*
* This is done by calculating the relative offset using each [[RasterSource]] underlying [[Extent]].
*
* @param gb
* global bounds, relative to the [[MosaicRasterSourceIO]]
* @param extent
* extent of the [[RasterSource]]
* @return
* relative to the extent [[GridBounds]]
*/
def relativeGridBounds(gb: GridBounds[Long], extent: Extent): GridBounds[Long] = {
val GridBounds(colMin, rowMin, colMax, rowMax) = gb
val (sourceColOffset, sourceRowOffset) = gridExtent.mapToGrid(extent.xmin, extent.ymax)
GridBounds(
colMin - sourceColOffset,
rowMin - sourceRowOffset,
colMax - sourceColOffset,
rowMax - sourceRowOffset
)
}
sourcesIO
.parTraverse {
_.flatMap { rs =>
logger
.trace(s"${rs.name}::read($bounds, $bands): ${Thread.currentThread().getName}")
.as(rs.read(relativeGridBounds(bounds, rs.extent), bands))
}
}
.map(_.reduce)
.unsafeRunSync()
}
def resample(
resampleTarget: ResampleTarget,
method: ResampleMethod,
strategy: OverviewStrategy
): RasterSource =
MosaicRasterSourceIO
.instance(
sourcesIO.parTraverse(_.map(_.resample(resampleTarget, method, strategy))).unsafeRunSync(),
crs,
name,
attributes
)
def convert(targetCellType: TargetCellType): RasterSource =
MosaicRasterSourceIO
.instance(
sourcesIO.parTraverse(_.map(_.convert(targetCellType))).unsafeRunSync(),
crs,
name,
attributes
)
override def toString: String = s"MosaicRasterSourceFixed(${sources.toList}, $crs, $gridExtent, $name)"
}
object MosaicRasterSourceIO {
/**
* This instance method allows to create an instance of the MosaicRasterSource. It assumes that all raster sources are known, and the GridExtent is
* also known.
*/
def instance(
sourcesList: NonEmptyList[RasterSource],
targetCRS: CRS,
targetGridExtent: GridExtent[Long],
sourceName: SourceName,
stacAttributes: Map[String, String]
)(implicit ceRuntime: IORuntime): MosaicRasterSourceIO =
new MosaicRasterSourceIO {
implicit val runtime: IORuntime = ceRuntime
val sources: NonEmptyList[RasterSource] = sourcesList
val crs: CRS = targetCRS
val gridExtent: GridExtent[Long] = targetGridExtent
val name: SourceName = sourceName
override val attributes: Map[String, String] = stacAttributes
}
/**
* This instance method allows to create an instance of the MosaicRasterSourceFixed. It computes the MosaicRasterSourceFixed basing on the input
* sourcesList.
*/
def instance(
sourcesList: NonEmptyList[RasterSource],
targetCRS: CRS,
sourceName: SourceName,
stacAttributes: Map[String, String]
)(implicit ceRuntime: IORuntime): MosaicRasterSourceIO = {
val combinedExtent = sourcesList.map(_.extent).toList.reduce(_ combine _)
val minCellSize = sourcesList.map(_.cellSize).toList.maxBy(_.resolution)
val combinedGridExtent = GridExtent[Long](combinedExtent, minCellSize)
instance(sourcesList, targetCRS, combinedGridExtent, sourceName, stacAttributes)
}
def instance(sourcesList: NonEmptyList[RasterSource], targetCRS: CRS)(implicit ceRuntime: IORuntime): MosaicRasterSourceIO =
instance(sourcesList, targetCRS, EmptyName, Map.empty)
def instance(sourcesList: NonEmptyList[RasterSource], targetCRS: CRS, targetGridExtent: GridExtent[Long])(implicit
ceRuntime: IORuntime
): MosaicRasterSourceIO =
instance(sourcesList, targetCRS, targetGridExtent, EmptyName, Map.empty)
/**
* All apply methods reproject the input sourcesList to the targetGridExtent
*/
def apply(sourcesList: NonEmptyList[RasterSource], targetCRS: CRS, targetGridExtent: GridExtent[Long])(implicit
ceRuntime: IORuntime
): MosaicRasterSourceIO =
apply(sourcesList, targetCRS, targetGridExtent, EmptyName)
def apply(
sourcesList: NonEmptyList[RasterSource],
targetCRS: CRS,
targetGridExtent: GridExtent[Long],
rasterSourceName: SourceName
)(implicit ceRuntime: IORuntime): MosaicRasterSourceIO =
new MosaicRasterSourceIO {
implicit val runtime: IORuntime = ceRuntime
val name = rasterSourceName
lazy val sources = sourcesList.map(IO.blocking(_)).parTraverse(_.map(_.reprojectToGrid(targetCRS, gridExtent))).unsafeRunSync()(runtime)
val crs = targetCRS
val gridExtent: GridExtent[Long] = targetGridExtent
}
def apply(sourcesList: NonEmptyList[RasterSource], targetCRS: CRS)(implicit ceRuntime: IORuntime): MosaicRasterSourceIO =
apply(sourcesList, targetCRS, EmptyName)
def apply(sourcesList: NonEmptyList[RasterSource], targetCRS: CRS, rasterSourceName: SourceName)(implicit
ceRuntime: IORuntime
): MosaicRasterSourceIO =
new MosaicRasterSourceIO {
implicit val runtime: IORuntime = ceRuntime
val name = rasterSourceName
val sources =
sourcesList.map(IO.blocking(_)).parTraverse(_.map(_.reprojectToGrid(targetCRS, sourcesList.head.gridExtent))).unsafeRunSync()(runtime)
val crs = targetCRS
def gridExtent: GridExtent[Long] = {
val reprojectedSources = sourcesIO.toList
val combinedExtent = reprojectedSources.parTraverse(_.map(_.extent)).map(_.reduce(_ combine _)).unsafeRunSync()(runtime)
val minCellSize = reprojectedSources.parTraverse(_.map(_.cellSize)).map(_.maxBy(_.resolution)).unsafeRunSync()(runtime)
GridExtent[Long](combinedExtent, minCellSize)
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy