Subversion Repositories Projects

Rev

Rev 2323 | Go to most recent revision | Blame | Last modification | View Log | RSS feed


namespace GMap.NET.WindowsPresentation
{
   using System;
   using System.Collections.Generic;
   using System.Collections.ObjectModel;
   using System.ComponentModel;
   using System.Globalization;
   using System.Linq;
   using System.Windows;
   using System.Windows.Controls;
   using System.Windows.Data;
   using System.Windows.Input;
   using System.Windows.Media;
   using System.Windows.Media.Effects;
   using System.Windows.Media.Imaging;
   using System.Windows.Shapes;
   using System.Windows.Threading;
   using GMap.NET;
   using GMap.NET.Internals;
   using System.Diagnostics;
   using GMap.NET.MapProviders;
   using System.Windows.Media.Animation;
   using GMap.NET.Projections;

   /// <summary>
   /// GMap.NET control for Windows Presentation
   /// </summary>
   public partial class GMapControl : ItemsControl, Interface, IDisposable
   {
      #region DependencyProperties and related stuff

      public System.Windows.Point MapPoint
      {
         get
         {
            return (System.Windows.Point)GetValue(MapPointProperty);
         }
         set
         {
            SetValue(MapPointProperty, value);
         }
      }


      // Using a DependencyProperty as the backing store for point.  This enables animation, styling, binding, etc...
      public static readonly DependencyProperty MapPointProperty =
             DependencyProperty.Register("MapPoint", typeof(System.Windows.Point), typeof(GMapControl), new PropertyMetadata(new Point(), OnMapPointPropertyChanged));


      private static void OnMapPointPropertyChanged(DependencyObject source,
      DependencyPropertyChangedEventArgs e)
      {
         Point temp = (Point)e.NewValue;
         (source as GMapControl).Position = new PointLatLng(temp.X, temp.Y);
      }

      public static readonly DependencyProperty MapProviderProperty = DependencyProperty.Register("MapProvider", typeof(GMapProvider), typeof(GMapControl), new UIPropertyMetadata(EmptyProvider.Instance, new PropertyChangedCallback(MapProviderPropertyChanged)));

      /// <summary>
      /// type of map
      /// </summary>
      [Browsable(false)]
      public GMapProvider MapProvider
      {
         get
         {
            return GetValue(MapProviderProperty) as GMapProvider;
         }
         set
         {
            SetValue(MapProviderProperty, value);
         }
      }

      private static void MapProviderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
         GMapControl map = (GMapControl)d;
         if(map != null && e.NewValue != null)
         {
            Debug.WriteLine("MapType: " + e.OldValue + " -> " + e.NewValue);

            RectLatLng viewarea = map.SelectedArea;
            if(viewarea != RectLatLng.Empty)
            {
               map.Position = new PointLatLng(viewarea.Lat - viewarea.HeightLat / 2, viewarea.Lng + viewarea.WidthLng / 2);
            }
            else
            {
               viewarea = map.ViewArea;
            }

            map.Core.Provider = e.NewValue as GMapProvider;

            map.Copyright = null;
            if(!string.IsNullOrEmpty(map.Core.Provider.Copyright))
            {
               map.Copyright = new FormattedText(map.Core.Provider.Copyright, CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, new Typeface("GenericSansSerif"), 9, Brushes.Navy);
            }

            if(map.Core.IsStarted && map.Core.zoomToArea)
            {
               // restore zoomrect as close as possible
               if(viewarea != RectLatLng.Empty && viewarea != map.ViewArea)
               {
                  int bestZoom = map.Core.GetMaxZoomToFitRect(viewarea);
                  if(bestZoom > 0 && map.Zoom != bestZoom)
                  {
                     map.Zoom = bestZoom;
                  }
               }
            }
         }
      }

      public static readonly DependencyProperty ZoomProperty = DependencyProperty.Register("Zoom", typeof(double), typeof(GMapControl), new UIPropertyMetadata(0.0, new PropertyChangedCallback(ZoomPropertyChanged), new CoerceValueCallback(OnCoerceZoom)));

      /// <summary>
      /// map zoom
      /// </summary>
      [Category("GMap.NET")]
      public double Zoom
      {
         get
         {
            return (double)(GetValue(ZoomProperty));
         }
         set
         {
            SetValue(ZoomProperty, value);
         }
      }

      private static object OnCoerceZoom(DependencyObject o, object value)
      {
         GMapControl map = o as GMapControl;
         if(map != null)
         {
            double result = (double)value;
            if(result > map.MaxZoom)
            {
               result = map.MaxZoom;
            }
            if(result < map.MinZoom)
            {
               result = map.MinZoom;
            }

            return result;
         }
         else
         {
            return value;
         }
      }

      private ScaleModes scaleMode = ScaleModes.Integer;

      /// <summary>
      /// Specifies, if a floating map scale is displayed using a
      /// stretched, or a narrowed map.
      /// If <code>ScaleMode</code> is <code>ScaleDown</code>,
      /// then a scale of 12.3 is displayed using a map zoom level of 13
      /// resized to the lower level. If the parameter is <code>ScaleUp</code> ,
      /// then the same scale is displayed using a zoom level of 12 with an
      /// enlarged scale. If the value is <code>Dynamic</code>, then until a
      /// remainder of 0.25 <code>ScaleUp</code> is applied, for bigger
      /// remainders <code>ScaleDown</code>.
      /// </summary>
      [Category("GMap.NET")]
      [Description("map scale type")]
      public ScaleModes ScaleMode
      {
         get
         {
            return scaleMode;
         }
         set
         {
            scaleMode = value;
            InvalidateVisual();
         }
      }

