//Use this method if you want keyframes ONLY at line vertices. This is good if the line is highly densified.
        //However, you will get sharp turns at corners because there is no attempt to smooth the animation
        public static async Task CreateKeyframes_AtVertices(MapView mapView, SpatialReference layerSpatRef, ProjectionTransformation transformation,
                                                            CameraTrack cameraTrack, Polyline lineGeom, IEnumerator <ReadOnlySegmentCollection> segments,
                                                            int segmentCount, double pathLength)
        {
            double segmentLength  = 0;
            int    num_iterations = 0;

            segments.Reset();

            //process each segment depending upon its type - straight line or arc
            while (segments.MoveNext())
            {
                ReadOnlySegmentCollection seg = segments.Current;
                double accumulatedDuration    = mapView.Map.Animation.Duration.TotalSeconds + ((mapView.Map.Animation.Duration.TotalSeconds > 0) ? ANIMATION_APPEND_TIME : 0); // 0;

                foreach (Segment s in seg)
                {
                    segmentLength = Math.Sqrt((s.EndPoint.X - s.StartPoint.X) * (s.EndPoint.X - s.StartPoint.X) +
                                              (s.EndPoint.Y - s.StartPoint.Y) * (s.EndPoint.Y - s.StartPoint.Y) +
                                              (s.EndPoint.Z - s.StartPoint.Z) * (s.EndPoint.Z - s.StartPoint.Z));

                    double segmentDuration = (TotalDuration / pathLength) * segmentLength;

                    MapPoint startPt = await QueuedTask.Run(() => MapPointBuilder.CreateMapPoint(s.StartPoint.X, s.StartPoint.Y, s.StartPoint.Z, layerSpatRef));

                    MapPoint endPt = await QueuedTask.Run(() => MapPointBuilder.CreateMapPoint(s.EndPoint.X, s.EndPoint.Y, s.EndPoint.Z, layerSpatRef));

                    //create keyframe at start vertex of path in map space
                    double   timeSpanValue    = accumulatedDuration;
                    TimeSpan keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);
                    SetPitchAndHeadingForLine(startPt, endPt);
                    await CreateCameraKeyframe(mapView, startPt, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);

                    //Create a keyframe at end point of segment only for the end point of last segment
                    //Otherwise we will get duplicate keyframes at end of one segment and start of the next one
                    if (num_iterations == segmentCount - 1)
                    {
                        timeSpanValue    = accumulatedDuration + segmentDuration;
                        keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);

                        if (SelectedCameraView == "Face target")
                        {
                            SetPitchAndHeadingForLine(endPt, TargetPoint);
                        }

                        await CreateCameraKeyframe(mapView, endPt, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);
                    }

                    accumulatedDuration += segmentDuration;
                    num_iterations++;
                }
            }
        }
예제 #2
0
        protected override async void OnClick()
        {
            FeatureLayer             ftrLayer       = (MapView.Active.Map.Layers.First(layer => layer.Name.Equals("MyRide")) as FeatureLayer);
            ProjectionTransformation transformation = await QueuedTask.Run(() => ProjectionTransformation.Create(ftrLayer.GetSpatialReference(), MapView.Active.Map.SpatialReference));

            SpatialReference layerSpatRef = await QueuedTask.Run(() => ftrLayer.GetSpatialReference());

            Polyline lineGeom = await GetPolyFineFromLayer(ftrLayer);

            //couldn't get the selected feature
            if (lineGeom == null)
            {
                return;
            }

            var animation   = MapView.Active.Map.Animation;
            var cameraTrack = animation.Tracks.OfType <CameraTrack>().First();
            var keyframes   = cameraTrack.Keyframes;

            //Get segment list for line
            ReadOnlyPartCollection polylineParts = lineGeom.Parts;

            //get total segment count and determine path length
            double pathLength   = 0;
            int    segmentCount = 0;
            IEnumerator <ReadOnlySegmentCollection> segments = polylineParts.GetEnumerator();

            while (segments.MoveNext())
            {
                ReadOnlySegmentCollection seg = segments.Current;
                foreach (Segment s in seg)
                {
                    double length3D = Math.Sqrt((s.EndPoint.X - s.StartPoint.X) * (s.EndPoint.X - s.StartPoint.X) +
                                                (s.EndPoint.Y - s.StartPoint.Y) * (s.EndPoint.Y - s.StartPoint.Y) +
                                                (s.EndPoint.Z - s.StartPoint.Z) * (s.EndPoint.Z - s.StartPoint.Z));
                    pathLength   += length3D;
                    segmentCount += 1;
                }
            }

            await CreateKeyframes_AtVertices(MapView.Active, layerSpatRef, transformation, cameraTrack, segments, segmentCount, pathLength);
        }
