Ejemplo n.º 1
0
        /// <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);
        }
Ejemplo n.º 2
0
        /// <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);
        }