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

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

There is a newer version: 0.108.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.Order
import cats.Show
import io.circe.*
import lucuma.core.enums.EphemerisKeyType
import lucuma.core.model.parser.EphemerisKeyParsers
import lucuma.core.optics.Format
import lucuma.core.syntax.string.*
import monocle.Focus
import monocle.Lens

/**
 * Ephemeris data lookup key which uniquely identifies a non-sidereal object in
 * the database.
 */
sealed trait EphemerisKey extends Product with Serializable {

  import EphemerisKey.{ AsteroidNew, AsteroidOld, Comet, MajorBody, UserSupplied }

  /**
   * Human readable desgination that discriminates among ephemeris keys of the
   * same type.
   */
  def des: String

  /**
   * Extracts the ephemeris lookup key type.
   */
  def keyType: EphemerisKeyType =
    this match {
      case AsteroidNew(_)  => EphemerisKeyType.AsteroidNew
      case AsteroidOld(_)  => EphemerisKeyType.AsteroidOld
      case Comet(_)        => EphemerisKeyType.Comet
      case MajorBody(_)    => EphemerisKeyType.MajorBody
      case UserSupplied(_) => EphemerisKeyType.UserSupplied
    }

}

object EphemerisKey extends EphemerisOptics with EphemerisJson {

  /**
   * Unique Horizons designation, which should allow for reproducible ephemeris
   * queries if the values passed to the constructors are extracted
   * correctly from search results.
   */
  sealed abstract class Horizons(val queryString: String) extends EphemerisKey

  /**
   * Horizons designation for asteroids, old and new style.
   */
  sealed abstract class Asteroid(s: String) extends Horizons(s)

  /**
   * Designation for a comet, in the current apparition. Example: `C/1973 E1`
   * for Kohoutek, yielding the query string `NAME=C/1973 E1;CAP`.
   */
  final case class Comet(des: String) extends Horizons(s"NAME=$des;CAP")

  object Comet {
    val des: Lens[Comet, String] = Focus[Comet](_.des)
  }

  /**
   * Designation for an asteroid under modern naming conventions. Example:
   * `1971 UC1` for 1896 Beer, yielding a query string `ASTNAM=1971 UC1`.
   */
  final case class AsteroidNew(des: String) extends Asteroid(s"ASTNAM=$des")

  object AsteroidNew {
    val des: Lens[AsteroidNew, String] = Focus[AsteroidNew](_.des)
  }

  /**
   * Designation for an asteroid under "old" naming conventions. These are
   * small numbers. Example: `4` for Vesta, yielding a query string `4;`
   */
  final case class AsteroidOld(num: Int) extends Asteroid(s"$num;") {
    override def des: String =
      num.toString
  }

  object AsteroidOld {
    val num: Lens[AsteroidOld, Int] = Focus[AsteroidOld](_.num)
  }

  /**
   * Designation for a major body (planet or satellite thereof). These have
   * small numbers. Example: `606` for Titan, yielding a query string `606`.
   */
  final case class MajorBody(num: Int) extends Horizons(s"$num") {
    override def des: String =
      num.toString
  }
  object MajorBody {
    val num: Lens[MajorBody, Int] = Focus[MajorBody](_.num)
  }

  /**
   * Identifies a user-supplied collection of ephemeris data, where the number
   * comes from a database sequence.
   */
  final case class UserSupplied(id: Int) extends EphemerisKey {
    override def des: String =
      id.toString
  }

  object UserSupplied {
    val id: Lens[UserSupplied, Int] = Focus[UserSupplied](_.id)
  }

  given Show[EphemerisKey] =
    Show.fromToString

  given Order[EphemerisKey] =
    Order.by(fromString.reverseGet)
}

trait EphemerisOptics { this: EphemerisKey.type =>

  val fromString: Format[String, EphemerisKey] =
    Format(
      EphemerisKeyParsers.ephemerisKey.parseAll(_).toOption,
      k => {
        val (keyType, des) = fromTypeAndDes.reverseGet(k)
        s"${keyType.tag}_${des}"
      }
    )

  val fromTypeAndDes: Format[(EphemerisKeyType, String), EphemerisKey] =
    Format(
      { case (t, des) =>
        t match {
          case EphemerisKeyType.Comet        => Some(Comet(des))
          case EphemerisKeyType.AsteroidNew  => Some(AsteroidNew(des))
          case EphemerisKeyType.AsteroidOld  => des.parseIntOption.map(AsteroidOld(_))
          case EphemerisKeyType.MajorBody    => des.parseIntOption.map(MajorBody(_))
          case EphemerisKeyType.UserSupplied => des.parseIntOption.map(UserSupplied(_))
        }
      },
      k => (k.keyType, k.des)
    )
}

trait EphemerisJson { this: EphemerisOptics & EphemerisKey.type =>
  given Encoder[EphemerisKey] =
    Encoder[String].contramap(fromString.reverseGet)

  given Decoder[EphemerisKey] =
    Decoder[String].emap(s => fromString.getOption(s).toRight(s"Invalid EphemerisKey value: [$s]"))
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy