CdmRelativeMetadata.java

/* Copyright 2002-2026 CS GROUP
 * Licensed to CS GROUP (CS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * CS licenses this file to You 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 org.orekit.files.ccsds.ndm.cdm;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.orekit.annotation.Nullable;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.files.ccsds.definitions.PocMethodFacade;
import org.orekit.files.ccsds.definitions.TimeSystem;
import org.orekit.time.AbsoluteDate;

/** This class gathers the relative meta-data present in the Conjunction Data Message (CDM).
 * @author Melina Vanel
 * @since 11.2
 */
public class CdmRelativeMetadata {

    /** Time System: used for metadata, orbit state and covariance data. */
    private TimeSystem timeSystem;

    /** Comment. */
    private final List<String> comment;

    /** Date and time in UTC of the closest approach. */
    private AbsoluteDate tca;

    /** Norm of relative position vector at TCA. */
    private double missDistance;

    /** Norm of relative velocity vector at TCA. */
    @Nullable
    private Double relativeSpeed;

    /** The length of the relative position vector, normalized to one-sigma dispersions of the combined error covariance
     * in the direction of the relative position vector. */
    @Nullable
    private Double mahalanobisDistance;

    /** The R component of Object2's position relative to Object1's position in the Radial/Transverse/Normal coordinate frame. */
    @Nullable
    private Double relativePositionR;

    /** The T component of Object2's position relative to Object1's position in the Radial/Transverse/Normal coordinate frame. */
    @Nullable
    private Double relativePositionT;

    /** The N component of Object2's position relative to Object1's position in the Radial/Transverse/Normal coordinate frame. */
    @Nullable
    private Double relativePositionN;

    /** The R component of Object2's velocity relative to Object1's veloity in the Radial/Transverse/Normal coordinate frame. */
    @Nullable
    private Double relativeVelocityR;

    /** The T component of Object2's velocity relative to Object1's veloity in the Radial/Transverse/Normal coordinate frame. */
    @Nullable
    private Double relativeVelocityT;

    /** The N component of Object2's velocity relative to Object1's veloity in the Radial/Transverse/Normal coordinate frame. */
    @Nullable
    private Double relativeVelocityN;

    /** The start time in UTC of the screening period for the conjunction assessment. */
    @Nullable
    private AbsoluteDate startScreenPeriod;

    /** The stop time in UTC of the screening period for the conjunction assessment. */
    @Nullable
    private AbsoluteDate stopScreenPeriod;

    /** Shape of the screening volume. */
    @Nullable
    private ScreenVolumeShape screenVolumeShape;

    /** Radius of the screening volume. */
    @Nullable
    private Double screenVolumeRadius;

    /** Name of the Object1 centered reference frame in which the screening volume data are given. */
    @Nullable
    private ScreenVolumeFrame screenVolumeFrame;

    /** The R or T (depending on if RTN or TVN is selected) component size of the screening volume in the SCREEN_VOLUME_FRAME. */
    @Nullable
    private Double screenVolumeX;

    /** The T or V (depending on if RTN or TVN is selected) component size of the screening volume in the SCREEN_VOLUME_FRAME. */
    @Nullable
    private Double screenVolumeY;

    /** The N component size of the screening volume in the SCREEN_VOLUME_FRAME. */
    @Nullable
    private Double screenVolumeZ;

    /** The time in UTC when Object2 enters the screening volume. */
    @Nullable
    private AbsoluteDate screenEntryTime;

    /** The time in UTC when Object2 exits the screening volume. */
    @Nullable
    private AbsoluteDate screenExitTime;

    /** The probability (denoted 'p' where 0.0<=p<=1.0), that Object1 and Object2 will collide. */
    @Nullable
    private Double collisionProbability;

    /** The method that was used to calculate the collision probability. */
    @Nullable
    private PocMethodFacade collisionProbabilityMethod;

    /** the Originator's ID that uniquely identifies the conjunction to which the message refers. */
    @Nullable
    private String conjunctionId;

    /** The approach angle computed between Objects 1 and 2 in the RTN coordinate frame relative to object 1. */
    @Nullable
    private Double approachAngle;

    /** The type of screening to be used. */
    @Nullable
    private ScreenType screenType;

