This project has moved. For the latest updates, please go here.

Force redraw of map (WPF)

Nov 25, 2014 at 11:28 AM
Edited Nov 25, 2014 at 11:29 AM
Is there a way that I can force the map to be redrawn, including the reloading of all visible tiles?

I want to be able to force a map to be redrawn, including the reloading of the visible tiles (either from cache or directly from source) when the user changes the value that I am assigning to the TileImageLoader.DefaultCacheExpiration property.

The objective here is that the user will probably have a long cache expiration by default, but have the opportunity to shorten that (or even set it to zero) in order to force updates where they can see that one or more tiles are out of date.

I doubt that it makes any difference, however I am using the ImageFileCache. I am not expecting the control to update simply because I changed the TileImageLoader.DefaultCacheExpiration value, just for something that I can call to update it.

Thanks.
Coordinator
Nov 25, 2014 at 2:39 PM
DefaultCacheExpiration is only used when there is no Expires or Cache-Control/max-age header in the HTTP response from the tile server. Just don't use caching if you want to manually control the update behaviour. One way to implement this yourself would be to use a derived ImageTileSource.
Nov 26, 2014 at 1:58 PM
Edited Nov 26, 2014 at 2:45 PM
Because I wanted to maintain as much of the base functionality as possible, I have instead chosen to create a new TileImageLoader (based entirely on the original code) - a shared instance of which I pass to the constructor of each TileLayer - and I have then modified the DownloadImage to store the date/time downloaded in the date taken metadata and the GetCachedImage methods to compare the date taken metadata with the maximum age allowed at the time the image is retrieved...
private static ImageSource DownloadImage(Uri uri, string cacheKey, out HttpStatusCode statusCode) {

            BitmapSource image = null;
            statusCode = HttpStatusCode.Unused;

            try {

                var request = HttpWebRequest.CreateHttp(uri);
                request.UserAgent = TileImageLoader.HttpUserAgent;
                using (var response = (HttpWebResponse)request.GetResponse()) {
                    statusCode = response.StatusCode;

                    using (var responseStream = response.GetResponseStream())
                    using (var memoryStream = new MemoryStream()) {
                        responseStream.CopyTo(memoryStream);
                        memoryStream.Position = 0;
                        image = BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
                    }

                    if (cacheKey != null) {
                        MapWindowTileImageLoader.SetCachedImage(cacheKey, image, DateTime.UtcNow);
                    }

                }

            }
            catch (WebException ex) {

                var response = ex.Response as HttpWebResponse;
                if (response != null) {
                    statusCode = response.StatusCode;
                }

                Debug.WriteLine("Downloading {0} failed: {1}: {2}", uri, ex.Status, ex.Message);

            }
            catch (Exception ex) {
                Debug.WriteLine("Downloading {0} failed: {1}", uri, ex.Message);
            }

            return image;

        }

        private static bool GetCachedImage(string cacheKey, out BitmapSource image) {

            image = TileImageLoader.Cache.Get(cacheKey) as BitmapSource;
            if (image == null)
                return false;

            var metadata = (BitmapMetadata)image.Metadata;
            DateTime dateTaken;

            // get cache expiration date from BitmapMetadata.DateTaken, must be parsed with CurrentCulture
            return metadata == null || metadata.DateTaken == null
                || !DateTime.TryParse(metadata.DateTaken, CultureInfo.CurrentCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out dateTaken)
                || DateTime.UtcNow.Subtract(TileImageLoader.DefaultCacheExpiration) < dateTaken;

        }
I was able to then force a redraw by setting the ParentMap property of the TileLayer to null and then setting it back to the map again...
            MapBase parentMap = this.TileLayer.ParentMap;
            this.TileLayer.ParentMap = null;
            this.TileLayer.ParentMap = parentMap;
Finally, I have modified the SetCachedImage method; given that the date taken is now being checked within the GetCachedImage method, that I am simply going to allow all entries to stay in cache for the duration of the application...
        private static void SetCachedImage(string cacheKey, BitmapSource image, DateTime dateTaken) {

            var bitmap = BitmapFrame.Create(image);
            var metadata = (BitmapMetadata)bitmap.Metadata;

            metadata.DateTaken = dateTaken.ToString(CultureInfo.InvariantCulture);
            metadata.Freeze();
            bitmap.Freeze();

            TileImageLoader.Cache.Set(cacheKey, bitmap, new CacheItemPolicy { SlidingExpiration = TimeSpan.MaxValue });

        }
I would of course be interested to know if you (ClemensF) have any comments or suggestions regarding my approach. You after all know more about the map control than anybody else.

Martin.
Nov 26, 2014 at 5:06 PM
Edited Nov 26, 2014 at 5:08 PM
I have just realised that there was perhaps a little more work required; I needed to change the ImageFileCache class too in order to override the expiration within the MemoryCache for items retrieved from disk within the Get(string, string) method. This in turn required that I also override all other methods that use the MemoryCache object and actually maintain the MemoryCache object myself. Otherwise, having set the BitmapMetadata.DateTaken property to the date of the download, all images added to the MemoryCache are immediately expelled again as the original "absoluteExpiration" was being set to a date/time prior to the date/time the object is added to the cache...
        public override object Get(string key, string regionName = null) {

            var bitmap = this.memoryCache.Get(key, regionName) as BitmapFrame;
            if (bitmap == null) {
                try {

                    var path = MapWindowTileCache.FindFile(this.GetPath(key));
                    if (path != null) {
                        using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) {
                            bitmap = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
                            this.memoryCache.Set(key, bitmap, new CacheItemPolicy { SlidingExpiration = TimeSpan.MaxValue }, regionName);
                        }
                    }

                }
                catch {
                }
            }

            return bitmap;
        }
I hope this helps somebody.

Martin.