Hopefully some good news for you - by taking a different approach with the buffered dynamic layer (with some support from a couple of the devs on the team here) we have managed to get the buffered dynamic layer with the accelerated display and time aware support you were looking for. The code for updated custom layer below - not necessarily the most elegant code but I am aware you are under time pressure therefore I wanted to get it to you as soon as I had confirmed basic functioning. you will likely need to make some tweaks to this to make it work in your app. I will send your contact in tech support (Pete) a zipped up project which demonstrates how you might tie it in with the TimeSlider control from the toolkit and he can forward to you.
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Windows.Media;
using ESRI.ArcGIS.Client;
using ESRI.ArcGIS.Client.Geometry;
using System.Globalization;
namespace CustomDynamicLayer
{
/// <summary>
/// This layer is a DynamicLayer that displays an ArcGIS dynamic map service, while maintaining a buffer of imagery outside the visible extent in order to
/// reduce the number of requests made to the server.
/// When the map is panned but the newly visible map extent is still within the extent of previously-requested imagery*, no new request is made to the
/// dynamic map service.
///
/// *NB: the extent that is checked when suppressing new requests is smaller than the extent that is then requested, this is to trigger new requests before "blank" areas
/// of the map are exposed.
///
/// Diagram showing visible extent, the extent that gets requested, and the smaller extent inside which no new request is made.
///
/// +----------------------------------------------+
/// | extent requested from server |
/// | +----------------------------------+ |
/// | |requests suppressed | |
/// | | +--------------------+ | |
/// | | | visible extent | | |
/// | | | | | |
/// | | | | | |
/// | | | | | |
/// | | | | | |
/// | | | | | |
/// | | +--------------------+ | |
/// | | | |
/// | +----------------------------------+ |
/// | |
/// +----------------------------------------------+
///
/// After panning to the east, there's still no new request made because the visible extent is still inside the 'requests suppressed' extent:
///
/// +----------------------------------------------+
/// |cached image requested from server |
/// | +----------------------------------+ |
/// | | requests suppressed | |
/// | | +--------------------++ |
/// | | | visible extent || |
/// | | | || |
/// | | | || |
/// | | | || |
/// | | | || |
/// | | | || |
/// | | +--------------------++ |
/// | | | |
/// | +----------------------------------+ |
/// | |
/// +----------------------------------------------+
///
/// When visible extent moves outside "requests suppressed", then a new expanded extent is requested from the server.
///
/// </summary>
internal class BufferedArcGISDynamicMapServiceLayerByUrl : DynamicMapServiceLayer
{
/// <summary>
/// Contained layer we use to hit the server.
/// </summary>
public ArcGISDynamicMapServiceLayer _layer;
/// <summary>
/// Extent inside which the map can be panned without triggering a new request
/// </summary>
private Envelope _suppressRequestsExtent;
/// <summary>
/// Resolution of cached imagery
/// </summary>
private double _requestedResolution;
private ImageResult _cachedResult;
private string _cachedUrl;
/// <summary>
/// List of callbacks waiting to be called.
/// </summary>
List<OnUrlComplete> _callbacks = new List<OnUrlComplete>();
private ImageParameters _adjustedProperties;
public BufferedArcGISDynamicMapServiceLayerByUrl()
{
_layer = new ArcGISDynamicMapServiceLayer();
_layer.Initialized += ContainedLayerInitialized;
_layer.InitializationFailed += ContainedLayerInitializationFailed;
}
public override void Initialize()
{
_layer.Initialize();
}
private void ContainedLayerInitializationFailed(object sender, EventArgs e)
{
InitializationFailure = _layer.InitializationFailure;
base.Initialize();
}
private void ContainedLayerInitialized(object sender, EventArgs e)
{
SpatialReference = _layer.SpatialReference;
SupportsRotation = _layer.SupportsRotation;
base.Initialize();
}
public string Url
{
get { return _layer.Url; }
set { _layer.Url = value; }
}
double GetImageParametersResolution(ImageParameters properties)
{
return properties.Extent.Width / properties.Width;
}
public override void GetUrl(ImageParameters properties, OnUrlComplete onComplete)
{
// calculate if the requested extent can be dealt with without a new HTTP request
bool requestedExtentIsContainedInExistingImagery = _suppressRequestsExtent.Contains(properties.Extent);
bool resolutionMatchesExistingImagery = Math.Abs(GetImageParametersResolution(properties) - _requestedResolution) < 0.1;
if (requestedExtentIsContainedInExistingImagery && resolutionMatchesExistingImagery)
{
_layer.GetUrl(_adjustedProperties, (url, res) =>
{
_cachedUrl = url + "&time="
+ MillisecondsSinceEpoch(Map.TimeExtent.Start)
+ ","
+ MillisecondsSinceEpoch(Map.TimeExtent.End)
+ "&imageSR=" + Map.SpatialReference.WKID.ToString()
+ "&bboxSR=" + Map.SpatialReference.WKID.ToString();
_cachedResult = res;
onComplete(_cachedUrl, _cachedResult);
});
}
else
{
// A new image needs to be requested to cover the map
// Add a buffer around the requested extent
Envelope bufferedExtent = properties.Extent.Expanded(properties.Extent.Width/2,
properties.Extent.Height/2);
_adjustedProperties = new ImageParameters(bufferedExtent, properties.Width*2, properties.Height*2)
{
Dpi = properties.Dpi,
Rotation = properties.Rotation,
WrapAround = properties.WrapAround
};
// Store new 'covered extent' and resolution now, otherwise if new getsource calls are made we might re-request new images that would have been covered by this one.
_suppressRequestsExtent = properties.Extent.Expanded(properties.Extent.Width/3, properties.Extent.Height/3);
_requestedResolution = GetImageParametersResolution(_adjustedProperties);
_layer.GetUrl(_adjustedProperties, (url, res) =>
{
url += "&time="
+ MillisecondsSinceEpoch(Map.TimeExtent.Start)
+ ","
+ MillisecondsSinceEpoch(Map.TimeExtent.End)
+ "&imageSR=" + Map.SpatialReference.WKID.ToString()
+ "&bboxSR=" + Map.SpatialReference.WKID.ToString();
onComplete(url, res);
});
}
}
private string MillisecondsSinceEpoch(DateTime myDateTime)
{
//return myDateTime.ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
return ((myDateTime).ToUniversalTime() - epoch).TotalMilliseconds.ToString(CultureInfo.InvariantCulture);
}
}
}