Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/io/nmea/NmeaReader.java
r14010 r14067 15 15 import java.util.Locale; 16 16 import java.util.Objects; 17 import java.util.Optional; 17 import java.util.regex.Matcher; 18 import java.util.regex.Pattern; 18 19 19 20 import org.openstreetmap.josm.data.coor.LatLon; … … 45 46 public class NmeaReader implements IGpxReader { 46 47 48 /** 49 * Course Over Ground and Ground Speed. 50 * <p> 51 * The actual course and speed relative to the ground 52 */ 47 53 enum VTG { 48 54 COURSE(1), COURSE_REF(2), // true course … … 59 65 } 60 66 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 */ 61 75 enum RMC { 62 76 TIME(1), … … 81 95 } 82 96 97 /** 98 * Global Positioning System Fix Data. 99 * <p> 100 * Time, position and fix related data for a GPS receiver. 101 */ 83 102 enum GGA { 84 103 TIME(1), LATITUDE(2), LATITUDE_NAME(3), LONGITUDE(4), LONGITUDE_NAME(5), … … 99 118 } 100 119 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 */ 101 132 enum GSA { 102 133 AUTOMATIC(1), … … 115 146 } 116 147 148 /** 149 * Geographic Position - Latitude/Longitude. 150 * <p> 151 * Latitude and Longitude of vessel position, time of position fix and status. 152 */ 117 153 enum GLL { 118 154 LATITUDE(1), LATITUDE_NS(2), // Latitude, NS … … 135 171 GpxData data; 136 172 173 private static final Pattern DATE_TIME_PATTERN = Pattern.compile("(\\d{12})(\\.\\d+)?"); 174 137 175 private final SimpleDateFormat rmcTimeFmt = new SimpleDateFormat("ddMMyyHHmmss.SSS", Locale.ENGLISH); 138 private final SimpleDateFormat rmcTimeFmtStd = new SimpleDateFormat("ddMMyyHHmmss", Locale.ENGLISH);139 176 140 177 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 + "'"); 146 194 } 147 195 … … 177 225 this.source = Objects.requireNonNull(source); 178 226 rmcTimeFmt.setTimeZone(DateUtils.UTC); 179 rmcTimeFmtStd.setTimeZone(DateUtils.UTC);180 227 } 181 228 -
trunk/test/unit/org/openstreetmap/josm/io/nmea/NmeaReaderTest.java
r14010 r14067 6 6 import static org.junit.Assert.assertTrue; 7 7 8 import java.io.ByteArrayInputStream; 8 9 import java.io.FileInputStream; 9 10 import java.io.IOException; 11 import java.nio.charset.StandardCharsets; 10 12 import java.text.SimpleDateFormat; 11 13 import java.util.ArrayList; 14 import java.util.Date; 12 15 import java.util.List; 13 16 import java.util.TimeZone; 14 17 18 import org.junit.Before; 15 19 import org.junit.Rule; 16 20 import org.junit.Test; … … 40 44 public JOSMTestRules test = new JOSMTestRules(); 41 45 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 42 56 /** 43 57 * Tests reading a nmea file. … … 58 72 assertEquals(wayPoints.get(0).getTime(), DateUtils.fromString(wayPoints.get(0).get(GpxConstants.PT_TIME).toString())); 59 73 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())); 64 77 65 78 assertEquals(new LatLon(46.98807, -1.400525), wayPoints.get(0).getCoor()); … … 146 159 compareWithReference(14924, "input", 0); 147 160 } 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 } 148 178 }
Note:
See TracChangeset
for help on using the changeset viewer.