Example #1
0
		public GPSFix(GPSFix fix)
		{
			posChanged = fix.posChanged;

			ecef_x = fix.ecef_x;
			ecef_y = fix.ecef_y;
			ecef_z = fix.ecef_z;

			m_LatitudeRadians = fix.m_LatitudeRadians;
			m_LongitudeRadians = fix.m_LongitudeRadians;
			m_AltitudeAboveMSL = fix.m_AltitudeAboveMSL;
			m_MSLAltitudeAboveWGS84 = fix.m_MSLAltitudeAboveWGS84;
			m_HeadingRadians = fix.m_HeadingRadians;

			m_HDOP = fix.m_HDOP;
			m_VDOP = fix.m_VDOP;
			m_EPH = fix.m_EPH;
			m_EPV = fix.m_EPV;
		
			FixType = fix.FixType;
			IsDifferential = fix.IsDifferential;
			UTCFixTime = fix.UTCFixTime;
			SpeedEast = fix.SpeedEast;
			SpeedNorth = fix.SpeedNorth;
			SpeedUp = fix.SpeedUp;
		}
Example #2
0
        public GPSFix(GPSFix fix)
        {
            posChanged = fix.posChanged;

            ecef_x = fix.ecef_x;
            ecef_y = fix.ecef_y;
            ecef_z = fix.ecef_z;

            m_LatitudeRadians       = fix.m_LatitudeRadians;
            m_LongitudeRadians      = fix.m_LongitudeRadians;
            m_AltitudeAboveMSL      = fix.m_AltitudeAboveMSL;
            m_MSLAltitudeAboveWGS84 = fix.m_MSLAltitudeAboveWGS84;
            m_HeadingRadians        = fix.m_HeadingRadians;

            m_HDOP = fix.m_HDOP;
            m_VDOP = fix.m_VDOP;
            m_EPH  = fix.m_EPH;
            m_EPV  = fix.m_EPV;

            FixType        = fix.FixType;
            IsDifferential = fix.IsDifferential;
            UTCFixTime     = fix.UTCFixTime;
            SpeedEast      = fix.SpeedEast;
            SpeedNorth     = fix.SpeedNorth;
            SpeedUp        = fix.SpeedUp;
        }
		// <summary>
		// Calculates the speed and heading of the specified current fix based on the distance and bearing to the
		// specified origin point, and the time taken to travel from the origin point to here. Elevation is not
		// taken into account in the geodesic distance calculation (it is assumed that elevation does not
		// significantly affect geodesic distance calculations, which may or may not be true), but elevation
		// differens are taken into account when calculating vertical speed. This probably needs looking at
		// in the future.
		// </summary>
		private void SetSpeedAndHeading(GPSFix current, GPSFix origin, out double distance)
		{
			// Check that we are dealing with valid fixes, and that the time difference between this fix and
			// the origin fix is positive.
			if ((origin.FixType == GPSFixTypes.Invalid) || (current.FixType == GPSFixTypes.Invalid))
				throw new Exception("Invalid current or origin fix for speed calculation.");
			if (current.UTCFixTime.CompareTo(origin.UTCFixTime) <= 0)
				throw new Exception("Negative or zero time difference from origin to current fix for speed calculation.");

			// Calculate the distance and bearing from the current point to the origin point.
			double bearing;
			GeoidUtils.GetDistanceAndBearing(current.LatitudeRadians, current.LongitudeRadians,
				origin.LatitudeRadians, origin.LongitudeRadians, out distance, out bearing);

			// Rotate the bearing 180 degress to get the heading.
			current.HeadingRadians = bearing >= 0 ? bearing - Math.PI : bearing + Math.PI;

			// Calculate the north and east components of the speed based on the distance, time difference
			// and heading.
			double seconds = current.UTCFixTime.Subtract(origin.UTCFixTime).TotalSeconds;
			double groundSpeed = distance / seconds;
			current.SpeedNorth = groundSpeed * Math.Cos(current.HeadingRadians);
			current.SpeedEast = groundSpeed * Math.Sin(current.HeadingRadians);

			// Calculate the vertical speed based on the elevation and time differences, but only if
			// both fixes are 3D.
			if ((current.FixType == GPSFixTypes.Fix3D) && (origin.FixType == GPSFixTypes.Fix3D))
				current.SpeedUp = (current.AltitudeAboveMSL - origin.AltitudeAboveMSL) / seconds;
			else
				current.SpeedUp = 0;
		}
		private void PublishFix(GPSFix fix, NMEASentenceType fixSource)
		{
			if (fixSource == fixTriggerSentence) 
			{
				if (haveGGA && haveRMC) 
				{
					// Output a combined fix if the partial fix is valid.
					if ((partialFix != null) && (partialFix.UTCFixTime == fix.UTCFixTime) &&
						(fixSource != partialFixSentenceType))
					{
						GPSFix fixRMC = fixSource == NMEASentenceType.RMC ? fix : partialFix;
						GPSFix fixGGA = fixSource == NMEASentenceType.GGA ? fix : partialFix;

						m_MostRecentGPSFix = fixGGA;
						m_MostRecentGPSFix.HeadingRadians = fixRMC.HeadingRadians;
						m_MostRecentGPSFix.SpeedEast = fixRMC.SpeedEast;
						m_MostRecentGPSFix.SpeedNorth = fixRMC.SpeedNorth;

						// Still need to calculate vertical speed. Sigh.
						CalculateSpeedAndHeading(m_MostRecentGPSFix, true);
					}
					partialFix = null;
					partialFixSentenceType = NMEASentenceType.Unknown;
				}
				else if (fixSource == NMEASentenceType.GGA)
				{
					// Don't have speed information, so calculate it.
					m_MostRecentGPSFix = fix;
					CalculateSpeedAndHeading(m_MostRecentGPSFix, false);
				}
				else if (fixSource == NMEASentenceType.RMC) 
				{
					// Publish the fix as is.
					m_MostRecentGPSFix = fix;
				}
			}
			else
			{
				// Save the fix until we hit the trigger sentence.
				partialFix = fix;
				partialFixSentenceType = fixSource;
			}
		}
		private void CalculateSpeedAndHeading(GPSFix fix, bool verticalOnly)
		{
			// Only calculate speed between valid fixes, and only save valid fixes for next time. This
			// allows the occassinal invalid fix in between valid fixes without disrupting the speed
			// calculations for the valid fixes (the last valid fix will hang around for a while).
			if (fix.FixType != GPSFixTypes.Invalid) 
			{
				double saveAltitude = fix.AltitudeAboveMSL;
				if (savedFix != null)
				{
					double seconds = fix.UTCFixTime.Subtract(savedFix.UTCFixTime).TotalSeconds;
					if ((seconds > 0) && (seconds <= savedFixValiditySeconds)) 
					{
						// Only calculate speed and heading if the total position change,
						// which may occur across multiple fixes, is greater than a
						// predefined threshold. We obtain the total position change by
						// not updating the position in the saved fix until the threshold
						// has been reached, at which point we adjust the saved fix to
						// the new position. Since we report speeds of 0 until the
						// threshold has been reached, we always calculate the speed from
						// the saved position from the time of the last fix, not the
						// original time of the saved fix, so that a speed * time
						// calculation will return the correct distance for this position
						// change.
						if (verticalOnly) 
						{
							// Calculate the vertical speed based on the elevation and time differences, but only if
							// both fixes are 3D.
							if ((fix.FixType == GPSFixTypes.Fix3D) && (savedFix.FixType == GPSFixTypes.Fix3D))
							{
								if (Math.Abs(fix.AltitudeAboveMSL - savedFix.AltitudeAboveMSL) <=
									movementThreshold)
								{
									// Change in altitude is insignificant, so set vertical speed to zero and ensure that
									// the altitude of the saved fix is preserved below.
									fix.SpeedUp = 0;
									saveAltitude = savedFix.AltitudeAboveMSL;
								}
								else
								{
									fix.SpeedUp = (fix.AltitudeAboveMSL - savedFix.AltitudeAboveMSL) / seconds;
								}
							}
							else
							{
								fix.SpeedUp = 0;
							}

							// Always update the saved fix.
							savedFix = null;
						}
						else
						{
							double distance;
							SetSpeedAndHeading(fix, savedFix, out distance);
							if (Math.Abs(fix.AltitudeAboveMSL - savedFix.AltitudeAboveMSL) <=
								movementThreshold)
							{
								// Change in altitude is insignificant, so set vertical speed to zero and ensure that
								// the altitude of the saved fix is preserved below.
								fix.SpeedUp = 0;
								saveAltitude = savedFix.AltitudeAboveMSL;
							}
							if (distance > movementThreshold)
							{
								// We have moved enough for a reliable speed calculation, so save the current fix
								// for next time.
								savedFix = null;
							}
							else
							{
								// Haven't moved enough to exclude the effects of position error, so set all
								// speeds to zero and update the time of the saved fix so that we can test against
								// the same saved position next time around.
								fix.SpeedEast = 0;
								fix.SpeedNorth = 0;
								fix.HeadingRadians = savedFix.HeadingRadians;
								savedFix.UTCFixTime = fix.UTCFixTime;
							}
						}
					}
					else
					{
						// Too long in between fixes, so invalidate the saved fix.
						savedFix = null;
					}
				}

				// Only save the current fix if we haven't updated the existing saved fix.
				if (savedFix == null)
					savedFix = new GPSFix(fix);
				savedFix.AltitudeAboveMSL = saveAltitude;
			}
		}
		private void ProcessRMC(string[] parts)
		{
			// Make sure we have the right number of parts.
			if (parts.Length < 6) 
			{
				CallOnLogEvent("Invalid RMC sentence.");
				return;
			}

			// Create a new fix object.
			GPSFix fix = new GPSFix();

			// Assume a 2D fix type unless the fix is invalid.
			fix.FixType = parts[2].Equals("A") ? GPSFixTypes.Fix2D : GPSFixTypes.Invalid;

			// Extract the UTC time, if present.
			fix.UTCFixTime = ParseNMEATimestamp(parts[1]);
			
			// Extract the latitude, converting the minutes portion to a fraction of a degree.
			if (parts[3].Equals(String.Empty)) 
			{
				// Invalidate the fix.
				fix.FixType = GPSFixTypes.Invalid;
			}
			else 
			{
				double temp = 0;
				if (! ParseDouble(parts[3], ref temp))
					throw new Exception("Invalid latitude: " + parts[3]);
				temp /= 100;
				double lat = (int)temp;
				lat = (lat + (temp - lat) / 0.6) * (parts[4].Equals("N") ? 1 : -1);
				fix.LatitudeDegrees = lat;
			}

			// Extract the longitude.
			if (parts[5].Equals(String.Empty)) 
			{
				// Invalidate the fix.
				fix.FixType = GPSFixTypes.Invalid;
			}
			else
			{
				double temp = 0;
				if (! ParseDouble(parts[5], ref temp))
					throw new Exception("Invalid longitude: " + parts[5]);
				temp /= 100;
				double lon = (int)temp;
				lon = (lon + (temp - lon) / 0.6) * (parts[6].Equals("E") ? 1 : -1);
				fix.LongitudeDegrees = lon;
			}

			// Extract the speed and heading. If parsing fails, the values will be left as 0.
			double speed = 0, track = 0;
			ParseDouble(parts[7], ref speed);
			ParseDouble(parts[8], ref track);
			speed *= 1852.0 / 3600;
			fix.HeadingDegrees = track;
			fix.SpeedNorth = speed * Math.Cos(fix.HeadingRadians);
			fix.SpeedEast = speed * Math.Sin(fix.HeadingRadians);

			// Fake an EPE.
			fix.EPH = 5;

			// Publish the fix.
			PublishFix(fix, NMEASentenceType.RMC);
		}
		private void ProcessGGA(string[] parts)
		{
			// Make sure we have the right number of parts.
			if (parts.Length != 15)
			{
				CallOnLogEvent("Invalid GGA sentence.");
				return;
			}

			// Create a new fix object.
			GPSFix fix = new GPSFix();

			// Determine the fix type from this sentence. We can't reliably uses the
			// presence of an altitude to distinguish between 2D and 3D fixes,
			// so assume that all fixes are 2D in the absence of GSA information.
			fix.FixType = (parts[6].Equals("1") || parts[6].Equals("2")) ?
				GPSFixTypes.Fix2D : GPSFixTypes.Invalid;

			// Extract the HDOP, leaving it set to zero if this fails.
			double hdop = 0;
			if (! parts[8].Equals(String.Empty)) 
				ParseDouble(parts[8], ref hdop);
			fix.HDOP = hdop;

			// Determine if we have a differential fix.
			fix.IsDifferential = parts[6].Equals("2");

			// Extract the UTC time, if present.
			fix.UTCFixTime = ParseNMEATimestamp(parts[1]);
			
			// Extract the latitude, converting the minutes portion to a fraction of a degree.
			if (parts[2].Equals(String.Empty)) 
			{
				// Invalidate the fix.
				fix.FixType = GPSFixTypes.Invalid;
			}
			else 
			{
				double temp = 0;
				if (! ParseDouble(parts[2], ref temp))
					throw new Exception("Invalid latitude: " + parts[2]);
				temp /= 100;
				double lat = (int)temp;
				lat = (lat + (temp - lat) / 0.6) * (parts[3].Equals("N") ? 1 : -1);
				fix.LatitudeDegrees = lat;
			}

			// Extract the longitude.
			if (parts[4].Equals(String.Empty)) 
			{
				// Invalidate the fix.
				fix.FixType = GPSFixTypes.Invalid;
			}
			else
			{
				double temp = 0;
				if (! ParseDouble(parts[4], ref temp))
					throw new Exception("Invalid longitude: " + parts[4]);
				temp /= 100;
				double lon = (int)temp;
				lon = (lon + (temp - lon) / 0.6) * (parts[5].Equals("E") ? 1 : -1);
				fix.LongitudeDegrees = lon;
			}

			// Check if we have an altitude value.
			if (! parts[9].Equals(String.Empty))
			{
				// Parse the altitude.
				double alt = 0;
				if (! ParseDouble(parts[9], ref alt))
					throw new Exception("Invalid altitude: " + parts[9]);
				switch (parts[10]) 
				{
					case "M":
						break;
					case "f":
						alt *= 0.3048;
						break;
					default:
						throw new Exception("Invalid unit of measurement: " + parts[10]);
				}
				fix.AltitudeAboveMSL = alt;

				// Extract the MSL height above WGS84. If not present, implies previous altitude
				// is relative to WGS84 and not MSL (for Garmin receivers, anyway).
				double mslAltitude = 0;
				if (parts[11].Equals(String.Empty))
				{
					// TODO: calculate correct MSLAltitudeAboveWGS84
					mslAltitude = 0;

					// Convert from Altitude above WGS84 to Altitude above MSL;
					fix.AltitudeAboveMSL -= mslAltitude;
				}
				else
				{
					if (! ParseDouble(parts[11], ref mslAltitude))
						throw new Exception("Invalid MSL altitude: " + parts[11]);
					switch (parts[12]) 
					{
						case "M":
							break;
						case "f":
							mslAltitude *= 0.3048;
							break;
						default:
							throw new Exception("Invalid unit of measurement: " + parts[12]);
					}
				}
				fix.MSLAltitudeAboveWGS84 = mslAltitude;
			}

			// Update the fix based on the contents of the last GSA sentence, if any.
			if ((gsaData != null) && (DateTime.Now.Subtract(gsaData.timeReceived).TotalSeconds < gsaDataValiditySeconds))
			{
				// Bump the fix type up to 3D if the GSA fix was 3D.
				if ((fix.FixType == GPSFixTypes.Fix2D) && (gsaData.fixType == GPSFixTypes.Fix3D))
					fix.FixType = GPSFixTypes.Fix3D;

				// Add the VDOP information.
				fix.VDOP = gsaData.VDOP;
			}

			// Publish the fix.
			PublishFix(fix, NMEASentenceType.GGA);
		}
		public void Reset()
		{
			readBuffer = "";

			partialFix = null;
			partialFixSentenceType = NMEASentenceType.Unknown;
			haveGGA = false;
			haveRMC = false;
			lastGGATimestamp = new DateTime(0);
			lastRMCTimestamp = new DateTime(0);
			fixTriggerSentence = NMEASentenceType.Unknown;

			savedFix = null;
			m_MostRecentGPSFix = null;

			gsaData = null;

			partialSatelliteVehicles = null;
			partialSatelliteVehicleIndex = 0;
			m_MostRecentSatelliteVehicles = null;

			lastGSVMessageNumber = 0;
		}
