diff --git a/MapControl/Avalonia/DependencyPropertyHelper.Avalonia.cs b/MapControl/Avalonia/DependencyPropertyHelper.Avalonia.cs index 2a1f9e2d..60e13cf0 100644 --- a/MapControl/Avalonia/DependencyPropertyHelper.Avalonia.cs +++ b/MapControl/Avalonia/DependencyPropertyHelper.Avalonia.cs @@ -10,6 +10,23 @@ namespace MapControl { public static class DependencyPropertyHelper { + public static AttachedProperty RegisterAttached( + string name, + Type ownerType, + TValue defaultValue = default, + Action changed = null, + bool inherits = false) + { + var property = AvaloniaProperty.RegisterAttached(name, ownerType, defaultValue, inherits); + + if (changed != null) + { + property.Changed.AddClassHandler((o, e) => changed(o, e.OldValue.Value, e.NewValue.Value)); + } + + return property; + } + public static StyledProperty Register( string name, TValue defaultValue = default, @@ -38,22 +55,6 @@ namespace MapControl return property; } - public static AttachedProperty RegisterAttached( - string name, - TValue defaultValue = default, - Action changed = null, - bool inherits = false) - { - var property = AvaloniaProperty.RegisterAttached(name, defaultValue, inherits); - - if (changed != null) - { - property.Changed.AddClassHandler((o, e) => changed(o, e.OldValue.Value, e.NewValue.Value)); - } - - return property; - } - public static StyledProperty AddOwner( StyledProperty property, TValue defaultValue = default, diff --git a/MapControl/Avalonia/GeoImage.Avalonia.cs b/MapControl/Avalonia/GeoImage.Avalonia.cs index 09f636c8..d166ad8b 100644 --- a/MapControl/Avalonia/GeoImage.Avalonia.cs +++ b/MapControl/Avalonia/GeoImage.Avalonia.cs @@ -7,13 +7,16 @@ using System.Threading.Tasks; namespace MapControl { - public partial class GeoImage + public static partial class GeoImage { - private Point BitmapSize => new(bitmapSource.PixelSize.Width, bitmapSource.PixelSize.Height); + private partial class GeoBitmap + { + public Point BitmapSize => new(BitmapSource.PixelSize.Width, BitmapSource.PixelSize.Height); - private ImageBrush ImageBrush => new(bitmapSource); + public ImageBrush ImageBrush => new(BitmapSource); + } - private Task LoadGeoTiffAsync(string sourcePath) + private static Task LoadGeoTiffAsync(string sourcePath) { throw new InvalidOperationException("GeoTIFF is not supported."); } diff --git a/MapControl/Avalonia/MapPanel.Avalonia.cs b/MapControl/Avalonia/MapPanel.Avalonia.cs index f126f16c..c2cf37cd 100644 --- a/MapControl/Avalonia/MapPanel.Avalonia.cs +++ b/MapControl/Avalonia/MapPanel.Avalonia.cs @@ -9,13 +9,13 @@ namespace MapControl public partial class MapPanel { public static readonly AttachedProperty AutoCollapseProperty = - DependencyPropertyHelper.RegisterAttached("AutoCollapse"); + DependencyPropertyHelper.RegisterAttached("AutoCollapse", typeof(MapPanel)); public static readonly AttachedProperty LocationProperty = - DependencyPropertyHelper.RegisterAttached("Location"); + DependencyPropertyHelper.RegisterAttached("Location", typeof(MapPanel)); public static readonly AttachedProperty BoundingBoxProperty = - DependencyPropertyHelper.RegisterAttached("BoundingBox"); + DependencyPropertyHelper.RegisterAttached("BoundingBox", typeof(MapPanel)); protected IEnumerable ChildElements => Children; diff --git a/MapControl/Shared/GeoImage.cs b/MapControl/Shared/GeoImage.cs index ae7526af..ef382545 100644 --- a/MapControl/Shared/GeoImage.cs +++ b/MapControl/Shared/GeoImage.cs @@ -6,7 +6,6 @@ using System; using System.Diagnostics; using System.Globalization; using System.IO; -using System.Linq; using System.Threading.Tasks; #if WPF using System.Windows; @@ -32,8 +31,26 @@ using Shape = Avalonia.Controls.Shapes.Shape; namespace MapControl { - public partial class GeoImage + public static partial class GeoImage { + private partial class GeoBitmap + { + public BitmapSource BitmapSource { get; } + public LatLonBox LatLonBox { get; } + + public GeoBitmap(BitmapSource bitmapSource, Matrix transform, MapProjection projection) + { + BitmapSource = bitmapSource; + + var p1 = transform.Transform(new Point()); + var p2 = transform.Transform(BitmapSize); + + LatLonBox = projection != null + ? new LatLonBox(projection.MapToBoundingBox(new Rect(p1, p2))) + : new LatLonBox(p1.Y, p1.X, p2.Y, p2.X); + } + } + private const ushort ProjectedCRSGeoKey = 3072; private const ushort GeoKeyDirectoryTag = 34735; private const ushort ModelPixelScaleTag = 33550; @@ -43,13 +60,9 @@ namespace MapControl private static string QueryString(ushort tag) => $"/ifd/{{ushort={tag}}}"; - private BitmapSource bitmapSource; - private Matrix transformMatrix; - private MapProjection mapProjection; - public static readonly DependencyProperty SourcePathProperty = - DependencyPropertyHelper.RegisterAttached("SourcePath", null, - async (image, oldValue, newValue) => await LoadGeoImageAsync(image, newValue)); + DependencyPropertyHelper.RegisterAttached("SourcePath", typeof(GeoImage), null, + async (element, oldValue, newValue) => await LoadGeoImageAsync(element, newValue)); public static string GetSourcePath(FrameworkElement image) { @@ -63,40 +76,33 @@ namespace MapControl public static Image LoadGeoImage(string sourcePath) { - var image = new Image { Stretch = Stretch.Fill }; + var image = new Image(); SetSourcePath(image, sourcePath); return image; } - private static async Task LoadGeoImageAsync(FrameworkElement element, string sourcePath) + public static async Task LoadGeoImageAsync(this FrameworkElement element, string sourcePath) { if (!string.IsNullOrEmpty(sourcePath)) { try { - var geoImage = new GeoImage(); - - await geoImage.LoadGeoImageAsync(sourcePath); - - var p1 = geoImage.transformMatrix.Transform(new Point()); - var p2 = geoImage.transformMatrix.Transform(geoImage.BitmapSize); - - var latLonBox = geoImage.mapProjection != null - ? new LatLonBox(geoImage.mapProjection.MapToBoundingBox(new Rect(p1, p2))) - : new LatLonBox(p1.Y, p1.X, p2.Y, p2.X); + var geoBitmap = await LoadGeoBitmapAsync(sourcePath); if (element is Image image) { - image.Source = geoImage.bitmapSource; + image.Source = geoBitmap.BitmapSource; + image.Stretch = Stretch.Fill; } else if (element is Shape shape) { - shape.Fill = geoImage.ImageBrush; + shape.Fill = geoBitmap.ImageBrush; + shape.Stretch = Stretch.Fill; } - MapPanel.SetBoundingBox(element, latLonBox); + MapPanel.SetBoundingBox(element, geoBitmap.LatLonBox); } catch (Exception ex) { @@ -105,7 +111,7 @@ namespace MapControl } } - private async Task LoadGeoImageAsync(string sourcePath) + private static async Task LoadGeoBitmapAsync(string sourcePath) { var ext = Path.GetExtension(sourcePath); @@ -117,47 +123,48 @@ namespace MapControl if (File.Exists(worldFilePath)) { - await LoadWorldFileImageAsync(sourcePath, worldFilePath); + return new GeoBitmap( + (BitmapSource)await ImageLoader.LoadImageAsync(sourcePath), + await ReadWorldFileMatrix(worldFilePath), + null); } } - if (bitmapSource == null) + return await LoadGeoTiffAsync(sourcePath); + } + + private static async Task ReadWorldFileMatrix(string worldFilePath) + { + using (var fileStream = File.OpenRead(worldFilePath)) + using (var streamReader = new StreamReader(fileStream)) { - await LoadGeoTiffAsync(sourcePath); + var parameters = new double[6]; + var index = 0; + string line; + + while (index < 6 && + (line = await streamReader.ReadLineAsync()) != null && + double.TryParse(line, NumberStyles.Float, CultureInfo.InvariantCulture, out double parameter)) + { + parameters[index++] = parameter; + } + + if (index != 6) + { + throw new ArgumentException($"Insufficient number of parameters in world file {worldFilePath}."); + } + + 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 async Task LoadWorldFileImageAsync(string sourcePath, string worldFilePath) - { - transformMatrix = await Task.Run(() => ReadWorldFileMatrix(worldFilePath)); - - bitmapSource = (BitmapSource)await ImageLoader.LoadImageAsync(sourcePath); - } - - private static Matrix ReadWorldFileMatrix(string worldFilePath) - { - var parameters = File.ReadLines(worldFilePath) - .Select(line => double.TryParse(line, NumberStyles.Float, CultureInfo.InvariantCulture, out double p) ? (double?)p : null) - .Where(p => p.HasValue) - .Select(p => p.Value) - .Take(6) - .ToList(); - - if (parameters.Count != 6) - { - throw new ArgumentException($"Insufficient number of parameters in world file {worldFilePath}."); - } - - 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 void SetProjection(short[] geoKeyDirectory) + private static MapProjection GetProjection(short[] geoKeyDirectory) { for (var i = 4; i < geoKeyDirectory.Length - 3; i += 4) { @@ -165,9 +172,11 @@ namespace MapControl { var epsgCode = geoKeyDirectory[i + 3]; - mapProjection = MapProjectionFactory.Instance.GetProjection($"EPSG:{epsgCode}"); + return MapProjectionFactory.Instance.GetProjection($"EPSG:{epsgCode}"); } } + + return null; } } } diff --git a/MapControl/Shared/MapBorderPanel.cs b/MapControl/Shared/MapBorderPanel.cs index 957a6f75..30d63bf3 100644 --- a/MapControl/Shared/MapBorderPanel.cs +++ b/MapControl/Shared/MapBorderPanel.cs @@ -24,7 +24,7 @@ namespace MapControl DependencyPropertyHelper.Register(nameof(BorderWidth)); public static readonly DependencyProperty OnBorderProperty = - DependencyPropertyHelper.RegisterAttached("OnBorder"); + DependencyPropertyHelper.RegisterAttached("OnBorder", typeof(MapBorderPanel)); public double BorderWidth { diff --git a/MapControl/Shared/MapPanel.cs b/MapControl/Shared/MapPanel.cs index 226935a8..041fd6cd 100644 --- a/MapControl/Shared/MapPanel.cs +++ b/MapControl/Shared/MapPanel.cs @@ -37,10 +37,10 @@ namespace MapControl public partial class MapPanel : Panel, IMapElement { private static readonly DependencyProperty ViewPositionProperty = - DependencyPropertyHelper.RegisterAttached("ViewPosition"); + DependencyPropertyHelper.RegisterAttached("ViewPosition", typeof(MapPanel)); private static readonly DependencyProperty ParentMapProperty = - DependencyPropertyHelper.RegisterAttached("ParentMap", null, + DependencyPropertyHelper.RegisterAttached("ParentMap", typeof(MapPanel), null, (element, oldValue, newValue) => { if (element is IMapElement mapElement) diff --git a/MapControl/WPF/DependencyPropertyHelper.WPF.cs b/MapControl/WPF/DependencyPropertyHelper.WPF.cs index 23b208d7..e606e916 100644 --- a/MapControl/WPF/DependencyPropertyHelper.WPF.cs +++ b/MapControl/WPF/DependencyPropertyHelper.WPF.cs @@ -9,6 +9,44 @@ namespace MapControl { public static class DependencyPropertyHelper { + public static DependencyProperty RegisterAttached( + string name, + Type ownerType, + TValue defaultValue = default, + Action changed = null, + bool inherits = false) + { + var metadata = new FrameworkPropertyMetadata + { + DefaultValue = defaultValue, + Inherits = inherits + }; + + if (changed != null) + { + metadata.PropertyChangedCallback = (o, e) => + { + if (o is FrameworkElement element) + { + changed(element, (TValue)e.OldValue, (TValue)e.NewValue); + } + }; + } + + return DependencyProperty.RegisterAttached(name, typeof(TValue), ownerType, metadata); + } + + public static DependencyProperty RegisterAttached( + string name, + Type ownerType, + TValue defaultValue, + FrameworkPropertyMetadataOptions options) + { + var metadata = new FrameworkPropertyMetadata(defaultValue, options); + + return DependencyProperty.RegisterAttached(name, typeof(TValue), ownerType, metadata); + } + public static DependencyProperty Register( string name, TValue defaultValue, @@ -47,42 +85,6 @@ namespace MapControl return DependencyProperty.Register(name, typeof(TValue), typeof(TOwner), metadata); } - public static DependencyProperty RegisterAttached( - string name, - TValue defaultValue, - FrameworkPropertyMetadataOptions options) - { - var metadata = new FrameworkPropertyMetadata(defaultValue, options); - - return DependencyProperty.RegisterAttached(name, typeof(TValue), typeof(TOwner), metadata); - } - - public static DependencyProperty RegisterAttached( - string name, - TValue defaultValue = default, - Action changed = null, - bool inherits = false) - { - var metadata = new FrameworkPropertyMetadata - { - DefaultValue = defaultValue, - Inherits = inherits - }; - - if (changed != null) - { - metadata.PropertyChangedCallback = (o, e) => - { - if (o is FrameworkElement element) - { - changed(element, (TValue)e.OldValue, (TValue)e.NewValue); - } - }; - } - - return DependencyProperty.RegisterAttached(name, typeof(TValue), typeof(TOwner), metadata); - } - public static DependencyPropertyKey RegisterReadOnly( string name, TValue defaultValue = default) diff --git a/MapControl/WPF/GeoImage.WPF.cs b/MapControl/WPF/GeoImage.WPF.cs index daf30957..6447a1d4 100644 --- a/MapControl/WPF/GeoImage.WPF.cs +++ b/MapControl/WPF/GeoImage.WPF.cs @@ -12,16 +12,23 @@ using System.Windows.Media.Imaging; namespace MapControl { - public partial class GeoImage + public static partial class GeoImage { - private Point BitmapSize => new Point(bitmapSource.PixelWidth, bitmapSource.PixelHeight); + private partial class GeoBitmap + { + public Point BitmapSize => new Point(BitmapSource.PixelWidth, BitmapSource.PixelHeight); - private ImageBrush ImageBrush => new ImageBrush(bitmapSource); + public ImageBrush ImageBrush => new ImageBrush(BitmapSource); + } - private Task LoadGeoTiffAsync(string sourcePath) + private static Task LoadGeoTiffAsync(string sourcePath) { return Task.Run(() => { + BitmapSource bitmapSource; + Matrix transformMatrix; + MapProjection mapProjection = null; + using (var stream = File.OpenRead(sourcePath)) { bitmapSource = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); @@ -50,7 +57,7 @@ namespace MapControl if (metadata.GetQuery(QueryString(GeoKeyDirectoryTag)) is short[] geoKeyDirectory) { - SetProjection(geoKeyDirectory); + mapProjection = GetProjection(geoKeyDirectory); } if (metadata.GetQuery(QueryString(NoDataTag)) is string noData && @@ -58,6 +65,8 @@ namespace MapControl { bitmapSource = ConvertTransparentPixel(bitmapSource, noDataValue); } + + return new GeoBitmap(bitmapSource, transformMatrix, mapProjection); }); } diff --git a/MapControl/WPF/MapPanel.WPF.cs b/MapControl/WPF/MapPanel.WPF.cs index 636bcd62..c6d3e212 100644 --- a/MapControl/WPF/MapPanel.WPF.cs +++ b/MapControl/WPF/MapPanel.WPF.cs @@ -12,14 +12,14 @@ namespace MapControl public partial class MapPanel { public static readonly DependencyProperty AutoCollapseProperty = - DependencyPropertyHelper.RegisterAttached("AutoCollapse"); + DependencyPropertyHelper.RegisterAttached< bool>("AutoCollapse", typeof(MapPanel)); public static readonly DependencyProperty LocationProperty = - DependencyPropertyHelper.RegisterAttached("Location", null, + DependencyPropertyHelper.RegisterAttached("Location", typeof(MapPanel), null, FrameworkPropertyMetadataOptions.AffectsParentArrange); public static readonly DependencyProperty BoundingBoxProperty = - DependencyPropertyHelper.RegisterAttached("BoundingBox", null, + DependencyPropertyHelper.RegisterAttached("BoundingBox", typeof(MapPanel), null, FrameworkPropertyMetadataOptions.AffectsParentArrange); protected IEnumerable ChildElements => InternalChildren.OfType(); diff --git a/MapControl/WinUI/DependencyPropertyHelper.WinUI.cs b/MapControl/WinUI/DependencyPropertyHelper.WinUI.cs index 486de731..108a2c42 100644 --- a/MapControl/WinUI/DependencyPropertyHelper.WinUI.cs +++ b/MapControl/WinUI/DependencyPropertyHelper.WinUI.cs @@ -15,21 +15,9 @@ namespace MapControl { public static class DependencyPropertyHelper { - public static DependencyProperty Register( - string name, - TValue defaultValue = default, - Action changed = null) - where TOwner : DependencyObject - { - var metadata = changed != null - ? new PropertyMetadata(defaultValue, (o, e) => changed((TOwner)o, (TValue)e.OldValue, (TValue)e.NewValue)) - : new PropertyMetadata(defaultValue); - - return DependencyProperty.Register(name, typeof(TValue), typeof(TOwner), metadata); - } - - public static DependencyProperty RegisterAttached( + public static DependencyProperty RegisterAttached( string name, + Type ownerType, TValue defaultValue = default, Action changed = null, bool inherits = false) // unused in WinUI/UWP @@ -44,7 +32,20 @@ namespace MapControl } }); - return DependencyProperty.RegisterAttached(name, typeof(TValue), typeof(TOwner), metadata); + return DependencyProperty.RegisterAttached(name, typeof(TValue), ownerType, metadata); + } + + public static DependencyProperty Register( + string name, + TValue defaultValue = default, + Action changed = null) + where TOwner : DependencyObject + { + var metadata = changed != null + ? new PropertyMetadata(defaultValue, (o, e) => changed((TOwner)o, (TValue)e.OldValue, (TValue)e.NewValue)) + : new PropertyMetadata(defaultValue); + + return DependencyProperty.Register(name, typeof(TValue), typeof(TOwner), metadata); } } } diff --git a/MapControl/WinUI/GeoImage.WinUI.cs b/MapControl/WinUI/GeoImage.WinUI.cs index f3bedd78..6d0781c9 100644 --- a/MapControl/WinUI/GeoImage.WinUI.cs +++ b/MapControl/WinUI/GeoImage.WinUI.cs @@ -8,20 +8,29 @@ using Windows.Graphics.Imaging; using Windows.Storage; #if UWP using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Media.Imaging; #else using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Imaging; #endif namespace MapControl { - public partial class GeoImage + public static partial class GeoImage { - private Point BitmapSize => new Point(bitmapSource.PixelWidth, bitmapSource.PixelHeight); - - private ImageBrush ImageBrush => new ImageBrush { ImageSource = bitmapSource }; - - private async Task LoadGeoTiffAsync(string sourcePath) + private partial class GeoBitmap { + public Point BitmapSize => new Point(BitmapSource.PixelWidth, BitmapSource.PixelHeight); + + public ImageBrush ImageBrush => new ImageBrush { ImageSource = BitmapSource }; + } + + private static async Task LoadGeoTiffAsync(string sourcePath) + { + BitmapSource bitmapSource; + Matrix transformMatrix; + MapProjection mapProjection = null; + var file = await StorageFile.GetFileFromPathAsync(FilePath.GetFullPath(sourcePath)); using (var stream = await file.OpenReadAsync()) @@ -68,9 +77,11 @@ namespace MapControl if (metadata.TryGetValue(geoKeyDirectoryQuery, out BitmapTypedValue geoKeyDirValue) && geoKeyDirValue.Value is short[] geoKeyDirectory) { - SetProjection(geoKeyDirectory); + mapProjection = GetProjection(geoKeyDirectory); } } + + return new GeoBitmap(bitmapSource, transformMatrix, mapProjection); } } } diff --git a/MapControl/WinUI/MapPanel.WinUI.cs b/MapControl/WinUI/MapPanel.WinUI.cs index 31bdecd1..83084109 100644 --- a/MapControl/WinUI/MapPanel.WinUI.cs +++ b/MapControl/WinUI/MapPanel.WinUI.cs @@ -17,14 +17,14 @@ namespace MapControl public partial class MapPanel { public static readonly DependencyProperty AutoCollapseProperty = - DependencyPropertyHelper.RegisterAttached("AutoCollapse"); + DependencyPropertyHelper.RegisterAttached("AutoCollapse", typeof(MapPanel)); public static readonly DependencyProperty LocationProperty = - DependencyPropertyHelper.RegisterAttached("Location", null, + DependencyPropertyHelper.RegisterAttached("Location", typeof(MapPanel), null, (element, oldValue, newValue) => (element.Parent as MapPanel)?.InvalidateArrange()); public static readonly DependencyProperty BoundingBoxProperty = - DependencyPropertyHelper.RegisterAttached("BoundingBox", null, + DependencyPropertyHelper.RegisterAttached("BoundingBox", typeof(MapPanel), null, (element, oldValue, newValue) => (element.Parent as MapPanel)?.InvalidateArrange()); protected IEnumerable ChildElements => Children.OfType();