This is a migrated thread and some comments may be shown as answers.

Map is stalling causing update glitches.

1 Answer 64 Views
Map
This is a migrated thread and some comments may be shown as answers.
Jonathan
Top achievements
Rank 1
Jonathan asked on 08 Jun 2012, 10:17 AM
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
    }
 
  }
}

 

1 Answer, 1 is accepted

Sort by
0
Andrey
Telerik team
answered on 13 Jun 2012, 10:25 AM
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 >>

Tags
Map
Asked by
Jonathan
Top achievements
Rank 1
Answers by
Andrey
Telerik team
Share this question
or