Move a graphic along a path in ArcMap
MapGraphicKeyframe.cs
// Copyright 2012 ESRI
// 
// All rights reserved under the copyright laws of the United States
// and applicable international laws, treaties, and conventions.
// 
// You may freely redistribute and use this sample code, with or
// without modification, provided you include the original copyright
// notice and use restrictions.
// 
// See the use restrictions.
// 

using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using ESRI.ArcGIS.Animation;
using ESRI.ArcGIS.ADF;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.SystemUI;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Display;

namespace AnimationDeveloperSamples
{
    [Guid("B2263D65-7FAF-4118-A255-5B0D47E453EE")]
    [ClassInterface(ClassInterfaceType.None)]
    [ProgId("AnimationDeveloperSamples.MapGraphicKeyframe")]
    public class MapGraphicKeyframe:IAGKeyframe, IAGKeyframeUI
    {
        private ILongArray activeProps;
        private IAGAnimationType animType;
        private string name;
        private bool bObjectsNeedRefresh;
        private IPoint Position;
        private double Rotation;
        private double timeStamp;

        #region constructor
        public MapGraphicKeyframe()
        {
            activeProps = new LongArrayClass();
            activeProps.Add(0);
            activeProps.Add(1);
            animType = new AnimationTypeMapGraphic();
            name = "";
            bObjectsNeedRefresh = false;
            Position = new PointClass();
            Rotation = 0.0;
            timeStamp = 0.0;
        }
        #endregion

        #region IAGKeyframe members
        public ILongArray ActiveProperties 
        {
            get {
                return activeProps;
            }
            set {
                activeProps = value;
            }
        }

        public IAGAnimationType AnimationType 
        {
            get {
                return animType;
            }            
        }

        public void Apply(IAGAnimationTrack pTrack, IAGAnimationContainer pContainer, object pObject)
        {
            IElement elem = (IElement) pObject;

            UpdateGraphicObject(elem,pContainer,pTrack,Position,Rotation);

            return;
        }

        public void CaptureProperties(IAGAnimationContainer pContainer, object pObject)
        {
            IElement elem = pObject as IElement;
            IActiveView view = pContainer.CurrentView as IActiveView;
            IGraphicsContainerSelect graphicsConSel = view as IGraphicsContainerSelect;
            IDisplay disp = view.ScreenDisplay as IDisplay;
            IEnvelope elemEnv = GetElementBound(elem,pContainer);

            if (elem.Geometry.GeometryType == esriGeometryType.esriGeometryPoint)
            {
                Position = elem.Geometry as IPoint;
            }
            else
            {
                IEnvelope elementEnvelope = elem.Geometry.Envelope;
                IArea elementArea = elementEnvelope as IArea;
                Position = elementArea.Centroid;
            }

            IElementProperties elemProps = (IElementProperties)elem;
            if(elemProps.CustomProperty!=null)
                Rotation = (double)elemProps.CustomProperty;
        }

        public void Interpolate(IAGAnimationTrack pTrack, IAGAnimationContainer pContainer,
            object pObject, int propertyIndex, double time, IAGKeyframe pNextKeyframe,
            IAGKeyframe pPrevKeyframe, IAGKeyframe pAfterNextKeyframe)
        {
            if (time < TimeStamp || time > pNextKeyframe.TimeStamp)
                return;

            IElement elem = (IElement)pObject;
            IPoint new_pos = new PointClass();
           
            if (propertyIndex == 0)
            {
                double x1;
                double y1;
                IPoint nextPosition = (IPoint)pNextKeyframe.get_PropertyValue(0);

                double timeFactor;
                timeFactor = (time - TimeStamp) / (pNextKeyframe.TimeStamp - TimeStamp); //ignoring pPrevKeyframe and pAfterNextKeyframe

                x1 = Position.X * (1 - timeFactor) + nextPosition.X * timeFactor;
                y1 = Position.Y * (1 - timeFactor) + nextPosition.Y * timeFactor;
                
                new_pos.PutCoords(x1, y1);

                if (!(elem is ILineElement))
                {
                    MoveElement(elem, new_pos, pContainer);
                    TracePath(elem, new_pos, pContainer, pTrack, this);
                    bObjectsNeedRefresh = true;
                }
            }
            if (propertyIndex == 1)
            {
                //this property only applies to the point graphic 
                if (!(elem is ILineElement))
                {
                    RotateElement(elem, Rotation, pContainer);
                    bObjectsNeedRefresh = true;
                }
            }

            return;
        }

        public bool get_IsActiveProperty(int propIndex)
        {
            bool bIsActive = false;
            int count = activeProps.Count;

            for (int i = 0; i < count; i++)
            {
                long temp = activeProps.get_Element(i);
                if (temp == propIndex)
                    bIsActive = true;
            }
            return bIsActive;
        }