    /** The maximum collision probability that Object1 and Object2 will collide. */
    @Nullable
    private Double maxCollisionProbability;

    /** The method that was used to calculate the maximum collision probability. */
    @Nullable
    private PocMethodFacade maxCollisionProbabilityMethod;

    /**  The space environment fragmentation impact (SEFI) adjusted estimate of collision probability that Object1 and Object2 will collide. */
    @Nullable
    private Double sefiCollisionProbability;

    /** The method that was used to calculate the space environment fragmentation impact collision probability. */
    @Nullable
    private PocMethodFacade sefiCollisionProbabilityMethod;

    /** The Space environment fragmentation model used. */
    @Nullable
    private String sefiFragmentationModel;

    /** The collision probability screening threshold used to identify this conjunction. */
    @Nullable
    private Double screenPcThreshold;

    /** An array of 1 to n elements indicating the percentile(s) for which estimates of the collision probability are provided in the
     * COLLISION_PROBABILITY variable. */
    @Nullable
    private int[] collisionPercentile;

    /** ID of previous CDM issued for event identified by CONJUNCTION_ID. */
    @Nullable
    private String previousMessageId;

    /** UTC epoch of the previous CDM issued for the event identified by CONJUNCTION_ID. */
    @Nullable
    private AbsoluteDate previousMessageEpoch;

    /** Scheduled UTC epoch of the next CDM associated with the event identified by CONJUNCTION_ID. */
    @Nullable
    private AbsoluteDate nextMessageEpoch;

    /** Simple constructor.
     */
    public CdmRelativeMetadata() {
        this.comment = new ArrayList<>();
    }

    /** Check is all mandatory entries have been initialized.
    */
    public void validate() {
        checkNotNull(tca,            CdmRelativeMetadataKey.TCA);
        checkNotNull(missDistance,   CdmRelativeMetadataKey.MISS_DISTANCE);
        checkScreenVolumeConditions();
    }

    /**
     * Get the Originator's ID that uniquely identifies the conjunction to which the message refers.
     * @return the conjunction id
     */
    public Optional<String> getConjunctionId() {
        return Optional.ofNullable(conjunctionId);
    }

    /**
     * Set the Originator's ID that uniquely identifies the conjunction to which the message refers.
     * @param conjunctionId the conjunction id to be set
     */
    public void setConjunctionId(final String conjunctionId) {
        this.conjunctionId = conjunctionId;
    }

    /**
     * Get the date and time in UTC of the closest approach.
     * @return time of closest approach
     */
    public AbsoluteDate getTca() {
        return tca;
    }

    /**
     * Set the date and time in UTC of the closest approach.
     * @param tca time of closest approach to be set
     */
    public void setTca(final AbsoluteDate tca) {
        this.tca = tca;
    }

    /**
     * Get the norm of relative position vector at TCA.
     * @return the miss distance (in m)
     */
    public double getMissDistance() {
        return missDistance;
    }

    /**
     * Set the norm of relative position vector at TCA.
     * @param missDistance the miss distance to be set (in m)
     */
    public void setMissDistance(final double missDistance) {
        this.missDistance = missDistance;
    }

    /**
     * Get the norm of relative velocity vector at TCA.
     * @return the relative speed at TCA (in m/s)
     */
    public Optional<Double> getRelativeSpeed() {
        return Optional.ofNullable(relativeSpeed);
    }

    /**
     * Set the norm of relative velocity vector at TCA.
     * @param relativeSpeed the relative speed (in m/s) at TCA to be set
     */
    public void setRelativeSpeed(final double relativeSpeed) {
        this.relativeSpeed = relativeSpeed;
    }

    /**
     * Get the Object2’s velocity vector relative to Object1's at TCA in RTN frame, getX for R component,
     * getY for T component, getZ for N component.
     * @return the relative speed vector at TCA (in m/s)
     */
    public Optional<Vector3D> getRelativeVelocity() {
        if (relativeVelocityR == null || relativeVelocityT == null || relativeVelocityN == null) {
            return Optional.empty();
        }
        return Optional.of(new Vector3D(relativeVelocityR, relativeVelocityT, relativeVelocityN));
    }

