/// <summary> /// The main zone generating method. /// </summary> /// <param name="mg"> /// The MilGraphic associated with the zone to be generated. /// </param> /// <param name="isClosed"> /// Whether or not the zone outline is to be closed since this method can be used for polylines as well. /// </param> /// <param name="count"> /// The count of the map locations making up the zone. /// </param> /// <param name="anchors"> /// The map locations making up the zone. /// </param> /// <param name="pointAnchors"> /// The locations in ResourceDictionary space. /// </param> /// <returns> /// A Path representing the map locations, to be associated with the MilGraphic. /// </returns> public static Path GenerateLines( MilGraphic mg, bool isClosed, // do we close the boundary int count, // the number of transformed points to use in boundary IList <ILocation> anchors, out IList <Point> pointAnchors) { IList <Point> pointCp1 = null; IList <Point> pointCp2 = null; pointAnchors = MapHelper.LatLonsToPixels(anchors, 2.0); // Need to convert the cardinal spline points into Bezier spline control points if (mg.IsSpline) { ClosedBezierSpline.GetCurveControlPoints(count, pointAnchors, out pointCp1, out pointCp2); } ScaleData(pointAnchors, pointCp1, pointCp2); // Generate Bezier or line segments for insertion into a figure collection PathFigureCollection figures; if (mg.IsSpline) { figures = new PathFigureCollection { new PathFigure { Segments = GenerateBezierSegments(count, pointAnchors, pointCp1, pointCp2, isClosed) } }; } else { switch (mg.StencilType) { case "ObstacleZone": case "ObstacleBelt": pointAnchors.Add(pointAnchors[0]); figures = MilLine.GenerateDecoratedSegments( mg, DecoratedType.Triangular, false, count + 1, pointAnchors); pointAnchors.RemoveAt(count); break; case "ObstacleRestrictedArea": case "ObstacleFreeArea": pointAnchors.Add(pointAnchors[0]); figures = MilLine.GenerateDecoratedSegments( mg, DecoratedType.Triangular, true, count + 1, pointAnchors); pointAnchors.RemoveAt(count); break; case "Boundaries": figures = MilLine.GenerateDecoratedSegments(mg, DecoratedType.Echelon, true, count, pointAnchors); var labels = new[] { mg.LabelT, Echelon.GetEchelonSymbol(mg.SymbolCode), mg.LabelT1 }; int figureCount = figures.Count; for (int i = 0; i < figureCount - 1; i++) { var pls = figures[i].Segments[0] as PolyLineSegment; if (pls == null) { continue; } var pc = pls.Points.Count - 1; if (pc > 0) { mg.AddChild( "Label" + i, MilLine.GenerateLabels( mg, labels, pls.Points[pc], pls.Points[pc - 1], pointAnchors[0], new Point(1.0, 0.5))); } } break; default: figures = new PathFigureCollection { new PathFigure { Segments = GeneratePolygonSegments(count, pointAnchors) } }; break; } } // In most cases we have a single figure figures[0].StartPoint = pointAnchors[0]; figures[0].IsClosed = isClosed; var path = new Path { Data = new PathGeometry { Figures = figures }, Style = mg.ContentControl.UnframedLine }; if (mg.StencilType == "ObstacleRestrictedArea") { path.Fill = HatchBrush(mg.ContentControl.Brush); } #if WINDOWS_UWP path.SetBinding(Shape.StrokeThicknessProperty, new Binding() { Path = new PropertyPath("LineThickness"), Source = mg.ContentControl }); #else path.SetBinding(Shape.StrokeThicknessProperty, new Binding("LineThickness") { Source = mg.ContentControl }); #endif return(path); }
/// <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); }