// Get the angle from the given control index to the next control. public static double ComputeAngleOut(EventDB eventDB, CourseView courseView, int controlIndex) { PointF pt1 = eventDB.GetControl(courseView.ControlViews[controlIndex].controlId).location; // Get index of next control. int nextControlIndex = courseView.GetNextControl(controlIndex); if (nextControlIndex < 0) return double.NaN; // By default, the location of the next control is the direction. PointF pt2 = eventDB.GetControl(courseView.ControlViews[nextControlIndex].controlId).location; // If there is a custom leg, then use the location of the first bend instead. Id<Leg> legId = QueryEvent.FindLeg(eventDB, courseView.ControlViews[controlIndex].controlId, courseView.ControlViews[nextControlIndex].controlId); if (legId.IsNotNone) { Leg leg = eventDB.GetLeg(legId); if (leg.bends != null && leg.bends.Length > 0) pt2 = leg.bends[0]; } return Math.Atan2(pt2.Y - pt1.Y, pt2.X - pt1.X); }
// Find the SymPath of the path between controls, taking any bends into account. If no bends, the path is just the // simple path between the controls. public static SymPath GetLegPath(EventDB eventDB, Id<ControlPoint> controlId1, Id<ControlPoint> controlId2, Id<Leg> legId) { PointF location1 = eventDB.GetControl(controlId1).location; PointF location2 = eventDB.GetControl(controlId2).location; if (legId.IsNotNone) { Leg leg = eventDB.GetLeg(legId); Debug.Assert(leg.controlId1 == controlId1 && leg.controlId2 == controlId2); if (leg.bends != null) { List<PointF> points = new List<PointF>(); points.Add(location1); points.AddRange(leg.bends); points.Add(location2); return new SymPath(points.ToArray()); } } // No bends. return new SymPath(new PointF[] { location1, location2 }); }
// Find the start/stop gaps fort he leg from controlId1 to controlsId2. The legId, if not none, must be the correct leg id. public static LegGap[] GetLegGaps(EventDB eventDB, Id<ControlPoint> controlId1, Id<ControlPoint> controlId2, Id<Leg> legId) { if (legId.IsNotNone) { Leg leg = eventDB.GetLeg(legId); Debug.Assert(leg.controlId1 == controlId1 && leg.controlId2 == controlId2); return (leg.gaps == null) ? null : (LegGap[])leg.gaps.Clone(); } else { return null; } }
// Find the kind of flagging for the leg from controlId1 to controlIs2. The legId, if not none, must be the correct leg id. public static FlaggingKind GetLegFlagging(EventDB eventDB, Id<ControlPoint> controlId1, Id<ControlPoint> controlId2, Id<Leg> legId) { FlaggingKind flagging = FlaggingKind.None; if (legId.IsNotNone) { Leg leg = eventDB.GetLeg(legId); Debug.Assert(leg.controlId1 == controlId1 && leg.controlId2 == controlId2); flagging = leg.flagging; } ControlPoint control2 = eventDB.GetControl(controlId2); if (control2.kind == ControlPointKind.Finish && control2.symbolIds[0] == "14.1") flagging = FlaggingKind.All; if (control2.kind == ControlPointKind.MapExchange) flagging = FlaggingKind.All; return flagging; }
// Find a leg object, if one exists, between the two controls. public static Id<Leg> FindLeg(EventDB eventDB, Id<ControlPoint> controlId1, Id<ControlPoint> controlId2) { // Go through all the legs to find one that matches. foreach (Id<Leg> legId in eventDB.AllLegIds) { Leg leg = eventDB.GetLeg(legId); if (leg.controlId1 == controlId1 && leg.controlId2 == controlId2) { return legId; } } // Didn't find it. return Id<Leg>.None; }
// Similar to ComputeLegLength. However, if the leg is flagged partially, only the length of the flagged portion is returned. // Note: if the flagging is NONE, this still returns the length of the whole leg! public static float ComputeFlaggedLegLength(EventDB eventDB, Id<ControlPoint> controlId1, Id<ControlPoint> controlId2, Id<Leg> legId) { PointF location1 = eventDB.GetControl(controlId1).location; PointF location2 = eventDB.GetControl(controlId2).location; PointF[] bends = null; Leg leg = null; if (legId.IsNotNone) { leg = eventDB.GetLeg(legId); Debug.Assert(leg.controlId1 == controlId1 && leg.controlId2 == controlId2); bends = leg.bends; } if (bends == null) { return (float)((eventDB.GetEvent().mapScale * Geometry.Distance(location1, location2)) / 1000.0); } else { List<PointF> points = new List<PointF>(); int bendIndexStart, bendIndexStop; points.Add(location1); points.AddRange(bends); points.Add(location2); // Which part is flagged? if (leg.flagging == FlaggingKind.Begin) { bendIndexStart = 0; bendIndexStop = points.IndexOf(leg.flagStartStop); } else if (leg.flagging == FlaggingKind.End) { bendIndexStart = points.IndexOf(leg.flagStartStop); bendIndexStop = points.Count - 1; } else { bendIndexStart = 0; bendIndexStop = points.Count - 1; } double dist = 0; for (int i = bendIndexStart + 1; i <= bendIndexStop; ++i) dist += Geometry.Distance(points[i - 1], points[i]); return (float)((eventDB.GetEvent().mapScale * dist) / 1000.0); } }
// Create the objects associated with the leg from controlView1 to controlView2. Could be multiple because // a leg may be partly flagged, and so forth. Gaps do not create separate course objects. private static CourseObj[] CreateLeg(EventDB eventDB, float scaleRatio, CourseAppearance appearance, Id<CourseControl> courseControlId1, CourseView.ControlView controlView1, CourseView.ControlView controlView2, Id<Leg> legId) { ControlPoint control1 = eventDB.GetControl(controlView1.controlId); ControlPoint control2 = eventDB.GetControl(controlView2.controlId); Leg leg = (legId.IsNotNone) ? eventDB.GetLeg(legId) : null; List<SymPath> paths = new List<SymPath>(); // paths for each segment of the leg. List<LegGap[]> gapsList = new List<LegGap[]>(); // gaps for each segment of the leg. List<bool> isFlagged = new List<bool>(); // indicates if each segment is flagged or not. LegGap[] gaps; // What kind of gaps are present? Null array if none // Get the path of the line, and the gaps. SymPath legPath = GetLegPath(eventDB, control1.location, controlView1.hiddenControl ? ControlPointKind.None : control1.kind, controlView1.controlId, control2.location, controlView2.hiddenControl ? ControlPointKind.None : control2.kind, controlView2.controlId, scaleRatio, appearance, out gaps); if (legPath == null) return null; // What kind of flagging does this leg have (none/full/begin/end)? FlaggingKind flagging = QueryEvent.GetLegFlagging(eventDB, controlView1.controlId, controlView2.controlId, legId); // Based on flagging kind, set up the paths/isFlagged lists. Add in gaps as part of it. if (flagging == FlaggingKind.Begin || flagging == FlaggingKind.End) { // Flagging is partial. We need to split the path into two. SymPath beginPath, endPath; legPath.Split(leg.flagStartStop, out beginPath, out endPath); paths.Add(beginPath); gapsList.Add(gaps); isFlagged.Add(flagging == FlaggingKind.Begin); // Update gaps for the end part. if (gaps != null) { gaps = (LegGap[]) gaps.Clone(); for (int i = 0; i < gaps.Length; ++i) gaps[i].distanceFromStart -= beginPath.Length; } paths.Add(endPath); gapsList.Add(gaps); isFlagged.Add(flagging == FlaggingKind.End); } else { // flagging is not partial. A single path is OK. paths.Add(legPath); gapsList.Add(gaps); isFlagged.Add(flagging == FlaggingKind.All); } // Create course objects for this leg from the paths/isFlagged lists. CourseObj[] objs = new CourseObj[paths.Count]; for (int i = 0; i < paths.Count; ++i) { if (isFlagged[i]) objs[i] = new FlaggedLegCourseObj(controlView1.controlId, courseControlId1, controlView2.courseControlIds[0], scaleRatio, appearance, paths[i], gapsList[i]); else objs[i] = new LegCourseObj(controlView1.controlId, courseControlId1, controlView2.courseControlIds[0], scaleRatio, appearance, paths[i], gapsList[i]); } return objs; }
// Create a path from pt1 to pt2, with a radius aroudn the points correct for the given control kind. If the leg would // be of zero length, return null. The controlIds for the start and end points are optional -- if supplied, they are used // to deal with bends and gaps. If either is None, then the legs don't use bends or gaps. Returns the gaps to used // with the radius subtracted from them. public static SymPath GetLegPath(EventDB eventDB, PointF pt1, ControlPointKind kind1, Id<ControlPoint> controlId1, PointF pt2, ControlPointKind kind2, Id<ControlPoint> controlId2, float scaleRatio, CourseAppearance appearance, out LegGap[] gaps) { PointF[] bends = null; gaps = null; // Get bends and gaps if controls were supplied. if (controlId1.IsNotNone && controlId2.IsNotNone) { Id<Leg> legId = QueryEvent.FindLeg(eventDB, controlId1, controlId2); Leg leg = (legId.IsNotNone) ? eventDB.GetLeg(legId) : null; // Get the path of the line. if (leg != null) { bends = leg.bends; gaps = QueryEvent.GetLegGaps(eventDB, controlId1, controlId2); } } return GetLegPath(pt1, GetLegRadius(kind1, scaleRatio, appearance), pt2, GetLegRadius(kind2, scaleRatio, appearance), bends, gaps); }
// Change the flagging associated with a leg. If changing to Begin/End flagging, then a bend will be introduced if no bends currently exist // in the leg. If the leg ends in the finish, the finish symbol may be changed to match if appropriate. public static void ChangeFlagging(EventDB eventDB, Id<ControlPoint> controlId1, Id<ControlPoint> controlId2, FlaggingKind flagging) { ControlPoint control1 = eventDB.GetControl(controlId1); ControlPoint control2 = eventDB.GetControl(controlId2); if (control2.kind == ControlPointKind.Finish && flagging == FlaggingKind.All) { // If the leg ends in the finish control, we can set all flagging by just changing the finish control symbol. ChangeDescriptionSymbol(eventDB, controlId2, 0, "14.1"); return; } // We need a leg object. Create a new one or get the existing one. Id<Leg> legId = QueryEvent.FindLeg(eventDB, controlId1, controlId2); Leg leg; if (legId.IsNone) leg = new Leg(controlId1, controlId2); else leg = (Leg) eventDB.GetLeg(legId).Clone(); // Set the flagging kind. leg.flagging = flagging; if (flagging == FlaggingKind.Begin || flagging == FlaggingKind.End) { // These kinds of flagging require a bend in the flaggingStartStop field. if (leg.bends != null && leg.bends.Length > 0) { // Already have a bend we can use. leg.flagStartStop = (flagging == FlaggingKind.Begin) ? leg.bends[leg.bends.Length - 1] : leg.bends[0]; } else { // Create a bend half-way along the leg. leg.flagStartStop = new PointF((control1.location.X + control2.location.X) / 2, (control1.location.Y + control2.location.Y) / 2); leg.bends = new PointF[] { leg.flagStartStop }; } } // Update the leg object. if (legId.IsNone) eventDB.AddLeg(leg); else { if (leg.IsVacuous()) eventDB.RemoveLeg(legId); else eventDB.ReplaceLeg(legId, leg); } // Update the finish control symbol if reasonable. if (control2.kind == ControlPointKind.Finish) { // Update the finish control symbol. if ((flagging == FlaggingKind.None || flagging == FlaggingKind.Begin) && control2.symbolIds[0] == "14.1") { // Remove the "flagged from last control symbol" and change it to "no flagging". ChangeDescriptionSymbol(eventDB, controlId2, 0, "14.3"); } else if (flagging == FlaggingKind.End) { // If partial flagging on the end part of the leg, change the symbol to finish funnel. ChangeDescriptionSymbol(eventDB, controlId2, 0, "14.2"); } } }
// Change the gaps associated with a leg. public static void ChangeLegGaps(EventDB eventDB, Id<ControlPoint> controlId1, Id<ControlPoint> controlId2, LegGap[] newGaps) { // Get the leg object for this leg. Create one if needed. Id<Leg> legId = QueryEvent.FindLeg(eventDB, controlId1, controlId2); Leg leg; if (legId.IsNone) leg = new Leg(controlId1, controlId2); else leg = (Leg) eventDB.GetLeg(legId).Clone(); // Change the gaps. leg.gaps = (newGaps == null) ? null : (LegGap[]) newGaps.Clone(); // Write the change leg object to the event DB. If the leg is vacuous, could involve removing the leg. if (leg.IsVacuous()) { if (legId.IsNotNone) eventDB.RemoveLeg(legId); } else { if (legId.IsNone) eventDB.AddLeg(leg); else eventDB.ReplaceLeg(legId, leg); } }
// Change the location of a control. Used when dragging a control to a new location, for example. public static void ChangeControlLocation(EventDB eventDB, Id<ControlPoint> controlId, PointF newLocation) { // Check to see if any legs exist that include this control, and if any of those legs have gaps. List<LegGapChange> legGapChangeList = new List<LegGapChange>(); foreach (Id<Leg> legId in eventDB.AllLegIds) { Leg leg = eventDB.GetLeg(legId); if ((leg.controlId1 == controlId || leg.controlId2 == controlId) && leg.gaps != null) legGapChangeList.Add(new LegGapChange(leg.controlId1, leg.controlId2, QueryEvent.GetLegPath(eventDB, leg.controlId1, leg.controlId2, legId))); } // Move the control. ControlPoint control = eventDB.GetControl(controlId); control = (ControlPoint) control.Clone(); control.location = newLocation; eventDB.ReplaceControlPoint(controlId, control); // If there are any leg gaps that need to be repositioned, do that. if (legGapChangeList.Count > 0) { foreach (LegGapChange legGapChange in legGapChangeList) { Id<Leg> legId = QueryEvent.FindLeg(eventDB, legGapChange.controlId1, legGapChange.controlId2); if (legId.IsNotNone) { Leg leg = (Leg) eventDB.GetLeg(legId).Clone(); SymPath newPath = QueryEvent.GetLegPath(eventDB, legGapChange.controlId1, legGapChange.controlId2, legId); LegGap[] newGaps = LegGap.MoveGapsToNewPath(leg.gaps, legGapChange.legPath, newPath); ChangeEvent.ChangeLegGaps(eventDB, legGapChange.controlId1, legGapChange.controlId2, newGaps); } } } }
// Add a bend to a leg. public static void AddLegBend(EventDB eventDB, Id<ControlPoint> controlId1, Id<ControlPoint> controlId2, PointF newBend) { // Get the leg object for this leg. Create one if needed. Id<Leg> legId = QueryEvent.FindLeg(eventDB, controlId1, controlId2); Leg leg; if (legId.IsNone) leg = new Leg(controlId1, controlId2); else leg = (Leg) eventDB.GetLeg(legId).Clone(); SymPath oldPath = QueryEvent.GetLegPath(eventDB, controlId1, controlId2, legId); // Get an array with the start/end points and the bends. PointF[] oldBendArray = new PointF[(leg.bends == null) ? 2 : leg.bends.Length + 2]; if (leg.bends != null) Array.Copy(leg.bends, 0, oldBendArray, 1, leg.bends.Length); oldBendArray[0] = eventDB.GetControl(controlId1).location; oldBendArray[oldBendArray.Length - 1] = eventDB.GetControl(controlId2).location; // Insert the new point into the array at the right place. PointF[] newBendArray = Util.AddPointToArray(oldBendArray, newBend); // Copy the new bend parts into the bends array. leg.bends = new PointF[newBendArray.Length - 2]; Array.Copy(newBendArray, 1, leg.bends, 0, newBendArray.Length - 2); // Update the leg. if (legId.IsNone) eventDB.AddLeg(leg); else eventDB.ReplaceLeg(legId, leg); // If the leg had gaps, update the gaps for the new path. if (leg.gaps != null) { SymPath newPath = QueryEvent.GetLegPath(eventDB, controlId1, controlId2); LegGap[] newGaps = LegGap.MoveGapsToNewPath(leg.gaps, oldPath, newPath); ChangeLegGaps(eventDB, controlId1, controlId2, newGaps); } }
// Remove a bend from a leg. public static void RemoveLegBend(EventDB eventDB, Id<ControlPoint> controlId1, Id<ControlPoint> controlId2, PointF bendToRemove) { bool newFlagging = false; FlaggingKind newFlaggingKind = FlaggingKind.None; // Get the leg object for this leg. One must exists Id<Leg> legId = QueryEvent.FindLeg(eventDB, controlId1, controlId2); Leg leg = (Leg) eventDB.GetLeg(legId).Clone(); SymPath oldPath = QueryEvent.GetLegPath(eventDB, controlId1, controlId2, legId); if (leg.flagging == FlaggingKind.Begin || leg.flagging == FlaggingKind.End && leg.flagStartStop == bendToRemove) { // We are removing the point at which flagging starts/stop. The start/stop point must move to another, unless there are no // other bends left. if (leg.bends.Length == 1) { // No other bends left. Make leg all flagging. newFlagging = true; newFlaggingKind = FlaggingKind.All; } else { // Basic idea is to move to the bend that is at the flagging end, unless there is no such bend. // Where is the bend? int index = Array.IndexOf(leg.bends, bendToRemove); if ((index == 0 || leg.flagging == FlaggingKind.End) && index != leg.bends.Length - 1) { // move to next bend after the one removed. leg.flagStartStop = leg.bends[index + 1]; } else { // move to previous bend before the one removed. leg.flagStartStop = leg.bends[index - 1]; } } } // Remove the bend from the bend array. leg.bends = Util.RemovePointFromArray(leg.bends, bendToRemove); if (leg.bends.Length == 0) leg.bends = null; // Update the leg objects. if (leg.IsVacuous()) eventDB.RemoveLeg(legId); else eventDB.ReplaceLeg(legId, leg); // Change flagging if we need to. This is more complex that just setting the flagging kind in the leg object. if (newFlagging) ChangeFlagging(eventDB, controlId1, controlId2, newFlaggingKind); // If the leg had gaps, update the gaps for the new path. if (leg.gaps != null) { SymPath newPath = QueryEvent.GetLegPath(eventDB, controlId1, controlId2); LegGap[] newGaps = LegGap.MoveGapsToNewPath(leg.gaps, oldPath, newPath); ChangeLegGaps(eventDB, controlId1, controlId2, newGaps); } }
// Removes a control from the event. If the control is present in any course as a course-control, those // course-controls are also removed. public static void RemoveControl(EventDB eventDB, Id<ControlPoint> controlId) { // Find all of the courses/course-controls that are this control. foreach (Id<Course> courseId in QueryEvent.CoursesUsingControl(eventDB, controlId)) { Id<CourseControl> courseControlId; do { // Remove one course control could remove multiple, so only remove first of the course controls that use that control, then // check again. courseControlId = QueryEvent.GetCourseControlsInCourse(eventDB, new CourseDesignator(courseId), controlId).FirstOrDefault(); if (courseControlId.IsNotNone) RemoveCourseControl(eventDB, courseId, courseControlId); } while (courseControlId.IsNotNone); } // Find all of the legs that use this control and remove them. List<Id<Leg>> legIdList = new List<Id<Leg>>(); foreach (Id<Leg> legId in eventDB.AllLegIds) { Leg leg = eventDB.GetLeg(legId); if (leg.controlId1 == controlId || leg.controlId2 == controlId) legIdList.Add(legId); } foreach (Id<Leg> legId in legIdList) eventDB.RemoveLeg(legId); // Remove the control point itself. eventDB.RemoveControlPoint(controlId); }
// Move the bend in a leg to a new location. public static void MoveLegBend(EventDB eventDB, Id<ControlPoint> controlId1, Id<ControlPoint> controlId2, PointF oldBend, PointF newBend) { // Get the old leg. Id<Leg> legId = QueryEvent.FindLeg(eventDB, controlId1, controlId2); Debug.Assert(legId.IsNotNone); Leg leg = (Leg) eventDB.GetLeg(legId).Clone(); SymPath oldPath = QueryEvent.GetLegPath(eventDB, controlId1, controlId2, legId); // Change the bend. for (int i = 0; i < leg.bends.Length; ++i) { if (leg.bends[i] == oldBend) leg.bends[i] = newBend; } if (leg.flagStartStop == oldBend) leg.flagStartStop = newBend; // Update the leg. eventDB.ReplaceLeg(legId, leg); // If the leg had gaps, update the gaps for the new path. if (leg.gaps != null) { SymPath newPath = QueryEvent.GetLegPath(eventDB, controlId1, controlId2); leg.gaps = LegGap.MoveGapsToNewPath(leg.gaps, oldPath, newPath); eventDB.ReplaceLeg(legId, leg); } }