Changeset 14067 in josm


Ignore:
Timestamp:
2018-07-30T21:57:20+02:00 (8 weeks ago)
Author:
Don-vip
Message:

fix #16496 - fix invalid parsing of NMEA time (decimal-fraction of seconds)

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/io/nmea/NmeaReader.java

    r14010 r14067  
    1515import java.util.Locale;
    1616import java.util.Objects;
    17 import java.util.Optional;
     17import java.util.regex.Matcher;
     18import java.util.regex.Pattern;
    1819
    1920import org.openstreetmap.josm.data.coor.LatLon;
     
    4546public class NmeaReader implements IGpxReader {
    4647
     48    /**
     49     * Course Over Ground and Ground Speed.
     50     * <p>
     51     * The actual course and speed relative to the ground
     52     */
    4753    enum VTG {
    4854        COURSE(1), COURSE_REF(2), // true course
     
    5965    }
    6066
     67    /**
     68     * Recommended Minimum Specific GNSS Data.
     69     * <p>
     70     * Time, date, position, course and speed data provided by a GNSS navigation receiver.
     71     * This sentence is transmitted at intervals not exceeding 2-seconds.
     72     * RMC is the recommended minimum data to be provided by a GNSS receiver.
     73     * All data fields must be provided, null fields used only when data is temporarily unavailable.
     74     */
    6175    enum RMC {
    6276        TIME(1),
     
    8195    }
    8296
     97    /**
     98     * Global Positioning System Fix Data.
     99     * <p>
     100     * Time, position and fix related data for a GPS receiver.
     101     */
    83102    enum GGA {
    84103        TIME(1), LATITUDE(2), LATITUDE_NAME(3), LONGITUDE(4), LONGITUDE_NAME(5),
     
    99118    }
    100119
     120    /**
     121     * GNSS DOP and Active Satellites.
     122     * <p>
     123     * GNSS receiver operating mode, satellites used in the navigation solution reported by the GGA or GNS sentence,
     124     * and DOP values.
     125     * If only GPS, GLONASS, etc. is used for the reported position solution the talker ID is GP, GL, etc.
     126     * and the DOP values pertain to the individual system. If GPS, GLONASS, etc. are combined to obtain the
     127     * reported position solution multiple GSA sentences are produced, one with the GPS satellites, another with
     128     * the GLONASS satellites, etc. Each of these GSA sentences shall have talker ID GN, to indicate that the
     129     * satellites are used in a combined solution and each shall have the PDOP, HDOP and VDOP for the
     130     * combined satellites used in the position.
     131     */
    101132    enum GSA {
    102133        AUTOMATIC(1),
     
    115146    }
    116147
     148    /**
     149     * Geographic Position - Latitude/Longitude.
     150     * <p>
     151     * Latitude and Longitude of vessel position, time of position fix and status.
     152     */
    117153    enum GLL {
    118154        LATITUDE(1), LATITUDE_NS(2), // Latitude, NS
     
    135171    GpxData data;
    136172
     173    private static final Pattern DATE_TIME_PATTERN = Pattern.compile("(\\d{12})(\\.\\d+)?");
     174
    137175    private final SimpleDateFormat rmcTimeFmt = new SimpleDateFormat("ddMMyyHHmmss.SSS", Locale.ENGLISH);
    138     private final SimpleDateFormat rmcTimeFmtStd = new SimpleDateFormat("ddMMyyHHmmss", Locale.ENGLISH);
    139176
    140177    private Date readTime(String p) throws IllegalDataException {
    141         Date d = Optional.ofNullable(rmcTimeFmt.parse(p, new ParsePosition(0)))
    142                 .orElseGet(() -> rmcTimeFmtStd.parse(p, new ParsePosition(0)));
    143         if (d == null)
    144             throw new IllegalDataException("Date is malformed: '" + p + "'");
    145         return d;
     178        // NMEA defines time with "a variable number of digits for decimal-fraction of seconds"
     179        // This variable decimal fraction cannot be parsed by SimpleDateFormat
     180        Matcher m = DATE_TIME_PATTERN.matcher(p);
     181        if (m.matches()) {
     182            String date = m.group(1);
     183            double milliseconds = 0d;
     184            if (m.groupCount() > 1 && m.group(2) != null) {
     185                milliseconds = 1000d * Double.parseDouble("0" + m.group(2));
     186            }
     187            // Add milliseconds on three digits to match SimpleDateFormat pattern
     188            date += String.format(".%03d", (int) milliseconds);
     189            Date d = rmcTimeFmt.parse(date, new ParsePosition(0));
     190            if (d != null)
     191                return d;
     192        }
     193        throw new IllegalDataException("Date is malformed: '" + p + "'");
    146194    }
    147195
     
    177225        this.source = Objects.requireNonNull(source);
    178226        rmcTimeFmt.setTimeZone(DateUtils.UTC);
    179         rmcTimeFmtStd.setTimeZone(DateUtils.UTC);
    180227    }
    181228
  • trunk/test/unit/org/openstreetmap/josm/io/nmea/NmeaReaderTest.java

    r14010 r14067  
    66import static org.junit.Assert.assertTrue;
    77
     8import java.io.ByteArrayInputStream;
    89import java.io.FileInputStream;
    910import java.io.IOException;
     11import java.nio.charset.StandardCharsets;
    1012import java.text.SimpleDateFormat;
    1113import java.util.ArrayList;
     14import java.util.Date;
    1215import java.util.List;
    1316import java.util.TimeZone;
    1417
     18import org.junit.Before;
    1519import org.junit.Rule;
    1620import org.junit.Test;
     
    4044    public JOSMTestRules test = new JOSMTestRules();
    4145
     46    private final SimpleDateFormat iso8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
     47
     48    /**
     49     * Forces the timezone.
     50     */
     51    @Before
     52    public void setUp() {
     53        iso8601.setTimeZone(TimeZone.getTimeZone("UTC"));
     54    }
     55
    4256    /**
    4357     * Tests reading a nmea file.
     
    5872        assertEquals(wayPoints.get(0).getTime(), DateUtils.fromString(wayPoints.get(0).get(GpxConstants.PT_TIME).toString()));
    5973
    60         final SimpleDateFormat iso8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
    61         assertEquals("2016-01-25T06:05:09.200+01", iso8601.format(wayPoints.get(0).getTime()));
    62         assertEquals("2016-01-25T06:05:09.400+01", iso8601.format(wayPoints.get(1).getTime()));
    63         assertEquals("2016-01-25T06:05:09.600+01", iso8601.format(wayPoints.get(2).getTime()));
     74        assertEquals("2016-01-25T05:05:09.200Z", iso8601.format(wayPoints.get(0).getTime()));
     75        assertEquals("2016-01-25T05:05:09.400Z", iso8601.format(wayPoints.get(1).getTime()));
     76        assertEquals("2016-01-25T05:05:09.600Z", iso8601.format(wayPoints.get(2).getTime()));
    6477
    6578        assertEquals(new LatLon(46.98807, -1.400525), wayPoints.get(0).getCoor());
     
    146159        compareWithReference(14924, "input", 0);
    147160    }
     161
     162    private static Date readDate(String nmeaLine) throws IOException, SAXException {
     163        NmeaReader in = new NmeaReader(new ByteArrayInputStream(nmeaLine.getBytes(StandardCharsets.UTF_8)));
     164        in.parse(true);
     165        return in.data.tracks.iterator().next().getSegments().iterator().next().getWayPoints().iterator().next().getTime();
     166    }
     167
     168    /**
     169     * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/16496">Bug #16496</a>.
     170     * @throws Exception if an error occurs
     171     */
     172    @Test
     173    public void testTicket16496() throws Exception {
     174        assertEquals("2018-05-30T16:28:59.400Z", iso8601.format(readDate("$GNRMC,162859.400,A,4543.03388,N,00058.19870,W,45.252,209.07,300518,,,D,V*13")));
     175        assertEquals("2018-05-30T16:28:59.400Z", iso8601.format(readDate("$GNRMC,162859.40,A,4543.03388,N,00058.19870,W,45.252,209.07,300518,,,D,V*23")));
     176        assertEquals("2018-05-30T16:28:59.400Z", iso8601.format(readDate("$GNRMC,162859.4,A,4543.03388,N,00058.19870,W,45.252,209.07,300518,,,D,V*13")));
     177    }
    148178}
Note: See TracChangeset for help on using the changeset viewer.