diff --git a/MBTiles/Shared/MBTileData.cs b/MBTiles/Shared/MBTileData.cs index a2fcacc6..63764f77 100644 --- a/MBTiles/Shared/MBTileData.cs +++ b/MBTiles/Shared/MBTileData.cs @@ -13,7 +13,7 @@ using SQLiteConnection = Microsoft.Data.Sqlite.SqliteConnection; using System.Data.SQLite; #endif -namespace MapControl +namespace MapControl.MBTiles { public class MBTileData : IDisposable { diff --git a/MBTiles/Shared/MBTileLayer.cs b/MBTiles/Shared/MBTileLayer.cs index 7d775a0c..206d7007 100644 --- a/MBTiles/Shared/MBTileLayer.cs +++ b/MBTiles/Shared/MBTileLayer.cs @@ -9,7 +9,7 @@ using Windows.UI.Xaml; using System.Windows; #endif -namespace MapControl +namespace MapControl.MBTiles { public class MBTileLayer : MapTileLayer { diff --git a/MBTiles/Shared/MBTileSource.cs b/MBTiles/Shared/MBTileSource.cs index d0446710..de2bae6a 100644 --- a/MBTiles/Shared/MBTileSource.cs +++ b/MBTiles/Shared/MBTileSource.cs @@ -10,7 +10,7 @@ using Windows.UI.Xaml.Media; using System.Windows.Media; #endif -namespace MapControl +namespace MapControl.MBTiles { public class MBTileSource : TileSource, IDisposable { diff --git a/MBTiles/UWP/MBTiles.UWP.csproj b/MBTiles/UWP/MBTiles.UWP.csproj index 46ec7d8f..d8d3ec26 100644 --- a/MBTiles/UWP/MBTiles.UWP.csproj +++ b/MBTiles/UWP/MBTiles.UWP.csproj @@ -7,7 +7,7 @@ {DCC111E9-EC8B-492A-A09D-DF390D83AE8D} Library Properties - MapControl + MapControl.MBTiles MBTiles.UWP en-US UAP @@ -54,7 +54,7 @@ - 2.1.0 + 2.2.0 6.2.2 diff --git a/MBTiles/WPF/MBTiles.WPF.csproj b/MBTiles/WPF/MBTiles.WPF.csproj index 1e5e58b3..ebb3fc56 100644 --- a/MBTiles/WPF/MBTiles.WPF.csproj +++ b/MBTiles/WPF/MBTiles.WPF.csproj @@ -7,7 +7,7 @@ {38B18AB6-6E70-4696-8FB4-E8C8E12BF50C} Library Properties - MapControl + MapControl.MBTiles MBTiles.WPF v4.7.2 512 @@ -32,6 +32,12 @@ prompt 4 + + true + + + ..\..\MapControl.snk + @@ -63,6 +69,9 @@ + + MapControl.snk + diff --git a/MapControl/Shared/ImageLoader.cs b/MapControl/Shared/ImageLoader.cs index 40e5dd6c..34b22411 100644 --- a/MapControl/Shared/ImageLoader.cs +++ b/MapControl/Shared/ImageLoader.cs @@ -7,11 +7,9 @@ using System.Diagnostics; using System.Threading.Tasks; #if WINDOWS_UWP using Windows.Web.Http; -using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Imaging; #else using System.Net.Http; -using System.Windows.Media; using System.Windows.Media.Imaging; #endif @@ -24,23 +22,23 @@ namespace MapControl /// public static HttpClient HttpClient { get; set; } = new HttpClient(); - public static async Task LoadImageAsync(Uri uri) + public static async Task LoadImageAsync(Uri uri) { - ImageSource imageSource = null; + BitmapSource image = null; try { if (!uri.IsAbsoluteUri || uri.Scheme == "file") { - imageSource = await LoadLocalImageAsync(uri); + image = await LoadLocalImageAsync(uri); } else if (uri.Scheme == "http" || uri.Scheme == "https") { - imageSource = await LoadHttpImageAsync(uri); + image = await LoadHttpImageAsync(uri); } else { - imageSource = new BitmapImage(uri); + image = new BitmapImage(uri); } } catch (Exception ex) @@ -48,12 +46,12 @@ namespace MapControl Debug.WriteLine("ImageLoader: {0}: {1}", uri, ex.Message); } - return imageSource; + return image; } - private static async Task LoadHttpImageAsync(Uri uri) + private static async Task LoadHttpImageAsync(Uri uri) { - ImageSource imageSource = null; + BitmapSource image = null; using (var response = await HttpClient.GetAsync(uri)) { @@ -63,11 +61,11 @@ namespace MapControl } else if (IsTileAvailable(response.Headers)) { - imageSource = await LoadImageAsync(response.Content); + image = await LoadImageAsync(response.Content); } } - return imageSource; + return image; } } } \ No newline at end of file diff --git a/MapControl/UWP/ImageLoader.UWP.cs b/MapControl/UWP/ImageLoader.UWP.cs index f39651ef..8becd5ac 100644 --- a/MapControl/UWP/ImageLoader.UWP.cs +++ b/MapControl/UWP/ImageLoader.UWP.cs @@ -9,7 +9,6 @@ using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; using Windows.Storage; using Windows.Storage.Streams; -using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Imaging; using Windows.Web.Http; using Windows.Web.Http.Headers; @@ -18,14 +17,14 @@ namespace MapControl { public static partial class ImageLoader { - public static async Task LoadImageAsync(IRandomAccessStream stream) + public static async Task LoadImageAsync(IRandomAccessStream stream) { - var bitmapImage = new BitmapImage(); - await bitmapImage.SetSourceAsync(stream); - return bitmapImage; + var image = new BitmapImage(); + await image.SetSourceAsync(stream); + return image; } - public static async Task LoadImageAsync(byte[] buffer) + public static async Task LoadImageAsync(byte[] buffer) { using (var stream = new InMemoryRandomAccessStream()) { @@ -35,7 +34,7 @@ namespace MapControl } } - private static async Task LoadImageAsync(IHttpContent content) + private static async Task LoadImageAsync(IHttpContent content) { using (var stream = new InMemoryRandomAccessStream()) { @@ -45,9 +44,9 @@ namespace MapControl } } - private static async Task LoadLocalImageAsync(Uri uri) + private static async Task LoadLocalImageAsync(Uri uri) { - ImageSource imageSource = null; + BitmapSource image = null; var path = uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString; if (File.Exists(path)) @@ -56,11 +55,11 @@ namespace MapControl using (var stream = await file.OpenReadAsync()) { - imageSource = await LoadImageAsync(stream); + image = await LoadImageAsync(stream); } } - return imageSource; + return image; } internal static async Task> LoadHttpBufferAsync(Uri uri) diff --git a/MapControl/WPF/ImageLoader.WPF.cs b/MapControl/WPF/ImageLoader.WPF.cs index 42d3a3d5..a89869bc 100644 --- a/MapControl/WPF/ImageLoader.WPF.cs +++ b/MapControl/WPF/ImageLoader.WPF.cs @@ -10,14 +10,13 @@ using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; -using System.Windows.Media; using System.Windows.Media.Imaging; namespace MapControl { public static partial class ImageLoader { - public static ImageSource LoadImage(Stream stream) + public static BitmapSource LoadImage(Stream stream) { var bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); @@ -28,12 +27,12 @@ namespace MapControl return bitmapImage; } - public static Task LoadImageAsync(Stream stream) + public static Task LoadImageAsync(Stream stream) { return Task.Run(() => LoadImage(stream)); } - public static ImageSource LoadImage(byte[] buffer) + public static BitmapSource LoadImage(byte[] buffer) { using (var stream = new MemoryStream(buffer)) { @@ -41,12 +40,12 @@ namespace MapControl } } - public static Task LoadImageAsync(byte[] buffer) + public static Task LoadImageAsync(byte[] buffer) { return Task.Run(() => LoadImage(buffer)); } - private static async Task LoadImageAsync(HttpContent content) + private static async Task LoadImageAsync(HttpContent content) { using (var stream = new MemoryStream()) { @@ -56,23 +55,23 @@ namespace MapControl } } - private static ImageSource LoadLocalImage(Uri uri) + private static BitmapSource LoadLocalImage(Uri uri) { - ImageSource imageSource = null; + BitmapSource image = null; var path = uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString; if (File.Exists(path)) { using (var stream = File.OpenRead(path)) { - imageSource = LoadImage(stream); + image = LoadImage(stream); } } - return imageSource; + return image; } - private static Task LoadLocalImageAsync(Uri uri) + private static Task LoadLocalImageAsync(Uri uri) { return Task.Run(() => LoadLocalImage(uri)); } diff --git a/MapControlExtended.sln b/MapControlExtended.sln index 15315186..2caf67e3 100644 --- a/MapControlExtended.sln +++ b/MapControlExtended.sln @@ -33,6 +33,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfApplication", "SampleApp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapProjections.UWP", "MapProjections\UWP\MapProjections.UWP.csproj", "{9EE69591-5EDC-45E3-893E-2F9A4B82D538}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MapImages", "MapImages", "{2FDC8B91-FB95-4C57-8183-63587FBFE180}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapImages.WPF", "MapImages\WPF\MapImages.WPF.csproj", "{52042F63-563A-45BB-9A08-A8635AAAB84C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapImages.UWP", "MapImages\UWP\MapImages.UWP.csproj", "{BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -213,6 +219,38 @@ Global {9EE69591-5EDC-45E3-893E-2F9A4B82D538}.Release|x64.Build.0 = Release|Any CPU {9EE69591-5EDC-45E3-893E-2F9A4B82D538}.Release|x86.ActiveCfg = Release|Any CPU {9EE69591-5EDC-45E3-893E-2F9A4B82D538}.Release|x86.Build.0 = Release|Any CPU + {52042F63-563A-45BB-9A08-A8635AAAB84C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52042F63-563A-45BB-9A08-A8635AAAB84C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52042F63-563A-45BB-9A08-A8635AAAB84C}.Debug|ARM.ActiveCfg = Debug|Any CPU + {52042F63-563A-45BB-9A08-A8635AAAB84C}.Debug|ARM.Build.0 = Debug|Any CPU + {52042F63-563A-45BB-9A08-A8635AAAB84C}.Debug|x64.ActiveCfg = Debug|Any CPU + {52042F63-563A-45BB-9A08-A8635AAAB84C}.Debug|x64.Build.0 = Debug|Any CPU + {52042F63-563A-45BB-9A08-A8635AAAB84C}.Debug|x86.ActiveCfg = Debug|Any CPU + {52042F63-563A-45BB-9A08-A8635AAAB84C}.Debug|x86.Build.0 = Debug|Any CPU + {52042F63-563A-45BB-9A08-A8635AAAB84C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52042F63-563A-45BB-9A08-A8635AAAB84C}.Release|Any CPU.Build.0 = Release|Any CPU + {52042F63-563A-45BB-9A08-A8635AAAB84C}.Release|ARM.ActiveCfg = Release|Any CPU + {52042F63-563A-45BB-9A08-A8635AAAB84C}.Release|ARM.Build.0 = Release|Any CPU + {52042F63-563A-45BB-9A08-A8635AAAB84C}.Release|x64.ActiveCfg = Release|Any CPU + {52042F63-563A-45BB-9A08-A8635AAAB84C}.Release|x64.Build.0 = Release|Any CPU + {52042F63-563A-45BB-9A08-A8635AAAB84C}.Release|x86.ActiveCfg = Release|Any CPU + {52042F63-563A-45BB-9A08-A8635AAAB84C}.Release|x86.Build.0 = Release|Any CPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Debug|ARM.ActiveCfg = Debug|Any CPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Debug|ARM.Build.0 = Debug|Any CPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Debug|x64.ActiveCfg = Debug|Any CPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Debug|x64.Build.0 = Debug|Any CPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Debug|x86.ActiveCfg = Debug|Any CPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Debug|x86.Build.0 = Debug|Any CPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Release|Any CPU.Build.0 = Release|Any CPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Release|ARM.ActiveCfg = Release|Any CPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Release|ARM.Build.0 = Release|Any CPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Release|x64.ActiveCfg = Release|Any CPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Release|x64.Build.0 = Release|Any CPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Release|x86.ActiveCfg = Release|Any CPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -228,6 +266,8 @@ Global {426C21C0-5F14-491F-BCD1-6D2993510420} = {7BC11E28-8D3B-4C5B-AC08-AB249CC95F6D} {F92DA93D-75DB-4308-A5F9-6B4C3908A675} = {8F2103C2-78AF-4810-8FB9-67572F50C8FC} {9EE69591-5EDC-45E3-893E-2F9A4B82D538} = {7BC11E28-8D3B-4C5B-AC08-AB249CC95F6D} + {52042F63-563A-45BB-9A08-A8635AAAB84C} = {2FDC8B91-FB95-4C57-8183-63587FBFE180} + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372} = {2FDC8B91-FB95-4C57-8183-63587FBFE180} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {458346DD-B23F-4FDC-8F9D-A10F1882A4DB} diff --git a/MapImages/Shared/GroundOverlayPanel.cs b/MapImages/Shared/GroundOverlayPanel.cs new file mode 100644 index 00000000..7a1b3f13 --- /dev/null +++ b/MapImages/Shared/GroundOverlayPanel.cs @@ -0,0 +1,205 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2018 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Xml; +#if WINDOWS_UWP +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; +#else +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +#endif + +namespace MapControl.Images +{ + public partial class GroundOverlayPanel : MapPanel + { + class LatLonBox : BoundingBox + { + public LatLonBox(double south, double west, double north, double east, double rotation) + : base(south, west, north, east) + { + Rotation = rotation; + } + + public double Rotation { get; } + } + + class ImageOverlay + { + public ImageOverlay(LatLonBox latLonBox, string imagePath, int zIndex) + { + LatLonBox = latLonBox; + ImagePath = imagePath; + ZIndex = zIndex; + } + + public LatLonBox LatLonBox { get; } + public string ImagePath { get; } + public int ZIndex { get; } + public ImageSource ImageSource { get; set; } + } + + public static readonly DependencyProperty KmlFileProperty = DependencyProperty.Register( + nameof(KmlFile), typeof(string), typeof(GroundOverlayPanel), + new PropertyMetadata(null, async (o, e) => await ((GroundOverlayPanel)o).KmlFilePropertyChanged((string)e.NewValue))); + + public string KmlFile + { + get { return (string)GetValue(KmlFileProperty); } + set { SetValue(KmlFileProperty, value); } + } + + private async Task KmlFilePropertyChanged(string path) + { + IEnumerable imageOverlays = null; + + if (!string.IsNullOrEmpty(path)) + { + try + { + var ext = Path.GetExtension(path).ToLower(); + if (ext == ".kmz") + { + imageOverlays = await ReadGroundOverlaysFromArchive(path); + } + else if (ext == ".kml") + { + imageOverlays = await ReadGroundOverlaysFromFile(path); + } + } + catch (Exception ex) + { + Debug.WriteLine("GroundOverlayPanel: {0}: {1}", path, ex.Message); + } + } + + Children.Clear(); + + if (imageOverlays != null) + { + AddImageOverlays(imageOverlays); + } + } + + private void AddImageOverlays(IEnumerable imageOverlays) + { + foreach (var imageOverlay in imageOverlays.Where(i => i.ImageSource != null)) + { + FrameworkElement overlay = new Image + { + Source = imageOverlay.ImageSource, + Stretch = Stretch.Fill + }; + + if (imageOverlay.LatLonBox.Rotation != 0d) + { + overlay.RenderTransform = new RotateTransform { Angle = -imageOverlay.LatLonBox.Rotation }; + overlay.RenderTransformOrigin = new Point(0.5, 0.5); + + // additional Panel for map rotation, see MapPanel.ArrangeElementWithBoundingBox + var panel = new Grid { Background = null }; + panel.Children.Add(overlay); + overlay = panel; + } + + SetBoundingBox(overlay, imageOverlay.LatLonBox); + Canvas.SetZIndex(overlay, imageOverlay.ZIndex); + Children.Add(overlay); + } + } + + private IEnumerable ReadGroundOverlays(XmlDocument kmlDocument) + { + foreach (XmlElement groundOverlayElement in kmlDocument.GetElementsByTagName("GroundOverlay")) + { + LatLonBox latLonBox = null; + string imagePath = null; + int zIndex = 0; + + foreach (var childElement in groundOverlayElement.ChildNodes.OfType()) + { + switch (childElement.LocalName) + { + case "LatLonBox": + latLonBox = ReadLatLonBox(childElement); + break; + case "Icon": + imagePath = ReadImagePath(childElement); + break; + case "drawOrder": + int.TryParse(childElement.InnerText.Trim(), out zIndex); + break; + } + } + + if (latLonBox != null && imagePath != null) + { + yield return new ImageOverlay(latLonBox, imagePath, zIndex); + } + } + } + + private string ReadImagePath(XmlElement element) + { + string href = null; + + foreach (var childElement in element.ChildNodes.OfType()) + { + switch (childElement.LocalName) + { + case "href": + href = childElement.InnerText.Trim(); + break; + } + } + + return href; + } + + private LatLonBox ReadLatLonBox(XmlElement element) + { + double north = double.NaN; + double south = double.NaN; + double east = double.NaN; + double west = double.NaN; + double rotation = 0d; + + foreach (var childElement in element.ChildNodes.OfType()) + { + switch (childElement.LocalName) + { + case "north": + double.TryParse(childElement.InnerText.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out north); + break; + case "south": + double.TryParse(childElement.InnerText.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out south); + break; + case "east": + double.TryParse(childElement.InnerText.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out east); + break; + case "west": + double.TryParse(childElement.InnerText.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out west); + break; + case "rotation": + double.TryParse(childElement.InnerText.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out rotation); + break; + } + } + + return !double.IsNaN(north) && !double.IsNaN(south) && !double.IsNaN(east) && !double.IsNaN(west) + ? new LatLonBox(south, west, north, east, rotation) + : null; + } + } +} diff --git a/MapImages/Shared/WorldFile.cs b/MapImages/Shared/WorldFile.cs new file mode 100644 index 00000000..59492305 --- /dev/null +++ b/MapImages/Shared/WorldFile.cs @@ -0,0 +1,133 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2018 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.IO; +using System.Threading.Tasks; +using MapControl.Projections; +#if WINDOWS_UWP +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Media.Imaging; +#else +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; +#endif + +namespace MapControl.Images +{ + public static class WorldFile + { + public static readonly DependencyProperty ImagePathProperty = DependencyProperty.RegisterAttached( + "ImagePath", typeof(string), typeof(WorldFile), + new PropertyMetadata(null, async (o, e) => await SetWorldImageAsync((Image)o, (string)e.NewValue))); + + public static string GetImagePath(this Image image) + { + return (string)image.GetValue(ImagePathProperty); + } + + public static void SetImagePath(this Image image, string imagePath) + { + image.SetValue(ImagePathProperty, imagePath); + } + + public static void SetWorldImage(this Image image, BitmapSource bitmapSource, WorldFileParameters parameters, MapProjection projection = null) + { + image.Source = bitmapSource; + image.Stretch = Stretch.Fill; + + MapPanel.SetBoundingBox(image, parameters.GetBoundingBox(bitmapSource.PixelWidth, bitmapSource.PixelHeight, projection)); + } + + private static async Task SetWorldImageAsync(Image image, string imagePath) + { + var ext = Path.GetExtension(imagePath); + if (ext.Length < 4) + { + throw new ArgumentException("Invalid image file path extension, must have at least three characters."); + } + + BitmapSource bitmap; + using (var stream = File.OpenRead(imagePath)) + { +#if WINDOWS_UWP + bitmap = await ImageLoader.LoadImageAsync(stream.AsRandomAccessStream()); +#else + bitmap = await ImageLoader.LoadImageAsync(stream); +#endif + } + + var dir = Path.GetDirectoryName(imagePath); + var file = Path.GetFileNameWithoutExtension(imagePath); + var worldFilePath = Path.Combine(dir, file + ext.Remove(2, 1) + "w"); + var projFilePath = Path.Combine(dir, file + ".prj"); + + var parameters = new WorldFileParameters(worldFilePath); + MapProjection projection = null; + + if (File.Exists(projFilePath)) + { + projection = new GeoApiProjection { WKT = File.ReadAllText(projFilePath) }; + } + + SetWorldImage(image, bitmap, parameters, projection); + } + + public static FrameworkElement CreateWorldImage(BitmapSource bitmap, WorldFileParameters parameters) + { + if (parameters.XScale == 0d || parameters.YScale == 0d) + { + throw new ArgumentException("Invalid WorldFileParameters, XScale and YScale must be non-zero."); + } + + var pixelWidth = parameters.XScale; + var pixelHeight = parameters.YScale; + var rotation = 0d; + + if (parameters.YSkew != 0 || parameters.XSkew != 0) + { + pixelWidth = Math.Sqrt(parameters.XScale * parameters.XScale + parameters.YSkew * parameters.YSkew); + pixelHeight = Math.Sqrt(parameters.YScale * parameters.YScale + parameters.XSkew * parameters.XSkew); + + var xAxisRotation = Math.Atan2(parameters.YSkew, parameters.XScale) / Math.PI * 180d; + var yAxisRotation = Math.Atan2(parameters.XSkew, -parameters.YScale) / Math.PI * 180d; + rotation = 0.5 * (xAxisRotation + yAxisRotation); + } + + var x1 = parameters.XOrigin; + var x2 = parameters.XOrigin + pixelWidth * bitmap.PixelWidth; + var y1 = parameters.YOrigin; + var y2 = parameters.YOrigin + pixelHeight * bitmap.PixelHeight; + + var bbox = new BoundingBox + { + West = Math.Min(x1, x2), + East = Math.Max(x1, x2), + South = Math.Min(y1, y2), + North = Math.Max(y1, y2) + }; + + FrameworkElement image = new Image + { + Source = bitmap, + Stretch = Stretch.Fill + }; + + if (rotation != 0d) + { + image.RenderTransform = new RotateTransform { Angle = rotation }; + var panel = new Grid(); + panel.Children.Add(image); + image = panel; + } + + MapPanel.SetBoundingBox(image, bbox); + return image; + } + } +} diff --git a/MapImages/Shared/WorldFileParameters.cs b/MapImages/Shared/WorldFileParameters.cs new file mode 100644 index 00000000..267136d4 --- /dev/null +++ b/MapImages/Shared/WorldFileParameters.cs @@ -0,0 +1,94 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2018 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Globalization; +using System.IO; +using System.Linq; +#if WINDOWS_UWP +using Windows.Foundation; +#else +using System.Windows; +#endif + +namespace MapControl.Images +{ + public class WorldFileParameters + { + public WorldFileParameters() + { + } + + public WorldFileParameters(string path) + { + if (!File.Exists(path)) + { + throw new ArgumentException("World file \"" + path + "\"not found."); + } + + var lines = File.ReadLines(path).Take(6).ToList(); + + if (lines.Count != 6) + { + throw new ArgumentException("Invalid number of parameters in world file \"" + path + "\"."); + } + + double xscale, yskew, xskew, yscale, xorigin, yorigin; + + if (!double.TryParse(lines[0], NumberStyles.Float, CultureInfo.InvariantCulture, out xscale) || + !double.TryParse(lines[1], NumberStyles.Float, CultureInfo.InvariantCulture, out yskew) || + !double.TryParse(lines[2], NumberStyles.Float, CultureInfo.InvariantCulture, out xskew) || + !double.TryParse(lines[3], NumberStyles.Float, CultureInfo.InvariantCulture, out yscale) || + !double.TryParse(lines[4], NumberStyles.Float, CultureInfo.InvariantCulture, out xorigin) || + !double.TryParse(lines[5], NumberStyles.Float, CultureInfo.InvariantCulture, out yorigin)) + { + throw new ArgumentException("Failed parsing parameters in world file \"" + path + "\"."); + } + + XScale = xscale; + YSkew = yskew; + XSkew = xskew; + YScale = yscale; + XOrigin = xorigin; + YOrigin = yorigin; + } + + public double XScale { get; set; } + public double YSkew { get; set; } + public double XSkew { get; set; } + public double YScale { get; set; } + public double XOrigin { get; set; } + public double YOrigin { get; set; } + + public BoundingBox GetBoundingBox(double imageWidth, double imageHeight, MapProjection projection = null) + { + if (XScale == 0d || YScale == 0d) + { + throw new ArgumentException("Invalid WorldFileParameters, XScale and YScale must be non-zero."); + } + + if (YSkew != 0d || XSkew != 0d) + { + throw new ArgumentException("Invalid WorldFileParameters, YSkew and XSkew must be zero."); + } + + var p1 = new Point(XOrigin, YOrigin); + var p2 = new Point(XOrigin + XScale * imageWidth, YOrigin + YScale * imageHeight); + var rect = new Rect(p1, p2); + + if (projection != null) + { + return projection.RectToBoundingBox(rect); + } + + return new BoundingBox + { + West = rect.X, + East = rect.X + rect.Width, + South = rect.Y, + North = rect.Y + rect.Height + }; + } + } +} diff --git a/MapImages/Shared/ZoomLevelToOpacityConverter.cs b/MapImages/Shared/ZoomLevelToOpacityConverter.cs new file mode 100644 index 00000000..2fb38717 --- /dev/null +++ b/MapImages/Shared/ZoomLevelToOpacityConverter.cs @@ -0,0 +1,62 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2018 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Globalization; +#if WINDOWS_UWP +using Windows.UI.Xaml; +using Windows.UI.Xaml.Data; +#else +using System.Windows; +using System.Windows.Data; +#endif + +namespace MapControl.Images +{ + public class ZoomLevelToOpacityConverter : IValueConverter + { + public double MinZoomLevel { get; set; } = 0d; + public double MaxZoomLevel { get; set; } = 22d; + public double FadeZoomRange { get; set; } = 1d; + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (!(value is double)) + { + return DependencyProperty.UnsetValue; + } + + var zoomLevel = (double)value; + var opacity = 0d; + + if (zoomLevel > MinZoomLevel && zoomLevel < MaxZoomLevel) + { + opacity = 1d; + + if (FadeZoomRange > 0d) + { + opacity = Math.Min(opacity, (zoomLevel - MinZoomLevel) / FadeZoomRange); + opacity = Math.Min(opacity, (MaxZoomLevel - zoomLevel) / FadeZoomRange); + } + } + + return opacity; + } + + public object Convert(object value, Type targetType, object parameter, string language) + { + return Convert(value, targetType, parameter, CultureInfo.InvariantCulture); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotSupportedException(); + } + } +} diff --git a/MapImages/UWP/GroundOverlayPanel.UWP.cs b/MapImages/UWP/GroundOverlayPanel.UWP.cs new file mode 100644 index 00000000..bce993ae --- /dev/null +++ b/MapImages/UWP/GroundOverlayPanel.UWP.cs @@ -0,0 +1,92 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2018 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; +using Windows.Storage; + +namespace MapControl.Images +{ + public partial class GroundOverlayPanel + { + private async Task> ReadGroundOverlaysFromFile(string docFile) + { + if (!Path.IsPathRooted(docFile)) + { + docFile = Path.Combine(Directory.GetCurrentDirectory(), docFile); + } + + var docUri = new Uri(docFile); + + var imageOverlays = await Task.Run(async () => + { + var file = await StorageFile.GetFileFromPathAsync(docFile); + var kmlDocument = new XmlDocument(); + + using (var stream = await file.OpenReadAsync()) + { + kmlDocument.Load(stream.AsStreamForRead()); + } + + return ReadGroundOverlays(kmlDocument).ToList(); + }); + + foreach (var imageOverlay in imageOverlays) + { + imageOverlay.ImageSource = await ImageLoader.LoadImageAsync(new Uri(docUri, imageOverlay.ImagePath)); + } + + return imageOverlays; + } + + private async Task> 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); + } + + var imageOverlays = await Task.Run(() => + { + var kmlDocument = new XmlDocument(); + + using (var docStream = docEntry.Open()) + { + kmlDocument.Load(docStream); + } + + return 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.Position = 0; + imageOverlay.ImageSource = await ImageLoader.LoadImageAsync(memoryStream.AsRandomAccessStream()); + } + } + } + + return imageOverlays; + } + } + } +} diff --git a/MapImages/UWP/MapImages.UWP.csproj b/MapImages/UWP/MapImages.UWP.csproj new file mode 100644 index 00000000..56ffffd9 --- /dev/null +++ b/MapImages/UWP/MapImages.UWP.csproj @@ -0,0 +1,96 @@ + + + + + Debug + AnyCPU + {BE08B7BC-8C89-4837-BCE7-EDDDABEAB372} + Library + Properties + MapControl.Images + MapImages.UWP + en-US + UAP + 10.0.10586.0 + 10.0.10586.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + prompt + 4 + + + PackageReference + + + + GroundOverlayPanel.cs + + + WorldFile.cs + + + WorldFileParameters.cs + + + ZoomLevelToOpacityConverter.cs + + + + + + + + 6.2.2 + + + + + {9545f73c-9c35-4cf6-baae-19a0baebd344} + MapControl.UWP + + + {9ee69591-5edc-45e3-893e-2f9a4b82d538} + MapProjections.UWP + + + + + MapControl.snk + + + + 14.0 + + + true + + + ..\..\MapControl.snk + + + + \ No newline at end of file diff --git a/MapImages/UWP/Properties/AssemblyInfo.cs b/MapImages/UWP/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..51a08645 --- /dev/null +++ b/MapImages/UWP/Properties/AssemblyInfo.cs @@ -0,0 +1,14 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("XAML Map Control Image Support (UWP)")] +[assembly: AssemblyDescription("Image Support Library for XAML Map Control")] +[assembly: AssemblyProduct("XAML Map Control")] +[assembly: AssemblyCompany("Clemens Fischer")] +[assembly: AssemblyCopyright("© 2018 Clemens Fischer")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyVersion("4.12.0")] +[assembly: AssemblyFileVersion("4.12.0")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] diff --git a/MapImages/UWP/Properties/MapImages.UWP.rd.xml b/MapImages/UWP/Properties/MapImages.UWP.rd.xml new file mode 100644 index 00000000..bebda206 --- /dev/null +++ b/MapImages/UWP/Properties/MapImages.UWP.rd.xml @@ -0,0 +1,33 @@ + + + + + + + + + diff --git a/MapImages/WPF/GroundOverlayPanel.WPF.cs b/MapImages/WPF/GroundOverlayPanel.WPF.cs new file mode 100644 index 00000000..b4dd9af5 --- /dev/null +++ b/MapImages/WPF/GroundOverlayPanel.WPF.cs @@ -0,0 +1,86 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2018 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; + +namespace MapControl.Images +{ + public partial class GroundOverlayPanel + { + private async Task> ReadGroundOverlaysFromFile(string docFile) + { + if (!Path.IsPathRooted(docFile)) + { + docFile = Path.Combine(Directory.GetCurrentDirectory(), docFile); + } + + var docUri = new Uri(docFile); + + var imageOverlays = await Task.Run(() => + { + var kmlDocument = new XmlDocument(); + kmlDocument.Load(docUri.ToString()); + + return ReadGroundOverlays(kmlDocument).ToList(); + }); + + foreach (var imageOverlay in imageOverlays) + { + imageOverlay.ImageSource = await ImageLoader.LoadImageAsync(new Uri(docUri, imageOverlay.ImagePath)); + } + + return imageOverlays; + } + + private Task> ReadGroundOverlaysFromArchive(string archiveFile) + { + return Task.Run(() => + { + 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 = 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()) + { + zipStream.CopyTo(memoryStream); + memoryStream.Position = 0; + imageOverlay.ImageSource = ImageLoader.LoadImage(memoryStream); + } + } + } + + return imageOverlays; + } + }); + } + } +} diff --git a/MapImages/WPF/MapImages.WPF.csproj b/MapImages/WPF/MapImages.WPF.csproj new file mode 100644 index 00000000..7b34f18e --- /dev/null +++ b/MapImages/WPF/MapImages.WPF.csproj @@ -0,0 +1,81 @@ + + + + + Debug + AnyCPU + {52042F63-563A-45BB-9A08-A8635AAAB84C} + Library + Properties + MapControl.Images + MapImages.WPF + v4.7.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + none + true + bin\Release\ + TRACE + prompt + 4 + + + true + + + ..\..\MapControl.snk + + + + + + + + + + + + + + GroundOverlayPanel.cs + + + WorldFile.cs + + + WorldFileParameters.cs + + + ZoomLevelToOpacityConverter.cs + + + + + + + {a204a102-c745-4d65-aec8-7b96faedef2d} + MapControl.WPF + + + {426c21c0-5f14-491f-bcd1-6d2993510420} + MapProjections.WPF + + + + + MapControl.snk + + + + \ No newline at end of file diff --git a/MapImages/WPF/Properties/AssemblyInfo.cs b/MapImages/WPF/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..eea202e6 --- /dev/null +++ b/MapImages/WPF/Properties/AssemblyInfo.cs @@ -0,0 +1,14 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("XAML Map Control Image Support (WPF)")] +[assembly: AssemblyDescription("Image Support Library for XAML Map Control")] +[assembly: AssemblyProduct("XAML Map Control")] +[assembly: AssemblyCompany("Clemens Fischer")] +[assembly: AssemblyCopyright("© 2018 Clemens Fischer")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyVersion("4.12.0")] +[assembly: AssemblyFileVersion("4.12.0")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] diff --git a/MapProjections/UWP/MapProjections.UWP.csproj b/MapProjections/UWP/MapProjections.UWP.csproj index 31b1daa8..6424f1c2 100644 --- a/MapProjections/UWP/MapProjections.UWP.csproj +++ b/MapProjections/UWP/MapProjections.UWP.csproj @@ -63,9 +63,20 @@ MapControl.UWP + + + MapControl.snk + + 14.0 + + true + + + ..\..\MapControl.snk +