title: Zoom and Pan a Map Image with C# WPF
body: <p>I'm trying to make a map from an image using the projection of Robinson.
  The idea is to give the image the same behaviors of a normal map, so the user should be able to zoom and pan on the map, it should be also able to mark the localisation that he want using a pushpin.</p>
  
  <p>To do this I create a class that manage the zoom and pan behaviors, and a user control that manage the projection.</p>
  
  <p>Th problem that i have now is that i can't fix the pushpin on the image, and it lose it's position when i'm zooming. 
  Also when the map scale is over 1, the push pin is not placed on the right position.</p>
  
  <p>If anyone has an idea of how can I fix this problem, please help.</p>
  
  <p>Here is the code of the class and of the user control.</p>
  
  <p>ZoomPan.cs</p>
  
  <pre><code>    #region Properties
  
      private Canvas m_Map;
      private UIElement m_Pin;
  
      private Point m_Origin;
      private Point m_Start;
      public bool IsMouseWheel = false;
  
      #endregion
  
      #region Method
  
      /// &lt;summary&gt;
      /// Initialize child Events.
      /// &lt;/summary&gt;
      public void Initialize()
      {
          m_Map = this.Children[0] as Canvas;
          m_Pin = this.Children[1];
  
          if (this.Children.Count &gt;= 0)
          {
              TransformGroup aMapGroup = new TransformGroup();
              ScaleTransform aMapScale = new ScaleTransform();
              aMapGroup.Children.Add(aMapScale);
              TranslateTransform aMapTranslate = new TranslateTransform();
              aMapGroup.Children.Add(aMapTranslate);
  
              m_Map.RenderTransform = aMapGroup;
              m_Map.RenderTransformOrigin = new Point(0.0, 0.0);
  
              TransformGroup aPinGroup = new TransformGroup();
              ScaleTransform aPinScale = new ScaleTransform();
              aPinGroup.Children.Add(aPinScale);
              TranslateTransform aPinTranslate = new TranslateTransform();
              aPinGroup.Children.Add(aPinTranslate);
  
              m_Pin.RenderTransform = aPinGroup;
              m_Pin.RenderTransformOrigin = new Point(0.0, 0.0);
  
              this.MouseWheel += child_MouseWheel;
              this.MouseLeftButtonDown += child_MouseLeftButtonDown;
              this.MouseLeftButtonUp += child_MouseLeftButtonUp;
              this.MouseMove += child_MouseMove;
          }
      }
  
      /// &lt;summary&gt;
      /// Get Translate Transform.
      /// &lt;/summary&gt;
      private TranslateTransform GetTranslateTransform(UIElement element)
      {
          return (TranslateTransform)((TransformGroup)element.RenderTransform)
            .Children.First(tr =&gt; tr is TranslateTransform);
      }
  
      /// &lt;summary&gt;
      /// Get Scale Transform.
      /// &lt;/summary&gt;
      private ScaleTransform GetScaleTransform(UIElement element)
      {
          return (ScaleTransform)((TransformGroup)element.RenderTransform)
            .Children.First(tr =&gt; tr is ScaleTransform);
      }
  
      /// &lt;summary&gt;
      /// Set Translation.
      /// &lt;/summary&gt;
      private double SetTranslation(double p_Max, double p_Vector)
      {
          if (p_Vector &lt; -p_Max)
          {
              return -p_Max;
          }
          else
          {
              return p_Vector;
          }
      }
  
      /// &lt;summary&gt;
      /// Pan Child
      /// &lt;/summary&gt;
      private void PanChild(double p_TranslateX, double p_TranslateY)
      {
          var transform = m_Pin.TransformToVisual(m_Map);
          Point p = transform.Transform(new Point(0, 0));
  
          p.ToString();
  
          var aMapScale = GetScaleTransform(m_Map);
          var aMapTranslation = GetTranslateTransform(m_Map);
  
          var aPinScale = GetScaleTransform(m_Pin);
          var aPinTranslation = GetTranslateTransform(m_Pin);
  
          if (p_TranslateX &lt; 0)
          {
              aMapTranslation.X = SetTranslation(this.ActualWidth * (aMapScale.ScaleX - 1), p_TranslateX);
              aPinTranslation.X = SetTranslation(this.ActualWidth * (aMapScale.ScaleX - 1), p_TranslateX);
          }
          else
          {
              aMapTranslation.X = 0;
              aPinTranslation.X = 0;
          }
  
          if (p_TranslateY &lt; 0)
          {
              aMapTranslation.Y = SetTranslation(this.ActualHeight * (aMapScale.ScaleY - 1), p_TranslateY);
              aPinTranslation.Y = SetTranslation(this.ActualWidth * (aMapScale.ScaleY - 1), p_TranslateY);
          }
          else
          {
              aMapTranslation.Y = 0;
              aPinTranslation.Y = 0;
          }
      }
  
      #endregion
  
      #region Child Events
  
      /// &lt;summary&gt;
      /// On Mouse Wheele (Zoom).
      /// &lt;/summary&gt;
      private void child_MouseWheel(object sender, MouseWheelEventArgs e)
      {
          if (m_Map != null &amp;&amp; m_Pin !=null)
          {
              IsMouseWheel = true;
  
              var aMapScale = GetScaleTransform(m_Map);
              var aMapTranslation = GetTranslateTransform(m_Map);
  
              var aPinScale = GetScaleTransform(m_Pin);
              var aPinTranslation = GetTranslateTransform(m_Pin);
  
              double zoom = e.Delta &gt; 0 ? .5 : -.5;
  
              if (!(e.Delta &gt; 0) &amp;&amp; (aMapScale.ScaleX &lt;= 1 || aMapScale.ScaleY &lt;= 1))
              {
                  return;
              }
  
              Point relative = e.GetPosition(m_Map);
  
              double abosuluteX;
              double abosuluteY;
  
              abosuluteX = relative.X * aMapScale.ScaleX + aMapTranslation.X;
              abosuluteY = relative.Y * aMapScale.ScaleY + aMapTranslation.Y;
  
              if (aMapScale.ScaleX &gt;= 7 || aMapScale.ScaleY &gt;= 7)
              {
                  if (e.Delta &gt; 0)
                  {
                      return;
                  }
              }
  
              aMapScale.ScaleX += zoom;
              aMapScale.ScaleY += zoom;
  
              CUserControlMap.m_Zoom = (int)(aMapScale.ScaleX - 1) * 2;
  
              double X = abosuluteX - relative.X * aMapScale.ScaleX;
              double Y = abosuluteY - relative.Y * aMapScale.ScaleY;
  
              PanChild(X, Y);
          }
      }
  
      /// &lt;summary&gt;
      /// Mouse Left Button Down (Translate).
      /// &lt;/summary&gt;
      private void child_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
      {
          if (m_Map != null &amp;&amp; m_Pin !=null)
          {
              var aMapScale = GetScaleTransform(m_Map);
  
              if (aMapScale.ScaleX == 1 || aMapScale.ScaleY == 1)
              {
                  return;
              }
  
              var aMapTranslation = GetTranslateTransform(m_Map);
              m_Start = e.GetPosition(this);
              m_Origin = new Point(aMapTranslation.X, aMapTranslation.Y);
              this.Cursor = Cursors.CCursors.CursorHand;
              m_Map.CaptureMouse();
          }
      }
  
  
      /// &lt;summary&gt;
      /// Mouse Left Button Up.
      /// &lt;/summary&gt;
      private void child_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
      {
          if (m_Map != null &amp;&amp; m_Pin !=null)
          {
              m_Map.ReleaseMouseCapture();
              this.Cursor = Cursors.CCursors.CursorArrow;
          }
      }
  
      /// &lt;summary&gt;
      /// Mouse Move
      /// &lt;/summary&gt;
      private void child_MouseMove(object sender, MouseEventArgs e)
      {
          if (m_Map != null &amp;&amp; m_Pin != null)
          {
              if (m_Map.IsMouseCaptured)
              {
                  var aMapScale = GetScaleTransform(m_Map);
                  var aMapTranslation = GetTranslateTransform(m_Map);
  
                  var aPinScale = GetScaleTransform(m_Pin);
                  var aPinTranslation = GetTranslateTransform(m_Pin);
  
                  Vector v = m_Start - e.GetPosition(this);
  
                  double X = m_Origin.X - v.X;
                  double Y = m_Origin.Y - v.Y;
  
                  PanChild(X, Y);
              }
          }
      }
      #endregion
  }
  </code></pre>
  
  <p>The code of MapControl.cs</p>
  
  <pre><code>    #region Properties
  
      /// &lt;summary&gt; Latitude&lt;/summary&gt;
      public float Latitude = 0;
  
      /// &lt;summary&gt; Longitude&lt;/summary&gt;
      public float Longitude = 0;
  
      /// &lt;summary&gt; Longitude in Pixels&lt;/summary&gt;
      private static double s_kLongitudeInPixel = 0.81;
  
      /// &lt;summary&gt; Latitude in Pixels&lt;/summary&gt;
      private static double s_kLatitudeInPixel = 0.71;
  
      /// &lt;summary&gt; Middle of the map&lt;/summary&gt;
      private static double s_KMiddle = 206;
  
      /// &lt;summary&gt; Pin Image&lt;/summary&gt;
      private Image m_Pin = new Image();
  
      public static double m_Zoom = 0;
  
      #endregion
  
      #region Constructor
  
      /// &lt;summary&gt;
      /// Constructor
      /// &lt;/summary&gt;
      public CUserControlMap()
      {
          InitializeComponent();
  
          m_Pin.Source = Application.Current.TryFindResource("Pin") as BitmapImage;
          m_Pin.Height = 24;
          m_Pin.Width = 30;
  
          m_Canvas.Children.Add(m_Pin);
          m_Canvas.Initialize();
      }
  
      #endregion
  
      #region Methods
  
      /// &lt;summary&gt;
      /// Add a pin to the current Localization
      /// &lt;/summary&gt;
      public void SetCurrentLocalization(float p_latitude, float p_longitude)
      {
          double pointX = GetXFromLongitude(p_longitude);
          double pointY = GetYFromLatitude(p_latitude);
  
          m_Pin.Margin = new Thickness(pointX + 97 , pointY - 22, 0, 0);
      }
  
      /// &lt;summary&gt;
      /// Get X from longitude
      /// &lt;/summary&gt;
      private double GetXFromLongitude(float p_longitude)
      {
          return (p_longitude / s_kLongitudeInPixel) + (s_KMiddle / 2);
      }
  
      /// &lt;summary&gt;
      /// Get Y from latitude
      /// &lt;/summary&gt;
      private double GetYFromLatitude(float p_latitude)
      {
          return (m_Image.Height / 2) - (p_latitude / s_kLatitudeInPixel);
      }
  
      #endregion
  
      #region Events
  
      /// &lt;summary&gt;
      /// Image Mouse Right Button Down
      /// &lt;/summary&gt;
      private void Image_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
      {
          Point point = e.GetPosition(m_Image);
  
          Longitude = (float)(point.X - s_KMiddle) * (float)s_kLongitudeInPixel;
          Latitude = (float)(m_Image.Height / 2 - point.Y) * (float)s_kLatitudeInPixel;
  
          SetCurrentLocalization((float)Latitude, (float)Longitude);
      }
  
      #endregion
  </code></pre>