    /**
     * Get the Object2’s position vector relative to Object1's at TCA in RTN frame, getX for R component,
     * getY for T component, getZ for N component.
     * @return the relative position vector at TCA (in m)
     */
    public Optional<Vector3D> getRelativePosition() {
        if (relativePositionR == null || relativePositionT == null || relativePositionN == null) {
            return Optional.empty();
        }
        return Optional.of(new Vector3D(relativePositionR, relativePositionT, relativePositionN));
    }

    /**
     * Set the R component of Object2’s position relative to Object1’s in RTN frame.
     * @param relativePositionR the R component (in m) of Object2’s position relative to Object1’s
     */
    public void setRelativePositionR(final double relativePositionR) {
        this.relativePositionR = relativePositionR;
    }

    /**
     * Set the T component of Object2’s position relative to Object1’s in RTN frame.
     * @param relativePositionT the T component (in m) of Object2’s position relative to Object1’s
     */
    public void setRelativePositionT(final double relativePositionT) {
        this.relativePositionT = relativePositionT;
    }

    /**
     * Set the N component of Object2’s position relative to Object1’s in RTN frame.
     * @param relativePositionN the N component (in m) of Object2’s position relative to Object1’s
     */
    public void setRelativePositionN(final double relativePositionN) {
        this.relativePositionN = relativePositionN;
    }

    /**
     * Set the R component of Object2’s velocity relative to Object1’s in RTN frame.
     * @param relativeVelocityR the R component (in m/s) of Object2’s velocity relative to Object1’s
     */
    public void setRelativeVelocityR(final double relativeVelocityR) {
        this.relativeVelocityR = relativeVelocityR;
    }

    /**
     * Set the T component of Object2’s velocity relative to Object1’s in RTN frame.
     * @param relativeVelocityT the T component (in m/s) of Object2’s velocity relative to Object1’s
     */
    public void setRelativeVelocityT(final double relativeVelocityT) {
        this.relativeVelocityT = relativeVelocityT;
    }

    /**
     * Set the N component of Object2’s velocity relative to Object1’s in RTN frame.
     * @param relativeVelocityN the N component (in m/s) of Object2’s velocity relative to Object1’s
     */
    public void setRelativeVelocityN(final double relativeVelocityN) {
        this.relativeVelocityN = relativeVelocityN;
    }

    /**
     * Get the start time in UTC of the screening period for the conjunction assessment.
     * @return start time in UTC of the screening period
     */
    public Optional<AbsoluteDate> getStartScreenPeriod() {
        return Optional.ofNullable(startScreenPeriod);
    }

    /**
     * Set the start time in UTC of the screening period for the conjunction assessment.
     * @param startScreenPeriod start time in UTC of the screening period to be set
     */
    public void setStartScreenPeriod(final AbsoluteDate startScreenPeriod) {
        this.startScreenPeriod = startScreenPeriod;
    }

    /**
     * Get the stop time in UTC of the screening period for the conjunction assessment.
     * @return stop time in UTC of the screening period
     */
    public Optional<AbsoluteDate> getStopScreenPeriod() {
        return Optional.ofNullable(stopScreenPeriod);
    }

    /**
     * Set the stop time in UTC of the screening period for the conjunction assessment.
     * @param stopScreenPeriod stop time in UTC of the screening period to be set
     */
    public void setStopScreenPeriod(final AbsoluteDate stopScreenPeriod) {
        this.stopScreenPeriod = stopScreenPeriod;
    }

    /**
     * Get the name of the Object1 centered reference frame in which the screening volume data are given.
     * @return name of screen volume frame
     */
    public Optional<ScreenVolumeFrame> getScreenVolumeFrame() {
        return Optional.ofNullable(screenVolumeFrame);
    }

    /**
     * Set the name of the Object1 centered reference frame in which the screening volume data are given.
     * @param screenVolumeFrame name of screen volume frame
     */
    public void setScreenVolumeFrame(final ScreenVolumeFrame screenVolumeFrame) {
        this.screenVolumeFrame = screenVolumeFrame;
    }

    /**
     * Get the shape of the screening volume.
     * @return shape of the screening volume
     */
    public Optional<ScreenVolumeShape> getScreenVolumeShape() {
        return Optional.ofNullable(screenVolumeShape);
    }

