All Downloads are FREE. Search and download functionalities are using the official Maven repository.

geotrellis.server.ogc.wms.WmsParams.scala Maven / Gradle / Ivy

Go to download

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 2020 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.server.ogc.wms

import geotrellis.server.ogc._
import geotrellis.server.ogc.params._
import geotrellis.proj4.{CRS, LatLng}
import geotrellis.store.query._
import geotrellis.vector.{io => _, _}
import cats.syntax.apply._
import cats.syntax.option._
import cats.data.{Validated, ValidatedNel}
import Validated._
import geotrellis.raster.{CellSize, RasterExtent}
import geotrellis.store.query.vector.ProjectedGeometry
import io.circe.Codec
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.semiauto.deriveConfiguredCodec

import scala.util.Try

sealed abstract class WmsParams {
  val version: String
}

object WmsParams {

  val wmsVersion = "1.3.0"

  final case class GetCapabilitiesParams(
    version: String,
    format: Option[String],
    updateSequence: Option[String]
  ) extends WmsParams

  object GetCapabilitiesParams {
    def build(params: ParamMap): ValidatedNel[ParamError, WmsParams] =
      (params.validatedVersion(wmsVersion), params.validatedOptionalParam("format"), params.validatedOptionalParam("updatesequence"))
        .mapN(GetCapabilitiesParams.apply)
  }

  case class GetMapParams(
    version: String,
    layers: List[String],
    styles: List[String],
    boundingBox: Extent,
    format: OutputFormat,
    width: Int,
    height: Int,
    crs: CRS,
    time: OgcTime,
    params: ParamMap
  ) extends WmsParams {
    def toQuery: Query = {
      val layer = layers.headOption.map(withName).getOrElse(nothing)
      val query = layer.and(intersects(ProjectedGeometry(boundingBox, crs)))
      time match {
        case timeInterval: OgcTimeInterval => query.and(between(timeInterval.start, timeInterval.end))
        case OgcTimePositions(list)        => query.and(list.toList.map(at(_)).reduce(_ or _))
        case OgcTimeEmpty                  => query
      }
    }

    def rasterExtent: RasterExtent = RasterExtent(boundingBox, width, height)

    def cellSize: CellSize = rasterExtent.cellSize
  }

  object GetMapParams {
    def build(params: ParamMap): ValidatedNel[ParamError, WmsParams] = {
      val versionParam = params.validatedVersion(wmsVersion)
      versionParam
        .andThen { version =>
          val layers = params.validatedParam[List[String]]("layers", s => s.split(",").toList.some)
          val styles = params.validatedParam[List[String]]("styles", s => s.split(",").toList.some)
          val crs = params.validatedParam("crs", s => Try(CRS.fromName(s)).toOption)

          val bbox = crs.andThen { crs =>
            params.validatedParam(
              "bbox",
              s =>
                s.split(",").map(_.toDouble) match {
                  case Array(xmin, ymin, xmax, ymax) =>
                    val extent = Extent(xmin, ymin, xmax, ymax)
                    if (crs.isGeographic) extent.swapXY.some
                    else extent.some
                  case _ => None
                }
            )
          }

          val width = params.validatedParam[Int]("width", s => Try(s.toInt).toOption)
          val height = params.validatedParam[Int]("height", s => Try(s.toInt).toOption)
          val time = params.validatedOgcTime("time")

          val format =
            params
              .validatedParam("format")
              .andThen { f =>
                OutputFormat.fromString(f) match {
                  case Some(format) => Valid(format).toValidatedNel
                  case None =>
                    Invalid(ParamError.UnsupportedFormatError(f)).toValidatedNel
                }
              }

          (layers, styles, bbox, format, width, height, crs, time).mapN { case (layers, styles, bbox, format, width, height, crs, time) =>
            GetMapParams(version, layers, styles, bbox, format = format, width = width, height = height, crs = crs, time = time, params)
          }
        }
    }
  }

  case class GetFeatureInfoParams(
    version: String,
    infoFormat: InfoFormat,
    queryLayers: List[String],
    i: Int, // col
    j: Int, // row
    exceptions: InfoFormat,
    // GetMap params
    layers: List[String],
    boundingBox: Extent,
    format: OutputFormat,
    width: Int,
    height: Int,
    crs: CRS,
    time: OgcTime,
    params: ParamMap
  ) extends WmsParams {
    def toGetMapParams: GetMapParams =
      GetMapParams(version, layers, Nil, boundingBox, format, width, height, crs, time, params)

    def toGetMapParamsQuery: GetMapParams =
      GetMapParams(version, layers.filter(queryLayers.contains), Nil, boundingBox, format, width, height, crs, time, params)

    def rasterExtent: RasterExtent = RasterExtent(boundingBox, width, height)

    def cellSize: CellSize = rasterExtent.cellSize
  }

