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

lucuma.core.model.Ephemeris.scala Maven / Gradle / Ivy

There is a newer version: 0.105.0
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.core.model

import cats.Eq
import cats.Foldable
import cats.Monoid
import cats.kernel.Order.catsKernelOrderingForOrder
import cats.syntax.all.*
import lucuma.core.optics.SplitMono
import lucuma.core.syntax.treemap.*
import lucuma.core.util.Timestamp

import scala.collection.immutable.TreeMap

/**
 * Time-parameterized coordinates over a fixed interval, defined pairwise. Coordinates that fall
 * between known instants are interpolated.
 */
sealed abstract case class Ephemeris private (toMap: TreeMap[Timestamp, EphemerisCoordinates]) {
  import Ephemeris.Element

  // N.B. this case class is abstract and has a private ctor because we want to keep construction of
  // the TreeMap private to ensure that the correct ordering is used and remains consistent.

  /** First element in this ephemeris, if any. */
  def first: Option[Element] =
    toMap.headOption

  /** Last element in this ephemeris, if any. */
  def last: Option[Element] =
    toMap.lastOption

  /** Coordinates at time `t`, exact if known, interpolated if `bracket(t)` is known. */
  def get(t: Timestamp): Option[EphemerisCoordinates] =
    toMap
      .get(t)
      .orElse(bracket(t).map {
        case ((a, ca), (b, cb)) =>
          val (iʹ, aʹ, bʹ) = (t.toEpochMilli, a.toEpochMilli, b.toEpochMilli)
          val factor       = (iʹ - aʹ).toDouble / (bʹ - aʹ).toDouble
          ca.interpolate(cb, factor)
      })

  /**
   * Greatest lower and least upper bounds of `t`; i.e., the closest elements on either side,
   * inclusive (so if `t` is present then `bracket(t) = (t, t)`).
   */
  def bracket(t: Timestamp): Option[(Element, Element)] =
    (toMap.rangeTo(t).lastOption, toMap.rangeFrom(t).headOption).tupled

  /** The sum of this ephemeris and `e`, taking values from `e` in the case of overlap. */
  def ++(e: Ephemeris): Ephemeris =
    new Ephemeris(toMap ++ e.toMap) {}

}
object Ephemeris {

  /** An ephemeris element. */
  type Element = (Timestamp, EphemerisCoordinates)

  /** The empty ephemeris. */
  val empty: Ephemeris = apply()

  /** Construct an ephemeris from a sequence of literal elements. */
  def apply(es: Element*): Ephemeris =
    fromList(es.toList)

  /** Construct an ephemeris from a `List` of elements. */
  def fromList(es:                     List[Element]): Ephemeris =
    new Ephemeris(TreeMap.fromList(es)) {}

  /** Construct an ephemeris from a foldable of elements. */
  def fromFoldable[F[_]: Foldable](fa: F[Element]): Ephemeris    =
    fromList(fa.toList)

  /** Ephemerides form a monoid, using `++` as the combining operation. */
  given Monoid[Ephemeris] =
    new Monoid[Ephemeris] {
      val empty: Ephemeris = Ephemeris.empty
      def combine(a: Ephemeris, b: Ephemeris) = a ++ b
    }

  given Eq[Ephemeris] =
    Eq.fromUniversalEquals

  val elements: SplitMono[Ephemeris, List[Element]] =
    SplitMono(_.toMap.toList, fromFoldable(_))

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy