From fefc4e3294a2101b88d0678275f933cd350ce8ce Mon Sep 17 00:00:00 2001 From: ClemensF Date: Sat, 13 Jul 2019 19:53:03 +0200 Subject: [PATCH] Version 4.12.2 Improved ImageLoader/TileImageLoader --- MapControl/Shared/ImageLoader.cs | 16 ++---- MapControl/Shared/TileImageLoader.cs | 2 +- MapControl/UWP/ImageLoader.UWP.cs | 19 +++++--- MapControl/UWP/TileImageLoader.UWP.cs | 26 +++------- MapControl/WPF/ImageFileCache.WPF.cs | 2 + MapControl/WPF/TileImageLoader.WPF.cs | 50 ++++++++----------- MapImages/Shared/GroundOverlayPanel.cs | 49 +++++++++++++++++-- MapImages/UWP/GroundOverlayPanel.UWP.cs | 44 +---------------- MapImages/WPF/GroundOverlayPanel.WPF.cs | 65 +++---------------------- 9 files changed, 102 insertions(+), 171 deletions(-) diff --git a/MapControl/Shared/ImageLoader.cs b/MapControl/Shared/ImageLoader.cs index c2085cbb..cf8c6351 100644 --- a/MapControl/Shared/ImageLoader.cs +++ b/MapControl/Shared/ImageLoader.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.IO; using System.Net.Http; using System.Threading.Tasks; #if WINDOWS_UWP @@ -22,7 +21,7 @@ namespace MapControl public static partial class ImageLoader { /// - /// The System.Net.Http.HttpClient instance used when image data is downloaded via a http or https Uri. + /// The System.Net.Http.HttpClient instance used to download images via a http or https Uri. /// public static HttpClient HttpClient { get; set; } = new HttpClient(); @@ -41,12 +40,9 @@ namespace MapControl { var response = await GetHttpResponseAsync(uri); - if (response?.Stream != null) + if (response != null && response.Buffer != null) { - using (var stream = response.Stream) - { - image = await LoadImageAsync(stream); - } + image = await LoadImageAsync(response.Buffer); } } else @@ -90,7 +86,7 @@ namespace MapControl internal class HttpResponse { - public MemoryStream Stream { get; private set; } + public byte[] Buffer { get; private set; } public TimeSpan? MaxAge { get; private set; } internal static async Task Create(HttpResponseMessage message, bool continueOnCapturedContext) @@ -100,9 +96,7 @@ namespace MapControl if (!message.Headers.TryGetValues("X-VE-Tile-Info", out tileInfo) || !tileInfo.Contains("no-tile")) { - response.Stream = new MemoryStream(); - await message.Content.CopyToAsync(response.Stream).ConfigureAwait(continueOnCapturedContext); - response.Stream.Seek(0, SeekOrigin.Begin); + response.Buffer = await message.Content.ReadAsByteArrayAsync().ConfigureAwait(continueOnCapturedContext); response.MaxAge = message.Headers.CacheControl?.MaxAge; } diff --git a/MapControl/Shared/TileImageLoader.cs b/MapControl/Shared/TileImageLoader.cs index b9ca462c..86ad596f 100644 --- a/MapControl/Shared/TileImageLoader.cs +++ b/MapControl/Shared/TileImageLoader.cs @@ -107,7 +107,7 @@ namespace MapControl try { - Debug.WriteLine("TileImageLoader: loading {0}/{1}/{2} in thread {3}", tile.ZoomLevel, tile.XIndex, tile.Y, Environment.CurrentManagedThreadId); + //Debug.WriteLine("TileImageLoader: loading {0}/{1}/{2} in thread {3}", tile.ZoomLevel, tile.XIndex, tile.Y, Environment.CurrentManagedThreadId); await loadTileImage(tile).ConfigureAwait(false); } diff --git a/MapControl/UWP/ImageLoader.UWP.cs b/MapControl/UWP/ImageLoader.UWP.cs index 7f45dac7..aff97747 100644 --- a/MapControl/UWP/ImageLoader.UWP.cs +++ b/MapControl/UWP/ImageLoader.UWP.cs @@ -15,11 +15,6 @@ namespace MapControl { public static partial class ImageLoader { - public static Task LoadImageAsync(Stream stream) - { - return LoadImageAsync(stream.AsRandomAccessStream()); - } - public static async Task LoadImageAsync(IRandomAccessStream stream) { var image = new BitmapImage(); @@ -29,11 +24,11 @@ namespace MapControl return image; } - public static async Task LoadImageAsync(byte[] buffer) + public static async Task LoadImageAsync(IBuffer buffer) { using (var stream = new InMemoryRandomAccessStream()) { - await stream.WriteAsync(buffer.AsBuffer()); + await stream.WriteAsync(buffer); stream.Seek(0); return await LoadImageAsync(stream); @@ -56,5 +51,15 @@ namespace MapControl return image; } + + public static Task LoadImageAsync(Stream stream) + { + return LoadImageAsync(stream.AsRandomAccessStream()); + } + + public static Task LoadImageAsync(byte[] buffer) + { + return LoadImageAsync(buffer.AsBuffer()); + } } } diff --git a/MapControl/UWP/TileImageLoader.UWP.cs b/MapControl/UWP/TileImageLoader.UWP.cs index 1c8c3cdf..890b8f8c 100644 --- a/MapControl/UWP/TileImageLoader.UWP.cs +++ b/MapControl/UWP/TileImageLoader.UWP.cs @@ -6,7 +6,6 @@ using System; using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; using Windows.Storage; -using Windows.Storage.Streams; using Windows.UI.Core; using Windows.UI.Xaml.Media; @@ -32,37 +31,26 @@ namespace MapControl private static async Task LoadCachedTileImageAsync(Tile tile, Uri uri, string cacheKey) { var cacheItem = await Cache.GetAsync(cacheKey).ConfigureAwait(false); - var cacheBuffer = cacheItem?.Buffer; + var buffer = cacheItem?.Buffer; - if (cacheBuffer == null || cacheItem.Expiration < DateTime.UtcNow) + if (buffer == null || cacheItem.Expiration < DateTime.UtcNow) { var response = await ImageLoader.GetHttpResponseAsync(uri, false).ConfigureAwait(false); if (response != null) // download succeeded { - cacheBuffer = null; // discard cached image + buffer = response.Buffer.AsBuffer(); - if (response.Stream != null) // tile image available + if (buffer != null) // tile image available { - using (var stream = response.Stream) - { - await SetTileImageAsync(tile, () => ImageLoader.LoadImageAsync(stream)).ConfigureAwait(false); - - await Cache.SetAsync(cacheKey, stream.ToArray().AsBuffer(), GetExpiration(response.MaxAge)).ConfigureAwait(false); - } + await Cache.SetAsync(cacheKey, buffer, GetExpiration(response.MaxAge)).ConfigureAwait(false); } } } - if (cacheBuffer != null) // cached image not expired or download failed + if (buffer != null) { - using (var stream = new InMemoryRandomAccessStream()) - { - await stream.WriteAsync(cacheBuffer); - stream.Seek(0); - - await SetTileImageAsync(tile, () => ImageLoader.LoadImageAsync(stream)).ConfigureAwait(false); - } + await SetTileImageAsync(tile, () => ImageLoader.LoadImageAsync(buffer)).ConfigureAwait(false); } } diff --git a/MapControl/WPF/ImageFileCache.WPF.cs b/MapControl/WPF/ImageFileCache.WPF.cs index 61f655b3..9263d114 100644 --- a/MapControl/WPF/ImageFileCache.WPF.cs +++ b/MapControl/WPF/ImageFileCache.WPF.cs @@ -107,6 +107,7 @@ namespace MapControl.Caching try { //Debug.WriteLine("ImageFileCache: Reading " + path); + buffer = File.ReadAllBytes(path); memoryCache.Set(key, buffer, new CacheItemPolicy()); } @@ -160,6 +161,7 @@ namespace MapControl.Caching try { //Debug.WriteLine("ImageFileCache: Writing {0}, Expires {1}", path, policy.AbsoluteExpiration.DateTime.ToLocalTime()); + Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllBytes(path, buffer); diff --git a/MapControl/WPF/TileImageLoader.WPF.cs b/MapControl/WPF/TileImageLoader.WPF.cs index 9ff273d1..d1e6e64d 100644 --- a/MapControl/WPF/TileImageLoader.WPF.cs +++ b/MapControl/WPF/TileImageLoader.WPF.cs @@ -30,40 +30,27 @@ namespace MapControl private static async Task LoadCachedTileImageAsync(Tile tile, Uri uri, string cacheKey) { - ImageSource image = null; DateTime expiration; - byte[] cacheBuffer; + var buffer = GetCachedImage(cacheKey, out expiration); - GetCachedImage(cacheKey, out cacheBuffer, out expiration); - - if (cacheBuffer == null || expiration < DateTime.UtcNow) + if (buffer == null || expiration < DateTime.UtcNow) { var response = await ImageLoader.GetHttpResponseAsync(uri, false).ConfigureAwait(false); if (response != null) // download succeeded { - cacheBuffer = null; // discard cached image + buffer = response.Buffer; - if (response.Stream != null) // tile image available + if (buffer != null) // tile image available { - using (var stream = response.Stream) - { - image = ImageLoader.LoadImage(stream); - - SetCachedImage(cacheKey, stream, GetExpiration(response.MaxAge)); - } + await SetCachedImage(cacheKey, buffer, GetExpiration(response.MaxAge)).ConfigureAwait(false); } } } - if (cacheBuffer != null) // cached image not expired or download failed + if (buffer != null) { - image = ImageLoader.LoadImage(cacheBuffer); - } - - if (image != null) - { - SetTileImage(tile, image); + SetTileImageAsync(tile, await ImageLoader.LoadImageAsync(buffer).ConfigureAwait(false)); } } @@ -73,18 +60,18 @@ namespace MapControl if (image != null) { - SetTileImage(tile, image); + SetTileImageAsync(tile, image); } } - private static void SetTileImage(Tile tile, ImageSource image) + private static void SetTileImageAsync(Tile tile, ImageSource image) { tile.Image.Dispatcher.InvokeAsync(() => tile.SetImage(image)); } - private static void GetCachedImage(string cacheKey, out byte[] buffer, out DateTime expiration) + private static byte[] GetCachedImage(string cacheKey, out DateTime expiration) { - buffer = Cache.Get(cacheKey) as byte[]; + var buffer = Cache.Get(cacheKey) as byte[]; if (buffer != null && buffer.Length >= 16 && Encoding.ASCII.GetString(buffer, buffer.Length - 16, 8) == "EXPIRES:") @@ -95,15 +82,20 @@ namespace MapControl { expiration = DateTime.MinValue; } + + return buffer; } - private static void SetCachedImage(string cacheKey, MemoryStream stream, DateTime expiration) + private static async Task SetCachedImage(string cacheKey, byte[] buffer, DateTime expiration) { - stream.Seek(0, SeekOrigin.End); - stream.Write(Encoding.ASCII.GetBytes("EXPIRES:"), 0, 8); - stream.Write(BitConverter.GetBytes(expiration.Ticks), 0, 8); + using (var stream = new MemoryStream(buffer.Length + 16)) + { + await stream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false); + await stream.WriteAsync(Encoding.ASCII.GetBytes("EXPIRES:"), 0, 8).ConfigureAwait(false); + await stream.WriteAsync(BitConverter.GetBytes(expiration.Ticks), 0, 8).ConfigureAwait(false); - Cache.Set(cacheKey, stream.ToArray(), new CacheItemPolicy { AbsoluteExpiration = expiration }); + Cache.Set(cacheKey, stream.ToArray(), new CacheItemPolicy { AbsoluteExpiration = expiration }); + } } } } diff --git a/MapImages/Shared/GroundOverlayPanel.cs b/MapImages/Shared/GroundOverlayPanel.cs index f447b382..5ac7e8c9 100644 --- a/MapImages/Shared/GroundOverlayPanel.cs +++ b/MapImages/Shared/GroundOverlayPanel.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; +using System.IO.Compression; using System.Linq; using System.Threading.Tasks; using System.Xml; @@ -119,7 +120,49 @@ namespace MapControl.Images } } - private IEnumerable ReadGroundOverlays(XmlDocument kmlDocument) + private static async Task> ReadGroundOverlaysFromArchiveAsync(string archiveFile) + { + using (var archive = ZipFile.OpenRead(archiveFile)) + { + var docEntry = archive.GetEntry("doc.kml") + ?? archive.Entries.FirstOrDefault(e => e.Name.EndsWith(".kml")); + + if (docEntry == null) + { + throw new ArgumentException("No KML entry found in " + archiveFile); + } + + var kmlDocument = new XmlDocument(); + + using (var docStream = docEntry.Open()) + { + kmlDocument.Load(docStream); + } + + var imageOverlays = await Task.Run(() => ReadGroundOverlays(kmlDocument).ToList()); + + foreach (var imageOverlay in imageOverlays) + { + var imageEntry = archive.GetEntry(imageOverlay.ImagePath); + + if (imageEntry != null) + { + using (var zipStream = imageEntry.Open()) + using (var memoryStream = new MemoryStream()) + { + await zipStream.CopyToAsync(memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + + imageOverlay.ImageSource = await ImageLoader.LoadImageAsync(memoryStream); + } + } + } + + return imageOverlays; + } + } + + private static IEnumerable ReadGroundOverlays(XmlDocument kmlDocument) { foreach (XmlElement groundOverlayElement in kmlDocument.GetElementsByTagName("GroundOverlay")) { @@ -150,7 +193,7 @@ namespace MapControl.Images } } - private string ReadImagePath(XmlElement element) + private static string ReadImagePath(XmlElement element) { string href = null; @@ -167,7 +210,7 @@ namespace MapControl.Images return href; } - private LatLonBox ReadLatLonBox(XmlElement element) + private static LatLonBox ReadLatLonBox(XmlElement element) { double north = double.NaN; double south = double.NaN; diff --git a/MapImages/UWP/GroundOverlayPanel.UWP.cs b/MapImages/UWP/GroundOverlayPanel.UWP.cs index 690e1051..7145265d 100644 --- a/MapImages/UWP/GroundOverlayPanel.UWP.cs +++ b/MapImages/UWP/GroundOverlayPanel.UWP.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.IO.Compression; using System.Linq; using System.Threading.Tasks; using System.Xml; @@ -20,7 +19,7 @@ namespace MapControl.Images UseLayoutRounding = false; } - private async Task> ReadGroundOverlaysFromFileAsync(string docFile) + private static async Task> ReadGroundOverlaysFromFileAsync(string docFile) { docFile = Path.GetFullPath(docFile); @@ -42,46 +41,5 @@ namespace MapControl.Images return imageOverlays; } - - private async Task> ReadGroundOverlaysFromArchiveAsync(string archiveFile) - { - using (var archive = ZipFile.OpenRead(archiveFile)) - { - var docEntry = archive.GetEntry("doc.kml") - ?? archive.Entries.FirstOrDefault(e => e.Name.EndsWith(".kml")); - - if (docEntry == null) - { - throw new ArgumentException("No KML entry found in " + archiveFile); - } - - var kmlDocument = new XmlDocument(); - - using (var docStream = docEntry.Open()) - { - kmlDocument.Load(docStream); - } - - var imageOverlays = await Task.Run(() => ReadGroundOverlays(kmlDocument).ToList()); - - foreach (var imageOverlay in imageOverlays) - { - var imageEntry = archive.GetEntry(imageOverlay.ImagePath); - - if (imageEntry != null) - { - using (var zipStream = imageEntry.Open()) - using (var memoryStream = new MemoryStream()) - { - await zipStream.CopyToAsync(memoryStream); - memoryStream.Seek(0, SeekOrigin.Begin); - imageOverlay.ImageSource = await ImageLoader.LoadImageAsync(memoryStream.AsRandomAccessStream()); - } - } - } - - return imageOverlays; - } - } } } diff --git a/MapImages/WPF/GroundOverlayPanel.WPF.cs b/MapImages/WPF/GroundOverlayPanel.WPF.cs index 0b71bdec..62d6791e 100644 --- a/MapImages/WPF/GroundOverlayPanel.WPF.cs +++ b/MapImages/WPF/GroundOverlayPanel.WPF.cs @@ -2,10 +2,8 @@ // © 2019 Clemens Fischer // Licensed under the Microsoft Public License (Ms-PL) -using System; using System.Collections.Generic; using System.IO; -using System.IO.Compression; using System.Linq; using System.Threading.Tasks; using System.Xml; @@ -14,74 +12,25 @@ namespace MapControl.Images { public partial class GroundOverlayPanel { - private Task> ReadGroundOverlaysFromFileAsync(string docFile) + private static Task> ReadGroundOverlaysFromFileAsync(string docFile) { - return Task.Run(() => ReadGroundOverlaysFromFile(docFile)); - } - - private Task> ReadGroundOverlaysFromArchiveAsync(string archiveFile) - { - return Task.Run(() => ReadGroundOverlaysFromArchive(archiveFile)); - } - - private List ReadGroundOverlaysFromFile(string docFile) - { - docFile = Path.GetFullPath(docFile); - - var kmlDocument = new XmlDocument(); - kmlDocument.Load(docFile); - - var imageOverlays = ReadGroundOverlays(kmlDocument).ToList(); - var docDir = Path.GetDirectoryName(docFile); - - foreach (var imageOverlay in imageOverlays) + return Task.Run(() => { - imageOverlay.ImageSource = ImageLoader.LoadImage(Path.Combine(docDir, imageOverlay.ImagePath)); - } - - return imageOverlays; - - } - - private List ReadGroundOverlaysFromArchive(string archiveFile) - { - using (var archive = ZipFile.OpenRead(archiveFile)) - { - var docEntry = archive.GetEntry("doc.kml") - ?? archive.Entries.FirstOrDefault(e => e.Name.EndsWith(".kml")); - - if (docEntry == null) - { - throw new ArgumentException("No KML entry found in " + archiveFile); - } + docFile = Path.GetFullPath(docFile); var kmlDocument = new XmlDocument(); - - using (var docStream = docEntry.Open()) - { - kmlDocument.Load(docStream); - } + kmlDocument.Load(docFile); var imageOverlays = ReadGroundOverlays(kmlDocument).ToList(); + var docDir = Path.GetDirectoryName(docFile); foreach (var imageOverlay in imageOverlays) { - var imageEntry = archive.GetEntry(imageOverlay.ImagePath); - - if (imageEntry != null) - { - using (var zipStream = imageEntry.Open()) - using (var memoryStream = new MemoryStream()) - { - zipStream.CopyTo(memoryStream); - memoryStream.Seek(0, SeekOrigin.Begin); - imageOverlay.ImageSource = ImageLoader.LoadImage(memoryStream); - } - } + imageOverlay.ImageSource = ImageLoader.LoadImage(Path.Combine(docDir, imageOverlay.ImagePath)); } return imageOverlays; - } + }); } } }