/// <summary> /// AddArrow generates the missing points for an arrow's tail as appropriate for Bing's mercator projection /// </summary> /// <param name="mg"> /// The MilGraphic representing the arrow shape. /// </param> /// <param name="inPoints"> /// The points from the resource dictionary entry for the particular symbol. /// </param> /// <param name="inAnchors"> /// The points representing the map coordinates of the arrow's head to tail points. /// </param> /// <returns> /// A list of points that represent the head to tail coordinates of the arrow /// </returns> public static IList <Point> AddArrow( MilGraphic mg, IList <Point> inPoints, IList <ILocation> inAnchors) { //// ·N-1 //// |\ //// ------------ \ //// ·1 ·0 //// ------------ / //// |/ // First point [0] is the head of the arrow. // Second point [1] is the end of the first arrow segment. // Subsequent points define additional arrow segment ends. // Last point [N - 1] is the top of the arrowhead (different from 2525B). // Looks like the height of the arrow body is 1/2 of the height of the arrow head. // Convert the points into "pixel" space. To keep a constant arrow // width it makes more sense to do the computations in this space. var count = mg.Anchors.Count; IList <Point> points = MapHelper.LatLonsToPixels(mg.Anchors, 4.0); // This is the projection of the arrowhead onto the arrow baseline var pt = MapHelper.CalculateProjection(points[0], points[1], points[count - 1]); // The origin is the first point in this points array var origin = points[0]; // Compute half the width of the arrow var distance = 0.5 * PointHelper.Magnitude(pt, points[count - 1]); // Let's go ahead and compute the unit vectors in the direction // of each vector making up the arrow, starting at the head and // moving towards the tail. var unitVectors = new Point[count - 2]; for (int i = 0; i < count - 2; i++) { unitVectors[i] = MapHelper.Normalize(points[i], points[i + 1]); } var leftNormal = new Point[count - 2]; var rightNormal = new Point[count - 2]; // Now compute the "left" and "right" perpendicular unit vectors Point normal; for (int i = 0; i < count - 2; i++) { // The rightNormal vectors cross with the unit vector to give a +1 in Z // The leftNormal vectors cross with the unit vector to give a -1 in Z normal = new Point(unitVectors[i].Y, -unitVectors[i].X); var cross = (normal.X * unitVectors[i].Y) - (normal.Y * unitVectors[i].X); leftNormal[i] = (cross < 0.0) ? normal : new Point(-normal.X, -normal.Y); rightNormal[i] = (cross < 0.0) ? new Point(-normal.X, -normal.Y) : normal; } // We're not mitering any of these angles - that's up to the user. // So we're bisecting the angle between two vectors and moving // out a distance based on the tangent of that angle. // This gives us the outside point. // So outsidePoint = point[i] + d * outsideVector[i-1] + d / tan(<) * unitVector[i-1] // where < is acos(dot(unitVector[i-1], unitVector[i])). // // ______. d * tan(<) // d|< // i-1------->-------+i // \ // \ // i+1 // Need to flip the starting points if Aviation, Airborne or RotaryWing bool flip = mg.StencilType == "AxisOfAdvanceAviation" || mg.StencilType == "AxisOfAdvanceAirborne" || mg.StencilType == "AxisOfAdvanceRotaryWing"; var fwd = PointHelper.LinearCombination(pt, distance, leftNormal[0]); var bck = PointHelper.LinearCombination(pt, distance, rightNormal[0]); // Start the arrow's body - first point defined by the top of the arrowhead var arrow = new List <Point> { flip?bck : fwd }; var backArrow = new List <Point> { flip?fwd : bck }; for (int i = 0; i < count - 2; i++) { // Get bisected angle // Use the half-angle tangent formula here instead of the brute force method // var angle = Math.Atan2( // unitVectors[i].Y * unitVectors[i + 1].X - unitVectors[i].X * unitVectors[i + 1].Y, // unitVectors[i].X * unitVectors[i + 1].X + unitVectors[i].Y * unitVectors[i + 1].Y) / 2.0; // var tangent = Math.Tan(angle); var tangent = (i == count - 3) ? 0.0 : ((unitVectors[i].Y * unitVectors[i + 1].X) - (unitVectors[i].X * unitVectors[i + 1].Y)) / ((unitVectors[i].X * unitVectors[i + 1].X) + (unitVectors[i].Y * unitVectors[i + 1].Y) + 1.0); // points[i+1] + distance * (leftNormal[i] + tangent * unitVectors[i]) arrow.Add( PointHelper.LinearCombination( points[i + 1], distance, PointHelper.LinearCombination(leftNormal[i], tangent, unitVectors[i]))); // points[i+1] + distance * (rightNormal[i] - tangent * unitVectors[i]) backArrow.Add( PointHelper.LinearCombination( points[i + 1], distance, PointHelper.LinearCombination(rightNormal[i], -tangent, unitVectors[i]))); } points.Clear(); count = backArrow.Count; for (int i = count - 1; i >= 0; i--) { arrow.Add(backArrow[i]); } // Add the origin and the projected arrowhead point to the list of arrow points. // We need to get these points transformed and this // is currently the only way to do it. // We'll have to remove them later from the list. arrow.Add(origin); arrow.Add(pt); mg.IsSpline = false; IList <ILocation> anchors = arrow.Select(a => MapHelper.PixelToLatLon(a, 4.0)).ToList(); Path path = MilZone.GenerateLines(mg, false, anchors.Count - 2, anchors, out points); // OK, let's record the transformed origin and kill these extra points. int index = points.Count - 1; pt = points[index]; points.RemoveAt(index); anchors.RemoveAt(index); index--; origin = points[index]; points.RemoveAt(index); anchors.RemoveAt(index); double newScaleFactor; MilGraphic.FullTransformation(mg, path, null, points, anchors, origin, out newScaleFactor); // We also need the oldScaleFactor - the scaleFactor that the rest of the graphic will be using. double oldScaleFactor; MilGraphic.ComputeScales(MapHelper.ComputeTransform(inPoints, inAnchors), out oldScaleFactor); // OK now we need to scale the transform matrix relative to the oldScaleFactor var r = newScaleFactor / oldScaleFactor; SetPathMatrix(r, path); mg.AddChild("Boundary", path); if (mg.StencilType == "AxisOfAdvanceForFeint") { // We need to add LabelT but the position (right after the projection // of the arrow head onto the main arrow axis) needs to be scaled according // to our transformation which uses the "r" scale factor. var pointNew = PointHelper.LinearCombination(origin, r, PointHelper.Minus(pt, origin)); mg.AddChild( "Label", MilLine.GenerateLabels(mg, new[] { mg.LabelT }, pointNew, origin, origin, new Point(1.0, 0.5))); } else if (mg.StencilType == "AxisOfAdvanceRotaryWing") { // Add rotary wing symbol by appending to existing path AddRotaryWing(path, points); } return(points); }
/// <summary> /// AddRotaryWing deals with the messiness that a rotary wing introduces such as the crossing /// of the arrow boundary lines and the insertion of the rotary symbol. /// This is a brute force method which really knocks up the code count. /// Should revisit when all else is done. /// </summary> /// <param name="path"> /// The Path element that represents the rotary wing. /// </param> /// <param name="pts"> /// The array of points representing the head to tail rotary wing arrow vertices. /// </param> private static void AddRotaryWing(Path path, IList <Point> pts) { // We need to find the intersection of the first and last lines of the arrow. // Find alpha or beta to make the two lines intersect. We can dot the following // equation with the normal to the vector from pts[0] to pts[1]. // // pts[0] + alpha * (pts[1] - pts[0]) = pts[Count-2] + beta * (pts[Count-1] - pts[Count-2]) int count = pts.Count; var normal = PointHelper.LeftNormal(pts[0], pts[1]); // vector perpendicular to one arrow boundary var boundary = PointHelper.Vector(pts[count - 2], pts[count - 1]); // vector parallel to opposite arrow boundary var axis = PointHelper.Vector(pts[count - 2], pts[0]); // vector parallel to arrow axis double beta = PointHelper.DotProduct(axis, normal) / PointHelper.DotProduct(boundary, normal); var intersect = PointHelper.LinearCombination(pts[count - 2], beta, boundary); // intersection of arrow boundaries var topNormal = PointHelper.LeftUnitNormal(pts[0], pts[count - 2]); // unit vector perpendicular to arrow axis var bottomNormal = PointHelper.RightUnitNormal(pts[0], pts[count - 2]); // other perpendicular unit vector // Y is smaller at the top in this coordinate system if (topNormal.Y > bottomNormal.Y) { var temp = topNormal; topNormal = bottomNormal; bottomNormal = temp; } double distance = PointHelper.Magnitude(pts[0], pts[pts.Count - 1]) / 2.0; // half the arrow width var top = PointHelper.LinearCombination(intersect, distance, topNormal); var bottom = PointHelper.LinearCombination(intersect, distance, bottomNormal); // Compute the base of the rotary wing symbol var unitAxis = PointHelper.UnitVector(axis); var startBase = PointHelper.LinearCombination(bottom, 0.25 * distance, unitAxis); var endBase = PointHelper.LinearCombination(bottom, -0.25 * distance, unitAxis); // Compute the tip of the rotary wing symbol var t = PointHelper.LinearCombination(intersect, 0.75 * distance, topNormal); var startTip = PointHelper.LinearCombination(t, 0.25 * distance, unitAxis); var endTip = PointHelper.LinearCombination(t, -0.25 * distance, unitAxis); // Compute the sides of the rotary wing symbol var bnA = PointHelper.UnitVector(pts[0], pts[1]); var bnB = PointHelper.UnitVector(pts[count - 1], pts[count - 2]); distance = PointHelper.Magnitude(pts[0], pts[1]); var sideOneA = PointHelper.LinearCombination(intersect, 0.1 * distance, bnA); var sideOneB = PointHelper.LinearCombination(intersect, 0.1 * distance, bnB); var sideTwoA = PointHelper.LinearCombination(intersect, -0.1 * distance, bnA); var sideTwoB = PointHelper.LinearCombination(intersect, -0.1 * distance, bnB); PathFigureCollection pfc = ((PathGeometry)path.Data).Figures; IEnumerable <PathFigure> pfs = new List <PathFigure> { Polyline(top, bottom), Polyline(startBase, endBase), Polyline(startTip, top, endTip), Polyline(sideOneA, sideOneB), Polyline(sideTwoA, sideTwoB) }; foreach (var p in pfs) { pfc.Add(p); } }