    /**
     * Set the shape of the screening volume.
     * @param screenVolumeShape shape of the screening volume
     */
    public void setScreenVolumeShape(final ScreenVolumeShape screenVolumeShape) {
        this.screenVolumeShape = screenVolumeShape;
    }

    /**
     * Get the R or T (depending on if RTN or TVN is selected) component size of the screening volume in the corresponding frame.
     * @return first component size of the screening volume (in m)
     */
    public Optional<Double> getScreenVolumeX() {
        return Optional.ofNullable(screenVolumeX);    }

    /**
     * Set the R or T (depending on if RTN or TVN is selected) component size of the screening volume in the corresponding frame.
     * @param screenVolumeX first component size of the screening volume (in m)
     */
    public void setScreenVolumeX(final double screenVolumeX) {
        this.screenVolumeX = screenVolumeX;
    }

    /**
     * Set the R or T (depending on if RTN or TVN is selected) component size of the screening volume in the corresponding frame.
     * @param screenVolumeX first component size of the screening volume (in m)
     */
    public void setScreenVolumeX(final Double screenVolumeX) {
        this.screenVolumeX = screenVolumeX;
    }

    /**
     * Get the T or V (depending on if RTN or TVN is selected) component size of the screening volume in the corresponding frame.
     * @return second component size of the screening volume (in m)
     */
    public Optional<Double> getScreenVolumeY() {
        return Optional.ofNullable(screenVolumeY);
    }

    /**
     * Set the T or V (depending on if RTN or TVN is selected) component size of the screening volume in the corresponding frame.
     * @param screenVolumeY second component size of the screening volume (in m)
     */
    public void setScreenVolumeY(final double screenVolumeY) {
        this.screenVolumeY = screenVolumeY;
    }

    /**
     * Set the T or V (depending on if RTN or TVN is selected) component size of the screening volume in the corresponding frame.
     * @param screenVolumeY second component size of the screening volume (in m)
     */
    public void setScreenVolumeY(final Double screenVolumeY) {
        this.screenVolumeY = screenVolumeY;
    }

    /**
     * Get the N component size of the screening volume in the corresponding frame.
     * @return third component size of the screening volume (in m)
     */
    public Optional<Double> getScreenVolumeZ() {
        return Optional.ofNullable(screenVolumeZ);
    }

    /**
     * Set the N component size of the screening volume in the corresponding frame.
     * @param screenVolumeZ third component size of the screening volume (in m)
     */
    public void setScreenVolumeZ(final double screenVolumeZ) {
        this.screenVolumeZ = screenVolumeZ;
    }

    /**
     * Set the N component size of the screening volume in the corresponding frame.
     * @param screenVolumeZ third component size of the screening volume (in m)
     */
    public void setScreenVolumeZ(final Double screenVolumeZ) {
        this.screenVolumeZ = screenVolumeZ;
    }


    /**
     * Get the time in UTC when Object2 enters the screening volume.
     * @return time in UTC when Object2 enters the screening volume
     */
    public Optional<AbsoluteDate> getScreenEntryTime() {
        return Optional.ofNullable(screenEntryTime);
    }

    /**
     * Set the time in UTC when Object2 enters the screening volume.
     * @param screenEntryTime time in UTC when Object2 enters the screening volume
     */
    public void setScreenEntryTime(final AbsoluteDate screenEntryTime) {
        this.screenEntryTime = screenEntryTime;
    }

    /**
     * Get the time in UTC when Object2 exits the screening volume.
     * @return time in UTC when Object2 exits the screening volume
     */
    public Optional<AbsoluteDate> getScreenExitTime() {
        return Optional.ofNullable(screenExitTime);
    }

    /**
     * Set the time in UTC when Object2 exits the screening volume.
     * @param screenExitTime time in UTC when Object2 exits the screening volume
     */
    public void setScreenExitTime(final AbsoluteDate screenExitTime) {
        this.screenExitTime = screenExitTime;
    }

    /**
     * Get the probability (between 0.0 and 1.0) that Object1 and Object2 will collide.
     * @return probability of collision
     */
    public Optional<Double> getCollisionProbability() {
        return Optional.ofNullable(collisionProbability);
    }

