Skip to content

Commit

Permalink
Add a SourceName field into RasterSources (#224)
Browse files Browse the repository at this point in the history
* Introduce name for virtual RasterSources that doesn't have a path to the file
* Refactor Metadata and make it lazy
  • Loading branch information
pomadchin committed Aug 12, 2019
1 parent 29bc4e6 commit 0772265
Show file tree
Hide file tree
Showing 36 changed files with 445 additions and 357 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Expose GDAL Metadata API, expose RasterSources Metadata API
- LayoutTileSource[K] implementation
- Add Named RasterSources

## [3.17.0] - 2019-07-23

Expand Down
58 changes: 44 additions & 14 deletions gdal/src/main/scala/geotrellis/contrib/vlm/gdal/GDALMetadata.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,20 @@

package geotrellis.contrib.vlm.gdal

import geotrellis.contrib.vlm.{RasterMetadata, RasterSourceMetadata}
import geotrellis.contrib.vlm.effect.RasterSourceF
import geotrellis.contrib.vlm.{RasterMetadata, SourceName}
import geotrellis.contrib.vlm.effect.RasterMetadataF
import geotrellis.proj4.CRS
import geotrellis.raster.{CellType, GridExtent}

import cats.Monad
import cats.instances.list._
import cats.syntax.functor._
import cats.syntax.flatMap._
import cats.syntax.traverse._
import cats.syntax.apply._

case class GDALMetadata(
name: SourceName,
crs: CRS,
bandCount: Int,
cellType: CellType,
Expand All @@ -34,38 +39,63 @@ case class GDALMetadata(
baseMetadata: Map[GDALMetadataDomain, Map[String, String]] = Map.empty,
/** GDAL per band per domain metadata */
bandsMetadata: List[Map[GDALMetadataDomain, Map[String, String]]] = Nil
) extends RasterSourceMetadata {
) extends RasterMetadata {
/** Returns the GDAL metadata merged into a single metadata domain. */
def attributes: Map[String, String] = baseMetadata.flatMap(_._2)
/** Returns the GDAL per band metadata merged into a single metadata domain. */
def attributesForBand(band: Int): Map[String, String] = bandsMetadata.map(_.flatMap(_._2)).lift(band).getOrElse(Map.empty)
}

object GDALMetadata {
def apply(rasterSource: RasterMetadata, dataset: GDALDataset, domains: List[GDALMetadataDomain]): GDALMetadata = {
def apply(rasterMetadata: RasterMetadata, dataset: GDALDataset, domains: List[GDALMetadataDomain]): GDALMetadata =
domains match {
case Nil =>
GDALMetadata(rasterSource.crs, rasterSource.bandCount, rasterSource.cellType, rasterSource.gridExtent, rasterSource.resolutions)
GDALMetadata(rasterMetadata.name, rasterMetadata.crs, rasterMetadata.bandCount, rasterMetadata.cellType, rasterMetadata.gridExtent, rasterMetadata.resolutions)
case _ =>
GDALMetadata(
rasterSource.crs, rasterSource.bandCount, rasterSource.cellType, rasterSource.gridExtent, rasterSource.resolutions,
rasterMetadata.name, rasterMetadata.crs, rasterMetadata.bandCount, rasterMetadata.cellType, rasterMetadata.gridExtent, rasterMetadata.resolutions,
dataset.getMetadata(GDALDataset.SOURCE, domains, 0),
(1 until dataset.bandCount).toList.map(dataset.getMetadata(GDALDataset.SOURCE, domains, _))
)
}
}

def apply(rasterSource: RasterMetadata, dataset: GDALDataset): GDALMetadata = {
def apply(rasterMetadata: RasterMetadata, dataset: GDALDataset): GDALMetadata =
GDALMetadata(
rasterSource.crs, rasterSource.bandCount, rasterSource.cellType, rasterSource.gridExtent, rasterSource.resolutions,
rasterMetadata.name, rasterMetadata.crs, rasterMetadata.bandCount, rasterMetadata.cellType, rasterMetadata.gridExtent, rasterMetadata.resolutions,
dataset.getAllMetadata(GDALDataset.SOURCE, 0),
(1 until dataset.bandCount).toList.map(dataset.getAllMetadata(GDALDataset.SOURCE, _))
)
}

def apply[F[_] : Monad](rasterSource: RasterSourceF[F], dataset: F[GDALDataset], domains: List[GDALMetadataDomain]): F[GDALMetadata] =
(rasterSource: F[RasterMetadata], dataset).mapN(GDALMetadata.apply(_, _, domains))
def apply[F[_] : Monad](rasterMetadata: RasterMetadataF[F], dataset: F[GDALDataset], domains: List[GDALMetadataDomain]): F[GDALMetadata] =
domains match {
case Nil =>
(Monad[F].pure(rasterMetadata.name), rasterMetadata.crs, rasterMetadata.bandCount, rasterMetadata.cellType, rasterMetadata.gridExtent, rasterMetadata.resolutions).mapN(GDALMetadata(_, _, _, _, _, _))
case _ =>
(Monad[F].pure(rasterMetadata.name),
rasterMetadata.crs,
rasterMetadata.bandCount,
rasterMetadata.cellType,
rasterMetadata.gridExtent,
rasterMetadata.resolutions,
dataset.map(_.getAllMetadata(GDALDataset.SOURCE, 0)),
dataset >>= { ds =>
(1 until ds.bandCount).toList.traverse { list =>
dataset.map(_.getMetadata(GDALDataset.SOURCE, domains, list))
}
}).mapN(apply)
}

def apply[F[_] : Monad](rasterSource: RasterSourceF[F], dataset: F[GDALDataset]): F[GDALMetadata] =
(rasterSource: F[RasterMetadata], dataset).mapN(GDALMetadata.apply)
def apply[F[_] : Monad](rasterMetadata: RasterMetadataF[F], dataset: F[GDALDataset]): F[GDALMetadata] =
(Monad[F].pure(rasterMetadata.name),
rasterMetadata.crs,
rasterMetadata.bandCount,
rasterMetadata.cellType,
rasterMetadata.gridExtent,
rasterMetadata.resolutions,
dataset.map(_.getAllMetadata(GDALDataset.SOURCE, 0)),
dataset >>= { ds =>
(1 until ds.bandCount).toList.traverse { list =>
dataset.map(_.getAllMetadata(GDALDataset.SOURCE, list))
}
}).mapN(apply)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package geotrellis.contrib.vlm.gdal

import geotrellis.contrib.vlm.DataPath
import geotrellis.contrib.vlm.SourcePath

import cats.syntax.option._
import io.lemonlabs.uri._
Expand All @@ -26,7 +26,7 @@ import java.net.MalformedURLException

/** Represents and formats a path that points to a files to be read by GDAL.
*
* @param path Path to the file. This path can be formatted in the following
* @param value Path to the file. This path can be formatted in the following
* styles: `VSI`, `URI`, or relative path if the file is local. In addition,
* this path can be prefixed with, '''gdal+''' to signify that the target GeoTiff
* is to be read in only by [[GDALRasterSource]].
Expand All @@ -39,18 +39,18 @@ import java.net.MalformedURLException
*
* @example "zip+s3://bucket/prefix/zipped-data.zip!data.tif"
*/
case class GDALDataPath(path: String) extends DataPath
case class GDALPath(value: String) extends SourcePath

object GDALDataPath {
object GDALPath {
val PREFIX = "gdal+"

implicit def toGDALDataPath(path: String): GDALDataPath = GDALDataPath.parse(path)
implicit def toGDALDataPath(path: String): GDALPath = GDALPath.parse(path)

def parseOption(
path: String,
compressedFileDelimiter: Option[String] = "!".some,
percentEncoder: PercentEncoder = PercentEncoder(PATH_CHARS_TO_ENCODE ++ Set('%', '?', '#'))
): Option[GDALDataPath] = {
): Option[GDALPath] = {
import Schemes._

// Trying to read something locally on Windows matters
Expand Down Expand Up @@ -108,14 +108,14 @@ object GDALDataPath {
}
}

vsiPath.map(GDALDataPath(_))
vsiPath.map(GDALPath(_))
}

def parse(
path: String,
compressedFileDelimiter: Option[String] = "!".some,
percentEncoder: PercentEncoder = PercentEncoder(PATH_CHARS_TO_ENCODE ++ Set('%', '?', '#'))
): GDALDataPath =
): GDALPath =
parseOption(path, compressedFileDelimiter, percentEncoder)
.getOrElse(throw new MalformedURLException(s"Unable to parse GDALDataPath: $path"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import geotrellis.raster.resample.{NearestNeighbor, ResampleMethod}
import geotrellis.vector._

case class GDALRasterSource(
dataPath: GDALDataPath,
dataPath: GDALPath,
options: GDALWarpOptions = GDALWarpOptions.EMPTY,
private[vlm] val targetCellType: Option[TargetCellType] = None
) extends RasterSource {
Expand All @@ -45,7 +45,8 @@ case class GDALRasterSource(
*
*/

val path: String = dataPath.path
def name: GDALPath = dataPath
val path: String = dataPath.value

lazy val datasetType: DatasetType = options.datasetType

Expand All @@ -59,6 +60,17 @@ case class GDALRasterSource(
*/
lazy val metadata: GDALMetadata = GDALMetadata(this, dataset, DefaultDomain :: Nil)

/**
* Return the "base" metadata, usually it is a zero band metadata,
* a metadata that is valid for the entire source and for the zero band
*/
def attributes: Map[String, String] = metadata.attributes

/**
* Return a per band metadata
*/
def attributesForBand(band: Int): Map[String, String] = metadata.attributesForBand(band)

/**
* Fetches a metadata from the specified [[GDALMetadataDomain]] list.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
package geotrellis.contrib.vlm.gdal

import geotrellis.contrib.vlm.RasterSourceProvider
import geotrellis.contrib.vlm.avro.GeoTrellisDataPath
import geotrellis.contrib.vlm.geotiff.GeoTiffDataPath
import geotrellis.contrib.vlm.avro.GeoTrellisPath
import geotrellis.contrib.vlm.geotiff.GeoTiffPath

class GDALRasterSourceProvider extends RasterSourceProvider {
def canProcess(path: String): Boolean =
(!path.startsWith(GeoTiffDataPath.PREFIX) && !path.startsWith(GeoTrellisDataPath.PREFIX)) && GDALDataPath.parseOption(path).nonEmpty && path.nonEmpty
(!path.startsWith(GeoTiffPath.PREFIX) && !path.startsWith(GeoTrellisPath.PREFIX)) && GDALPath.parseOption(path).nonEmpty && path.nonEmpty

def rasterSource(path: String): GDALRasterSource = GDALRasterSource(path)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package geotrellis.contrib.vlm.gdal.effect

import geotrellis.contrib.vlm._
import geotrellis.contrib.vlm.gdal.{DefaultDomain, GDALDataPath, GDALDataset, GDALMetadata, GDALMetadataDomain, GDALWarpOptions}
import geotrellis.contrib.vlm.gdal.{DefaultDomain, GDALPath, GDALDataset, GDALMetadata, GDALMetadataDomain, GDALWarpOptions}
import geotrellis.contrib.vlm.gdal.GDALDataset.DatasetType
import geotrellis.contrib.vlm.effect._
import geotrellis.contrib.vlm.effect.geotiff.UnsafeLift
Expand All @@ -34,11 +34,12 @@ import cats.syntax.apply._
import cats.syntax.functor._

case class GDALRasterSource[F[_]: Monad: UnsafeLift](
dataPath: GDALDataPath,
dataPath: GDALPath,
options: F[GDALWarpOptions] = GDALWarpOptions.EMPTY,
private[vlm] val targetCellType: Option[TargetCellType] = None
) extends RasterSourceF[F] {
val path: String = dataPath.path
def name: GDALPath = dataPath
val path: String = dataPath.value

lazy val datasetType: F[DatasetType] = options.map(_.datasetType)

Expand Down Expand Up @@ -76,6 +77,17 @@ case class GDALRasterSource[F[_]: Monad: UnsafeLift](
*/
lazy val metadata: F[GDALMetadata] = GDALMetadata(this, datasetF, DefaultDomain :: Nil)

/**
* Return the "base" metadata, usually it is a zero band metadata,
* a metadata that is valid for the entire source and for the zero band
*/
def attributes: F[Map[String, String]] = metadata.map(_.attributes)

/**
* Return a per band metadata
*/
def attributesForBand(band: Int): F[Map[String, String]] = metadata.map(_.attributesForBand(band))

/**
* Fetches a metadata from the specified [[GDALMetadataDomain]] list.
*/
Expand Down
Loading

0 comments on commit 0772265

Please sign in to comment.