TLE.java

  1. /* Copyright 2002-2013 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.propagation.analytical.tle;

  18. import java.io.Serializable;
  19. import java.text.DecimalFormat;
  20. import java.text.DecimalFormatSymbols;
  21. import java.util.Locale;
  22. import java.util.regex.Pattern;

  23. import org.apache.commons.math3.util.FastMath;
  24. import org.orekit.errors.OrekitException;
  25. import org.orekit.errors.OrekitMessages;
  26. import org.orekit.time.AbsoluteDate;
  27. import org.orekit.time.DateComponents;
  28. import org.orekit.time.TimeComponents;
  29. import org.orekit.time.TimeScale;
  30. import org.orekit.time.TimeScalesFactory;
  31. import org.orekit.time.TimeStamped;
  32. import org.orekit.utils.Constants;

  33. /** This class is a container for a single set of TLE data.
  34.  *
  35.  * <p>TLE sets can be built either by providing directly the two lines, in
  36.  * which case parsing is performed internally or by providing the already
  37.  * parsed elements.</p>
  38.  * <p>TLE are not transparently convertible to {@link org.orekit.orbits.Orbit Orbit}
  39.  * instances. They are significant only with respect to their dedicated {@link
  40.  * TLEPropagator propagator}, which also computes position and velocity coordinates.
  41.  * Any attempt to directly use orbital parameters like {@link #getE() eccentricity},
  42.  * {@link #getI() inclination}, etc. without any reference to the {@link TLEPropagator
  43.  * TLE propagator} is prone to errors.</p>
  44.  * <p>More information on the TLE format can be found on the
  45.  * <a href="http://www.celestrak.com/">CelesTrak website.</a></p>
  46.  * @author Fabien Maussion
  47.  * @author Luc Maisonobe
  48.  */
  49. public class TLE implements TimeStamped, Serializable {

  50.     /** Identifier for default type of ephemeris (SGP4/SDP4). */
  51.     public static final int DEFAULT = 0;

  52.     /** Identifier for SGP type of ephemeris. */
  53.     public static final int SGP = 1;

  54.     /** Identifier for SGP4 type of ephemeris. */
  55.     public static final int SGP4 = 2;

  56.     /** Identifier for SDP4 type of ephemeris. */
  57.     public static final int SDP4 = 3;

  58.     /** Identifier for SGP8 type of ephemeris. */
  59.     public static final int SGP8 = 4;

  60.     /** Identifier for SDP8 type of ephemeris. */
  61.     public static final int SDP8 = 5;

  62.     /** Pattern for line 1. */
  63.     private static final Pattern LINE_1_PATTERN =
  64.         Pattern.compile("1 [ 0-9]{5}U [ 0-9]{5}[ A-Z]{3} [ 0-9]{5}[.][ 0-9]{8} [ +-][.][ 0-9]{8} " +
  65.                         "[ +-][ 0-9]{5}[+-][ 0-9] [ +-][ 0-9]{5}[+-][ 0-9] [ 0-9] [ 0-9]{4}[ 0-9]");

  66.     /** Pattern for line 2. */
  67.     private static final Pattern LINE_2_PATTERN =
  68.         Pattern.compile("2 [ 0-9]{5} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{7} " +
  69.                         "[ 0-9]{3}[.][ 0-9]{4} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{2}[.][ 0-9]{13}[ 0-9]");

  70.     /** International symbols for parsing. */
  71.     private static final DecimalFormatSymbols SYMBOLS =
  72.         new DecimalFormatSymbols(Locale.US);

  73.     /** Serializable UID. */
  74.     private static final long serialVersionUID = -1596648022319057689L;

  75.     /** The satellite number. */
  76.     private final int satelliteNumber;

  77.     /** Classification (U for unclassified). */
  78.     private final char classification;

  79.     /** Launch year. */
  80.     private final int launchYear;

  81.     /** Launch number. */
  82.     private final int launchNumber;

  83.     /** Piece of launch (from "A" to "ZZZ"). */
  84.     private final String launchPiece;

  85.     /** Type of ephemeris. */
  86.     private final int ephemerisType;

  87.     /** Element number. */
  88.     private final int elementNumber;

  89.     /** the TLE current date. */
  90.     private final AbsoluteDate epoch;

  91.     /** Mean motion (rad/s). */
  92.     private final double meanMotion;

  93.     /** Mean motion first derivative (rad/s<sup>2</sup>). */
  94.     private final double meanMotionFirstDerivative;

  95.     /** Mean motion second derivative (rad/s<sup>3</sup>). */
  96.     private final double meanMotionSecondDerivative;

  97.     /** Eccentricity. */
  98.     private final double eccentricity;

  99.     /** Inclination (rad). */
  100.     private final double inclination;

  101.     /** Argument of perigee (rad). */
  102.     private final double pa;

  103.     /** Right Ascension of the Ascending node (rad). */
  104.     private final double raan;

  105.     /** Mean anomaly (rad). */
  106.     private final double meanAnomaly;

  107.     /** Revolution number at epoch. */
  108.     private final int revolutionNumberAtEpoch;

  109.     /** Ballistic coefficient. */
  110.     private final double bStar;

  111.     /** First line. */
  112.     private String line1;

  113.     /** Second line. */
  114.     private String line2;

  115.     /** Simple constructor from unparsed two lines.
  116.      * <p>The static method {@link #isFormatOK(String, String)} should be called
  117.      * before trying to build this object.<p>
  118.      * @param line1 the first element (69 char String)
  119.      * @param line2 the second element (69 char String)
  120.      * @exception OrekitException if some format error occurs or lines are inconsistent
  121.      */
  122.     public TLE(final String line1, final String line2) throws OrekitException {

  123.         // identification
  124.         satelliteNumber = parseInteger(line1, 2, 5);
  125.         final int satNum2 = parseInteger(line2, 2, 5);
  126.         if (satelliteNumber != satNum2) {
  127.             throw new OrekitException(OrekitMessages.TLE_LINES_DO_NOT_REFER_TO_SAME_OBJECT,
  128.                                       line1, line2);
  129.         }
  130.         classification  = line1.charAt(7);
  131.         launchYear      = parseYear(line1, 9);
  132.         launchNumber    = parseInteger(line1, 11, 3);
  133.         launchPiece     = line1.substring(14, 17).trim();
  134.         ephemerisType   = parseInteger(line1, 62, 1);
  135.         elementNumber   = parseInteger(line1, 64, 4);

  136.         // Date format transform (nota: 27/31250 == 86400/100000000)
  137.         final int    year      = parseYear(line1, 18);
  138.         final int    dayInYear = parseInteger(line1, 20, 3);
  139.         final long   df        = 27l * parseInteger(line1, 24, 8);
  140.         final int    secondsA  = (int) (df / 31250l);
  141.         final double secondsB  = (df % 31250l) / 31250.0;
  142.         epoch = new AbsoluteDate(new DateComponents(year, dayInYear),
  143.                                  new TimeComponents(secondsA, secondsB),
  144.                                  TimeScalesFactory.getUTC());

  145.         // mean motion development
  146.         // converted from rev/day, 2 * rev/day^2 and 6 * rev/day^3 to rad/s, rad/s^2 and rad/s^3
  147.         meanMotion                 = parseDouble(line2, 52, 11) * FastMath.PI / 43200.0;
  148.         meanMotionFirstDerivative  = parseDouble(line1, 33, 10) * FastMath.PI / 1.86624e9;
  149.         meanMotionSecondDerivative = Double.parseDouble((line1.substring(44, 45) + '.' +
  150.                                                          line1.substring(45, 50) + 'e' +
  151.                                                          line1.substring(50, 52)).replace(' ', '0')) *
  152.                                      FastMath.PI / 5.3747712e13;

  153.         eccentricity = Double.parseDouble("." + line2.substring(26, 33).replace(' ', '0'));
  154.         inclination  = FastMath.toRadians(parseDouble(line2, 8, 8));
  155.         pa           = FastMath.toRadians(parseDouble(line2, 34, 8));
  156.         raan         = FastMath.toRadians(Double.parseDouble(line2.substring(17, 25).replace(' ', '0')));
  157.         meanAnomaly  = FastMath.toRadians(parseDouble(line2, 43, 8));

  158.         revolutionNumberAtEpoch = parseInteger(line2, 63, 5);
  159.         bStar = Double.parseDouble((line1.substring(53, 54) + '.' +
  160.                                     line1.substring(54, 59) + 'e' +
  161.                                     line1.substring(59, 61)).replace(' ', '0'));

  162.         // save the lines
  163.         this.line1 = line1;
  164.         this.line2 = line2;

  165.     }

  166.     /** Simple constructor from already parsed elements.
  167.      * @param satelliteNumber satellite number
  168.      * @param classification classification (U for unclassified)
  169.      * @param launchYear launch year (all digits)
  170.      * @param launchNumber launch number
  171.      * @param launchPiece launch piece
  172.      * @param ephemerisType type of ephemeris
  173.      * @param elementNumber element number
  174.      * @param epoch elements epoch
  175.      * @param meanMotion mean motion (rad/s)
  176.      * @param meanMotionFirstDerivative mean motion first derivative (rad/s<sup>2</sup>)
  177.      * @param meanMotionSecondDerivative mean motion second derivative (rad/s<sup>3</sup>)
  178.      * @param e eccentricity
  179.      * @param i inclination (rad)
  180.      * @param pa argument of perigee (rad)
  181.      * @param raan right ascension of ascending node (rad)
  182.      * @param meanAnomaly mean anomaly (rad)
  183.      * @param revolutionNumberAtEpoch revolution number at epoch
  184.      * @param bStar ballistic coefficient
  185.      */
  186.     public TLE(final int satelliteNumber, final char classification,
  187.                final int launchYear, final int launchNumber, final String launchPiece,
  188.                final int ephemerisType, final int elementNumber, final AbsoluteDate epoch,
  189.                final double meanMotion, final double meanMotionFirstDerivative,
  190.                final double meanMotionSecondDerivative, final double e, final double i,
  191.                final double pa, final double raan, final double meanAnomaly,
  192.                final int revolutionNumberAtEpoch, final double bStar) {

  193.         // identification
  194.         this.satelliteNumber = satelliteNumber;
  195.         this.classification  = classification;
  196.         this.launchYear      = launchYear;
  197.         this.launchNumber    = launchNumber;
  198.         this.launchPiece     = launchPiece;
  199.         this.ephemerisType   = ephemerisType;
  200.         this.elementNumber   = elementNumber;

  201.         // orbital parameters
  202.         this.epoch                      = epoch;
  203.         this.meanMotion                 = meanMotion;
  204.         this.meanMotionFirstDerivative  = meanMotionFirstDerivative;
  205.         this.meanMotionSecondDerivative = meanMotionSecondDerivative;
  206.         this.inclination                = i;
  207.         this.raan                       = raan;
  208.         this.eccentricity               = e;
  209.         this.pa                         = pa;
  210.         this.meanAnomaly                = meanAnomaly;

  211.         this.revolutionNumberAtEpoch = revolutionNumberAtEpoch;
  212.         this.bStar                   = bStar;

  213.         // don't build the line until really needed
  214.         this.line1 = null;
  215.         this.line2 = null;

  216.     }

  217.     /** Get the first line.
  218.      * @return first line
  219.      * @exception OrekitException if UTC conversion cannot be done
  220.      */
  221.     public String getLine1()
  222.         throws OrekitException {
  223.         if (line1 == null) {
  224.             buildLine1();
  225.         }
  226.         return line1;
  227.     }

  228.     /** Get the second line.
  229.      * @return second line
  230.      */
  231.     public String getLine2() {
  232.         if (line2 == null) {
  233.             buildLine2();
  234.         }
  235.         return line2;
  236.     }

  237.     /** Build the line 1 from the parsed elements.
  238.      * @exception OrekitException if UTC conversion cannot be done
  239.      */
  240.     private void buildLine1()
  241.         throws OrekitException {

  242.         final StringBuffer buffer = new StringBuffer();
  243.         final DecimalFormat f38  = new DecimalFormat("000.00000000", SYMBOLS);

  244.         buffer.append('1');

  245.         buffer.append(' ');
  246.         buffer.append(addPadding(satelliteNumber, '0', 5, true));
  247.         buffer.append(classification);

  248.         buffer.append(' ');
  249.         buffer.append(addPadding(launchYear % 100, '0', 2, true));
  250.         buffer.append(addPadding(launchNumber, '0', 3, true));
  251.         buffer.append(addPadding(launchPiece, ' ', 3, false));

  252.         buffer.append(' ');
  253.         final TimeScale utc = TimeScalesFactory.getUTC();
  254.         final int year = epoch.getComponents(utc).getDate().getYear();
  255.         buffer.append(addPadding(year % 100, '0', 2, true));
  256.         final double day = 1.0 + epoch.durationFrom(new AbsoluteDate(year, 1, 1, utc)) / Constants.JULIAN_DAY;
  257.         buffer.append(f38.format(day));

  258.         buffer.append(' ');
  259.         final double n1 = meanMotionFirstDerivative * 1.86624e9 / FastMath.PI;
  260.         final String sn1 = addPadding(new DecimalFormat(".00000000", SYMBOLS).format(n1), ' ', 10, true);
  261.         buffer.append(sn1);

  262.         buffer.append(' ');
  263.         final double n2 = meanMotionSecondDerivative * 5.3747712e13 / FastMath.PI;
  264.         buffer.append(addPadding(formatExponentMarkerFree(n2, 5), ' ', 8, true));

  265.         buffer.append(' ');
  266.         buffer.append(addPadding(formatExponentMarkerFree(bStar, 5), ' ', 8, true));

  267.         buffer.append(' ');
  268.         buffer.append(ephemerisType);

  269.         buffer.append(' ');
  270.         buffer.append(addPadding(elementNumber, ' ', 4, true));

  271.         buffer.append(Integer.toString(checksum(buffer)));

  272.         line1 = buffer.toString();

  273.     }

  274.     /** Format a real number without 'e' exponent marker.
  275.      * @param d number to format
  276.      * @param mantissaSize size of the mantissa (not counting initial '-' or ' ' for sign)
  277.      * @return formatted number
  278.      */
  279.     private String formatExponentMarkerFree(final double d, final int mantissaSize) {
  280.         final double dAbs = FastMath.abs(d);
  281.         int exponent = (dAbs < 1.0e-9) ? -9 : (int) FastMath.ceil(FastMath.log10(dAbs));
  282.         final long mantissa = FastMath.round(dAbs * FastMath.pow(10.0, mantissaSize - exponent));
  283.         if (mantissa == 0) {
  284.             exponent = 0;
  285.         }
  286.         final String sMantissa = addPadding((int) mantissa, '0', mantissaSize, true);
  287.         final String sExponent = Integer.toString(FastMath.abs(exponent));
  288.         return (d <  0 ? '-' : ' ') + sMantissa + (exponent <= 0 ? '-' : '+') + sExponent;
  289.     }

  290.     /** Build the line 2 from the parsed elements.
  291.      */
  292.     private void buildLine2() {

  293.         final StringBuffer buffer = new StringBuffer();
  294.         final DecimalFormat f34   = new DecimalFormat("##0.0000", SYMBOLS);
  295.         final DecimalFormat f211  = new DecimalFormat("#0.00000000", SYMBOLS);

  296.         buffer.append('2');

  297.         buffer.append(' ');
  298.         buffer.append(addPadding(satelliteNumber, '0', 5, true));

  299.         buffer.append(' ');
  300.         buffer.append(addPadding(f34.format(FastMath.toDegrees(inclination)), ' ', 8, true));
  301.         buffer.append(' ');
  302.         buffer.append(addPadding(f34.format(FastMath.toDegrees(raan)), ' ', 8, true));
  303.         buffer.append(' ');
  304.         buffer.append(addPadding((int) FastMath.rint(eccentricity * 1.0e7), '0', 7, true));
  305.         buffer.append(' ');
  306.         buffer.append(addPadding(f34.format(FastMath.toDegrees(pa)), ' ', 8, true));
  307.         buffer.append(' ');
  308.         buffer.append(addPadding(f34.format(FastMath.toDegrees(meanAnomaly)), ' ', 8, true));

  309.         buffer.append(' ');
  310.         buffer.append(addPadding(f211.format(meanMotion * 43200.0 / FastMath.PI), ' ', 11, true));
  311.         buffer.append(addPadding(revolutionNumberAtEpoch, ' ', 5, true));

  312.         buffer.append(Integer.toString(checksum(buffer)));

  313.         line2 = buffer.toString();

  314.     }

  315.     /** Add padding characters before an integer.
  316.      * @param k integer to pad
  317.      * @param c padding character
  318.      * @param size desired size
  319.      * @param rightJustified if true, the resulting string is
  320.      * right justified (i.e. space are added to the left)
  321.      * @return padded string
  322.      */
  323.     private String addPadding(final int k, final char c,
  324.                               final int size, final boolean rightJustified) {
  325.         return addPadding(Integer.toString(k), c, size, rightJustified);
  326.     }

  327.     /** Add padding characters to a string.
  328.      * @param string string to pad
  329.      * @param c padding character
  330.      * @param size desired size
  331.      * @param rightJustified if true, the resulting string is
  332.      * right justified (i.e. space are added to the left)
  333.      * @return padded string
  334.      */
  335.     private String addPadding(final String string, final char c,
  336.                               final int size, final boolean rightJustified) {

  337.         final StringBuffer padding = new StringBuffer();
  338.         for (int i = 0; i < size; ++i) {
  339.             padding.append(c);
  340.         }

  341.         if (rightJustified) {
  342.             final String concatenated = padding + string;
  343.             final int l = concatenated.length();
  344.             return concatenated.substring(l - size, l);
  345.         }

  346.         return (string + padding).substring(0, size);

  347.     }

  348.     /** Parse a double.
  349.      * @param line line to parse
  350.      * @param start start index of the first character
  351.      * @param length length of the string
  352.      * @return value of the double
  353.      */
  354.     private double parseDouble(final String line, final int start, final int length) {
  355.         return Double.parseDouble(line.substring(start, start + length).replace(' ', '0'));
  356.     }

  357.     /** Parse an integer.
  358.      * @param line line to parse
  359.      * @param start start index of the first character
  360.      * @param length length of the string
  361.      * @return value of the integer
  362.      */
  363.     private int parseInteger(final String line, final int start, final int length) {
  364.         return Integer.parseInt(line.substring(start, start + length).replace(' ', '0'));
  365.     }

  366.     /** Parse a year written on 2 digits.
  367.      * @param line line to parse
  368.      * @param start start index of the first character
  369.      * @return value of the year
  370.      */
  371.     private int parseYear(final String line, final int start) {
  372.         final int year = 2000 + parseInteger(line, start, 2);
  373.         return (year > 2056) ? (year - 100) : year;
  374.     }

  375.     /** Get the satellite id.
  376.      * @return the satellite number
  377.      */
  378.     public int getSatelliteNumber() {
  379.         return satelliteNumber;
  380.     }

  381.     /** Get the classification.
  382.      * @return classification
  383.      */
  384.     public char getClassification() {
  385.         return classification;
  386.     }

  387.     /** Get the launch year.
  388.      * @return the launch year
  389.      */
  390.     public int getLaunchYear() {
  391.         return launchYear;
  392.     }

  393.     /** Get the launch number.
  394.      * @return the launch number
  395.      */
  396.     public int getLaunchNumber() {
  397.         return launchNumber;
  398.     }

  399.     /** Get the launch piece.
  400.      * @return the launch piece
  401.      */
  402.     public String getLaunchPiece() {
  403.         return launchPiece;
  404.     }

  405.     /** Get the type of ephemeris.
  406.      * @return the ephemeris type (one of {@link #DEFAULT}, {@link #SGP},
  407.      * {@link #SGP4}, {@link #SGP8}, {@link #SDP4}, {@link #SDP8})
  408.      */
  409.     public int getEphemerisType() {
  410.         return ephemerisType;
  411.     }

  412.     /** Get the element number.
  413.      * @return the element number
  414.      */
  415.     public int getElementNumber() {
  416.         return elementNumber;
  417.     }

  418.     /** Get the TLE current date.
  419.      * @return the epoch
  420.      */
  421.     public AbsoluteDate getDate() {
  422.         return epoch;
  423.     }

  424.     /** Get the mean motion.
  425.      * @return the mean motion (rad/s)
  426.      */
  427.     public double getMeanMotion() {
  428.         return meanMotion;
  429.     }

  430.     /** Get the mean motion first derivative.
  431.      * @return the mean motion first derivative (rad/s<sup>2</sup>)
  432.      */
  433.     public double getMeanMotionFirstDerivative() {
  434.         return meanMotionFirstDerivative;
  435.     }

  436.     /** Get the mean motion second derivative.
  437.      * @return the mean motion second derivative (rad/s<sup>3</sup>)
  438.      */
  439.     public double getMeanMotionSecondDerivative() {
  440.         return meanMotionSecondDerivative;
  441.     }

  442.     /** Get the eccentricity.
  443.      * @return the eccentricity
  444.      */
  445.     public double getE() {
  446.         return eccentricity;
  447.     }

  448.     /** Get the inclination.
  449.      * @return the inclination (rad)
  450.      */
  451.     public double getI() {
  452.         return inclination;
  453.     }

  454.     /** Get the argument of perigee.
  455.      * @return omega (rad)
  456.      */
  457.     public double getPerigeeArgument() {
  458.         return pa;
  459.     }

  460.     /** Get Right Ascension of the Ascending node.
  461.      * @return the raan (rad)
  462.      */
  463.     public double getRaan() {
  464.         return raan;
  465.     }

  466.     /** Get the mean anomaly.
  467.      * @return the mean anomaly (rad)
  468.      */
  469.     public double getMeanAnomaly() {
  470.         return meanAnomaly;
  471.     }

  472.     /** Get the revolution number.
  473.      * @return the revolutionNumberAtEpoch
  474.      */
  475.     public int getRevolutionNumberAtEpoch() {
  476.         return revolutionNumberAtEpoch;
  477.     }

  478.     /** Get the ballistic coefficient.
  479.      * @return bStar
  480.      */
  481.     public double getBStar() {
  482.         return bStar;
  483.     }

  484.     /** Get a string representation of this TLE set.
  485.      * <p>The representation is simply the two lines separated by the
  486.      * platform line separator.</p>
  487.      * @return string representation of this TLE set
  488.      */
  489.     public String toString() {
  490.         try {
  491.             return getLine1() + System.getProperty("line.separator") + getLine2();
  492.         } catch (OrekitException oe) {
  493.             throw OrekitException.createInternalError(oe);
  494.         }
  495.     }

  496.     /** Check the lines format validity.
  497.      * @param line1 the first element
  498.      * @param line2 the second element
  499.      * @return true if format is recognized (non null lines, 69 characters length,
  500.      * line content), false if not
  501.      * @exception OrekitException if checksum is not valid
  502.      */
  503.     public static boolean isFormatOK(final String line1, final String line2)
  504.         throws OrekitException {

  505.         if (line1 == null || line1.length() != 69 ||
  506.             line2 == null || line2.length() != 69) {
  507.             return false;
  508.         }

  509.         if (!(LINE_1_PATTERN.matcher(line1).matches() &&
  510.               LINE_2_PATTERN.matcher(line2).matches())) {
  511.             return false;
  512.         }

  513.         // check sums
  514.         final int checksum1 = checksum(line1);
  515.         if (Integer.parseInt(line1.substring(68)) != (checksum1 % 10)) {
  516.             throw new OrekitException(OrekitMessages.TLE_CHECKSUM_ERROR,
  517.                                       1, line1.substring(68) ,
  518.                                       checksum1 % 10, line1);
  519.         }

  520.         final int checksum2 = checksum(line2);
  521.         if (Integer.parseInt(line2.substring(68)) != (checksum2 % 10)) {
  522.             throw new OrekitException(OrekitMessages.TLE_CHECKSUM_ERROR,
  523.                                       2, line2.substring(68) ,
  524.                                           checksum2 % 10, line2);
  525.         }

  526.         return true;

  527.     }

  528.     /** Compute the checksum of the first 68 characters of a line.
  529.      * @param line line to check
  530.      * @return checksum
  531.      */
  532.     private static int checksum(final CharSequence line) {
  533.         int sum = 0;
  534.         for (int j = 0; j < 68; j++) {
  535.             final char c = line.charAt(j);
  536.             if (Character.isDigit(c)) {
  537.                 sum += Character.digit(c, 10);
  538.             } else if (c == '-') {
  539.                 ++sum;
  540.             }
  541.         }
  542.         return sum % 10;
  543.     }

  544. }