    /**
     * Set the probability (between 0.0 and 1.0) that Object1 and Object2 will collide.
     * @param collisionProbability first component size of the screening volume
     */
    public void setCollisionProbability(final double collisionProbability) {
        this.collisionProbability = collisionProbability;
    }

    /**
     * Get the method that was used to calculate the collision probability.
     * @return method to calculate probability of collision
     */
    public Optional<PocMethodFacade> getCollisionProbaMethod() {
        return Optional.ofNullable(collisionProbabilityMethod);
    }

    /**
     * Set the method that was used to calculate the collision probability.
     * @param collisionProbaMethod method used to calculate probability of collision
     */
    public void setCollisionProbaMethod(final PocMethodFacade collisionProbaMethod) {
        this.collisionProbabilityMethod = collisionProbaMethod;
    }

    /** Complain if a field is null.
     * @param field field to check
     * @param key key associated with the field
     */
    public void checkNotNull(final Object field, final Enum<?> key) {
        if (field == null) {
            throw new OrekitException(OrekitMessages.UNINITIALIZED_VALUE_FOR_KEY, key.name());
        }
    }

    /** Set the Time System that: for CDM, is used for relative metadata, metadata,
     * OD parameters, state vector. In CDM all date are given in UTC.
     * @param timeSystem the time system to be set
     */
    public void setTimeSystem(final TimeSystem timeSystem) {
        this.timeSystem = timeSystem;
    }

    /** Get the Time System that: for CDM, is used for relative metadata, metadata,
     * OD parameters, state vector. In CDM all date are given in UTC.
     * @return the time system
     */
    public TimeSystem getTimeSystem() {
        return timeSystem;
    }

    /** Set comment for relative metadata.
     * @param comments to be set
     */
    public void addComment(final String comments) {
        this.comment.add(comments);
    }

    /** Get comment for relative metadata.
     * @return the time system
     */
    public List<String> getComment() {
        return comment;
    }

    /** Get the approach angle computed between Objects 1 and 2 in the RTN coordinate frame relative to object 1.
     * @return the approachAngle
     */
    public Optional<Double> getApproachAngle() {
        return Optional.ofNullable(approachAngle);
    }

    /** Set the approach angle computed between Objects 1 and 2 in the RTN coordinate frame relative to object 1.
     * @param approachAngle the approachAngle to set
     */
    public void setApproachAngle(final double approachAngle) {
        this.approachAngle = approachAngle;
    }

    /** Get the type of screening to be used.
     * @return the screenType
     */
    public Optional<ScreenType> getScreenType() {
        return Optional.ofNullable(screenType);
    }

    /** Set the type of screening to be used.
     * @param screenType the screenType to set
     */
    public void setScreenType(final ScreenType screenType) {
        this.screenType = screenType;
    }

    /** Get max collision probability.
     * @return the max collision probability
     */
    public Optional<Double> getMaxCollisionProbability() {
        return Optional.ofNullable(maxCollisionProbability);
    }

    /** Set max collision probability.
     * @param maxCollisionProbability the max collision probability to set
     */
    public void setMaxCollisionProbability(final double maxCollisionProbability) {
        this.maxCollisionProbability = maxCollisionProbability;
    }

    /** Get max collision probability method.
     * @return the max collision probability method
     */
    public Optional<PocMethodFacade> getMaxCollisionProbabilityMethod() {
        return Optional.ofNullable(maxCollisionProbabilityMethod);
    }

    /** Set max collision probability method.
     * @param pocMethodFacade the max collision probability method to set
     */
    public void setMaxCollisionProbabilityMethod(final PocMethodFacade pocMethodFacade) {
        this.maxCollisionProbabilityMethod = pocMethodFacade;
    }

    /** Get the Space Environment Fragmentation Impact probability.
     * @return the Space Environment Fragmentation Impact probability
     */
    public Optional<Double> getSefiCollisionProbability() {
        return Optional.ofNullable(sefiCollisionProbability);
    }

    /** Set the Space Environment Fragmentation Impact probability.
     * @param sefiCollisionProbability the Space Environment Fragmentation Impact probability to set
     */
    public void setSefiCollisionProbability(final double sefiCollisionProbability) {
        this.sefiCollisionProbability = sefiCollisionProbability;
    }