        public void set_IsActiveProperty(int propIndex, bool pbIsActiveProp)
        {
            if (pbIsActiveProp)
            {
                if (get_IsActiveProperty(propIndex) == false)
                {
                    activeProps.Add(propIndex);
                }
            }
            else
            {
                if (get_IsActiveProperty(propIndex) == true)
                {
                    int i = 0;
                    int count = activeProps.Count;
                    for (i = 0; i < count; i++)
                    {
                        long temp = activeProps.get_Element(i);
                        if (temp == propIndex)
                            break;
                    }

                    activeProps.Remove(i);
                }
            }          
        }
        public string Name 
        { 
            get {
                return name;
            }

            set {
                name = value;
            }        
        }
        public bool ObjectNeedsRefresh 
        {
            get {
                return bObjectsNeedRefresh;
            }        
        }
        
        public object get_PropertyValue(int propIndex)
        {
            if (propIndex == 0)
                return (object)Position;
            else if (propIndex == 1)
                return (object)Rotation;
            else
                return null;
        }

        public void set_PropertyValue(int propIndex, object pValue)
        {
            if (propIndex == 0)
                Position = (IPoint)pValue;
            else if (propIndex == 1)
                Rotation = (Double)pValue;
            else
                return;
        }

        public void RefreshObject(IAGAnimationTrack pTrack,
            IAGAnimationContainer pContainer, object pObject)
        {         
            RefreshGraphicObject((IElement)pObject, pContainer);
        }
        public double TimeStamp
        { 
            get {
                return timeStamp;
            }

            set {
                timeStamp = value;
            }
        }
        #endregion

        #region IAGKeyframeUI members
        public string GetText(int propIndex, int columnIndex)
        {
            string text;
            text = null;
            switch (propIndex)
            {
                case 0:
                    {
                        if (columnIndex == 0)
                        {
                            text = System.Convert.ToString(Position.X);
                        }
                        if (columnIndex == 1)
                        {
                            text = System.Convert.ToString(Position.Y);
                        }
                    }
                    break;
                case 1:
                    text = System.Convert.ToString(Rotation);
                    break;
            }
            return text;
        }

        public void SetText(int propIndex, int columnIndex, string text)
        {
            switch (propIndex)
            {
                case 0:
                    {
                        if (columnIndex == 0)
                        {
                            Position.X = System.Convert.ToDouble(text);
                        }
                        if (columnIndex == 1)
                        {
                            Position.Y = System.Convert.ToDouble(text);
                        }
                    }
                    break;
                case 1:
                    {
                        Rotation = System.Convert.ToDouble(text);
                    }
                    break;
            }

            return;
        }
        #endregion 

        #region private methods
        private void RotateElement(IElement elem, double new_angle, IAGAnimationContainer pContainer)
        {
            ITransform2D transform2D = elem as ITransform2D;
            IPoint rotateOrigin;

            if (elem.Geometry.GeometryType == esriGeometryType.esriGeometryPoint)
            {
                rotateOrigin = elem.Geometry as IPoint;
            }
            else
            {
                IEnvelope elementEnvelope = elem.Geometry.Envelope;
                IArea elementArea = elementEnvelope as IArea;
                rotateOrigin = elementArea.Centroid;
            }

            AddPropertySet(elem);

            IElementProperties prop = (IElementProperties)elem; //record the old properties
            IPropertySet propSet;
            propSet = (IPropertySet)prop.CustomProperty;
            double old_angle;
            old_angle = (double)propSet.GetProperty("Angle");

            propSet.SetProperty("Angle", new_angle); //update old angle

            transform2D.Rotate(rotateOrigin, new_angle-old_angle);
        }

        private void TracePath(IElement elem, IPoint new_pos, IAGAnimationContainer pContainer, IAGAnimationTrack pTrack, IAGKeyframe pKeyframe)
        {
            IAGAnimationTrackExtensions trackExtensions = (IAGAnimationTrackExtensions)(pTrack);
            IMapGraphicTrackExtension graphicTrackExtension;
            if (trackExtensions.ExtensionCount == 0) //if there is no extension, add one
            {
                graphicTrackExtension = new MapGraphicTrackExtension();
                trackExtensions.AddExtension(graphicTrackExtension);
            }
            else
            {
                graphicTrackExtension = (IMapGraphicTrackExtension)trackExtensions.get_Extension(0);
            }

            ILineElement path = graphicTrackExtension.TraceElement;

            bool showTrace = graphicTrackExtension.ShowTrace;
            if (!showTrace)
            {
                if (CheckGraphicExistance((IElement)path, pContainer))
                {
                    RemoveGraphicFromDisplay((IElement)path, pContainer);
                }
                return;
            }

            //Add the path to the graphic container
            if (!CheckGraphicExistance((IElement)path, pContainer))
            {
                AddGraphicToDisplay((IElement)path, pContainer);
            }

            RecreateLineGeometry((IElement)path, pTrack, pKeyframe, new_pos);
        }

        private void AddGraphicToDisplay(IElement elem, IAGAnimationContainer animContainer)
        {
            IActiveView view = animContainer.CurrentView as IActiveView;
            IGraphicsContainer graphicsContainer = view as IGraphicsContainer;
            graphicsContainer.AddElement(elem, 0);            
            elem.Activate(view.ScreenDisplay);
        }

