// Get Length of a special, in meters. public static float ComputeSpecialLength(EventDB eventDB, Id<Special> specialId) { Special special = eventDB.GetSpecial(specialId); SymPath path = new SymPath(special.locations); return (float)((eventDB.GetEvent().mapScale * path.Length) / 1000.0); }
// 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); } }
// 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); }
// Get the print area for a course, or for all controls if CourseId is none. public static PrintArea GetPrintArea(EventDB eventDB, CourseDesignator courseDesignator) { PrintArea printArea; if (courseDesignator.IsAllControls) printArea = eventDB.GetEvent().printArea; else { Course course = eventDB.GetCourse(courseDesignator.CourseId); printArea = course.printArea; if (!courseDesignator.AllParts && course.partPrintAreas.ContainsKey(courseDesignator.Part)) printArea = course.partPrintAreas[courseDesignator.Part]; } return printArea; }
// Renumber all controls according the current start code/invertible code. // Rather than change every control, any code that will continue to be used remains assigned to the same control id. Controls getting // new codes are assigned in code order, to new codes in code order. public static void AutoRenumberControls(EventDB eventDB) { Event e = eventDB.GetEvent(); Dictionary<string, bool> newCodes = new Dictionary<string,bool>(); // dictionary of new codes and if we still need to assign them Dictionary<string, Id<ControlPoint>> codeToControl = new Dictionary<string, Id<ControlPoint>>(); // dictionary mapping current codes to control ids. // Initialize the newCodes and codeToControl data structures int newCode = e.firstControlCode; string newCodeString = newCode.ToString(); foreach (Id<ControlPoint> controlId in eventDB.AllControlPointIds) { ControlPoint control = eventDB.GetControl(controlId); string reason; bool legal; if (control.kind == ControlPointKind.Normal) { // Add to codeToControl mapping dictionary. codeToControl.Add(control.code, controlId); // Add a new code to the new code dictionary newCodes.Add(newCodeString, true); do { ++newCode; if (newCode >= 1000) newCode = 31; newCodeString = newCode.ToString(); // Is the new code legal and preferred? legal = QueryEvent.IsPreferredControlCode(eventDB, newCodeString, out reason); } while (!legal || reason != null); // filters out invertible (if selected in the event). } } // Remove controls from the codeToControl dictionary that have codes we will continue to use, and mark those // codes are assigned. List<string> newCodeStrings = new List<string>(newCodes.Keys); foreach (string code in newCodeStrings) { if (codeToControl.ContainsKey(code)) { codeToControl.Remove(code); newCodes[code] = false; } } // Put the codeToControl dictionary into a list and sort it. List<KeyValuePair<string, Id<ControlPoint>>> codeToControlList = new List<KeyValuePair<string, Id<ControlPoint>>>(codeToControl); codeToControlList.Sort(delegate(KeyValuePair<string, Id<ControlPoint>> pair1, KeyValuePair<string, Id<ControlPoint>> pair2) { return Util.CompareCodes(pair1.Key, pair2.Key); }); // Put the codes still to be assigned into a list and sort it. List<string> newCodeList = new List<string>(); foreach (string code in newCodes.Keys) if (newCodes[code]) newCodeList.Add(code); newCodeList.Sort(Util.CompareCodes); // Assign new codes. Debug.Assert(codeToControlList.Count == newCodeList.Count); for (int i = 0; i < codeToControlList.Count; ++i) { ChangeEvent.ChangeCode(eventDB, codeToControlList[i].Value, newCodeList[i]); } }
// Gets the custom symbol text/key dictionary. Makes a copy of them, so that changes don't cause weird effects. public static void GetCustomSymbolText(EventDB eventDB, out Dictionary<string, List<SymbolText>> customSymbolText, out Dictionary<string, bool> customSymbolKey) { Event ev = eventDB.GetEvent(); customSymbolText = Util.CopyDictionary(ev.customSymbolText); customSymbolKey = Util.CopyDictionary(ev.customSymbolKey); }
// Get the description language. public static string GetDescriptionLanguage(EventDB eventDB) { Event ev = eventDB.GetEvent(); return ev.descriptionLangId; }
// Change the event title. Seperate lines with "|". public static void ChangeEventTitle(EventDB eventDB, string newTitle) { Event e = eventDB.GetEvent(); e = (Event) e.Clone(); e.title = newTitle; eventDB.ChangeEvent(e); }
// Change the map scale. public static void ChangeMapScale(EventDB eventDB, float newScale) { Event e = eventDB.GetEvent(); e = (Event) e.Clone(); e.mapScale = newScale; eventDB.ChangeEvent(e); }
// Change the custom system text for the event. public static void ChangeCustomSymbolText(EventDB eventDB, Dictionary<string, List<SymbolText>> customSymbolText, Dictionary<string, bool> customSymbolKey) { Event e = eventDB.GetEvent(); e = (Event) e.Clone(); e.customSymbolText = Util.CopyDictionary(customSymbolText); e.customSymbolKey = Util.CopyDictionary(customSymbolKey); eventDB.ChangeEvent(e); }
// Change the description language public static void ChangeDescriptionLanguage(EventDB eventDB, string newLangId) { Event e = eventDB.GetEvent(); e = (Event) e.Clone(); e.descriptionLangId = newLangId; eventDB.ChangeEvent(e); }
// Set the course appearance for this event. public static void ChangeCourseAppearance(EventDB eventDB, CourseAppearance courseAppearance) { Event e = eventDB.GetEvent(); e = (Event) e.Clone(); e.courseAppearance = (CourseAppearance) courseAppearance.Clone(); eventDB.ChangeEvent(e); }
// Change the auto numbering options. public static void ChangeAutoNumbering(EventDB eventDB, int startCode, bool disallowInvertibleCodes) { Event e = eventDB.GetEvent(); e = (Event) e.Clone(); e.firstControlCode = startCode; e.disallowInvertibleCodes = disallowInvertibleCodes; eventDB.ChangeEvent(e); }
// Change the attributes of a all controls display. public static void ChangeAllControlsProperties(EventDB eventDB, float printScale, DescriptionKind descriptionKind) { Event e = eventDB.GetEvent(); e = (Event) e.Clone(); e.allControlsPrintScale = printScale; e.allControlsDescKind = descriptionKind; eventDB.ChangeEvent(e); }
// Get the real world distance, in meters, between two points. public static float DistanceBetweenPointsInMeters(EventDB eventDB, PointF pt1, PointF pt2) { return (float)((eventDB.GetEvent().mapScale * Geometry.Distance(pt1, pt2)) / 1000.0); }
// Change the course print area. If "removeParts" is true, and the course is an all parts, // then remove print descriptions for each part. public static void ChangePrintArea(EventDB eventDB, CourseDesignator courseDesignator, bool removeParts, PrintArea printArea) { printArea = (PrintArea) printArea.Clone(); if (courseDesignator.IsAllControls) { Event e = eventDB.GetEvent(); e = (Event) e.Clone(); e.printArea = printArea; eventDB.ChangeEvent(e); } else { Course course = eventDB.GetCourse(courseDesignator.CourseId); course = (Course) course.Clone(); if (courseDesignator.AllParts) { course.printArea = printArea; if (removeParts) course.partPrintAreas = new Dictionary<int, PrintArea>(); } else { course.partPrintAreas[courseDesignator.Part] = printArea; } eventDB.ReplaceCourse(courseDesignator.CourseId, course); } }
// Get auto numbering values. public static void GetAutoNumbering(EventDB eventDB, out int firstCode, out bool disallowInvertibleCodes) { Event ev = eventDB.GetEvent(); firstCode = ev.firstControlCode; disallowInvertibleCodes = ev.disallowInvertibleCodes; }
// Set the punch card format for the event. public static void ChangePunchcardFormat(EventDB eventDB, PunchcardFormat punchcardFormat) { Event e = eventDB.GetEvent(); e = (Event) e.Clone(); e.punchcardFormat = (PunchcardFormat) punchcardFormat.Clone(); eventDB.ChangeEvent(e); }
// What is the default description kind for this course. Can be None, which gets // All Controls print scale. public static DescriptionKind GetDefaultDescKind(EventDB eventDB, Id<Course> courseId) { if (courseId.IsNone) return eventDB.GetEvent().allControlsDescKind; else return eventDB.GetCourse(courseId).descKind; }
// Determine if a code is legal and preferred. Some legal codes like "20" are never preferred. Invertible codes are // preferred only if the event is set to disallow such codes. // Returns true if the code is legal, false if illegal. // If legal but not preferred, returns true, but reason is set to a non-null value. // Legal but not preferred includes: under 31, leading zero, invertable (and event disallows those). public static bool IsPreferredControlCode(EventDB eventDB, string code, out string reason) { int codeNumber; bool disallowInvertibleCodes = eventDB.GetEvent().disallowInvertibleCodes; if (!IsLegalControlCode(code, out reason)) return false; if (int.TryParse(code, System.Globalization.NumberStyles.None, null, out codeNumber)) { if (code[0] == '0') { reason = MiscText.CodeBeginsWithZero; return true; // legal but not preferred. } if (disallowInvertibleCodes && Array.IndexOf(badUpsideDownNumbers, codeNumber) >= 0) { reason = MiscText.CodeCouldBeUpsideDown; return true; // legal but not preferred. } // It's OK. reason = null; return true; } else { // Non-numerics are OK. reason = null; return true; } }
// Get the event title, with particular string for newlines. public static string GetEventTitle(EventDB eventDB, string lineSep) { Event ev = eventDB.GetEvent(); return ev.title.Replace("|", lineSep); // In internal storage, | is used as line seperator. }
// Get the next unused control code. The next unused control code is determined by first taking // the initial code for this event. The next legal numeric control code // that isn't in use after this is returned, taking into account the invertibility setting for this event also. public static string NextUnusedControlCode(EventDB eventDB) { Event ev = eventDB.GetEvent(); int lowestCode = ev.firstControlCode; // Loop to find a legal, unused control code. bool wrapped = false; int codeNumber = lowestCode; for (;;) { string code = codeNumber.ToString(); string reason; if (FindCode(eventDB, code).IsNone && IsPreferredControlCode(eventDB, code, out reason) && reason == null) return code; ++codeNumber; if (codeNumber == 1000) { // Hit 1000. Wrap around to find another place. if (wrapped) return "XX"; // all the numbers are used! Unlikely, but we don't want to hang! wrapped = true; codeNumber = 31; } } }
// What is the print scale for this course? Can be None, which gets All Controls print scale. public static float GetPrintScale(EventDB eventDB, Id<Course> courseId) { if (courseId.IsNone) return eventDB.GetEvent().allControlsPrintScale; else return eventDB.GetCourse(courseId).printScale; }
// Create a new course with the given attributes. The course sorts after all existing courses. // If addStartAndFinish is true, then if exact one start control exists, it is added. If exactly one finish control exists, it is added. public static Id<Course> CreateCourse(EventDB eventDB, CourseKind courseKind, string name, ControlLabelKind labelKind, int scoreColumn, string secondaryTitle, float printScale, float climb, float? length, DescriptionKind descriptionKind, int firstControlOrdinal, bool addStartAndFinish) { // Find max sort order in use. int maxSortOrder = 0; foreach (Course course in eventDB.AllCourses) if (course.sortOrder > maxSortOrder) maxSortOrder = course.sortOrder; PrintArea printArea = (PrintArea) eventDB.GetEvent().printArea.Clone(); Course newCourse = new Course(courseKind, name, printScale, maxSortOrder + 1); newCourse.secondaryTitle = secondaryTitle; newCourse.climb = climb; newCourse.overrideCourseLength = length; newCourse.descKind = descriptionKind; newCourse.labelKind = labelKind; newCourse.scoreColumn = scoreColumn; newCourse.firstControlOrdinal = firstControlOrdinal; newCourse.printArea = printArea; Id<Course> newCourseId = eventDB.AddCourse(newCourse); if (addStartAndFinish) { // Add unique start and finish, if they exist. Id<ControlPoint> uniqueStart = FindUniqueControl(eventDB, newCourseId, ControlPointKind.Start); if (uniqueStart.IsNotNone) AddStartToCourse(eventDB, uniqueStart, newCourseId, false); Id<ControlPoint> uniqueFinish = FindUniqueControl(eventDB, newCourseId, ControlPointKind.Finish); if (uniqueFinish.IsNotNone) AddFinishToCourse(eventDB, uniqueFinish, newCourseId, false); } return newCourseId; }