From 91ff46c506155ae0043352bc5c78cfedf7202674 Mon Sep 17 00:00:00 2001 From: ClemensF Date: Sun, 19 Oct 2014 21:50:23 +0200 Subject: [PATCH] Version 2.3.0: - Added BingMapsTileLayer - Added TileLayer.DescriptionInlines property - Added global Settings class - Added Phone Silverlight 8.1 build target - Use expiration time of downloaded images for caching --- .../FileDbCache/Properties/AssemblyInfo.cs | 4 +- Caching/ImageFileCache/ImageFileCache.cs | 18 ++- .../ImageFileCache/Properties/AssemblyInfo.cs | 4 +- MapControl.sln | 20 ++- MapControl/BingMapsTileLayer.cs | 153 ++++++++++++++++++ MapControl/BingMapsTileSource.cs | 39 +++++ MapControl/Extensions.Silverlight.WinRT.cs | 4 - MapControl/Extensions.WinRT.cs | 7 + MapControl/GlyphRunText.cs | 2 +- MapControl/HyperlinkText.cs | 60 +++++++ MapControl/Int32Rect.cs | 2 +- MapControl/MapBase.cs | 17 +- MapControl/MapControl.PhoneSilverlight.csproj | 150 +++++++++++++++++ .../MapControl.PhoneSilverlight.csproj.user | 15 ++ MapControl/MapControl.Silverlight.csproj | 6 + MapControl/MapControl.WPF.csproj | 6 + MapControl/MapImage.cs | 8 +- MapControl/MapImageLayer.Silverlight.WinRT.cs | 11 +- MapControl/MapImageLayer.WPF.cs | 13 +- MapControl/MapImageLayer.cs | 151 +++++++++-------- MapControl/MapPanel.cs | 4 +- MapControl/MapRectangle.Silverlight.WinRT.cs | 24 +++ MapControl/MapRectangle.WPF.cs | 40 +++++ MapControl/MapRectangle.cs | 66 ++++---- MapControl/PanelBase.cs | 10 +- MapControl/Properties/AssemblyInfo.cs | 9 +- MapControl/Settings.cs | 32 ++++ MapControl/Themes/Generic.xaml | 1 + MapControl/Tile.Silverlight.WinRT.cs | 6 +- MapControl/Tile.WPF.cs | 6 +- MapControl/Tile.cs | 2 - MapControl/TileContainer.Silverlight.WinRT.cs | 4 +- MapControl/TileContainer.WPF.cs | 3 +- MapControl/TileContainer.cs | 27 ++-- MapControl/TileImageLoader.Silverlight.cs | 6 +- MapControl/TileImageLoader.WPF.cs | 86 +++++----- MapControl/TileImageLoader.WinRT.cs | 6 +- MapControl/TileLayer.cs | 109 +++++++------ MapControl/TileSource.cs | 16 +- MapControl/TileSourceConverter.cs | 2 +- MapControl/WinRT/MapControl.WinRT.csproj | 28 +++- MapControl/WinRT/Properties/AssemblyInfo.cs | 4 +- MapControl/WinRT/Themes/Generic.xaml | 1 + SampleApps/PhoneApplication/App.xaml.cs | 10 -- SampleApps/PhoneApplication/MainPage.xaml | 81 +++++++--- SampleApps/PhoneApplication/MainPage.xaml.cs | 25 ++- .../Properties/AssemblyInfo.cs | 4 +- SampleApps/PhoneApplication/ViewModel.cs | 5 +- .../Properties/AssemblyInfo.cs | 4 +- .../SilverlightApplication/MainPage.xaml | 85 +++++++--- .../SilverlightApplication/MainPage.xaml.cs | 27 +++- .../Properties/AssemblyInfo.cs | 4 +- SampleApps/StoreApplication/MainPage.xaml | 88 ++++++---- SampleApps/StoreApplication/MainPage.xaml.cs | 32 ++-- .../Properties/AssemblyInfo.cs | 4 +- SampleApps/WpfApplication/MainWindow.xaml | 130 +++++++++------ SampleApps/WpfApplication/MainWindow.xaml.cs | 31 +++- .../WpfApplication/Properties/AssemblyInfo.cs | 4 +- 58 files changed, 1225 insertions(+), 491 deletions(-) create mode 100644 MapControl/BingMapsTileLayer.cs create mode 100644 MapControl/BingMapsTileSource.cs create mode 100644 MapControl/HyperlinkText.cs create mode 100644 MapControl/MapControl.PhoneSilverlight.csproj create mode 100644 MapControl/MapControl.PhoneSilverlight.csproj.user create mode 100644 MapControl/MapRectangle.Silverlight.WinRT.cs create mode 100644 MapControl/MapRectangle.WPF.cs create mode 100644 MapControl/Settings.cs diff --git a/Caching/FileDbCache/Properties/AssemblyInfo.cs b/Caching/FileDbCache/Properties/AssemblyInfo.cs index 65c400ee..43e8ef2f 100644 --- a/Caching/FileDbCache/Properties/AssemblyInfo.cs +++ b/Caching/FileDbCache/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.2.0")] -[assembly: AssemblyFileVersion("2.2.0")] +[assembly: AssemblyVersion("2.3.0")] +[assembly: AssemblyFileVersion("2.3.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/Caching/ImageFileCache/ImageFileCache.cs b/Caching/ImageFileCache/ImageFileCache.cs index 452e4375..d3f0fa5c 100644 --- a/Caching/ImageFileCache/ImageFileCache.cs +++ b/Caching/ImageFileCache/ImageFileCache.cs @@ -128,12 +128,18 @@ namespace Caching if (path != null) { - var creationTime = File.GetLastWriteTimeUtc(path).ToBinary(); + var expirationTime = File.GetLastAccessTimeUtc(path); + var creationTime = File.GetLastWriteTimeUtc(path); + + if (expirationTime < creationTime) + { + expirationTime = creationTime; + } using (var fileStream = new FileStream(path, FileMode.Open)) using (var memoryStream = new MemoryStream((int)(fileStream.Length + 8))) { - memoryStream.Write(BitConverter.GetBytes(creationTime), 0, 8); + memoryStream.Write(BitConverter.GetBytes(expirationTime.ToBinary()), 0, 8); fileStream.CopyTo(memoryStream); value = memoryStream.GetBuffer(); } @@ -150,6 +156,7 @@ namespace Caching public override CacheItem GetCacheItem(string key, string regionName = null) { var value = Get(key, regionName); + return value != null ? new CacheItem(key, value) : null; } @@ -197,6 +204,9 @@ namespace Caching fileStream.Write(buffer, 8, buffer.Length - 8); } + var expirationTime = DateTime.FromBinary(BitConverter.ToInt64(buffer, 0)); + File.SetLastAccessTimeUtc(path, expirationTime); + var fileSecurity = File.GetAccessControl(path); fileSecurity.AddAccessRule(fullControlRule); File.SetAccessControl(path, fileSecurity); @@ -220,14 +230,18 @@ namespace Caching public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null) { var oldValue = Get(key, regionName); + Set(key, value, policy); + return oldValue; } public override CacheItem AddOrGetExisting(CacheItem item, CacheItemPolicy policy) { var oldItem = GetCacheItem(item.Key, item.RegionName); + Set(item, policy); + return oldItem; } diff --git a/Caching/ImageFileCache/Properties/AssemblyInfo.cs b/Caching/ImageFileCache/Properties/AssemblyInfo.cs index 8fb62034..9dfb11fa 100644 --- a/Caching/ImageFileCache/Properties/AssemblyInfo.cs +++ b/Caching/ImageFileCache/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.2.0")] -[assembly: AssemblyFileVersion("2.2.0")] +[assembly: AssemblyVersion("2.3.0")] +[assembly: AssemblyFileVersion("2.3.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapControl.sln b/MapControl.sln index 28867e55..8f9d1105 100644 --- a/MapControl.sln +++ b/MapControl.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.30501.0 +VisualStudioVersion = 12.0.30723.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileDbCache", "Caching\FileDbCache\FileDbCache.csproj", "{EF44F661-B98A-4676-927F-85D138F82300}" EndProject @@ -13,8 +13,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SilverlightApplication.Web" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.WPF", "MapControl\MapControl.WPF.csproj", "{226F3575-B683-446D-A2F0-181291DC8787}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.Silverlight", "MapControl\MapControl.Silverlight.csproj", "{EB133B78-DEFF-416A-8F0C-89E54D766576}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoreApplication", "SampleApps\StoreApplication\StoreApplication.csproj", "{747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.WinRT", "MapControl\WinRT\MapControl.WinRT.csproj", "{63CEFDF7-5170-43B6-86F8-5C4A383A1615}" @@ -23,6 +21,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfApplication", "SampleApp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhoneApplication", "SampleApps\PhoneApplication\PhoneApplication.csproj", "{8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.Silverlight", "MapControl\MapControl.Silverlight.csproj", "{EB133B78-DEFF-416A-8F0C-89E54D766576}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.PhoneSilverlight", "MapControl\MapControl.PhoneSilverlight.csproj", "{3499D618-2846-4FCE-A418-7D211FDBDCB3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -49,10 +51,6 @@ Global {226F3575-B683-446D-A2F0-181291DC8787}.Debug|Any CPU.Build.0 = Debug|Any CPU {226F3575-B683-446D-A2F0-181291DC8787}.Release|Any CPU.ActiveCfg = Release|Any CPU {226F3575-B683-446D-A2F0-181291DC8787}.Release|Any CPU.Build.0 = Release|Any CPU - {EB133B78-DEFF-416A-8F0C-89E54D766576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EB133B78-DEFF-416A-8F0C-89E54D766576}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EB133B78-DEFF-416A-8F0C-89E54D766576}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EB133B78-DEFF-416A-8F0C-89E54D766576}.Release|Any CPU.Build.0 = Release|Any CPU {747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Debug|Any CPU.Build.0 = Debug|Any CPU {747A3F84-E11F-4EC8-9463-98BBB1E0D0A4}.Debug|Any CPU.Deploy.0 = Debug|Any CPU @@ -73,6 +71,14 @@ Global {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.Build.0 = Release|Any CPU {8D0A57DF-FABF-4AEE-8768-9C18B2B43CA9}.Release|Any CPU.Deploy.0 = Release|Any CPU + {EB133B78-DEFF-416A-8F0C-89E54D766576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB133B78-DEFF-416A-8F0C-89E54D766576}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB133B78-DEFF-416A-8F0C-89E54D766576}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB133B78-DEFF-416A-8F0C-89E54D766576}.Release|Any CPU.Build.0 = Release|Any CPU + {3499D618-2846-4FCE-A418-7D211FDBDCB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3499D618-2846-4FCE-A418-7D211FDBDCB3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3499D618-2846-4FCE-A418-7D211FDBDCB3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3499D618-2846-4FCE-A418-7D211FDBDCB3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MapControl/BingMapsTileLayer.cs b/MapControl/BingMapsTileLayer.cs new file mode 100644 index 00000000..6c5b15b0 --- /dev/null +++ b/MapControl/BingMapsTileLayer.cs @@ -0,0 +1,153 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © 2014 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Net; +using System.Xml; +#if WINDOWS_RUNTIME +using Windows.UI.Xaml; +#else +using System.Windows; +#endif + +namespace MapControl +{ + public class BingMapsTileLayer : TileLayer + { + public enum MapMode + { + Road, Aerial, AerialWithLabels + } + + public BingMapsTileLayer() + { + MinZoomLevel = 1; + MaxZoomLevel = 21; + Loaded += OnLoaded; + } + + public static string ApiKey { get; set; } + + public MapMode Mode { get; set; } + public string Culture { get; set; } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + Loaded -= OnLoaded; + + if (string.IsNullOrWhiteSpace(ApiKey)) + { + throw new InvalidOperationException("A Bing Maps API Key must be assigned to the ApiKey property."); + } + + var uri = string.Format("http://dev.virtualearth.net/REST/V1/Imagery/Metadata/{0}?output=xml&key={1}", Mode, ApiKey); + var request = HttpWebRequest.CreateHttp(uri); + + request.BeginGetResponse(HandleImageryMetadataResponse, request); + } + + private void HandleImageryMetadataResponse(IAsyncResult asyncResult) + { + try + { + var request = (HttpWebRequest)asyncResult.AsyncState; + + using (var response = request.EndGetResponse(asyncResult)) + using (var responseStream = response.GetResponseStream()) + using (var xmlReader = XmlReader.Create(responseStream)) + { + ReadImageryMetadataResponse(xmlReader); + } + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + } + } + + private void ReadImageryMetadataResponse(XmlReader xmlReader) + { + string imageUrl = null; + string[] imageUrlSubdomains = null; + int? zoomMin = null; + int? zoomMax = null; + + do + { + if (xmlReader.NodeType == XmlNodeType.Element) + { + switch (xmlReader.Name) + { + case "ImageUrl": + imageUrl = xmlReader.ReadElementContentAsString(); + break; + case "ImageUrlSubdomains": + imageUrlSubdomains = ReadStrings(xmlReader.ReadSubtree()); + break; + case "ZoomMin": + zoomMin = xmlReader.ReadElementContentAsInt(); + break; + case "ZoomMax": + zoomMax = xmlReader.ReadElementContentAsInt(); + break; + default: + xmlReader.Read(); + break; + } + } + else + { + xmlReader.Read(); + } + } + while (xmlReader.NodeType != XmlNodeType.None); + + if (imageUrl != null && imageUrlSubdomains != null && imageUrlSubdomains.Length > 0) + { + Dispatcher.BeginInvoke(new Action(() => + { + if (string.IsNullOrWhiteSpace(Culture)) + { + Culture = CultureInfo.CurrentUICulture.Name; + } + + TileSource = new BingMapsTileSource(imageUrl.Replace("{culture}", Culture), imageUrlSubdomains); + + if (zoomMin.HasValue && zoomMin.Value > MinZoomLevel) + { + MinZoomLevel = zoomMin.Value; + } + + if (zoomMax.HasValue && zoomMax.Value < MaxZoomLevel) + { + MaxZoomLevel = zoomMax.Value; + } + })); + } + } + + private static string[] ReadStrings(XmlReader xmlReader) + { + var strings = new List(); + + do + { + if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.Name == "string") + { + strings.Add(xmlReader.ReadElementContentAsString()); + } + else + { + xmlReader.Read(); + } + } + while (xmlReader.NodeType != XmlNodeType.None); + + return strings.ToArray(); + } + } +} diff --git a/MapControl/BingMapsTileSource.cs b/MapControl/BingMapsTileSource.cs new file mode 100644 index 00000000..046705ca --- /dev/null +++ b/MapControl/BingMapsTileSource.cs @@ -0,0 +1,39 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © 2014 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; + +namespace MapControl +{ + internal class BingMapsTileSource : TileSource + { + private string[] subdomains; + + public BingMapsTileSource(string uriFormat, string[] subdomains) + : base(uriFormat) + { + this.subdomains = subdomains; + } + + public override Uri GetUri(int x, int y, int zoomLevel) + { + if (zoomLevel < 1) + { + return null; + } + + var subdomain = subdomains[(x + y) % subdomains.Length]; + var quadkey = new char[zoomLevel]; + + for (var z = zoomLevel - 1; z >= 0; z--, x /= 2, y /= 2) + { + quadkey[z] = (char)('0' + 2 * (y % 2) + (x % 2)); + } + + return new Uri(UriFormat. + Replace("{subdomain}", subdomain). + Replace("{quadkey}", new string(quadkey))); + } + } +} diff --git a/MapControl/Extensions.Silverlight.WinRT.cs b/MapControl/Extensions.Silverlight.WinRT.cs index 5ba06a01..f121b376 100644 --- a/MapControl/Extensions.Silverlight.WinRT.cs +++ b/MapControl/Extensions.Silverlight.WinRT.cs @@ -13,10 +13,6 @@ namespace MapControl { internal static partial class Extensions { - public static void Freeze(this object freezable) - { - } - public static Matrix Translate(this Matrix matrix, double offsetX, double offsetY) { matrix.OffsetX += offsetX; diff --git a/MapControl/Extensions.WinRT.cs b/MapControl/Extensions.WinRT.cs index db7cefbf..a3e619f9 100644 --- a/MapControl/Extensions.WinRT.cs +++ b/MapControl/Extensions.WinRT.cs @@ -2,7 +2,9 @@ // Copyright © 2014 Clemens Fischer // Licensed under the Microsoft Public License (Ms-PL) +using System; using Windows.Foundation; +using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Animation; @@ -11,6 +13,11 @@ namespace MapControl { internal static partial class Extensions { + public static void BeginInvoke(this CoreDispatcher dispatcher, Action action) + { + var asyncAction = dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(action)); + } + public static Point Transform(this GeneralTransform transform, Point point) { return transform.TransformPoint(point); diff --git a/MapControl/GlyphRunText.cs b/MapControl/GlyphRunText.cs index 1c55a5b9..dcf933c1 100644 --- a/MapControl/GlyphRunText.cs +++ b/MapControl/GlyphRunText.cs @@ -19,7 +19,7 @@ namespace MapControl if (!typeface.TryGetGlyphTypeface(out glyphTypeface)) { - throw new ArgumentException(string.Format("{0}: no GlyphTypeface found", typeface.FontFamily)); + throw new ArgumentException(string.Format("{0}: No GlyphTypeface found", typeface.FontFamily)); } var glyphIndices = new ushort[text.Length]; diff --git a/MapControl/HyperlinkText.cs b/MapControl/HyperlinkText.cs new file mode 100644 index 00000000..767cf72a --- /dev/null +++ b/MapControl/HyperlinkText.cs @@ -0,0 +1,60 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © 2014 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +#if WINDOWS_RUNTIME +using Windows.UI.Xaml.Documents; +#else +using System.Windows.Documents; +#endif + +namespace MapControl +{ + public static class HyperlinkText + { + private static Regex regex = new Regex(@"\[([^\]]+)\]\(([^\)]+)\)"); + + /// + /// Converts text containing hyperlinks in markdown syntax [text](url) + /// to a collection of Run and Hyperlink inlines. + /// + public static ICollection ToInlines(this string text) + { + var inlines = new List(); + + while (!string.IsNullOrEmpty(text)) + { + var match = regex.Match(text); + Uri uri; + + if (match.Success && + match.Groups.Count == 3 && + Uri.TryCreate(match.Groups[2].Value, UriKind.Absolute, out uri)) + { + inlines.Add(new Run { Text = text.Substring(0, match.Index) }); + text = text.Substring(match.Index + match.Length); + + var link = new Hyperlink { NavigateUri = uri }; + link.Inlines.Add(new Run { Text = match.Groups[1].Value }); +#if SILVERLIGHT + link.TargetName = "_blank"; +#elif !WINDOWS_RUNTIME + link.ToolTip = uri.ToString(); + link.RequestNavigate += (s, e) => System.Diagnostics.Process.Start(e.Uri.ToString()); +#endif + inlines.Add(link); + } + else + { + inlines.Add(new Run { Text = text }); + text = null; + } + } + + return inlines; + } + } +} diff --git a/MapControl/Int32Rect.cs b/MapControl/Int32Rect.cs index 6dea4511..2cbd9e8b 100644 --- a/MapControl/Int32Rect.cs +++ b/MapControl/Int32Rect.cs @@ -4,7 +4,7 @@ namespace MapControl { - public struct Int32Rect + internal struct Int32Rect { public Int32Rect(int x, int y, int width, int height) : this() diff --git a/MapControl/MapBase.cs b/MapControl/MapBase.cs index c86820ef..18a4b744 100644 --- a/MapControl/MapBase.cs +++ b/MapControl/MapBase.cs @@ -28,9 +28,6 @@ namespace MapControl { private const double MaximumZoomLevel = 22d; - public static TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.5); - public static EasingFunctionBase AnimationEasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }; - public static readonly DependencyProperty TileLayersProperty = DependencyProperty.Register( "TileLayers", typeof(TileLayerCollection), typeof(MapBase), new PropertyMetadata(null, (o, e) => ((MapBase)o).TileLayersPropertyChanged((TileLayerCollection)e.OldValue, (TileLayerCollection)e.NewValue))); @@ -74,7 +71,7 @@ namespace MapControl SetParentMap(); TileLayers = new TileLayerCollection(); - InternalChildren.Add(tileContainer); + Children.Add(tileContainer); Initialize(); Loaded += OnLoaded; @@ -578,8 +575,8 @@ namespace MapControl { From = MapTransform.Transform(Center), To = MapTransform.Transform(targetCenter, Center.Longitude), - Duration = AnimationDuration, - EasingFunction = AnimationEasingFunction, + Duration = Settings.MapAnimationDuration, + EasingFunction = Settings.MapAnimationEasingFunction, FillBehavior = FillBehavior.HoldEnd }; @@ -683,8 +680,8 @@ namespace MapControl zoomLevelAnimation = new DoubleAnimation { To = targetZoomLevel, - Duration = AnimationDuration, - EasingFunction = AnimationEasingFunction, + Duration = Settings.MapAnimationDuration, + EasingFunction = Settings.MapAnimationEasingFunction, FillBehavior = FillBehavior.HoldEnd }; @@ -758,8 +755,8 @@ namespace MapControl headingAnimation = new DoubleAnimation { By = delta, - Duration = AnimationDuration, - EasingFunction = AnimationEasingFunction, + Duration = Settings.MapAnimationDuration, + EasingFunction = Settings.MapAnimationEasingFunction, FillBehavior = FillBehavior.HoldEnd }; diff --git a/MapControl/MapControl.PhoneSilverlight.csproj b/MapControl/MapControl.PhoneSilverlight.csproj new file mode 100644 index 00000000..d914da3a --- /dev/null +++ b/MapControl/MapControl.PhoneSilverlight.csproj @@ -0,0 +1,150 @@ + + + + Debug + AnyCPU + 10.0.20506 + 2.0 + {3499D618-2846-4FCE-A418-7D211FDBDCB3} + {C089C8C0-30E0-4E22-80C0-CE093F111A43};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + MapControl + MapControl.PhoneSilverlight + WindowsPhone + v8.1 + $(TargetFrameworkVersion) + false + true + 12.0 + true + + + true + full + false + Bin\Debug + DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE + true + true + prompt + 4 + + + none + true + Bin\Release + TRACE;SILVERLIGHT;WINDOWS_PHONE + true + true + prompt + 4 + + + true + full + false + Bin\x86\Debug + DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE + true + true + prompt + 4 + + + pdbonly + true + Bin\x86\Release + TRACE;SILVERLIGHT;WINDOWS_PHONE + true + true + prompt + 4 + + + true + full + false + Bin\ARM\Debug + DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE + true + true + prompt + 4 + + + pdbonly + true + Bin\ARM\Release + TRACE;SILVERLIGHT;WINDOWS_PHONE + true + true + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + Designer + + + + + + + \ No newline at end of file diff --git a/MapControl/MapControl.PhoneSilverlight.csproj.user b/MapControl/MapControl.PhoneSilverlight.csproj.user new file mode 100644 index 00000000..d3122c8a --- /dev/null +++ b/MapControl/MapControl.PhoneSilverlight.csproj.user @@ -0,0 +1,15 @@ + + + + + + + True + Managed + Managed + False + + + + + \ No newline at end of file diff --git a/MapControl/MapControl.Silverlight.csproj b/MapControl/MapControl.Silverlight.csproj index 924b39b3..142a599b 100644 --- a/MapControl/MapControl.Silverlight.csproj +++ b/MapControl/MapControl.Silverlight.csproj @@ -65,10 +65,14 @@ $(TargetFrameworkDirectory)System.Core.dll + + + + Code @@ -97,11 +101,13 @@ + + diff --git a/MapControl/MapControl.WPF.csproj b/MapControl/MapControl.WPF.csproj index d24d60a8..747fc7b7 100644 --- a/MapControl/MapControl.WPF.csproj +++ b/MapControl/MapControl.WPF.csproj @@ -48,10 +48,14 @@ + + + + @@ -72,6 +76,7 @@ + @@ -83,6 +88,7 @@ + diff --git a/MapControl/MapImage.cs b/MapControl/MapImage.cs index b890f429..ae0ed05a 100644 --- a/MapControl/MapImage.cs +++ b/MapControl/MapImage.cs @@ -29,16 +29,10 @@ namespace MapControl private void SourceChanged(ImageSource image) { - var transform = new MatrixTransform - { - Matrix = new Matrix(1d, 0d, 0d, -1d, 0d, 1d) - }; - transform.Freeze(); - Fill = new ImageBrush { ImageSource = image, - RelativeTransform = transform + RelativeTransform = FillTransform }; } } diff --git a/MapControl/MapImageLayer.Silverlight.WinRT.cs b/MapControl/MapImageLayer.Silverlight.WinRT.cs index 3dde5f53..4dfb45f5 100644 --- a/MapControl/MapImageLayer.Silverlight.WinRT.cs +++ b/MapControl/MapImageLayer.Silverlight.WinRT.cs @@ -8,16 +8,13 @@ using Windows.UI.Xaml.Media.Imaging; #else using System.Windows; using System.Windows.Media.Imaging; -using System.Windows.Threading; #endif namespace MapControl { public partial class MapImageLayer { - private readonly DispatcherTimer updateTimer = new DispatcherTimer(); - - private void AddDownloadEventHandlers(BitmapSource bitmap) + private void ImageUpdated(BitmapSource bitmap) { var bitmapImage = bitmap as BitmapImage; @@ -35,7 +32,6 @@ namespace MapControl private void BitmapImageOpened(object sender, RoutedEventArgs e) { var bitmap = (BitmapImage)sender; - bitmap.ImageOpened -= BitmapImageOpened; bitmap.ImageFailed -= BitmapImageFailed; @@ -45,11 +41,12 @@ namespace MapControl private void BitmapImageFailed(object sender, ExceptionRoutedEventArgs e) { var bitmap = (BitmapImage)sender; - bitmap.ImageOpened -= BitmapImageOpened; bitmap.ImageFailed -= BitmapImageFailed; - ((MapImage)Children[currentImageIndex]).Source = null; + var mapImage = (MapImage)Children[currentImageIndex]; + mapImage.Source = null; + BlendImages(); } } diff --git a/MapControl/MapImageLayer.WPF.cs b/MapControl/MapImageLayer.WPF.cs index ba3d2aa3..791d3922 100644 --- a/MapControl/MapImageLayer.WPF.cs +++ b/MapControl/MapImageLayer.WPF.cs @@ -5,17 +5,14 @@ using System; using System.Windows.Media; using System.Windows.Media.Imaging; -using System.Windows.Threading; namespace MapControl { public partial class MapImageLayer { - private readonly DispatcherTimer updateTimer = new DispatcherTimer(DispatcherPriority.Background); - - private void AddDownloadEventHandlers(BitmapSource bitmap) + private void ImageUpdated(BitmapSource bitmap) { - if (bitmap.IsDownloading) + if (bitmap != null && !bitmap.IsFrozen && bitmap.IsDownloading) { bitmap.DownloadCompleted += BitmapDownloadCompleted; bitmap.DownloadFailed += BitmapDownloadFailed; @@ -29,7 +26,6 @@ namespace MapControl private void BitmapDownloadCompleted(object sender, EventArgs e) { var bitmap = (BitmapSource)sender; - bitmap.DownloadCompleted -= BitmapDownloadCompleted; bitmap.DownloadFailed -= BitmapDownloadFailed; @@ -39,11 +35,12 @@ namespace MapControl private void BitmapDownloadFailed(object sender, ExceptionEventArgs e) { var bitmap = (BitmapSource)sender; - bitmap.DownloadCompleted -= BitmapDownloadCompleted; bitmap.DownloadFailed -= BitmapDownloadFailed; - ((MapImage)Children[currentImageIndex]).Source = null; + var mapImage = (MapImage)Children[currentImageIndex]; + mapImage.Source = null; + BlendImages(); } } diff --git a/MapControl/MapImageLayer.cs b/MapControl/MapImageLayer.cs index a54cda3a..81ebd005 100644 --- a/MapControl/MapImageLayer.cs +++ b/MapControl/MapImageLayer.cs @@ -13,14 +13,14 @@ using Windows.UI.Xaml.Media.Animation; using System.Windows; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; +using System.Windows.Threading; #endif namespace MapControl { /// - /// Map image overlay. Fills the entire viewport with a map image from a web request, - /// for example from a Web Map Service (WMS). - /// The image request Uri is specified by the UriFormat property. + /// Map image overlay. Fills the entire viewport with map images provided by a web service, + /// e.g. a Web Map Service (WMS). The image request Uri is specified by the UriFormat property. /// public partial class MapImageLayer : MapPanel { @@ -31,6 +31,7 @@ namespace MapControl public static readonly DependencyProperty RelativeImageSizeProperty = DependencyProperty.Register( "RelativeImageSize", typeof(double), typeof(MapImageLayer), new PropertyMetadata(1d)); + private readonly DispatcherTimer updateTimer; private int currentImageIndex; private bool updateInProgress; @@ -39,15 +40,15 @@ namespace MapControl Children.Add(new MapImage { Opacity = 0d }); Children.Add(new MapImage { Opacity = 0d }); - updateTimer.Interval = TileContainer.UpdateInterval; + updateTimer = new DispatcherTimer { Interval = Settings.TileUpdateInterval }; updateTimer.Tick += (s, e) => UpdateImage(); } /// - /// The format string of the image request Uri. The format must contain {X} and {Y} - /// format specifiers for the map width and height in pixels, and either - /// {w},{s},{e},{n} for the bounding box in lat/lon (like for example EPSG:4326) or - /// {W},{S},{E},{N} for the bounding box in meters (like for example EPSG:3857). + /// The format string of the image request Uri. The format must contain + /// {X} and {Y} format specifiers for the map width and height in pixels and either + /// {w},{s},{e},{n} for the bounding box in lat/lon (like EPSG:4326) or + /// {W},{S},{E},{N} for the bounding box in meters (like EPSG:3857). /// public string UriFormat { @@ -74,10 +75,46 @@ namespace MapControl updateTimer.Start(); } - protected virtual BitmapSource GetBitmap(double west, double east, double south, double north, int width, int height) + protected void UpdateImage() { - BitmapImage image = null; + updateTimer.Stop(); + if (updateInProgress) + { + updateTimer.Start(); // update image on next timer tick + } + else if (ParentMap != null && RenderSize.Width > 0 && RenderSize.Height > 0) + { + updateInProgress = true; + + var relativeSize = Math.Max(RelativeImageSize, 1d); + var width = RenderSize.Width * relativeSize; + var height = RenderSize.Height * relativeSize; + var dx = (RenderSize.Width - width) / 2d; + var dy = (RenderSize.Height - height) / 2d; + + var loc1 = ParentMap.ViewportPointToLocation(new Point(dx, dy)); + var loc2 = ParentMap.ViewportPointToLocation(new Point(dx + width, dy)); + var loc3 = ParentMap.ViewportPointToLocation(new Point(dx, dy + height)); + var loc4 = ParentMap.ViewportPointToLocation(new Point(dx + width, dy + height)); + + var west = Math.Min(loc1.Longitude, Math.Min(loc2.Longitude, Math.Min(loc3.Longitude, loc4.Longitude))); + var east = Math.Max(loc1.Longitude, Math.Max(loc2.Longitude, Math.Max(loc3.Longitude, loc4.Longitude))); + var south = Math.Min(loc1.Latitude, Math.Min(loc2.Latitude, Math.Min(loc3.Latitude, loc4.Latitude))); + var north = Math.Max(loc1.Latitude, Math.Max(loc2.Latitude, Math.Max(loc3.Latitude, loc4.Latitude))); + + var p1 = ParentMap.MapTransform.Transform(new Location(south, west)); + var p2 = ParentMap.MapTransform.Transform(new Location(north, east)); + + width = Math.Round((p2.X - p1.X) * ParentMap.ViewportScale); + height = Math.Round((p2.Y - p1.Y) * ParentMap.ViewportScale); + + UpdateImage(west, east, south, north, (int)width, (int)height); + } + } + + protected virtual void UpdateImage(double west, double east, double south, double north, int width, int height) + { if (UriFormat != null && width > 0 && height > 0) { var uri = UriFormat.Replace("{X}", width.ToString()).Replace("{Y}", height.ToString()); @@ -102,98 +139,56 @@ namespace MapControl Replace("{n}", north.ToString(CultureInfo.InvariantCulture)); } - image = new BitmapImage(new Uri(uri)); - } - - return image; - } - - protected void UpdateImage() - { - if (updateInProgress) - { - updateTimer.Start(); // update image on next timer tick + UpdateImage(west, east, south, north, new Uri(uri)); } else { - updateTimer.Stop(); - - if (ParentMap != null && RenderSize.Width > 0 && RenderSize.Height > 0) - { - updateInProgress = true; - - var relativeSize = Math.Max(RelativeImageSize, 1d); - var width = RenderSize.Width * relativeSize; - var height = RenderSize.Height * relativeSize; - var dx = (RenderSize.Width - width) / 2d; - var dy = (RenderSize.Height - height) / 2d; - - var loc1 = ParentMap.ViewportPointToLocation(new Point(dx, dy)); - var loc2 = ParentMap.ViewportPointToLocation(new Point(dx + width, dy)); - var loc3 = ParentMap.ViewportPointToLocation(new Point(dx, dy + height)); - var loc4 = ParentMap.ViewportPointToLocation(new Point(dx + width, dy + height)); - - var west = Math.Min(loc1.Longitude, Math.Min(loc2.Longitude, Math.Min(loc3.Longitude, loc4.Longitude))); - var east = Math.Max(loc1.Longitude, Math.Max(loc2.Longitude, Math.Max(loc3.Longitude, loc4.Longitude))); - var south = Math.Min(loc1.Latitude, Math.Min(loc2.Latitude, Math.Min(loc3.Latitude, loc4.Latitude))); - var north = Math.Max(loc1.Latitude, Math.Max(loc2.Latitude, Math.Max(loc3.Latitude, loc4.Latitude))); - - var p1 = ParentMap.MapTransform.Transform(new Location(south, west)); - var p2 = ParentMap.MapTransform.Transform(new Location(north, east)); - - width = Math.Round((p2.X - p1.X) * ParentMap.ViewportScale); - height = Math.Round((p2.Y - p1.Y) * ParentMap.ViewportScale); - - var image = GetBitmap(west, east, south, north, (int)width, (int)height); - - UpdateImage(west, east, south, north, image); - } + UpdateImage(west, east, south, north, (BitmapSource)null); } } - private void UpdateImage(double west, double east, double south, double north, BitmapSource image) + protected virtual void UpdateImage(double west, double east, double south, double north, Uri uri) + { + UpdateImage(west, east, south, north, new BitmapImage(uri)); + } + + protected void UpdateImage(double west, double east, double south, double north, BitmapSource bitmap) { currentImageIndex = (currentImageIndex + 1) % 2; var mapImage = (MapImage)Children[currentImageIndex]; - mapImage.Source = null; mapImage.North = double.NaN; // avoid frequent MapRectangle.UpdateData() calls mapImage.West = west; mapImage.East = east; mapImage.South = south; mapImage.North = north; + mapImage.Source = bitmap; - if (image != null) - { - mapImage.Source = image; - AddDownloadEventHandlers(image); - } - else - { - BlendImages(); - } + ImageUpdated(bitmap); } private void BlendImages() { -#if WINDOWS_RUNTIME - var duration = TimeSpan.Zero; // animation not working in Windows Runtime (?) -#else - var duration = Tile.AnimationDuration; -#endif - var mapImage = (MapImage)Children[currentImageIndex]; - var fadeOut = new DoubleAnimation { To = 0d, Duration = duration }; + var topImage = (MapImage)Children[currentImageIndex]; + var bottomImage = (MapImage)Children[(currentImageIndex + 1) % 2]; - if (mapImage.Source != null) + if (topImage.Source != null) { - mapImage.BeginAnimation(UIElement.OpacityProperty, - new DoubleAnimation { To = 1d, Duration = duration }); - - fadeOut.BeginTime = duration; + topImage.BeginAnimation(UIElement.OpacityProperty, + new DoubleAnimation { To = 1d, Duration = Settings.TileAnimationDuration }); } - mapImage = (MapImage)Children[(currentImageIndex + 1) % 2]; - mapImage.BeginAnimation(UIElement.OpacityProperty, fadeOut); + if (bottomImage.Source != null) + { + var fadeOutAnimation = new DoubleAnimation { To = 0d, Duration = Settings.TileAnimationDuration }; + + if (topImage.Source != null) + { + fadeOutAnimation.BeginTime = Settings.TileAnimationDuration; + } + + bottomImage.BeginAnimation(UIElement.OpacityProperty, fadeOutAnimation); + } updateInProgress = false; } diff --git a/MapControl/MapPanel.cs b/MapControl/MapPanel.cs index f50da6f3..42a8b580 100644 --- a/MapControl/MapPanel.cs +++ b/MapControl/MapPanel.cs @@ -72,7 +72,7 @@ namespace MapControl protected override Size ArrangeOverride(Size finalSize) { - foreach (UIElement element in InternalChildren) + foreach (UIElement element in Children) { var location = GetLocation(element); @@ -92,7 +92,7 @@ namespace MapControl protected virtual void OnViewportChanged() { - foreach (UIElement element in InternalChildren) + foreach (UIElement element in Children) { var location = GetLocation(element); diff --git a/MapControl/MapRectangle.Silverlight.WinRT.cs b/MapControl/MapRectangle.Silverlight.WinRT.cs new file mode 100644 index 00000000..8737e1bf --- /dev/null +++ b/MapControl/MapRectangle.Silverlight.WinRT.cs @@ -0,0 +1,24 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © 2014 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +#if WINDOWS_RUNTIME +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media; +#else +using System.Windows; +using System.Windows.Media; +#endif + +namespace MapControl +{ + public partial class MapRectangle + { + private void SetRect(Rect rect) + { + ((RectangleGeometry)Data).Rect = rect; + RenderTransform = ParentMap.ViewportTransform; + } + } +} diff --git a/MapControl/MapRectangle.WPF.cs b/MapControl/MapRectangle.WPF.cs new file mode 100644 index 00000000..1b77cf88 --- /dev/null +++ b/MapControl/MapRectangle.WPF.cs @@ -0,0 +1,40 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © 2014 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Windows; +using System.Windows.Media; + +namespace MapControl +{ + public partial class MapRectangle + { + static MapRectangle() + { + FillTransform.Freeze(); + } + + private void SetRect(Rect rect) + { + // Apply scaling to the RectangleGeometry to compensate for inaccurate hit testing in WPF. + // See http://stackoverflow.com/a/19335624/1136211 + + var scale = 1e6 / Math.Min(rect.Width, rect.Height); + rect.X *= scale; + rect.Y *= scale; + rect.Width *= scale; + rect.Height *= scale; + + var scaleTransform = new ScaleTransform(1d / scale, 1d / scale); + scaleTransform.Freeze(); + + var transform = new TransformGroup(); + transform.Children.Add(scaleTransform); // revert scaling + transform.Children.Add(ParentMap.ViewportTransform); + + ((RectangleGeometry)Data).Rect = rect; + RenderTransform = transform; + } + } +} \ No newline at end of file diff --git a/MapControl/MapRectangle.cs b/MapControl/MapRectangle.cs index 79baf632..cf244559 100644 --- a/MapControl/MapRectangle.cs +++ b/MapControl/MapRectangle.cs @@ -7,6 +7,7 @@ using Windows.Foundation; using Windows.UI.Xaml; using Windows.UI.Xaml.Media; #else +using System; using System.Windows; using System.Windows.Media; #endif @@ -16,15 +17,15 @@ namespace MapControl /// /// Fills a rectangular area defined by South, North, West and East with a Brush. /// - public class MapRectangle : MapPath + public partial class MapRectangle : MapPath { - public static readonly DependencyProperty SouthProperty = DependencyProperty.Register( - "South", typeof(double), typeof(MapRectangle), - new PropertyMetadata(double.NaN, (o, e) => ((MapRectangle)o).UpdateData())); - - public static readonly DependencyProperty NorthProperty = DependencyProperty.Register( - "North", typeof(double), typeof(MapRectangle), - new PropertyMetadata(double.NaN, (o, e) => ((MapRectangle)o).UpdateData())); + /// + /// Used in derived classes like MapImage. + /// + protected static readonly MatrixTransform FillTransform = new MatrixTransform + { + Matrix = new Matrix(1d, 0d, 0d, -1d, 0d, 1d) + }; public static readonly DependencyProperty WestProperty = DependencyProperty.Register( "West", typeof(double), typeof(MapRectangle), @@ -34,24 +35,20 @@ namespace MapControl "East", typeof(double), typeof(MapRectangle), new PropertyMetadata(double.NaN, (o, e) => ((MapRectangle)o).UpdateData())); + public static readonly DependencyProperty SouthProperty = DependencyProperty.Register( + "South", typeof(double), typeof(MapRectangle), + new PropertyMetadata(double.NaN, (o, e) => ((MapRectangle)o).UpdateData())); + + public static readonly DependencyProperty NorthProperty = DependencyProperty.Register( + "North", typeof(double), typeof(MapRectangle), + new PropertyMetadata(double.NaN, (o, e) => ((MapRectangle)o).UpdateData())); + public MapRectangle() { Data = new RectangleGeometry(); StrokeThickness = 0d; } - public double South - { - get { return (double)GetValue(SouthProperty); } - set { SetValue(SouthProperty, value); } - } - - public double North - { - get { return (double)GetValue(NorthProperty); } - set { SetValue(NorthProperty, value); } - } - public double West { get { return (double)GetValue(WestProperty); } @@ -64,6 +61,18 @@ namespace MapControl set { SetValue(EastProperty, value); } } + public double South + { + get { return (double)GetValue(SouthProperty); } + set { SetValue(SouthProperty, value); } + } + + public double North + { + get { return (double)GetValue(NorthProperty); } + set { SetValue(NorthProperty, value); } + } + protected override void UpdateData() { var geometry = (RectangleGeometry)Data; @@ -73,25 +82,10 @@ namespace MapControl !double.IsNaN(West) && !double.IsNaN(East) && South < North && West < East) { - // Create a scaled RectangleGeometry due to inaccurate hit testing in WPF. - // See http://stackoverflow.com/a/19335624/1136211 - - const double scale = 1e6; var p1 = ParentMap.MapTransform.Transform(new Location(South, West)); var p2 = ParentMap.MapTransform.Transform(new Location(North, East)); - geometry.Rect = new Rect(p1.X * scale, p1.Y * scale, (p2.X - p1.X) * scale, (p2.Y - p1.Y) * scale); - var scaleTransform = new ScaleTransform // revert scaling - { - ScaleX = 1d / scale, - ScaleY = 1d / scale - }; - scaleTransform.Freeze(); - - var transform = new TransformGroup(); - transform.Children.Add(scaleTransform); - transform.Children.Add(ParentMap.ViewportTransform); - RenderTransform = transform; + SetRect(new Rect(p1.X, p1.Y, p2.X - p1.X, p2.Y - p1.Y)); } else { diff --git a/MapControl/PanelBase.cs b/MapControl/PanelBase.cs index 063a9623..5bfa7463 100644 --- a/MapControl/PanelBase.cs +++ b/MapControl/PanelBase.cs @@ -19,17 +19,11 @@ namespace MapControl /// public class PanelBase : Panel { -#if WINDOWS_RUNTIME || SILVERLIGHT - protected internal UIElementCollection InternalChildren - { - get { return Children; } - } -#endif protected override Size MeasureOverride(Size availableSize) { availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity); - foreach (UIElement element in InternalChildren) + foreach (UIElement element in Children) { element.Measure(availableSize); } @@ -39,7 +33,7 @@ namespace MapControl protected override Size ArrangeOverride(Size finalSize) { - foreach (UIElement child in InternalChildren) + foreach (UIElement child in Children) { child.Arrange(new Rect(new Point(), finalSize)); } diff --git a/MapControl/Properties/AssemblyInfo.cs b/MapControl/Properties/AssemblyInfo.cs index fa261b79..97cb566c 100644 --- a/MapControl/Properties/AssemblyInfo.cs +++ b/MapControl/Properties/AssemblyInfo.cs @@ -2,7 +2,10 @@ using System.Runtime.InteropServices; using System.Windows; -#if SILVERLIGHT +#if WINDOWS_PHONE +[assembly: AssemblyTitle("XAML Map Control (Windows Phone Silverlight)")] +[assembly: AssemblyDescription("XAML Map Control Library for Windows Phone Silverlight")] +#elif SILVERLIGHT [assembly: AssemblyTitle("XAML Map Control (Silverlight)")] [assembly: AssemblyDescription("XAML Map Control Library for Silverlight")] #else @@ -14,8 +17,8 @@ using System.Windows; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.2.0")] -[assembly: AssemblyFileVersion("2.2.0")] +[assembly: AssemblyVersion("2.3.0")] +[assembly: AssemblyFileVersion("2.3.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapControl/Settings.cs b/MapControl/Settings.cs new file mode 100644 index 00000000..0938f846 --- /dev/null +++ b/MapControl/Settings.cs @@ -0,0 +1,32 @@ +// XAML Map Control - http://xamlmapcontrol.codeplex.com/ +// Copyright © 2014 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +#if WINDOWS_RUNTIME +using Windows.UI.Xaml.Media.Animation; +#else +using System.Windows.Media.Animation; +#endif + +namespace MapControl +{ + /// + /// Stores global static properties that control the behaviour of the map control. + /// + public static class Settings + { + public static TimeSpan TileUpdateInterval { get; set; } + public static TimeSpan TileAnimationDuration { get; set; } + public static TimeSpan MapAnimationDuration { get; set; } + public static EasingFunctionBase MapAnimationEasingFunction { get; set; } + + static Settings() + { + TileUpdateInterval = TimeSpan.FromSeconds(0.5); + TileAnimationDuration = TimeSpan.FromSeconds(0.3); + MapAnimationDuration = TimeSpan.FromSeconds(0.3); + MapAnimationEasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }; + } + } +} diff --git a/MapControl/Themes/Generic.xaml b/MapControl/Themes/Generic.xaml index ad67eb4d..526596ba 100644 --- a/MapControl/Themes/Generic.xaml +++ b/MapControl/Themes/Generic.xaml @@ -40,6 +40,7 @@ + diff --git a/MapControl/Tile.Silverlight.WinRT.cs b/MapControl/Tile.Silverlight.WinRT.cs index 82d1bafc..c5128834 100644 --- a/MapControl/Tile.Silverlight.WinRT.cs +++ b/MapControl/Tile.Silverlight.WinRT.cs @@ -35,7 +35,8 @@ namespace MapControl } else { - Image.BeginAnimation(Image.OpacityProperty, new DoubleAnimation { To = 1d, Duration = AnimationDuration }); + Image.BeginAnimation(Image.OpacityProperty, + new DoubleAnimation { To = 1d, Duration = Settings.TileAnimationDuration }); } } else @@ -55,7 +56,8 @@ namespace MapControl bitmap.ImageOpened -= BitmapImageOpened; bitmap.ImageFailed -= BitmapImageFailed; - Image.BeginAnimation(Image.OpacityProperty, new DoubleAnimation { To = 1d, Duration = AnimationDuration }); + Image.BeginAnimation(Image.OpacityProperty, + new DoubleAnimation { To = 1d, Duration = Settings.TileAnimationDuration }); } private void BitmapImageFailed(object sender, ExceptionRoutedEventArgs e) diff --git a/MapControl/Tile.WPF.cs b/MapControl/Tile.WPF.cs index e3706e85..7ba45cc0 100644 --- a/MapControl/Tile.WPF.cs +++ b/MapControl/Tile.WPF.cs @@ -27,7 +27,8 @@ namespace MapControl } else { - Image.BeginAnimation(Image.OpacityProperty, new DoubleAnimation(1d, AnimationDuration)); + Image.BeginAnimation(Image.OpacityProperty, + new DoubleAnimation(1d, Settings.TileAnimationDuration)); } } else @@ -47,7 +48,8 @@ namespace MapControl bitmap.DownloadCompleted -= BitmapDownloadCompleted; bitmap.DownloadFailed -= BitmapDownloadFailed; - Image.BeginAnimation(Image.OpacityProperty, new DoubleAnimation(1d, AnimationDuration)); + Image.BeginAnimation(Image.OpacityProperty, + new DoubleAnimation(1d, Settings.TileAnimationDuration)); } private void BitmapDownloadFailed(object sender, ExceptionEventArgs e) diff --git a/MapControl/Tile.cs b/MapControl/Tile.cs index c5ab8a7f..ddd43cf2 100644 --- a/MapControl/Tile.cs +++ b/MapControl/Tile.cs @@ -13,8 +13,6 @@ namespace MapControl { public partial class Tile { - public static TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.5); - public readonly int ZoomLevel; public readonly int X; public readonly int Y; diff --git a/MapControl/TileContainer.Silverlight.WinRT.cs b/MapControl/TileContainer.Silverlight.WinRT.cs index c5a4a44c..2e14305d 100644 --- a/MapControl/TileContainer.Silverlight.WinRT.cs +++ b/MapControl/TileContainer.Silverlight.WinRT.cs @@ -13,10 +13,8 @@ namespace MapControl { internal partial class TileContainer { - private Matrix GetTileIndexMatrix(int numTiles) + private Matrix GetTileIndexMatrix(double scale) { - var scale = (double)numTiles / 360d; - return ViewportTransform.Matrix .Invert() // view to map coordinates .Translate(180d, -180d) diff --git a/MapControl/TileContainer.WPF.cs b/MapControl/TileContainer.WPF.cs index c5174c69..378ac839 100644 --- a/MapControl/TileContainer.WPF.cs +++ b/MapControl/TileContainer.WPF.cs @@ -9,9 +9,8 @@ namespace MapControl { internal partial class TileContainer { - private Matrix GetTileIndexMatrix(int numTiles) + private Matrix GetTileIndexMatrix(double scale) { - var scale = (double)numTiles / 360d; var transform = ViewportTransform.Matrix; transform.Invert(); // view to map coordinates transform.Translate(180d, -180d); diff --git a/MapControl/TileContainer.cs b/MapControl/TileContainer.cs index cc4e47b7..3abd3942 100644 --- a/MapControl/TileContainer.cs +++ b/MapControl/TileContainer.cs @@ -19,11 +19,9 @@ namespace MapControl { internal partial class TileContainer : PanelBase { - // relative scaled tile size ranges from 0.75 to 1.5 (192 to 384 pixels) + // relative size of scaled tile ranges from 0.75 to 1.5 (192 to 384 pixels) private static double zoomLevelSwitchDelta = -Math.Log(0.75, 2d); - public static TimeSpan UpdateInterval = TimeSpan.FromSeconds(0.5); - private readonly DispatcherTimer updateTimer; private Size viewportSize; private Point viewportOrigin; @@ -38,26 +36,26 @@ namespace MapControl public TileContainer() { RenderTransform = new MatrixTransform(); - updateTimer = new DispatcherTimer { Interval = UpdateInterval }; + updateTimer = new DispatcherTimer { Interval = Settings.TileUpdateInterval }; updateTimer.Tick += UpdateTiles; } public IEnumerable TileLayers { - get { return InternalChildren.Cast(); } + get { return Children.Cast(); } } public void AddTileLayers(int index, IEnumerable tileLayers) { foreach (var tileLayer in tileLayers) { - if (index < InternalChildren.Count) + if (index < Children.Count) { - InternalChildren.Insert(index, tileLayer); + Children.Insert(index, tileLayer); } else { - InternalChildren.Add(tileLayer); + Children.Add(tileLayer); } index++; @@ -69,19 +67,19 @@ namespace MapControl { while (count-- > 0) { - ((TileLayer)InternalChildren[index]).ClearTiles(); - InternalChildren.RemoveAt(index); + ((TileLayer)Children[index]).ClearTiles(); + Children.RemoveAt(index); } } public void ClearTileLayers() { - foreach (TileLayer tileLayer in InternalChildren) + foreach (TileLayer tileLayer in Children) { tileLayer.ClearTiles(); } - InternalChildren.Clear(); + Children.Clear(); } public double SetViewportTransform(double mapZoomLevel, double mapRotation, Point mapOrigin, Point vpOrigin, Size vpSize) @@ -127,7 +125,8 @@ namespace MapControl updateTimer.Stop(); var zoom = (int)Math.Floor(zoomLevel + zoomLevelSwitchDelta); - var transform = GetTileIndexMatrix(1 << zoom); + var scale = (double)(1 << zoom) / 360d; + var transform = GetTileIndexMatrix(scale); // tile indices of visible rectangle var p1 = transform.Transform(new Point(0d, 0d)); @@ -149,7 +148,7 @@ namespace MapControl UpdateRenderTransform(); - foreach (TileLayer tileLayer in InternalChildren) + foreach (TileLayer tileLayer in Children) { tileLayer.UpdateTiles(tileZoomLevel, tileGrid); } diff --git a/MapControl/TileImageLoader.Silverlight.cs b/MapControl/TileImageLoader.Silverlight.cs index 37e06286..4ff7fba4 100644 --- a/MapControl/TileImageLoader.Silverlight.cs +++ b/MapControl/TileImageLoader.Silverlight.cs @@ -13,9 +13,9 @@ namespace MapControl /// /// Loads map tile images. /// - internal class TileImageLoader + internal class TileImageLoader : ITileImageLoader { - internal void BeginGetTiles(TileLayer tileLayer, IEnumerable tiles) + public void BeginLoadTiles(TileLayer tileLayer, IEnumerable tiles) { var imageTileSource = tileLayer.TileSource as ImageTileSource; @@ -48,7 +48,7 @@ namespace MapControl } } - internal void CancelGetTiles() + public void CancelLoadTiles(TileLayer tileLayer) { } } diff --git a/MapControl/TileImageLoader.WPF.cs b/MapControl/TileImageLoader.WPF.cs index 16621882..913bad84 100644 --- a/MapControl/TileImageLoader.WPF.cs +++ b/MapControl/TileImageLoader.WPF.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -20,7 +21,7 @@ namespace MapControl /// /// Loads map tile images and optionally caches them in a System.Runtime.Caching.ObjectCache. /// - public class TileImageLoader + public class TileImageLoader : ITileImageLoader { /// /// Default Name of an ObjectCache instance that is assigned to the Cache property. @@ -33,39 +34,27 @@ namespace MapControl public static readonly string DefaultCacheDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapControl"); + /// + /// Default expiration time span for cached images. Used when no expiration date + /// was transmitted on download. The default value is seven days. + /// + public static TimeSpan DefaultCacheExpiration { get; set; } + /// /// The ObjectCache used to cache tile images. The default is MemoryCache.Default. /// public static ObjectCache Cache { get; set; } - /// - /// The time interval after which cached images expire. The default value is seven days. - /// When an image is not retrieved from the cache during this interval it is considered as expired - /// and will be removed from the cache, provided that the cache implementation supports expiration. - /// If an image is retrieved from the cache and the CacheUpdateAge time interval has expired, - /// the image is downloaded again and rewritten to the cache with a new expiration time. - /// - public static TimeSpan CacheExpiration { get; set; } - - /// - /// The time interval after which a cached image is updated and rewritten to the cache. - /// The default value is one day. This time interval should not be greater than the value - /// of the CacheExpiration property. - /// If CacheUpdateAge is less than or equal to TimeSpan.Zero, cached images are never updated. - /// - public static TimeSpan CacheUpdateAge { get; set; } - static TileImageLoader() { + DefaultCacheExpiration = TimeSpan.FromDays(7); Cache = MemoryCache.Default; - CacheExpiration = TimeSpan.FromDays(7); - CacheUpdateAge = TimeSpan.FromDays(1); } private readonly ConcurrentQueue pendingTiles = new ConcurrentQueue(); private int threadCount; - internal void BeginGetTiles(TileLayer tileLayer, IEnumerable tiles) + public void BeginLoadTiles(TileLayer tileLayer, IEnumerable tiles) { if (tiles.Any()) { @@ -96,7 +85,7 @@ namespace MapControl } } - internal void CancelGetTiles() + public void CancelLoadTiles(TileLayer tileLayer) { Tile tile; while (pendingTiles.TryDequeue(out tile)) ; // no Clear method @@ -119,7 +108,7 @@ namespace MapControl { pendingTiles.Enqueue(tile); // not yet cached } - else if (CacheUpdateAge > TimeSpan.Zero && TileCache.CreationTime(buffer) + CacheUpdateAge < DateTime.UtcNow) + else if (TileCache.IsExpired(buffer)) { dispatcher.Invoke(setImageAction, tile, image); // synchronously before enqueuing outdatedTiles.Add(tile); // update outdated cache @@ -154,7 +143,6 @@ namespace MapControl while (pendingTiles.TryDequeue(out tile)) { - byte[] buffer = null; ImageSource image = null; if (imageTileSource != null) @@ -167,14 +155,24 @@ namespace MapControl if (uri != null) { - if (uri.Scheme == "file") // create from FileStream because creating from URI leaves the file open + if (uri.Scheme == "file") // create from FileStream because creating from Uri leaves the file open { image = CreateImage(uri.LocalPath); } else { - buffer = DownloadImage(uri); + DateTime expirationTime; + var buffer = DownloadImage(uri, out expirationTime); + image = CreateImage(buffer); + + if (image != null && + Cache != null && + !string.IsNullOrWhiteSpace(sourceName) && + expirationTime > DateTime.UtcNow) + { + Cache.Set(TileCache.Key(sourceName, tile), buffer, new CacheItemPolicy { AbsoluteExpiration = expirationTime }); + } } } } @@ -183,11 +181,6 @@ namespace MapControl { dispatcher.BeginInvoke(setImageAction, tile, image); } - - if (image != null && buffer != null && Cache != null && !string.IsNullOrWhiteSpace(sourceName)) - { - Cache.Set(TileCache.Key(sourceName, tile), buffer, new CacheItemPolicy { SlidingExpiration = CacheExpiration }); - } } Interlocked.Decrement(ref threadCount); @@ -253,20 +246,33 @@ namespace MapControl return image; } - private static byte[] DownloadImage(Uri uri) + private static byte[] DownloadImage(Uri uri, out DateTime expirationTime) { + expirationTime = DateTime.UtcNow + DefaultCacheExpiration; + byte[] buffer = null; try { - var request = (HttpWebRequest)WebRequest.Create(uri); - request.UserAgent = "XAML Map Control"; + var request = HttpWebRequest.CreateHttp(uri); using (var response = (HttpWebResponse)request.GetResponse()) using (var responseStream = response.GetResponseStream()) { - buffer = TileCache.CreateBuffer(responseStream, (int)response.ContentLength); + var expiresHeader = response.Headers["Expires"]; + DateTime expires; + + if (expiresHeader != null && + DateTime.TryParse(expiresHeader, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out expires) && + expirationTime > expires) + { + expirationTime = expires; + } + + buffer = TileCache.CreateBuffer(responseStream, (int)response.ContentLength, expirationTime); } + + //Trace.TraceInformation("Downloaded {0}, expires {1}", uri, expirationTime); } catch (WebException ex) { @@ -305,18 +311,16 @@ namespace MapControl return new MemoryStream(cacheBuffer, imageBufferOffset, cacheBuffer.Length - imageBufferOffset, false); } - public static DateTime CreationTime(byte[] cacheBuffer) + public static bool IsExpired(byte[] cacheBuffer) { - return DateTime.FromBinary(BitConverter.ToInt64(cacheBuffer, 0)); + return DateTime.FromBinary(BitConverter.ToInt64(cacheBuffer, 0)) < DateTime.UtcNow; } - public static byte[] CreateBuffer(Stream imageStream, int length) + public static byte[] CreateBuffer(Stream imageStream, int length, DateTime expirationTime) { - var creationTime = BitConverter.GetBytes(DateTime.UtcNow.ToBinary()); - using (var memoryStream = length > 0 ? new MemoryStream(length + imageBufferOffset) : new MemoryStream()) { - memoryStream.Write(creationTime, 0, imageBufferOffset); + memoryStream.Write(BitConverter.GetBytes(expirationTime.ToBinary()), 0, imageBufferOffset); imageStream.CopyTo(memoryStream); return length > 0 ? memoryStream.GetBuffer() : memoryStream.ToArray(); diff --git a/MapControl/TileImageLoader.WinRT.cs b/MapControl/TileImageLoader.WinRT.cs index 9bf00dd8..01348e35 100644 --- a/MapControl/TileImageLoader.WinRT.cs +++ b/MapControl/TileImageLoader.WinRT.cs @@ -20,13 +20,13 @@ namespace MapControl /// /// Loads map tile images. /// - public class TileImageLoader + public class TileImageLoader : ITileImageLoader { public static IObjectCache Cache { get; set; } private HttpClient httpClient; - internal void BeginGetTiles(TileLayer tileLayer, IEnumerable tiles) + public void BeginLoadTiles(TileLayer tileLayer, IEnumerable tiles) { var imageTileSource = tileLayer.TileSource as ImageTileSource; @@ -69,7 +69,7 @@ namespace MapControl } } - internal void CancelGetTiles() + public void CancelLoadTiles(TileLayer tileLayer) { } diff --git a/MapControl/TileLayer.cs b/MapControl/TileLayer.cs index 2b623f2c..215e71b6 100644 --- a/MapControl/TileLayer.cs +++ b/MapControl/TileLayer.cs @@ -7,16 +7,24 @@ using System.Collections.Generic; using System.Linq; #if WINDOWS_RUNTIME using Windows.Foundation; +using Windows.UI.Xaml.Documents; using Windows.UI.Xaml.Markup; using Windows.UI.Xaml.Media; #else using System.Windows; +using System.Windows.Documents; using System.Windows.Markup; using System.Windows.Media; #endif namespace MapControl { + public interface ITileImageLoader + { + void BeginLoadTiles(TileLayer tileLayer, IEnumerable tiles); + void CancelLoadTiles(TileLayer tileLayer); + } + /// /// Fills a rectangular area with map tiles from a TileSource. /// @@ -34,29 +42,35 @@ namespace MapControl return new TileLayer { SourceName = "OpenStreetMap", - Description = "© {y} OpenStreetMap Contributors, CC-BY-SA", - TileSource = new TileSource("http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png") + Description="© [OpenStreetMap Contributors](http://www.openstreetmap.org/copyright)", + TileSource = new TileSource { UriFormat = "http://{c}.tile.openstreetmap.org/{z}/{x}/{y}.png" } }; } } - private readonly TileImageLoader tileImageLoader = new TileImageLoader(); - private string description = string.Empty; + private readonly ITileImageLoader tileImageLoader; private TileSource tileSource; private List tiles = new List(); - private Int32Rect grid; private int zoomLevel; + private Int32Rect grid; public TileLayer() + : this(new TileImageLoader()) { + } + + public TileLayer(ITileImageLoader tileImageLoader) + { + this.tileImageLoader = tileImageLoader; MinZoomLevel = 0; MaxZoomLevel = 18; - MaxParallelDownloads = 8; + MaxParallelDownloads = 4; LoadLowerZoomLevels = true; AnimateTileOpacity = true; } public string SourceName { get; set; } + public string Description { get; set; } public int MinZoomLevel { get; set; } public int MaxZoomLevel { get; set; } public int MaxParallelDownloads { get; set; } @@ -64,10 +78,14 @@ namespace MapControl public bool AnimateTileOpacity { get; set; } public Brush Foreground { get; set; } - public string Description + /// + /// In case the Description text contains copyright links in markdown syntax [text](url), + /// the DescriptionInlines property may be used to create a collection of Run and Hyperlink + /// inlines to be displayed in e.g. a TextBlock or a Silverlight RichTextBlock. + /// + public ICollection DescriptionInlines { - get { return description; } - set { description = value.Replace("{y}", DateTime.Now.Year.ToString()); } + get { return Description.ToInlines(); } } public TileSource TileSource @@ -79,42 +97,43 @@ namespace MapControl if (grid.Width > 0 && grid.Height > 0) { - tileImageLoader.CancelGetTiles(); - tiles.Clear(); - - if (tileSource != null) - { - SelectTiles(); - RenderTiles(); - tileImageLoader.BeginGetTiles(this, tiles.Where(t => !t.HasImageSource)); - } - else - { - RenderTiles(); - } + ClearTiles(); + UpdateTiles(); } } } - internal void UpdateTiles(int zoomLevel, Int32Rect grid) - { - this.grid = grid; - this.zoomLevel = zoomLevel; - - if (tileSource != null) - { - tileImageLoader.CancelGetTiles(); - SelectTiles(); - RenderTiles(); - tileImageLoader.BeginGetTiles(this, tiles.Where(t => !t.HasImageSource)); - } - } - internal void ClearTiles() { - tileImageLoader.CancelGetTiles(); + tileImageLoader.CancelLoadTiles(this); tiles.Clear(); - RenderTiles(); + Children.Clear(); + } + + internal void UpdateTiles(int zoomLevel, Int32Rect grid) + { + this.zoomLevel = zoomLevel; + this.grid = grid; + + UpdateTiles(); + } + + private void UpdateTiles() + { + if (tileSource != null) + { + tileImageLoader.CancelLoadTiles(this); + + SelectTiles(); + Children.Clear(); + + foreach (var tile in tiles) + { + Children.Add(tile.Image); + } + + tileImageLoader.BeginLoadTiles(this, tiles.Where(t => !t.HasImageSource)); + } } private void SelectTiles() @@ -122,7 +141,9 @@ namespace MapControl var maxZoomLevel = Math.Min(zoomLevel, MaxZoomLevel); var minZoomLevel = maxZoomLevel; - if (LoadLowerZoomLevels && Parent is TileContainer && ((TileContainer)Parent).TileLayers.FirstOrDefault() == this) + if (LoadLowerZoomLevels && + Parent is TileContainer && + ((TileContainer)Parent).TileLayers.FirstOrDefault() == this) { minZoomLevel = MinZoomLevel; } @@ -165,16 +186,6 @@ namespace MapControl tiles = newTiles; } - private void RenderTiles() - { - InternalChildren.Clear(); - - foreach (var tile in tiles) - { - InternalChildren.Add(tile.Image); - } - } - protected override Size ArrangeOverride(Size finalSize) { foreach (var tile in tiles) diff --git a/MapControl/TileSource.cs b/MapControl/TileSource.cs index d2bb78d6..cfe8499f 100644 --- a/MapControl/TileSource.cs +++ b/MapControl/TileSource.cs @@ -4,7 +4,6 @@ using System; using System.Globalization; -using System.Text; #if WINDOWS_RUNTIME using Windows.Foundation; #else @@ -28,12 +27,11 @@ namespace MapControl { } - public TileSource(string uriFormat) - : this() + protected TileSource(string uriFormat) { - UriFormat = uriFormat; + this.uriFormat = uriFormat; } - + public string UriFormat { get { return uriFormat; } @@ -147,16 +145,16 @@ namespace MapControl return null; } - var key = new StringBuilder { Length = zoomLevel }; + var quadkey = new char[zoomLevel]; for (var z = zoomLevel - 1; z >= 0; z--, x /= 2, y /= 2) { - key[z] = (char)('0' + 2 * (y % 2) + (x % 2)); + quadkey[z] = (char)('0' + 2 * (y % 2) + (x % 2)); } return new Uri(uriFormat. - Replace("{i}", key.ToString(key.Length - 1, 1)). - Replace("{q}", key.ToString())); + Replace("{i}", new string(quadkey[zoomLevel - 1], 1)). + Replace("{q}", new string(quadkey))); } private Uri GetBoundingBoxUri(int x, int y, int zoomLevel) diff --git a/MapControl/TileSourceConverter.cs b/MapControl/TileSourceConverter.cs index 60070fa0..50aa3b54 100644 --- a/MapControl/TileSourceConverter.cs +++ b/MapControl/TileSourceConverter.cs @@ -17,7 +17,7 @@ namespace MapControl public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return new TileSource(value as string); + return new TileSource { UriFormat = value as string }; } } diff --git a/MapControl/WinRT/MapControl.WinRT.csproj b/MapControl/WinRT/MapControl.WinRT.csproj index 5fc867d2..21a860df 100644 --- a/MapControl/WinRT/MapControl.WinRT.csproj +++ b/MapControl/WinRT/MapControl.WinRT.csproj @@ -36,12 +36,21 @@ 4 + + BingMapsTileLayer.cs + + + BingMapsTileSource.cs + Extensions.Silverlight.WinRT.cs Extensions.WinRT.cs + + HyperlinkText.cs + ImageFileCache.WinRT.cs @@ -120,6 +129,9 @@ MapRectangle.cs + + MapRectangle.Silverlight.WinRT.cs + MapTransform.cs @@ -132,6 +144,9 @@ Pushpin.Silverlight.WinRT.cs + + Settings.cs + Tile.cs @@ -163,16 +178,17 @@ MapControl.snk - - - MSBuild:Compile - Designer - - + + + + Designer + MSBuild:Compile + + 12.0 diff --git a/MapControl/WinRT/Properties/AssemblyInfo.cs b/MapControl/WinRT/Properties/AssemblyInfo.cs index 72eb776b..6bbff177 100644 --- a/MapControl/WinRT/Properties/AssemblyInfo.cs +++ b/MapControl/WinRT/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.2.0")] -[assembly: AssemblyFileVersion("2.2.0")] +[assembly: AssemblyVersion("2.3.0")] +[assembly: AssemblyFileVersion("2.3.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapControl/WinRT/Themes/Generic.xaml b/MapControl/WinRT/Themes/Generic.xaml index 84a5361e..6a6cf529 100644 --- a/MapControl/WinRT/Themes/Generic.xaml +++ b/MapControl/WinRT/Themes/Generic.xaml @@ -40,6 +40,7 @@ + diff --git a/SampleApps/PhoneApplication/App.xaml.cs b/SampleApps/PhoneApplication/App.xaml.cs index aec98412..8e7691c2 100644 --- a/SampleApps/PhoneApplication/App.xaml.cs +++ b/SampleApps/PhoneApplication/App.xaml.cs @@ -1,18 +1,8 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; -using Windows.Foundation; -using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Data; -using Windows.UI.Xaml.Input; -using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Animation; using Windows.UI.Xaml.Navigation; diff --git a/SampleApps/PhoneApplication/MainPage.xaml b/SampleApps/PhoneApplication/MainPage.xaml index ce92100c..37ee652c 100644 --- a/SampleApps/PhoneApplication/MainPage.xaml +++ b/SampleApps/PhoneApplication/MainPage.xaml @@ -7,30 +7,58 @@ Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> - + + - - + + - - + + - - + + - + + + + + + + - - - - - - - + + + + + + @@ -43,6 +71,7 @@ + + + + + + @@ -83,13 +117,16 @@ - - - - - - + + + + + + + + diff --git a/SampleApps/PhoneApplication/MainPage.xaml.cs b/SampleApps/PhoneApplication/MainPage.xaml.cs index a764a555..6764fdf1 100644 --- a/SampleApps/PhoneApplication/MainPage.xaml.cs +++ b/SampleApps/PhoneApplication/MainPage.xaml.cs @@ -10,38 +10,49 @@ namespace PhoneApplication { public sealed partial class MainPage : Page { + private TileLayerCollection tileLayers; private bool manipulationActive; public MainPage() { TileImageLoader.Cache = new ImageFileCache(); + //BingMapsTileLayer.ApiKey = ... InitializeComponent(); - var tileLayers = (TileLayerCollection)Resources["TileLayers"]; - map.TileLayer = tileLayers[0]; + tileLayers = (TileLayerCollection)Resources["TileLayers"]; + SetTileLayer(tileLayers[0].SourceName); DataContext = new ViewModel(Dispatcher); NavigationCacheMode = NavigationCacheMode.Required; } + private void SetTileLayer(string tileLayer) + { + map.TileLayer = tileLayers[tileLayer]; + + mapLegend.Inlines.Clear(); + + foreach (var inline in map.TileLayer.DescriptionInlines) + { + mapLegend.Inlines.Add(inline); + } + } + private void SeamarksChecked(object sender, RoutedEventArgs e) { - var tileLayers = (TileLayerCollection)Resources["TileLayers"]; map.TileLayers.Add((TileLayer)tileLayers["Seamarks"]); } private void SeamarksUnchecked(object sender, RoutedEventArgs e) { - var tileLayers = (TileLayerCollection)Resources["TileLayers"]; map.TileLayers.Remove((TileLayer)tileLayers["Seamarks"]); } private void MapMenuItemClick(object sender, RoutedEventArgs e) { - var selectedValue = ((MenuFlyoutItem)sender).Text; - var tileLayers = (TileLayerCollection)Resources["TileLayers"]; - map.TileLayer = tileLayers[selectedValue]; + var selectedItem = (MenuFlyoutItem)sender; + SetTileLayer((string)selectedItem.Tag); } private void CenterButtonClick(object sender, RoutedEventArgs e) diff --git a/SampleApps/PhoneApplication/Properties/AssemblyInfo.cs b/SampleApps/PhoneApplication/Properties/AssemblyInfo.cs index 3d543212..b71720b9 100644 --- a/SampleApps/PhoneApplication/Properties/AssemblyInfo.cs +++ b/SampleApps/PhoneApplication/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.2.0")] -[assembly: AssemblyFileVersion("2.2.0")] +[assembly: AssemblyVersion("2.3.0")] +[assembly: AssemblyFileVersion("2.3.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/PhoneApplication/ViewModel.cs b/SampleApps/PhoneApplication/ViewModel.cs index 10857534..69cb26aa 100644 --- a/SampleApps/PhoneApplication/ViewModel.cs +++ b/SampleApps/PhoneApplication/ViewModel.cs @@ -63,10 +63,7 @@ namespace PhoneApplication if (args.Status != PositionStatus.Initializing && args.Status != PositionStatus.Ready) { - await dispatcher.RunAsync(CoreDispatcherPriority.Low, () => - { - Location = null; - }); + await dispatcher.RunAsync(CoreDispatcherPriority.Low, () => Location = null); } } diff --git a/SampleApps/SilverlightApplication.Web/Properties/AssemblyInfo.cs b/SampleApps/SilverlightApplication.Web/Properties/AssemblyInfo.cs index b3e071f4..e658088b 100644 --- a/SampleApps/SilverlightApplication.Web/Properties/AssemblyInfo.cs +++ b/SampleApps/SilverlightApplication.Web/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.2.0")] -[assembly: AssemblyFileVersion("2.2.0")] +[assembly: AssemblyVersion("2.3.0")] +[assembly: AssemblyFileVersion("2.3.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/SilverlightApplication/MainPage.xaml b/SampleApps/SilverlightApplication/MainPage.xaml index dec6834b..91edfd0f 100644 --- a/SampleApps/SilverlightApplication/MainPage.xaml +++ b/SampleApps/SilverlightApplication/MainPage.xaml @@ -4,25 +4,58 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:map="clr-namespace:MapControl;assembly=MapControl.Silverlight" xmlns:vm="clr-namespace:ViewModel" xmlns:local="clr-namespace:SilverlightApplication" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> - - - - - - + + + + + + + + + + + + + + @@ -127,9 +160,10 @@ - + + + @@ -154,13 +188,20 @@ - - - OpenStreetMap - OpenCycleMap - OCM Transport - OCM Landscape - MapQuest OSM + + + OpenStreetMap + OpenCycleMap + Landscape + Outdoors + Transport + Transport Dark + MapQuest Open + diff --git a/SampleApps/SilverlightApplication/MainPage.xaml.cs b/SampleApps/SilverlightApplication/MainPage.xaml.cs index 7e80530e..43a62147 100644 --- a/SampleApps/SilverlightApplication/MainPage.xaml.cs +++ b/SampleApps/SilverlightApplication/MainPage.xaml.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Windows; using System.Windows.Controls; +using System.Windows.Documents; using System.Windows.Input; using MapControl; @@ -9,9 +10,15 @@ namespace SilverlightApplication { public partial class MainPage : UserControl { + private TileLayerCollection tileLayers; + public MainPage() { + //BingMapsTileLayer.ApiKey = ... + InitializeComponent(); + + tileLayers = (TileLayerCollection)Resources["TileLayers"]; tileLayerComboBox.SelectedIndex = 0; } @@ -48,19 +55,29 @@ namespace SilverlightApplication private void TileLayerSelectionChanged(object sender, SelectionChangedEventArgs e) { - var comboBox = (ComboBox)sender; - var tileLayers = (TileLayerCollection)Resources["TileLayers"]; - map.TileLayer = tileLayers[(string)comboBox.SelectedItem]; + var selectedItem = (ComboBoxItem)tileLayerComboBox.SelectedItem; + + map.TileLayer = tileLayers[(string)selectedItem.Tag]; + + var paragraph = new Paragraph(); + + foreach (var inline in map.TileLayer.DescriptionInlines) + { + paragraph.Inlines.Add(inline); + } + + mapLegend.Blocks.Clear(); + mapLegend.Blocks.Add(paragraph); } private void SeamarksChecked(object sender, RoutedEventArgs e) { - map.TileLayers.Add((TileLayer)((TileLayerCollection)Resources["TileLayers"])["Seamarks"]); + map.TileLayers.Add(tileLayers["Seamarks"]); } private void SeamarksUnchecked(object sender, RoutedEventArgs e) { - map.TileLayers.Remove((TileLayer)((TileLayerCollection)Resources["TileLayers"])["Seamarks"]); + map.TileLayers.Remove(tileLayers["Seamarks"]); } } } diff --git a/SampleApps/SilverlightApplication/Properties/AssemblyInfo.cs b/SampleApps/SilverlightApplication/Properties/AssemblyInfo.cs index aa9dedf7..490b51d4 100644 --- a/SampleApps/SilverlightApplication/Properties/AssemblyInfo.cs +++ b/SampleApps/SilverlightApplication/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.2.0")] -[assembly: AssemblyFileVersion("2.2.0")] +[assembly: AssemblyVersion("2.3.0")] +[assembly: AssemblyFileVersion("2.3.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/StoreApplication/MainPage.xaml b/SampleApps/StoreApplication/MainPage.xaml index c99f6c27..f30eda6d 100644 --- a/SampleApps/StoreApplication/MainPage.xaml +++ b/SampleApps/StoreApplication/MainPage.xaml @@ -7,30 +7,58 @@ xmlns:local="using:StoreApplication"> - + + - - + + - - + + - - + + - + + + + + + + - - + + + + + + @@ -162,10 +190,10 @@ - - + + + @@ -188,16 +216,20 @@ - - - OpenStreetMap - OpenCycleMap - OCM Transport - OCM Landscape - MapQuest OSM - + + + OpenStreetMap + OpenCycleMap + Landscape + Outdoors + Transport + Transport Dark + MapQuest Open + diff --git a/SampleApps/StoreApplication/MainPage.xaml.cs b/SampleApps/StoreApplication/MainPage.xaml.cs index 5b628107..c8f2ccfe 100644 --- a/SampleApps/StoreApplication/MainPage.xaml.cs +++ b/SampleApps/StoreApplication/MainPage.xaml.cs @@ -1,5 +1,4 @@ using MapControl; -using Windows.Storage; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; @@ -8,14 +7,17 @@ namespace StoreApplication { public sealed partial class MainPage : Page { + private TileLayerCollection tileLayers; + public MainPage() { TileImageLoader.Cache = new ImageFileCache(); + //BingMapsTileLayer.ApiKey = ... this.InitializeComponent(); - var tileLayers = (TileLayerCollection)Resources["TileLayers"]; - map.TileLayer = tileLayers[0]; + tileLayers = (TileLayerCollection)Resources["TileLayers"]; + tileLayerComboBox.SelectedIndex = 0; } private void ImageOpacitySliderValueChanged(object sender, RangeBaseValueChangedEventArgs e) @@ -26,28 +28,28 @@ namespace StoreApplication } } - private void TileLayerComboBoxLoaded(object sender, RoutedEventArgs e) - { - ((ComboBox)sender).SelectedIndex = 0; - } - private void TileLayerSelectionChanged(object sender, SelectionChangedEventArgs e) { - var selectedValue = (string)((ComboBox)sender).SelectedValue; - var tileLayers = (TileLayerCollection)Resources["TileLayers"]; - map.TileLayer = tileLayers[selectedValue]; + var selectedItem = (ComboBoxItem)tileLayerComboBox.SelectedItem; + + map.TileLayer = tileLayers[(string)selectedItem.Tag]; + + mapLegend.Inlines.Clear(); + + foreach (var inline in map.TileLayer.DescriptionInlines) + { + mapLegend.Inlines.Add(inline); + } } private void SeamarksChecked(object sender, RoutedEventArgs e) { - var tileLayers = (TileLayerCollection)Resources["TileLayers"]; - map.TileLayers.Add((TileLayer)tileLayers["Seamarks"]); + map.TileLayers.Add(tileLayers["Seamarks"]); } private void SeamarksUnchecked(object sender, RoutedEventArgs e) { - var tileLayers = (TileLayerCollection)Resources["TileLayers"]; - map.TileLayers.Remove((TileLayer)tileLayers["Seamarks"]); + map.TileLayers.Remove(tileLayers["Seamarks"]); } } } diff --git a/SampleApps/StoreApplication/Properties/AssemblyInfo.cs b/SampleApps/StoreApplication/Properties/AssemblyInfo.cs index 0c757a8d..3a136e6e 100644 --- a/SampleApps/StoreApplication/Properties/AssemblyInfo.cs +++ b/SampleApps/StoreApplication/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.2.0")] -[assembly: AssemblyFileVersion("2.2.0")] +[assembly: AssemblyVersion("2.3.0")] +[assembly: AssemblyFileVersion("2.3.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/WpfApplication/MainWindow.xaml b/SampleApps/WpfApplication/MainWindow.xaml index 98a9188d..44499226 100644 --- a/SampleApps/WpfApplication/MainWindow.xaml +++ b/SampleApps/WpfApplication/MainWindow.xaml @@ -9,48 +9,75 @@ Stylus.IsPressAndHoldEnabled="False"> - - - - - + + + + + + + + + - - + + + + + + - - - - - - + - - + - - @@ -66,6 +93,7 @@