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

OgcTime logic cleanup #371

Merged
merged 3 commits into from
May 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- WMS Parent Layer default time should be omitted [#368](/~https://github.com/geotrellis/geotrellis-server/pull/368)
- Fix WCS DescribeCoverage extents axis ordering [#369](/~https://github.com/geotrellis/geotrellis-server/pull/369)

## Changed
- OgcTime logic cleanup [#371](/~https://github.com/geotrellis/geotrellis-server/pull/371)

## [4.4.0] - 2021-04-30

## Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ case class RasterSourceConf(
resampleMethod: ResampleMethod = ResampleMethod.DEFAULT,
overviewStrategy: OverviewStrategy = OverviewStrategy.DEFAULT,
datetimeField: String = SimpleSource.TimeFieldDefault,
timeFormat: OgcTimeFormat = OgcTimeFormat.Self,
timeFormat: OgcTimeFormat = OgcTimeFormat.Default,
timeDefault: OgcTimeDefault = OgcTimeDefault.Oldest
) extends OgcSourceConf {
def toLayer: RasterOgcSource = {
Expand Down Expand Up @@ -84,7 +84,7 @@ case class MapAlgebraSourceConf(
styles: List[StyleConf],
resampleMethod: ResampleMethod = ResampleMethod.DEFAULT,
overviewStrategy: OverviewStrategy = OverviewStrategy.DEFAULT,
timeFormat: OgcTimeFormat = OgcTimeFormat.Self,
timeFormat: OgcTimeFormat = OgcTimeFormat.Default,
timeDefault: OgcTimeDefault = OgcTimeDefault.Oldest
) extends OgcSourceConf {
private def listParams(expr: Expression): List[String] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,7 @@ class WcsView[F[_]: Concurrent: Parallel: ApplicativeThrow: Logger](
WcsParams(req.multiParams) match {
case Validated.Invalid(errors) =>
val msg = ParamError.generateErrorMessage(errors.toList)
logger.warn(s"""Error parsing parameters: ${msg}""") *> BadRequest(
s"""Error parsing parameters: ${msg}"""
)
logger.warn(msg) >> BadRequest(msg)

case Validated.Valid(wcsParams) =>
wcsParams match {
Expand Down
10 changes: 5 additions & 5 deletions ogc/src/main/scala/geotrellis/server/ogc/OgcSource.scala
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ case class SimpleSource(
timeFormat: OgcTimeFormat
) extends RasterOgcSource {
val timeDefault: OgcTimeDefault = OgcTimeDefault.Oldest
lazy val time: OgcTime = source.time(timeMetadataKey).format(timeFormat)
lazy val time: OgcTime = source.time(timeMetadataKey).format(timeFormat).sorted

def toLayer(crs: CRS, style: Option[OgcStyle], temporalSequence: List[OgcTime]): SimpleOgcLayer =
SimpleOgcLayer(name, title, crs, source, style, resampleMethod, overviewStrategy)
Expand Down Expand Up @@ -123,8 +123,8 @@ case class GeoTrellisOgcSource(
temporalSequence.headOption match {
case Some(t) if t.nonEmpty => sourceForTime(t)
case _ if temporalSequence.isEmpty && source.isTemporal =>
val sorted = source.times
val time = timeDefault match {
lazy val sorted = source.times.sorted
val time = timeDefault match {
case OgcTimeDefault.Oldest => sorted.head
case OgcTimeDefault.Newest => sorted.last
case OgcTimeDefault.Time(dt) => dt
Expand Down Expand Up @@ -155,7 +155,7 @@ case class GeoTrellisOgcSource(

lazy val time: OgcTime =
if (!source.isTemporal) OgcTimeEmpty
else OgcTimePositions(source.times).format(timeFormat)
else OgcTimePositions(source.times).format(timeFormat).sorted

/** If temporal, try to match in the following order:
*
Expand Down Expand Up @@ -273,7 +273,7 @@ case class MapAlgebraSource(
new GridExtent[Long](nativeExtent, cellSize)
}

val time: OgcTime = ogcSources.values.toList.map(_.time).foldLeft[OgcTime](OgcTimeEmpty)(_ |+| _).format(timeFormat)
val time: OgcTime = ogcSources.values.toList.map(_.time).foldLeft[OgcTime](OgcTimeEmpty)(_ |+| _).format(timeFormat).sorted

val attributes: Map[String, String] = Map.empty
lazy val nativeCrs: Set[CRS] = ogcSourcesList.flatMap(_.nativeCrs).toSet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ object OgcSourceRepository {
_.filter {
_.time match {
case OgcTimePositions(list) =>
val sorted = list.toList.sorted
val sorted = list.sorted
val start = sorted.head
val end = sorted.last
t1 <= start && start <= t2 || t1 <= end && end <= t2
Expand Down
22 changes: 16 additions & 6 deletions ogc/src/main/scala/geotrellis/server/ogc/OgcTime.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package geotrellis.server.ogc

import cats.data.NonEmptyList
import cats.{Monoid, Order, Semigroup}
import cats.{Monoid, Semigroup}
import cats.syntax.semigroup._
import jp.ne.opt.chronoscala.Imports._
import org.threeten.extra.PeriodDuration
Expand Down Expand Up @@ -67,7 +67,7 @@ object OgcTime {
case positions: OgcTimePositions => positions
case _ => self
}
case OgcTimeFormat.Self => self
case OgcTimeFormat.Default => self
}

def strictTimeMatch(dt: ZonedDateTime): Boolean =
Expand All @@ -76,6 +76,16 @@ object OgcTime {
case OgcTimeInterval(start, _, _) => start == dt
case OgcTimeEmpty => true
}

def sorted: OgcTime =
self match {
case OgcTimeEmpty => OgcTimeEmpty
case OgcTimePositions(list) => OgcTimePositions(list.sorted)
case OgcTimeInterval(start, end, interval) =>
val List(s, e) = List(start, end).sorted
OgcTimeInterval(s, e, interval)

}
}
}

Expand All @@ -88,8 +98,6 @@ case object OgcTimeEmpty extends OgcTime {

/** Represents the TimePosition used in TimeSequence requests */
final case class OgcTimePositions(list: NonEmptyList[ZonedDateTime]) extends OgcTime {
import OgcTimePositions._

def sorted: NonEmptyList[ZonedDateTime] = list.sorted

/** Compute (if possible) the period of the [[ZonedDateTime]] lists. */
Expand All @@ -114,8 +122,6 @@ final case class OgcTimePositions(list: NonEmptyList[ZonedDateTime]) extends Ogc
}

object OgcTimePositions {
implicit val timeOrder: Order[ZonedDateTime] = Order.fromOrdering

implicit val ogcTimePositionsSemigroup: Semigroup[OgcTimePositions] = { (l, r) =>
OgcTimePositions((l.list ::: r.list).distinct.sorted)
}
Expand All @@ -128,6 +134,8 @@ object OgcTimePositions {
case _ => OgcTimeEmpty
}
def apply(times: List[String])(implicit d: DummyImplicit): OgcTime = apply(times.map(ZonedDateTime.parse))

def parse(times: List[String]): Try[OgcTime] = Try(apply(times.map(ZonedDateTime.parse)))
}

/** Represents the TimeInterval used in TimeSequence requests
Expand Down Expand Up @@ -196,4 +204,6 @@ object OgcTimeInterval {
case _ => throw new UnsupportedOperationException("Unsupported string format for OgcTimeInterval")
}
}

def parse(timeString: String): Try[OgcTimeInterval] = Try(fromString(timeString))
}
8 changes: 5 additions & 3 deletions ogc/src/main/scala/geotrellis/server/ogc/OgcTimeDefault.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ object OgcTimeDefault {
}

implicit class OgcTimeDefaultOps(val self: OgcTimeDefault) extends AnyVal {
def selectTime(list: NonEmptyList[ZonedDateTime]): ZonedDateTime =
def selectTime(list: NonEmptyList[ZonedDateTime]): ZonedDateTime = {
lazy val sorted = list.sorted
self match {
case OgcTimeDefault.Oldest => list.head
case OgcTimeDefault.Newest => list.last
case OgcTimeDefault.Oldest => sorted.head
case OgcTimeDefault.Newest => sorted.last
case OgcTimeDefault.Time(t) => t
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ object OgcTimeFormat {
case object Interval extends OgcTimeFormat

/** Don't change the internal [[OgcTime]] representation. */
case object Self extends OgcTimeFormat
case object Default extends OgcTimeFormat

private implicit val config: Configuration = Configuration.default.copy(transformConstructorNames = _.toLowerCase)
implicit val ogcTimeFormatCodec: Codec[OgcTimeFormat] = deriveEnumerationCodec
Expand Down
6 changes: 6 additions & 0 deletions ogc/src/main/scala/geotrellis/server/ogc/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@ import geotrellis.proj4.CRS
import geotrellis.raster.{CellSize, Histogram, MultibandTile, Raster}
import geotrellis.server.ogc.style.OgcStyle
import geotrellis.vector.Extent
import cats.Order
import org.threeten.extra.PeriodDuration
import jp.ne.opt.chronoscala.Imports._

import java.time.ZonedDateTime

package object ogc {
implicit val ZonedDateTimeOrder: Order[ZonedDateTime] = Order.fromOrdering[ZonedDateTime]

implicit class ExtentOps(val self: Extent) extends AnyVal {
def swapXY: Extent = Extent(xmin = self.ymin, ymin = self.xmin, xmax = self.ymax, ymax = self.xmax)
def buffer(cellSize: CellSize): Extent = self.buffer(cellSize.width / 2, cellSize.height / 2)
Expand Down
29 changes: 22 additions & 7 deletions ogc/src/main/scala/geotrellis/server/ogc/params/ParamMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
package geotrellis.server.ogc.params

import geotrellis.server.ogc._

import cats.syntax.option._
import cats.data.{Validated, ValidatedNel}
import cats.syntax.traverse._
import cats.data.{NonEmptyList, Validated, ValidatedNel}
import Validated._

import scala.util.{Failure, Success, Try}
Expand Down Expand Up @@ -118,12 +118,27 @@ case class ParamMap(params: Map[String, Seq[String]]) {
}
}).toValidatedNel

def validatedOgcTimeSequence(field: String): ValidatedNel[ParamError, List[OgcTime]] =
validatedOptionalParam(field).map {
case Some(timeString) if timeString.contains("/") => timeString.split(",").map(OgcTimeInterval.fromString).toList
case Some(timeString) => OgcTimePositions(timeString.split(",").toList) :: Nil
case None => List.empty[OgcTime]
def validatedOgcTimeSequence(field: String): ValidatedNel[ParamError, List[OgcTime]] = {
validatedOptionalParam(field).andThen {
case Some(timeString) if timeString.contains("/") =>
timeString
.split(",")
.map(OgcTimeInterval.parse)
.toList
.sequence match {
case Success(list) => Valid(list)
case Failure(e) => Invalid(NonEmptyList.of(ParamError.ParseError(field, e.getMessage)))
}
case Some(timeString) =>
OgcTimePositions
.parse(timeString.split(",").toList)
.map(_ :: Nil) match {
case Success(list) => Valid(list)
case Failure(e) => Invalid(NonEmptyList.of(ParamError.ParseError(field, e.getMessage)))
}
case None => Valid(Nil)
}
}

def validatedOgcTime(field: String): ValidatedNel[ParamError, OgcTime] =
validatedOgcTimeSequence(field).map(_.headOption.getOrElse(OgcTimeEmpty))
Expand Down
4 changes: 2 additions & 2 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ object Dependencies {
val http4sDsl = http4sVer("dsl")
val http4sXml = http4sVer("scala-xml")
val jaxbApi = "javax.xml.bind" % "jaxb-api" % jaxbApiVer
val kindProjector = "org.typelevel" %% "kind-projector" % "0.11.3"
val semanticdbScalac = "org.scalameta" % "semanticdb-scalac" % "4.4.10"
val kindProjector = "org.typelevel" %% "kind-projector" % "0.13.0"
val semanticdbScalac = "org.scalameta" % "semanticdb-scalac" % "4.4.18"
val log4cats = "io.chrisdavenport" %% "log4cats-slf4j" % "1.1.1"
val mamlJvm = "com.azavea.geotrellis" %% "maml-jvm" % "0.6.1"
val pureConfig = "com.github.pureconfig" %% "pureconfig" % "0.14.1"
Expand Down
10 changes: 5 additions & 5 deletions stac-example/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ postgres:
migrations:
docker run --rm \
--network stac-example_default \
quay.io/azavea/franklin:404f1c0 \
quay.io/azavea/franklin:367cdc7 \
migrate \
--db-user franklin \
--db-name franklin \
Expand All @@ -23,7 +23,7 @@ import-landsat-stac-collection:
--network stac-example_default \
-e AWS_REGION=us-east-1 \
-v ${PWD}/catalog:/opt/data/ \
quay.io/azavea/franklin:404f1c0 \
quay.io/azavea/franklin:367cdc7 \
import-catalog \
--db-user franklin \
--db-name franklin \
Expand All @@ -36,7 +36,7 @@ import-landsat-stac-periodic-collection:
--network stac-example_default \
-e AWS_REGION=us-east-1 \
-v ${PWD}/catalog:/opt/data/ \
quay.io/azavea/franklin:404f1c0 \
quay.io/azavea/franklin:367cdc7 \
import-catalog \
--db-user franklin \
--db-name franklin \
Expand All @@ -49,7 +49,7 @@ import-landsat-stac-layers:
--network stac-example_default \
-e AWS_REGION=us-east-1 \
-v ${PWD}/catalog:/opt/data/ \
quay.io/azavea/franklin:404f1c0 \
quay.io/azavea/franklin:367cdc7 \
import-catalog \
--db-user franklin \
--db-name franklin \
Expand All @@ -61,7 +61,7 @@ run-franklin:
docker run --rm \
--network stac-example_default \
-p 9090:9090 \
quay.io/azavea/franklin:404f1c0 \
quay.io/azavea/franklin:367cdc7 \
serve \
--db-user franklin \
--db-name franklin \
Expand Down
8 changes: 4 additions & 4 deletions stac-example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ 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
// force time positions fetch from items even if the range is given through the collection / layer summary
fetch-time-positions = true
// optional field (can be added to all layers definitions)
// the default value is "self", meaning that it would use the OGC Time in a format recieved from the source
time-format = "{interval | positions | self}"
// the default value is "default", meaning that it would use the OGC Time in a format recieved from the source
time-format = "{interval | positions | default}"
ignore-time = false // work with temporal layers as with spatial layers, false by default
// specify the default temporal rollback, oldest by default
time-default = "{newest | oldest | ISO compatible date time}"
Expand Down
8 changes: 4 additions & 4 deletions stac-example/src/main/scala/geotrellis/server/ogc/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,11 @@ object Main
for {
conf <- Conf.loadResourceF[IO](configPath)
/** MosaicRasterSources pool init for the graceful shutdown. */
blockingPool <- Resource.make(IO.delay(BlockingThreadPool.pool))(p => IO.delay(p.shutdown()))
// blockingPool <- Resource.make(IO.delay(BlockingThreadPool.pool))(p => IO.delay(p.shutdown()))
/** Uses server pool. */
http4sClient <- Http4sBackend.usingDefaultBlazeClientBuilder[IO](Blocker.liftExecutionContext(executionContext), executionContext)
simpleSources = conf.layers.values.collect { case rsc: RasterSourceConf => rsc.toLayer }.toList
_ <- Resource.liftF(
_ <- Resource.eval(
logOptState(
conf.wms,
ansi"%green{WMS configuration detected}, starting Web Map Service",
Expand All @@ -131,7 +131,7 @@ object Main
ExtendedParameters.extendedParametersBinding
)
}
_ <- Resource.liftF(
_ <- Resource.eval(
logOptState(
conf.wmts,
ansi"%green{WMTS configuration detected}, starting Web Map Tiling Service",
Expand All @@ -145,7 +145,7 @@ object Main
svc.layerSources(simpleSources, http4sClient)
)
}
_ <- Resource.liftF(
_ <- Resource.eval(
logOptState(
conf.wcs,
ansi"%green{WCS configuration detected}, starting Web Coverage Service",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ case class StacOgcSource(
resampleMethod: ResampleMethod,
overviewStrategy: OverviewStrategy,
timeMetadataKey: Option[String],
computeTimePositions: Boolean,
fetchTimePositions: Boolean,
timeFormat: OgcTimeFormat,
timeDefault: OgcTimeDefault
) extends RasterOgcSource {

lazy val time: OgcTime = {
val summaryTime = if (!computeTimePositions) stacSource.stacExtent.ogcTime else None
val summaryTime = if (!fetchTimePositions) stacSource.stacExtent.ogcTime else None
summaryTime
.orElse(
attributes
Expand All @@ -53,6 +53,7 @@ case class StacOgcSource(
)
.getOrElse(source.time(timeMetadataKey))
.format(timeFormat)
.sorted
}

def source: RasterSource = stacSource.source
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ object Conf {
}

def loadResourceF[F[_]: Sync](configPath: Option[String]): Resource[F, Conf] =
Resource.liftF(loadF[F](configPath))
Resource.eval(loadF[F](configPath))

// This is a work-around to use pureconfig to read scalaxb generated case classes
// DataRecord should never be specified from configuration, this satisfied the resolution
Expand Down
Loading