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

Last change on this file since 1415 was 1415, checked in by stoecker, 17 years ago

applied patch #2185 by bruce89

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