Auto Refreshing Map Layers

More and more I see the need for data to be served up in real time or with an associated timestamp. The addition of time extents to data was a welcome addition to the ArcGIS stack but what if you just want to frequently refresh the display of data for layers within your map display with data that you know will be changing. The concept for this is very simple, you just need to refresh the layer at a defined interval. Within the Esri Silverlight API there are a number of layers that support this but they also need to have the DisableClientCaching property as this will allow you to tell the application to get a new version of the data rather than using the cached data from the browser. Here’s a simple way to add this functionality into your Esri Silverlight projects for any layer with the Refresh method and DisableClientCaching property using a bit of reflection, this includes your own layers that implement these. 

public class AutoRefresher : DependencyObject
{
    private DispatcherTimer _timer;

    public AutoRefresher()
    {
        Interval = 10;
    }

    /// <summary>
    /// Number of seconds between refreshes. Default is 10
    /// </summary>
    public int Interval { get; set; }

    /// <summary>
    /// Gets the refresher attached to the dependency object
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public static AutoRefresher GetRefresher(DependencyObject obj)
    {
        return (AutoRefresher)obj.GetValue(RefresherProperty);
    }
    /// <summary>
    /// Attaches a refresher to a dependency object
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="value"></param>
    public static void SetRefresher(DependencyObject obj, AutoRefresher value)
    {
        obj.SetValue(RefresherProperty, value);
    }

    /// <summary>
    /// Identifies the Refresher attached dependency property.
    /// </summary>
    public static readonly DependencyProperty RefresherProperty =
        DependencyProperty.RegisterAttached("Refresher", typeof(AutoRefresher), typeof(AutoRefresher), new PropertyMetadata(OnRefresherPropertyChanged));

    private static void OnRefresherPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var layer = d as Layer;
        if (layer == null) return;
        
        var newVal = e.NewValue as AutoRefresher;
        var oldVal = e.OldValue as AutoRefresher;

        if (oldVal != null)
            oldVal.DetachTimer(layer);

        if (newVal != null)
            newVal.AttachTimer(layer);        
    }

    private void DetachTimer(Layer layer)
    {
        if (_timer != null && _timer.IsEnabled)
            _timer.Stop();

        _timer = null;
    }

    private void AttachTimer(Layer layer)
    {
        var type = layer.GetType();

        var propertyInfos = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
        var propertyInfo = propertyInfos.FirstOrDefault(pi => string.Equals("DisableClientCaching", pi.Name, StringComparison.OrdinalIgnoreCase));

        MethodInfo methodInfo = null;
        if (propertyInfo != null)
        {
            // Set DisableClientCaching = true
            propertyInfo.SetValue(layer, true, null);
            methodInfo = type.GetMethod("Refresh", BindingFlags.Public | BindingFlags.Instance);
        }

        if (methodInfo == null)
            throw new NotSupportedException(
                "The layer type that the behavior is attached to does not support automatic data refresh.");

        _timer = new DispatcherTimer { Interval = new TimeSpan(0, 0, Interval) };
        _timer.Tick += (sender, e) =>
        {
            if (layer.Visible) methodInfo.Invoke(layer, null);
        };

        if (layer.IsInitialized && layer.InitializationFailure == null)
            _timer.Start();
        else
            layer.Initialized += (sender2, e2) => _timer.Start();
    }
}

Then in markup

<esri:ArcGISDynamicMapServiceLayer 
    ID="Blah" Url="your url">
    <local:AutoRefresher.Refresher>
        <local:AutoRefresher Interval="5" />
    </local:AutoRefresher.Refresher>                
</esri:ArcGISDynamicMapServiceLayer>

or code

var layer = MyMap.Layers["Blah"] as ArcGISDynamicMapServiceLayer;            
var refresher = new AutoRefresher { Interval = 5 };
layer.SetValue(AutoRefresher.RefresherProperty, refresher);

Depending on your requirements you may want to do something as simple as update the TimeExtent on the map control or for specific layers but this gives you another mechanism to work with real time data. Also working with time is always a headache especially when using data from multiple time zones so experiment to find the best solution for your needs.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s