AstronomicalAmplitudeReader.java

/* Copyright 2002-2023 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.forces.gravity.potential;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.hipparchus.util.FastMath;
import org.orekit.data.DataLoader;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;

/**
 * Parser for tides astronomical amplitude H<sub>f</sub>.
 * @author Luc Maisonobe
 * @since 6.1
 */
public class AstronomicalAmplitudeReader implements DataLoader {

    /** Pattern for optional fields (either nothing or non-space characters). */
    private static final String  OPTIONAL_FIELD_PATTERN = "\\S*";

    /** Pattern for fields with Doodson number. */
    private static final String  DOODSON_TYPE_PATTERN = "\\p{Digit}{2,3}[.,]\\p{Digit}{3}";

    /** Pattern for fields with real type. */
    private static final String  REAL_TYPE_PATTERN =
            "[-+]?(?:(?:\\p{Digit}+(?:\\.\\p{Digit}*)?)|(?:\\.\\p{Digit}+))(?:[eE][-+]?\\p{Digit}+)?";

    /** Pattern for regular data. */
    private static final Pattern PATTERN = Pattern.compile("[.,]");

    /** Regular expression for supported files names. */
    private final String supportedNames;

    /** Pattern for regular data lines. */
    private final Pattern regularLinePattern;

    /** Doodson number column. */
    private final int columnDoodson;

    /** H<sub>f</sub> column. */
    private final int columnHf;

    /** Scaling factor for astronomical amplitude. */
    private final double scale;

    /** Amplitudes map. */
    private final Map<Integer, Double> amplitudesMap;

    /** Simple constructor.
     * @param supportedNames regular expression for supported files names
     * @param columns number of columns
     * @param columnDoodson Doodson number column (counting from 1)
     * @param columnHf H<sub>f</sub> column (counting from 1)
     * @param scale scaling factor for astronomical amplitude
     */
    public AstronomicalAmplitudeReader(final String supportedNames, final int columns,
                                       final int columnDoodson, final int columnHf,
                                       final double scale) {

        // build the pattern for the regular data lines
        final StringBuilder builder = new StringBuilder("^\\p{Space}*");
        for (int i = 1; i <= columns; ++i) {
            builder.append("(");
            if (i == columnDoodson) {
                builder.append(DOODSON_TYPE_PATTERN);
            } else if (i == columnHf) {
                builder.append(REAL_TYPE_PATTERN);
            } else {
                builder.append(OPTIONAL_FIELD_PATTERN);
            }
            builder.append(")");
            builder.append(i < FastMath.max(columnDoodson, columnHf) ? "\\p{Space}+" : "\\p{Space}*");
        }
        builder.append('$');
        this.regularLinePattern = Pattern.compile(builder.toString());

        this.supportedNames = supportedNames;
        this.columnDoodson  = columnDoodson;
        this.columnHf       = columnHf;
        this.scale          = scale;

        this.amplitudesMap  = new HashMap<>();

    }

    /** Get the regular expression for supported files names.
     * @return regular expression for supported files names
     */
    public String getSupportedNames() {
        return supportedNames;
    }

    /** {@inheritDoc} */
    @Override
    public boolean stillAcceptsData() {
        return amplitudesMap.isEmpty();
    }

    /** {@inheritDoc} */
    @Override
    public void loadData(final InputStream input, final String name)
        throws IOException {

        int lineNumber = 0;
        String line = null;
        // parse the file
        try (BufferedReader r = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {

            for (line = r.readLine(); line != null; line = r.readLine()) {
                ++lineNumber;

                // replace unicode minus sign ('−') by regular hyphen ('-') for parsing
                // such unicode characters occur in tables that are copy-pasted from PDF files
                line = line.replace('\u2212', '-');

                final Matcher regularMatcher = regularLinePattern.matcher(line);
                if (regularMatcher.matches()) {
                    // we have found a regular data line
                    final int    doodson = Integer.parseInt(PATTERN.matcher(regularMatcher.group(columnDoodson)).replaceAll(""));
                    final double hf      = scale * Double.parseDouble(regularMatcher.group(columnHf));
                    amplitudesMap.put(doodson, hf);
                }
            }

        } catch (NumberFormatException nfe) {
            throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
                                      lineNumber, name, line);
        }

        if (amplitudesMap.isEmpty()) {
            throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_IERS_DATA_FILE, name);
        }

    }

    /** Get astronomical amplitudes map.
     * @return an unmodifiable map containing astronomical amplitudes H<sub>f</sub>
     * from a Doodson number key
     */
    public Map<Integer, Double> getAstronomicalAmplitudesMap() {
        return Collections.unmodifiableMap(amplitudesMap);
    }

}