public GetControl ( Id |
||
controlId | Id |
|
return |
// 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); }
// Get all the locations in the course exception controlView. private static PointF[] GetOtherLocations(EventDB eventDB, CourseView courseView, CourseView.ControlView controlViewExcept) { List<PointF> list = new List<PointF>(); foreach (CourseView.ControlView controlView in courseView.ControlViews) { if (controlView != controlViewExcept) list.Add(eventDB.GetControl(controlView.controlId).location); } return list.ToArray(); }
// Create an filtered All Controls view -- show controls from the control collection, but only includes some. // excludedCourses contains an array of course ids to excluded from the contgrols. // kindFilter, if non-null, limits the controls to this kind of controls. public static CourseView CreateFilteredAllControlsView(EventDB eventDB, CourseDesignator[] excludedCourses, ControlPointKind kindFilter, bool addSpecials, bool addDescription) { CourseView courseView = new CourseView(eventDB, CourseDesignator.AllControls); courseView.courseName = MiscText.AllControls; courseView.scoreColumn = -1; // Add every control to the course view, subject to the filters. foreach (Id<ControlPoint> controlId in eventDB.AllControlPointIds) { ControlPoint control = eventDB.GetControl(controlId); // Check if the control is filtered out. if (excludedCourses != null) { // Filter excluded courses. foreach (CourseDesignator excludedCourseDesignator in excludedCourses) { if (QueryEvent.CourseUsesControl(eventDB, excludedCourseDesignator, controlId)) goto SKIP; } } if (kindFilter != ControlPointKind.None) { // Filter on control type. if (control.kind != kindFilter) goto SKIP; } // We are going to include this control in the collection. ControlView controlView = new ControlView(); controlView.courseControlIds = new[] { Id<CourseControl>.None }; controlView.controlId = controlId; // All controls doesn't have ordinals. controlView.ordinal = -1; controlView.joinIndex = -1; courseView.controlViews.Add(controlView); SKIP: ; } // Sort the control views: first by kind, then by code. courseView.controlViews.Sort((view1, view2) => QueryEvent.CompareControlIds(eventDB, view1.controlId, view2.controlId)); courseView.Finish(); if (addSpecials) { // Add every special, regardless of courses it is on, except for descriptions. Descriptions are added to all // controls only if they appear in all courses (or specifically for the all controls view), and if "addDescription" is true foreach (Id<Special> specialId in eventDB.AllSpecialIds) { Special special = eventDB.GetSpecial(specialId); if (special.kind == SpecialKind.Descriptions) { if (addDescription && QueryEvent.CourseContainsSpecial(eventDB, CourseDesignator.AllControls, specialId)) courseView.descriptionViews.Add(new DescriptionView(specialId, CourseDesignator.AllControls)); } else courseView.specialIds.Add(specialId); } } return courseView; }
// 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 }); }
// Get the gaps in a control for a given scale. // Returns null if no gaps defined for that scale. public static CircleGap[] GetControlGaps(EventDB eventDB, Id<ControlPoint> controlId, float scale) { int scaleInt = (int)Math.Round(scale); ControlPoint control = eventDB.GetControl(controlId); if (control.gaps == null) return null; else if (!control.gaps.ContainsKey(scaleInt)) return null; else { return control.gaps[scaleInt]; } }
// Find the control with a code, and return its ID. Else return None. public static Id<ControlPoint> FindCode(EventDB eventDB, string code) { if (string.IsNullOrWhiteSpace(code)) return Id<ControlPoint>.None; foreach (Id<ControlPoint> controlId in eventDB.AllControlPointIds) { if (eventDB.GetControl(controlId).code == code) return controlId; } return Id<ControlPoint>.None; }
// Compute the length of a leg between two controls, in meters. The indicated leg id, if non-zero, is used // to get bend information. The event map scale converts between the map scale, in mm, which is used // for the coordinate information, to meters in the world scale. public static float ComputeLegLength(EventDB eventDB, Id<ControlPoint> controlId1, Id<ControlPoint> controlId2, Id<Leg> legId) { PointF location1 = eventDB.GetControl(controlId1).location; PointF location2 = eventDB.GetControl(controlId2).location; SymPath path = GetLegPath(eventDB, controlId1, controlId2, legId); return (float)((eventDB.GetEvent().mapScale * path.Length) / 1000.0); }
// Compare control ID by kind and code. public static int CompareControlIds(EventDB eventDB, Id<ControlPoint> controlId1, Id<ControlPoint> controlId2) { ControlPoint control1 = eventDB.GetControl(controlId1); ControlPoint control2 = eventDB.GetControl(controlId2); if (control1.kind < control2.kind) return -1; else if (control1.kind > control2.kind) return 1; int result = Util.CompareCodes(control1.code, control2.code); if (result != 0) return result; return controlId1.id.CompareTo(controlId2.id); }
// Get missing points. List<MissingThing> MissingScores(EventDB eventDB) { List<MissingThing> missingScores = new List<MissingThing>(); foreach (Id<Course> courseId in QueryEvent.SortedCourseIds(eventDB)) { Course course = eventDB.GetCourse(courseId); bool anyScores = false; List<MissingThing> missingScoresThisCourse = new List<MissingThing>(); if (course.kind == CourseKind.Score) { for (Id<CourseControl> courseControlId = course.firstCourseControl; courseControlId.IsNotNone; courseControlId = eventDB.GetCourseControl(courseControlId).nextCourseControl) { CourseControl courseControl = eventDB.GetCourseControl(courseControlId); if (eventDB.GetControl(courseControl.control).kind == ControlPointKind.Normal) { if (courseControl.points <= 0) missingScoresThisCourse.Add(new MissingThing(courseId, courseControl.control, ReportText.EventAudit_MissingScore)); else anyScores = true; } } if (anyScores) missingScores.AddRange(missingScoresThisCourse); // only report missing scores if some control in this course has a score. } } return missingScores; }
// Get missing punches. List<MissingThing> MissingPunches(EventDB eventDB, List<Id<ControlPoint>> unusedControls) { List<MissingThing> missingPunches = new List<MissingThing>(); bool anyPunches = false; // Keep track if any controls have non-empty punch pattersn. foreach (Id<ControlPoint> controlId in eventDB.AllControlPointIds) { if (unusedControls.Contains(controlId)) continue; ControlPoint control = eventDB.GetControl(controlId); if (control.kind != ControlPointKind.Normal) continue; if (control.punches == null || control.punches.IsEmpty) missingPunches.Add(new MissingThing(controlId, ReportText.EventAudit_MissingPunch)); else anyPunches = true; } if (anyPunches) { missingPunches.Sort((thing1, thing2) => QueryEvent.CompareControlIds(eventDB, thing1.controlId, thing2.controlId)); return missingPunches; } else { // No controls had punch patterns defined. This event clearly is not using punches. return new List<MissingThing>(); } }
// Get missing description boxes. List<MissingThing> MissingDescriptionBoxes(EventDB eventDB, List<Id<ControlPoint>> unusedControls) { List<MissingThing> missingBoxes = new List<MissingThing>(); foreach (Id<ControlPoint> controlId in eventDB.AllControlPointIds) { // Go through all regular or start controls that are in use. if (unusedControls.Contains(controlId)) continue; ControlPoint control = eventDB.GetControl(controlId); if (control.kind != ControlPointKind.Normal && control.kind != ControlPointKind.Start) continue; // If a start control is completely empty, don't process it. if (control.kind == ControlPointKind.Start) { if (! Array.Exists(control.symbolIds, id => !string.IsNullOrEmpty(id))) continue; } // Each start or normal control has 6 boxes. C==0, D==1, E==2, F==3, G==4, H==5 Debug.Assert(control.symbolIds.Length == 6); if (string.IsNullOrEmpty(control.symbolIds[1])) { missingBoxes.Add(new MissingThing(controlId, "D", ReportText.EventAudit_MissingD)); } else if (! string.IsNullOrEmpty(control.symbolIds[3]) && string.IsNullOrEmpty(control.symbolIds[2])) { missingBoxes.Add(new MissingThing(controlId, "E", ReportText.EventAudit_MissingEJunction)); } else if (control.symbolIds[4] == "11.15" && string.IsNullOrEmpty(control.symbolIds[2])) { missingBoxes.Add(new MissingThing(controlId, "E", ReportText.EventAudit_MissingEBetween)); } } missingBoxes.Sort(((thing1, thing2) => QueryEvent.CompareControlIds(eventDB, thing1.controlId, thing2.controlId))); return missingBoxes; }
// Get all the control IDs to cross-ref, in the correct order. private Id<ControlPoint>[] GetControlIdsToXref(EventDB eventDB) { // Only cross-ref regular controls. Then sort by code. List<Id<ControlPoint>> list = new List<Id<ControlPoint>>(); foreach (Id<ControlPoint> controlId in eventDB.AllControlPointIds) { if (eventDB.GetControl(controlId).kind == ControlPointKind.Normal) list.Add(controlId); } list.Sort(delegate(Id<ControlPoint> id1, Id<ControlPoint> id2) { ControlPoint control1 = eventDB.GetControl(id1), control2 = eventDB.GetControl(id2); return Util.CompareCodes(control1.code, control2.code); }); return list.ToArray(); }
// Return a list of all controls that are nearby each other. It is sorted in order of distance. private List<NearbyControls> FindNearbyControls(EventDB eventDB, float distanceLimit) { ICollection<Id<ControlPoint>> allPoints = eventDB.AllControlPointIds; List<NearbyControls> list = new List<NearbyControls>(); // Go through every pair of normal controls. If the distance between them is less than the distance limit, add to the list. foreach (Id<ControlPoint> controlId1 in allPoints) { ControlPoint control1 = eventDB.GetControl(controlId1); if (control1.kind != ControlPointKind.Normal) continue; // only deal with normal points. string symbol1 = SymbolOfControl(control1); // Check all other controls with greater ids (so each pair considered only once) foreach (Id<ControlPoint> controlId2 in allPoints) { ControlPoint control2 = eventDB.GetControl(controlId2); if (control2.kind != ControlPointKind.Normal || controlId2.id <= controlId1.id) continue; // only deal with normal points with greater id. string symbol2 = SymbolOfControl(control2); float distance = QueryEvent.ComputeStraightLineControlDistance(eventDB, controlId1, controlId2); if (distance < distanceLimit) { NearbyControls nearbyControls; nearbyControls.controlId1 = controlId1; nearbyControls.controlId2 = controlId2; nearbyControls.distance = distance; nearbyControls.sameSymbol = (symbol1 != null && symbol2 != null && symbol1 == symbol2); // only same symbol if both have symbols! list.Add(nearbyControls); } } } // Sort the list by distance. list.Sort((x1, x2) => x1.distance.CompareTo(x2.distance)); return list; }
internal string CreateCrossReferenceReport(EventDB eventDB) { InitReport(); // Header. WriteH1(string.Format(ReportText.CrossRef_Title, QueryEvent.GetEventTitle(eventDB, " "))); Id<ControlPoint>[] controlsToXref = GetControlIdsToXref(eventDB); Id<Course>[] coursesToXref = QueryEvent.SortedCourseIds(eventDB); string[,] xref = CreateXref(eventDB, controlsToXref, coursesToXref); string[] classes = new string[coursesToXref.Length + 1]; classes[0] = "leftalign"; for (int i = 1; i < classes.Length; ++i) classes[i] = "rightalign"; BeginTable("", classes.Length, classes); // Write the header row. BeginTableRow(); WriteTableHeaderCell(ReportText.ColumnHeader_Control); for (int i = 0; i < coursesToXref.Length; ++i) WriteTableHeaderCell(eventDB.GetCourse(coursesToXref[i]).name); EndTableRow(); // Write the cross-reference rows. Table rule after every 3rd line for (int row = 0; row < controlsToXref.Length; ++row) { bool tablerule = (row % 3 == 2); BeginTableRow(); WriteTableCell(tablerule ? "tablerule" : "", eventDB.GetControl(controlsToXref[row]).code); for (int col = 0; col < coursesToXref.Length; ++col) WriteTableCell(tablerule ? "tablerule" : "", xref[row, col]); EndTableRow(); } EndTable(); return FinishReport(); }
// Create a report showing missing things public string CreateEventAuditReport(EventDB eventDB) { bool problemFound = false; // Initialize the report InitReport(); // Header. WriteH1(string.Format(ReportText.EventAudit_Title, QueryEvent.GetEventTitle(eventDB, " "))); // Courses missing things. Climb (not score course), start, finish (not score course), competitor load. List<MissingThing> missingCourseThings = MissingCourseThings(eventDB); if (missingCourseThings.Count > 0) { problemFound = true; WriteH2(ReportText.EventAudit_MissingItems); BeginTable("", 3, "leftalign", "leftalign", "leftalign"); WriteTableHeaderRow(ReportText.ColumnHeader_Course, ReportText.ColumnHeader_Item, ReportText.ColumnHeader_Reason); foreach (MissingThing thing in missingCourseThings) { WriteTableRow(eventDB.GetCourse(thing.courseId).name, thing.what, thing.why); } EndTable(); } // Close together controls. float DISTANCE_LIMIT = 100.4999F; // limit of distance for close controls (100m, when rounded). List<NearbyControls> nearbyList = FindNearbyControls(eventDB, DISTANCE_LIMIT); if (nearbyList.Count > 0) { problemFound = true; // Informational text. WriteH2(ReportText.EventAudit_CloseTogetherControls); StartPara(); WriteText(string.Format(ReportText.EventAudit_CloseTogetherExplanation, Math.Round(DISTANCE_LIMIT))); EndPara(); // The report. BeginTable("", 3, "leftalign", "rightalign", "leftalign"); WriteTableHeaderRow(ReportText.ColumnHeader_ControlCodes, ReportText.ColumnHeader_Distance, ReportText.ColumnHeader_SameSymbol); foreach (NearbyControls nearby in nearbyList) { string code1 = eventDB.GetControl(nearby.controlId1).code; string code2 = eventDB.GetControl(nearby.controlId2).code; if (Util.CompareCodes(code1, code2) > 0) { // swap code1 and code 2 so they always appear in order. string temp = code1; code1 = code2; code2 = temp; } WriteTableRow(string.Format("{0}, {1}", code1, code2), string.Format("{0} m", Math.Round(nearby.distance)), nearby.sameSymbol ? ReportText.EventAudit_Yes : ReportText.EventAudit_No); } EndTable(); } // Unused controls. List<Id<ControlPoint>> unusedControls = SortedUnusedControls(eventDB); if (unusedControls.Count > 0) { problemFound = true; WriteH2(ReportText.EventAudit_UnusedControls); StartPara(); WriteText(ReportText.EventAudit_UnusedControlsExplanation); EndPara(); BeginTable("", 2, "leftalign", "leftalign"); WriteTableHeaderRow(ReportText.ColumnHeader_Code, ReportText.ColumnHeader_Location); foreach (Id<ControlPoint> controlId in unusedControls) { ControlPoint control = eventDB.GetControl(controlId); WriteTableRow(Util.ControlPointName(eventDB, controlId, NameStyle.Medium), string.Format("({0}, {1})", Math.Round(control.location.X), Math.Round(control.location.Y))); } EndTable(); } // Missing descriptions boxes (regular controls only). Missing column D. Missing column E when between/junction/crossing used. Missing between/junction/crossing when column E is a symbol. List<MissingThing> missingBoxes = MissingDescriptionBoxes(eventDB, unusedControls); if (missingBoxes.Count > 0) { problemFound = true; WriteH2(ReportText.EventAudit_MissingBoxes); BeginTable("", 3, "leftalign", "leftalign", "leftalign"); WriteTableHeaderRow(ReportText.ColumnHeader_Code, ReportText.ColumnHeader_Column, ReportText.ColumnHeader_Reason); foreach (MissingThing thing in missingBoxes) { WriteTableRow(Util.ControlPointName(eventDB, thing.controlId, NameStyle.Medium), thing.what, thing.why); } EndTable(); } // Missing punches. List<MissingThing> missingPunches = MissingPunches(eventDB, unusedControls); if (missingPunches.Count > 0) { problemFound = true; WriteH2(ReportText.EventAudit_MissingPunchPatterns); BeginTable("", 2, "leftalign", "leftalign"); WriteTableHeaderRow(ReportText.ColumnHeader_Code, ReportText.ColumnHeader_Reason); foreach (MissingThing thing in missingPunches) { WriteTableRow(Util.ControlPointName(eventDB, thing.controlId, NameStyle.Medium), thing.what); } EndTable(); } // Missing points (score course only) List<MissingThing> missingScores = MissingScores(eventDB); if (missingScores.Count > 0) { problemFound = true; WriteH2(ReportText.EventAudit_MissingScores); BeginTable("", 3, "leftalign", "leftalign", "leftalign"); WriteTableHeaderRow(ReportText.ColumnHeader_Course, ReportText.ColumnHeader_Control, ReportText.ColumnHeader_Reason); foreach (MissingThing thing in missingScores) { WriteTableRow(eventDB.GetCourse(thing.courseId).name, Util.ControlPointName(eventDB, thing.controlId, NameStyle.Medium), thing.what); } EndTable(); } // If none of the above, then "no problems found". if (!problemFound) { StartPara(); WriteText(ReportText.EventAudit_NoProblems); EndPara(); } return FinishReport(); }
// Get the last course control in a course. In dontReturnFinish is true, will never return a finish control, but // always the control before the finish. Returns None if the course has no controls (or only a finish control.) public static Id<CourseControl> LastCourseControl(EventDB eventDB, Id<Course> courseId, bool dontReturnFinish) { Id<CourseControl> last = Id<CourseControl>.None; foreach (Id<CourseControl> courseControlId in EnumCourseControlIds(eventDB, new CourseDesignator(courseId))) { if (!dontReturnFinish || eventDB.GetControl(eventDB.GetCourseControl(courseControlId).control).kind != ControlPointKind.Finish) last = courseControlId; } return last; }
// Determine if you should warn about moving a shared course control. If a normal control is being // moved more than 75 meters, and is in other courses, then warn. // Returns null to not warn, or array of other courses to warn. public static Id<Course>[] ShouldWarnAboutMovingControl(EventDB eventDB, Id<Course> courseId, Id<CourseControl> courseControlId, PointF newLocation) { Id<ControlPoint> controlId = eventDB.GetCourseControl(courseControlId).control; Debug.Assert(CourseUsesControl(eventDB, new CourseDesignator(courseId), controlId)); if (eventDB.GetControl(controlId).kind != ControlPointKind.Normal) return null; float distance = DistanceBetweenPointsInMeters(eventDB, eventDB.GetControl(controlId).location, newLocation); if (distance < MOVE_THRESHOLD) return null; List<Id<Course>> list = new List<Id<Course>>(); foreach (Id<Course> containingCourseId in SortedCourseIds(eventDB)) { if (containingCourseId != courseId && CourseUsesControl(eventDB, new CourseDesignator(containingCourseId), controlId)) { list.Add(containingCourseId); } } if (list.Count == 0) return null; else return list.ToArray(); }
void WriteControlLoadSection(EventDB eventDB) { List<ControlLoadInfo> loadInfos = new List<ControlLoadInfo>(); // Get load information about each control. foreach (Id<ControlPoint> controlId in eventDB.AllControlPointIds) { ControlPoint control = eventDB.GetControl(controlId); if (control.kind != ControlPointKind.Normal) continue; // only list normal controls. ControlLoadInfo loadInfo = new ControlLoadInfo(); loadInfo.controlId = controlId; loadInfo.controlName = Util.ControlPointName(eventDB, controlId, NameStyle.Medium); loadInfo.numCourses = QueryEvent.CoursesUsingControl(eventDB, controlId).Length; loadInfo.load = QueryEvent.GetControlLoad(eventDB, controlId); loadInfos.Add(loadInfo); } // Sort the load information, first by load, then by number of courses, then by name. loadInfos.Sort(delegate(ControlLoadInfo loadInfo1, ControlLoadInfo loadInfo2) { if (loadInfo1.load < loadInfo2.load) return 1; else if (loadInfo1.load > loadInfo2.load) return -1; if (loadInfo1.numCourses < loadInfo2.numCourses) return 1; else if (loadInfo1.numCourses > loadInfo2.numCourses) return -1; int result = Util.CompareCodes(loadInfo1.controlName, loadInfo2.controlName); if (result != 0) return result; return loadInfo1.controlId.id.CompareTo(loadInfo2.controlId.id); }); // Write the table. BeginTable("", 3, "leftalign", "rightalign", "rightalign"); WriteTableHeaderRow(ReportText.ColumnHeader_Control, ReportText.ColumnHeader_NumberOfCourses, ReportText.ColumnHeader_Load); foreach (ControlLoadInfo loadInfo in loadInfos) { WriteTableRow(loadInfo.controlName, Convert.ToString(loadInfo.numCourses), loadInfo.load >= 0 ? Convert.ToString(loadInfo.load) : ""); } EndTable(); }
// 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); } }
private void WriteLegLengthTable(EventDB eventDB, CourseView courseView) { BeginTable("", 3, "leftalign", "leftalign", "rightalign"); WriteTableHeaderRow(ReportText.ColumnHeader_Leg, ReportText.ColumnHeader_Controls, ReportText.ColumnHeader_Length); // Go through the control views. int controlViewIndex = 0; float distanceThisLeg = 0; float totalLegs = 0; int legNumber = 1; Id<ControlPoint> controlIdPrev = Id<ControlPoint>.None; while (controlViewIndex >= 0 && controlViewIndex < courseView.ControlViews.Count) { CourseView.ControlView controlView = courseView.ControlViews[controlViewIndex]; ControlPointKind kind = eventDB.GetControl(controlView.controlId).kind; // Don't report crossing points. if (kind != ControlPointKind.CrossingPoint) { if (controlIdPrev.IsNotNone) { string legText = string.Format("{0}\u2013{1}", Util.ControlPointName(eventDB, controlIdPrev, NameStyle.Medium), Util.ControlPointName(eventDB, controlView.controlId, NameStyle.Medium)); WriteTableRow(Convert.ToString(legNumber), legText, string.Format("{0} m", Math.Round(distanceThisLeg))); totalLegs += distanceThisLeg; legNumber += 1; } controlIdPrev = controlView.controlId; distanceThisLeg = 0; } if (controlView.legLength != null) distanceThisLeg += controlView.legLength[0]; controlViewIndex = courseView.GetNextControl(controlViewIndex); } // Write average row if (legNumber > 1) { BeginTableRow("summaryrow"); WriteSpannedTableCell(2, ReportText.LegLength_Average); WriteTableCell(string.Format("{0} m", Convert.ToString(Math.Round(totalLegs / (float) (legNumber - 1))))); EndTableRow(); } EndTable(); }
// Compute the distance between two control points, in meters. The controls need not be part of a leg, and if they are, bends // in the leg are NOT taken into account. public static float ComputeStraightLineControlDistance(EventDB eventDB, Id<ControlPoint> controlId1, Id<ControlPoint> controlId2) { PointF location1 = eventDB.GetControl(controlId1).location; PointF location2 = eventDB.GetControl(controlId2).location; return DistanceBetweenPointsInMeters(eventDB, location1, location2); }
// Create highlights to and from a point to course controls. If controlDrag is set (optional), it is // used to get the correct bends for legs. // Static because it is used from DragControlMode also. public static CourseObj[] CreateLegHighlights(EventDB eventDB, PointF newPoint, Id<ControlPoint>controlDrag, ControlPointKind controlKind, Id<CourseControl> courseControlId1, Id<CourseControl> courseControlId2, float scaleRatio, CourseAppearance appearance) { List<CourseObj> highlights = new List<CourseObj>(); if (courseControlId1.IsNotNone) { Id<ControlPoint> controlId1 = eventDB.GetCourseControl(courseControlId1).control; ControlPoint control1 = eventDB.GetControl(controlId1); LegCourseObj highlight = CreateLegHighlight(eventDB, control1.location, control1.kind, controlId1, newPoint, controlKind, controlDrag, scaleRatio, appearance); if (highlight != null) highlights.Add(highlight); } if (courseControlId2.IsNotNone) { Id<ControlPoint> controlId2 = eventDB.GetCourseControl(courseControlId2).control; ControlPoint control2 = eventDB.GetControl(controlId2); LegCourseObj highlight = CreateLegHighlight(eventDB, newPoint, controlKind, controlDrag, control2.location, control2.kind, controlId2, scaleRatio, appearance); if (highlight != null) highlights.Add(highlight); } return highlights.ToArray(); }
// Finds where a new regular control would be inserted into an existing course. courseControl1 and courseControl2 can either or both be none, to identify // a leg to insert into, a control to insert after, or no information about where to insert. Updates courseControl1 and courseControl2 to identify exactly // where on the course the control should be inserted as follows: // If inserting between two course controls -- these are denoted by courseControl1 and courseControl2 // If inserting as last course control -- courseControl1 is the current last control and courseControl2 is None (only occurs when there is no finish) // If inserting as first course control -- courseControl2 is None and courseControl2 is current first control (only occurs when there is no start) // If inserting as only course control -- both are none (only occurs if course is currently empty) public static void FindControlInsertionPoint(EventDB eventDB, CourseDesignator courseDesignator, ref Id<CourseControl> courseControl1, ref Id<CourseControl> courseControl2) { Id<Course> courseId = courseDesignator.CourseId; if (courseControl1.IsNotNone && courseControl2.IsNotNone) { CourseControl cc1 = eventDB.GetCourseControl(courseControl1); CourseControl cc2 = eventDB.GetCourseControl(courseControl2); if (cc1.nextCourseControl != courseControl2) { Debug.Assert(cc2.split && cc2.splitCourseControls.Contains(courseControl2)); courseControl2 = cc1.nextCourseControl; } return; } else { // Adding after courseControl1. If none, or a finish control, add at end, before the finish control if any. if (courseControl1.IsNone || eventDB.GetControl(eventDB.GetCourseControl(courseControl1).control).kind == ControlPointKind.Finish) courseControl1 = QueryEvent.LastCourseControl(eventDB, courseId, true); if (courseControl1.IsNone) { // Empty course or adding at start. courseControl2 = eventDB.GetCourse(courseId).firstCourseControl; return; } else { // Adding after courseControl1. CourseControl before = (CourseControl)eventDB.GetCourseControl(courseControl1); courseControl2 = before.nextCourseControl; return; } } }
// Describe a control point. private static TextPart[] DescribeControlPoint(SymbolDB symbolDB, EventDB eventDB, Id<ControlPoint> controlId, DescKind descKind) { Debug.Assert(descKind == DescKind.DescPane || descKind == DescKind.Tooltip); List<TextPart> list = new List<TextPart>(); ControlPoint control = eventDB.GetControl(controlId); // Control name/code. list.Add(new TextPart(TextFormat.Title, Util.ControlPointName(eventDB, controlId, NameStyle.Long))); // Control location. if (descKind == DescKind.DescPane) { list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.Location + " ")); list.Add(new TextPart(TextFormat.SameLine, string.Format("({0:##0.0}, {1:##0.0})", control.location.X, control.location.Y))); } // Which courses is it used in? list.Add(new TextPart(TextFormat.Header, (descKind == DescKind.Tooltip ? SelectionDescriptionText.UsedIn : SelectionDescriptionText.UsedInCourses))); Id<Course>[] coursesUsingControl = QueryEvent.CoursesUsingControl(eventDB, controlId); list.Add(new TextPart(descKind == DescKind.Tooltip ? TextFormat.SameLine : TextFormat.NewLine, CourseListText(eventDB, coursesUsingControl))); // What is the competitor load? int load = QueryEvent.GetControlLoad(eventDB, controlId); if (load >= 0) { list.Add(new TextPart(TextFormat.Header, (descKind == DescKind.Tooltip ? SelectionDescriptionText.Load : SelectionDescriptionText.CompetitorLoad))); list.Add(new TextPart(TextFormat.SameLine, string.Format("{0}", load))); } // Text version of the descriptions if (descKind == DescKind.DescPane) { Textifier textifier = new Textifier(eventDB, symbolDB, QueryEvent.GetDescriptionLanguage(eventDB)); string descText = textifier.CreateTextForControl(controlId, null); list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.TextDescription)); list.Add(new TextPart(TextFormat.NewLine, descText)); } return list.ToArray(); }
// 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; }
// Determine the type of flagging private static string FlaggingType(EventDB eventDB, Id<ControlPoint> controlId1, Id<ControlPoint> controlId2, Id<Leg> legId) { string flaggingType = SelectionDescriptionText.None; FlaggingKind kind = QueryEvent.GetLegFlagging(eventDB, controlId1, controlId2, legId); switch (kind) { case FlaggingKind.All: flaggingType = SelectionDescriptionText.EntireLeg; break; case FlaggingKind.Begin: flaggingType = SelectionDescriptionText.AwayFromControl; break; case FlaggingKind.End: flaggingType = SelectionDescriptionText.IntoControl; break; } // We use slightly different wording based on the finish control. ControlPoint ending = eventDB.GetControl(controlId2); if (ending.kind == ControlPointKind.Finish) { // finish control can influence flagging! if (ending.symbolIds[0] == "14.2" && kind == FlaggingKind.None) flaggingType = SelectionDescriptionText.FinishFunnel; else if (ending.symbolIds[0] == "14.2" && kind == FlaggingKind.End) flaggingType = SelectionDescriptionText.IntoFinishFunnel; } return flaggingType; }
// Create the standard view onto a regular course, or a single variation of a variation course. private static CourseView CreateStandardCourseView(EventDB eventDB, CourseDesignator courseDesignator) { Course course = eventDB.GetCourse(courseDesignator.CourseId); if (QueryEvent.HasVariations(eventDB, courseDesignator.CourseId) && courseDesignator.VariationInfo == null) throw new ApplicationException("Cannot create course view without specifying which variation"); // Get sub-part of the course. firstCourseControls is the first control to process, lastCourseControl is the last one to // process, or None if process to the end of the course. Id<CourseControl> firstCourseControl, lastCourseControl; if (courseDesignator.AllParts) { firstCourseControl = course.firstCourseControl; lastCourseControl = Id<CourseControl>.None; } else { QueryEvent.GetCoursePartBounds(eventDB, courseDesignator, out firstCourseControl, out lastCourseControl); } CourseView courseView = new CourseView(eventDB, courseDesignator); int ordinal; courseView.courseName = course.name; courseView.scoreColumn = -1; ordinal = 1; ordinal = course.firstControlOrdinal; // To get the ordinals correct, we get the course control ids for all parts. List<Id<CourseControl>> courseControls = QueryEvent.EnumCourseControlIds(eventDB, courseDesignator.WithAllParts()).ToList(); int index = 0; // Increase the ordinal value for each normal control before the first one we're considering. while (index < courseControls.Count && courseControls[index] != firstCourseControl) { CourseControl courseControl = eventDB.GetCourseControl(courseControls[index]); ControlPoint control = eventDB.GetControl(courseControl.control); if (control.kind == ControlPointKind.Normal) ++ordinal; ++index; } for (; index < courseControls.Count; ++index) { Id<CourseControl> courseControlId = courseControls[index]; ControlView controlView = new ControlView(); CourseControl courseControl = eventDB.GetCourseControl(courseControlId); ControlPoint control = eventDB.GetControl(courseControl.control); controlView.courseControlIds = new[] { courseControlId }; controlView.controlId = courseControl.control; // Set the ordinal number. if (control.kind == ControlPointKind.Normal) controlView.ordinal = ordinal++; else if (control.kind == ControlPointKind.Start || control.kind == ControlPointKind.MapExchange) controlView.ordinal = 0; else controlView.ordinal = -1; controlView.joinIndex = -1; // Don't show the map exchange for the next part at the end of this part. if (courseControlId == lastCourseControl && !courseDesignator.AllParts && control.kind == ControlPointKind.MapExchange) { controlView.hiddenControl = true; } // Set the legTo array with the next courseControlID. This is later updated // to the indices. if (index < courseControls.Count - 1 && courseControlId != lastCourseControl) { Id<CourseControl> nextCourseControl = courseControls[index + 1]; controlView.legTo = new int[1] { nextCourseControl.id }; // legTo initially holds course control ids, later changed. } // Add the controlview. courseView.controlViews.Add(controlView); if (courseControlId == lastCourseControl) break; } // If this is a part that should also have the finish on it, and it isn't the last part, then // add the finish. if (courseDesignator.IsNotAllControls && !courseDesignator.AllParts && courseDesignator.Part != QueryEvent.CountCourseParts(eventDB, courseDesignator.CourseId) - 1 && QueryEvent.GetPartOptions(eventDB, courseDesignator).ShowFinish) { if (QueryEvent.HasFinishControl(eventDB, courseDesignator.CourseId)) courseView.extraCourseControls.Add(QueryEvent.LastCourseControl(eventDB, courseDesignator.CourseId, false)); } courseView.Finish(); return courseView; }
// Does the course have a finish control? public static bool HasFinishControl(EventDB eventDB, Id<Course> courseId) { Id<CourseControl> lastId = QueryEvent.LastCourseControl(eventDB, courseId, false); if (lastId.IsNone || eventDB.GetControl(eventDB.GetCourseControl(lastId).control).kind != ControlPointKind.Finish) return false; else return true; }
// Create the normal view onto a score course private static CourseView CreateScoreCourseView(EventDB eventDB, CourseDesignator courseDesignator) { Course course = eventDB.GetCourse(courseDesignator.CourseId); CourseView courseView = new CourseView(eventDB, courseDesignator); Id<CourseControl> courseControlId; courseView.courseName = course.name; courseView.scoreColumn = course.scoreColumn; courseControlId = course.firstCourseControl; while (courseControlId.IsNotNone) { ControlView controlView = new ControlView(); CourseControl courseControl = eventDB.GetCourseControl(courseControlId); controlView.courseControlIds = new[] { courseControlId }; controlView.controlId = courseControl.control; // Ordinals assigned after sorting. controlView.ordinal = -1; controlView.joinIndex = -1; // Move to the next control. courseView.controlViews.Add(controlView); courseControlId = courseControl.nextCourseControl; } // Sort the control views: first by kind, then by score, then by code. courseView.controlViews.Sort(delegate(ControlView view1, ControlView view2) { ControlPoint control1 = eventDB.GetControl(view1.controlId); ControlPoint control2 = eventDB.GetControl(view2.controlId); CourseControl courseControl1 = eventDB.GetCourseControl(view1.courseControlIds[0]); CourseControl courseControl2 = eventDB.GetCourseControl(view2.courseControlIds[0]); if (control1.kind < control2.kind) return -1; else if (control1.kind > control2.kind) return 1; if (courseControl1.points != courseControl2.points) return courseControl1.points.CompareTo(courseControl2.points); int result = Util.CompareCodes(control1.code, control2.code); if (result != 0) return result; return view1.controlId.id.CompareTo(view2.controlId.id); }); // Assign ordinals, if applicable. If scores in column A, then no ordinals will be assigned. if (courseView.scoreColumn != 0) { int ordinal = course.firstControlOrdinal; foreach (ControlView control in courseView.controlViews) { if (eventDB.GetControl(control.controlId).kind == ControlPointKind.Normal) control.ordinal = ordinal++; } } courseView.Finish(); return courseView; }
// Does the course have a start control? public static bool HasStartControl(EventDB eventDB, Id<Course> courseId) { Id<CourseControl> firstId = eventDB.GetCourse(courseId).firstCourseControl; if (firstId.IsNone || eventDB.GetControl(eventDB.GetCourseControl(firstId).control).kind != ControlPointKind.Start) return false; else return true; }