        private void RemoveGraphicFromDisplay(IElement elem, IAGAnimationContainer animContainer)
        {
            IActiveView view = animContainer.CurrentView as IActiveView;
            IGraphicsContainer graphicsContainer = view as IGraphicsContainer;
            graphicsContainer.DeleteElement(elem);
        }

        private bool CheckGraphicExistance(IElement elem, IAGAnimationContainer animContainer)
        {
            IActiveView view = animContainer.CurrentView as IActiveView;
            IGraphicsContainer graphicsContainer = view as IGraphicsContainer;
            graphicsContainer.Reset();

            bool exists = false;
            IElement temp = graphicsContainer.Next();
            while (temp != null)
            {
                if (temp == elem)
                {
                    exists = true;
                    break;
                }
                temp = graphicsContainer.Next();
            }
            return exists;
        }

        private void MoveElement(IElement elem, IPoint new_pos, IAGAnimationContainer pContainer)
        {
            ITransform2D transform2D = elem as ITransform2D;
            IPoint origin;

            if (elem.Geometry.GeometryType == esriGeometryType.esriGeometryPoint)
            {
                origin = elem.Geometry as IPoint;
            }
            else
            {
                IEnvelope elementEnvelope = elem.Geometry.Envelope;
                IArea elementArea = elementEnvelope as IArea;
                origin = elementArea.Centroid;
            }

            AddPropertySet(elem);

            IElementProperties prop = (IElementProperties)elem; //record the old properties
            IPropertySet propSet;
            propSet = (IPropertySet)prop.CustomProperty;
            IEnvelope oldEnv = GetElementBound(elem, pContainer);
            propSet.SetProperty("Envelope", oldEnv);

            transform2D.Move(new_pos.X - origin.X, new_pos.Y - origin.Y);
        }

        private void RecreateLineGeometry(IElement elem, IAGAnimationTrack pTrack, IAGKeyframe pKeyframe, IPoint new_pos)
        {
            IGeometry newGeometry = new PolylineClass();
            IPointCollection newPointCol = (IPointCollection)newGeometry;

            IAGAnimationTrackKeyframes trackKeyframes = (IAGAnimationTrackKeyframes)pTrack;
            int tCount = trackKeyframes.KeyframeCount;
            object missing = Type.Missing;
            for (int i = 0; i < tCount; i++)
            {
                IAGKeyframe tempKeyframe = trackKeyframes.get_Keyframe(i);
                newPointCol.AddPoint((IPoint)tempKeyframe.get_PropertyValue(0), ref missing, ref missing);
                if ((IPoint)tempKeyframe.get_PropertyValue(0) == (IPoint)pKeyframe.get_PropertyValue(0))
                    break;
            }
            if (new_pos != null)
                newPointCol.AddPoint(new_pos, ref missing, ref missing);

            elem.Geometry = newGeometry;
        }

        private IEnvelope GetElementBound(IElement elem, IAGAnimationContainer pContainer)
        {
            IActiveView view = pContainer.CurrentView as IActiveView;
            IGraphicsContainerSelect graphicsContainerSelect = view as IGraphicsContainerSelect;
            IDisplay disp = view.ScreenDisplay as IDisplay;
            
            IEnvelope elementEnvelope = new EnvelopeClass();
            elem.QueryBounds(disp, elementEnvelope);

            if (graphicsContainerSelect.ElementSelected(elem))
            {
                elementEnvelope = elem.SelectionTracker.get_Bounds(disp);
            }

            return elementEnvelope;
        }

        private void UpdateGraphicObject(IElement elem, IAGAnimationContainer pContainer, IAGAnimationTrack pTrack, IPoint new_pos, double new_angle)
        {
            if (elem == null||new_pos==null)
                return; //invalidate parameter

            MoveElement(elem, new_pos, pContainer);
            RotateElement(elem, new_angle, pContainer);

            return;
        }

        private void AddPropertySet(IElement elem)
        {
            if (elem == null)
                return; //invalidate parameter

            IElementProperties prop = (IElementProperties)elem; //record the old properties
            IPropertySet propSet;
            if (prop.CustomProperty == null)
            {
                propSet = new PropertySetClass();
                propSet.SetProperty("Envelope", null);
                propSet.SetProperty("Angle", 0.0);
                prop.CustomProperty = propSet;
            }
        }

        private void RefreshGraphicObject(IElement elem, IAGAnimationContainer pContainer)
        {
            IActiveView view = pContainer.CurrentView as IActiveView;
            IEnvelope elemEnv = GetElementBound(elem, pContainer);

            IEnvelope oldEnv = new EnvelopeClass();
            IElementProperties2 elemProps2 = (IElementProperties2) elem;
            oldEnv = (IEnvelope) elemProps2.CustomProperty;

            IViewRefresh animRefresh = (IViewRefresh)view;

            if (oldEnv != null)
            {
                elemEnv.Union(oldEnv);
            }

            animRefresh.AnimationRefresh(esriViewDrawPhase.esriViewGraphics, elem, elemEnv);
        }
        #endregion 
    }
}