/// <summary> /// Generates phase lines. /// </summary> /// <param name="mg"> /// The MilGraphic associated with the phase lines. /// </param> /// <param name="anchors"> /// The map locations of the polyline vertices. /// </param> /// <param name="points"> /// The map locations converted to the ResourceDictionary space. /// </param> /// <returns> /// The graphical Path associated with the phase lines. /// </returns> public static Path GeneratePhaseLine( MilGraphic mg, IList <ILocation> anchors, out IList <Point> points) { return(MilZone.GenerateLines(mg, false, anchors.Count, anchors, out points)); }
/// <summary> /// Generates multiple types of decorated lines. /// </summary> /// <param name="mg"> /// The MilGraphic associated with the decorated lines. /// </param> /// <param name="anchors"> /// The map locations of the polyline vertices. /// </param> /// <param name="points"> /// The map locations converted to the ResourceDictionary space. /// </param> /// <returns> /// The graphical Path associated with the decorated lines. /// </returns> public static Path GenerateFlot( MilGraphic mg, IList <ILocation> anchors, out IList <Point> points) { // 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 = anchors.Count; points = MapHelper.LatLonsToPixels(anchors, 4.0); // Convert everything into a Cartesian space about [-300, 300] in each direction MilZone.ScaleData(points, null, null); // Now generate a collection of arcs along these line segments var style = mg.ContentControl.UnframedLine; PathFigureCollection figures; if (mg.StencilType == "FortifiedArea") { points.Add(points[0]); figures = GenerateDecoratedSegments(mg, DecoratedType.Square, false, count + 1, points); points.RemoveAt(count); } else if (mg.StencilType == "ObstacleLine") { figures = GenerateDecoratedSegments(mg, DecoratedType.Triangular, false, count, points); } else if (mg.StencilType == "AntiTankDitchUnderConstruction") { figures = GenerateDecoratedSegments(mg, DecoratedType.Saw, false, count, points); } else if (mg.StencilType == "AntiTankDitchComplete") { figures = GenerateDecoratedSegments(mg, DecoratedType.Saw, false, count, points); style = mg.ContentControl.UnframedLineFill; } else if (mg.StencilType == "AntiTankDitchAntiMine") { figures = GenerateDecoratedSegments(mg, DecoratedType.SolidTriangular, false, count, points); style = mg.ContentControl.UnframedLineFill; } else { figures = new PathFigureCollection { new PathFigure { Segments = GenerateArcSegments(count, points) } }; } // Set the start point for the first figure (usually there is only one figure) figures[0].StartPoint = points[0]; var path = new Path { Data = new PathGeometry { Figures = figures }, Style = style }; #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); }