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

lucuma.ags.AgsParams.scala Maven / Gradle / Ivy

There is a newer version: 0.48.3
Show newest version
// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause

package lucuma.ags

import cats.Eq
import cats.Order
import cats.data.NonEmptyList
import cats.data.NonEmptyMap
import cats.derived.*
import lucuma.core.enums.GmosNorthFpu
import lucuma.core.enums.GmosSouthFpu
import lucuma.core.enums.GuideProbe
import lucuma.core.enums.PortDisposition
import lucuma.core.geom.Area
import lucuma.core.geom.ShapeExpression
import lucuma.core.geom.gmos.probeArm
import lucuma.core.geom.gmos.scienceArea
import lucuma.core.geom.jts.interpreter.given
import lucuma.core.geom.syntax.all.*
import lucuma.core.math.Angle
import lucuma.core.math.Offset
import lucuma.core.math.syntax.int.*

private given Order[Angle] = Angle.SignedAngleOrder

case class AgsPosition(posAngle: Angle, offsetPos: Offset) derives Order

sealed trait AgsGeomCalc:
  // Indicates if the given offset is reachable
  def isReachable(gsOffset: Offset): Boolean

  // Calculates the area vignetted at a given offset
  def vignettingArea(gsOffset: Offset): Area

  // Indicates if the given guide star would vignette the science target
  def overlapsScience(gsOffset: Offset): Boolean

sealed trait AgsParams derives Eq:

  def probe: GuideProbe

  // Builds an AgsGeom object for each position
  // Some of the geometries don't chage with the position and we can cache them
  def posCalculations(positions: NonEmptyList[AgsPosition]): NonEmptyMap[AgsPosition, AgsGeomCalc]

object AgsParams:
  val scienceRadius = 20.arcseconds

  case class GmosAgsParams(
    fpu:  Option[Either[GmosNorthFpu, GmosSouthFpu]],
    port: PortDisposition
  ) extends AgsParams
      derives Eq:
    val probe = GuideProbe.GmosOIWFS

    def posCalculations(
      positions: NonEmptyList[AgsPosition]
    ): NonEmptyMap[AgsPosition, AgsGeomCalc] =
      val result = positions.map { position =>
        position -> new AgsGeomCalc() {
          private val intersectionPatrolField =
            positions
              .map(_.offsetPos)
              .distinct
              // note we use the outer posAngle but the inner offset
              // we want the intersection of offsets at a single PA
              .map(offset => probeArm.patrolFieldAt(position.posAngle, offset, fpu, port))
              .reduce(_ ∩ _)
              .eval

          private val scienceAreaShape =
            scienceArea.shapeAt(position.posAngle, position.offsetPos, fpu)

          private val scienceTargetArea =
            ShapeExpression.centeredEllipse(scienceRadius,
                                            scienceRadius
            ) ↗ position.offsetPos ⟲ position.posAngle

          override def isReachable(gsOffset: Offset): Boolean =
            intersectionPatrolField.contains(gsOffset)

          def overlapsScience(gsOffset: Offset): Boolean =
            // Calculating with area maybe more precise but it is more costly
            (probeArm
              .shapeAt(position.posAngle, gsOffset, position.offsetPos, fpu, port)
              ∩ scienceTargetArea).maxSide.toMicroarcseconds > 5

          override def vignettingArea(gsOffset: Offset): Area =
            (scienceAreaShape ∩
              probeArm.shapeAt(position.posAngle,
                               gsOffset,
                               position.offsetPos,
                               fpu,
                               port
              )).eval.area

        }
      }
      result.toNem




© 2015 - 2024 Weber Informatics LLC | Privacy Policy