source: josm/trunk/src/org/openstreetmap/josm/io/NmeaReader.java@ 1180

Last change on this file since 1180 was 1169, checked in by stoecker, 15 years ago

removed usage of tab stops

File size: 16.7 KB
Line 
1//License: GPL. Copyright 2008 by Christoph Brill
2
3package org.openstreetmap.josm.io;
4
5import java.io.BufferedReader;
6import java.io.File;
7import java.io.IOException;
8import java.io.InputStream;
9import java.io.InputStreamReader;
10import java.text.ParsePosition;
11import java.text.SimpleDateFormat;
12import java.util.ArrayList;
13import java.util.Arrays;
14import java.util.Collection;
15import java.util.Date;
16
17import org.openstreetmap.josm.data.coor.LatLon;
18import org.openstreetmap.josm.data.gpx.GpxData;
19import org.openstreetmap.josm.data.gpx.GpxTrack;
20import org.openstreetmap.josm.data.gpx.WayPoint;
21
22/**
23 * Read a nmea file. Based on information from
24 * http://www.kowoma.de/gps/zusatzerklaerungen/NMEA.htm
25 *
26 * @author cbrill
27 */
28public class NmeaReader {
29
30 /** Handler for the different types that NMEA speaks. */
31 public static enum NMEA_TYPE {
32
33 /** RMC = recommended minimum sentence C. */
34 GPRMC("$GPRMC"),
35 /** GPS positions. */
36 GPGGA("$GPGGA"),
37 /** SA = satellites active. */
38 GPGSA("$GPGSA"),
39 /** Course over ground and ground speed */
40 GPVTG("$GPVTG");
41
42 private final String type;
43
44 NMEA_TYPE(String type) {
45 this.type = type;
46 }
47
48 public String getType() {
49 return this.type;
50 }
51
52 public boolean equals(String type) {
53 return this.type.equals(type);
54 }
55 }
56
57 // GPVTG
58 public static enum GPVTG {
59 COURSE(1),COURSE_REF(2), // true course
60 COURSE_M(3), COURSE_M_REF(4), // magnetic course
61 SPEED_KN(5), SPEED_KN_UNIT(6), // speed in knots
62 SPEED_KMH(7), SPEED_KMH_UNIT(8), // speed in km/h
63 REST(9); // version-specific rest
64
65 public final int position;
66
67 GPVTG(int position) {
68 this.position = position;
69 }
70 }
71
72 // The following only applies to GPRMC
73 public static enum GPRMC {
74 TIME(1),
75 /** Warning from the receiver (A = data ok, V = warning) */
76 RECEIVER_WARNING(2),
77 WIDTH_NORTH(3), WIDTH_NORTH_NAME(4), // Latitude, NS
78 LENGTH_EAST(5), LENGTH_EAST_NAME(6), // Longitude, EW
79 SPEED(7), COURSE(8), DATE(9), // Speed in knots
80 MAGNETIC_DECLINATION(10), UNKNOWN(11), // magnetic declination
81 /**
82 * Mode (A = autonom; D = differential; E = estimated; N = not valid; S
83 * = simulated)
84 *
85 * @since NMEA 2.3
86 */
87 MODE(12);
88
89 public final int position;
90
91 GPRMC(int position) {
92 this.position = position;
93 }
94 }
95
96 // The following only applies to GPGGA
97 public static enum GPGGA {
98 TIME(1), LATITUDE(2), LATITUDE_NAME(3), LONGITUDE(4), LONGITUDE_NAME(5),
99 /**
100 * Quality (0 = invalid, 1 = GPS, 2 = DGPS, 6 = estimanted (@since NMEA
101 * 2.3))
102 */
103 QUALITY(6), SATELLITE_COUNT(7),
104 HDOP(8), // HDOP (horizontal dilution of precision)
105 HEIGHT(9), HEIGHT_UNTIS(10), // height above NN (above geoid)
106 HEIGHT_2(11), HEIGHT_2_UNTIS(12), // height geoid - height ellipsoid (WGS84)
107 GPS_AGE(13),// Age of differential GPS data
108 REF(14); // REF station
109
110 public final int position;
111 GPGGA(int position) {
112 this.position = position;
113 }
114 }
115
116 public static enum GPGSA {
117 AUTOMATIC(1),
118 FIX_TYPE(2), // 1 = not fixed, 2 = 2D fixed, 3 = 3D fixed)
119 // PRN numbers for max 12 satellites
120 PRN_1(3), PRN_2(4), PRN_3(5), PRN_4(6), PRN_5(7), PRN_6(8),
121 PRN_7(9), PRN_8(10), PRN_9(11), PRN_10(12), PRN_11(13), PRN_12(14),
122 PDOP(15), // PDOP (precision)
123 HDOP(16), // HDOP (horizontal precision)
124 VDOP(17), ; // VDOP (vertical precision)
125
126 public final int position;
127 GPGSA(int position) {
128 this.position = position;
129 }
130 }
131
132 public GpxData data;
133
134// private final static SimpleDateFormat GGATIMEFMT =
135// new SimpleDateFormat("HHmmss.SSS");
136 private final static SimpleDateFormat RMCTIMEFMT =
137 new SimpleDateFormat("ddMMyyHHmmss.SSS");
138
139 // functons for reading the error stats
140 public NMEAParserState ps;
141
142 public int getParserUnknown() {
143 return ps.unknown;
144 }
145 public int getParserZeroCoordinates() {
146 return ps.zero_coord;
147 }
148 public int getParserChecksumErrors() {
149 return ps.checksum_errors;
150 }
151 public int getParserMalformed() {
152 return ps.malformed;
153 }
154 public int getNumberOfCoordinates() {
155 return ps.success;
156 }
157
158 public NmeaReader(InputStream source, File relativeMarkerPath) {
159
160 // create the data tree
161 data = new GpxData();
162 GpxTrack currentTrack = new GpxTrack();
163 data.tracks.add(currentTrack);
164
165 try {
166 BufferedReader rd =
167 new BufferedReader(new InputStreamReader(source));
168
169 StringBuffer sb = new StringBuffer(1024);
170 int loopstart_char = rd.read();
171 if(loopstart_char == -1) {// zero size file
172 //TODO tell user about the problem?
173 return;
174 }
175 sb.append((char)loopstart_char);
176 ps = new NMEAParserState();
177 ps.p_Date="010100"; // TODO date problem
178 while(true) {
179 // don't load unparsable files completely to memory
180 if(sb.length()>=1020) sb.delete(0, sb.length()-1);
181 int c = rd.read();
182 if(c=='$') {
183 ParseNMEASentence(sb.toString(), ps);
184 sb.delete(0, sb.length());
185 sb.append('$');
186 } else if(c == -1) {
187 // EOF: add last WayPoint if it works out
188 ParseNMEASentence(sb.toString(),ps);
189 break;
190 } else sb.append((char)c);
191 }
192 rd.close();
193 Object[] wparr = ps.waypoints.toArray();
194 currentTrack.trackSegs.add(ps.waypoints);
195 data.recalculateBounds();
196
197 } catch (final IOException e) {
198 // TODO tell user about the problem?
199 }
200 }
201 private class NMEAParserState {
202 protected Collection<WayPoint> waypoints = new ArrayList<WayPoint>();
203 protected String p_Time;
204 protected String p_Date;
205 protected WayPoint p_Wp;
206
207 protected int success = 0; // number of successfully parsend sentences
208 protected int malformed = 0;
209 protected int checksum_errors = 0;
210 protected int unknown = 0;
211 protected int zero_coord = 0;
212 }
213
214 // Parses split up sentences into WayPoints which are stored
215 // in the collection in the NMEAParserState object.
216 // Returns true if the input made sence, false otherwise.
217 private boolean ParseNMEASentence(String s, NMEAParserState ps) {
218 try {
219 if(s.equals("")) throw(null);
220
221 // checksum check:
222 // the bytes between the $ and the * are xored;
223 // if there is no * or other meanities it will throw
224 // and result in a malformed packet.
225 String[] chkstrings = s.split("\\*");
226 byte[] chb = chkstrings[0].getBytes();
227 int chk=0;
228 for(int i = 1; i < chb.length; i++) chk ^= chb[i];
229 if(Integer.parseInt(chkstrings[1].substring(0,2),16) != chk) {
230 //System.out.println("Checksum error");
231 ps.checksum_errors++;
232 ps.p_Wp=null;
233 return false;
234 }
235 // now for the content
236 String[] e = chkstrings[0].split(",");
237 String accu;
238
239 WayPoint currentwp = ps.p_Wp;
240 String currentDate = ps.p_Date;
241
242 // handle the packet content
243 if(e[0].equals("$GPGGA")) {
244 // Position
245 LatLon latLon = parseLatLon(
246 e[GPGGA.LATITUDE_NAME.position],
247 e[GPGGA.LONGITUDE_NAME.position],
248 e[GPGGA.LATITUDE.position],
249 e[GPGGA.LONGITUDE.position]
250 );
251 if(latLon==null) throw(null); // malformed
252
253 if((latLon.lat()==0.0) && (latLon.lon()==0.0)) {
254 ps.zero_coord++;
255 return false;
256 }
257
258 // time
259 accu = e[GPGGA.TIME.position];
260 Date d = RMCTIMEFMT.parse(currentDate+accu, new ParsePosition(0));
261 if (d == null) throw(null); // malformed
262
263 if((ps.p_Time==null) || (currentwp==null) || !ps.p_Time.equals(accu)) {
264 // this node is newer than the previous, create a new waypoint.
265 // no matter if previous WayPoint was null, we got something
266 // better now.
267 ps.p_Time=accu;
268 currentwp = new WayPoint(latLon);
269 }
270 if(!currentwp.attr.containsKey("time")) {
271 // As this sentence has no complete time only use it
272 // if there is no time so far
273 String gpxdate = WayPoint.GPXTIMEFMT.format(d);
274 currentwp.attr.put("time", gpxdate);
275 }
276 // elevation
277 accu=e[GPGGA.HEIGHT_UNTIS.position];
278 if(accu.equals("M")) {
279 // Ignore heights that are not in meters for now
280 accu=e[GPGGA.HEIGHT.position];
281 if(!accu.equals("")) {
282 Double.parseDouble(accu);
283 // if it throws it's malformed; this should only happen if the
284 // device sends nonstandard data.
285 if(!accu.equals("")) currentwp.attr.put("ele", accu);
286 }
287 }
288 // number of sattelites
289 accu=e[GPGGA.SATELLITE_COUNT.position];
290 int sat = 0;
291 if(!accu.equals("")) {
292 sat = Integer.parseInt(accu);
293 currentwp.attr.put("sat", accu);
294 }
295 // h-dilution
296 accu=e[GPGGA.HDOP.position];
297 if(!accu.equals("")) {
298 Double.parseDouble(accu);
299 currentwp.attr.put("hdop", accu);
300 }
301 // fix
302 accu=e[GPGGA.QUALITY.position];
303 if(!accu.equals("")) {
304 int fixtype = Integer.parseInt(accu);
305 switch(fixtype) {
306 case 0:
307 currentwp.attr.put("fix", "none");
308 break;
309 case 1:
310 if(sat < 4) currentwp.attr.put("fix", "2d");
311 else currentwp.attr.put("fix", "3d");
312 break;
313 case 2:
314 currentwp.attr.put("fix", "dgps");
315 break;
316 default:
317 break;
318 }
319 }
320 } else if(e[0].equals("$GPVTG")) {
321 // COURSE
322 accu = e[GPVTG.COURSE_REF.position];
323 if(accu.equals("T")) {
324 // other values than (T)rue are ignored
325 accu = e[GPVTG.COURSE.position];
326 if(!accu.equals("")) {
327 Double.parseDouble(accu);
328 currentwp.attr.put("course", accu);
329 }
330 }
331 // SPEED
332 accu = e[GPVTG.SPEED_KMH_UNIT.position];
333 if(accu.startsWith("K")) {
334 accu = e[GPVTG.SPEED_KMH.position];
335 if(!accu.equals("")) {
336 double speed = Double.parseDouble(accu);
337 speed /= 3.6; // speed in m/s
338 currentwp.attr.put("speed", Double.toString(speed));
339 }
340 }
341 } else if(e[0].equals("$GPGSA")) {
342 // vdop
343 accu=e[GPGSA.VDOP.position];
344 if(!accu.equals("")) {
345 Double.parseDouble(accu);
346 currentwp.attr.put("vdop", accu);
347 }
348 // hdop
349 accu=e[GPGSA.HDOP.position];
350 if(!accu.equals("")) {
351 Double.parseDouble(accu);
352 currentwp.attr.put("hdop", accu);
353 }
354 // pdop
355 accu=e[GPGSA.PDOP.position];
356 if(!accu.equals("")) {
357 Double.parseDouble(accu);
358 currentwp.attr.put("pdop", accu);
359 }
360 }
361 else if(e[0].equals("$GPRMC")) {
362 // coordinates
363 LatLon latLon = parseLatLon(
364 e[GPRMC.WIDTH_NORTH_NAME.position],
365 e[GPRMC.LENGTH_EAST_NAME.position],
366 e[GPRMC.WIDTH_NORTH.position],
367 e[GPRMC.LENGTH_EAST.position]
368 );
369 if(latLon==null) throw(null);
370 if((latLon.lat()==0.0) && (latLon.lon()==0.0)) {
371 ps.zero_coord++;
372 return false;
373 }
374 // time
375 currentDate = e[GPRMC.DATE.position];
376 String time = e[GPRMC.TIME.position];
377
378 Date d = RMCTIMEFMT.parse(currentDate+time, new ParsePosition(0));
379 if (d == null) throw(null);
380
381 if((ps.p_Time==null) || (currentwp==null) || !ps.p_Time.equals(time)) {
382 // this node is newer than the previous, create a new waypoint.
383 ps.p_Time=time;
384 currentwp = new WayPoint(latLon);
385 }
386 // time: this sentence has complete time so always use it.
387 String gpxdate = WayPoint.GPXTIMEFMT.format(d);
388 currentwp.attr.put("time", gpxdate);
389 // speed
390 accu = e[GPRMC.SPEED.position];
391 if(!accu.equals("") && !currentwp.attr.containsKey("speed")) {
392 double speed = Double.parseDouble(accu);
393 speed *= 0.514444444; // to m/s
394 currentwp.attr.put("speed", Double.toString(speed));
395 }
396 // course
397 accu = e[GPRMC.COURSE.position];
398 if(!accu.equals("") && !currentwp.attr.containsKey("course")) {
399 Double.parseDouble(accu);
400 currentwp.attr.put("course", accu);
401 }
402
403 // TODO fix?
404 // * Mode (A = autonom; D = differential; E = estimated; N = not valid; S
405 // * = simulated)
406 // *
407 // * @since NMEA 2.3
408 //
409 //MODE(12);
410 } else {
411 ps.unknown++;
412 return false;
413 }
414 ps.p_Date = currentDate;
415 if(ps.p_Wp != currentwp) {
416 if(ps.p_Wp!=null) {
417 ps.p_Wp.setTime();
418 }
419 ps.p_Wp = currentwp;
420 ps.waypoints.add(currentwp);
421 ps.success++;
422 return true;
423 }
424 return true;
425
426 } catch(Exception x) {
427 // out of bounds and such
428 //System.out.println("Malformed line: "+s.toString().trim());
429 ps.malformed++;
430 ps.p_Wp=null;
431 return false;
432 }
433 }
434
435 private LatLon parseLatLon(String ew, String ns, String dlat, String dlon)
436 throws NumberFormatException {
437 String widthNorth = dlat.trim();
438 String lengthEast = dlon.trim();
439
440 // return a zero latlon instead of null so it is logged as zero coordinate
441 // instead of malformed sentence
442 if(widthNorth.equals("")&&lengthEast.equals("")) return new LatLon(0.0,0.0);
443
444 // The format is xxDDLL.LLLL
445 // xx optional whitespace
446 // DD (int) degres
447 // LL.LLLL (double) latidude
448 int latdegsep = widthNorth.indexOf('.') - 2;
449 if (latdegsep < 0) return null;
450
451 int latdeg = Integer.parseInt(widthNorth.substring(0, latdegsep));
452 double latmin = Double.parseDouble(widthNorth.substring(latdegsep));
453 double lat = latdeg + latmin / 60;
454 if ("S".equals(ns)) {
455 if(!ew.equals("N")) return null;
456 lat = -lat;
457 }
458
459 int londegsep = lengthEast.indexOf('.') - 2;
460 if (londegsep < 0) return null;
461
462 int londeg = Integer.parseInt(lengthEast.substring(0, londegsep));
463 double lonmin = Double.parseDouble(lengthEast.substring(londegsep));
464 double lon = londeg + lonmin / 60;
465 if ("W".equals(ew)) {
466 if(!ew.equals("E")) return null;
467 lon = -lon;
468 }
469 return new LatLon(lat, lon);
470 }
471}
Note: See TracBrowser for help on using the repository browser.