Example #9
0
		public IGPSFix GetGPSFix()
		{
			GPSFix fix = new GPSFix();
			GPSPVTDataType pvt;
			ushort err = GPSGetPVT(out pvt);
			if (err == 0)
			{
				// Convert the fix time.
				DateTime utc = DateTime.UtcNow;
				fix.UTCFixTime = new DateTime(utc.Year, utc.Month, utc.Day,
					(int)(pvt.time.seconds / 3600),
					(int)(pvt.time.seconds % 3600 / 60),
					(int)(pvt.time.seconds % 3600 % 60),
					(int)((double)pvt.time.fracSeconds / 4294967296 * 1000));

				// Determine the fix type.
				switch (pvt.status.fix)
				{
					case gpsFixInvalid:
					case gpsFixUnusable:
						fix.FixType = GPSFixTypes.Invalid;
						break;
					case gpsFix2D:
					case gpsFix2DDiff:
						fix.FixType = GPSFixTypes.Fix2D;
						fix.IsDifferential = pvt.status.fix == gpsFix2DDiff;
						break;
					case gpsFix3D:
					case gpsFix3DDiff:
						fix.FixType = GPSFixTypes.Fix3D;
						fix.IsDifferential = pvt.status.fix == gpsFix3DDiff;
						break;
				}

				// Populate the rest of the fields.
				fix.LatitudeRadians = pvt.position.lat * Math.PI / 2147483648;
				fix.LongitudeRadians = pvt.position.lon * Math.PI / 2147483648;
				fix.AltitudeAboveMSL = pvt.position.altMSL;
				fix.MSLAltitudeAboveWGS84 = pvt.position.altWGS84 - pvt.position.altMSL;
				fix.EPH = pvt.status.eph;
				fix.EPV = pvt.status.epv;
				fix.SpeedEast = pvt.velocity.east;
				fix.SpeedNorth = pvt.velocity.north;
				fix.SpeedUp = pvt.velocity.up;
				fix.HeadingRadians = Math.Atan2(fix.SpeedEast, fix.SpeedNorth);
			}
			else
			{
				fix.FixType = GPSFixTypes.Invalid;
			}
			return fix;
		}