SinexParseInfo.java

/* Copyright 2002-2025 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.sinex;

import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.gnss.GnssSignal;
import org.orekit.gnss.SatInSystem;
import org.orekit.models.earth.displacement.PsdCorrection;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeScales;
import org.orekit.utils.TimeSpanMap;

import java.util.HashMap;
import java.util.Map;

/** Parse information for Solution INdependent EXchange (SINEX) files.
 * @author Bryan Cazabonne
 * @author Luc Maisonobe
 * @since 13.0
 */
public class SinexParseInfo extends ParseInfo<Sinex> {

    /** Satellites antennas. */
    private final Map<SatInSystem, Map<GnssSignal, Vector3D>> satellitesPhaseCenters;

    /** Stations phase centers. */
    private final Map<AntennaKey, Map<GnssSignal, Vector3D>> stationsPhaseCenters;

    /** Station data. */
    private final Map<String, Station> stations;

    /** Earth Orientation Parameters data. */
    private final Map<AbsoluteDate, SinexEopEntry> eop;

    /** Station position X coordinate. */
    private double px;

    /** Station position Y coordinate. */
    private double py;

    /** Station position Z coordinate. */
    private double pz;

    /** Station velocity X coordinate. */
    private double vx;

    /** Station velocity Y coordinate. */
    private double vy;

    /** Station velocity Z coordinate. */
    private double vz;

    /** Correction axis. */
    private PsdCorrection.Axis axis;

    /** Correction time evolution. */
    private PsdCorrection.TimeEvolution evolution;

    /** Correction amplitude. */
    private double amplitude;

    /** Correction relaxation time. */
    private double relaxationTime;

    /** Phase centers. */
    private final Map<GnssSignal, Vector3D> phaseCenters;

    /** Simple constructor.
     * @param timeScales time scales
     */
    SinexParseInfo(final TimeScales timeScales) {
        super(timeScales);
        this.satellitesPhaseCenters = new HashMap<>();
        this.stationsPhaseCenters   = new HashMap<>();
        this.stations               = new HashMap<>();
        this.eop                    = new HashMap<>();
        this.phaseCenters           = new HashMap<>();
    }

    /** {@inheritDoc} */
    @Override
    void newSource(final String name) {
        super.newSource(name);
        resetPosition();
        resetVelocity();
        resetPsdCorrection();
    }

    /** Add satellite phase center.
     * @param satInSystem satellite id
     * @param signal signal
     * @param phaseCenter phase center
     */
    void addSatellitePhaseCenter(final SatInSystem satInSystem, final GnssSignal signal, final Vector3D phaseCenter) {
        satellitesPhaseCenters.
            computeIfAbsent(satInSystem, s -> new HashMap<>()).
            put(signal, phaseCenter);
    }

    /** Add station phase center.
     * @param key antenna key
     * @param phaseCenter phase center
     * @param signals signals to use in order
     */
    void addStationPhaseCenter(final AntennaKey key, final Vector3D phaseCenter, final GnssSignal[] signals) {
        phaseCenters.put(signals[phaseCenters.size()], phaseCenter);
        if (phaseCenters.size() == signals.length) {
            // we have parsed all expected signals
            stationsPhaseCenters.computeIfAbsent(key, k -> new HashMap<>()).putAll(phaseCenters);
            phaseCenters.clear();
        }
    }

    /** Add station.
     * @param station station to add
     */
    void addStation(final Station station) {
        stations.putIfAbsent(station.getSiteCode(), station);
    }

    /** Get station from current line.
     * @param index index of station in current line
     * @return station
     */
    Station getCurrentLineStation(final int index) {
        return stations.get(parseString(index, 4));
    }

    /** Get start date from current line.
     * @return start date
     */
    AbsoluteDate getCurrentLineStartDate() {
        return stringEpochToAbsoluteDate(parseString(16, 12), true);
    }

    /** Get end date from current line.
     * @return end date
     */
    AbsoluteDate getCurrentLineEndDate() {
        return stringEpochToAbsoluteDate(parseString(29, 12), false);
    }

    /** Set station position X coordinate.
     * @param x station position X coordinate
     * @param station station
     * @param epoch   coordinates epoch
     */
    void setPx(final double x, final Station station, final AbsoluteDate epoch) {
        this.px = x;
        finalizePositionIfComplete(station, epoch);
    }

    /** Set station position Y coordinate.
     * @param y station position Y coordinate
     * @param station station
     * @param epoch   coordinates epoch
     */
    void setPy(final double y, final Station station, final AbsoluteDate epoch) {
        this.py = y;
        finalizePositionIfComplete(station, epoch);
    }

    /** Set station position Z coordinate.
     * @param z station position Z coordinate
     * @param station station
     * @param epoch   coordinates epoch
     */
    void setPz(final double z, final Station station, final AbsoluteDate epoch) {
        this.pz = z;
        finalizePositionIfComplete(station, epoch);
    }

    /** Finalize station position if complete.
     * @param station station
     * @param epoch   coordinates epoch
     */
    private void finalizePositionIfComplete(final Station station, final AbsoluteDate epoch) {
        if (!Double.isNaN(px + py + pz)) {
            // all coordinates are available, position is complete
            station.setEpoch(epoch);
            station.setPosition(new Vector3D(px, py, pz));
            resetPosition();
        }
    }