      private static void ZoomPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
         GMapControl map = (GMapControl)d;
         if(map != null && map.MapProvider.Projection != null)
         {
            double value = (double)e.NewValue;

            Debug.WriteLine("Zoom: " + e.OldValue + " -> " + value);

            double remainder = value % 1;
            if(map.ScaleMode != ScaleModes.Integer && remainder != 0 && map.ActualWidth > 0)
            {
               bool scaleDown;

               switch(map.ScaleMode)
               {
                  case ScaleModes.ScaleDown:
                  scaleDown = true;
                  break;

                  case ScaleModes.Dynamic:
                  scaleDown = remainder > 0.25;
                  break;

                  default:
                  scaleDown = false;
                  break;
               }

               if(scaleDown)
                  remainder--;

               double scaleValue = Math.Pow(2d, remainder);
               {
                  if(map.MapScaleTransform == null)
                  {
                     map.MapScaleTransform = map.lastScaleTransform;
                  }
                  map.MapScaleTransform.ScaleX = scaleValue;
                  map.MapScaleTransform.ScaleY = scaleValue;

                  map.Core.scaleX = 1 / scaleValue;
                  map.Core.scaleY = 1 / scaleValue;

                  map.MapScaleTransform.CenterX = map.ActualWidth / 2;
                  map.MapScaleTransform.CenterY = map.ActualHeight / 2;
               }

               map.Core.Zoom = Convert.ToInt32(scaleDown ? Math.Ceiling(value) : value - remainder);
            }
            else
            {
               map.MapScaleTransform = null;
               map.Core.scaleX = 1;
               map.Core.scaleY = 1;
               map.Core.Zoom = (int)Math.Floor(value);
            }

            if(map.IsLoaded)
            {
               map.ForceUpdateOverlays();
               map.InvalidateVisual(true);
            }
         }
      }

      readonly ScaleTransform lastScaleTransform = new ScaleTransform();

      #endregion

      readonly Core Core = new Core();
      //GRect region;
      delegate void MethodInvoker();
      PointLatLng selectionStart;
      PointLatLng selectionEnd;
      Typeface tileTypeface = new Typeface("Arial");
      bool showTileGridLines = false;

      FormattedText Copyright;

      /// <summary>
      /// enables filling empty tiles using lower level images
      /// </summary>
      [Browsable(false)]
      public bool FillEmptyTiles
      {
         get
         {
            return Core.fillEmptyTiles;
         }
         set
         {
            Core.fillEmptyTiles = value;
         }
      }

      /// <summary>
      /// max zoom
      /// </summary>        
      [Category("GMap.NET")]
      [Description("maximum zoom level of map")]
      public int MaxZoom
      {
         get
         {
            return Core.maxZoom;
         }
         set
         {
            Core.maxZoom = value;
         }
      }

      /// <summary>
      /// min zoom
      /// </summary>      
      [Category("GMap.NET")]
      [Description("minimum zoom level of map")]
      public int MinZoom
      {
         get
         {
            return Core.minZoom;
         }
         set
         {
            Core.minZoom = value;
         }
      }

      /// <summary>
      /// pen for empty tile borders
      /// </summary>
      public Pen EmptyTileBorders = new Pen(Brushes.White, 1.0);

      /// <summary>
      /// pen for Selection
      /// </summary>
      public Pen SelectionPen = new Pen(Brushes.Blue, 2.0);

      /// <summary>
      /// background of selected area
      /// </summary>
      public Brush SelectedAreaFill = new SolidColorBrush(Color.FromArgb(33, Colors.RoyalBlue.R, Colors.RoyalBlue.G, Colors.RoyalBlue.B));

      /// <summary>
      /// /// <summary>
      /// pen for empty tile background
      /// </summary>
      public Brush EmptytileBrush = Brushes.Navy;

      /// <summary>
      /// text on empty tiles
      /// </summary>
      public FormattedText EmptyTileText = new FormattedText("We are sorry, but we don't\nhave imagery at this zoom\n     level for this region.", System.Globalization.CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, new Typeface("Arial"), 16, Brushes.Blue);

      /// <summary>
      /// map zooming type for mouse wheel
      /// </summary>
      [Category("GMap.NET")]
      [Description("map zooming type for mouse wheel")]
      public MouseWheelZoomType MouseWheelZoomType
      {
         get
         {
            return Core.MouseWheelZoomType;
         }
         set
         {
            Core.MouseWheelZoomType = value;
         }
      }
     
              /// <summary>
        /// enable map zoom on mouse wheel
        /// </summary>
        [Category("GMap.NET")]
        [Description("enable map zoom on mouse wheel")]
        public bool MouseWheelZoomEnabled
        {
            get
            {
                return Core.MouseWheelZoomEnabled;
            }
            set
            {
                Core.MouseWheelZoomEnabled = value;
            }
        }

      /// <summary>
      /// map dragg button
      /// </summary>
      [Category("GMap.NET")]
      public MouseButton DragButton = MouseButton.Right;

      /// <summary>
      /// use circle for selection
      /// </summary>
      public bool SelectionUseCircle = false;

      /// <summary>
      /// shows tile gridlines
      /// </summary>
      [Category("GMap.NET")]
      public bool ShowTileGridLines
      {
         get
         {
            return showTileGridLines;
         }
         set
         {
            showTileGridLines = value;
            InvalidateVisual();
         }
      }

      /// <summary>
      /// retry count to get tile
      /// </summary>
      [Browsable(false)]
      public int RetryLoadTile
      {
         get
         {
            return Core.RetryLoadTile;
         }
         set
         {
            Core.RetryLoadTile = value;
         }
      }

      /// <summary>
      /// how many levels of tiles are staying decompresed in memory
      /// </summary>
      [Browsable(false)]
      public int LevelsKeepInMemmory
      {
         get
         {
            return Core.LevelsKeepInMemmory;
         }

         set
         {
            Core.LevelsKeepInMemmory = value;
         }
      }

      /// <summary>
      /// current selected area in map
      /// </summary>
      private RectLatLng selectedArea;

      [Browsable(false)]
      public RectLatLng SelectedArea
      {
         get
         {
            return selectedArea;
         }
         set
         {
            selectedArea = value;
            InvalidateVisual();
         }
      }

      /// <summary>
      /// is touch control enabled
      /// </summary>
      public bool TouchEnabled = true;

      /// <summary>
      /// map boundaries
      /// </summary>
      public RectLatLng? BoundsOfMap = null;

      /// <summary>
      /// occurs when mouse selection is changed
      /// </summary>        
      public event SelectionChange OnSelectionChange;

      /// <summary>
      /// list of markers
      /// </summary>
      public readonly ObservableCollection<GMapMarker> Markers = new ObservableCollection<GMapMarker>();

      /// <summary>
      /// current markers overlay offset
      /// </summary>
      internal readonly TranslateTransform MapTranslateTransform = new TranslateTransform();
      internal readonly TranslateTransform MapOverlayTranslateTransform = new TranslateTransform();

      internal ScaleTransform MapScaleTransform = new ScaleTransform();
      internal RotateTransform MapRotateTransform = new RotateTransform();

      protected bool DesignModeInConstruct
      {
         get
         {
            return System.ComponentModel.DesignerProperties.GetIsInDesignMode(this);
         }
      }

      Canvas mapCanvas = null;

      /// <summary>
      /// markers overlay
      /// </summary>
      internal Canvas MapCanvas
      {
         get
         {
            if(mapCanvas == null)
            {
               if(this.VisualChildrenCount > 0)
               {
                  Border border = VisualTreeHelper.GetChild(this, 0) as Border;
                  ItemsPresenter items = border.Child as ItemsPresenter;
                  DependencyObject target = VisualTreeHelper.GetChild(items, 0);
                  mapCanvas = target as Canvas;

                  mapCanvas.RenderTransform = MapTranslateTransform;
               }
            }

            return mapCanvas;
         }
      }

      public GMaps Manager
      {
         get
         {
            return GMaps.Instance;
         }
      }

      static DataTemplate DataTemplateInstance;
      static ItemsPanelTemplate ItemsPanelTemplateInstance;
      static Style StyleInstance;

      public GMapControl()
      {
         if(!DesignModeInConstruct)
         {
            #region -- templates --

            #region -- xaml --
            //  <ItemsControl Name="figures">
            //    <ItemsControl.ItemTemplate>
            //        <DataTemplate>
            //            <ContentPresenter Content="{Binding Path=Shape}" />
            //        </DataTemplate>
            //    </ItemsControl.ItemTemplate>
            //    <ItemsControl.ItemsPanel>
            //        <ItemsPanelTemplate>
            //            <Canvas />
            //        </ItemsPanelTemplate>
            //    </ItemsControl.ItemsPanel>
            //    <ItemsControl.ItemContainerStyle>
            //        <Style>
            //            <Setter Property="Canvas.Left" Value="{Binding Path=LocalPositionX}"/>
            //            <Setter Property="Canvas.Top" Value="{Binding Path=LocalPositionY}"/>
            //        </Style>
            //    </ItemsControl.ItemContainerStyle>
            //</ItemsControl>
            #endregion

            if(DataTemplateInstance == null)
            {
               DataTemplateInstance = new DataTemplate(typeof(GMapMarker));
               {
                  FrameworkElementFactory fef = new FrameworkElementFactory(typeof(ContentPresenter));
                  fef.SetBinding(ContentPresenter.ContentProperty, new Binding("Shape"));
                  DataTemplateInstance.VisualTree = fef;
               }
            }
            ItemTemplate = DataTemplateInstance;

            if(ItemsPanelTemplateInstance == null)
            {
               var factoryPanel = new FrameworkElementFactory(typeof(Canvas));
               {
                  factoryPanel.SetValue(Canvas.IsItemsHostProperty, true);

                  ItemsPanelTemplateInstance = new ItemsPanelTemplate();
                  {
                     ItemsPanelTemplateInstance.VisualTree = factoryPanel;
                  }
               }
            }
            ItemsPanel = ItemsPanelTemplateInstance;

            if(StyleInstance == null)
            {
               StyleInstance = new Style();
               {
                  StyleInstance.Setters.Add(new Setter(Canvas.LeftProperty, new Binding("LocalPositionX")));
                  StyleInstance.Setters.Add(new Setter(Canvas.TopProperty, new Binding("LocalPositionY")));
                  StyleInstance.Setters.Add(new Setter(Canvas.ZIndexProperty, new Binding("ZIndex")));
               }
            }
            ItemContainerStyle = StyleInstance;
            #endregion

            ClipToBounds = true;
            SnapsToDevicePixels = true;

            Core.SystemType = "WindowsPresentation";

            Core.RenderMode = GMap.NET.RenderMode.WPF;

            Core.OnMapZoomChanged += new MapZoomChanged(ForceUpdateOverlays);
            Loaded += new RoutedEventHandler(GMapControl_Loaded);
            Dispatcher.ShutdownStarted += new EventHandler(Dispatcher_ShutdownStarted);
            SizeChanged += new SizeChangedEventHandler(GMapControl_SizeChanged);

            // by default its internal property, feel free to use your own
            if(ItemsSource == null)
            {
               ItemsSource = Markers;
            }

            Core.Zoom = (int)((double)ZoomProperty.DefaultMetadata.DefaultValue);
         }
      }

      static GMapControl()
      {
         GMapImageProxy.Enable();
#if !PocketPC
         GMaps.Instance.SQLitePing();
#endif
      }

      void invalidatorEngage(object sender, ProgressChangedEventArgs e)
      {
         base.InvalidateVisual();
      }

      /// <summary>
      /// enque built-in thread safe invalidation
      /// </summary>
      public new void InvalidateVisual()
      {
         if(Core.Refresh != null)
         {
            Core.Refresh.Set();
         }
      }

      /// <summary>
      /// Invalidates the rendering of the element, and forces a complete new layout
      /// pass. System.Windows.UIElement.OnRender(System.Windows.Media.DrawingContext)
      /// is called after the layout cycle is completed. If not forced enques built-in thread safe invalidation
      /// </summary>
      /// <param name="forced"></param>
      public void InvalidateVisual(bool forced)
      {
         if(forced)
         {
            lock(Core.invalidationLock)
            {
               Core.lastInvalidation = DateTime.Now;
            }
            base.InvalidateVisual();
         }
         else
         {
            InvalidateVisual();
         }
      }

      protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
      {
         base.OnItemsChanged(e);
         
         if(e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
         {
             ForceUpdateOverlays(e.NewItems);
         }
         else
         {
             InvalidateVisual();
         }
      }

      /// <summary>
      /// inits core system
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      void GMapControl_Loaded(object sender, RoutedEventArgs e)
      {
         if(!Core.IsStarted)
         {
            if(lazyEvents)
            {
               lazyEvents = false;

               if(lazySetZoomToFitRect.HasValue)
               {
                  SetZoomToFitRect(lazySetZoomToFitRect.Value);
                  lazySetZoomToFitRect = null;
               }
            }
            Core.OnMapOpen().ProgressChanged += new ProgressChangedEventHandler(invalidatorEngage);
            ForceUpdateOverlays();

            if(Application.Current != null)
            {
               loadedApp = Application.Current;

               loadedApp.Dispatcher.Invoke(DispatcherPriority.ApplicationIdle,
                  new Action(delegate()
                  {
                     loadedApp.SessionEnding += new SessionEndingCancelEventHandler(Current_SessionEnding);
                  }
                  ));
            }
         }
      }

      Application loadedApp;

      void Current_SessionEnding(object sender, SessionEndingCancelEventArgs e)
      {
         GMaps.Instance.CancelTileCaching();
      }

      void Dispatcher_ShutdownStarted(object sender, EventArgs e)
      {
         Dispose();
      }

      /// <summary>
      /// recalculates size
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      void GMapControl_SizeChanged(object sender, SizeChangedEventArgs e)
      {
         System.Windows.Size constraint = e.NewSize;

         // 50px outside control
         //region = new GRect(-50, -50, (int)constraint.Width + 100, (int)constraint.Height + 100);

         Core.OnMapSizeChanged((int)constraint.Width, (int)constraint.Height);

         if(Core.IsStarted)
         {
            if(IsRotated)
            {
               UpdateRotationMatrix();
            }

            ForceUpdateOverlays();
         }
      }
     
      void ForceUpdateOverlays()
      {
          ForceUpdateOverlays(ItemsSource);          
      }

      void ForceUpdateOverlays(System.Collections.IEnumerable items)
      {
         using(Dispatcher.DisableProcessing())
         {
            UpdateMarkersOffset();

            foreach(GMapMarker i in items)
            {
               if(i != null)
               {
                  i.ForceUpdateLocalPosition(this);

                  if(i is IShapable)
                  {
                     (i as IShapable).RegenerateShape(this);
                  }
               }
            }
         }
         InvalidateVisual();
      }

      /// <summary>
      /// updates markers overlay offset
      /// </summary>
      void UpdateMarkersOffset()
      {
         if(MapCanvas != null)
         {
            if(MapScaleTransform != null)
            {
               var tp = MapScaleTransform.Transform(new System.Windows.Point(Core.renderOffset.X, Core.renderOffset.Y));
               MapOverlayTranslateTransform.X = tp.X;
               MapOverlayTranslateTransform.Y = tp.Y;

               // map is scaled already
               MapTranslateTransform.X = Core.renderOffset.X;
               MapTranslateTransform.Y = Core.renderOffset.Y;
            }
            else
            {
               MapTranslateTransform.X = Core.renderOffset.X;
               MapTranslateTransform.Y = Core.renderOffset.Y;

               MapOverlayTranslateTransform.X = MapTranslateTransform.X;
               MapOverlayTranslateTransform.Y = MapTranslateTransform.Y;
            }
         }
      }

      public Brush EmptyMapBackground = Brushes.WhiteSmoke;

      /// <summary>
      /// render map in WPF
      /// </summary>
      /// <param name="g"></param>
      void DrawMap(DrawingContext g)
      {
         if(MapProvider == EmptyProvider.Instance || MapProvider == null)
         {
            return;
         }

         Core.tileDrawingListLock.AcquireReaderLock();
         Core.Matrix.EnterReadLock();
         try
         {
            foreach(var tilePoint in Core.tileDrawingList)
            {
               Core.tileRect.Location = tilePoint.PosPixel;
               Core.tileRect.OffsetNegative(Core.compensationOffset);

               //if(region.IntersectsWith(Core.tileRect) || IsRotated)
               {
                  bool found = false;

                  Tile t = Core.Matrix.GetTileWithNoLock(Core.Zoom, tilePoint.PosXY);
                  if(t.NotEmpty)
                  {
                     foreach(GMapImage img in t.Overlays)
                     {
                        if(img != null && img.Img != null)
                        {
                           if(!found)
                              found = true;

                           var imgRect = new Rect(Core.tileRect.X + 0.6, Core.tileRect.Y + 0.6, Core.tileRect.Width + 0.6, Core.tileRect.Height + 0.6);
                           if(!img.IsParent)
                           {
                              g.DrawImage(img.Img, imgRect);
                           }
                           else
                           {
                              // TODO: move calculations to loader thread
                              var geometry = new RectangleGeometry(imgRect);
                              var parentImgRect = new Rect(Core.tileRect.X - Core.tileRect.Width * img.Xoff + 0.6, Core.tileRect.Y - Core.tileRect.Height * img.Yoff + 0.6, Core.tileRect.Width * img.Ix + 0.6, Core.tileRect.Height * img.Ix + 0.6);

                              g.PushClip(geometry);
                              g.DrawImage(img.Img, parentImgRect);
                              g.Pop();
                              geometry = null;
                           }
                        }
                     }
                  }
                  else if(FillEmptyTiles && MapProvider.Projection is MercatorProjection)
                  {
                     #region -- fill empty tiles --
                     int zoomOffset = 1;
                     Tile parentTile = Tile.Empty;
                     long Ix = 0;

                     while(!parentTile.NotEmpty && zoomOffset < Core.Zoom && zoomOffset <= LevelsKeepInMemmory)
                     {
                        Ix = (long)Math.Pow(2, zoomOffset);
                        parentTile = Core.Matrix.GetTileWithNoLock(Core.Zoom - zoomOffset++, new GMap.NET.GPoint((int)(tilePoint.PosXY.X / Ix), (int)(tilePoint.PosXY.Y / Ix)));
                     }

                     if(parentTile.NotEmpty)
                     {
                        long Xoff = Math.Abs(tilePoint.PosXY.X - (parentTile.Pos.X * Ix));
                        long Yoff = Math.Abs(tilePoint.PosXY.Y - (parentTile.Pos.Y * Ix));

                        var geometry = new RectangleGeometry(new Rect(Core.tileRect.X + 0.6, Core.tileRect.Y + 0.6, Core.tileRect.Width + 0.6, Core.tileRect.Height + 0.6));
                        var parentImgRect = new Rect(Core.tileRect.X - Core.tileRect.Width * Xoff + 0.6, Core.tileRect.Y - Core.tileRect.Height * Yoff + 0.6, Core.tileRect.Width * Ix + 0.6, Core.tileRect.Height * Ix + 0.6);

                        // render tile
                        {
                           foreach(GMapImage img in parentTile.Overlays)
                           {
                              if(img != null && img.Img != null && !img.IsParent)
                              {
                                 if(!found)
                                    found = true;

                                 g.PushClip(geometry);
                                 g.DrawImage(img.Img, parentImgRect);
                                 g.DrawRectangle(SelectedAreaFill, null, geometry.Bounds);
                                 g.Pop();
                              }
                           }
                        }

                        geometry = null;
                     }
                     #endregion
                  }

                  // add text if tile is missing
                  if(!found)
                  {
                     lock(Core.FailedLoads)
                     {
                        var lt = new LoadTask(tilePoint.PosXY, Core.Zoom);

                        if(Core.FailedLoads.ContainsKey(lt))
                        {
                           g.DrawRectangle(EmptytileBrush, EmptyTileBorders, new Rect(Core.tileRect.X, Core.tileRect.Y, Core.tileRect.Width, Core.tileRect.Height));

                           var ex = Core.FailedLoads[lt];
                           FormattedText TileText = new FormattedText("Exception: " + ex.Message, System.Globalization.CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, tileTypeface, 14, Brushes.Red);
                           TileText.MaxTextWidth = Core.tileRect.Width - 11;

                           g.DrawText(TileText, new System.Windows.Point(Core.tileRect.X + 11, Core.tileRect.Y + 11));

                           g.DrawText(EmptyTileText, new System.Windows.Point(Core.tileRect.X + Core.tileRect.Width / 2 - EmptyTileText.Width / 2, Core.tileRect.Y + Core.tileRect.Height / 2 - EmptyTileText.Height / 2));
                        }
                     }
                  }

                  if(ShowTileGridLines)
                  {
                     g.DrawRectangle(null, EmptyTileBorders, new Rect(Core.tileRect.X, Core.tileRect.Y, Core.tileRect.Width, Core.tileRect.Height));

                     if(tilePoint.PosXY == Core.centerTileXYLocation)
                     {
                        FormattedText TileText = new FormattedText("CENTER:" + tilePoint.ToString(), System.Globalization.CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, tileTypeface, 16, Brushes.Red);
                        TileText.MaxTextWidth = Core.tileRect.Width;
                        g.DrawText(TileText, new System.Windows.Point(Core.tileRect.X + Core.tileRect.Width / 2 - EmptyTileText.Width / 2, Core.tileRect.Y + Core.tileRect.Height / 2 - TileText.Height / 2));
                     }
                     else
                     {
                        FormattedText TileText = new FormattedText("TILE: " + tilePoint.ToString(), System.Globalization.CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, tileTypeface, 16, Brushes.Red);
                        TileText.MaxTextWidth = Core.tileRect.Width;
                        g.DrawText(TileText, new System.Windows.Point(Core.tileRect.X + Core.tileRect.Width / 2 - EmptyTileText.Width / 2, Core.tileRect.Y + Core.tileRect.Height / 2 - TileText.Height / 2));
                     }
                  }
               }
            }
         }
         finally
         {
            Core.Matrix.LeaveReadLock();
            Core.tileDrawingListLock.ReleaseReaderLock();
         }
      }

      /// <summary>
      /// gets image of the current view
      /// </summary>
      /// <returns></returns>
      public ImageSource ToImageSource()
      {
         FrameworkElement obj = this;

         // Save current canvas transform
         Transform transform = obj.LayoutTransform;
         obj.LayoutTransform = null;

         // fix margin offset as well
         Thickness margin = obj.Margin;
         obj.Margin = new Thickness(0, 0,
         margin.Right - margin.Left, margin.Bottom - margin.Top);

         // Get the size of canvas
         Size size = new Size(obj.ActualWidth, obj.ActualHeight);

         // force control to Update
         obj.Measure(size);
         obj.Arrange(new Rect(size));

         RenderTargetBitmap bmp = new RenderTargetBitmap(
         (int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);

         bmp.Render(obj);

         if(bmp.CanFreeze)
         {
            bmp.Freeze();
         }

         // return values as they were before
         obj.LayoutTransform = transform;
         obj.Margin = margin;

         return bmp;
      }

      /// <summary>
      /// creates path from list of points, for performance set addBlurEffect to false
      /// </summary>
      /// <param name="pl"></param>
      /// <returns></returns>
      public virtual Path CreateRoutePath(List<Point> localPath)
      {
         return CreateRoutePath(localPath, true , null);
      }

      /// <summary>
      /// creates path from list of points, for performance set addBlurEffect to false
      /// </summary>
      /// <param name="pl"></param>
      /// <returns></returns>
      public virtual Path CreateRoutePath(List<Point> localPath, bool addBlurEffect, Brush brush)
      {
         // Create a StreamGeometry to use to specify myPath.
         StreamGeometry geometry = new StreamGeometry();

         using(StreamGeometryContext ctx = geometry.Open())
         {
            ctx.BeginFigure(localPath[0], false, false);

            // Draw a line to the next specified point.
            ctx.PolyLineTo(localPath, true, true);
         }

         // Freeze the geometry (make it unmodifiable)
         // for additional performance benefits.
         geometry.Freeze();

         // Create a path to draw a geometry with.
         Path myPath = new Path();
         {
            // Specify the shape of the Path using the StreamGeometry.
            myPath.Data = geometry;

            if (addBlurEffect)
            {
                BlurEffect ef = new BlurEffect();
                {
                    ef.KernelType = KernelType.Gaussian;
                    ef.Radius = 3.0;
                    ef.RenderingBias = RenderingBias.Performance;
                }

                myPath.Effect = ef;
            }
            if(brush !=null)
                myPath.Stroke = brush;
            else
                myPath.Stroke = Brushes.Magenta;
            myPath.StrokeThickness = 5;
            myPath.StrokeLineJoin = PenLineJoin.Round;
            myPath.StrokeStartLineCap = PenLineCap.Triangle;
            myPath.StrokeEndLineCap = PenLineCap.Square;

            myPath.Opacity = 0.6;
            myPath.IsHitTestVisible = false;
         }
         return myPath;
      }

      /// <summary>
      /// creates path from list of points, for performance set addBlurEffect to false
      /// </summary>
      /// <param name="pl"></param>
      /// <returns></returns>
      public virtual Path CreatePolygonPath(List<Point> localPath)
      {
         return CreatePolygonPath(localPath, false);
      }

      /// <summary>
      /// creates path from list of points, for performance set addBlurEffect to false
      /// </summary>
      /// <param name="pl"></param>
      /// <returns></returns>
      public virtual Path CreatePolygonPath(List<Point> localPath, bool addBlurEffect)
      {
         // Create a StreamGeometry to use to specify myPath.
         StreamGeometry geometry = new StreamGeometry();

         using(StreamGeometryContext ctx = geometry.Open())
         {
            ctx.BeginFigure(localPath[0], true, true);

            // Draw a line to the next specified point.
            ctx.PolyLineTo(localPath, true, true);
         }

         // Freeze the geometry (make it unmodifiable)
         // for additional performance benefits.
         geometry.Freeze();

         // Create a path to draw a geometry with.
         Path myPath = new Path();
         {
            // Specify the shape of the Path using the StreamGeometry.
            myPath.Data = geometry;

            if (addBlurEffect)
            {
                BlurEffect ef = new BlurEffect();
                {
                    ef.KernelType = KernelType.Gaussian;
                    ef.Radius = 3.0;
                    ef.RenderingBias = RenderingBias.Performance;
                }

                myPath.Effect = ef;
            }

            myPath.Stroke = Brushes.MidnightBlue;
            myPath.StrokeThickness = 5;
            myPath.StrokeLineJoin = PenLineJoin.Round;
            myPath.StrokeStartLineCap = PenLineCap.Triangle;
            myPath.StrokeEndLineCap = PenLineCap.Square;

            myPath.Fill = Brushes.AliceBlue;

            myPath.Opacity = 0.6;
            myPath.IsHitTestVisible = false;
         }
         return myPath;
      }

      /// <summary>
      /// sets zoom to max to fit rect
      /// </summary>
      /// <param name="rect">area</param>
      /// <returns></returns>
      public bool SetZoomToFitRect(RectLatLng rect)
      {
         if(lazyEvents)
         {
            lazySetZoomToFitRect = rect;
         }
         else
         {
            int maxZoom = Core.GetMaxZoomToFitRect(rect);
            if(maxZoom > 0)
            {
               PointLatLng center = new PointLatLng(rect.Lat - (rect.HeightLat / 2), rect.Lng + (rect.WidthLng / 2));
               Position = center;

               if(maxZoom > MaxZoom)
               {
                  maxZoom = MaxZoom;
               }

               if(Core.Zoom != maxZoom)
               {
                  Zoom = maxZoom;
               }

               return true;
            }
         }
         return false;
      }

      RectLatLng? lazySetZoomToFitRect = null;
      bool lazyEvents = true;

      /// <summary>
      /// sets to max zoom to fit all markers and centers them in map
      /// </summary>
      /// <param name="ZIndex">z index or null to check all</param>
      /// <returns></returns>
      public bool ZoomAndCenterMarkers(int? ZIndex)
      {
         RectLatLng? rect = GetRectOfAllMarkers(ZIndex);
         if(rect.HasValue)
         {
            return SetZoomToFitRect(rect.Value);
         }

         return false;
      }

      /// <summary>
      /// gets rectangle with all objects inside
      /// </summary>
      /// <param name="ZIndex">z index or null to check all</param>
      /// <returns></returns>
      public RectLatLng? GetRectOfAllMarkers(int? ZIndex)
      {
         RectLatLng? ret = null;

         double left = double.MaxValue;
         double top = double.MinValue;
         double right = double.MinValue;
         double bottom = double.MaxValue;
         IEnumerable<GMapMarker> Overlays;

         if(ZIndex.HasValue)
         {
            Overlays = ItemsSource.Cast<GMapMarker>().Where(p => p != null && p.ZIndex == ZIndex);
         }
         else
         {
            Overlays = ItemsSource.Cast<GMapMarker>();
         }

         if(Overlays != null)
         {
            foreach(var m in Overlays)
            {
               if(m.Shape != null && m.Shape.Visibility == System.Windows.Visibility.Visible)
               {
                  // left
                  if(m.Position.Lng < left)
                  {
                     left = m.Position.Lng;
                  }

                  // top
                  if(m.Position.Lat > top)
                  {
                     top = m.Position.Lat;
                  }

                  // right
                  if(m.Position.Lng > right)
                  {
                     right = m.Position.Lng;
                  }

                  // bottom
                  if(m.Position.Lat < bottom)
                  {
                     bottom = m.Position.Lat;
                  }
               }
            }
         }

         if(left != double.MaxValue && right != double.MinValue && top != double.MinValue && bottom != double.MaxValue)
         {
            ret = RectLatLng.FromLTRB(left, top, right, bottom);
         }

         return ret;
      }

      /// <summary>
      /// offset position in pixels
      /// </summary>
      /// <param name="x"></param>
      /// <param name="y"></param>
      public void Offset(int x, int y)
      {
         if(IsLoaded)
         {
            if(IsRotated)
            {
               Point p = new Point(x, y);
               p = rotationMatrixInvert.Transform(p);
               x = (int)p.X;
               y = (int)p.Y;

               Core.DragOffset(new GPoint(x, y));

               ForceUpdateOverlays();
            }
            else
            {
               Core.DragOffset(new GPoint(x, y));

               UpdateMarkersOffset();
               InvalidateVisual(true);
            }
         }
      }

      readonly RotateTransform rotationMatrix = new RotateTransform();
      GeneralTransform rotationMatrixInvert = new RotateTransform();

      /// <summary>
      /// updates rotation matrix
      /// </summary>
      void UpdateRotationMatrix()
      {
         System.Windows.Point center = new System.Windows.Point(ActualWidth / 2.0, ActualHeight / 2.0);

         rotationMatrix.Angle = -Bearing;
         rotationMatrix.CenterY = center.Y;
         rotationMatrix.CenterX = center.X;

         rotationMatrixInvert = rotationMatrix.Inverse;
      }

      /// <summary>
      /// returs true if map bearing is not zero
      /// </summary>        
      public bool IsRotated
      {
         get
         {
            return Core.IsRotated;
         }
      }

      /// <summary>
      /// bearing for rotation of the map
      /// </summary>
      [Category("GMap.NET")]
      public float Bearing
      {
         get
         {
            return Core.bearing;
         }
         set
         {
            if(Core.bearing != value)
            {
               bool resize = Core.bearing == 0;
               Core.bearing = value;

               UpdateRotationMatrix();

               if(value != 0 && value % 360 != 0)
               {
                  Core.IsRotated = true;

                  if(Core.tileRectBearing.Size == Core.tileRect.Size)
                  {
                     Core.tileRectBearing = Core.tileRect;
                     Core.tileRectBearing.Inflate(1, 1);
                  }
               }
               else
               {
                  Core.IsRotated = false;
                  Core.tileRectBearing = Core.tileRect;
               }

               if(resize)
               {
                  Core.OnMapSizeChanged((int)ActualWidth, (int)ActualHeight);
               }

               if(Core.IsStarted)
               {
                  ForceUpdateOverlays();
               }
            }
         }
      }

      /// <summary>
      /// apply transformation if in rotation mode
      /// </summary>
      System.Windows.Point ApplyRotation(double x, double y)
      {
         System.Windows.Point ret = new System.Windows.Point(x, y);

         if(IsRotated)
         {
            ret = rotationMatrix.Transform(ret);
         }

         return ret;
      }

      /// <summary>
      /// apply transformation if in rotation mode
      /// </summary>
      System.Windows.Point ApplyRotationInversion(double x, double y)
      {
         System.Windows.Point ret = new System.Windows.Point(x, y);

         if(IsRotated)
         {
            ret = rotationMatrixInvert.Transform(ret);
         }

         return ret;
      }

      #region UserControl Events
      protected override void OnRender(DrawingContext drawingContext)
      {
         if(!Core.IsStarted)
            return;

         drawingContext.DrawRectangle(EmptyMapBackground, null, new Rect(RenderSize));

         if(IsRotated)
         {
            drawingContext.PushTransform(rotationMatrix);

            if(MapScaleTransform != null)
            {
               drawingContext.PushTransform(MapScaleTransform);
               drawingContext.PushTransform(MapTranslateTransform);
               {
                  DrawMap(drawingContext);
               }
               drawingContext.Pop();
               drawingContext.Pop();
            }
            else
            {
               drawingContext.PushTransform(MapTranslateTransform);
               {
                  DrawMap(drawingContext);
               }
               drawingContext.Pop();
            }

            drawingContext.Pop();
         }
         else
         {
            if(MapScaleTransform != null)
            {
               drawingContext.PushTransform(MapScaleTransform);
               drawingContext.PushTransform(MapTranslateTransform);
               {
                  DrawMap(drawingContext);

#if DEBUG
                  drawingContext.DrawLine(VirtualCenterCrossPen, new Point(-20, 0), new Point(20, 0));
                  drawingContext.DrawLine(VirtualCenterCrossPen, new Point(0, -20), new Point(0, 20));
#endif
               }
               drawingContext.Pop();
               drawingContext.Pop();
            }
            else
            {
               drawingContext.PushTransform(MapTranslateTransform);
               {
                  DrawMap(drawingContext);
#if DEBUG
                  drawingContext.DrawLine(VirtualCenterCrossPen, new Point(-20, 0), new Point(20, 0));
                  drawingContext.DrawLine(VirtualCenterCrossPen, new Point(0, -20), new Point(0, 20));
#endif
               }
               drawingContext.Pop();
            }
         }

         // selection
         if(!SelectedArea.IsEmpty)
         {
            GPoint p1 = FromLatLngToLocal(SelectedArea.LocationTopLeft);
            GPoint p2 = FromLatLngToLocal(SelectedArea.LocationRightBottom);

            long x1 = p1.X;
            long y1 = p1.Y;
            long x2 = p2.X;
            long y2 = p2.Y;

            if(SelectionUseCircle)
            {
               drawingContext.DrawEllipse(SelectedAreaFill, SelectionPen, new System.Windows.Point(x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2), (x2 - x1) / 2, (y2 - y1) / 2);
            }
            else
            {
               drawingContext.DrawRoundedRectangle(SelectedAreaFill, SelectionPen, new Rect(x1, y1, x2 - x1, y2 - y1), 5, 5);
            }
         }

         if(ShowCenter)
         {
            drawingContext.DrawLine(CenterCrossPen, new System.Windows.Point((ActualWidth / 2) - 5, ActualHeight / 2), new System.Windows.Point((ActualWidth / 2) + 5, ActualHeight / 2));
            drawingContext.DrawLine(CenterCrossPen, new System.Windows.Point(ActualWidth / 2, (ActualHeight / 2) - 5), new System.Windows.Point(ActualWidth / 2, (ActualHeight / 2) + 5));
         }

         if(renderHelperLine)
         {
            var p = Mouse.GetPosition(this);

            drawingContext.DrawLine(HelperLinePen, new Point(p.X, 0), new Point(p.X, ActualHeight));
            drawingContext.DrawLine(HelperLinePen, new Point(0, p.Y), new Point(ActualWidth, p.Y));
         }

         #region -- copyright --

         if(Copyright != null)
         {
            drawingContext.DrawText(Copyright, new System.Windows.Point(5, ActualHeight - Copyright.Height - 5));
         }

         #endregion

         base.OnRender(drawingContext);
      }

      public Pen CenterCrossPen = new Pen(Brushes.Red, 1);
      public bool ShowCenter = true;

#if DEBUG
      readonly Pen VirtualCenterCrossPen = new Pen(Brushes.Blue, 1);
#endif

      HelperLineOptions helperLineOption = HelperLineOptions.DontShow;

      /// <summary>
      /// draw lines at the mouse pointer position
      /// </summary>
      [Browsable(false)]
      public HelperLineOptions HelperLineOption
      {
         get
         {
            return helperLineOption;
         }
         set
         {
            helperLineOption = value;
            renderHelperLine = (helperLineOption == HelperLineOptions.ShowAlways);
            if(Core.IsStarted)
            {
               InvalidateVisual();
            }
         }
      }

      public Pen HelperLinePen = new Pen(Brushes.Blue, 1);
      bool renderHelperLine = false;

      protected override void OnKeyUp(KeyEventArgs e)
      {
         base.OnKeyUp(e);
         
         if(HelperLineOption == HelperLineOptions.ShowOnModifierKey)
         {
            renderHelperLine = !(e.IsUp && (e.Key == Key.LeftShift || e.SystemKey == Key.LeftAlt));
            if(!renderHelperLine)
            {
               InvalidateVisual();
            }
         }        
      }

      protected override void OnKeyDown(KeyEventArgs e)
      {
         base.OnKeyDown(e);
         
         if(HelperLineOption == HelperLineOptions.ShowOnModifierKey)
         {
            renderHelperLine = e.IsDown && (e.Key == Key.LeftShift || e.SystemKey == Key.LeftAlt);
            if(renderHelperLine)
            {
               InvalidateVisual();
            }
         }        
      }

      /// <summary>
      /// reverses MouseWheel zooming direction
      /// </summary>
      public bool InvertedMouseWheelZooming = false;

      /// <summary>
      /// lets you zoom by MouseWheel even when pointer is in area of marker
      /// </summary>
      public bool IgnoreMarkerOnMouseWheel = false;

      protected override void OnMouseWheel(MouseWheelEventArgs e)
      {
         base.OnMouseWheel(e);

         if(MouseWheelZoomEnabled && (IsMouseDirectlyOver || IgnoreMarkerOnMouseWheel) && !Core.IsDragging)
         {
            System.Windows.Point p = e.GetPosition(this);

            if(MapScaleTransform != null)
            {
               p = MapScaleTransform.Inverse.Transform(p);
            }

            p = ApplyRotationInversion(p.X, p.Y);

            if(Core.mouseLastZoom.X != (int)p.X && Core.mouseLastZoom.Y != (int)p.Y)
            {
               if(MouseWheelZoomType == MouseWheelZoomType.MousePositionAndCenter)
               {
                  Core.position = FromLocalToLatLng((int)p.X, (int)p.Y);
               }
               else if(MouseWheelZoomType == MouseWheelZoomType.ViewCenter)
               {
                  Core.position = FromLocalToLatLng((int)ActualWidth / 2, (int)ActualHeight / 2);
               }
               else if(MouseWheelZoomType == MouseWheelZoomType.MousePositionWithoutCenter)
               {
                  Core.position = FromLocalToLatLng((int)p.X, (int)p.Y);
               }

               Core.mouseLastZoom.X = (int)p.X;
               Core.mouseLastZoom.Y = (int)p.Y;
            }

            // set mouse position to map center
            if(MouseWheelZoomType != MouseWheelZoomType.MousePositionWithoutCenter)
            {
               System.Windows.Point ps = PointToScreen(new System.Windows.Point(ActualWidth / 2, ActualHeight / 2));
               Stuff.SetCursorPos((int)ps.X, (int)ps.Y);
            }

            Core.MouseWheelZooming = true;

            if(e.Delta > 0)
            {
               if(!InvertedMouseWheelZooming)
               {
                  Zoom = ((int)Zoom) + 1;
               }
               else
               {
                  Zoom = ((int)(Zoom + 0.99)) - 1;
               }
            }
            else
            {
               if(InvertedMouseWheelZooming)
               {
                  Zoom = ((int)Zoom) + 1;
               }
               else
               {
                  Zoom = ((int)(Zoom + 0.99)) - 1;
               }
            }

            Core.MouseWheelZooming = false;
         }
      }

      bool isSelected = false;

      protected override void OnMouseDown(MouseButtonEventArgs e)
      {
         base.OnMouseDown(e);
         if(CanDragMap && e.ChangedButton == DragButton)
         {
            Point p = e.GetPosition(this);

            if(MapScaleTransform != null)
            {
               p = MapScaleTransform.Inverse.Transform(p);
            }

            p = ApplyRotationInversion(p.X, p.Y);

            Core.mouseDown.X = (int)p.X;
            Core.mouseDown.Y = (int)p.Y;

            InvalidateVisual();
         }
         else
         {
            if(!isSelected)
            {
               Point p = e.GetPosition(this);
               isSelected = true;
               SelectedArea = RectLatLng.Empty;
               selectionEnd = PointLatLng.Empty;
               selectionStart = FromLocalToLatLng((int)p.X, (int)p.Y);
            }
         }        
      }

      int onMouseUpTimestamp = 0;

      protected override void OnMouseUp(MouseButtonEventArgs e)
      {
         base.OnMouseUp(e);
         
         if(isSelected)
         {
            isSelected = false;
         }

         if(Core.IsDragging)
         {
            if(isDragging)
            {
               onMouseUpTimestamp = e.Timestamp & Int32.MaxValue;
               isDragging = false;
               Debug.WriteLine("IsDragging = " + isDragging);
               Cursor = cursorBefore;
               Mouse.Capture(null);
            }
            Core.EndDrag();

            if(BoundsOfMap.HasValue && !BoundsOfMap.Value.Contains(Position))
            {
               if(Core.LastLocationInBounds.HasValue)
               {
                  Position = Core.LastLocationInBounds.Value;
               }
            }
         }
         else
         {
            if(e.ChangedButton == DragButton)
            {
               Core.mouseDown = GPoint.Empty;
            }

            if(!selectionEnd.IsEmpty && !selectionStart.IsEmpty)
            {
               bool zoomtofit = false;

               if(!SelectedArea.IsEmpty && Keyboard.Modifiers == ModifierKeys.Shift)
               {
                  zoomtofit = SetZoomToFitRect(SelectedArea);
               }

               if(OnSelectionChange != null)
               {
                  OnSelectionChange(SelectedArea, zoomtofit);
               }
            }
            else
            {
               InvalidateVisual();
            }
         }
      }

      Cursor cursorBefore = Cursors.Arrow;

      protected override void OnMouseMove(MouseEventArgs e)
      {
           if (CanDragMap)
            {
                 base.OnMouseMove(e);
                // wpf generates to many events if mouse is over some visual
                // and OnMouseUp is fired, wtf, anyway...
                // http://greatmaps.codeplex.com/workitem/16013
                if ((e.Timestamp & Int32.MaxValue) - onMouseUpTimestamp < 55)
                {
                    Debug.WriteLine("OnMouseMove skipped: " + ((e.Timestamp & Int32.MaxValue) - onMouseUpTimestamp) + "ms");
                    return;
                }

                if (!Core.IsDragging && !Core.mouseDown.IsEmpty)
                {
                    Point p = e.GetPosition(this);

                    if (MapScaleTransform != null)
                    {
                        p = MapScaleTransform.Inverse.Transform(p);
                    }

                    p = ApplyRotationInversion(p.X, p.Y);

                    // cursor has moved beyond drag tolerance
                    if (Math.Abs(p.X - Core.mouseDown.X) * 2 >= SystemParameters.MinimumHorizontalDragDistance || Math.Abs(p.Y - Core.mouseDown.Y) * 2 >= SystemParameters.MinimumVerticalDragDistance)
                    {
                        Core.BeginDrag(Core.mouseDown);
                    }
                }

                if (Core.IsDragging)
                {
                    if (!isDragging)
                    {
                        isDragging = true;
                        Debug.WriteLine("IsDragging = " + isDragging);
                        cursorBefore = Cursor;
                        Cursor = Cursors.SizeAll;
                        Mouse.Capture(this);
                    }

                    if (BoundsOfMap.HasValue && !BoundsOfMap.Value.Contains(Position))
                    {
                        // ...
                    }
                    else
                    {
                        Point p = e.GetPosition(this);

                        if (MapScaleTransform != null)
                        {
                            p = MapScaleTransform.Inverse.Transform(p);
                        }

                        p = ApplyRotationInversion(p.X, p.Y);

                        Core.mouseCurrent.X = (int)p.X;
                        Core.mouseCurrent.Y = (int)p.Y;
                        {
                            Core.Drag(Core.mouseCurrent);
                        }

                        if (IsRotated || scaleMode != ScaleModes.Integer)
                        {
                            ForceUpdateOverlays();
                        }
                        else
                        {
                            UpdateMarkersOffset();
                        }
                    }
                    InvalidateVisual(true);
                }
                else
                {
                    if (isSelected && !selectionStart.IsEmpty && (Keyboard.Modifiers == ModifierKeys.Shift || Keyboard.Modifiers == ModifierKeys.Alt || DisableAltForSelection))
                    {
                        System.Windows.Point p = e.GetPosition(this);
                        selectionEnd = FromLocalToLatLng((int)p.X, (int)p.Y);
                        {
                            GMap.NET.PointLatLng p1 = selectionStart;
                            GMap.NET.PointLatLng p2 = selectionEnd;

                            double x1 = Math.Min(p1.Lng, p2.Lng);
                            double y1 = Math.Max(p1.Lat, p2.Lat);
                            double x2 = Math.Max(p1.Lng, p2.Lng);
                            double y2 = Math.Min(p1.Lat, p2.Lat);

                            SelectedArea = new RectLatLng(y1, x1, x2 - x1, y1 - y2);
                        }
                    }

                    if (renderHelperLine)
                    {
                        InvalidateVisual(true);
                    }
                }
            }    
      }

      /// <summary>
      /// if true, selects area just by holding mouse and moving
      /// </summary>
      public bool DisableAltForSelection = false;

      protected override void OnStylusDown(StylusDownEventArgs e)
      {
         base.OnStylusDown(e);
         
         if(TouchEnabled && CanDragMap && !e.InAir)
         {
            Point p = e.GetPosition(this);

            if(MapScaleTransform != null)
            {
               p = MapScaleTransform.Inverse.Transform(p);
            }

            p = ApplyRotationInversion(p.X, p.Y);

            Core.mouseDown.X = (int)p.X;
            Core.mouseDown.Y = (int)p.Y;

            InvalidateVisual();
         }        
      }

      protected override void OnStylusUp(StylusEventArgs e)
      {
         base.OnStylusUp(e);
         
         if(TouchEnabled)
         {
            if(isSelected)
            {
               isSelected = false;
            }

            if(Core.IsDragging)
            {
               if(isDragging)
               {
                  onMouseUpTimestamp = e.Timestamp & Int32.MaxValue;
                  isDragging = false;
                  Debug.WriteLine("IsDragging = " + isDragging);
                  Cursor = cursorBefore;
                  Mouse.Capture(null);
               }
               Core.EndDrag();

               if(BoundsOfMap.HasValue && !BoundsOfMap.Value.Contains(Position))
               {
                  if(Core.LastLocationInBounds.HasValue)
                  {
                     Position = Core.LastLocationInBounds.Value;
                  }
               }
            }
            else
            {
               Core.mouseDown = GPoint.Empty;
               InvalidateVisual();
            }
         }
      }

      protected override void OnStylusMove(StylusEventArgs e)
      {
         base.OnStylusMove(e);
         
         if(TouchEnabled && CanDragMap)
         {
            // wpf generates to many events if mouse is over some visual
            // and OnMouseUp is fired, wtf, anyway...
            // http://greatmaps.codeplex.com/workitem/16013
            if((e.Timestamp & Int32.MaxValue) - onMouseUpTimestamp < 55)
            {
               Debug.WriteLine("OnMouseMove skipped: " + ((e.Timestamp & Int32.MaxValue) - onMouseUpTimestamp) + "ms");
               return;
            }

            if(!Core.IsDragging && !Core.mouseDown.IsEmpty)
            {
               Point p = e.GetPosition(this);

               if(MapScaleTransform != null)
               {
                  p = MapScaleTransform.Inverse.Transform(p);
               }

               p = ApplyRotationInversion(p.X, p.Y);

               // cursor has moved beyond drag tolerance
               if(Math.Abs(p.X - Core.mouseDown.X) * 2 >= SystemParameters.MinimumHorizontalDragDistance || Math.Abs(p.Y - Core.mouseDown.Y) * 2 >= SystemParameters.MinimumVerticalDragDistance)
               {
                  Core.BeginDrag(Core.mouseDown);
               }
            }

            if(Core.IsDragging)
            {
               if(!isDragging)
               {
                  isDragging = true;
                  Debug.WriteLine("IsDragging = " + isDragging);
                  cursorBefore = Cursor;
                  Cursor = Cursors.SizeAll;
                  Mouse.Capture(this);
               }

               if(BoundsOfMap.HasValue && !BoundsOfMap.Value.Contains(Position))
               {
                  // ...
               }
               else
               {
                  Point p = e.GetPosition(this);

                  if(MapScaleTransform != null)
                  {
                     p = MapScaleTransform.Inverse.Transform(p);
                  }

                  p = ApplyRotationInversion(p.X, p.Y);

                  Core.mouseCurrent.X = (int)p.X;
                  Core.mouseCurrent.Y = (int)p.Y;
                  {
                     Core.Drag(Core.mouseCurrent);
                  }

                  if(IsRotated)
                  {
                     ForceUpdateOverlays();
                  }
                  else
                  {
                     UpdateMarkersOffset();
                  }
               }
               InvalidateVisual();
            }
         }        
      }

      #endregion

      #region IGControl Members

      /// <summary>
      /// Call it to empty tile cache & reload tiles
      /// </summary>
      public void ReloadMap()
      {
         Core.ReloadMap();
      }

      /// <summary>
      /// sets position using geocoder
      /// </summary>
      /// <param name="keys"></param>
      /// <returns></returns>
      public GeoCoderStatusCode SetPositionByKeywords(string keys)
      {
         GeoCoderStatusCode status = GeoCoderStatusCode.Unknow;

         GeocodingProvider gp = MapProvider as GeocodingProvider;
         if(gp == null)
         {
            gp = GMapProviders.OpenStreetMap as GeocodingProvider;
         }

         if(gp != null)
         {
            var pt = gp.GetPoint(keys, out status);
            if(status == GeoCoderStatusCode.G_GEO_SUCCESS && pt.HasValue)
            {
               Position = pt.Value;
            }
         }

         return status;
      }

      public PointLatLng FromLocalToLatLng(int x, int y)
      {
         if(MapScaleTransform != null)
         {
            var tp = MapScaleTransform.Inverse.Transform(new System.Windows.Point(x, y));
            x = (int)tp.X;
            y = (int)tp.Y;
         }

         if(IsRotated)
         {
            var f = rotationMatrixInvert.Transform(new System.Windows.Point(x, y));

            x = (int)f.X;
            y = (int)f.Y;
         }

         return Core.FromLocalToLatLng(x, y);
      }

      public GPoint FromLatLngToLocal(PointLatLng point)
      {
         GPoint ret = Core.FromLatLngToLocal(point);

         if(MapScaleTransform != null)
         {
            var tp = MapScaleTransform.Transform(new System.Windows.Point(ret.X, ret.Y));
            ret.X = (int)tp.X;
            ret.Y = (int)tp.Y;
         }

         if(IsRotated)
         {
            var f = rotationMatrix.Transform(new System.Windows.Point(ret.X, ret.Y));

            ret.X = (int)f.X;
            ret.Y = (int)f.Y;
         }

         return ret;
      }

      public bool ShowExportDialog()
      {
         var dlg = new Microsoft.Win32.SaveFileDialog();
         {
            dlg.CheckPathExists = true;
            dlg.CheckFileExists = false;
            dlg.AddExtension = true;
            dlg.DefaultExt = "gmdb";
            dlg.ValidateNames = true;
            dlg.Title = "GMap.NET: Export map to db, if file exsist only new data will be added";
            dlg.FileName = "DataExp";
            dlg.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            dlg.Filter = "GMap.NET DB files (*.gmdb)|*.gmdb";
            dlg.FilterIndex = 1;
            dlg.RestoreDirectory = true;

            if(dlg.ShowDialog() == true)
            {
               bool ok = GMaps.Instance.ExportToGMDB(dlg.FileName);
               if(ok)
               {
                  MessageBox.Show("Complete!", "GMap.NET", MessageBoxButton.OK, MessageBoxImage.Information);
               }
               else
               {
                  MessageBox.Show("  Failed!", "GMap.NET", MessageBoxButton.OK, MessageBoxImage.Warning);
               }

               return ok;
            }
         }

         return false;
      }

      public bool ShowImportDialog()
      {
         var dlg = new Microsoft.Win32.OpenFileDialog();
         {
            dlg.CheckPathExists = true;
            dlg.CheckFileExists = false;
            dlg.AddExtension = true;
            dlg.DefaultExt = "gmdb";
            dlg.ValidateNames = true;
            dlg.Title = "GMap.NET: Import to db, only new data will be added";
            dlg.FileName = "DataImport";
            dlg.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            dlg.Filter = "GMap.NET DB files (*.gmdb)|*.gmdb";
            dlg.FilterIndex = 1;
            dlg.RestoreDirectory = true;

            if(dlg.ShowDialog() == true)
            {
               Cursor = Cursors.Wait;

               bool ok = GMaps.Instance.ImportFromGMDB(dlg.FileName);
               if(ok)
               {
                  MessageBox.Show("Complete!", "GMap.NET", MessageBoxButton.OK, MessageBoxImage.Information);
                  ReloadMap();
               }
               else
               {
                  MessageBox.Show("  Failed!", "GMap.NET", MessageBoxButton.OK, MessageBoxImage.Warning);
               }

               Cursor = Cursors.Arrow;

               return ok;
            }
         }

         return false;
      }

      /// <summary>
      /// current coordinates of the map center
      /// </summary>
      [Browsable(false)]
      public PointLatLng Position
      {
         get
         {
            return Core.Position;
         }
         set
         {
            Core.Position = value;

            if(Core.IsStarted)
            {
               ForceUpdateOverlays();
            }
         }
      }

      [Browsable(false)]
      public GPoint PositionPixel
      {
         get
         {
            return Core.PositionPixel;
         }
      }

      [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
      [Browsable(false)]
      public string CacheLocation
      {
         get
         {
            return CacheLocator.Location;
         }
         set
         {
            CacheLocator.Location = value;
         }
      }

      bool isDragging = false;

      [Browsable(false)]
      public bool IsDragging
      {
         get
         {
            return isDragging;
         }
      }

      [Browsable(false)]
      public RectLatLng ViewArea
      {
         get
         {
             if(!IsRotated)
             {
                 return Core.ViewArea;
             }
             else if(Core.Provider.Projection != null)
             {
                 var p = FromLocalToLatLng(0, 0);
                 var p2 = FromLocalToLatLng((int)Width, (int)Height);
   
                 return RectLatLng.FromLTRB(p.Lng, p.Lat, p2.Lng, p2.Lat);
             }
             return RectLatLng.Empty;
         }
      }

      [Category("GMap.NET")]
      public bool CanDragMap
      {
         get
         {
            return Core.CanDragMap;
         }
         set
         {
            Core.CanDragMap = value;
         }
      }

      public GMap.NET.RenderMode RenderMode
      {
         get
         {
            return GMap.NET.RenderMode.WPF;
         }
      }

      #endregion

      #region IGControl event Members

      public event PositionChanged OnPositionChanged
      {
         add
         {
            Core.OnCurrentPositionChanged += value;
         }
         remove
         {
            Core.OnCurrentPositionChanged -= value;
         }
      }

      public event TileLoadComplete OnTileLoadComplete
      {
         add
         {
            Core.OnTileLoadComplete += value;
         }
         remove
         {
            Core.OnTileLoadComplete -= value;
         }
      }

      public event TileLoadStart OnTileLoadStart
      {
         add
         {
            Core.OnTileLoadStart += value;
         }
         remove
         {
            Core.OnTileLoadStart -= value;
         }
      }

      public event MapDrag OnMapDrag
      {
         add
         {
            Core.OnMapDrag += value;
         }
         remove
         {
            Core.OnMapDrag -= value;
         }
      }

      public event MapZoomChanged OnMapZoomChanged
      {
         add
         {
            Core.OnMapZoomChanged += value;
         }
         remove
         {
            Core.OnMapZoomChanged -= value;
         }
      }

      /// <summary>
      /// occures on map type changed
      /// </summary>
      public event MapTypeChanged OnMapTypeChanged
      {
         add
         {
            Core.OnMapTypeChanged += value;
         }
         remove
         {
            Core.OnMapTypeChanged -= value;
         }
      }

      /// <summary>
      /// occurs on empty tile displayed
      /// </summary>
      public event EmptyTileError OnEmptyTileError
      {
         add
         {
            Core.OnEmptyTileError += value;
         }
         remove
         {
            Core.OnEmptyTileError -= value;
         }
      }
      #endregion

      #region IDisposable Members

      public virtual void Dispose()
      {
         if(Core.IsStarted)
         {
            Core.OnMapZoomChanged -= new MapZoomChanged(ForceUpdateOverlays);
            Loaded -= new RoutedEventHandler(GMapControl_Loaded);
            Dispatcher.ShutdownStarted -= new EventHandler(Dispatcher_ShutdownStarted);
            SizeChanged -= new SizeChangedEventHandler(GMapControl_SizeChanged);
            if(loadedApp != null)
            {
               loadedApp.SessionEnding -= new SessionEndingCancelEventHandler(Current_SessionEnding);
            }
            Core.OnMapClose();
         }
      }

      #endregion
   }

   public enum HelperLineOptions
   {
      DontShow = 0,
      ShowAlways = 1,
      ShowOnModifierKey = 2
   }

   public enum ScaleModes
   {
      /// <summary>
      /// no scaling
      /// </summary>
      Integer,

      /// <summary>
      /// scales to fractional level using a stretched tiles, any issues -> http://greatmaps.codeplex.com/workitem/16046
      /// </summary>
      ScaleUp,

      /// <summary>
      /// scales to fractional level using a narrowed tiles, any issues -> http://greatmaps.codeplex.com/workitem/16046
      /// </summary>
      ScaleDown,

      /// <summary>
      /// scales to fractional level using a combination both stretched and narrowed tiles, any issues -> http://greatmaps.codeplex.com/workitem/16046
      /// </summary>
      Dynamic
   }

   public delegate void SelectionChange(RectLatLng Selection, bool ZoomToFit);
}