  object GetFeatureInfoParams {
    def build(params: ParamMap): ValidatedNel[ParamError, WmsParams] = {

      val getMap: ValidatedNel[ParamError, WmsParams] = GetMapParams.build(params)
      val versionParam: ValidatedNel[ParamError, String] = params.validatedVersion(wmsVersion)
      (versionParam, getMap).tupled
        .andThen {
          case (version, getMap: GetMapParams) =>
            val infoFormat =
              params
                .validatedParam("info_format")
                .andThen { f =>
                  InfoFormat.fromString(f) match {
                    case Some(format) => Valid(format).toValidatedNel
                    case None         => Valid(InfoFormat.XML).toValidatedNel
                  }
                }

            val exceptions =
              params
                .validatedOptionalParam("exceptions")
                .andThen {
                  case Some(f) =>
                    InfoFormat.fromString(f) match {
                      case Some(format) => Valid(format).toValidatedNel
                      case None         => Valid(InfoFormat.XML).toValidatedNel
                    }
                  case _ => Valid(InfoFormat.XML).toValidatedNel
                }

            val queryLayers = params.validatedParam[List[String]]("query_layers", s => s.split(",").toList.some)

            val i = params.validatedParam[Int]("i", s => Try(s.toInt).toOption)
            val j = params.validatedParam[Int]("j", s => Try(s.toInt).toOption)

            (infoFormat, exceptions, queryLayers, i, j).mapN { case (infoFormat, exceptions, queryLayers, i, j) =>
              GetFeatureInfoParams(
                version,
                infoFormat,
                queryLayers,
                i,
                j,
                exceptions,
                getMap.layers,
                getMap.boundingBox,
                getMap.format,
                getMap.width,
                getMap.height,
                getMap.crs,
                getMap.time,
                params
              )
            }
          case (_, getMap) => Invalid(ParamError.InvalidValue("getMap", getMap.toString, Nil)).toValidatedNel
        }
    }
  }

  /**
   * An extended version, can be passed via POST request, does not perform mosaics, operates with bulk requests.
   */
  case class GetFeatureInfoExtendedParams(
    version: String,
    layers: List[String],
    multiPoint: MultiPoint,
    crs: CRS = LatLng,
    time: Option[OgcTime] = None,
    cellSize: Option[CellSize] = None
  ) {
    def projectedGeometry: ProjectedGeometry = ProjectedGeometry(multiPoint, crs)

    def getTime: OgcTime = time.getOrElse(OgcTimeEmpty)

    def toQuery: Query = {
      val layer = if (layers.nonEmpty) withNames(layers.toSet) else nothing
      val query = layer.and(intersects(projectedGeometry))
      time.foldLeft(query) { case (q, t) =>
        t match {
          case timeInterval: OgcTimeInterval => query.and(between(timeInterval.start, timeInterval.end))
          case OgcTimePositions(list)        => query.and(list.toList.map(at(_)).reduce(_ or _))
          case OgcTimeEmpty                  => q
        }
      }
    }
  }

  object GetFeatureInfoExtendedParams {
    implicit private val config: Configuration = Configuration.default.copy(transformConstructorNames = _.toLowerCase, useDefaults = true)
    implicit val getFeatureInfoExtendedParamsCodec: Codec[GetFeatureInfoExtendedParams] = deriveConfiguredCodec
  }

  def apply(queryParams: Map[String, Seq[String]]): ValidatedNel[ParamError, WmsParams] = {
    val params = ParamMap(queryParams)

    val serviceParam = params.validatedParam("service", validValues = Set("wms"))
    val requestParam = params.validatedParam("request", validValues = Set("getcapabilities", "getmap", "getfeatureinfo"))
    val firstStageValidation = (serviceParam, requestParam).mapN { case (_, b) => b }

    firstStageValidation.andThen {
      case "getcapabilities" => GetCapabilitiesParams.build(params)
      case "getmap"          => GetMapParams.build(params)
      case "getfeatureinfo"  => GetFeatureInfoParams.build(params)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy