Skip to content

Commit

Permalink
Add StacCollections temporal extent
Browse files Browse the repository at this point in the history
  • Loading branch information
pomadchin committed Mar 30, 2021
1 parent 713f4da commit c209c7a
Show file tree
Hide file tree
Showing 16 changed files with 111 additions and 77 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- StacCollectionRasterSource implementation [#340](https://github.com/geotrellis/geotrellis-server/issues/340)
- WCS Rendering formats support [#195](https://github.com/geotrellis/geotrellis-server/issues/195)
- GeoTrellis Server STAC Package [#350](https://github.com/geotrellis/geotrellis-server/issues/350)
- STAC Collection temporal extents support [#347](https://github.com/geotrellis/geotrellis-server/issues/347)

### Changed
- Update GT Server STAC4S dependency [#319](https://github.com/geotrellis/geotrellis-server/issues/319)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,26 +73,24 @@ case class MapAlgebraSourceConf(
* in the algebra field
*/
def model(possibleSources: List[RasterOgcSource]): MapAlgebraSource = {
val layerNames = listParams(algebra)
val sourceList = layerNames.map { name =>
val layerNames = listParams(algebra)
val sourceList = layerNames.map { name =>
val layerSrc = possibleSources.find(_.name == name).getOrElse {
throw new Exception(
s"MAML Layer expected but was unable to find the simple layer '$name', make sure all required layers are in the server configuration and are correctly spelled there and in all provided MAML"
)
}
(layerSrc.timeMetadataKey, name -> layerSrc.source)
name -> layerSrc
}
val timeMetadataKey = sourceList.flatMap(_._1).headOption
MapAlgebraSource(
name,
title,
sourceList.map(_._2).toMap,
sourceList.toMap,
algebra,
defaultStyle,
styles.map(_.toStyle),
resampleMethod,
overviewStrategy,
timeMetadataKey
overviewStrategy
)
}
}
33 changes: 18 additions & 15 deletions ogc/src/main/scala/geotrellis/server/ogc/OgcSource.scala
Original file line number Diff line number Diff line change
Expand Up @@ -212,16 +212,22 @@ case class MapAlgebraSourceMetadata(
case class MapAlgebraSource(
name: String,
title: String,
sources: Map[String, RasterSource],
ogcSources: Map[String, RasterOgcSource],
algebra: Expression,
defaultStyle: Option[String],
styles: List[OgcStyle],
resampleMethod: ResampleMethod,
overviewStrategy: OverviewStrategy,
timeMetadataKey: Option[String]
overviewStrategy: OverviewStrategy
) extends OgcSource {
// each of the underlying ogcSources uses it's own timeMetadataKey
val timeMetadataKey: Option[String] = None

lazy val sources: Map[String, RasterSource] = ogcSources.mapValues(_.source)
lazy val sourcesList: List[RasterSource] = sources.values.toList
lazy val ogcSourcesList: List[RasterOgcSource] = ogcSources.values.toList

def extentIn(crs: CRS): Extent = {
val reprojectedSources: NEL[RasterSource] = NEL.fromListUnsafe(sources.values.map(_.reproject(crs)).toList)
val reprojectedSources: NEL[RasterSource] = NEL.fromListUnsafe(sourcesList.map(_.reproject(crs)))
val extents = reprojectedSources.map(_.extent)

SampleUtils.intersectExtents(extents).getOrElse {
Expand All @@ -230,7 +236,7 @@ case class MapAlgebraSource(
}

def bboxIn(crs: CRS): BoundingBox = {
val reprojectedSources: NEL[RasterSource] = NEL.fromListUnsafe(sources.values.map(_.reproject(crs)).toList)
val reprojectedSources: NEL[RasterSource] = NEL.fromListUnsafe(sourcesList.map(_.reproject(crs)))
val extents = reprojectedSources.map(_.extent)
val extentIntersection = SampleUtils.intersectExtents(extents)
val cellSize = SampleUtils.chooseLargestCellSize(reprojectedSources.map(_.cellSize))
Expand All @@ -253,7 +259,7 @@ case class MapAlgebraSource(
)

lazy val nativeExtent: Extent = {
val reprojectedSources: NEL[RasterSource] = NEL.fromListUnsafe(sources.values.map(_.reproject(nativeCrs.head)).toList)
val reprojectedSources: NEL[RasterSource] = NEL.fromListUnsafe(sourcesList.map(_.reproject(nativeCrs.head)))
val extents = reprojectedSources.map(_.extent)
val extentIntersection = SampleUtils.intersectExtents(extents)

Expand All @@ -264,20 +270,17 @@ case class MapAlgebraSource(
}

lazy val nativeRE: GridExtent[Long] = {
val reprojectedSources: NEL[RasterSource] = NEL.fromListUnsafe(sources.values.map(_.reproject(nativeCrs.head)).toList)
val reprojectedSources: NEL[RasterSource] = NEL.fromListUnsafe(sourcesList.map(_.reproject(nativeCrs.head)))
val cellSize = SampleUtils.chooseSmallestCellSize(reprojectedSources.map(_.cellSize))

new GridExtent[Long](nativeExtent, cellSize)
}

val time: OgcTime =
timeMetadataKey.toList
.flatMap { key => sources.values.toList.map(_.time(key.some)) }
.foldLeft[OgcTime](OgcTimeEmpty)(_ |+| _)
val time: OgcTime = ogcSources.values.toList.map(_.time).foldLeft[OgcTime](OgcTimeEmpty)(_ |+| _)

val attributes: Map[String, String] = Map.empty
lazy val nativeCrs: Set[CRS] = sources.values.map(_.crs).toSet
lazy val minBandCount: Int = sources.values.map(_.bandCount).min
lazy val cellTypes: Set[CellType] = sources.values.map(_.cellType).toSet
lazy val resolutions: List[CellSize] = sources.values.flatMap(_.resolutions).toList.distinct
lazy val nativeCrs: Set[CRS] = ogcSourcesList.flatMap(_.nativeCrs).toSet
lazy val minBandCount: Int = sourcesList.map(_.bandCount).min
lazy val cellTypes: Set[CellType] = sourcesList.map(_.cellType).toSet
lazy val resolutions: List[CellSize] = sourcesList.flatMap(_.resolutions).distinct
}
6 changes: 5 additions & 1 deletion ogc/src/main/scala/geotrellis/server/ogc/OgcTime.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ object OgcTime {
(l, r) match {
case (l: OgcTimePositions, r: OgcTimePositions) => l |+| r
case (l: OgcTimeInterval, r: OgcTimeInterval) => l |+| r
case (l: OgcTimePositions, r: OgcTimeInterval) => l.toOgcTimeInterval |+| r
case (l: OgcTimeInterval, r: OgcTimePositions) => l |+| r.toOgcTimeInterval
case (l: OgcTimePositions, _: OgcTimeEmpty.type) => l
case (l: OgcTimeInterval, _: OgcTimeEmpty.type) => l
case (_: OgcTimeEmpty.type, r: OgcTimePositions) => r
Expand Down Expand Up @@ -126,7 +128,9 @@ object OgcTimeInterval {
OgcTimeInterval(times.head, times.last, None)
}

def apply(timePeriod: ZonedDateTime): OgcTimeInterval = OgcTimeInterval(timePeriod, timePeriod, None)
def apply(start: ZonedDateTime): OgcTimeInterval = OgcTimeInterval(start, start, None)

def apply(start: ZonedDateTime, end: ZonedDateTime): OgcTimeInterval = OgcTimeInterval(start, end, None)

def apply(timeString: String): OgcTimeInterval = fromString(timeString)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ object CoverageView {
val nativeCrs = source.nativeCrs.head
val re = source.nativeRE
val llre = source match {
case MapAlgebraSource(_, _, rss, _, _, _, resampleMethod, _, _) =>
rss.values
case mas: MapAlgebraSource =>
mas.sourcesList
.map { rs =>
ReprojectRasterExtent(
rs.gridExtent,
rs.crs,
LatLng,
Options.DEFAULT.copy(resampleMethod)
Options.DEFAULT.copy(mas.resampleMethod)
)
}
.reduce { (re1, re2) =>
Expand All @@ -82,7 +82,7 @@ object CoverageView {
else re2.cellSize
new GridExtent[Long](e, cs)
}
case rasterOgcLayer: RasterOgcSource =>
case rasterOgcLayer: RasterOgcSource =>
val rs = rasterOgcLayer.source
ReprojectRasterExtent(
rs.gridExtent,
Expand Down
11 changes: 5 additions & 6 deletions ogc/src/main/scala/geotrellis/server/ogc/wcs/WcsModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,11 @@ case class WcsModel[F[_]: Functor](
val filteredSources = sources.find(p.toQuery)
filteredSources.map {
_.map {
case rs: RasterOgcSource => rs.toLayer(p.crs, None, p.temporalSequence)
case MapAlgebraSource(name, title, sources, algebra, _, _, resampleMethod, overviewStrategy, _) =>
val simpleLayers = sources.mapValues { rs =>
SimpleOgcLayer(name, title, p.crs, rs, None, resampleMethod, overviewStrategy)
}
val extendedParameters = extendedParametersBinding.flatMap(_.apply(p.params))
case rs: RasterOgcSource => rs.toLayer(p.crs, None, p.temporalSequence)
case mas: MapAlgebraSource =>
val (name, title, algebra, resampleMethod, overviewStrategy) = (mas.name, mas.title, mas.algebra, mas.resampleMethod, mas.overviewStrategy)
val simpleLayers = mas.sources.mapValues { rs => SimpleOgcLayer(name, title, p.crs, rs, None, resampleMethod, overviewStrategy) }
val extendedParameters = extendedParametersBinding.flatMap(_.apply(p.params))
MapAlgebraOgcLayer(
name,
title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,14 @@ object CapabilitiesView {
},
EX_GeographicBoundingBox = {
val llre = source match {
case MapAlgebraSource(_, _, rss, _, _, _, resampleMethod, _, _) =>
rss.values
case mas: MapAlgebraSource =>
mas.sourcesList
.map { rs =>
ReprojectRasterExtent(
rs.gridExtent,
rs.crs,
LatLng,
Options.DEFAULT.copy(resampleMethod)
Options.DEFAULT.copy(mas.resampleMethod)
)
}
.reduce { (re1, re2) =>
Expand All @@ -183,7 +183,7 @@ object CapabilitiesView {
else re2.cellSize
new GridExtent[Long](e, cs)
}
case rasterOgcLayer: RasterOgcSource =>
case rasterOgcLayer: RasterOgcSource =>
val rs = rasterOgcLayer.source
ReprojectRasterExtent(
rs.gridExtent,
Expand Down Expand Up @@ -240,15 +240,15 @@ object CapabilitiesView {
val bboxAndLayers = model.sources.store map { sources =>
val bboxes = sources map { source =>
val llre = source match {
case MapAlgebraSource(_, _, rss, _, _, _, resampleMethod, _, _) =>
rss.values
.map { rs => ReprojectRasterExtent(rs.gridExtent, rs.crs, LatLng, Options.DEFAULT.copy(resampleMethod)) }
case mas: MapAlgebraSource =>
mas.sourcesList
.map { rs => ReprojectRasterExtent(rs.gridExtent, rs.crs, LatLng, Options.DEFAULT.copy(mas.resampleMethod)) }
.reduce { (re1, re2) =>
val e = re1.extent combine re2.extent
val cs = if (re1.cellSize.resolution < re2.cellSize.resolution) re1.cellSize else re2.cellSize
new GridExtent[Long](e, cs)
}
case rasterOgcLayer: RasterOgcSource =>
case rasterOgcLayer: RasterOgcSource =>
val rs = rasterOgcLayer.source
ReprojectRasterExtent(rs.gridExtent, rs.crs, LatLng, Options.DEFAULT.copy(rasterOgcLayer.resampleMethod))
}
Expand Down
10 changes: 6 additions & 4 deletions ogc/src/main/scala/geotrellis/server/ogc/wms/WmsModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ case class WmsModel[F[_]: Monad](
source.styles.find(_.name == name)
}
source match {
case rs: RasterOgcSource => rs.toLayer(supportedCrs, None, p.time :: Nil)
case MapAlgebraSource(name, title, rasterSources, algebra, _, _, resampleMethod, overviewStrategy, _) =>
val simpleLayers = rasterSources.mapValues { rs =>
case rs: RasterOgcSource => rs.toLayer(supportedCrs, None, p.time :: Nil)
case mas: MapAlgebraSource =>
val (name, title, algebra, resampleMethod, overviewStrategy) =
(mas.name, mas.title, mas.algebra, mas.resampleMethod, mas.overviewStrategy)
val simpleLayers = mas.sources.mapValues { rs =>
SimpleOgcLayer(name, title, supportedCrs, rs, style, resampleMethod, overviewStrategy)
}
val extendedParameters = extendedParametersBinding.flatMap(_.apply(p.params))
val extendedParameters = extendedParametersBinding.flatMap(_.apply(p.params))
MapAlgebraOgcLayer(
name,
title,
Expand Down
8 changes: 5 additions & 3 deletions ogc/src/main/scala/geotrellis/server/ogc/wmts/WmtsModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,14 @@ case class WmtsModel[F[_]: Monad](
val style: Option[OgcStyle] = source.styles.find(_.name == p.style)

source match {
case MapAlgebraSource(name, title, rasterSources, algebra, _, _, resampleMethod, overviewStrategy, _) =>
val simpleLayers = rasterSources.mapValues { rs =>
case mas: MapAlgebraSource =>
val (name, title, algebra, resampleMethod, overviewStrategy) =
(mas.name, mas.title, mas.algebra, mas.resampleMethod, mas.overviewStrategy)
val simpleLayers = mas.sources.mapValues { rs =>
SimpleTiledOgcLayer(name, title, crs, layout, rs, style, resampleMethod, overviewStrategy)
}
MapAlgebraTiledOgcLayer(name, title, crs, layout, simpleLayers, algebra, style, resampleMethod, overviewStrategy)
case SimpleSource(name, title, rasterSource, _, _, resampleMethod, overviewStrategy, _) =>
case SimpleSource(name, title, rasterSource, _, _, resampleMethod, overviewStrategy, _) =>
SimpleTiledOgcLayer(name, title, crs, layout, rasterSource, style, resampleMethod, overviewStrategy)
}
}
Expand Down
2 changes: 2 additions & 0 deletions stac-example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ stac-lc8-red-us = {
asset-limit = 1000 // Max assets returned by the STAC search request
source = "http://localhost:9090/" // Path to the STAC API endpoint
default-style = "red-to-blue"
// force time positions computation even if the range is given through the collection / layer summary
compute-time-positions = true
styles = [
{
name = "red-to-blue"
Expand Down
3 changes: 3 additions & 0 deletions stac-example/src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ layers = {
source = "http://localhost:9090/"
default-time = true
default-style = "red-to-blue"
compute-time-positions = false
styles = [
{
name = "red-to-blue"
Expand All @@ -254,6 +255,7 @@ layers = {
source = "http://localhost:9090/"
default-time = true
default-style = "red-to-blue"
compute-time-positions = false
styles = [
{
name = "red-to-blue"
Expand All @@ -276,6 +278,7 @@ layers = {
source = "http://localhost:9090/"
default-time = true
default-style = "red-to-blue"
compute-time-positions = false
styles = [
{
name = "red-to-blue"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package geotrellis.server.ogc

import geotrellis.server.ogc.stac._

import com.azavea.stac4s.StacCollection
import geotrellis.proj4.CRS
import geotrellis.raster.{RasterSource, ResampleMethod}
Expand All @@ -35,9 +37,20 @@ case class StacOgcSource(
styles: List[OgcStyle],
resampleMethod: ResampleMethod,
overviewStrategy: OverviewStrategy,
timeMetadataKey: Option[String]
timeMetadataKey: Option[String],
computeTimePositions: Boolean
) extends RasterOgcSource {
lazy val time: OgcTime = attributes.time(timeMetadataKey).orElse(source.attributes.time(timeMetadataKey)).getOrElse(source.time(timeMetadataKey))

lazy val time: OgcTime = {
val summaryTime = if (!computeTimePositions) stacSource.stacExtent.ogcTime else None
summaryTime
.orElse(
attributes
.time(timeMetadataKey)
.orElse(source.attributes.time(timeMetadataKey))
)
.getOrElse(source.time(timeMetadataKey))
}

def source: RasterSource = stacSource.source

Expand Down
Loading

0 comments on commit c209c7a

Please sign in to comment.