/// <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> /// Set the text labels for the mil air zones, in a text block. /// </summary> /// <param name="mg"> /// The MilGraphic associated with the labels. /// </param> /// <param name="labels"> /// The label strings. /// </param> /// <returns> /// A TextBlock containing the label strings. /// </returns> private static TextBlock GenerateLabels(MilGraphic mg, string[] labels) { var tb = new TextBlock { Style = SymbolData.GetStyle("BT20"), Foreground = mg.ContentControl.Brush, }; tb.SetBinding(UIElement.VisibilityProperty, new Binding { Source = mg.TextVisibility, Mode = BindingMode.OneWay }); var count = labels.Length; for (int i = 0; i < count; i++) { tb.Inlines.Add(new Run { Text = labels[i] }); if (i < count - 1) { tb.Inlines.Add(new LineBreak()); } } tb.FindTextExtent(); tb.SetValue(Canvas.TopProperty, mg.LabelOffset.Y * HalfSize); tb.SetValue(Canvas.LeftProperty, (mg.LabelOffset.X * HalfSize) - (tb.Width / 2.0)); return(tb); }
/// <summary> /// Adds a generic zone with labels to the MilGraphic and returns the list of transformed points. /// </summary> /// <param name="mg"> /// The MilGraphic to receive the zone and the labels. /// </param> /// <param name="anchors"> /// The list of map locations making up the zone. /// </param> /// <param name="labels"> /// The labels associated with the zone. /// </param> /// <returns> /// The list of map locations transformed into ResourceDictionary space. /// </returns> public static IList <Point> AddZone(MilGraphic mg, IList <ILocation> anchors, string[] labels) { IList <Point> points; ApplyOffset(mg); // applies offset to any existing text mg.AddChild("Boundary", GenerateLines(mg, true, anchors.Count, anchors, out points)); mg.AddChild("Labels", GenerateLabels(mg, labels)); return(points); }
/// <summary> /// Set the text labels for the mil lines, in a text block /// </summary> /// <param name="mg"> /// The MilGraphic associated with the labels. /// </param> /// <param name="labels"> /// The label strings. /// </param> /// <param name="pointStart"> /// The starting point of the line to label. /// </param> /// <param name="pointEnd"> /// The ending point of the line to label. /// </param> /// <param name="origin"> /// The origin coordinate for the object as a whole. /// </param> /// <param name="offset"> /// The multiplicative offset for the object as a whole. /// </param> /// <returns> /// The TextBlock representing the labels. /// </returns> public static TextBlock GenerateLabels( MilGraphic mg, string[] labels, Point pointStart, Point pointEnd, Point origin, Point offset) { bool leftSide; MatrixTransform mt = GenerateLabelTransform(pointStart, pointEnd, origin, out leftSide); var tb = new TextBlock { Style = SymbolData.GetStyle("BT20"), RenderTransform = mt, Foreground = mg.ContentControl.Brush, Name = "Skip" + counter++ }; tb.SetBinding(UIElement.VisibilityProperty, new Binding { Source = mg.TextVisibility, Mode = BindingMode.OneWay }); var count = labels.Length; for (int i = 0; i < count; i++) { tb.Inlines.Add(new Run { Text = labels[i] }); if (i < count - 1) { tb.Inlines.Add(new LineBreak()); } } tb.FindTextExtent(); double left; // if (offset.Y == 0.5) if (Math.Abs(offset.Y - 0.5) <= double.Epsilon) { left = leftSide ? tb.Width : 0.0; } else { left = leftSide ? 0.0 : tb.Width; } //// if (left != 0) ////if (Math.Abs(left) > double.Epsilon) ////{ //// tb.TextAlignment = TextAlignment.Right; ////} ////Point p = mt.Matrix.Rotate(new Point(tb.Height / 2.0, left)); Point p = mt.Matrix.Rotate(new Point(offset.Y * tb.Height, offset.X * left)); tb.SetValue(Canvas.TopProperty, pointStart.Y - p.X); tb.SetValue(Canvas.LeftProperty, pointStart.X - p.Y); return(tb); }
/// <summary> /// Plots a 3+ point tactical graphic /// </summary> /// <param name="sc"> /// The symbol code to plot. /// </param> /// <param name="loc"> /// The location collection that defines the spatial extent of the symbol. /// </param> public void PlotSymbol(string sc, ILocationCollection loc) { this.pg = new MilGraphic(sc, loc); ToolTipService.SetToolTip(this.pg, sc); this.milsymLayer.AddSymbol(this.pg); // Draw the base and transformed base vectors this.polyLayer.AddPolyline(loc, new SolidColorBrush(Colors.Blue)); }
/// <summary> /// Plots a 3+ point tactical graphic /// </summary> /// <param name="symbolCode"> /// The symbol code to plot. /// </param> /// <param name="triads"> /// The map locations for the graphic. /// </param> /// <param name="offset"> /// An offset used to displace the graphic. /// </param> public void PlotSymbol(string symbolCode, ILocationCollection[] triads, Point offset) { var sc = symbolCode; foreach (var old in triads) { sc = NextCode(sc); var lc = this.milsymFactory.LocationCollection(); foreach (var loc in old) { lc.Add(this.milsymFactory.Location(Order.LatLon, loc.Latitude - offset.Y, loc.Longitude + offset.X)); } var saveLoc = this.milsymFactory.LocationCollection(); // Test to make sure presentation is still consistent with points re-ordered if (lc.Count == 3) { saveLoc.Add(lc[2]); saveLoc.Add(lc[1]); saveLoc.Add(lc[0]); } else { foreach (var loc in lc) { saveLoc.Add(loc); // can't re-order with four points } } var spline = sc.Contains("GFGPGLL"); var pg = new MilGraphic( sc, saveLoc, labelOffset: new Offset(-0.25, 0.25), labelString: "T=Hello;T1=Denver;X=2000' AGL;X1=4000' AGL", labelW: new DateTime(2010, 12, 18).ToString(CultureInfo.InvariantCulture), labelW1: new DateTime(2010, 12, 19).ToString(CultureInfo.InvariantCulture), isSpline: spline); ToolTipService.SetToolTip(pg, sc); this.milsymLayer.AddSymbol(pg); // Draw the base and transformed base vectors //this.polyLayer.AddPolyline(saveLoc, new SolidColorBrush(Colors.Orange)); //this.polyLayer.AddPolyline(lc, new SolidColorBrush(Colors.Purple)); } }
/// <summary> /// Apply an offset to all the text elements from the ResourceDictionary's template /// </summary> /// <param name="mg"> /// The MilGraphic containing the text elements. /// </param> private static void ApplyOffset(MilGraphic mg) { var canvas = VisualTreeHelper.GetChild(mg.ContentControl, 0) as Canvas; if (canvas == null) { return; } foreach (var tb in canvas.Children.OfType <TextBlock>()) { tb.FindTextExtent(); tb.SetValue(Canvas.TopProperty, (mg.LabelOffset.Y * HalfSize) - tb.Height); tb.SetValue(Canvas.LeftProperty, (mg.LabelOffset.X * HalfSize) - (tb.Width / 2.0)); } }
/// <summary> /// Adds an air zone to the MilGraphic and returns the list of transformed points. /// </summary> /// <param name="mg"> /// The MilGraphic to receive the air zone. /// </param> /// <param name="anchors"> /// The list of map locations making up the zone. /// </param> /// <returns> /// The list of map locations transformed into ResourceDictionary space. /// </returns> public static IList <Point> AddAirZone(MilGraphic mg, IList <ILocation> anchors) { IList <Point> points; ApplyOffset(mg); // applies offset to any existing text mg.AddChild("Boundary", GenerateLines(mg, true, anchors.Count, anchors, out points)); var labels = new[] { mg.LabelT, "Min Alt: " + mg.LabelX, "Max Alt: " + mg.LabelX1, "Time from: " + mg.LabelW, "Time to: " + mg.LabelW1 }; mg.AddChild("Labels", GenerateLabels(mg, labels)); return(points); }
/// <summary> /// A generic polyline drawing method with labels at either end of end /// </summary> /// <param name="mg"> /// The MilGraphic for the associated polyline. /// </param> /// <param name="gl"> /// The actual line generator to call. /// </param> /// <param name="labels"> /// The labels for the line ends. /// </param> /// <param name="anchors"> /// The map locations of the vertices. /// </param> /// <returns> /// The map locations converted to the ResourceDictionary space. /// </returns> public static IList <Point> AddLines( MilGraphic mg, GenerateLines gl, string[] labels, IList <ILocation> anchors) { IList <Point> points; mg.AddChild("Boundary", gl(mg, anchors, out points)); if (labels != null) { var c = points.Count; Point origin = points[0]; // Offset = new Point(1.0, 0.5) mg.AddChild("StartLabel", GenerateLabels(mg, labels, points[0], points[1], origin, new Point(1.0, 0.5))); mg.AddChild("EndLabel", GenerateLabels(mg, labels, points[c - 1], points[c - 2], origin, new Point(1.0, 0.5))); } return(points); }
/// <summary> /// Adds a weapons free zone to the MilGraphic and returns the list of transformed points. /// </summary> /// <param name="mg"> /// The MilGraphic to receive the weapons free zone. /// </param> /// <param name="anchors"> /// The list of map locations making up the zone. /// </param> /// <returns> /// The list of map locations transformed into ResourceDictionary space. /// </returns> public static IList <Point> AddWeaponsFreeZone(MilGraphic mg, IList <ILocation> anchors) { IList <Point> points; ApplyOffset(mg); // applies offset to any existing text Path path = GenerateLines(mg, true, anchors.Count, anchors, out points); path.Fill = HatchBrush(mg.ContentControl.Brush); mg.AddChild("Boundary", path); var labels = new[] { mg.LabelT, "Time from: " + mg.LabelW, "Time to: " + mg.LabelW1 }; mg.AddChild("Labels", GenerateLabels(mg, labels)); return(points); }
/// <summary> /// Generates obstacle lines for the associated MilGraphic. /// </summary> /// <param name="mg"> /// The associated MilGraphic. /// </param> /// <param name="anchors"> /// The map locations of the vertices. /// </param> /// <returns> /// The map locations converted to the ResourceDictionary space. /// </returns> public static IList <Point> AddObstacles(MilGraphic mg, IList <ILocation> anchors) { return(AddLines(mg, GenerateFlot, null, anchors)); }
/// <summary> /// Generates forward line of own troops for the associated MilGraphic. /// </summary> /// <param name="mg"> /// The associated MilGraphic. /// </param> /// <param name="anchors"> /// The map locations of the vertices. /// </param> /// <returns> /// The map locations converted to the ResourceDictionary space. /// </returns> public static IList <Point> AddFlot(MilGraphic mg, IList <ILocation> anchors) { return(AddLines(mg, GenerateFlot, new[] { "FLOT" }, anchors)); }
/// <summary> /// Generates phase lines for the associated MilGraphic. /// </summary> /// <param name="mg"> /// The associated MilGraphic. /// </param> /// <param name="anchors"> /// The map locations of the vertices. /// </param> /// <returns> /// The map locations converted to the ResourceDictionary space. /// </returns> public static IList <Point> AddPhaseLine(MilGraphic mg, IList <ILocation> anchors) { return(AddLines(mg, GeneratePhaseLine, new[] { "PL " + mg.LabelT }, anchors)); }
/// <summary> /// Generates boundary lines for the associated MilGraphic. /// </summary> /// <param name="mg"> /// The associated MilGraphic. /// </param> /// <param name="anchors"> /// The map locations of the vertices. /// </param> /// <returns> /// The map locations converted to the ResourceDictionary space. /// </returns> public static IList <Point> AddBoundary(MilGraphic mg, IList <ILocation> anchors) { return(AddLines(mg, GenerateBoundaryLine, null, anchors)); }
/// <summary> /// Generates phase lines for the associated MilGraphic. /// </summary> /// <param name="mg"> /// The associated MilGraphic. /// </param> /// <param name="anchors"> /// The map locations of the vertices. /// </param> /// <param name="header"> /// The labels. /// </param> /// <returns> /// The map locations converted to the ResourceDictionary space. /// </returns> public static IList <Point> AddPhaseLineType(MilGraphic mg, IList <ILocation> anchors, string header) { return(AddLines(mg, GeneratePhaseLine, new[] { header, "(PL " + mg.LabelT + ")" }, anchors)); }
/// <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> /// 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> /// 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> /// Generate a collection of decorated segments along a polyline /// </summary> /// <param name="mg"> /// The MilGraphic to receive the decorated line segments. /// </param> /// <param name="type"> /// The type of decoration from the enumerated values. /// </param> /// <param name="drawInside"> /// Whether to draw the features on the "interior" of the figure. /// </param> /// <param name="count"> /// The vertex count. /// </param> /// <param name="pts"> /// The array of vertices. /// </param> /// <returns> /// A graphic object representing the decorated lines. /// </returns> public static PathFigureCollection GenerateDecoratedSegments( MilGraphic mg, DecoratedType type, bool drawInside, int count, IList <Point> pts) { // Most of the time we'll have a single PathFigure consisting // of a single PolyLineSegment. // But occasionally the PathFigure will have more segments // and occasionally there will be more than one path figure. var pfc = new PathFigureCollection(); // Collect the points we're passing through var ptsLoc = new PointCollection(); // In many cases there will be only one polyline segment // but sometimes we'll have multiple segments which may, // or may not require multiple paths. var segments = new PathSegmentCollection(); double totalHypot; var hypots = CalculateHypots(count, pts, out totalHypot); var arcSize = 0.5 * (totalHypot / Math.Max(1, (int)(totalHypot / (2.0 * ArcSize)))); bool odd = true; // switch between even and odd int totalCount = 0; var leftOver = 0.0; // Break each segment up into parts for (var i = 1; i < count; ++i) { // Get the angle of the line segment Point para = MapHelper.Normalize(pts[i - 1], pts[i]); var perp = new Point(-para.Y, para.X); Point outside = PointHelper.Add(para, perp); if (drawInside != PointHelper.PointInPolygon(outside, count - 1, pts)) { perp = new Point(para.Y, -para.X); } // Need to round here to make sure we get last segment var segNum = (int)Math.Floor((hypots[i] + leftOver + 0.5) / arcSize); // Advance along the line segment using the arcSize var nextPt = pts[i - 1]; Point lastPt = nextPt; for (var j = 0; j < segNum; j++) { var stepSize = (j == 0) ? arcSize - leftOver : arcSize; nextPt = PointHelper.LinearCombination(nextPt, stepSize, para); if (type == DecoratedType.Saw) { var mid = PointHelper.MidPoint(nextPt, lastPt); ptsLoc.Add(PointHelper.LinearCombination(mid, stepSize, perp)); } if (odd) { // Add the mines to the anti-tank ditch if (type == DecoratedType.SolidTriangular) { var r = stepSize / 3.0; var p1 = PointHelper.MidPoint(nextPt, lastPt); var p2 = PointHelper.LinearCombination(p1, r, perp); var p3 = PointHelper.LinearCombination(p2, 2.0 * r, perp); var size = new Size(r, r); ptsLoc.Add(p1); ptsLoc.Add(p2); segments.Add(new PolyLineSegment { Points = ptsLoc }); segments.Add(new ArcSegment { Point = p3, Size = size }); segments.Add(new ArcSegment { Point = p2, Size = size }); ptsLoc = new PointCollection { p1 }; } } else { if (type == DecoratedType.Triangular || type == DecoratedType.SolidTriangular) { var mid = PointHelper.MidPoint(nextPt, lastPt); ptsLoc.Add(PointHelper.LinearCombination(mid, stepSize, perp)); } else if (type == DecoratedType.Square) { ptsLoc.Add(PointHelper.LinearCombination(lastPt, stepSize, perp)); ptsLoc.Add(PointHelper.LinearCombination(nextPt, stepSize, perp)); } } if (type == DecoratedType.Echelon) { // Skip every fifth line segment - approximately. // Ignore the fixups at the vertices. if ((++totalCount % 5) == 0) { segments.Add(new PolyLineSegment { Points = ptsLoc }); pfc.Add(new PathFigure { Segments = segments, StartPoint = ptsLoc[0] }); ptsLoc = new PointCollection(); segments = new PathSegmentCollection(); } odd = false; // force odd to always be true - picks up every vertex } ptsLoc.Add(nextPt); odd = !odd; lastPt = nextPt; } if (odd && PointHelper.Magnitude(nextPt, pts[i]) > 1.0) { ptsLoc.Add(pts[i]); } leftOver = hypots[i] - MapHelper.Hypoteneuse(nextPt, pts[i - 1]); } if (type == DecoratedType.Saw || type == DecoratedType.SolidTriangular) { for (int i = count - 2; i >= 0; i--) { ptsLoc.Add(pts[i]); } } if (ptsLoc.Count > 0) { segments.Add(new PolyLineSegment { Points = ptsLoc }); } if (segments.Count > 0) { var pf = new PathFigure { Segments = segments }; // For the echelon type we need to set the start point of this segment if (type == DecoratedType.Echelon) { pf.StartPoint = ptsLoc[0]; } pfc.Add(pf); } return(pfc); }