Index: /trunk/src/org/openstreetmap/josm/actions/OpenFileAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/OpenFileAction.java	(revision 1166)
+++ /trunk/src/org/openstreetmap/josm/actions/OpenFileAction.java	(revision 1167)
@@ -119,4 +119,22 @@
 		}
     }
+	
+	private void showNmeaInfobox(boolean success, NmeaReader r) {
+		String msg = tr("Coordinates imported: ") + r.getNumberOfCoordinates() + "\n" +
+		tr("Malformed sentences: ") + r.getParserMalformed() + "\n" +
+		tr("Checksum errors: ") + r.getParserChecksumErrors() + "\n";
+		if(!success) // don't scare the user unneccessarily
+			msg += tr("Unknown sentences: ") + r.getParserUnknown() + "\n";
+		msg += tr("Zero coordinates: ") + r.getParserZeroCoordinates();
+		if(success) {	
+			JOptionPane.showMessageDialog(
+				Main.parent, msg,
+				tr("NMEA import success"),JOptionPane.INFORMATION_MESSAGE);
+		} else {
+			JOptionPane.showMessageDialog(
+				Main.parent, msg,
+				tr("NMEA import faliure!"),JOptionPane.ERROR_MESSAGE);
+		}
+	}
 
 	private void openFileAsNmea(File file) throws IOException, FileNotFoundException {
@@ -124,14 +142,16 @@
 		if (ExtensionFileFilter.filters[ExtensionFileFilter.NMEA].acceptName(fn)) {
 			NmeaReader r = new NmeaReader(new FileInputStream(file), file.getAbsoluteFile().getParentFile());
-			r.data.storageFile = file;
-			GpxLayer gpxLayer = new GpxLayer(r.data, fn);
-			Main.main.addLayer(gpxLayer);
-			if (Main.pref.getBoolean("marker.makeautomarkers", true)) {
-				MarkerLayer ml = new MarkerLayer(r.data, tr("Markers from {0}", fn), file, gpxLayer);
-				if (ml.data.size() > 0) {
-					Main.main.addLayer(ml);
+			if(r.getNumberOfCoordinates()>0) {
+				r.data.storageFile = file;
+				GpxLayer gpxLayer = new GpxLayer(r.data, fn);
+				Main.main.addLayer(gpxLayer);
+				if (Main.pref.getBoolean("marker.makeautomarkers", true)) {
+					MarkerLayer ml = new MarkerLayer(r.data, tr("Markers from {0}", fn), file, gpxLayer);
+					if (ml.data.size() > 0) {
+						Main.main.addLayer(ml);
+					}
 				}
 			}
-
+			showNmeaInfobox(r.getNumberOfCoordinates()>0, r);
 		} else {
 			throw new IllegalStateException();
Index: /trunk/src/org/openstreetmap/josm/data/gpx/WayPoint.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/gpx/WayPoint.java	(revision 1166)
+++ /trunk/src/org/openstreetmap/josm/data/gpx/WayPoint.java	(revision 1167)
@@ -35,10 +35,12 @@
 	 * Convert the time stamp of the waypoint into seconds from the epoch
 	 */
+	public final static SimpleDateFormat GPXTIMEFMT = 
+		new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); // ignore timezone
+	
 	public void setTime() {
 		if (! attr.containsKey("time")) {
 			return;
 		}
-		SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); // ignore timezone
-		Date d = f.parse(attr.get("time").toString(), new ParsePosition(0));
+		Date d = GPXTIMEFMT.parse(attr.get("time").toString(), new ParsePosition(0));
 		if (d != null /* parsing ok */) {
 			time = d.getTime() / 1000.0; /* ms => seconds */
Index: /trunk/src/org/openstreetmap/josm/io/NmeaReader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/NmeaReader.java	(revision 1166)
+++ /trunk/src/org/openstreetmap/josm/io/NmeaReader.java	(revision 1167)
@@ -8,7 +8,10 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Date;
 
 import org.openstreetmap.josm.data.coor.LatLon;
@@ -20,5 +23,5 @@
  * Read a nmea file. Based on information from
  * http://www.kowoma.de/gps/zusatzerklaerungen/NMEA.htm
- * 
+ *
  * @author cbrill
  */
@@ -33,5 +36,7 @@
 		GPGGA("$GPGGA"),
 		/** SA = satellites active. */
-		GPGSA("$GPGSA");
+		GPGSA("$GPGSA"),
+		/** Course over ground and ground speed */
+		GPVTG("$GPVTG");
 
 		private final String type;
@@ -50,5 +55,18 @@
 	}
 
-	private static final int TYPE = 0;
+	// GPVTG
+	public static enum GPVTG {
+		COURSE(1),COURSE_REF(2), // true course
+		COURSE_M(3), COURSE_M_REF(4), // magnetic course
+		SPEED_KN(5), SPEED_KN_UNIT(6), // speed in knots
+		SPEED_KMH(7), SPEED_KMH_UNIT(8), // speed in km/h
+		REST(9); // version-specific rest
+
+		public final int position;
+
+		GPVTG(int position) {
+			this.position = position;
+		}
+	}
 
 	// The following only applies to GPRMC
@@ -56,15 +74,13 @@
 		TIME(1),
 		/** Warning from the receiver (A = data ok, V = warning) */
-		RECEIVER_WARNING(2), 
-		WIDTH_NORTH(3), WIDTH_NORTH_NAME(4), 
-		LENGTH_EAST(5), LENGTH_EAST_NAME(6),
-		/** Speed in knots */
-		SPEED(7), COURSE(8), DATE(9),
-		/** magnetic declination */
-		MAGNETIC_DECLINATION(10), UNKNOWN(11),
+		RECEIVER_WARNING(2),
+		WIDTH_NORTH(3), WIDTH_NORTH_NAME(4), // Latitude, NS
+		LENGTH_EAST(5), LENGTH_EAST_NAME(6), // Longitude, EW
+		SPEED(7), COURSE(8), DATE(9),           // Speed in knots
+		MAGNETIC_DECLINATION(10), UNKNOWN(11),  // magnetic declination
 		/**
 		 * Mode (A = autonom; D = differential; E = estimated; N = not valid; S
 		 * = simulated)
-		 * 
+		 *
 		 * @since NMEA 2.3
 		 */
@@ -86,13 +102,11 @@
 		 */
 		QUALITY(6), SATELLITE_COUNT(7),
-		/** HDOP (horizontal dilution of precision) */
-		HDOP(8),
-		/** height above NN (above geoid) */
-		HEIGHT(9), HEIGHT_UNTIS(10),
-		/** height geoid - height ellipsoid (WGS84) */
-		HEIGHT_2(11), HEIGHT_2_UNTIS(12);
+		HDOP(8), // HDOP (horizontal dilution of precision)
+		HEIGHT(9), HEIGHT_UNTIS(10), // height above NN (above geoid)
+		HEIGHT_2(11), HEIGHT_2_UNTIS(12), // height geoid - height ellipsoid (WGS84)
+		GPS_AGE(13),// Age of differential GPS data
+		REF(14); // REF station
 
 		public final int position;
-
 		GPGGA(int position) {
 			this.position = position;
@@ -100,21 +114,15 @@
 	}
 
-	// The following only applies to GPGGA
 	public static enum GPGSA {
 		AUTOMATIC(1),
-		/** 1 = not fixed, 2 = 2D fixed, 3 = 3D fixed) */
-		FIX_TYPE(2),
+		FIX_TYPE(2), // 1 = not fixed, 2 = 2D fixed, 3 = 3D fixed)
 		// PRN numbers for max 12 satellites
-		PRN_1(3), PRN_2(4), PRN_3(5), PRN_4(6), PRN_5(7), PRN_6(8), PRN_7(9), PRN_8(
-		        10), PRN_9(11), PRN_10(12), PRN_11(13), PRN_12(14),
-		/** PDOP (precision) */
-		PDOP(15),
-		/** HDOP (horizontal precision) */
-		HDOP(16),
-		/** VDOP (vertical precision) */
-		VDOP(17), ;
+		PRN_1(3), PRN_2(4), PRN_3(5), PRN_4(6), PRN_5(7), PRN_6(8),
+		PRN_7(9), PRN_8(10), PRN_9(11), PRN_10(12), PRN_11(13), PRN_12(14),
+		PDOP(15),   // PDOP (precision)
+		HDOP(16),   // HDOP (horizontal precision)
+		VDOP(17), ; // VDOP (vertical precision)
 
 		public final int position;
-
 		GPGSA(int position) {
 			this.position = position;
@@ -124,51 +132,313 @@
 	public GpxData data;
 
+//  private final static SimpleDateFormat GGATIMEFMT =
+//      new SimpleDateFormat("HHmmss.SSS");
+	private final static SimpleDateFormat RMCTIMEFMT =
+		new SimpleDateFormat("ddMMyyHHmmss.SSS");
+
+	// functons for reading the error stats
+	public NMEAParserState ps;
+
+	public int getParserUnknown() {
+		return ps.unknown;
+	}
+	public int getParserZeroCoordinates() {
+		return ps.zero_coord;
+	}
+	public int getParserChecksumErrors() {
+		return ps.checksum_errors;
+	}
+	public int getParserMalformed() {
+		return ps.malformed;
+	}
+	public int getNumberOfCoordinates() {
+		return ps.success;
+	}
+
 	public NmeaReader(InputStream source, File relativeMarkerPath) {
+
+		// create the data tree
 		data = new GpxData();
 		GpxTrack currentTrack = new GpxTrack();
-		Collection<WayPoint> currentTrackSeg = new ArrayList<WayPoint>();
-		currentTrack.trackSegs.add(currentTrackSeg);
 		data.tracks.add(currentTrack);
 
-		BufferedReader rd;
-		String nmeaWithChecksum;
-
 		try {
-			rd = new BufferedReader(new InputStreamReader(source));
-			while ((nmeaWithChecksum = rd.readLine()) != null) {
-				String[] nmeaAndChecksum = nmeaWithChecksum.split("\\*");
-				String nmea = nmeaAndChecksum[0];
-				// XXX: No need for it: String checksum = nmeaAndChecksum[1];
-				String[] e = nmea.split(",");
-				if (e.length == 0) {
-				    continue;
-				}
-				if (NMEA_TYPE.GPRMC.equals(e[TYPE])) {
-					LatLon latLon = parseLatLon(e);
-					if (latLon == null) {
-						continue;
+			BufferedReader rd =
+				new BufferedReader(new InputStreamReader(source));
+
+			StringBuffer sb = new StringBuffer(1024);
+			int loopstart_char = rd.read();
+			if(loopstart_char == -1) {// zero size file
+				//TODO tell user about the problem?
+				return;
+			}
+			sb.append((char)loopstart_char);
+			ps = new NMEAParserState();
+			ps.p_Date="010100"; // TODO date problem
+			while(true) {
+				// don't load unparsable files completely to memory
+				if(sb.length()>=1020) sb.delete(0, sb.length()-1);
+				int c = rd.read();
+				if(c=='$') {
+					ParseNMEASentence(sb.toString(), ps);
+					sb.delete(0, sb.length());
+					sb.append('$');
+				} else if(c == -1) {
+					// EOF: add last WayPoint if it works out
+					ParseNMEASentence(sb.toString(),ps);
+					break;
+				} else sb.append((char)c);
+			}
+			rd.close();
+			Object[] wparr = ps.waypoints.toArray();
+			currentTrack.trackSegs.add(ps.waypoints);
+			data.recalculateBounds();
+
+		} catch (final IOException e) {
+			// TODO tell user about the problem?
+		}
+	}
+	private class NMEAParserState {
+		protected Collection<WayPoint> waypoints = new ArrayList<WayPoint>();
+		protected String p_Time;
+		protected String p_Date;
+		protected WayPoint p_Wp;
+
+		protected int success = 0; // number of successfully parsend sentences
+		protected int malformed = 0;
+		protected int checksum_errors = 0;
+		protected int unknown = 0;
+		protected int zero_coord = 0;
+	}
+
+	// Parses split up sentences into WayPoints which are stored
+	// in the collection in the NMEAParserState object.
+	// Returns true if the input made sence, false otherwise.
+	private boolean ParseNMEASentence(String s, NMEAParserState ps) {
+		try {
+			if(s.equals("")) throw(null);
+
+			// checksum check:
+			// the bytes between the $ and the * are xored;
+			// if there is no * or other meanities it will throw
+			// and result in a malformed packet.
+			String[] chkstrings = s.split("\\*");
+			byte[] chb = chkstrings[0].getBytes();
+			int chk=0;
+			for(int i = 1; i < chb.length; i++) chk ^= chb[i];
+			if(Integer.parseInt(chkstrings[1].substring(0,2),16) != chk) {
+				//System.out.println("Checksum error");
+				ps.checksum_errors++;
+				ps.p_Wp=null;
+				return false;
+			}
+			// now for the content
+			String[] e = chkstrings[0].split(",");
+			String accu;
+
+			WayPoint currentwp = ps.p_Wp;
+			String currentDate = ps.p_Date;
+
+			// handle the packet content
+			if(e[0].equals("$GPGGA")) {
+				// Position
+				LatLon latLon = parseLatLon(
+						e[GPGGA.LATITUDE_NAME.position],
+						e[GPGGA.LONGITUDE_NAME.position],
+						e[GPGGA.LATITUDE.position],
+						e[GPGGA.LONGITUDE.position]
+					);
+				if(latLon==null) throw(null); // malformed
+
+				if((latLon.lat()==0.0) && (latLon.lon()==0.0)) {
+					ps.zero_coord++;
+					return false;
+				}
+
+				// time
+				accu = e[GPGGA.TIME.position];
+				Date d = RMCTIMEFMT.parse(currentDate+accu, new ParsePosition(0));
+				if (d == null) throw(null); // malformed
+
+				if((ps.p_Time==null) || (currentwp==null) || !ps.p_Time.equals(accu)) {
+					// this node is newer than the previous, create a new waypoint.
+					// no matter if previous WayPoint was null, we got something
+					// better now.
+					ps.p_Time=accu;
+					currentwp = new WayPoint(latLon);
+				}
+				if(!currentwp.attr.containsKey("time")) {
+					// As this sentence has no complete time only use it
+					// if there is no time so far
+					String gpxdate = WayPoint.GPXTIMEFMT.format(d);
+					currentwp.attr.put("time", gpxdate);
+				}
+				// elevation
+				accu=e[GPGGA.HEIGHT_UNTIS.position];
+				if(accu.equals("M")) {
+						// Ignore heights that are not in meters for now
+						accu=e[GPGGA.HEIGHT.position];
+						if(!accu.equals("")) {
+						Double.parseDouble(accu);
+						// if it throws it's malformed; this should only happen if the
+						// device sends nonstandard data.
+						if(!accu.equals("")) currentwp.attr.put("ele", accu);
 					}
-					WayPoint currentWayPoint = new WayPoint(latLon);
-					currentTrackSeg.add(currentWayPoint);
-				}
-			}
-			rd.close();
-		} catch (final IOException e) {
-			System.out.println("Error reading file");
-		}
-
-	}
-
-	private LatLon parseLatLon(String[] e) throws NumberFormatException {
-	    // If the array looks bogus don't try to get valuable information from it
-	    // But remember that the array is stripped of checksum and GPRMC is only 12 elements and split strips empty trailing elements  
-	    if (e.length < 10) {
-	        return null;
-	    }
-        String widthNorth = e[GPRMC.WIDTH_NORTH.position].trim();
-		String lengthEast = e[GPRMC.LENGTH_EAST.position].trim();
-		if ("".equals(widthNorth) || "".equals(lengthEast)) {
-			return null;
-		}
+				}
+				// number of sattelites
+				accu=e[GPGGA.SATELLITE_COUNT.position];
+				int sat = 0;
+				if(!accu.equals("")) {
+					sat = Integer.parseInt(accu);
+					currentwp.attr.put("sat", accu);
+				}
+				// h-dilution
+				accu=e[GPGGA.HDOP.position];
+				if(!accu.equals("")) {
+					Double.parseDouble(accu);
+					currentwp.attr.put("hdop", accu);
+				}
+				// fix
+				accu=e[GPGGA.QUALITY.position];
+				if(!accu.equals("")) {
+					int fixtype = Integer.parseInt(accu);
+					switch(fixtype) {
+					case 0:
+						currentwp.attr.put("fix", "none");
+						break;
+					case 1:
+						if(sat < 4) currentwp.attr.put("fix", "2d");
+						else currentwp.attr.put("fix", "3d");
+						break;
+					case 2:
+						currentwp.attr.put("fix", "dgps");
+						break;
+					default:
+						break;
+					}
+				}
+			} else if(e[0].equals("$GPVTG")) {
+				// COURSE
+				accu = e[GPVTG.COURSE_REF.position];
+				if(accu.equals("T")) {
+					// other values than (T)rue are ignored
+					accu = e[GPVTG.COURSE.position];
+					if(!accu.equals("")) {
+						Double.parseDouble(accu);
+						currentwp.attr.put("course", accu);
+					}
+				}
+				// SPEED
+				accu = e[GPVTG.SPEED_KMH_UNIT.position];
+				if(accu.startsWith("K")) {
+					accu = e[GPVTG.SPEED_KMH.position];
+					if(!accu.equals("")) {
+						double speed = Double.parseDouble(accu);
+						speed /= 3.6; // speed in m/s
+						currentwp.attr.put("speed", Double.toString(speed));
+					}
+				}
+			} else if(e[0].equals("$GPGSA")) {
+				// vdop
+				accu=e[GPGSA.VDOP.position];
+				if(!accu.equals("")) {
+					Double.parseDouble(accu);
+					currentwp.attr.put("vdop", accu);
+				}
+				// hdop
+				accu=e[GPGSA.HDOP.position];
+				if(!accu.equals("")) {
+					Double.parseDouble(accu);
+					currentwp.attr.put("hdop", accu);
+				}
+				// pdop
+				accu=e[GPGSA.PDOP.position];
+				if(!accu.equals("")) {
+					Double.parseDouble(accu);
+					currentwp.attr.put("pdop", accu);
+				}
+			}
+			else if(e[0].equals("$GPRMC")) {
+				// coordinates
+				LatLon latLon = parseLatLon(
+						e[GPRMC.WIDTH_NORTH_NAME.position],
+						e[GPRMC.LENGTH_EAST_NAME.position],
+						e[GPRMC.WIDTH_NORTH.position],
+						e[GPRMC.LENGTH_EAST.position]
+					);
+				if(latLon==null) throw(null);
+				if((latLon.lat()==0.0) && (latLon.lon()==0.0)) {
+					ps.zero_coord++;
+					return false;
+				}
+				// time
+				currentDate = e[GPRMC.DATE.position];
+				String time = e[GPRMC.TIME.position];
+
+				Date d = RMCTIMEFMT.parse(currentDate+time, new ParsePosition(0));
+				if (d == null) throw(null);
+
+				if((ps.p_Time==null) || (currentwp==null) || !ps.p_Time.equals(time)) {
+					// this node is newer than the previous, create a new waypoint.
+					ps.p_Time=time;
+					currentwp = new WayPoint(latLon);
+				}
+				// time: this sentence has complete time so always use it.
+				String gpxdate = WayPoint.GPXTIMEFMT.format(d);
+				currentwp.attr.put("time", gpxdate);
+				// speed
+				accu = e[GPRMC.SPEED.position];
+				if(!accu.equals("") && !currentwp.attr.containsKey("speed")) {
+					double speed = Double.parseDouble(accu);
+					speed *= 0.514444444; // to m/s
+					currentwp.attr.put("speed", Double.toString(speed));
+				}
+				// course
+				accu = e[GPRMC.COURSE.position];
+				if(!accu.equals("") && !currentwp.attr.containsKey("course")) {
+					Double.parseDouble(accu);
+					currentwp.attr.put("course", accu);
+				}
+
+				// TODO fix?
+				// * Mode (A = autonom; D = differential; E = estimated; N = not valid; S
+				// * = simulated)
+				// *
+				// * @since NMEA 2.3
+				//
+				//MODE(12);
+			} else {
+				ps.unknown++;
+				return false;
+			}
+			ps.p_Date = currentDate;
+			if(ps.p_Wp != currentwp) {
+				if(ps.p_Wp!=null) {
+					ps.p_Wp.setTime();
+				}
+				ps.p_Wp = currentwp;
+				ps.waypoints.add(currentwp);
+				ps.success++;
+				return true;
+			}
+			return true;
+
+		} catch(Exception x) {
+			// out of bounds and such
+			//System.out.println("Malformed line: "+s.toString().trim());
+			ps.malformed++;
+			ps.p_Wp=null;
+			return false;
+		}
+	}
+
+	private LatLon parseLatLon(String ew, String ns, String dlat, String dlon)
+		throws NumberFormatException {
+		String widthNorth = dlat.trim();
+		String lengthEast = dlon.trim();
+
+		// return a zero latlon instead of null so it is logged as zero coordinate
+		// instead of malformed sentence
+		if(widthNorth.equals("")&&lengthEast.equals("")) return new LatLon(0.0,0.0);
 
 		// The format is xxDDLL.LLLL
@@ -177,25 +447,24 @@
 		// LL.LLLL (double) latidude
 		int latdegsep = widthNorth.indexOf('.') - 2;
-		if (latdegsep < 0) {
-			return null;
-		}
+		if (latdegsep < 0) return null;
+
 		int latdeg = Integer.parseInt(widthNorth.substring(0, latdegsep));
 		double latmin = Double.parseDouble(widthNorth.substring(latdegsep));
 		double lat = latdeg + latmin / 60;
-		if ("S".equals(e[GPRMC.WIDTH_NORTH_NAME.position])) {
+		if ("S".equals(ns)) {
+			if(!ew.equals("N")) return null;
 			lat = -lat;
-		}	
+		}
 
 		int londegsep = lengthEast.indexOf('.') - 2;
-		if (londegsep < 0) {
-			return null;
-		}
+		if (londegsep < 0) return null;
+
 		int londeg = Integer.parseInt(lengthEast.substring(0, londegsep));
 		double lonmin = Double.parseDouble(lengthEast.substring(londegsep));
 		double lon = londeg + lonmin / 60;
-		if ("W".equals(e[GPRMC.LENGTH_EAST_NAME.position])) {
+		if ("W".equals(ew)) {
+			if(!ew.equals("E")) return null;
 			lon = -lon;
 		}
-		
 		return new LatLon(lat, lon);
 	}
