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

Revision 5108, 17.5 KB checked in by xeen, 2 months ago (diff)

remove superfluous calls to recalculateBounds() and add clarifying comment.

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