    /** Get the Space Environment Fragmentation Impact probability method.
     * @return the Space Environment Fragmentation Impact probability method
     */
    public Optional<PocMethodFacade> getSefiCollisionProbabilityMethod() {
        return Optional.ofNullable(sefiCollisionProbabilityMethod);
    }

    /** Set the Space Environment Fragmentation Impact probability method.
     * @param pocMethodFacade the Space Environment Fragmentation Impact probability method to set
     */
    public void setSefiCollisionProbabilityMethod(final PocMethodFacade pocMethodFacade) {
        this.sefiCollisionProbabilityMethod = pocMethodFacade;
    }

    /** Get the Space Environment Fragmentation Impact fragmentation model.
     * @return the Space Environment Fragmentation Impact fragmentation model
     */
    public Optional<String> getSefiFragmentationModel() {
        return Optional.ofNullable(sefiFragmentationModel);
    }

    /** Set the Space Environment Fragmentation Impact fragmentation model.
     * @param sefiFragmentationModel the Space Environment Fragmentation Impact fragmentation model to set
     */
    public void setSefiFragmentationModel(final String sefiFragmentationModel) {
        this.sefiFragmentationModel = sefiFragmentationModel;
    }

    /** Get the Mahalanobis Distance. The length of the relative position vector, normalized to one-sigma dispersions of the combined error covariance
     * in the direction of the relative position vector.
     * @return the mahalanobisDistance
     */
    public Optional<Double> getMahalanobisDistance() {
        return Optional.ofNullable(mahalanobisDistance);
    }

    /** Set the Mahalanobis Distance. The length of the relative position vector, normalized to one-sigma dispersions of the combined error covariance
     * in the direction of the relative position vector.
     * @param mahalanobisDistance the mahalanobisDistance to set
     */
    public void setMahalanobisDistance(final double mahalanobisDistance) {
        this.mahalanobisDistance = mahalanobisDistance;
    }

    /** Get the screen volume radius.
     * @return the screen volume radius
     */
    public Optional<Double> getScreenVolumeRadius() {
        return Optional.ofNullable(screenVolumeRadius);
    }

    /** set the screen volume radius.
     * @param screenVolumeRadius the screen volume radius to set
     */
    public void setScreenVolumeRadius(final double screenVolumeRadius) {
        this.screenVolumeRadius = screenVolumeRadius;
    }

    /** Get the collision probability screening threshold used to identify this conjunction.
    * @return the screenPcThreshold
    */
    public Optional<Double> getScreenPcThreshold() {
        return Optional.ofNullable(screenPcThreshold);
    }

    /** Set the collision probability screening threshold used to identify this conjunction.
    * @param screenPcThreshold the screenPcThreshold to set
    */
    public void setScreenPcThreshold(final double screenPcThreshold) {
        this.screenPcThreshold = screenPcThreshold;
    }

