AntexLoader.java

  1. /* Copyright 2002-2018 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.gnss.antenna;

  18. import java.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.util.ArrayList;
  23. import java.util.Collections;
  24. import java.util.HashMap;
  25. import java.util.List;
  26. import java.util.Map;
  27. import java.util.Optional;

  28. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  29. import org.hipparchus.util.FastMath;
  30. import org.orekit.data.DataLoader;
  31. import org.orekit.data.DataProvidersManager;
  32. import org.orekit.errors.OrekitException;
  33. import org.orekit.errors.OrekitIllegalArgumentException;
  34. import org.orekit.errors.OrekitMessages;
  35. import org.orekit.gnss.Frequency;
  36. import org.orekit.gnss.SatelliteSystem;
  37. import org.orekit.time.AbsoluteDate;
  38. import org.orekit.time.TimeScalesFactory;
  39. import org.orekit.utils.TimeSpanMap;

  40. /**
  41.  * Factory for GNSS antennas (both receiver and satellite).
  42.  * <p>
  43.  * The factory creates antennas by parsing an
  44.  * <a href="ftp://www.igs.org/pub/station/general/antex14.txt">ANTEX</a> file.
  45.  * </p>
  46.  *
  47.  * @author Luc Maisonobe
  48.  * @since 9.2
  49.  */
  50. public class AntexLoader {

  51.     /** Default supported files name pattern for antex files. */
  52.     public static final String DEFAULT_ANTEX_SUPPORTED_NAMES = "^\\w{5}(?:_\\d{4})?\\.atx$";

  53.     /** Satellites antennas. */
  54.     private final List<TimeSpanMap<SatelliteAntenna>> satellitesAntennas;

  55.     /** Receivers antennas. */
  56.     private final List<ReceiverAntenna> receiversAntennas;

  57.     /** Simple constructor.
  58.      * @param supportedNames regular expression for supported files names
  59.      * @exception OrekitException if no antex file can be read
  60.      */
  61.     public AntexLoader(final String supportedNames)
  62.         throws OrekitException {
  63.         satellitesAntennas = new ArrayList<>();
  64.         receiversAntennas  = new ArrayList<>();
  65.         DataProvidersManager.getInstance().feed(supportedNames, new Parser());
  66.     }

  67.     /** Add a satellite antenna.
  68.      * @param antenna satellite antenna to add
  69.      */
  70.     private void addSatelliteAntenna(final SatelliteAntenna antenna) {
  71.         try {
  72.             final TimeSpanMap<SatelliteAntenna> existing =
  73.                             findSatelliteAntenna(antenna.getSatelliteSystem(), antenna.getPrnNumber());
  74.             // this is an update for a satellite antenna, with new time span
  75.             existing.addValidAfter(antenna, antenna.getValidFrom());
  76.         } catch (OrekitException oe) {
  77.             // this is a new satellite antenna
  78.             satellitesAntennas.add(new TimeSpanMap<>(antenna));
  79.         }
  80.     }

  81.     /** Get parsed satellites antennas.
  82.      * @return unmodifiable view of parsed satellites antennas
  83.      */
  84.     public List<TimeSpanMap<SatelliteAntenna>> getSatellitesAntennas() {
  85.         return Collections.unmodifiableList(satellitesAntennas);
  86.     }

  87.     /** Find the time map for a specific satellite antenna.
  88.      * @param satelliteSystem satellite system
  89.      * @param prnNumber number within the satellite system
  90.      * @return time map for the antenna
  91.      * @exception OrekitException if satellite cannot be found
  92.      */
  93.     public TimeSpanMap<SatelliteAntenna> findSatelliteAntenna(final SatelliteSystem satelliteSystem,
  94.                                                               final int prnNumber)
  95.         throws OrekitException {
  96.         final Optional<TimeSpanMap<SatelliteAntenna>> existing =
  97.                         satellitesAntennas.
  98.                         stream().
  99.                         filter(m -> {
  100.                             final SatelliteAntenna first = m.getTransitions().first().getBefore();
  101.                             return first.getSatelliteSystem() == satelliteSystem &&
  102.                                    first.getPrnNumber() == prnNumber;
  103.                         }).findFirst();
  104.         if (existing.isPresent()) {
  105.             return existing.get();
  106.         } else {
  107.             throw new OrekitException(OrekitMessages.CANNOT_FIND_SATELLITE_IN_SYSTEM,
  108.                                       prnNumber, satelliteSystem);
  109.         }
  110.     }

  111.     /** Add a receiver antenna.
  112.      * @param antenna receiver antenna to add
  113.      */
  114.     private void addReceiverAntenna(final ReceiverAntenna antenna) {
  115.         receiversAntennas.add(antenna);
  116.     }

  117.     /** Get parsed receivers antennas.
  118.      * @return unmodifiable view of parsed receivers antennas
  119.      */
  120.     public List<ReceiverAntenna> getReceiversAntennas() {
  121.         return Collections.unmodifiableList(receiversAntennas);
  122.     }

  123.     /** Parser for antex files.
  124.      * @see <a href="ftp://www.igs.org/pub/station/general/antex14.txt">ANTEX: The Antenna Exchange Format, Version 1.4</a>
  125.      */
  126.     private class Parser implements DataLoader {

  127.         /** Index of label in data lines. */
  128.         private static final int LABEL_START = 60;

  129.         /** Supported format version. */
  130.         private static final double FORMAT_VERSION = 1.4;

  131.         /** Phase center eccentricities conversion factor. */
  132.         private static final double MM_TO_M = 0.001;

  133.         /** {@inheritDoc} */
  134.         @Override
  135.         public boolean stillAcceptsData() {
  136.             // we load all antex files we can find
  137.             return true;
  138.         }

  139.         /** {@inheritDoc} */
  140.         @Override
  141.         public void loadData(final InputStream input, final String name)
  142.             throws IOException, OrekitException {

  143.             try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"))) {

  144.                 // placeholders for parsed data
  145.                 int                              lineNumber           = 0;
  146.                 SatelliteSystem                  satelliteSystem      = null;
  147.                 String                           antennaType          = null;
  148.                 SatelliteAntennaCode             satelliteAntennaCode = null;
  149.                 String                           serialNumber         = null;
  150.                 int                              prnNumber            = -1;
  151.                 int                              satelliteCode        = -1;
  152.                 String                           cosparID             = null;
  153.                 AbsoluteDate                     validFrom            = AbsoluteDate.PAST_INFINITY;
  154.                 AbsoluteDate                     validUntil           = AbsoluteDate.FUTURE_INFINITY;
  155.                 String                           sinexCode            = null;
  156.                 double                           azimuthStep          = Double.NaN;
  157.                 double                           polarStart           = Double.NaN;
  158.                 double                           polarStop            = Double.NaN;
  159.                 double                           polarStep            = Double.NaN;
  160.                 double[]                         grid1D               = null;
  161.                 double[][]                       grid2D               = null;
  162.                 Vector3D                         eccentricities       = Vector3D.ZERO;
  163.                 int                              nbFrequencies        = -1;
  164.                 Frequency                        frequency            = null;
  165.                 Map<Frequency, FrequencyPattern> patterns             = null;
  166.                 boolean                          inFrequency          = false;
  167.                 boolean                          inRMS                = false;

  168.                 for (String line = reader.readLine(); line != null; line = reader.readLine()) {
  169.                     ++lineNumber;
  170.                     switch(line.substring(LABEL_START).trim()) {
  171.                         case "COMMENT" :
  172.                             // nothing to do
  173.                             break;
  174.                         case "ANTEX VERSION / SYST" :
  175.                             if (FastMath.abs(parseDouble(line, 0, 8) - FORMAT_VERSION) > 0.001) {
  176.                                 throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT, name);
  177.                             }
  178.                             // we parse the general setting for satellite system to check for format errors,
  179.                             // but otherwise ignore it
  180.                             SatelliteSystem.parseSatelliteSystem(parseString(line, 20, 1));
  181.                             break;
  182.                         case "PCV TYPE / REFANT" :
  183.                             // TODO
  184.                             break;
  185.                         case "END OF HEADER" :
  186.                             // nothing to do
  187.                             break;
  188.                         case "START OF ANTENNA" :
  189.                             // reset antenna data
  190.                             satelliteSystem      = null;
  191.                             antennaType          = null;
  192.                             satelliteAntennaCode = null;
  193.                             serialNumber         = null;
  194.                             prnNumber            = -1;
  195.                             satelliteCode        = -1;
  196.                             cosparID             = null;
  197.                             validFrom            = AbsoluteDate.PAST_INFINITY;
  198.                             validUntil           = AbsoluteDate.FUTURE_INFINITY;
  199.                             sinexCode            = null;
  200.                             azimuthStep          = Double.NaN;
  201.                             polarStart           = Double.NaN;
  202.                             polarStop            = Double.NaN;
  203.                             polarStep            = Double.NaN;
  204.                             grid1D               = null;
  205.                             grid2D               = null;
  206.                             eccentricities       = Vector3D.ZERO;
  207.                             nbFrequencies        = -1;
  208.                             frequency            = null;
  209.                             patterns             = null;
  210.                             inFrequency          = false;
  211.                             inRMS                = false;
  212.                             break;
  213.                         case "TYPE / SERIAL NO" :
  214.                             antennaType = parseString(line, 0, 20);
  215.                             try {
  216.                                 satelliteAntennaCode = SatelliteAntennaCode.parseSatelliteAntennaCode(antennaType);
  217.                                 final String satField = parseString(line, 20, 20);
  218.                                 if (satField.length() > 0) {
  219.                                     satelliteSystem = SatelliteSystem.parseSatelliteSystem(satField);
  220.                                     final int n = parseInt(satField, 1, 19);
  221.                                     switch (satelliteSystem) {
  222.                                         case GPS:
  223.                                         case GLONASS:
  224.                                         case GALILEO:
  225.                                         case BEIDOU:
  226.                                         case IRNSS:
  227.                                             prnNumber = n;
  228.                                             break;
  229.                                         case QZSS:
  230.                                             prnNumber = n + 192;
  231.                                             break;
  232.                                         case SBAS:
  233.                                             prnNumber = n + 100;
  234.                                             break;
  235.                                         default:
  236.                                             // MIXED satellite system is not allowed here
  237.                                             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  238.                                                                       lineNumber, name, line);
  239.                                     }
  240.                                     satelliteCode = parseInt(line, 41, 9); // we drop the system type
  241.                                     cosparID      = parseString(line, 50, 10);
  242.                                 }
  243.                             } catch (OrekitIllegalArgumentException oiae) {
  244.                                 // this is a receiver antenna, not a satellite antenna
  245.                                 serialNumber = parseString(line, 20, 20);
  246.                             }
  247.                             break;
  248.                         case "METH / BY / # / DATE" :
  249.                             // ignoreds
  250.                             break;
  251.                         case "DAZI" :
  252.                             azimuthStep = FastMath.toRadians(parseDouble(line,  2, 6));
  253.                             break;
  254.                         case "ZEN1 / ZEN2 / DZEN" :
  255.                             polarStart = FastMath.toRadians(parseDouble(line,  2, 6));
  256.                             polarStop  = FastMath.toRadians(parseDouble(line,  8, 6));
  257.                             polarStep  = FastMath.toRadians(parseDouble(line, 14, 6));
  258.                             break;
  259.                         case "# OF FREQUENCIES" :
  260.                             nbFrequencies = parseInt(line, 0, 6);
  261.                             patterns      = new HashMap<>(nbFrequencies);
  262.                             break;
  263.                         case "VALID FROM" :
  264.                             validFrom = new AbsoluteDate(parseInt(line,     0,  6),
  265.                                                          parseInt(line,     6,  6),
  266.                                                          parseInt(line,    12,  6),
  267.                                                          parseInt(line,    18,  6),
  268.                                                          parseInt(line,    24,  6),
  269.                                                          parseDouble(line, 30, 13),
  270.                                                          TimeScalesFactory.getGPS());
  271.                             break;
  272.                         case "VALID UNTIL" :
  273.                             validUntil = new AbsoluteDate(parseInt(line,     0,  6),
  274.                                                           parseInt(line,     6,  6),
  275.                                                           parseInt(line,    12,  6),
  276.                                                           parseInt(line,    18,  6),
  277.                                                           parseInt(line,    24,  6),
  278.                                                           parseDouble(line, 30, 13),
  279.                                                           TimeScalesFactory.getGPS());
  280.                             break;
  281.                         case "SINEX CODE" :
  282.                             sinexCode = parseString(line, 0, 10);
  283.                             break;
  284.                         case "START OF FREQUENCY" :
  285.                             try {
  286.                                 frequency = Frequency.valueOf(parseString(line, 3, 3));
  287.                                 grid1D    = new double[1 + (int) FastMath.round((polarStop - polarStart) / polarStep)];
  288.                                 if (azimuthStep > 0.001) {
  289.                                     grid2D = new double[1 + (int) FastMath.round(2 * FastMath.PI / azimuthStep)][grid1D.length];
  290.                                 }
  291.                             } catch (IllegalArgumentException iae) {
  292.                                 throw new OrekitException(OrekitMessages.UNKNOWN_RINEX_FREQUENCY,
  293.                                                           parseString(line, 3, 3), name, lineNumber);
  294.                             }
  295.                             inFrequency = true;
  296.                             break;
  297.                         case "NORTH / EAST / UP" :
  298.                             if (!inRMS) {
  299.                                 eccentricities = new Vector3D(parseDouble(line,  0, 10) * MM_TO_M,
  300.                                                               parseDouble(line, 10, 10) * MM_TO_M,
  301.                                                               parseDouble(line, 20, 10) * MM_TO_M);
  302.                             }
  303.                             break;
  304.                         case "END OF FREQUENCY" : {
  305.                             final String endFrequency = parseString(line, 3, 3);
  306.                             if (!frequency.toString().equals(endFrequency)) {
  307.                                 throw new OrekitException(OrekitMessages.MISMATCHED_FREQUENCIES,
  308.                                                           name, lineNumber, frequency.toString(), endFrequency);

  309.                             }

  310.                             final PhaseCenterVariationFunction phaseCenterVariation;
  311.                             if (grid2D == null) {
  312.                                 double max = 0;
  313.                                 for (final double v : grid1D) {
  314.                                     max = FastMath.max(max, FastMath.abs(v));
  315.                                 }
  316.                                 if (max == 0.0) {
  317.                                     // there are no known variations for this pattern
  318.                                     phaseCenterVariation = (polarAngle, azimuthAngle) -> 0.0;
  319.                                 } else {
  320.                                     phaseCenterVariation = new OneDVariation(polarStart, polarStep, grid1D);
  321.                                 }
  322.                             } else {
  323.                                 phaseCenterVariation = new TwoDVariation(polarStart, polarStep, azimuthStep, grid2D);
  324.                             }
  325.                             patterns.put(frequency, new FrequencyPattern(eccentricities, phaseCenterVariation));
  326.                             frequency   = null;
  327.                             grid1D      = null;
  328.                             grid2D      = null;
  329.                             inFrequency = false;
  330.                             break;
  331.                         }
  332.                         case "START OF FREQ RMS" :
  333.                             inRMS = true;
  334.                             break;
  335.                         case "END OF FREQ RMS" :
  336.                             inRMS = false;
  337.                             break;
  338.                         case "END OF ANTENNA" :
  339.                             if (satelliteAntennaCode == null) {
  340.                                 addReceiverAntenna(new ReceiverAntenna(antennaType, sinexCode, patterns, serialNumber));
  341.                             } else {
  342.                                 addSatelliteAntenna(new SatelliteAntenna(antennaType, sinexCode, patterns,
  343.                                                                          satelliteSystem, prnNumber, satelliteCode,
  344.                                                                          cosparID, validFrom, validUntil));
  345.                             }
  346.                             break;
  347.                         default :
  348.                             if (inFrequency) {
  349.                                 final String[] fields = line.trim().split("\\s+");
  350.                                 if (fields.length != grid1D.length + 1) {
  351.                                     throw new OrekitException(OrekitMessages.WRONG_COLUMNS_NUMBER,
  352.                                                               name, lineNumber, grid1D.length + 1, fields.length);
  353.                                 }
  354.                                 if ("NOAZI".equals(fields[0])) {
  355.                                     // azimuth-independent phase
  356.                                     for (int i = 0; i < grid1D.length; ++i) {
  357.                                         grid1D[i] = Double.parseDouble(fields[i + 1]) * MM_TO_M;
  358.                                     }

  359.                                 } else {
  360.                                     // azimuth-dependent phase
  361.                                     final int k = (int) FastMath.round(FastMath.toRadians(Double.parseDouble(fields[0])) / azimuthStep);
  362.                                     for (int i = 0; i < grid2D[k].length; ++i) {
  363.                                         grid2D[k][i] = Double.parseDouble(fields[i + 1]) * MM_TO_M;
  364.                                     }
  365.                                 }
  366.                             } else if (inRMS) {
  367.                                 // RMS section is ignored (furthermore there are no RMS sections in both igs08.atx and igs14.atx)
  368.                             } else {
  369.                                 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  370.                                                           lineNumber, name, line);
  371.                             }
  372.                     }
  373.                 }

  374.             }
  375.         }

  376.         /** Extract a string from a line.
  377.          * @param line to parse
  378.          * @param start start index of the string
  379.          * @param length length of the string
  380.          * @return parsed string
  381.          */
  382.         private String parseString(final String line, final int start, final int length) {
  383.             return line.substring(start, FastMath.min(line.length(), start + length)).trim();
  384.         }

  385.         /** Extract an integer from a line.
  386.          * @param line to parse
  387.          * @param start start index of the integer
  388.          * @param length length of the integer
  389.          * @return parsed integer
  390.          */
  391.         private int parseInt(final String line, final int start, final int length) {
  392.             return Integer.parseInt(parseString(line, start, length));
  393.         }

  394.         /** Extract a double from a line.
  395.          * @param line to parse
  396.          * @param start start index of the real
  397.          * @param length length of the real
  398.          * @return parsed real
  399.          */
  400.         private double parseDouble(final String line, final int start, final int length) {
  401.             return Double.parseDouble(parseString(line, start, length));
  402.         }

  403.     }

  404. }