/// <summary> /// find difference between current heading and course heading /// </summary> /// <param name="targetMark"></param> /// <param name="previousMark"></param> /// <param name="current"></param> /// <param name="previous"></param> /// <returns></returns> private double RelativeAngleToCourse(Mark targetMark, Mark previousMark, CoordinatePoint current, CoordinatePoint previous) { if (previousMark != null && targetMark != null) { float courseAngle = (float)AngleUtilities.FindAngle(targetMark.Location.Project(), previousMark.Location.Project()); float boatAngle = (float)AngleUtilities.FindAngle(previous.Project(), current.Project()); ; return AngleUtilities.AngleDifference(courseAngle, boatAngle); } else { return 0; } }
/// <summary> /// calculate vmg from marks /// </summary> /// <param name="targetMark"></param> /// <param name="previousMark"></param> /// <param name="current"></param> /// <param name="previous"></param> /// <param name="speed"></param> /// <returns></returns> private double VelocityMadeGood(Mark targetMark, Mark previousMark, CoordinatePoint current, CoordinatePoint previous,double speed) { return Math.Cos(Math.Abs(RelativeAngleToCourse(targetMark,previousMark,current,previous))) * speed; }
/// <inheritdoc /> public void SetMarkLocation(MarkType markType) { if(_state.Course ==null || !(_state.Course is CourseByMarks)) { _state.Course = new CourseByMarks(); } if (_state.Location != null) { var course = _state.Course as CourseByMarks; if (course.Marks == null) { course.Marks = new List<Mark>(); } if (!course.Marks.Any()) { var mark = new Mark() { MarkType = markType, CaptureMethod = MarkCaptureMethod.Location, Location = _state.Location }; course.Marks.Add(mark); State.TargetMark = mark; } else if (_state.TargetMark != null && _state.TargetMark.MarkType == markType) { _state.TargetMark.CaptureMethod = MarkCaptureMethod.Location; _state.TargetMark.Location = _state.Location; } else if (State.TargetMark != null && _state.TargetMark.MarkType != markType) { var mark = new Mark() { MarkType = markType, CaptureMethod = MarkCaptureMethod.Location, Location = _state.Location }; course.Marks.Add(mark); //State.TargetMark = mark; } else { _logger.Error("User set mark location for " + markType + " but unsure what to do with it"); } } }
/// <inheritdoc /> public void ProcessMarkRoundings() { if (_state.Course is CourseByMarks) { var course = _state.Course as CourseByMarks; //if the race just started, set the line if (DetectRaceStart ()) { var line = course.Marks.FirstOrDefault (x => x.MarkType == MarkType.Line); if (line == null) { line = new Mark () { MarkType = MarkType.Line, CaptureMethod = MarkCaptureMethod.Location, Location = _state.Location }; course.Marks.Insert (0, line); } _state.PreviousMark = line; if (course.Marks.Any (x => x.MarkType == MarkType.Windward)) { State.TargetMark = course.Marks.Where (x => x.MarkType == MarkType.Windward).Last (); } else { State.TargetMark = null; } } else if (_state.StartTime.HasValue && _state.BestTime > _state.StartTime && _state.TargetMark != null && _state.TargetMark.Location != null && _autoRoundMarkDistanceMeters.HasValue) { var nextMark = GetNextMark (_state.TargetMark); if (nextMark != null) { var distanceToMark = CoordinatePoint.HaversineDistance (_state.Location, _state.TargetMark.Location); if (distanceToMark < _autoRoundMarkDistanceMeters) { _logger.Info ("Distance to " + _state.TargetMark.MarkType + " is " + string.Format ("{0:0.0}m") + ", advancing to next mark"); NextMark (); } } } } else { DetectRaceStart (); } }
/// <summary> /// find the mark that is next in the course based on the current mark /// </summary> /// <param name="currentTargetMark">the mark that is prior to the one youre looking for</param> /// <returns>the the mark after the one specified as current</returns> private Mark GetNextMark(Mark currentTargetMark) { if (_state.Course is CourseByMarks) { var course = _state.Course as CourseByMarks; if (currentTargetMark == null) { return course.Marks.LastOrDefault(); } else if (currentTargetMark.MarkType == MarkType.Line) { return course.Marks.Where(x => x.MarkType == MarkType.Windward).LastOrDefault(); } else if (currentTargetMark.MarkType == MarkType.Windward) { return course.Marks.Where(x => x.MarkType == MarkType.Leeward).LastOrDefault(); } else if (currentTargetMark.MarkType == MarkType.Leeward) { return course.Marks.Where(x => x.MarkType == MarkType.Windward).LastOrDefault(); } else { throw new InvalidOperationException("Unknown condition selecting next mark"); } } else { return null; } }
/// <inheritdoc /> public void SetMarkBearing(MarkType markType, double bearing, bool magneticBearing) { if(markType==MarkType.Course) { if (_state.Course == null || !(_state.Course is CourseByAngle)) { _state.Course = new CourseByAngle(); if(magneticBearing) { if(_state.StateValues.ContainsKey(StateValue.MagneticDeviation)) { (_state.Course as CourseByAngle).CourseAngle = bearing + _state.StateValues[StateValue.MagneticDeviation]; } else { _logger.Error("Cannot set course angle using magnetic bearing without magnetic deviation!"); return; } } else { (_state.Course as CourseByAngle).CourseAngle = bearing; } } } else if (_state.Location != null) { if (_state.Course == null || !(_state.Course is CourseByMarks)) { _state.Course = new CourseByMarks(); } Bearing fullBearing = new Bearing() { Location = _state.Location, RecordedAt = _state.BestTime, CompassHeading = bearing }; //compensate for magnetic deviation if (magneticBearing) { if (_state.StateValues.ContainsKey(StateValue.MagneticDeviation)) { fullBearing.CompassHeading = fullBearing.CompassHeading + _state.StateValues[StateValue.MagneticDeviation]; } else { _logger.Error("Cannot calculate mark location using magnetic bearing without magnetic deviation!"); return; } } _logger.Info(string.Format("Received bearing for {0} of {1:0.00} ({2:0.00} true) from {3},{4}, altitude {5}, deviation {6}", markType, bearing, fullBearing.CompassHeading, fullBearing.Location.Latitude.Value, fullBearing.Location.Longitude.Value, _state.StateValues[StateValue.AltitudeInMeters], _state.StateValues[StateValue.MagneticDeviation])); Mark mark; var course = _state.Course as CourseByMarks; if (!course.Marks.Any(x => x.MarkType == markType)) { mark = new Mark() { MarkType = markType, CaptureMethod = MarkCaptureMethod.Bearing, Location = null }; mark.Bearings = new List<Bearing>(); mark.Bearings.Add(fullBearing); course.Marks.Add(mark); State.TargetMark = mark; } else { mark = course.Marks.Where(x => x.MarkType == markType).Last(); mark.Bearings.Add(fullBearing); } if (mark.Bearings.Count > 1 && mark.CaptureMethod == MarkCaptureMethod.Bearing) { var bearing1 = mark.Bearings[mark.Bearings.Count - 2]; var bearing2 = mark.Bearings[mark.Bearings.Count - 1]; var location = CoordinatePointUtilities.FindIntersection(bearing1.Location, bearing1.CompassHeading, bearing2.Location, bearing2.CompassHeading); mark.Location = location; _logger.Info(string.Format("Calculated new location of {0} via bearings to be {1},{2}", markType, mark.Location.Latitude.Value, mark.Location.Longitude.Value)); //TODO, if there's more than 2, do we average down? } } }