    /** Reset position.
     */
    void resetPosition() {
        px = Double.NaN;
        py = Double.NaN;
        pz = Double.NaN;
    }

    /** Set station velocity X coordinate.
     * @param x station velocity X coordinate
     * @param station station
     */
    void setVx(final double x, final Station station) {
        this.vx = x;
        finalizeVelocityIfComplete(station);
    }

    /** Set station velocity Y coordinate.
     * @param y station velocity Y coordinate
     * @param station station
     */
    void setVy(final double y, final Station station) {
        this.vy = y;
        finalizeVelocityIfComplete(station);
    }

    /** Set station velocity Z coordinate.
     * @param z station velocity Z coordinate
     * @param station station
     */
    void setVz(final double z, final Station station) {
        this.vz = z;
        finalizeVelocityIfComplete(station);
    }

    /** Finalize station velocity if complete.
     * @param station station
     */
    private void finalizeVelocityIfComplete(final Station station) {
        if (!Double.isNaN(vx + vy + vz)) {
            // all coordinates are available, velocity is complete
            station.setVelocity(new Vector3D(vx, vy, vz));
            resetVelocity();
        }
    }

    /** Reset velocity.
     */
    void resetVelocity() {
        vx = Double.NaN;
        vy = Double.NaN;
        vz = Double.NaN;
    }

    /** Set correction axis.
     * @param axis correction axis
     */
    void setAxis(final PsdCorrection.Axis axis) {
        this.axis = axis;
    }

    /** Set correction time evolution.
     * @param evolution correction time evolution
     */
    void setEvolution(final PsdCorrection.TimeEvolution evolution) {
        this.evolution = evolution;
    }

    /** Set correction amplitude.
     * @param correctionAmplitude correction amplitude
     * @param station station
     * @param epoch   coordinates epoch
     */
    void setAmplitude(final double correctionAmplitude, final Station station, final AbsoluteDate epoch) {
        this.amplitude = correctionAmplitude;
        finalizePsdCorrectionIfComplete(station, epoch);
    }

    /** Set correction relaxation time.
     * @param correctionRelaxationTime correction relaxation time
     * @param station station
     * @param epoch   coordinates epoch
     */
    void setRelaxationTime(final double correctionRelaxationTime,
                           final Station station, final AbsoluteDate epoch) {
        this.relaxationTime = correctionRelaxationTime;
        finalizePsdCorrectionIfComplete(station, epoch);
    }

    /** Finalize a Post-Seismic Deformation correction model if complete.
     * @param station station
     * @param epoch   coordinates epoch
     */
    private void finalizePsdCorrectionIfComplete(final Station station, final AbsoluteDate epoch) {
        if (!Double.isNaN(amplitude + relaxationTime)) {
            // both amplitude and relaxation time are available, correction is complete
            final PsdCorrection correction = new PsdCorrection(axis, evolution, epoch, amplitude, relaxationTime);
            station.addPsdCorrectionValidAfter(correction, epoch);
            resetPsdCorrection();
        }
    }

    /** Reset Post-Seismic Deformation correction model.
     */
    private void resetPsdCorrection() {
        axis           = null;
        evolution      = null;
        amplitude      = Double.NaN;
        relaxationTime = Double.NaN;
    }

    /** Create EOP entry.
     * @param date EOP date
     * @return EOP entry at date, creating it if needed
     */
    SinexEopEntry createEOPEntry(final AbsoluteDate date) {
        return eop.computeIfAbsent(date, SinexEopEntry::new);
    }

    /** {@inheritDoc} */
    @Override
    protected Sinex build() {

        // set up phase centers for stations
        for (final Station station : stations.values()) {

            // time span map we need to populate
            final TimeSpanMap<Map<GnssSignal, Vector3D>> phaseCentersMap = station.getPhaseCentersMap();

            if (station.getAntennaKeyTimeSpanMap().getSpansNumber() > 1) {
                for (TimeSpanMap.Span<AntennaKey> keySpan = station.getAntennaKeyTimeSpanMap().getFirstNonNullSpan();
                     keySpan != null; keySpan = keySpan.next()) {

                    // get the existing map for this span
                    Map<GnssSignal, Vector3D> centers =
                        phaseCentersMap.get(AbsoluteDate.createMedian(keySpan.getStart(), keySpan.getEnd()));
                    if (centers == null) {
                        // this is the first time we process this time span
                        centers = new HashMap<>();
                        phaseCentersMap.addValidBetween(centers, keySpan.getStart(), keySpan.getEnd());
                    }

                    if (keySpan.getData() != null) {

                        // try to identify the closest match for antenna
                        AntennaKey closestKey = null;
                        for (final AntennaKey candidate : keySpan.getData().matchingCandidates()) {
                            if (stationsPhaseCenters.containsKey(candidate)) {
                                closestKey = candidate;
                                break;
                            }
                        }
                        if (closestKey == null) {
                            throw new OrekitException(OrekitMessages.UNKNOWN_GNSS_ANTENNA,
                                                      keySpan.getData().getName(),
                                                      keySpan.getData().getRadomeCode(),
                                                      keySpan.getData().getSerialNumber());
                        }

                        // add the phase centers for the closest key
                        centers.putAll(stationsPhaseCenters.get(closestKey));

                    }

                }
            }

        }

        return new Sinex(getTimeScales(), getCreationDate(), getStartDate(), getEndDate(),
                         satellitesPhaseCenters, stations, eop);
    }

}