    /**
     * Check screen volume conditions.
     * <p>
     * The method verifies that all keys are present.
     * Otherwise, an exception is thrown.
     * </p>
     */
    public void checkScreenVolumeConditions() {

        if (this.getScreenType().isPresent() &&  this.getScreenType().get() == ScreenType.SHAPE) {

            getScreenEntryTime().
                orElseThrow(() -> new OrekitException(OrekitMessages.CCSDS_MISSING_KEYWORD,
                                                      CdmRelativeMetadataKey.SCREEN_ENTRY_TIME));

            getScreenExitTime().
                orElseThrow(() -> new OrekitException(OrekitMessages.CCSDS_MISSING_KEYWORD,
                                                      CdmRelativeMetadataKey.SCREEN_EXIT_TIME));

            getScreenVolumeShape().
                orElseThrow(() -> new OrekitException(OrekitMessages.CCSDS_MISSING_KEYWORD,
                                                      CdmRelativeMetadataKey.SCREEN_VOLUME_SHAPE));

            if (this.getScreenVolumeShape().isPresent() && this.getScreenVolumeShape().get() == ScreenVolumeShape.SPHERE) {

                getScreenVolumeRadius().
                    orElseThrow(() ->  new OrekitException(OrekitMessages.CCSDS_MISSING_KEYWORD,
                                                           CdmRelativeMetadataKey.SCREEN_VOLUME_RADIUS));

            } else if (this.getScreenVolumeShape().isPresent() && (this.getScreenVolumeShape().get() == ScreenVolumeShape.ELLIPSOID ||
                    this.getScreenVolumeShape().get() == ScreenVolumeShape.BOX)) {

                getScreenVolumeFrame().
                    orElseThrow(() -> new OrekitException(OrekitMessages.CCSDS_MISSING_KEYWORD,
                                                          CdmRelativeMetadataKey.SCREEN_VOLUME_FRAME));
                getScreenVolumeX().
                    orElseThrow(() -> new OrekitException(OrekitMessages.CCSDS_MISSING_KEYWORD,
                                                          CdmRelativeMetadataKey.SCREEN_VOLUME_X));
                getScreenVolumeY().
                    orElseThrow(() -> new OrekitException(OrekitMessages.CCSDS_MISSING_KEYWORD,
                                                          CdmRelativeMetadataKey.SCREEN_VOLUME_Y));
                getScreenVolumeZ().
                    orElseThrow(() -> new OrekitException(OrekitMessages.CCSDS_MISSING_KEYWORD,
                                                          CdmRelativeMetadataKey.SCREEN_VOLUME_Z));
            }

        } else if (this.getScreenType().isPresent() &&
                   (this.getScreenType().get() == ScreenType.PC || this.getScreenType().get() == ScreenType.PC_MAX)) {

            if (this.getScreenPcThreshold().isEmpty()) {
                throw new OrekitException(OrekitMessages.CCSDS_MISSING_KEYWORD, CdmRelativeMetadataKey.SCREEN_PC_THRESHOLD);
            }
        }

    }

    /** Get the array of 1 to n elements indicating the percentile(s) for which estimates of the collision probability are provided in the
     * COLLISION_PROBABILITY variable.
     * @return the collisionPercentile
     */
    public Optional<int[]> getCollisionPercentile() {
        return collisionPercentile == null ? Optional.empty() : Optional.ofNullable(collisionPercentile.clone());
    }

    /** Set the array of 1 to n elements indicating the percentile(s) for which estimates of the collision probability are provided in the
     * COLLISION_PROBABILITY variable.
     * @param collisionPercentile the collisionPercentile to set
     */
    public void setCollisionPercentile(final int[] collisionPercentile) {
        this.collisionPercentile = collisionPercentile == null ? null : collisionPercentile.clone();
    }

    /** Get the ID of previous CDM issued for event identified by CONJUNCTION_ID.
     * @return the previousMessageId
     */
    public Optional<String> getPreviousMessageId() {
        return Optional.ofNullable(previousMessageId);
    }

    /** Set the ID of previous CDM issued for event identified by CONJUNCTION_ID.
     * @param previousMessageId the previousMessageId to set
     */
    public void setPreviousMessageId(final String previousMessageId) {
        this.previousMessageId = previousMessageId;
    }

    /** Get the UTC epoch of the previous CDM issued for the event identified by CONJUNCTION_ID.
     * @return the previousMessageEpoch
     */
    public Optional<AbsoluteDate> getPreviousMessageEpoch() {
        return Optional.ofNullable(previousMessageEpoch);
    }

    /** Set the UTC epoch of the previous CDM issued for the event identified by CONJUNCTION_ID.
     * @param previousMessageEpoch the previousMessageEpoch to set
     */
    public void setPreviousMessageEpoch(final AbsoluteDate previousMessageEpoch) {
        this.previousMessageEpoch = previousMessageEpoch;
    }

    /** Get Scheduled UTC epoch of the next CDM associated with the event identified by CONJUNCTION_ID.
     * @return the nextMessageEpoch
     */
    public Optional<AbsoluteDate> getNextMessageEpoch() {
        return Optional.ofNullable(nextMessageEpoch);
    }

    /** Set Scheduled UTC epoch of the next CDM associated with the event identified by CONJUNCTION_ID.
     * @param nextMessageEpoch the nextMessageEpoch to set
     */
    public void setNextMessageEpoch(final AbsoluteDate nextMessageEpoch) {
        this.nextMessageEpoch = nextMessageEpoch;
    }
}