예제 #3
0
 public static bool IsCcw(ReadOnlySegmentCollection ring)
 {
     return(IsCcw(ring.GetPoints().ToList()));
 }
        //Use this method to create a keyframe at every n-second of the specified animation duration
        public static async Task CreateKeyframes_EveryNSeconds(MapView mapView, SpatialReference layerSpatRef, ProjectionTransformation transformation,
                                                               CameraTrack cameraTrack, IEnumerator <ReadOnlySegmentCollection> segments,
                                                               int segmentCount, double pathLength, double keyEveryNSecond = 1)
        {
            double segmentLength   = 0;
            int    numKeysToCreate = (int)(TotalDuration / keyEveryNSecond); //approximately
            double createKeyAtDist = pathLength / numKeysToCreate;

            double skippedDistance     = 0;
            double accumulatedDuration = mapView.Map.Animation.Duration.TotalSeconds + ((mapView.Map.Animation.Duration.TotalSeconds > 0) ? ANIMATION_APPEND_TIME : 0); // 0;

            int num_iterations = 0;

            segments.Reset();

            List <MapPoint> pointsForKeyframes = new List <MapPoint>();

            MapPoint pathEndPt = null;

            //process each segment depending upon its type - straight line or arc
            while (segments.MoveNext())
            {
                ReadOnlySegmentCollection seg = segments.Current;

                foreach (Segment s in seg)
                {
                    segmentLength = Math.Sqrt((s.EndPoint.X - s.StartPoint.X) * (s.EndPoint.X - s.StartPoint.X) +
                                              (s.EndPoint.Y - s.StartPoint.Y) * (s.EndPoint.Y - s.StartPoint.Y) +
                                              (s.EndPoint.Z - s.StartPoint.Z) * (s.EndPoint.Z - s.StartPoint.Z));

                    double segmentDuration = (TotalDuration / pathLength) * segmentLength;

                    //straight line segments
                    if (s.SegmentType == SegmentType.Line)
                    {
                        MapPoint startPt = await QueuedTask.Run(() => MapPointBuilder.CreateMapPoint(s.StartPoint.X, s.StartPoint.Y, s.StartPoint.Z, layerSpatRef));

                        MapPoint endPt = await QueuedTask.Run(() => MapPointBuilder.CreateMapPoint(s.EndPoint.X, s.EndPoint.Y, s.EndPoint.Z, layerSpatRef));

                        //add start of path to points collection
                        if (num_iterations == 0)
                        {
                            pointsForKeyframes.Add(startPt);
                        }

                        if (num_iterations == segmentCount - 1 || segmentCount == 1)
                        {
                            pathEndPt = endPt; //store path end pt. This will be the last keyframe.
                        }

                        double distCoveredAlongSeg = Math.Abs(createKeyAtDist - skippedDistance); //we are accouunting for skipped distances from previous segments

                        if (distCoveredAlongSeg < segmentLength)
                        {
                            MapPoint keyPt = await CreatePointAlongSegment(startPt, endPt, distCoveredAlongSeg, layerSpatRef);

                            //add point to collection
                            pointsForKeyframes.Add(keyPt);

                            //skipped distance is used now, reset to zero
                            skippedDistance = 0;

                            //are more keyframes possible for this segment
                            bool moreKeysPossible = ((segmentLength - distCoveredAlongSeg) >= createKeyAtDist);

                            while (moreKeysPossible)
                            {
                                double keyAtDistAlongSeg = distCoveredAlongSeg + createKeyAtDist;

                                keyPt = await CreatePointAlongSegment(startPt, endPt, keyAtDistAlongSeg, layerSpatRef);

                                //add point to collection
                                pointsForKeyframes.Add(keyPt);

                                distCoveredAlongSeg += createKeyAtDist;

                                moreKeysPossible = ((segmentLength - distCoveredAlongSeg) > createKeyAtDist);
                            }

                            //if any segment length left then add to skipped distance
                            skippedDistance += (segmentLength - distCoveredAlongSeg);
                        }
                        else
                        {
                            //add this segment's length to skipped distance as no keyframe could be created along it
                            skippedDistance += segmentLength;
                        }
                    }
                    else if (s.SegmentType == SegmentType.EllipticArc)
                    {
                        EllipticArcSegment ellipArc = s as EllipticArcSegment;
                        MapPoint           startPt  = await QueuedTask.Run(() => MapPointBuilder.CreateMapPoint(s.StartPoint.X, s.StartPoint.Y, s.StartPoint.Z, layerSpatRef));

                        MapPoint endPt = await QueuedTask.Run(() => MapPointBuilder.CreateMapPoint(s.EndPoint.X, s.EndPoint.Y, s.EndPoint.Z, layerSpatRef));

                        double   radius   = Math.Sqrt((ellipArc.CenterPoint.X - startPt.X) * (ellipArc.CenterPoint.X - startPt.X) + (ellipArc.CenterPoint.Y - startPt.Y) * (ellipArc.CenterPoint.Y - startPt.Y));
                        double   angle    = ellipArc.CentralAngle;
                        MapPoint centerPt = await QueuedTask.Run(() => MapPointBuilder.CreateMapPoint(ellipArc.CenterPoint.X, ellipArc.CenterPoint.Y, (s.StartPoint.Z + s.EndPoint.Z) / 2, layerSpatRef));

                        //add start of path to points collection
                        if (num_iterations == 0)
                        {
                            pointsForKeyframes.Add(startPt);
                        }

                        if (num_iterations == segmentCount - 1 || segmentCount == 1)
                        {
                            pathEndPt = endPt; //store path end pt. This will be the last keyframe.
                        }

                        double distCoveredAlongSeg = Math.Abs(createKeyAtDist - skippedDistance); //we are accouunting for skipped distances from previous segments

                        if (distCoveredAlongSeg < segmentLength)
                        {
                            MapPoint keyPt = await CreatePointAlongArc(startPt, endPt, centerPt, angle *distCoveredAlongSeg / segmentLength, radius, layerSpatRef, ellipArc.IsMinor, ellipArc.IsCounterClockwise);

                            //add point to collection
                            pointsForKeyframes.Add(keyPt);

                            //skipped distance is used now, reset to zero
                            skippedDistance = 0;

                            //are more keyframes possible for this segment
                            bool moreKeysPossible = ((segmentLength - distCoveredAlongSeg) >= createKeyAtDist);

                            while (moreKeysPossible)
                            {
                                double keyAtDistAlongSeg = distCoveredAlongSeg + createKeyAtDist;

                                keyPt = await CreatePointAlongArc(startPt, endPt, centerPt, angle *keyAtDistAlongSeg / segmentLength, radius, layerSpatRef, ellipArc.IsMinor, ellipArc.IsCounterClockwise);

                                //add point to collection
                                pointsForKeyframes.Add(keyPt);

                                distCoveredAlongSeg += createKeyAtDist;

                                moreKeysPossible = ((segmentLength - distCoveredAlongSeg) > createKeyAtDist);
                            }

                            //if any segment length left then add to skipped distance
                            skippedDistance += (segmentLength - distCoveredAlongSeg);
                        }
                        else
                        {
                            //add this segment's length to skipped distance as no keyframe could be created along it
                            skippedDistance += segmentLength;
                        }
                    }

                    num_iterations++;
                }
            }

            //now iterate over the points list and create keyframes

            double   timeSpanValue    = accumulatedDuration;
            TimeSpan keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);

            for (int i = 0; i < pointsForKeyframes.Count; i++)
            {
                MapPoint currentPt = pointsForKeyframes[i];
                MapPoint nextPt    = null;

                if (i + 1 < pointsForKeyframes.Count)
                {
                    nextPt = pointsForKeyframes[i + 1];
                }
                else
                {
                    nextPt = pathEndPt;
                }

                timeSpanValue    = i * keyEveryNSecond + accumulatedDuration;
                keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);

                SetPitchAndHeadingForLine(currentPt, nextPt);
                await CreateCameraKeyframe(mapView, currentPt, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);

                if (i == pointsForKeyframes.Count - 1 && skippedDistance > 0)
                {
                    keyframeTimespan = TimeSpan.FromSeconds(TotalDuration + accumulatedDuration);
                    await CreateCameraKeyframe(mapView, pathEndPt, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);
                }
            }
        }
        //Use this method for smoother turns at corners. Additionally this method processes straight line segments and arc segments separately
        //For arc segments a keyframe is created at every second. However a minimum of 5 keyframes are created for arcs.
        //So if arc segment length is less than 5 then we default to at least 5 keyframes. This is an attempt to stick to the path as much as possible.
        //For straight line segments, rotation is ignored at end point of each segment except for the end point of the path itself. Two keyframes with rotation
        //are created at certain distance (determined by LINE_CONSTRAINT_FACTOR) before and after the end point of each segment. This is an attempt to avoid
        //sharp turns at corners along the path.
        public static async Task CreateKeyframes_AlongPath(MapView mapView, SpatialReference layerSpatRef, ProjectionTransformation transformation,
                                                           CameraTrack cameraTrack, IEnumerator <ReadOnlySegmentCollection> segments,
                                                           int segmentCount, double pathLength)
        {
            double segmentLength  = 0;
            int    num_iterations = 0;

            segments.Reset();

            //process each segment depending upon its type - straight line or arc
            while (segments.MoveNext())
            {
                ReadOnlySegmentCollection seg = segments.Current;
                double accumulatedDuration    = mapView.Map.Animation.Duration.TotalSeconds + ((mapView.Map.Animation.Duration.TotalSeconds > 0) ? ANIMATION_APPEND_TIME : 0); // 0;

                foreach (Segment s in seg)
                {
                    double length3D = Math.Sqrt((s.EndPoint.X - s.StartPoint.X) * (s.EndPoint.X - s.StartPoint.X) +
                                                (s.EndPoint.Y - s.StartPoint.Y) * (s.EndPoint.Y - s.StartPoint.Y) +
                                                (s.EndPoint.Z - s.StartPoint.Z) * (s.EndPoint.Z - s.StartPoint.Z));


                    double segmentDuration = (TotalDuration / pathLength) * length3D;
                    segmentLength = length3D;

                    //straight line segments
                    if (s.SegmentType == SegmentType.Line)
                    {
                        MapPoint startPt = await QueuedTask.Run(() => MapPointBuilder.CreateMapPoint(s.StartPoint.X, s.StartPoint.Y, s.StartPoint.Z *Z_CONVERSION_FACTOR, layerSpatRef));

                        MapPoint endPt = await QueuedTask.Run(() => MapPointBuilder.CreateMapPoint(s.EndPoint.X, s.EndPoint.Y, s.EndPoint.Z *Z_CONVERSION_FACTOR, layerSpatRef));

                        //we will be creating three intermediate keyframes for staright segments only if segment length is more than a set threshold
                        //the threshold is just a guess and might have to be altered depending upon the path geometry. Should work for most cases though
                        MapPoint firstIntPoint = null;
                        MapPoint midIntPoint   = null;
                        MapPoint lastIntPoint  = null;

                        if (segmentLength >= STRAIGHT_SEGMENT_LENGTH_THRESHOLD)
                        {
                            //first intermediate point
                            firstIntPoint = await CreatePointAlongSegment(startPt, endPt, LINE_CONSTRAINT_FACTOR *segmentLength, layerSpatRef);

                            //mid point
                            midIntPoint = await CreatePointAlongSegment(startPt, endPt, 0.5 *segmentLength, layerSpatRef);

                            //last intermediate point
                            lastIntPoint = await CreatePointAlongSegment(startPt, endPt, (1 - LINE_CONSTRAINT_FACTOR) *segmentLength, layerSpatRef);
                        }

                        //create keyframe at start vertex of path in map space
                        double   timeSpanValue    = accumulatedDuration;
                        TimeSpan keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);

                        if (segmentLength >= STRAIGHT_SEGMENT_LENGTH_THRESHOLD)
                        {
                            SetPitchAndHeadingForLine(startPt, firstIntPoint);
                        }
                        else
                        {
                            SetPitchAndHeadingForLine(startPt, endPt);
                        }

                        //ignore rotation for all start vertices (which would also be end vertices of previous segments) EXCEPT for the first vertex of path
                        if (num_iterations == 0 || segmentLength < STRAIGHT_SEGMENT_LENGTH_THRESHOLD)
                        {
                            await CreateCameraKeyframe(mapView, startPt, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);
                        }
                        else
                        {
                            await CreateCameraKeyframe(mapView, startPt, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading, true, false);
                        }

                        if (segmentLength > STRAIGHT_SEGMENT_LENGTH_THRESHOLD)
                        {
                            //Create a keyframe at PATH_CONSTRAINT_FACTOR distance along the segment from start point
                            double distanceAlong = LINE_CONSTRAINT_FACTOR * segmentLength;
                            timeSpanValue    = accumulatedDuration + LINE_CONSTRAINT_FACTOR * segmentDuration;
                            keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);
                            SetPitchAndHeadingForLine(firstIntPoint, midIntPoint);
                            await CreateCameraKeyframe(mapView, firstIntPoint, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);

                            //Create a keyframe at middle of segment
                            distanceAlong    = 0.5 * segmentLength;
                            timeSpanValue    = accumulatedDuration + 0.5 * segmentDuration;
                            keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);
                            SetPitchAndHeadingForLine(midIntPoint, lastIntPoint);
                            //await CreateCameraKeyframe(mapView, midIntPoint, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);

                            //Create a keyframe at (1 - PATH_CONSTRAINT_FACTOR) distance along the segment from start point
                            distanceAlong    = (1 - LINE_CONSTRAINT_FACTOR) * segmentLength;
                            timeSpanValue    = accumulatedDuration + (1 - LINE_CONSTRAINT_FACTOR) * segmentDuration;
                            keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);
                            SetPitchAndHeadingForLine(lastIntPoint, endPt);
                            await CreateCameraKeyframe(mapView, lastIntPoint, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);
                        }

                        //Create a keyframe at end point of segment only for the end point of last segment
                        //Otherwise we will get duplicate keyframes at end of one segment and start of the next one
                        if (num_iterations == segmentCount - 1)
                        {
                            timeSpanValue    = accumulatedDuration + segmentDuration;
                            keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);

                            if (SelectedCameraView == "Face target")
                            {
                                SetPitchAndHeadingForLine(endPt, TargetPoint);
                            }

                            await CreateCameraKeyframe(mapView, endPt, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);
                        }
                    }
                    //processing for arcs - create a keyframe every second for arcs
                    //we will create a minimum of 5 keyframes along the arc
                    else if (s.SegmentType == SegmentType.EllipticArc && segmentDuration > 5)
                    {
                        EllipticArcSegment ellipArc = s as EllipticArcSegment;
                        MapPoint           startPt  = await QueuedTask.Run(() => MapPointBuilder.CreateMapPoint(s.StartPoint.X, s.StartPoint.Y, s.StartPoint.Z, layerSpatRef));

                        MapPoint endPt = await QueuedTask.Run(() => MapPointBuilder.CreateMapPoint(s.EndPoint.X, s.EndPoint.Y, s.EndPoint.Z, layerSpatRef));

                        double   radius   = Math.Sqrt((ellipArc.CenterPoint.X - startPt.X) * (ellipArc.CenterPoint.X - startPt.X) + (ellipArc.CenterPoint.Y - startPt.Y) * (ellipArc.CenterPoint.Y - startPt.Y));
                        double   angle    = ellipArc.CentralAngle;
                        MapPoint centerPt = await QueuedTask.Run(() => MapPointBuilder.CreateMapPoint(ellipArc.CenterPoint.X, ellipArc.CenterPoint.Y, (s.StartPoint.Z + s.EndPoint.Z) / 2, layerSpatRef));

                        int num_keys = (int)segmentDuration;

                        MapPoint firstIntPoint = null;

                        //first intermediate keyframe for arc - needed for setting heading for start vertex
                        // >2 to account for start and end
                        if (num_keys > 2)
                        {
                            firstIntPoint = await CreatePointAlongArc(startPt, endPt, centerPt, angle / (num_keys - 1), radius, layerSpatRef, ellipArc.IsMinor, ellipArc.IsCounterClockwise);
                        }

                        //Create keyframe at start vertex of path in map space
                        double   timeSpanValue    = accumulatedDuration;
                        TimeSpan keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);

                        if (firstIntPoint != null)
                        {
                            SetPitchAndHeadingForLine(startPt, firstIntPoint);
                        }
                        else
                        {
                            SetPitchAndHeadingForLine(startPt, endPt);
                        }

                        //Ignore rotation for all start vertices EXCEPT for the first vertex of path
                        if (num_iterations == 0)
                        {
                            await CreateCameraKeyframe(mapView, startPt, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);
                        }
                        else
                        {
                            await CreateCameraKeyframe(mapView, startPt, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading, true, false);
                        }

                        //Loop to create intermediate keyframes at each second
                        for (int i = 0; i < num_keys - 2; i++)
                        {
                            MapPoint currentIntPoint = null;
                            MapPoint nextIntPoint    = null;

                            currentIntPoint = await CreatePointAlongArc(startPt, endPt, centerPt, (angle / (num_keys - 1)) *(i + 1), radius, layerSpatRef, ellipArc.IsMinor, ellipArc.IsCounterClockwise);

                            if (i < num_keys - 3)
                            {
                                nextIntPoint = await CreatePointAlongArc(startPt, endPt, centerPt, (angle / (num_keys - 1)) *(i + 2), radius, layerSpatRef, ellipArc.IsMinor, ellipArc.IsCounterClockwise);
                            }
                            else //for the last intermediate keyframe, heading/pitch has to be determined relative to the end point fo segment
                            {
                                nextIntPoint = endPt;
                            }
                            //timeSpanValue = accumulatedDuration + (i + 1) * 1; //at each second
                            timeSpanValue = accumulatedDuration + (i + 1) * (segmentDuration / (num_keys - 1));

                            keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);
                            SetPitchAndHeadingForLine(currentIntPoint, nextIntPoint);
                            await CreateCameraKeyframe(mapView, currentIntPoint, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);
                        }

                        //Create a keyframe at end point of segment only for the end point of last segment
                        if (num_iterations == segmentCount - 1)
                        {
                            timeSpanValue    = accumulatedDuration + segmentDuration;
                            keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);

                            if (SelectedCameraView == "Face target")
                            {
                                SetPitchAndHeadingForLine(endPt, TargetPoint);
                            }

                            await CreateCameraKeyframe(mapView, endPt, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);
                        }
                    }
                    //create a minimum of 5 keyframes along the arc
                    else if (s.SegmentType == SegmentType.EllipticArc)
                    {
                        EllipticArcSegment ellipArc = s as EllipticArcSegment;
                        MapPoint           startPt  = await QueuedTask.Run(() => MapPointBuilder.CreateMapPoint(s.StartPoint.X, s.StartPoint.Y, s.StartPoint.Z, layerSpatRef));

                        MapPoint endPt = await QueuedTask.Run(() => MapPointBuilder.CreateMapPoint(s.EndPoint.X, s.EndPoint.Y, s.EndPoint.Z, layerSpatRef));

                        double   radius   = Math.Sqrt((ellipArc.CenterPoint.X - startPt.X) * (ellipArc.CenterPoint.X - startPt.X) + (ellipArc.CenterPoint.Y - startPt.Y) * (ellipArc.CenterPoint.Y - startPt.Y));
                        double   angle    = ellipArc.CentralAngle;
                        MapPoint centerPt = await QueuedTask.Run(() => MapPointBuilder.CreateMapPoint(ellipArc.CenterPoint.X, ellipArc.CenterPoint.Y, (s.StartPoint.Z + s.EndPoint.Z) / 2, layerSpatRef));

                        //we are creating five intermediate keyframes for arcs
                        MapPoint firstIntPoint = await CreatePointAlongArc(startPt, endPt, centerPt, angle *ARC_CONSTRAINT_FACTOR, radius, layerSpatRef, ellipArc.IsMinor, ellipArc.IsCounterClockwise);

                        MapPoint secondIntPoint = await CreatePointAlongArc(startPt, endPt, centerPt, angle *ARC_CONSTRAINT_FACTOR * 2, radius, layerSpatRef, ellipArc.IsMinor, ellipArc.IsCounterClockwise);

                        MapPoint midIntPoint = await CreatePointAlongArc(startPt, endPt, centerPt, angle * 0.5, radius, layerSpatRef, ellipArc.IsMinor, ellipArc.IsCounterClockwise);

                        MapPoint secondLastIntPoint = await CreatePointAlongArc(startPt, endPt, centerPt, angle *(1 - ARC_CONSTRAINT_FACTOR * 2), radius, layerSpatRef, ellipArc.IsMinor, ellipArc.IsCounterClockwise);

                        MapPoint lastIntPoint = await CreatePointAlongArc(startPt, endPt, centerPt, angle *(1 - ARC_CONSTRAINT_FACTOR), radius, layerSpatRef, ellipArc.IsMinor, ellipArc.IsCounterClockwise);

                        //Create keyframe at start vertex of path in map space
                        double   timeSpanValue    = accumulatedDuration;
                        TimeSpan keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);
                        SetPitchAndHeadingForLine(startPt, firstIntPoint);

                        //Ignore rotation for all start vertices EXCEPT for the first vertex of path
                        if (num_iterations == 0)
                        {
                            await CreateCameraKeyframe(mapView, startPt, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);
                        }
                        else
                        {
                            await CreateCameraKeyframe(mapView, startPt, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading, true, false);
                        }

                        //await CreateCameraKeyframe(mapView, startPt, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);

                        //Create a keyframe at PATH_CONSTRAINT_FACTOR distance along the segment from start point
                        timeSpanValue    = accumulatedDuration + ARC_CONSTRAINT_FACTOR * segmentDuration;
                        keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);
                        SetPitchAndHeadingForLine(firstIntPoint, secondIntPoint);
                        await CreateCameraKeyframe(mapView, firstIntPoint, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);

                        //Create a keyframe at 2* PATH_CONSTRAINT_FACTOR distance along the segment from start point
                        timeSpanValue    = accumulatedDuration + ARC_CONSTRAINT_FACTOR * 2 * segmentDuration;
                        keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);
                        SetPitchAndHeadingForLine(secondIntPoint, midIntPoint);
                        await CreateCameraKeyframe(mapView, secondIntPoint, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);

                        //Create a keyframe at middle of segment
                        timeSpanValue    = accumulatedDuration + 0.5 * segmentDuration;
                        keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);
                        SetPitchAndHeadingForLine(midIntPoint, secondLastIntPoint);
                        await CreateCameraKeyframe(mapView, midIntPoint, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);

                        //Create a keyframe at (1 - PATH_CONSTRAINT_FACTOR * 2) distance along the segment from start point
                        timeSpanValue    = accumulatedDuration + (1 - ARC_CONSTRAINT_FACTOR * 2) * segmentDuration;
                        keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);
                        SetPitchAndHeadingForLine(secondLastIntPoint, lastIntPoint);
                        await CreateCameraKeyframe(mapView, secondLastIntPoint, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);

                        //Create a keyframe at (1 - PATH_CONSTRAINT_FACTOR) distance along the segment from start point
                        timeSpanValue    = accumulatedDuration + (1 - ARC_CONSTRAINT_FACTOR) * segmentDuration;
                        keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);
                        SetPitchAndHeadingForLine(lastIntPoint, endPt);
                        await CreateCameraKeyframe(mapView, lastIntPoint, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);

                        //Create a keyframe at end point of segment only for the end point of last segment
                        if (num_iterations == segmentCount - 1)
                        {
                            timeSpanValue    = accumulatedDuration + segmentDuration;
                            keyframeTimespan = TimeSpan.FromSeconds(timeSpanValue);

                            if (SelectedCameraView == "Face target")
                            {
                                SetPitchAndHeadingForLine(endPt, TargetPoint);
                            }

                            await CreateCameraKeyframe(mapView, endPt, transformation, cameraTrack, keyframeTimespan, _keyframePitch, _keyframeHeading);
                        }
                    }

                    accumulatedDuration += segmentDuration;
                    num_iterations++;
                }
            }
        }
        public static async Task CreateKeyframes()
        {
            FeatureLayer ftrLayer = null;

            MapView mapView = MapView.Active;

            if (mapView == null)
            {
                return;
            }

            var mapSelection = await QueuedTask.Run(() => MapView.Active.Map.GetSelection());

            if (mapSelection.Count == 1)
            {
                var layer = mapSelection.First().Key;
                if (layer is FeatureLayer)
                {
                    ftrLayer = (FeatureLayer)layer;
                    if (ftrLayer.ShapeType != ArcGIS.Core.CIM.esriGeometryType.esriGeometryPolyline)
                    {
                        ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show("Select a polyline feature.");
                        return;
                    }

                    int numFtrsSelected = await QueuedTask.Run(() => ftrLayer.GetSelection().GetCount());

                    if (numFtrsSelected != 1)
                    {
                        ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show("Select only one polyline feature.");
                        return;
                    }
                }
                else
                {
                    ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show("Select a polyline feature.");
                    return;
                }
            }
            else
            {
                ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show("Select a polyline feature.");
                return;
            }

            if (SelectedCameraView == "Face target" && TargetPoint == null)
            {
                ArcGIS.Desktop.Framework.Dialogs.MessageBox.Show("Selected view type is - Face target - but a target point is not set.");
                return;
            }

            string oid_fieldName = await QueuedTask.Run(() => ftrLayer.GetTable().GetDefinition().GetObjectIDField());

            //get selected polyline
            Polyline lineGeom = await QueuedTask.Run <Polyline>(() =>
            {
                var selectedFtrOID = MapView.Active.Map.GetSelection()[ftrLayer][0];
                QueryFilter qf     = new QueryFilter();
                qf.WhereClause     = oid_fieldName + " = " + selectedFtrOID.ToString();
                RowCursor result   = ftrLayer.GetFeatureClass().Search(qf);
                if (result != null)
                {
                    result.MoveNext();
                    Feature selectedFtr = result.Current as Feature;
                    return(selectedFtr.GetShape() as Polyline);
                }
                return(null);
            });

            //couldn't get the selected feature
            if (lineGeom == null)
            {
                return;
            }

            ProjectionTransformation transformation = await QueuedTask.Run(() => ProjectionTransformation.Create(ftrLayer.GetSpatialReference(), mapView.Map.SpatialReference));

            SpatialReference layerSpatRef = await QueuedTask.Run(() => ftrLayer.GetSpatialReference());

            if (layerSpatRef.Unit.Name != "Degree")
            {
                Z_CONVERSION_FACTOR = layerSpatRef.Unit.ConversionFactor;
            }

            //Project target point if method is Face target
            if (SelectedCameraView == "Face target")
            {
                if (TargetPoint != null && TargetPoint.SpatialReference != layerSpatRef)
                {
                    ProjectionTransformation transf_forTarget = await QueuedTask.Run(() => ProjectionTransformation.Create(TargetPoint.SpatialReference, layerSpatRef));

                    MapPoint projected_targetPoint = (MapPoint)GeometryEngine.Instance.ProjectEx(TargetPoint, transf_forTarget);
                    TargetPoint = null;
                    TargetPoint = projected_targetPoint;
                }
            }

            var animation   = mapView.Map.Animation;
            var cameraTrack = animation.Tracks.OfType <CameraTrack>().First();
            var keyframes   = cameraTrack.Keyframes;

            //Get segment list for line
            ReadOnlyPartCollection polylineParts = lineGeom.Parts;

            //get total segment count and determine path length
            double pathLength   = 0;
            int    segmentCount = 0;
            IEnumerator <ReadOnlySegmentCollection> segments = polylineParts.GetEnumerator();

            while (segments.MoveNext())
            {
                ReadOnlySegmentCollection seg = segments.Current;
                foreach (Segment s in seg)
                {
                    //pathLength += s.Length;//s.Length returns 2D length

                    double length3D = Math.Sqrt((s.EndPoint.X - s.StartPoint.X) * (s.EndPoint.X - s.StartPoint.X) +
                                                (s.EndPoint.Y - s.StartPoint.Y) * (s.EndPoint.Y - s.StartPoint.Y) +
                                                (s.EndPoint.Z - s.StartPoint.Z) * (s.EndPoint.Z - s.StartPoint.Z));

                    pathLength   += length3D;
                    segmentCount += 1;
                }
            }

            //reset heading and pitch
            _keyframeHeading = 0;
            _keyframePitch   = 0;

            // Create keyframes based on chosen method
            if (SelectedMethod == "Keyframes along path")
            {
                await CreateKeyframes_AlongPath(mapView, layerSpatRef, transformation, cameraTrack, segments, segmentCount, pathLength);
            }
            else if (SelectedMethod == "Keyframes every N seconds")
            {
                await CreateKeyframes_EveryNSeconds(mapView, layerSpatRef, transformation, cameraTrack, segments, segmentCount, pathLength, KeyEveryNSecond);
            }
            else if (SelectedMethod == "Keyframes only at vertices")
            {
                await CreateKeyframes_AtVertices(mapView, layerSpatRef, transformation, cameraTrack, lineGeom, segments, segmentCount, pathLength);
            }
        }
        public async Task <string> GenerateGmlAsync()
        {
            MapView            mapView       = MapView.Active;
            Map                map           = mapView?.Map;
            SpatialReference   mapSpatRef    = map?.SpatialReference;
            MySpatialReference myCyclSpatRef = _settings.CycloramaViewerCoordinateSystem;

            SpatialReference cyclSpatRef = (myCyclSpatRef == null)
        ? mapSpatRef
        : (myCyclSpatRef.ArcGisSpatialReference ?? (await myCyclSpatRef.CreateArcGisSpatialReferenceAsync()));

            Unit   unit   = cyclSpatRef?.Unit;
            double factor = unit?.ConversionFactor ?? 1;
            Color  color  = Color.White;
            string result =
                "<wfs:FeatureCollection xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:wfs=\"http://www.opengis.net/wfs\" xmlns:gml=\"http://www.opengis.net/gml\">";

            await QueuedTask.Run(async() =>
            {
                SpatialReference layerSpatRef       = Layer.GetSpatialReference();
                IList <IList <Segment> > geometries = new List <IList <Segment> >();
                ICollection <Viewer> viewers        = _viewerList.Viewers;

                foreach (var viewer in viewers)
                {
                    double distance = viewer.OverlayDrawDistance;
                    RecordingLocation recordingLocation = viewer.Location;

                    if (recordingLocation != null)
                    {
                        if (cyclSpatRef?.IsGeographic ?? true)
                        {
                            distance = distance * factor;
                        }
                        else
                        {
                            distance = distance / factor;
                        }

                        double x    = recordingLocation.X;
                        double y    = recordingLocation.Y;
                        double xMin = x - distance;
                        double xMax = x + distance;
                        double yMin = y - distance;
                        double yMax = y + distance;

                        Envelope envelope     = EnvelopeBuilder.CreateEnvelope(xMin, yMin, xMax, yMax, cyclSpatRef);
                        Envelope copyEnvelope = envelope;

                        if (layerSpatRef.Wkid != 0)
                        {
                            ProjectionTransformation projection = ProjectionTransformation.Create(cyclSpatRef, layerSpatRef);
                            copyEnvelope = GeometryEngine.Instance.ProjectEx(envelope, projection) as Envelope;
                        }

                        Polygon copyPolygon = PolygonBuilder.CreatePolygon(copyEnvelope, layerSpatRef);
                        ReadOnlyPartCollection polygonParts = copyPolygon.Parts;
                        IEnumerator <ReadOnlySegmentCollection> polygonSegments = polygonParts.GetEnumerator();
                        IList <Segment> segments = new List <Segment>();

                        while (polygonSegments.MoveNext())
                        {
                            ReadOnlySegmentCollection polygonSegment = polygonSegments.Current;

                            foreach (Segment segment in polygonSegment)
                            {
                                segments.Add(segment);
                            }
                        }

                        geometries.Add(segments);
                    }
                }

                GC.Collect();
                Polygon polygon = PolygonBuilder.CreatePolygon(geometries, layerSpatRef);

                using (FeatureClass featureClass = Layer?.GetFeatureClass())
                {
                    string uri = Layer?.URI;

                    SpatialQueryFilter spatialFilter = new SpatialQueryFilter
                    {
                        FilterGeometry      = polygon,
                        SpatialRelationship = SpatialRelationship.Intersects,
                        SubFields           = "*"
                    };

                    using (RowCursor existsResult = featureClass?.Search(spatialFilter, false))
                    {
                        while (existsResult?.MoveNext() ?? false)
                        {
                            Row row       = existsResult.Current;
                            long objectId = row.GetObjectID();

                            if ((_selection == null) || (!_selection.Contains(objectId)))
                            {
                                Feature feature = row as Feature;
                                var fieldvalues = new Dictionary <string, string> {
                                    { FieldUri, uri }, { FieldObjectId, objectId.ToString() }
                                };

                                Geometry geometry         = feature?.GetShape();
                                GeometryType geometryType = geometry?.GeometryType ?? GeometryType.Unknown;
                                Geometry copyGeometry     = geometry;

                                if ((geometry != null) && (layerSpatRef.Wkid != 0))
                                {
                                    ProjectionTransformation projection = ProjectionTransformation.Create(layerSpatRef, cyclSpatRef);
                                    copyGeometry = GeometryEngine.Instance.ProjectEx(geometry, projection);
                                }

                                if (copyGeometry != null)
                                {
                                    string gml = string.Empty;

                                    switch (geometryType)
                                    {
                                    case GeometryType.Envelope:
                                        break;

                                    case GeometryType.Multipatch:
                                        break;

                                    case GeometryType.Multipoint:
                                        break;

                                    case GeometryType.Point:
                                        MapPoint point = copyGeometry as MapPoint;

                                        if (point != null)
                                        {
                                            gml =
                                                $"<gml:Point {GmlDimension(copyGeometry)}><gml:coordinates>{await GmlPointAsync(point)}</gml:coordinates></gml:Point>";
                                        }

                                        break;

                                    case GeometryType.Polygon:
                                        Polygon polygonGml = copyGeometry as Polygon;

                                        if (polygonGml != null)
                                        {
                                            ReadOnlyPartCollection polygonParts = polygonGml.Parts;
                                            IEnumerator <ReadOnlySegmentCollection> polygonSegments = polygonParts.GetEnumerator();

                                            while (polygonSegments.MoveNext())
                                            {
                                                ReadOnlySegmentCollection segments = polygonSegments.Current;

                                                gml =
                                                    $"{gml}<gml:MultiPolygon><gml:PolygonMember><gml:Polygon {GmlDimension(copyGeometry)}><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>";

                                                for (int i = 0; i < segments.Count; i++)
                                                {
                                                    if (segments[i].SegmentType == SegmentType.Line)
                                                    {
                                                        MapPoint polygonPoint = segments[i].StartPoint;
                                                        gml = $"{gml}{((i == 0) ? string.Empty : " ")}{await GmlPointAsync(polygonPoint)}";

                                                        if (i == (segments.Count - 1))
                                                        {
                                                            polygonPoint = segments[i].EndPoint;
                                                            gml          = $"{gml} {await GmlPointAsync(polygonPoint)}";
                                                        }
                                                    }
                                                }

                                                gml =
                                                    $"{gml}</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:PolygonMember></gml:MultiPolygon>";
                                            }
                                        }
                                        break;

                                    case GeometryType.Polyline:
                                        Polyline polylineGml = copyGeometry as Polyline;

                                        if (polylineGml != null)
                                        {
                                            ReadOnlyPartCollection polylineParts = polylineGml.Parts;
                                            IEnumerator <ReadOnlySegmentCollection> polylineSegments = polylineParts.GetEnumerator();

                                            while (polylineSegments.MoveNext())
                                            {
                                                ReadOnlySegmentCollection segments = polylineSegments.Current;
                                                gml =
                                                    $"{gml}<gml:MultiLineString><gml:LineStringMember><gml:LineString {GmlDimension(copyGeometry)}><gml:coordinates>";

                                                for (int i = 0; i < segments.Count; i++)
                                                {
                                                    if (segments[i].SegmentType == SegmentType.Line)
                                                    {
                                                        MapPoint linePoint = segments[i].StartPoint;
                                                        gml = $"{gml}{((i == 0) ? string.Empty : " ")}{await GmlPointAsync(linePoint)}";

                                                        if (i == (segments.Count - 1))
                                                        {
                                                            linePoint = segments[i].EndPoint;
                                                            gml       = $"{gml} {await GmlPointAsync(linePoint)}";
                                                        }
                                                    }
                                                }

                                                gml = $"{gml}</gml:coordinates></gml:LineString></gml:LineStringMember></gml:MultiLineString>";
                                            }
                                        }

                                        break;

                                    case GeometryType.Unknown:
                                        break;
                                    }

                                    string fieldValueStr = fieldvalues.Aggregate(string.Empty,
                                                                                 (current, fieldvalue) =>
                                                                                 string.Format("{0}<{1}>{2}</{1}>", current, fieldvalue.Key, fieldvalue.Value));
                                    result =
                                        $"{result}<gml:featureMember><xs:Geometry>{fieldValueStr}{gml}</xs:Geometry></gml:featureMember>";
                                }
                            }
                        }
                    }
                }

                CIMRenderer renderer             = Layer.GetRenderer();
                CIMSimpleRenderer simpleRenderer = renderer as CIMSimpleRenderer;
                CIMUniqueValueRenderer uniqueValueRendererRenderer = renderer as CIMUniqueValueRenderer;
                CIMSymbolReference symbolRef = simpleRenderer?.Symbol ?? uniqueValueRendererRenderer?.DefaultSymbol;
                CIMSymbol symbol             = symbolRef?.Symbol;
                CIMColor cimColor            = symbol?.GetColor();
                double[] colorValues         = cimColor?.Values;

                int red   = ((colorValues != null) && (colorValues.Length >= 1)) ? ((int)colorValues[0]) : 255;
                int green = ((colorValues != null) && (colorValues.Length >= 2)) ? ((int)colorValues[1]) : 255;
                int blue  = ((colorValues != null) && (colorValues.Length >= 3)) ? ((int)colorValues[2]) : 255;
                int alpha = ((colorValues != null) && (colorValues.Length >= 4)) ? ((int)colorValues[3]) : 255;
                color     = Color.FromArgb(alpha, red, green, blue);
            });

            GmlChanged = (Color != color);
            Color      = color;
            string newGml = $"{result}</wfs:FeatureCollection>";

            GmlChanged = ((newGml != Gml) || GmlChanged);
            return(Gml = newGml);
        }