Map is stalling causing update glitches.

2 posts, 0 answers
  1. Jonathan
    Jonathan avatar
    1 posts
    Member since:
    Jun 2012

    Posted 08 Jun 2012 Link to this post

    I am using the Telerik map, I am driving the map with custom controls, that I have on a timer update function
    When I move the map struggles to catch up & becomes quite jerky. I need to drive the map this way I don't have a choice in this. Using the Telerik controls to drive it is not an option unfortunately.

    From what I can tell it looks like the map or the provider is blocking the UI thread which causes the stall.
    This bug is more apparent in high resolutions.

    Below is the code I am using to generate the error (I know the code isn't perfect but it highlights the bug)

    turn these defines on to change timer methods (1 at a time please) or turn them both off to use the onIdle event
    #define DISPACTTIMER
    #define THREADTIMER

    <Window x:Class="WpfRadMap.MainWindow"
            Title="MainWindow" Height="1080" Width="1900" xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" ResizeMode="NoResize" Activated="Window_Activated">
      <Grid>
        <telerik:RadMap Panel.ZIndex="1" HorizontalAlignment="Stretch" Name="radMap1" VerticalAlignment="Stretch" Center="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Mode=TwoWay, Path=MapLocation}" ZoomChanged="radMap1_ZoomChanged"/>
        <Canvas Panel.ZIndex="2" x:Name="gizmoPanel" Visibility="Visible" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RenderTransformOrigin="0.5,0.5" >
          <Canvas.RenderTransform>
            <RotateTransform x:Name="GizmoRotate" Angle="0" />
          </Canvas.RenderTransform>
          <Line Stroke="Red"
                    StrokeThickness="3"
                    ClipToBounds="True"
                    X1="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoX1}"
                    X2="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoX1}"
                    Y1="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoY1}"
                    Y2="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoY2}"
                    Visibility="Visible"
                    IsHitTestVisible="False"
                    >
            <Line.Effect>
              <DropShadowEffect BlurRadius="5" ShadowDepth="0" />
            </Line.Effect>
          </Line>
     
          <!-- Small Line 1 -->
          <Line Stroke="Red"
                    StrokeThickness="3"
                    ClipToBounds="True"
                    X1="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoX1}"
                    X2="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoX2}"
                    Y1="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoY2}"
                    Y2="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoY3}"
                    Visibility="Visible"
                    IsHitTestVisible="False"
                    >
            <Line.Effect>
              <DropShadowEffect BlurRadius="5" ShadowDepth="0" />
            </Line.Effect>
          </Line>
     
          <!-- Small Line 2 -->
          <Line Stroke="Red"
                    StrokeThickness="3"
                    ClipToBounds="True"
                    X1="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoX1}"
                    X2="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoX3}"
                    Y1="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoY2}"
                    Y2="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=GizmoY3}"
                    Visibility="Visible"
                    IsHitTestVisible="False"
                    >
            <Line.Effect>
              <DropShadowEffect BlurRadius="5" ShadowDepth="0" />
            </Line.Effect>
          </Line>
        </Canvas>
        <Button Panel.ZIndex="3" Content="Rotate Clockwise" Height="39" Name="button1" Width="99" Click="button1_Click" Margin="1690,12,39,980" />
        <Button Panel.ZIndex="3" Content="Rotate AntiClockwise" Height="39" Name="button2" Width="119" Click="button2_Click" Margin="1680,57,28,935" />
        <Label Panel.ZIndex="3" Margin="1670,100, 0,910" Content="[W] to move along arrow" Height="28" Name="label1" Width="180" FontWeight="Bold" />
        <Label Panel.ZIndex="3" Margin="1670,120, 0,890" Content="[S] to move opposite to arrow" Height="28" Name="label2" Width="180" FontWeight="Bold" />
        <Label Panel.ZIndex="3" Margin="1670,140, 0,870" Content="[A] to move left of arrow" Height="28" Name="label3" Width="180" FontWeight="Bold" />
        <Label Panel.ZIndex="3" Margin="1670,160, 0,850" Content="[D] to move right of arrow" Height="28" Name="label4" Width="180" FontWeight="Bold" />
     
        <Label Panel.ZIndex="3" Margin="0,10, 1670,980" Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=FastestTime}" Height="28" Name="lblMinSpeed" Width="180" FontWeight="Bold" />
        <Label Panel.ZIndex="3" Margin="0,40, 1670,950" Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=SlowestTime}" Height="28" Name="lblMaxSpeed" Width="180" FontWeight="Bold" />
        <Label Panel.ZIndex="3" Margin="0,70, 1670,920" Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=AverageTime}" Height="28" Name="lblAvgSpeed" Width="180" FontWeight="Bold" />
     
        <Label Panel.ZIndex="3" Margin="0,110, 1670,880" Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=FastestUpdateTime}" Height="28" Name="lblMinUpdate" Width="180" FontWeight="Bold" />
        <Label Panel.ZIndex="3" Margin="0,140, 1670,850" Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=SlowestUpdateTime}" Height="28" Name="lblMaxUpdate" Width="180" FontWeight="Bold" />
        <Label Panel.ZIndex="3" Margin="0,170, 1670,820" Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=AverageUpdateTime}" Height="28" Name="lblAvgUpdate" Width="180" FontWeight="Bold" />
      </Grid>
    </Window>

    #define QUERY_PERFORMANCE
    //#define DISPACTTIMER
    //#define THREADTIMER
     
     
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    using System.Windows.Threading;
    using System.Timers;
    using System.ComponentModel;
     
     
     
    namespace WpfRadMap
    {
      /// <summary>
      /// Interaction logic for MainWindow.xaml
      /// </summary>
      public partial class MainWindow : Window, INotifyPropertyChanged
      {
        Telerik.Windows.Controls.Map.Location m_location;
     
        double width = 1900.0;
        double height = 1080.0;
     
        private double gizmoX1;
        private double gizmoX2;
        private double gizmoX3;
     
        private double gizmoY1;
        private double gizmoY2;
        private double gizmoY3;
     
        private RotateTransform longLatRotate;
        private double framesPerSecond = 60.0;
     
    #if DISPACTTIMER
        DispatcherTimer m_updateTimer;
    #elif THREADTIMER
        Timer m_updateTimer;
    #endif
     
     
        public MainWindow()
        {
          gizmoX1 = width * 0.5;
          gizmoX2 = width * 0.485;
          gizmoX3 = width * 0.515;
     
          gizmoY1 = height * 0.5;
          gizmoY2 = height * 0.45;
          gizmoY3 = height * 0.475;
          longLatRotate = new RotateTransform();
          longLatRotate.Angle = 0.0;
          m_location = new Telerik.Windows.Controls.Map.Location();
     
     
          InitializeComponent();
          const string bingKey = "";
          if (bingKey.Length > 0)
          {
            Telerik.Windows.Controls.Map.BingMapProvider bingProvider = new Telerik.Windows.Controls.Map.BingMapProvider(Telerik.Windows.Controls.Map.MapMode.Aerial, true, bingKey);
            bingProvider.IsTileCachingEnabled = true;
            bingProvider.IsLabelVisible = true;
            radMap1.Provider = bingProvider;
            SetLabelsFontColor(Brushes.White);
          }
          else
          {
            Telerik.Windows.Controls.Map.OpenStreetMapProvider openProvider = new Telerik.Windows.Controls.Map.OpenStreetMapProvider();
            openProvider.IsTileCachingEnabled = true;
            radMap1.Provider = openProvider;
          }
     
          radMap1.ZoomLevel = 19;
          m_location.Latitude = 48.856941702106354;
          m_location.Longitude = 2.3383069038354383;
          MapLocation = m_location;
     
    #if DISPACTTIMER
          m_updateTimer = new DispatcherTimer();
          m_updateTimer.Tick += new EventHandler(m_updateTimer_Tick);
          m_updateTimer.Interval = TimeSpan.FromSeconds(1.0/framesPerSecond);
    #elif THREADTIMER
          m_updateTimer = new Timer();
          m_updateTimer.Elapsed += new ElapsedEventHandler(m_updateTimer_Tick);
          m_updateTimer.Interval = (1.0 / framesPerSecond) * 100;
          m_updateTimer.Enabled = false;
    #else
          System.Windows.Interop.ComponentDispatcher.ThreadIdle += new System.EventHandler(ComponentDispatcher_ThreadIdle);
    #endif
     
          profileTimer = System.Diagnostics.Stopwatch.StartNew();
        }
     
     
        public Telerik.Windows.Controls.Map.Location MapLocation
        {
          get { return m_location; }
          set { m_location = value; RaisePropertyChanged("MapLocation");
          }
        }
     
        double m_mapHeight = 0.0001;
        public double MapHeight
        {
          get {return m_mapHeight;}
          set {m_mapHeight = value; RaisePropertyChanged("MapHeight");}
        }
     
        private void radMap1_ZoomChanged(object sender, EventArgs e)
        {
          MapHeight = radMap1.GeographicalBounds.Height;
        }
     
     
        readonly Queue<long> m_frameTicks = new Queue<long>();
        long m_slowest = long.MinValue;
        long m_fastest = long.MaxValue;
        long m_avg = 0;
     
        public string AverageTime
        {
          get { return "Avg: " + m_avg + "us"; }
        }
     
     
        public string SlowestTime
        {
          get { return "Slowest: " + m_slowest + "us"; }
        }
     
     
        public string FastestTime
        {
          get { return "Fastest: " + m_fastest + "us"; }
        }
     
        readonly Queue<long> m_frameUpdateTicks = new Queue<long>();
        long m_slowestUpdate = long.MinValue;
        long m_fastestUpdate = long.MaxValue;
        long m_avgUpdate = 0;
        System.Diagnostics.Stopwatch profileTimer;
     
        public string AverageUpdateTime
        {
          get { return "AvgUpdate: " + m_avgUpdate + "ms"; }
        }
     
     
        public string SlowestUpdateTime
        {
          get { return "SlowestUpdate: " + m_slowestUpdate + "ms"; }
        }
     
     
        public string FastestUpdateTime
        {
          get { return "FastestUpdate: " + m_fastestUpdate + "ms"; }
        }
     
        protected void CalculatePerformance(long a_currentTick, Queue<long> a_avgTicks, ref long a_slowestTick, ref long a_fastestTick, ref long a_avgTick)
        {
          a_avgTicks.Enqueue(a_currentTick);
          if (a_avgTicks.Count > 60)
          {
            a_avgTicks.Dequeue();
          }
     
          long slowest = a_currentTick;
          long fastest = a_currentTick;
     
          long total = 0;
          foreach (long time in a_avgTicks)
          {
            total += time;
     
            slowest = Math.Max(slowest, time);
            fastest = Math.Min(fastest, time);
          }
     
          a_avgTick = total / a_avgTicks.Count;
          a_slowestTick = slowest;
          a_fastestTick = fastest;
        }
     
     
    #if DISPACTTIMER
        void m_updateTimer_Tick(object sender, EventArgs e)
    #elif THREADTIMER
        void m_updateTimer_Tick(object sender, ElapsedEventArgs e)
    #else
        void ComponentDispatcher_ThreadIdle(object sender, EventArgs e)
    #endif
        {
          long elapsedTime = profileTimer.ElapsedTicks;
          long elapsedMilliSeconds = System.Convert.ToInt64((System.Convert.ToDouble(elapsedTime) / System.Convert.ToDouble(System.Diagnostics.Stopwatch.Frequency)) * 1000);
     
    //       if (elapsedMilliSeconds < (1.0/framesPerSecond)*1000)
    //       {
    //         return;
    //       }
     
          CalculatePerformance(elapsedMilliSeconds, m_frameUpdateTicks, ref m_slowestUpdate, ref m_fastestUpdate, ref m_avgUpdate);
          RaisePropertyChanged("SlowestUpdateTime");
          RaisePropertyChanged("FastestUpdateTime");
          RaisePropertyChanged("AverageUpdateTime");
          profileTimer.Restart();
     
          double tearRepair = 1.0;
          if (elapsedMilliSeconds > 0)
          {
            tearRepair = elapsedMilliSeconds / framesPerSecond;
          }
     
          double speed = 2.0;
          double latitude = m_location.Latitude;
          double longitude = m_location.Longitude;
    #if THREADTIMER 
          Point stepDir = new Point(0.0, (MapHeight/(speed * framesPerSecond))*tearRepair);
          Point dir = stepDir; //longLatRotate.Transform(stepDir);
          latitude += dir.Y;
          longitude -= dir.X;
    #else
    #if DISPACTTIMER
          Point stepDir = new Point(0.0, (MapHeight/(speed * framesPerSecond))*tearRepair);
    #else
          Point stepDir = new Point(0.0, (MapHeight / (speed * framesPerSecond)) * tearRepair);
    #endif
          Point dir = longLatRotate.Transform(stepDir);
          if ((Keyboard.GetKeyStates(Key.W) & KeyStates.Down) > 0)
          {
            latitude += dir.Y;
            longitude -= dir.X;
          }
          if ((Keyboard.GetKeyStates(Key.S) & KeyStates.Down) > 0)
          {
            latitude -= dir.Y;
            longitude += dir.X;
          }
          if ((Keyboard.GetKeyStates(Key.A) & KeyStates.Down) > 0)
          {
            latitude -= dir.X;
            longitude -= dir.Y;
          }
          if ((Keyboard.GetKeyStates(Key.D) & KeyStates.Down) > 0)
          {
            latitude += dir.X;
            longitude += dir.Y;
          }
    #endif
     
          while (latitude>360)
          {
            latitude-=360;
          }
          while(latitude < 0)
          {
            latitude += 360;
          }
          while(longitude > 360)
          {
            longitude -= 360;
          }
          while(longitude < 0)
          {
            longitude += 360;
          }
          m_location.Latitude = latitude;
          m_location.Longitude = longitude;
     
          RaisePropertyChanged("MapLocation");
     
          elapsedTime = profileTimer.ElapsedTicks;
          long elapsedMicroSeconds = System.Convert.ToInt64((System.Convert.ToDouble(elapsedTime) / System.Convert.ToDouble(System.Diagnostics.Stopwatch.Frequency))*1000000);
     
          CalculatePerformance(elapsedMicroSeconds, m_frameTicks, ref m_slowest, ref m_fastest, ref m_avg);
          RaisePropertyChanged("SlowestTime");
          RaisePropertyChanged("FastestTime");
          RaisePropertyChanged("AverageTime");
        }
     
     
        public double GizmoX1
        {
          get { return gizmoX1; }
          set { gizmoX1 = value; }
        }
     
        public double GizmoX2
        {
          get { return gizmoX2; }
          set { gizmoX2 = value; }
        }
     
        public double GizmoX3
        {
          get { return gizmoX3; }
          set { gizmoX3 = value; }
        }
        public double GizmoY1
        {
          get { return gizmoY1; }
          set { gizmoY1 = value; }
        }
     
        public double GizmoY2
        {
          get { return gizmoY2; }
          set { gizmoY2 = value; }
        }
     
        public double GizmoY3
        {
          get { return gizmoY3; }
          set { gizmoY3 = value; }
        }
     
        private void button2_Click(object sender, RoutedEventArgs e)
        {
          longLatRotate.Angle = GizmoRotate.Angle = (GizmoRotate.Angle + 15) % 360;
        }
     
        private void button1_Click(object sender, RoutedEventArgs e)
        {
          longLatRotate.Angle = GizmoRotate.Angle = (GizmoRotate.Angle - 15);
          if (GizmoRotate.Angle == 0)
          {
            longLatRotate.Angle = GizmoRotate.Angle = 360;
          }
        }
     
        void SetLabelsFontColor(Brush a_color)
        {
          lblMinUpdate.Foreground = a_color;
          lblMaxUpdate.Foreground = a_color;
          lblAvgUpdate.Foreground = a_color;
     
          lblMinSpeed.Foreground = a_color;
          lblMaxSpeed.Foreground = a_color;
          lblAvgSpeed.Foreground = a_color;
     
          label1.Foreground = a_color;
          label2.Foreground = a_color;
          label3.Foreground = a_color;
          label4.Foreground = a_color;
        }
     
     
     
        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The property that has a new value.</param>
        protected void RaisePropertyChanged(string propertyName)
        {
          this.VerifyPropertyName(propertyName);
     
          PropertyChangedEventHandler handler = this.PropertyChanged;
          if (handler != null)
          {
            PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
          }
        }
     
     
        /// <summary>
        /// Warns the developer if this object does not have
        /// a public property with the specified name. This
        /// method does not exist in a Release build.
        /// </summary>
        public void VerifyPropertyName(string propertyName)
        {
          // If you raise PropertyChanged and do not specify a property name,
          // all properties on the object are considered to be changed by the binding system.
          if (String.IsNullOrEmpty(propertyName))
          {
            return;
          }
     
          // Verify that the property name matches a real, 
          // public, instance property on this object.
          if (TypeDescriptor.GetProperties(this)[propertyName] == null)
          {
            string msg = "Invalid property name: " + propertyName;
     
            if (this.ThrowOnInvalidPropertyName)
            {
              throw new ArgumentException(msg);
            }
          }
        }
     
        /// <summary>
        /// Returns whether an exception is thrown, or if a Debug.Fail() is used
        /// when an invalid property name is passed to the VerifyPropertyName method.
        /// The default value is false, but subclasses used by unit tests might
        /// override this property's getter to return true.
        /// </summary>
        protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
     
     
        /// <summary>
        /// Raised when a property on this object has a new value.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
     
        private void Window_Activated(object sender, EventArgs e)
        {
          MapHeight = radMap1.GeographicalBounds.Height;
    #if DISPACTTIMER
          m_updateTimer.Start();
    #elif THREADTIMER
          m_updateTimer.Enabled = true;
    #endif
        }
     
      }
    }

     
  2. Andrey
    Admin
    Andrey avatar
    1681 posts

    Posted 13 Jun 2012 Link to this post

    Hi Jonathan,

    There is a difference between the Silverlight and the WPF version of RadMap. The Silverlight version is based on the standard Silverlight MultiScaleImage control. Unfortunately, there is no such control in WPF, so we have created our own implementation of the MultiScaleImage.
    All panning and zooming features which are available in Silverlight's MultiScaleImage control are simulated in WPF using the available animation features. So, the Silverlight and the WPF version have a difference for panning and zooming using mouse when the spring animation feature is enabled (RadMap.UseSpringAnimations="True"). You can disable this feature to make the panning faster. Also for performance purposes I would recommend to set IsTileCachingEnabled feature to False.

    We already have a ticket for smoothing of zooming and panning in the WPF version of the RadMap. You can track its progress using the following link:
    http://www.telerik.com/support/pits.aspx#/public/wpf/9981

    Thank you for the sample code. We will also use it for testing RadMap while working on this issue.

    Kind regards,
    Andrey Murzov
    the Telerik team

    Explore the entire Telerik portfolio by downloading the Ultimate Collection trial package. Get it now >>

  3. UI for WPF is Visual Studio 2017 Ready
Back to Top