diff --git a/MapImages/Shared/WorldFile.cs b/MapImages/Shared/WorldFile.cs deleted file mode 100644 index a41b3f33..00000000 --- a/MapImages/Shared/WorldFile.cs +++ /dev/null @@ -1,143 +0,0 @@ -// 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; -using System.Threading.Tasks; -#if WINDOWS_UWP -using Windows.Foundation; -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 -using MapControl.Projections; - -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, Matrix transform, MapProjection projection = null) - { - image.Source = bitmapSource; - image.Stretch = Stretch.Fill; - - var boundingBox = GetBoundingBox(bitmapSource.PixelWidth, bitmapSource.PixelHeight, transform, projection); - - MapPanel.SetBoundingBox(image, boundingBox); - } - - public static BoundingBox GetBoundingBox(double imageWidth, double imageHeight, Matrix transform, MapProjection projection = null) - { - if (transform.M12 != 0d || transform.M21 != 0d) - { - throw new ArgumentException("Invalid Matrix, M12 and M21 must be zero."); - } - - var rect = new Rect( - transform.Transform(new Point()), - transform.Transform(new Point(imageWidth, imageHeight))); - - 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 - }; - } - - public static Matrix ReadWorldFile(string path) - { - if (!File.Exists(path)) - { - throw new ArgumentException("World file \"" + path + "\"not found."); - } - - var parameters = File.ReadLines(path).Take(6).Select((line, i) => - { - double p; - if (!double.TryParse(line, NumberStyles.Float, CultureInfo.InvariantCulture, out p)) - { - throw new ArgumentException("Failed parsing line " + (i + 1) + " in world file \"" + path + "\"."); - } - return p; - }) - .ToList(); - - if (parameters.Count != 6) - { - throw new ArgumentException("Insufficient number of parameters in world file \"" + path + "\"."); - } - - return new Matrix( - parameters[0], // line 1: A or M11 - parameters[1], // line 2: D or M12 - parameters[2], // line 3: B or M21 - parameters[3], // line 4: E or M22 - parameters[4], // line 5: C or OffsetX - parameters[5]); // line 6: F or OffsetY - } - - 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 transform = ReadWorldFile(worldFilePath); - MapProjection projection = null; - - if (File.Exists(projFilePath)) - { - projection = new GeoApiProjection { WKT = File.ReadAllText(projFilePath) }; - } - - SetWorldImage(image, bitmap, transform, projection); - } - } -} diff --git a/MapImages/Shared/WorldFileImage.cs b/MapImages/Shared/WorldFileImage.cs new file mode 100644 index 00000000..f3411615 --- /dev/null +++ b/MapImages/Shared/WorldFileImage.cs @@ -0,0 +1,193 @@ +// 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; +using System.Threading.Tasks; +#if WINDOWS_UWP +using Windows.Foundation; +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 +using MapControl.Projections; + +namespace MapControl.Images +{ + public class WorldFileImage + { + public static readonly DependencyProperty PathProperty = DependencyProperty.RegisterAttached( + "Path", typeof(string), typeof(WorldFileImage), + new PropertyMetadata(null, async (o, e) => (await ReadWorldFileImage((string)e.NewValue)).SetImage((Image)o))); + + public static string GetPath(Image image) + { + return (string)image.GetValue(PathProperty); + } + + public static void SetPath(Image image, string path) + { + image.SetValue(PathProperty, path); + } + + public BitmapSource Bitmap { get; } + public Matrix Transform { get; } + public MapProjection Projection { get; } + public BoundingBox BoundingBox { get; } + public double Rotation { get; } + + public WorldFileImage(BitmapSource bitmap, Matrix transform, MapProjection projection) + { + Bitmap = bitmap; + Transform = transform; + Projection = projection; + + if (transform.M12 != 0 || transform.M21 != 0) + { + Rotation = (Math.Atan2(transform.M12, transform.M11) + Math.Atan2(transform.M21, -transform.M22)) * 90d / Math.PI; + + // effective unrotated transform + transform.M11 = Math.Sqrt(transform.M11 * transform.M11 + transform.M12 * transform.M12); + transform.M22 = -Math.Sqrt(transform.M22 * transform.M22 + transform.M21 * transform.M21); + transform.M12 = 0; + transform.M21 = 0; + } + + var rect = new Rect( + transform.Transform(new Point()), + transform.Transform(new Point(bitmap.PixelWidth, bitmap.PixelHeight))); + + BoundingBox = projection != null + ? projection.RectToBoundingBox(rect) + : new BoundingBox + { + West = rect.X, + East = rect.X + rect.Width, + South = rect.Y, + North = rect.Y + rect.Height + }; + } + + public static async Task ReadWorldFileImage(string imagePath, string worldFilePath, string projFilePath = null) + { + 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 transform = ReadWorldFile(worldFilePath); + MapProjection projection = null; + + if (projFilePath != null && File.Exists(projFilePath)) + { + projection = new GeoApiProjection { WKT = File.ReadAllText(projFilePath) }; + } + + return new WorldFileImage(bitmap, transform, projection); + } + + public static Task ReadWorldFileImage(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."); + } + + 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"); + + return ReadWorldFileImage(imagePath, worldFilePath, projFilePath); + } + + public static Matrix ReadWorldFile(string path) + { + if (!File.Exists(path)) + { + throw new ArgumentException("World file \"" + path + "\"not found."); + } + + var parameters = File.ReadLines(path).Take(6).Select((line, i) => + { + double p; + if (!double.TryParse(line, NumberStyles.Float, CultureInfo.InvariantCulture, out p)) + { + throw new ArgumentException("Failed parsing line " + (i + 1) + " in world file \"" + path + "\"."); + } + return p; + }) + .ToList(); + + if (parameters.Count != 6) + { + throw new ArgumentException("Insufficient number of parameters in world file \"" + path + "\"."); + } + + return new Matrix( + parameters[0], // line 1: A or M11 + parameters[1], // line 2: D or M12 + parameters[2], // line 3: B or M21 + parameters[3], // line 4: E or M22 + parameters[4], // line 5: C or OffsetX + parameters[5]); // line 6: F or OffsetY + } + + public static MapProjection ReadProjFile(string path) + { + return new GeoApiProjection { WKT = File.ReadAllText(path) }; + } + + public void SetImage(Image image) + { + image.Source = Bitmap; + image.Stretch = Stretch.Fill; + + MapPanel.SetBoundingBox(image, BoundingBox); + } + + public FrameworkElement CreateImage() + { + 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, BoundingBox); + return image; + } + + /// + /// Experimental + /// + public static async Task CreateImage(string imagePath) + { + return (await ReadWorldFileImage(imagePath)).CreateImage(); + } + } +} + diff --git a/MapImages/UWP/MapImages.UWP.csproj b/MapImages/UWP/MapImages.UWP.csproj index 1225f14b..4ec22988 100644 --- a/MapImages/UWP/MapImages.UWP.csproj +++ b/MapImages/UWP/MapImages.UWP.csproj @@ -43,8 +43,8 @@ GroundOverlayPanel.cs - - WorldFile.cs + + WorldFileImage.cs ZoomLevelToOpacityConverter.cs diff --git a/MapImages/WPF/MapImages.WPF.csproj b/MapImages/WPF/MapImages.WPF.csproj index 71f29470..769f06a0 100644 --- a/MapImages/WPF/MapImages.WPF.csproj +++ b/MapImages/WPF/MapImages.WPF.csproj @@ -50,8 +50,8 @@ GroundOverlayPanel.cs - - WorldFile.cs + + WorldFileImage.cs ZoomLevelToOpacityConverter.cs